數(shù)組在所有的語言當(dāng)中都是一種常見類型。在Solidity中,可以支持編譯期定長數(shù)組和變長數(shù)組。一個類型為T,長度為k的數(shù)組,可以聲明為T[k],而一個變長的數(shù)組則聲明為T[]。
1. 創(chuàng)建一個數(shù)組
1.1 字面量
創(chuàng)建數(shù)組時,我們可以使用字面量,隱式創(chuàng)建一個定長數(shù)組。
pragma solidity ^0.4.0;
contract ArrayLiteral{
function arrayLiteral(){
uint[3] memory a = [uint(1), 2, 3];
//長度必須匹配
//Type string memory[1] memory is not implicitly convertible to expected type string memory[2] memory.
//string[2] memory b = ["a"];
}
}
通過上面的代碼,我們可以發(fā)現(xiàn)。
首先元素類型是剛好能存儲的元素的類型,比如代碼里的[1, 2, 3],只需要uint8即可存儲。但由于我們聲明的變量是uint(默認(rèn)的uint表示的其實是uint256),所以要使用uint(1)來進(jìn)行顯式的類型轉(zhuǎn)換。
其次,字面量方式聲明的數(shù)組是定長的,且實際長度要與聲明的相匹配,否則編譯器會報錯Type string memory[1] memory is not implicitly convertible to expected type string memory[2] memory。
1.2 new關(guān)鍵字
對于變長數(shù)組,在初始化分配空間前不可使用,可以通過new關(guān)鍵字來初始化一個數(shù)組。
pragma solidity ^0.4.0;
contract NewArray{
uint[] stateVar;
function f(){
//定義一個變長數(shù)組
uint[] memory memVar;
//不能在使用new初始化以前使用
//VM Exception: invalid opcode
//memVar [0] = 100;
//通過new初始化一個memory的變長數(shù)組
memVar = new uint[](2);
//不能在使用new初始化以前使用
//VM Exception: invalid opcode
//stateVar[0] = 1;
//通過new初始化一個storage的變長數(shù)組
stateVar = new uint[](2);
stateVar[0] = 1;
}
}
在上面的例子中,我們聲明了一個storage的stateVar,和一個memory的memVar。它們不能在使用new關(guān)鍵字初始化前使用下標(biāo)方式訪問,會報錯VM Exception: invalid opcode??梢愿鶕?jù)情況使用如例子中的new uint[](2);來進(jìn)行初始化。
2. 數(shù)據(jù)的屬性和方法
2.1 length屬性
數(shù)組有一個length屬性,表示當(dāng)前的數(shù)組長度。對于storage的變長數(shù)組,可以通過給length賦值調(diào)整數(shù)組長度。
2.1.1 storage
我們來看一個自增長數(shù)組的例子。
pragma solidity ^0.4.0;
contract AutoExtendArray{
uint[] stateVar = new uint[](1);
function autoExendArray(uint a) returns (uint){
//stateVar.length++語句會修改數(shù)組的長度加1
stateVar[stateVar.length++] = a;
return stateVar[stateVar.length - 1];
}
}
在上面這個例子中,我們可以看到,通過stateVar.length++語句對數(shù)組長度進(jìn)行自增,我們就得到了一個不斷變長的數(shù)組。
還可以使用后面提到的push()方法,來隱式的調(diào)整數(shù)組長度。
不能通過對超出當(dāng)前數(shù)組的長度序號元素賦值的方式,來實現(xiàn)數(shù)組長度的自動擴(kuò)展。
2.1.2 memory
對于memory的變長數(shù)組,不支持修改length屬性,來調(diào)整數(shù)組大小。memory的變長數(shù)組雖然可以通過參數(shù)靈活指定大小,但一旦創(chuàng)建,大小不可調(diào)整。
2.2 push方法
變長的storage數(shù)組和bytes(不包括string)有一個push()方法??梢詫⒁粋€新元素附加到數(shù)組末端,返回值為當(dāng)前長度。
pragma solidity ^0.4.0;
contract NewArray{
uint[] stateVar;
function f() returns (uint){
//在元素初始化前使用
stateVar.push(1);
stateVar = new uint[](1);
stateVar[0] = 0;
//自動擴(kuò)充長度
uint len = stateVar.push(1);
//不支持memory
//Member "push" is not available in uint256[] memory outside of storage.
//uint[] memory memVar = new uint[](1);
//memVar.push(1);
return len;
}
}
在上面的代碼中,我們聲明了一個storage的stateVar,在未顯式初始化的情況下,通過push()附加了一個新值,方法內(nèi)進(jìn)行了初始化。后面的代碼中,我們通過stateVar = new uint[](1);顯示分配了存儲空間為1,使用push()能實現(xiàn)存儲空間的自動擴(kuò)展。另外,我們發(fā)現(xiàn),memory的變長數(shù)組不支持push()。
2.3 下標(biāo)
與大多數(shù)語言一樣,數(shù)組可以通過數(shù)字下標(biāo)訪問,從0開始。對于大小為2的數(shù)組T[2],要訪問第二個元素,要使用下標(biāo)值1。
如果狀態(tài)變量的類型為數(shù)組,也可以標(biāo)記為public類型,從而讓Solidity創(chuàng)建一個訪問器。
pragma solidity ^0.4.0;
contract ArrayPublic{
uint[] public stateVar = [uint(1)];
}
如上面的合約在Remix運行后,需要我們填入的是一個要訪問序號的數(shù)字,來訪問具體某個元素。
3. 多維數(shù)組
多維數(shù)據(jù)的定義與非區(qū)塊鏈語言類似。如,我們要創(chuàng)建一個長度為5的uint數(shù)組,每個元素又是一個變長數(shù)組。將被聲明為uint[][5](注意,定義方式對比大多數(shù)語言來說是反的,使用下標(biāo)訪問元素時與其它語言一致)。
pragma solidity ^0.4.6;
contract ArrayMD{
//一個變長的數(shù)組,里面的每個元素是一個長度為2的數(shù)組。
//定義方式與常規(guī)語言相反
bool[2][] flags;
function appendFlag() returns(uint length) {
//添加一個新元素到二維數(shù)組中
return flags.push([true,true]);
}
function getFlag(uint dynamicIndex, uint lengthTwoIndex) constant returns(bool flag) {
//訪問數(shù)組,第一個為變長數(shù)組的序號,第二個為長度為2的數(shù)組序號
return flags[dynamicIndex][lengthTwoIndex];
}
}
在上面的代碼中,我們聲明了一個二維數(shù)組,它是一個變長的數(shù)組,里面的每個元素是一個長度為2的數(shù)組。要訪問這個數(shù)組flags,第一個下標(biāo)為變長數(shù)組的序號,第二個下標(biāo)為長度為2的數(shù)組序號。
4. bytes與String
bytes和string是一種特殊的數(shù)組。bytes類似byte[],但在外部函數(shù)作為參數(shù)調(diào)用中,會進(jìn)行壓縮打包,更省空間,所以應(yīng)該盡量使用bytes[1]。string類似bytes,但不提供長度和按序號的訪問方式。
由于bytes與string,可以自由轉(zhuǎn)換,你可以將字符串s通過bytes(s)轉(zhuǎn)為一個bytes。但需要注意的是通過這種方式訪問到的是UTF-8編碼的碼流,并不是獨立的一個個字符。比如中文編碼是多字節(jié),變長的,所以你訪問到的很有可能只是其中的一個代碼點。
另外bytes支持push()方法,參考2.2節(jié)說明。
5. 限制
當(dāng)前在外部函數(shù)中,不能使用多維數(shù)組。另外,因為EVM的限制,不能通過外部函數(shù)返回變長的數(shù)據(jù)。
pragma solidity ^0.4.0;
contract ArrayWarming {
function f() returns (uint[]) {
return new uint[](1);
}
//Return argument type inaccessible dynamic type is not implicitly convertible to expected type (type of first return variable) uint256[] memory.
/*
function g() returns (uint[]){
return this.f();
}
}
*/
在上面的例子中,f()返回的是uint[]的變長數(shù)組。使用web3.js的方式可以訪問得到結(jié)果,但使用Solidity的外部函數(shù)的訪問方式,將編譯不通過,提示Return argument type inaccessible dynamic type is not implicitly convertible to expected type。
對于這樣的問題的一種臨時的解決辦法,是使用一個非常大的定長數(shù)組。
pragma solidity ^0.4.0;
contract ArrayBig {
function f() returns (uint[1]) {
//構(gòu)造一個超級大數(shù)組,來拼裝你想返回的結(jié)果
return [uint(1)];
}
function g() returns (uint[1]){
return this.f();
}
}
關(guān)于作者
專注基于以太坊的相關(guān)區(qū)塊鏈技術(shù),了解以太坊,Solidity,Truffle。
博客:http://me.tryblockchain.org
參考資料
-
因為字節(jié)數(shù)組在使用時會padding,所以不節(jié)省空間。https://github.com/chriseth/solidity/blob/48588b5c4f21c8bb7c5d4319b05a93ee995b457d/_docs/faq_basic.md#what-is-the-difference-between-bytes-and-byte ?