Solidity 官方文档中文版(三)

朴灿烈づ我的快乐病毒、 2022-06-01 23:43 1002阅读 0赞

#

赋值(Assignment)

解构赋值和返回多个结果(Destructing Assignments and Returning Multip Values)

Solidity内置支持元组(tuple),也就是说支持一个可能的完全不同类型组成的一个列表,数量上是固定的(Tuple一般指两个,还有个Triple一般指三个)。

这种内置结构可以同时返回多个结果,也可用于同时赋值给多个变量。

  1. pragma solidity ^0.4.0;
  2. contract C {
  3. uint[] data;
  4. function f() returns (uint, bool, uint) {
  5. return (7, true, 2);
  6. }
  7. function g() {
  8. // Declares and assigns the variables. Specifying the type explicitly is not possible.
  9. var (x, b, y) = f();
  10. // Assigns to a pre-existing variable.
  11. (x, y) = (2, 7);
  12. // Common trick to swap values -- does not work for non-value storage types.
  13. (x, y) = (y, x);
  14. // Components can be left out (also for variable declarations).
  15. // If the tuple ends in an empty component,
  16. // the rest of the values are discarded.
  17. (data.length,) = f(); // Sets the length to 7
  18. // The same can be done on the left side.
  19. (,data[3]) = f(); // Sets data[3] to 2
  20. // Components can only be left out at the left-hand-side of assignments, with
  21. // one exception:
  22. (x,) = (1,);
  23. // (1,) is the only way to specify a 1-component tuple, because (1) is
  24. // equivalent to 1.
  25. }
  26. }

数组和自定义结构体的复杂性(Complication for Arrays and Struts)

对于非值类型,比如数组和数组,赋值的语法有一些复杂。

  • 赋值给一个状态变量总是创建一个完全无关的拷贝。
  • 赋值给一个局部变量,仅对基本类型,如那些32字节以内的静态类型(static types),创建一份完全无关拷贝。
  • 如果是数据结构或者数组(包括bytesstring)类型,由状态变量赋值为一个局部变量,局部变量只是持有原始状态变量的一个引用。对这个局部变量再次赋值,并不会修改这个状态变量,只是修改了引用。但修改这个本地引用变量的成员值,会改变状态变量的值。

表达式的执行顺序(Order of Evaluation of Expressions)

表达式的求值顺序并不是确定的,更正式的说法是,表达式树一个节点的某个节点在求值时的顺序是不确定的,但是它肯定会比节点本身先执行。我们仅仅保证语句(statements)按顺序执行和布尔表达式的短路运算的支持。查看运算符的执行顺序了解更多。

创建合约实例(Creating Contracts via `new`)

一个合约可以通过new关键字来创建一个合约。要创建合约的完整代码,必须提前知道,所以递归创建依赖是不可能的。

  1. pragma solidity ^0.4.0;
  2. contract Account{
  3. uint accId;
  4. //construction?
  5. function Account(uint accountId) payable{
  6. accId = accountId;
  7. }
  8. }
  9. contract Initialize{
  10. Account account = new Account(10);
  11. function newAccount(uint accountId){
  12. account = new Account(accountId);
  13. }
  14. function newAccountWithEther(uint accountId, uint amount){
  15. account = (new Account).value(amount)(accountId);
  16. }
  17. }

从上面的例子可以看出来,可以在创建合约中,发送ether,但不能限制gas的使用。如果创建因为out-of-stack,或无足够的余额以及其它任何问题,会抛出一个异常。

函数调用(Function Calls)

内部函数调用(Internal Function Calls)

在当前的合约中,函数可以直接调用(内部调用方式),包括也可递归调用,来看一个简单的示例:

  1. contract C {
  2. function g(uint a) returns (uint ret) { return f(); }
  3. function f() returns (uint ret) { return g(7) + f(); }
  4. }

这些函数调用在EVM中被翻译成简单的跳转指令。这样带来的一个好处是,当前的内存不会被回收。所以在一个内部调用时传递一个内存型引用效率将非常高。当然,仅仅是同一个合约的函数之间才可通过内部的方式进行调用。

外部函数调用(External Function Calls)

表达式this.g(8);c.g(2)(这里的c是一个合约实例)是外部调用函数的方式。实现上是通过一个消息调用,而不是直接通过EVM的指令跳转。需要注意的是,在合约的构造器中,不能使用this调用函数,因为当前合约还没有创建完成。

其它合约的函数必须通过外部的方式调用。对于一个外部调用,所有函数的参数必须要拷贝到内存中。

当调用其它合约的函数时,可以通过选项.value(),和.gas()来分别指定,要发送的ether量(以wei为单位),和gas值。

  1. pragma solidity ^0.4.0;
  2. contract InfoFeed {
  3. function info() payable returns (uint ret) {
  4. return msg.value;
  5. }
  6. }
  7. contract Consumer {
  8. function deposit() payable returns (uint){
  9. return msg.value;
  10. }
  11. function left() constant returns (uint){
  12. return this.balance;
  13. }
  14. function callFeed(address addr) returns (uint) {
  15. return InfoFeed(addr).info.value(1).gas(8000)();
  16. }
  17. }

上面的代码中,我们首先调用deposit()Consumer合约存入一定量的ether。然后调用callFeed()通过value(1)的方式,向InfoFeed合约的info()函数发送1ether。需要注意的是,如果不先充值,由于合约余额为0,余额不足会报错Invalid opcode1。

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)的值有对应的改变时,才调用外部函数,这样你的合约就不会有可重入性漏洞。

命名参数调用和匿名函数参数(Named Calls and Anonymous Function Paramters)

函数调用的参数,可以通过指定名字的方式调用,但可以以任意的顺序,使用方式是{}包含。但参数的类型和数量要与定义一致。

  1. pragma solidity ^0.4.0;
  2. contract C {
  3. function add(uint val1, uint val2) returns (uint) { return val1 + val2; }
  4. function g() returns (uint){
  5. // named arguments
  6. return add({val2: 2, val1: 1});
  7. }
  8. }

省略函数名称(Omitted Function Parameter Names)

没有使用的参数名可以省略(一般常见于返回值)。这些名字在栈(stack)上存在,但不可访问。

  1. pragma solidity ^0.4.0;
  2. contract C {
  3. // omitted name for parameter
  4. function func(uint k, uint) returns(uint) {
  5. return k;
  6. }
  7. }

参考资料


  1. 如何使用Remix向合约发送ether参见这里。http://me.tryblockchain.org/%E6%94%AF%E4%BB%98%E7%9B%B8%E5%85%B3.html ↩

控制结构

不支持switchgoto,支持ifelsewhiledoforbreakcontinuereturn?:

条件判断中的括号不可省略,但在单行语句中的大括号可以省略。

需要注意的是,这里没有像C语言,和javascript里的非Boolean类型的自动转换,比如if(1){...}在Solidity中是无效的。

入参和出参(Input Parameters and Output Parameters)

javascript一样,函数有输入参数,但与之不同的是,函数可能有任意数量的返回参数。

入参(Input Parameters)

入参(Input Parameter)与变量的定义方式一致,稍微不同的是,不会用到的参数可以省略变量名称。一种可接受两个整型参数的函数如下:

  1. pragma solidity ^0.4.0;
  2. contract Simple {
  3. function taker(uint _a, uint) {
  4. // do something with _a.
  5. }
  6. }

出参(Output Parameters)

出参(Output Paramets)returns关键字后定义,语法类似变量的定义方式。下面的例子展示的是,返回两个输入参数的求和,乘积的实现:

  1. pragma solidity ^0.4.0;
  2. contract Simple {
  3. //return sum and product
  4. function arithmetics(uint _a, uint _b) returns (uint o_sum, uint o_product) {
  5. o_sum = _a + _b;
  6. o_product = _a * _b;
  7. }
  8. }

出参的的名字可以省略。返回的值,同样可以通过return关键字来指定。return也可以同时返回多个值,参见Returning Multiple Values。出参的默认值为0,如果没有明确被修改,它将一直是0。

入参和出参也可在函数体内用做表达式。它们也可被赋值。

返回多个值(Returning Multiple Values)

当返回多个参数时,使用return (v0, v1, ..., vn)。返回结果的数量需要与定义的一致。

地址相关(Address Related)

<address>.balance (uint256)

Address的余额,以wei为单位。

<address>.transfer(uint256 amount)

发送给定数量的ether,以wei为单位,到某个地址。失败时抛出异常。

<address>.send(uint256 amount) returns (bool):

发送给定数量的ether,以wei为单位,到某个地址。失败时返回false

<address>.call(...) returns (bool)

发起底层的call调用。失败时返回false

<address>.callcode(...) returns (bool)

发起底层的callcode调用,失败时返回false

<address>.delegatecall(...) returns (bool)

发起底层的delegatecall调用,失败时返回false

更多信息参加Address章节1。

使用send方法需要注意,调用栈深不能超过1024,或gas不足,都将导致发送失败。使用为了保证你的ether安全,要始终检查返回结果。当用户取款时,使用transfer或使用最佳实践的模式2。

合约相关

this(当前合约的类型)

当前合约的类型,可以显式的转换为Address

selfdestruct(address recipt):

销毁当前合约,并把它所有资金发送到给定的地址。

另外,当前合约里的所有函数均可支持调用,包括当前函数本身。

参考资料


  1. 更新关于地址的call,callcode,delegateCall的介绍。http://me.tryblockchain.org/Solidity-call-callcode-delegatecall.html ↩
  2. http://solidity.readthedocs.io/en/develop/common-patterns.html#withdrawal-from-contracts ↩

数学和加密函数(Mathematical and Cryptographic Functions)

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)”的,意思是说参数不会补位,就直接连接在一起的。下面来看一个例子:

  1. keccak256("ab", "c")
  2. keccak256("abc")
  3. //hex
  4. keccak256(0x616263)
  5. keccak256(6382179)
  6. //ascii
  7. keccak256(97, 98, 99)

上述例子中,三种表达方式都是一致的。

如果需要补位,需要明确的类型转换,如keccak256("\x00\x12")等同于keccak256(uint16(0x12))

需要注意的是字面量会用,尽可能小的空间来存储它们。比如,keccak256(0) == keccak256(uint8(0))keccak256(0x12345678) == keccak256(uint32(0x12345678))

私链(private blockchain)上运行sha256,ripemd160ecrecover可能会出现Out-Of-Gas报错。因为它们实现了一种预编译的机制,但合约要在收到第一个消息后才会存在。向一个不存在的合约发送消息,非常昂贵,所以才会导致Out-Of-Gas的问题。一种解决办法是每个在你真正使用它们前,先发送1 wei到这些合约上来完成初始化。在官方和测试链上没有这个问题。

参考资料


  1. 使用ecrecover实现签名检验的例子。http://me.tryblockchain.org/web3js-sign-ecrecover-decode.html ↩

特殊变量及函数(Special Variables and Functions)

有一些变量和函数存在于¥全局上下文中。主要用来提供一些区块链当前的信息。

区块和交易的属性(Block And Transaction Properties)

  • 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.

时间单位(Time Units)

seconds,minutes,hours,days,weeks,years均可做为后缀,并进行相互转换,默认是seconds为单位。

默认规则如下:

  • 1 == 1 seconds
  • 1 minutes == 60 seconds
  • 1 hours == 60 minutes
  • 1 days == 24 hours
  • 1 weeks = 7 days
  • 1 years = 365 days

如果你需要进行使用这些单位进行日期计算,需要特别小心,因为不是每年都是365天,且并不是每天都有24小时,因为还有闰秒。由于无法预测闰秒,必须由外部的oracle来更新从而得到一个精确的日历库(内部实现一个日期库也是消耗gas的)。

后缀不能用于变量。如果你想对输入的变量说明其不同的单位,可以使用下面的方式。

  1. pragma solidity ^0.4.0;
  2. contract DeleteExample{
  3. function nowInSeconds() returns (uint256){
  4. return now;
  5. }
  6. function f(uint start, uint daysAfter) {
  7. if (now >= start + daysAfter * 1 days) {
  8. }
  9. }
  10. }

货币单位(Ether Units)

一个字面量的数字,可以使用后缀wei,finney,szaboether来在不同面额中转换。不含任何后缀的默认单位是wei。如2 ether == 2000 finney的结果是true

  1. pragma solidity ^0.4.0;
  2. contract EthUnit{
  3. uint a;
  4. function f() returns (bool){
  5. if (2 ether == 2000 finney){
  6. return true;
  7. }
  8. return false;
  9. }
  10. }

类型推断(Type Deduction)

为了方便,并不总是需要明确指定一个变量的类型,编译器会通过第一个向这个对象赋予的值的类型来进行推断1。

  1. uint24 x = 0x123;
  2. var y = x;

函数的参数,包括返回参数,不可以使用var这种不指定类型的方式。

需要特别注意的是,由于类型推断是根据第一个变量进行的赋值。所以代码for (var i = 0; i < 2000; i++) {}将是一个无限循环,因为一个uint8i的将小于2000

  1. pragma solidity ^0.4.4;
  2. contract Test{
  3. function a() returns (uint){
  4. uint count = 0;
  5. for (var i = 0; i < 2000; i++) {
  6. count++;
  7. if(count >= 2100){
  8. break;
  9. }
  10. }
  11. return count;
  12. }
  13. }

参考资料


  1. http://solidity.readthedocs.io/en/develop/types.html#type-deduction ↩

基本类型间的转换

语言中经常会出现类型转换1。如将一个数字字符串转为整型,或浮点数。这种转换常常分为,隐式转换和显式转换。

隐式转换

如果运算符支持两边不同的类型,编译器会尝试隐式转换类型,同理,赋值时也是类似。通常,隐式转换需要能保证不会丢失数据,且语义可通。如uint8可以转化为uint16,uint256。但int8不能转为uint256,因为uint256不能表示-1

此外,任何无符号整数,可以转换为相同或更大大小的字节值。比如,任何可以转换为uint160的,也可以转换为address

显式转换

如果编译器不允许隐式的自动转换,但你知道转换没有问题时,可以进行强转。需要注意的是,不正确的转换会带来错误,所以你要进行谨慎的测试。

  1. pragma solidity ^0.4.0;
  2. contract DeleteExample{
  3. uint a;
  4. function f() returns (uint){
  5. int8 y = -3;
  6. uint x = uint(y);
  7. return x;
  8. }
  9. }

如果转换为一个更小的类型,高位将被截断。

  1. uint32 a = 0x12345678;
  2. uint16 b = uint16(a); // b will be 0x5678 now

参考资料


  1. http://solidity.readthedocs.io/en/develop/types.html#conversions-between-elementary-types ↩

左值的相关运算符

左值1,是指位于表达式左边的变量,可以是与操作符直接结合的形成的,如自增,自减;也可以是赋值,位运算。

可以支持操作符有:-=,+=,*=,%=,|=,&=,^=,++,--

特殊的运算符delete

delete运算符,用于将某个变量重置为初始值。对于整数,运算符的效果等同于a = 0。而对于定长数组,则是把数组中的每个元素置为初始值,变长数组则是将长度置为0。对于结构体,也是类似,是将所有的成员均重置为初始值。

delete对于映射类型几乎无影响,因为键可能是任意的,且往往不可知。所以如果你删除一个结构体,它会递归删除所有非mapping的成员。当然,你是可以单独删除映射里的某个键,以及这个键映射的某个值。

需要强调的是delete a的行为更像赋值,为a赋予一个新对象。我们来看看下文的示例:

  1. pragma solidity ^0.4.0;
  2. contract DeleteExample {
  3. uint data;
  4. uint[] dataArray;
  5. function f() {
  6. //值传递
  7. uint x = data;
  8. //删除x不会影响data
  9. delete x;
  10. //删除data,同样也不会影响x,因为是值传递,它存的是一份原值的拷贝。
  11. delete data;
  12. //引用赋值
  13. uint[] y = dataArray;
  14. //删除dataArray会影响y,y也将被赋值为初值。
  15. delete dataArray;
  16. //下面的操作为报错,因为删除是一个赋值操作,不能向引用类型的storage直接赋值从而报错
  17. //delete y;
  18. }
  19. }

通过上面的代码,我们可以看出,对于值类型,是值传递,删除x不会影响到data,同样的删除data也不会影响到x。因为他们都存了一份原值的拷贝。

而对于复杂类型略有不同,复杂类型在赋值时使用的是引用传递。删除会影响所有相关变量。比如上述代码中,删除dataArray同样会影响到y

由于delete的行为更像是赋值操作,所以不能在上述代码中执行delete y,因为不能对一个storage的引用赋值2

参考资料


  1. http://solidity.readthedocs.io/en/develop/types.html#operators-involving-lvalues ↩
  2. strorage的引用不能赋值原因,可以看看关于数据位置的这篇文章:http://me.tryblockchain.org/solidity-data-location.html。 ↩

映射/字典(mappings)

映射或字典类型,一种键值对的映射关系存储结构。定义方式为mapping(_KeyType => _KeyValue)。键的类型允许除映射外的所有类型,如数组,合约,枚举,结构体。值的类型无限制。

映射可以被视作为一个哈希表,其中所有可能的键已被虚拟化的创建,被映射到一个默认值(二进制表示的零)。但在映射表中,我们并不存储键的数据,仅仅存储它的keccak256哈希值,用来查找值时使用。

因此,映射并没有长度,键集合(或列表),值集合(或列表)这样的概念。

映射类型,仅能用来定义状态变量,或者是在内部函数中作为storage类型的引用。引用是指你可以声明一个,如var storage mappVal的用于存储状态变量的引用的对象,但你没办法使用非状态变量来初始化这个引用。

可以通过将映射标记为public,来让Solidity创建一个访问器。要想访问这样的映射,需要提供一个键值做为参数。如果映射的值类型也是映射,使用访问器访问时,要提供这个映射值所对应的键,不断重复这个过程。下面来看一个例子:

  1. contract MappingExample{
  2. mapping(address => uint) public balances;
  3. function update(uint amount) returns (address addr){
  4. balances[msg.sender] = amount;
  5. return msg.sender;
  6. }
  7. }

由于调试时,你不一定方便知道自己的发起地址,所以把这个函数,略微调整了一下,以在调用时,返回调用者的地址。编译上述合同后,可以先调用update(),执行成功后,查看调用信息,能看到你更新的地址,这样再查一下这个地址的在映射里存的值。

如果你想通过合约进行上述调用。

  1. pragma solidity ^0.4.0;
  2. //file indeed for compile
  3. //may store in somewhere and import
  4. contract MappingExample{
  5. mapping(address => uint) public balances;
  6. function update(uint amount) returns (address addr){
  7. balances[msg.sender] = amount;
  8. return msg.sender;
  9. }
  10. }
  11. contract MappingUser{
  12. address conAddr;
  13. address userAddr;
  14. function f() returns (uint amount){
  15. //address not resolved!
  16. //tringing
  17. conAddr = hex"0xf2bd5de8b57ebfc45dcee97524a7a08fccc80aef";
  18. userAddr = hex"0xca35b7d915458ef540ade6068dfe2f44e8fa733c";
  19. return MappingExample(conAddr).balances(userAddr);
  20. }
  21. }

映射并未提供迭代输出的方法,可以自行实现一个数据结构。参见iterable mapping。

结构体(struct)

Solidity提供struct来定义自定义类型。我们来看看下面的例子:

  1. pragma solidity ^0.4.0;
  2. contract CrowdFunding{
  3. struct Funder{
  4. address addr;
  5. uint amount;
  6. }
  7. struct Campaign{
  8. address beneficiary;
  9. uint goal;
  10. uint amount;
  11. uint funderNum;
  12. mapping(uint => Funder) funders;
  13. }
  14. uint compaingnID;
  15. mapping (uint => Campaign) campaigns;
  16. function candidate(address beneficiary, uint goal) returns (uint compaingnID){
  17. // initialize
  18. campaigns[compaingnID++] = Campaign(beneficiary, goal, 0, 0);
  19. }
  20. function vote(uint compaingnID) payable {
  21. Campaign c = campaigns[compaingnID];
  22. //another way to initialize
  23. c.funders[c.funderNum++] = Funder({addr: msg.sender, amount: msg.value});
  24. c.amount += msg.value;
  25. }
  26. function check(uint comapingnId) returns (bool){
  27. Campaign c = campaigns[comapingnId];
  28. if(c.amount < c.goal){
  29. return false;
  30. }
  31. uint amount = c.amount;
  32. // incase send much more
  33. c.amount = 0;
  34. if(!c.beneficiary.send(amount)){
  35. throw;
  36. }
  37. return true;
  38. }
  39. }

上面的代码向我们展示的一个简化版的众筹项目,其实包含了一些struct的使用。struct可以用于映射和数组中作为元素。其本身也可以包含映射和数组等类型。

我们不能声明一个struct同时将这个struct作为这个struct的一个成员。这个限制是基于结构体的大小必须是有限的。

虽然数据结构能作为一个mapping的值,但数据类型不能包含它自身类型的成员,因为数据结构的大小必须是有限的。

需要注意的是在函数中,将一个struct赋值给一个局部变量(默认是storage类型),实际是拷贝的引用,所以修改局部变量值时,会影响到原变量。

当然,你也可以直接通过访问成员修改值,而不用一定赋值给一个局部变量,如campaigns[comapingnId].amount = 0

数组

数组可以声明时指定长度,或者是变长的。对storage1的数组来说,元素类型可以是任意的,类型可以是数组,映射类型,数据结构等。但对于memory1的数组来说。如果函数是对外可见的2,那么函数参数不能是映射类型的数组,只能是支持ABI的类型3。

一个类型为T,长度为k的数组,可以声明为T[k],而一个变长的数组则声明为T[]
你还可以声明一个多维数据,如一个类型为uint的数组长度为5的变长数组,可以声明为uint[][5] x。需要留心的是,相比非区块链语言,多维数组的长度声明是反的。

要访问第三个动态数据的,第二个元素,使用x[2][1]。数组的序号是从0开始的,序号顺序与定义相反。

bytesstring是一种特殊的数组。bytes类似byte[],但在外部函数作为参数调用中,会进行压缩打包,更省空间,所以应该尽量使用bytes4。string类似bytes,但不提供长度和按序号的访问方式。

由于bytesstring,可以自由转换,你可以将字符串s通过bytes(s)转为一个bytes。但需要注意的是通过这种方式访问到的是UTF-8编码的码流,并不是独立的一个个字符。比如中文编码是多字节,变长的,所以你访问到的很有可能只是其中的一个代码点。

类型为数组的状态变量,可以标记为public类型,从而让Solidity创建一个访问器,如果要访问数组的某个元素,指定数字下标就好了。

创建一个数组

可使用new关键字创建一个memory的数组。与stroage数组不同的是,你不能通过.length的长度来修改数组大小属性。我们来看看下面的例子:

  1. pragma solidity ^0.4.0;
  2. contract C {
  3. function f() {
  4. //创建一个memory的数组
  5. uint[] memory a = new uint[](7);
  6. //不能修改长度
  7. //Error: Expression has to be an lvalue.
  8. //a.length = 100;
  9. }
  10. //storage
  11. uint[] b;
  12. function g(){
  13. b = new uint[](7);
  14. //可以修改storage的数组
  15. b.length = 10;
  16. b[9] = 100;
  17. }
  18. }

在上面的代码中,f()方法尝试调整数组a的长度,编译器报错Error: Expression has to be an lvalue.。但在g()方法中我们看到可以修改5。

字面量及内联数组

数组字面量,是指以表达式方式隐式声明一个数组,并作为一个数组变量使用的方式。下面是一个简单的例子:

  1. pragma solidity ^0.4.0;
  2. contract C {
  3. function f() {
  4. g([uint(1), 2, 3]);
  5. }
  6. function g(uint[3] _data) {
  7. // ...
  8. }
  9. }

通过数组字面量,创建的数组是memory的,同时还是定长的。元素类型则是使用刚好能存储的元素的能用类型,比如代码里的[1, 2, 3],只需要uint8即可存储。由于g()方法的参数需要的是uint(默认的uint表示的其实是uint256),所以要使用uint(1)来进行类型转换。

还需注意的一点是,定长数组,不能与变长数组相互赋值,我们来看下面的代码:

  1. pragma solidity ^0.4.0;
  2. contract C {
  3. function f() {
  4. // The next line creates a type error because uint[3] memory
  5. // cannot be converted to uint[] memory.
  6. uint[] x = [uint(1), 3, 4];
  7. }

限制的主要原因是,ABI不能很好的支持数组,已经计划在未来移除这样的限制。(当前的ABI接口,不是已经能支持数组了?)

数组的属性和方法

length属性

数组有一个.length属性,表示当前的数组长度。storage的变长数组,可以通过给.length赋值调整数组长度。memory的变长数组不支持。

不能通过访问超出当前数组的长度的方式,来自动实现上面说的这种情况。memory数组虽然可以通过参数,灵活指定大小,但一旦创建,大小不可调整,对于变长数组,可以通过参数在编译期指定数组大小。

push方法

storage的变长数组和bytes都有一个push(),用于附加新元素到数据末端,返回值为新的长度。

  1. pragma solidity ^0.4.0;
  2. contract C {
  3. uint[] u;
  4. bytes b;
  5. function testArryPush() returns (uint){
  6. uint[3] memory a = [uint(1), 2, 3];
  7. u = a;
  8. return u.push(4);
  9. }
  10. function testBytesPush() returns (uint){
  11. b = new bytes(3);
  12. return b.push(4);
  13. }
  14. }

限制的情况

当前在外部函数中,不能使用多维数组。

另外,基于EVM的限制,不能通过外部函数返回动态的内容。

  1. pragma solidity ^0.4.0;
  2. contract C {
  3. function f() returns (uint[]) {
  4. }
  5. }

在上面的例子中,通过web.js调用能返回数据,但在Solidity中不能返回数据。一种临时的解决办法,是使用一个非常大的静态数组。

  1. pragma solidity ^0.4.0;
  2. contract ArrayContract {
  3. //the orginal length of m_aLotOfIntegers is 2**20
  4. //run it cause a out of gas,so change it to a much smaller 2**2 for test
  5. uint[2**2] m_aLotOfIntegers;
  6. // Note that the following is not a pair of arrays but an array of pairs.
  7. bool[2][] m_pairsOfFlags;
  8. // newPairs is stored in memory - the default for function arguments
  9. function setAllFlagPairs(bool[2][] newPairs) {
  10. // assignment to a storage array replaces the complete array
  11. m_pairsOfFlags = newPairs;
  12. }
  13. function setFlagPair(uint index, bool flagA, bool flagB) {
  14. // access to a non-existing index will throw an exception
  15. m_pairsOfFlags[index][0] = flagA;
  16. m_pairsOfFlags[index][1] = flagB;
  17. }
  18. function changeFlagArraySize(uint newSize) {
  19. // if the new size is smaller, removed array elements will be cleared
  20. m_pairsOfFlags.length = newSize;
  21. }
  22. function clear() {
  23. // these clear the arrays completely
  24. delete m_pairsOfFlags;
  25. delete m_aLotOfIntegers;
  26. // identical effect here
  27. m_pairsOfFlags.length = 0;
  28. }
  29. function addFlag(bool[2] flag) returns (uint) {
  30. return m_pairsOfFlags.push(flag);
  31. }
  32. function createMemoryArray(uint size) {
  33. // Dynamic memory arrays are created using `new`:
  34. bool[2][] memory arrayOfPairs = new bool[2][](size);
  35. m_pairsOfFlags = arrayOfPairs;
  36. }
  37. }

更多请查看这里的重新梳理: http://me.tryblockchain.org/solidity-array.html

参考资料


  1. 变量声明时定义的数据位置属性。可以是memory的,也可以为storage的,详见:http://me.tryblockchain.org/data-location.org ↩
  2. 函数的可见性说明,详见这里: http://me.tryblockchain.org/solidity-function-advanced1.html ↩
  3. 了解更多关于ABI的信息,https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#types ↩
  4. 因为字节数组在使用时会padding,所以不节省空间。https://github.com/chriseth/solidity/blob/48588b5c4f21c8bb7c5d4319b05a93ee995b457d/_docs/faq_basic.md#what-is-the-difference-between-bytes-and-byte ↩
  5. http://stackoverflow.com/questions/33839122/in-ethereum-solidity-when-changing-an-array-length-i-get-value-must-be-an-lva ↩

感谢您的支持

发表评论

表情:
评论列表 (有 0 条评论,1002人围观)

还没有评论,来说两句吧...

相关阅读