Solidity内置支持元组(tuple)
,也就是说支持一个可能的完全不同类型组成的一个列表,数量上是固定的(Tuple一般指两个,还有个Triple一般指三个)。
这种内置结构可以同时返回多个结果,也可用于同时赋值给多个变量。
pragma solidity ^0.4.0;
contract C {
uint[] data;
function f() returns (uint, bool, uint) {
return (7, true, 2);
}
function g() {
// Declares and assigns the variables. Specifying the type explicitly is not possible.
var (x, b, y) = f();
// Assigns to a pre-existing variable.
(x, y) = (2, 7);
// Common trick to swap values -- does not work for non-value storage types.
(x, y) = (y, x);
// Components can be left out (also for variable declarations).
// If the tuple ends in an empty component,
// the rest of the values are discarded.
(data.length,) = f(); // Sets the length to 7
// The same can be done on the left side.
(,data[3]) = f(); // Sets data[3] to 2
// Components can only be left out at the left-hand-side of assignments, with
// one exception:
(x,) = (1,);
// (1,) is the only way to specify a 1-component tuple, because (1) is
// equivalent to 1.
}
}
对于非值类型,比如数组和数组,赋值的语法有一些复杂。
静态类型(static types)
,创建一份完全无关拷贝。bytes
和string
)类型,由状态变量赋值为一个局部变量,局部变量只是持有原始状态变量的一个引用。对这个局部变量再次赋值,并不会修改这个状态变量,只是修改了引用。但修改这个本地引用变量的成员值,会改变状态变量的值。表达式的求值顺序并不是确定的,更正式的说法是,表达式树一个节点的某个节点在求值时的顺序是不确定的,但是它肯定会比节点本身先执行。我们仅仅保证语句(statements)按顺序执行和布尔表达式的短路运算的支持。查看运算符的执行顺序了解更多。
一个合约可以通过new
关键字来创建一个合约。要创建合约的完整代码,必须提前知道,所以递归创建依赖是不可能的。
pragma solidity ^0.4.0;
contract Account{
uint accId;
//construction?
function Account(uint accountId) payable{
accId = accountId;
}
}
contract Initialize{
Account account = new Account(10);
function newAccount(uint accountId){
account = new Account(accountId);
}
function newAccountWithEther(uint accountId, uint amount){
account = (new Account).value(amount)(accountId);
}
}
从上面的例子可以看出来,可以在创建合约中,发送ether
,但不能限制gas的使用。如果创建因为out-of-stack
,或无足够的余额以及其它任何问题,会抛出一个异常。
在当前的合约中,函数可以直接调用(内部调用方式),包括也可递归调用,来看一个简单的示例:
contract C {
function g(uint a) returns (uint ret) { return f(); }
function f() returns (uint ret) { return g(7) + f(); }
}
这些函数调用在EVM中被翻译成简单的跳转指令。这样带来的一个好处是,当前的内存不会被回收。所以在一个内部调用时传递一个内存型引用效率将非常高。当然,仅仅是同一个合约的函数之间才可通过内部的方式进行调用。
表达式this.g(8);
和c.g(2)
(这里的c
是一个合约实例)是外部调用函数的方式。实现上是通过一个消息调用,而不是直接通过EVM的指令跳转。需要注意的是,在合约的构造器中,不能使用this
调用函数,因为当前合约还没有创建完成。
其它合约的函数必须通过外部的方式调用。对于一个外部调用,所有函数的参数必须要拷贝到内存中。
当调用其它合约的函数时,可以通过选项.value()
,和.gas()
来分别指定,要发送的ether
量(以wei
为单位),和gas
值。
pragma solidity ^0.4.0;
contract InfoFeed {
function info() payable returns (uint ret) {
return msg.value;
}
}
contract Consumer {
function deposit() payable returns (uint){
return msg.value;
}
function left() constant returns (uint){
return this.balance;
}
function callFeed(address addr) returns (uint) {
return InfoFeed(addr).info.value(1).gas(8000)();
}
}
上面的代码中,我们首先调用deposit()
为Consumer
合约存入一定量的ether
。然后调用callFeed()
通过value(1)
的方式,向InfoFeed
合约的info()
函数发送1ether
。需要注意的是,如果不先充值,由于合约余额为0,余额不足会报错Invalid opcode
1。
InfoFeed.info()
函数,必须使用payable
关键字,否则不能通过value()
选项来接收ether
。
代码InfoFeed(addr)
进行了一个显示的类型转换,声明了我们确定知道给定的地址是InfoFeed
类型。所以这里并不会执行构造器的初始化。显示的类型强制转换,需要极度小心,不要尝试调用一个你不知道类型的合约。
我们也可以使用function setFeed(InfoFeed _feed) { feed = _feed; }
来直接进行赋值。.info.value(1).gas(8000)
只是本地设置发送的数额和gas值,真正执行调用的是其后的括号.info.value(1).gas(8000)()
。
如果被调用的合约不存在,或者是不包代码的帐户,或调用的合约产生了异常,或者gas不足,均会造成函数调用发生异常。
如果被调用的合约源码并不事前知道,和它们交互会有潜在的风险。当前合约会将自己的控制权交给被调用的合约,而对方几乎可以做任何事。即使被调用的合约是继承自一个已知的父合约,但继承的子合约仅仅被要求正确实现了接口。合约的实现,可以是任意的内容,由此会有风险。另外,准备好处理调用你自己系统中的其它合约,可能在第一调用结果未返回之前就返回了调用的合约。某种程度上意味着,被调用的合约可以改变调用合约的状态变量(state variable)
来标记当前的状态。如,写一个函数,只有当状态变量(state variables)
的值有对应的改变时,才调用外部函数,这样你的合约就不会有可重入性漏洞。
函数调用的参数,可以通过指定名字的方式调用,但可以以任意的顺序,使用方式是{}
包含。但参数的类型和数量要与定义一致。
pragma solidity ^0.4.0;
contract C {
function add(uint val1, uint val2) returns (uint) { return val1 + val2; }
function g() returns (uint){
// named arguments
return add({val2: 2, val1: 1});
}
}
没有使用的参数名可以省略(一般常见于返回值)。这些名字在栈(stack)上存在,但不可访问。
pragma solidity ^0.4.0;
contract C {
// omitted name for parameter
function func(uint k, uint) returns(uint) {
return k;
}
}
如何使用Remix
向合约发送ether参见这里。http://me.tryblockchain.org/%E6%94%AF%E4%BB%98%E7%9B%B8%E5%85%B3.html ↩
不支持switch
和goto
,支持if
,else
,while
,do
,for
,break
,continue
,return
,?:
。
条件判断中的括号不可省略,但在单行语句中的大括号可以省略。
需要注意的是,这里没有像C语言,和javascript
里的非Boolean类型的自动转换,比如if(1){...}
在Solidity中是无效的。
同javascript
一样,函数有输入参数,但与之不同的是,函数可能有任意数量的返回参数。
入参(Input Parameter)
与变量的定义方式一致,稍微不同的是,不会用到的参数可以省略变量名称。一种可接受两个整型参数的函数如下:
pragma solidity ^0.4.0;
contract Simple {
function taker(uint _a, uint) {
// do something with _a.
}
}
出参(Output Paramets)
在returns
关键字后定义,语法类似变量的定义方式。下面的例子展示的是,返回两个输入参数的求和,乘积的实现:
pragma solidity ^0.4.0;
contract Simple {
//return sum and product
function arithmetics(uint _a, uint _b) returns (uint o_sum, uint o_product) {
o_sum = _a + _b;
o_product = _a * _b;
}
}
出参的的名字可以省略。返回的值,同样可以通过return
关键字来指定。return
也可以同时返回多个值,参见Returning Multiple Values。出参的默认值为0,如果没有明确被修改,它将一直是0。
入参和出参也可在函数体内用做表达式。它们也可被赋值。
当返回多个参数时,使用return (v0, v1, ..., vn)
。返回结果的数量需要与定义的一致。
.balance (uint256)
:
Address
的余额,以wei
为单位。
.transfer(uint256 amount)
:
发送给定数量的ether
,以wei
为单位,到某个地址。失败时抛出异常。
.send(uint256 amount) returns (bool)
:
发送给定数量的ether
,以wei
为单位,到某个地址。失败时返回false
。
.call(...) returns (bool)
:
发起底层的call
调用。失败时返回false
。
.callcode(...) returns (bool)
:
发起底层的callcode
调用,失败时返回false
。
.delegatecall(...) returns (bool)
:
发起底层的delegatecall
调用,失败时返回false
。
更多信息参加Address章节1。
使用send
方法需要注意,调用栈深不能超过1024,或gas不足,都将导致发送失败。使用为了保证你的ether
安全,要始终检查返回结果。当用户取款时,使用transfer
或使用最佳实践的模式2。
this
(当前合约的类型)
当前合约的类型,可以显式的转换为Address
selfdestruct(address recipt)
:
销毁当前合约,并把它所有资金发送到给定的地址。
另外,当前合约里的所有函数均可支持调用,包括当前函数本身。
更新关于地址的call,callcode,delegateCall的介绍。http://me.tryblockchain.org/Solidity-call-callcode-delegatecall.html ↩
http://solidity.readthedocs.io/en/develop/common-patterns.html#withdrawal-from-contracts ↩
asser(bool condition)
:
如果条件不满足,抛出异常。
addmod(uint x, uint y, uint k) returns (uint)
:
计算(x + y) % k
。加法支持任意的精度。但不超过(wrap around?)2**256
。
mulmod(uint x, uint y, uint k) returns (uint)
:
计算(x * y) % k
。乘法支持任意精度,但不超过(wrap around?)2**256
。
keccak256(...) returns (bytes32)
:
使用以太坊的(Keccak-256)计算HASH值。紧密打包。
sha3(...) returns (bytes32)
:
等同于keccak256()
。紧密打包。
sha256(...) returns (bytes32)
:
使用SHA-256计算HASH值。紧密打包。
ripemd160(...) returns (bytes20)
:
使用RIPEMD-160计算HASH值。紧密打包。
ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)
:
通过签名信息恢复非对称加密算法公匙地址。如果出错会返回0,附录提供了一个例子1.
revert()
:
取消执行,并回撤状态变化。
需要注意的是参数是“紧密打包(tightly packed)”的,意思是说参数不会补位,就直接连接在一起的。下面来看一个例子:
keccak256("ab", "c")
keccak256("abc")
//hex
keccak256(0x616263)
keccak256(6382179)
//ascii
keccak256(97, 98, 99)
上述例子中,三种表达方式都是一致的。
如果需要补位,需要明确的类型转换,如keccak256("\x00\x12")
等同于keccak256(uint16(0x12))
需要注意的是字面量会用,尽可能小的空间来存储它们。比如,keccak256(0) == keccak256(uint8(0))
,keccak256(0x12345678) == keccak256(uint32(0x12345678))
在私链(private blockchain)
上运行sha256
,ripemd160
或ecrecover
可能会出现Out-Of-Gas
报错。因为它们实现了一种预编译的机制,但合约要在收到第一个消息后才会存在。向一个不存在的合约发送消息,非常昂贵,所以才会导致Out-Of-Gas
的问题。一种解决办法是每个在你真正使用它们前,先发送1 wei
到这些合约上来完成初始化。在官方和测试链上没有这个问题。
使用ecrecover实现签名检验的例子。http://me.tryblockchain.org/web3js-sign-ecrecover-decode.html ↩
有一些变量和函数存在于¥全局上下文中。主要用来提供一些区块链当前的信息。
block.blockhash(uint blockNumber) returns (bytes32)
,给定区块号的哈希值,只支持最近256个区块,且不包含当前区块。block.coinbase (address)
当前块矿工的地址。block.difficulty (uint)
当前块的难度。block.gaslimit (uint)
当前块的gaslimit
。block.number (uint)
当前区块的块号。block.timestamp (uint)
当前块的时间戳。msg.data (bytes)
完整的调用数据(calldata)。msg.gas (uint)
当前还剩的gas
。msg.sender (address)
当前调用发起人的地址。msg.sig (bytes4)
调用数据的前四个字节(函数标识符)。msg.value (uint)
这个消息所附带的货币量,单位为wei
。now (uint)
当前块的时间戳,等同于block.timestamp
tx.gasprice (uint)
交易的gas
价格。tx.origin (address)
交易的发送者(完整的调用链)msg
的所有成员值,如msg.sender
,msg.value
的值可以因为每一次外部函数调用,或库函数调用发生变化(因为msg
就是和调用相关的全局变量)。
如果你想在库函数中,用msg.sender
实现访问控制,你需要将msg.sender
做为参数(就是说不能使用默认的msg.value
,因为它可能被更改)。
为了可扩展性的原因,你只能查最近256个块,所有其它的将返回0.
seconds
,minutes
,hours
,days
,weeks
,years
均可做为后缀,并进行相互转换,默认是seconds
为单位。
默认规则如下:
如果你需要进行使用这些单位进行日期计算,需要特别小心,因为不是每年都是365天,且并不是每天都有24小时,因为还有闰秒。由于无法预测闰秒,必须由外部的oracle来更新从而得到一个精确的日历库(内部实现一个日期库也是消耗gas的)。
后缀不能用于变量。如果你想对输入的变量说明其不同的单位,可以使用下面的方式。
pragma solidity ^0.4.0;
contract DeleteExample{
function nowInSeconds() returns (uint256){
return now;
}
function f(uint start, uint daysAfter) {
if (now >= start + daysAfter * 1 days) {
}
}
}
一个字面量的数字,可以使用后缀wei
,finney
,szabo
或ether
来在不同面额中转换。不含任何后缀的默认单位是wei
。如2 ether == 2000 finney
的结果是true
。
pragma solidity ^0.4.0;
contract EthUnit{
uint a;
function f() returns (bool){
if (2 ether == 2000 finney){
return true;
}
return false;
}
}
为了方便,并不总是需要明确指定一个变量的类型,编译器会通过第一个向这个对象赋予的值的类型来进行推断1。
uint24 x = 0x123;
var y = x;
函数的参数,包括返回参数,不可以使用var
这种不指定类型的方式。
需要特别注意的是,由于类型推断是根据第一个变量进行的赋值。所以代码for (var i = 0; i < 2000; i++) {}
将是一个无限循环,因为一个uint8
的i
的将小于2000
。
pragma solidity ^0.4.4;
contract Test{
function a() returns (uint){
uint count = 0;
for (var i = 0; i < 2000; i++) {
count++;
if(count >= 2100){
break;
}
}
return count;
}
}
http://solidity.readthedocs.io/en/develop/types.html#type-deduction ↩
语言中经常会出现类型转换1。如将一个数字字符串转为整型,或浮点数。这种转换常常分为,隐式转换和显式转换。
如果运算符支持两边不同的类型,编译器会尝试隐式转换类型,同理,赋值时也是类似。通常,隐式转换需要能保证不会丢失数据,且语义可通。如uint8
可以转化为uint16
,uint256
。但int8
不能转为uint256
,因为uint256
不能表示-1
。
此外,任何无符号整数,可以转换为相同或更大大小的字节值。比如,任何可以转换为uint160
的,也可以转换为address
。
如果编译器不允许隐式的自动转换,但你知道转换没有问题时,可以进行强转。需要注意的是,不正确的转换会带来错误,所以你要进行谨慎的测试。
pragma solidity ^0.4.0;
contract DeleteExample{
uint a;
function f() returns (uint){
int8 y = -3;
uint x = uint(y);
return x;
}
}
如果转换为一个更小的类型,高位将被截断。
uint32 a = 0x12345678;
uint16 b = uint16(a); // b will be 0x5678 now
http://solidity.readthedocs.io/en/develop/types.html#conversions-between-elementary-types ↩
左值
1,是指位于表达式左边的变量,可以是与操作符直接结合的形成的,如自增,自减;也可以是赋值,位运算。
可以支持操作符有:-=
,+=
,*=
,%=
,|=
,&=
,^=
,++
,--
。
delete
运算符,用于将某个变量重置为初始值。对于整数,运算符的效果等同于a = 0
。而对于定长数组,则是把数组中的每个元素置为初始值,变长数组则是将长度置为0。对于结构体,也是类似,是将所有的成员均重置为初始值。
delete
对于映射类型几乎无影响,因为键可能是任意的,且往往不可知。所以如果你删除一个结构体,它会递归删除所有非mapping的成员。当然,你是可以单独删除映射里的某个键,以及这个键映射的某个值。
需要强调的是delete a
的行为更像赋值,为a
赋予一个新对象。我们来看看下文的示例:
pragma solidity ^0.4.0;
contract DeleteExample {
uint data;
uint[] dataArray;
function f() {
//值传递
uint x = data;
//删除x不会影响data
delete x;
//删除data,同样也不会影响x,因为是值传递,它存的是一份原值的拷贝。
delete data;
//引用赋值
uint[] y = dataArray;
//删除dataArray会影响y,y也将被赋值为初值。
delete dataArray;
//下面的操作为报错,因为删除是一个赋值操作,不能向引用类型的storage直接赋值从而报错
//delete y;
}
}
通过上面的代码,我们可以看出,对于值类型,是值传递,删除x
不会影响到data
,同样的删除data
也不会影响到x
。因为他们都存了一份原值的拷贝。
而对于复杂类型略有不同,复杂类型在赋值时使用的是引用传递。删除会影响所有相关变量。比如上述代码中,删除dataArray
同样会影响到y
。
由于delete
的行为更像是赋值操作,所以不能在上述代码中执行delete y
,因为不能对一个storage的引用赋值2
http://solidity.readthedocs.io/en/develop/types.html#operators-involving-lvalues ↩
strorage的引用不能赋值原因,可以看看关于数据位置的这篇文章:http://me.tryblockchain.org/solidity-data-location.html。 ↩
映射
或字典类型,一种键值对的映射关系存储结构。定义方式为mapping(_KeyType => _KeyValue)
。键的类型允许除映射
外的所有类型,如数组,合约,枚举,结构体。值的类型无限制。
映射
可以被视作为一个哈希表,其中所有可能的键已被虚拟化的创建,被映射到一个默认值(二进制表示的零)。但在映射表中,我们并不存储键的数据,仅仅存储它的keccak256
哈希值,用来查找值时使用。
因此,映射
并没有长度,键集合(或列表),值集合(或列表)这样的概念。
映射
类型,仅能用来定义状态变量
,或者是在内部函数中作为storage
类型的引用。引用是指你可以声明一个,如var storage mappVal
的用于存储状态变量的引用的对象,但你没办法使用非状态变量来初始化这个引用。
可以通过将映射
标记为public
,来让Solidity创建一个访问器。要想访问这样的映射
,需要提供一个键值做为参数。如果映射
的值类型也是映射
,使用访问器访问时,要提供这个映射
值所对应的键,不断重复这个过程。下面来看一个例子:
contract MappingExample{
mapping(address => uint) public balances;
function update(uint amount) returns (address addr){
balances[msg.sender] = amount;
return msg.sender;
}
}
由于调试时,你不一定方便知道自己的发起地址,所以把这个函数,略微调整了一下,以在调用时,返回调用者的地址。编译上述合同后,可以先调用update()
,执行成功后,查看调用信息,能看到你更新的地址,这样再查一下这个地址的在映射里存的值。
如果你想通过合约进行上述调用。
pragma solidity ^0.4.0;
//file indeed for compile
//may store in somewhere and import
contract MappingExample{
mapping(address => uint) public balances;
function update(uint amount) returns (address addr){
balances[msg.sender] = amount;
return msg.sender;
}
}
contract MappingUser{
address conAddr;
address userAddr;
function f() returns (uint amount){
//address not resolved!
//tringing
conAddr = hex"0xf2bd5de8b57ebfc45dcee97524a7a08fccc80aef";
userAddr = hex"0xca35b7d915458ef540ade6068dfe2f44e8fa733c";
return MappingExample(conAddr).balances(userAddr);
}
}
映射
并未提供迭代输出的方法,可以自行实现一个数据结构。参见iterable mapping。
Solidity
提供struct
来定义自定义类型。我们来看看下面的例子:
pragma solidity ^0.4.0;
contract CrowdFunding{
struct Funder{
address addr;
uint amount;
}
struct Campaign{
address beneficiary;
uint goal;
uint amount;
uint funderNum;
mapping(uint => Funder) funders;
}
uint compaingnID;
mapping (uint => Campaign) campaigns;
function candidate(address beneficiary, uint goal) returns (uint compaingnID){
// initialize
campaigns[compaingnID++] = Campaign(beneficiary, goal, 0, 0);
}
function vote(uint compaingnID) payable {
Campaign c = campaigns[compaingnID];
//another way to initialize
c.funders[c.funderNum++] = Funder({addr: msg.sender, amount: msg.value});
c.amount += msg.value;
}
function check(uint comapingnId) returns (bool){
Campaign c = campaigns[comapingnId];
if(c.amount < c.goal){
return false;
}
uint amount = c.amount;
// incase send much more
c.amount = 0;
if(!c.beneficiary.send(amount)){
throw;
}
return true;
}
}
上面的代码向我们展示的一个简化版的众筹项目,其实包含了一些struct
的使用。struct
可以用于映射和数组中作为元素。其本身也可以包含映射和数组等类型。
我们不能声明一个struct
同时将这个struct
作为这个struct的一个成员。这个限制是基于结构体的大小必须是有限的。
虽然数据结构能作为一个mapping
的值,但数据类型不能包含它自身类型的成员,因为数据结构的大小必须是有限的。
需要注意的是在函数中,将一个struct
赋值给一个局部变量(默认是storage类型),实际是拷贝的引用,所以修改局部变量值时,会影响到原变量。
当然,你也可以直接通过访问成员修改值,而不用一定赋值给一个局部变量,如campaigns[comapingnId].amount = 0
数组
可以声明时指定长度,或者是变长的。对storage
1的数组来说,元素类型可以是任意的,类型可以是数组,映射类型,数据结构等。但对于memory
1的数组来说。如果函数是对外可见的2,那么函数参数不能是映射类型的数组,只能是支持ABI的类型3。
一个类型为T,长度为k的数组,可以声明为T[k]
,而一个变长的数组则声明为T[]
。
你还可以声明一个多维数据,如一个类型为uint
的数组长度为5的变长数组,可以声明为uint[][5] x
。需要留心的是,相比非区块链语言,多维数组的长度声明是反的。
要访问第三个动态数据的,第二个元素,使用x[2][1]
。数组的序号是从0开始的,序号顺序与定义相反。
bytes
和string
是一种特殊的数组。bytes
类似byte[]
,但在外部函数作为参数调用中,会进行压缩打包,更省空间,所以应该尽量使用bytes
4。string
类似bytes
,但不提供长度和按序号的访问方式。
由于bytes
与string
,可以自由转换,你可以将字符串s
通过bytes(s)
转为一个bytes
。但需要注意的是通过这种方式访问到的是UTF-8编码的码流,并不是独立的一个个字符。比如中文编码是多字节,变长的,所以你访问到的很有可能只是其中的一个代码点。
类型为数组的状态变量,可以标记为public
类型,从而让Solidity
创建一个访问器,如果要访问数组的某个元素,指定数字下标就好了。
可使用new
关键字创建一个memory
的数组。与stroage
数组不同的是,你不能通过.length
的长度来修改数组大小属性。我们来看看下面的例子:
pragma solidity ^0.4.0;
contract C {
function f() {
//创建一个memory的数组
uint[] memory a = new uint[](7);
//不能修改长度
//Error: Expression has to be an lvalue.
//a.length = 100;
}
//storage
uint[] b;
function g(){
b = new uint[](7);
//可以修改storage的数组
b.length = 10;
b[9] = 100;
}
}
在上面的代码中,f()
方法尝试调整数组a
的长度,编译器报错Error: Expression has to be an lvalue.
。但在g()
方法中我们看到可以修改5。
数组字面量,是指以表达式方式隐式声明一个数组,并作为一个数组变量使用的方式。下面是一个简单的例子:
pragma solidity ^0.4.0;
contract C {
function f() {
g([uint(1), 2, 3]);
}
function g(uint[3] _data) {
// ...
}
}
通过数组字面量,创建的数组是memory
的,同时还是定长的。元素类型则是使用刚好能存储的元素的能用类型,比如代码里的[1, 2, 3]
,只需要uint8
即可存储。由于g()
方法的参数需要的是uint
(默认的uint
表示的其实是uint256
),所以要使用uint(1)
来进行类型转换。
还需注意的一点是,定长数组,不能与变长数组相互赋值,我们来看下面的代码:
pragma solidity ^0.4.0;
contract C {
function f() {
// The next line creates a type error because uint[3] memory
// cannot be converted to uint[] memory.
uint[] x = [uint(1), 3, 4];
}
限制的主要原因是,ABI不能很好的支持数组,已经计划在未来移除这样的限制。(当前的ABI接口,不是已经能支持数组了?)
数组有一个.length
属性,表示当前的数组长度。storage
的变长数组,可以通过给.length
赋值调整数组长度。memory
的变长数组不支持。
不能通过访问超出当前数组的长度的方式,来自动实现上面说的这种情况。memory
数组虽然可以通过参数,灵活指定大小,但一旦创建,大小不可调整,对于变长数组,可以通过参数在编译期指定数组大小。
storage
的变长数组和bytes
都有一个push()
,用于附加新元素到数据末端,返回值为新的长度。
pragma solidity ^0.4.0;
contract C {
uint[] u;
bytes b;
function testArryPush() returns (uint){
uint[3] memory a = [uint(1), 2, 3];
u = a;
return u.push(4);
}
function testBytesPush() returns (uint){
b = new bytes(3);
return b.push(4);
}
}
当前在外部函数中,不能使用多维数组。
另外,基于EVM的限制,不能通过外部函数返回动态的内容。
pragma solidity ^0.4.0;
contract C {
function f() returns (uint[]) {
}
}
在上面的例子中,通过web.js调用能返回数据,但在Solidity中不能返回数据。一种临时的解决办法,是使用一个非常大的静态数组。
pragma solidity ^0.4.0;
contract ArrayContract {
//the orginal length of m_aLotOfIntegers is 2**20
//run it cause a out of gas,so change it to a much smaller 2**2 for test
uint[2**2] m_aLotOfIntegers;
// Note that the following is not a pair of arrays but an array of pairs.
bool[2][] m_pairsOfFlags;
// newPairs is stored in memory - the default for function arguments
function setAllFlagPairs(bool[2][] newPairs) {
// assignment to a storage array replaces the complete array
m_pairsOfFlags = newPairs;
}
function setFlagPair(uint index, bool flagA, bool flagB) {
// access to a non-existing index will throw an exception
m_pairsOfFlags[index][0] = flagA;
m_pairsOfFlags[index][1] = flagB;
}
function changeFlagArraySize(uint newSize) {
// if the new size is smaller, removed array elements will be cleared
m_pairsOfFlags.length = newSize;
}
function clear() {
// these clear the arrays completely
delete m_pairsOfFlags;
delete m_aLotOfIntegers;
// identical effect here
m_pairsOfFlags.length = 0;
}
function addFlag(bool[2] flag) returns (uint) {
return m_pairsOfFlags.push(flag);
}
function createMemoryArray(uint size) {
// Dynamic memory arrays are created using `new`:
bool[2][] memory arrayOfPairs = new bool[2][](size);
m_pairsOfFlags = arrayOfPairs;
}
}
更多请查看这里的重新梳理: http://me.tryblockchain.org/solidity-array.html
变量声明时定义的数据位置属性。可以是memory的,也可以为storage的,详见:http://me.tryblockchain.org/data-location.org ↩
函数的可见性说明,详见这里: http://me.tryblockchain.org/solidity-function-advanced1.html ↩
了解更多关于ABI的信息,https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#types ↩
因为字节数组在使用时会padding,所以不节省空间。https://github.com/chriseth/solidity/blob/48588b5c4f21c8bb7c5d4319b05a93ee995b457d/_docs/faq_basic.md#what-is-the-difference-between-bytes-and-byte ↩
http://stackoverflow.com/questions/33839122/in-ethereum-solidity-when-changing-an-array-length-i-get-value-must-be-an-lva ↩