【鏈塊技術42期】智能合約基礎語言(六)——Solidity變量類型:其他

智能合約基礎語言(六):Solidity變量類型:其他

一、目錄

☞映射

☞特殊的運算符delete

☞基本類間的轉換

二、變量類型——映射

映射或字典類型,一種鍵值對的映射關係存儲結構。定義方式爲mapping(_KeyType => _KeyValue)。鍵的類型允許除映射外的所有類型,如數組,合約,枚舉,結構體。值的類型無限制。

映射可以被視作爲一個哈希表,其中所有可能的鍵已被虛擬化的創建,被映射到一個默認值(二進制表示的零)。但在映射表中,我們並不存儲鍵的數據,僅僅存儲它的keccak256哈希值,用來查找值時使用。

因此,映射並沒有長度,鍵集合(或列表),值集合(或列表)這樣的概念。

映射類型,僅能用來定義狀態變量,或者是在內部函數中作爲storage類型的引用。引用是指你可以聲明一個,如var storage mappVal的用於存儲狀態變量的引用的對象,但你沒辦法使用非狀態變量來初始化這個引用。

可以通過將映射標記爲public,來讓Solidity創建一個訪問器。要想訪問這樣的映射,需要提供一個鍵值做爲參數。如果映射的值類型也是映射,使用訪問器訪問時,要提供這個映射值所對應的鍵,不斷重複這個過程。

2.1 只能是狀態變量

由於在映射中鍵的數量是任意的,導致映射的大小也是變長的。映射只能聲明爲storage的狀態變量,或被賦值給一個storage的對象引用。我們來看下面的示例:

在上面的示例中,我們聲明瞭storage的狀態變量stateVar,可以對其增加新鍵值對;也能通過引用傳遞的方式賦值給storage的引用storageRef。

2.2 支持的類型

映射類型的鍵支持除映射,變長數組,合約,枚舉,結構體以外的任意類型。值則允許任意類型,甚至是映射。下面是一個簡單的例子代碼:

2.3 setter方法

對於映射類型,也能標記爲public。以讓Solidity爲我們自動生成訪問器。

在上面的例子中,如果要訪問intMapp第二個元素,在一對中括號中輸入值1即intMapp[1]。而如果要訪問嵌套的映射mapMapp[2][2],則輸入兩個鍵對應的值2,2即可。

2.4 getter方法

可以通過將映射標記爲public,來讓Solidity創建一個訪問器。

三、變量類型——特殊的運算符delete

Solidity中有個特殊的操作符delete用於釋放空間(特別是對於數組結構體以及映射類型的變量),因爲區塊鏈做爲一種公用資源,爲避免大家濫用。且鼓勵主動對空間的回收,釋放空間將會返還一些gas給調用者。

delete關鍵字的作用是對某個類型值a賦予初始值。比如如果刪除整數delete a等同於a = 0。

3.1 刪除基本類型

對於基本類型,使用delete會設置爲對應的初始值:

刪除bool類型是false,變長字節數組是0x0。string則是空串。

3.2 刪除枚舉

刪除枚舉類型時,會將其值重置爲序號爲0的值。

上面的例子中,刪除light後,light將被置爲序號爲0的值即RED。

3.3 刪除函數

嘗試了一下刪除函數,會報錯Error: Expression has to be an lvalue.,看來不能刪除函數。

3.4 刪除結構體

刪除一個結構體,會將其中的所有成員變量一一置爲初值,我們來看一個例子。

在上面的例子中,我們聲明瞭結構體s,調用delete s,結構體的值將變爲其對應類型uint,string,bytes的初始值0,空串和0x0。

3.5 刪除映射

映射是一個特殊的存在,由於映射的鍵並不總是能有效遍歷(數據結構沒有提供接口,也並不總是需要關心所有鍵是什麼),所存的鍵的數量往往是非常大的,所以我們並不能直接刪除一個映射。

如果直接刪除一個映射會報錯Unary operator delete cannot be applied 但我們可以指定鍵來刪除映射中的某一項:

 

3.6 刪除結構體中的映射

如果刪除一個結構體時,其中含有映射類型,會跳過映射類型。我們來看一個刪除含映射的結構體示例:

上面的示例中,刪除結構體ms,並沒有影響其中映射ms.m的值。

3.7 刪除數組

對於定長數組,刪除時,是將數組內所有元素置爲初值。

而對於變長數組時,則是將長度置爲0。

 

3.8 刪除數組的一個元素

我們也可以刪除數組的一個元素,有一點違反直覺的是,刪除一個元素後,數組會留個空隙在那裏。比如三個元素的數組,刪除了第二個元素,只是將第二個元素置爲了初始值,其它沒變。

上述的代碼運行後,將返回1,0,3。刪除只是賦值,並沒有移動元素。

3.9 gas使用的考慮

上文中,我們瞭解到,刪除時會忽略映射,以及數組的某個元素被刪除後,並不會自動整理數組。這些看起來很不符合常理,其實是基於對gas限制的考慮。因爲如果映射或數組非常大的情況下,刪除或維護它們將變得非常消耗gas。

不過,清理空間,可以獲得gas的返還。但無特別意義的數組的整理和刪除,只會消耗更多gas,需要在業務實現上進行權衡, 站在以太坊設計者的角度,因爲不清理空間會浪費資源, 而大量遍歷和刪除操作又會佔用cpu以及需要很多節點同步,因此節約空間和減少cpu的消耗都會獎勵,兩者需要找到一個平衡點才不至於資源浪費消耗太多gas。

3.10 清理的最佳實踐

由於本身並未提供對映射這樣的大對象的清理,所以存儲並遍歷它們來進行清理,顯得特別消耗gas。一種實踐就是能複用就複用,一般不主動清理。下面是一個數組的插入實現,比如增加一個計數器,直接忽略已使用過的位置。

上面的例子中,我們在數組新增時,直接忽略掉已使用過的槽位。而在代碼內,我們使用numElements來代替array.length,以獲取當前數組所在的位置。

如果這種大對象是在某個事件發生時,一次性使用,然後需要回收的。一個更有效的方式是,在發生某個事件時,創建一個新合約,在新合約完成邏輯,完成後,讓合約suicide。清理合約佔用空間返還的gas就退還給了調用者,來節省主動遍歷刪除消耗的額外gas。

3.11 刪除的注意事項

刪除本質是對一個變量賦初值。所以我們刪除storage的引用時會報錯,因爲storage的引用並沒有自己已分配的存儲空間,所以不能對storage的引用直接賦初值。

上面的例子中,刪除storageRef會報錯。

四、變量類型——基本類型間的轉換

4.1 隱式轉換

如果一個運算符能支持不同類型。編譯器會隱式的嘗試將一個操作數的類型,轉爲另一個操作數的類型,賦值同理。

一般來說,值類型間的互相轉換隻要不丟失信息,語義可通則可轉換。下面,我們來看一個整數轉換的例子:

上面的例子中,我們將一個uint8的變量a隱式的轉換爲了uint16。同理它還支持轉爲uint32,uint128和uint256。

另外,無符號整數可以被轉爲同樣,或更大的字節的類型。但需要注意的是,不能反過來轉換。由於address是20字節大小,所以它與int160大小是一樣。

4.2 顯式轉換

編譯器不會將語法上不可轉換的類型進行隱式轉換,此時我們要通過顯式轉換的方式,比如將一個有符號整數,轉爲一個無符號整數。

4.3 類型推斷

有時爲了方便,我們不會顯式定義類型。但由於編譯器,會自動挑選一個最恰當的類型,所以會常常留下坑,我們來看這個例子:

大家可以想想上述代碼運行的結果。

上述代碼運行的結果實際爲2100。原因是因爲var i = 0定義時,通過類型推斷,i的實際類型爲uint8,所以它會一直循環,如果沒有count >= 2100這個判斷語句,這個循環將永遠不會結束。

4.4 一些常見的轉換方案

4.4.1 uint轉爲bytes

assembly是可以用匯編的方式實現某功能。 將一個uint轉換成bytes,可以使用assembly。

上面的轉換方式可能是效率最高的方式。

4.4.2 string轉爲bytes

string可以顯式的轉爲bytes。但如果要轉爲bytes32,可能只能使用assembly。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章