Solidity变量赋值及元组的解构赋值(十六)|入门系列

2017/5/7

1. 赋值规则

赋值是将一个变量赋值给另一个变量。赋值时遵循以下规则:

1.1 赋值给状态变量

赋值给状态变量,总是值传递。

  • 从状态状态赋值给状态变量总是创建一个无关的拷贝。
pragma solidity ^0.4.0;

contract StateToState{
  uint8[] arr1;
  uint8[] arr2;

  uint8 i1;
  uint8 i2;

  function f() returns (uint8, uint8, uint8, uint8){
    arr1 = new uint8[](2);
    arr1[0] = 100;
    arr2 = arr1;
    arr1[0] = 150;

    i1 = 200;
    i2 = i1;
    i1 = 250;

    return (arr1[0], arr2[0], i1, i2);//150, 100, 250, 200
  }
}

上面的例子中,将arr2赋值为arr1后,修改arr1的第一个元素的值为150,并未影响到arr2。对于基本类型也是如此,通过i2 = i1;赋值后,修改i1完全不影响i2

由于赋值时是值传递,总是创建一份拷贝,上述代码的执行结果是150100250200

  • 从局部变量赋值到状态变量也总是创建一个无关的拷贝。
pragma solidity ^0.4.0;

contract LocalToState{
  uint8[] stateVal;
  uint8 stateI;

  function f() returns(uint8, uint8, uint8, uint8){
    uint8[] memory arr = new uint8[](3);

    stateVal = arr;
    arr[0] = 100;

    var i = 200;
    stateI = i;
    i = 250;

    return (arr[0], stateVal[0], i, stateI);//100, 0, 250, 200
  }
}

在上面的例子中,当局部变量赋值给状态变量后,其后对局部变量的更改都不会影响到状态变量。

1.2 赋值给本地变量

  • 从状态变量到局部变量,其中基本类型是创建一份无关的拷贝。但对于引用类型,如结构体,数组(包含bytesstring)则是传递的引用。
pragma solidity ^0.4.0;

contract StateToLocal{
  uint8[] stateArr;
  uint8 stateI;

  function f() returns(uint8, uint8, uint8, uint8){
    stateArr = new uint8[](2);
    stateArr[0] = 100;
    uint8[] arr = stateArr;
    arr[0] = 150;

    stateI = 200;
    uint8 i = stateI;
    i = 250;

    return (arr[0], stateArr[0], i, stateI);//150, 150, 250, 200
  }
}

上面的例子中,状态状态通过uint8 i = stateI;赋值给局部变量后,对i的修改,并不影响状态变量。而对于引用类型则是引用传递,使用arr[0] = 150赋新值后,会影响到状态变量stateArr[0]

所以上述代码的运行结果是150150250200

  • 而对于本地变量到本地变量,基本类型是值传递,总是拷贝一个完全无关的对象。而对于复杂类型,如结构体和数组,则是引用传递。
pragma solidity ^0.4.0;

contract LocalToLocal{

  function f() returns (uint, uint, uint, uint){
    uint a = 100;
    uint b = a;
    a = 150;

    uint[] memory arr1 = new uint[](2);
    uint[] memory arr2 = new uint[](2);
    arr1[0] = uint(200);
    arr2[0] = uint(200);

    arr2 = arr1;
    arr1[0] = 250;

    return (a, b, arr1[0], arr2[0]);//150,100,250,250
  }

}

上面的例子告诉我们,对于基本类型通过b = a;赋值后,对a的修改并不会影响b。而对于引用类型arr2,通过arr2 = arr1;,修改arr1[0] = 250;,会影响到arr2[0]

所以上述代码的执行结果是150100250250

2 元组的解构赋值

Solidity还允许元组解构赋值。它是指一个长度在编译期知道的,不同类型的对象列表,即元组,可以同时赋值给多个变量1,这其实与Javascript里提出的解构赋值的概念类似,但相比Javascript提供的特性只是小巫见大巫。

Solidity的元组2,是由括号包含的,以英文逗号分隔的一个或多个值,如(var1, var2, var3)。一个元组对象,同时赋值给多个变量,这就是解构赋值。下面我们来看一个元组解构赋值的例子。

pragma solidity ^0.4.0;

contract TupleAssign{
  function f() returns(uint8, string, uint8[]){
    var (a, b, c) = (1, "Hello world", new uint8[](1));

    return (a, b, c);
  }
}

我们可以看到,等号两边的对象数量相等时,右边的值会一一赋值到左边的变量。

并且Solidity并不允许我们,左右两边的数量不相等。如果变量过多,会报错Not enough components in value to assign all variables;而如果值过多,会报错Too many components in value for variable assignment.

不过Solidity其实支持变量与值非一一对应。

pragma solidity ^0.4.0;

contract TupleIgnore{
  function f() returns(uint8, uint8, uint8, uint8, uint8, uint8, uint8, uint8){
    //按前对齐赋值
    var (a, b, ) = (1, 2, 3, 4);//1, 2

    //跳过一些值
    var (c, , d) = (4, 5, 6);//4, 6

    //后对齐赋值
    var (,e) = (7, 8, 9);//9
    var (, f, , g) = (10, 11, 12, 13, 14);//12, 14

    //特殊的单元组
    var (h) = (15);

    //Stack too deep, try removing local variables.
    //var(, i, ) = (16, 17, 18);

    return (a, b, c, d, e, f, g, h);
  }
}

通过上面的例子可以看出,我们可以在变量处以逗号结束,来按前对齐赋值。即使后面有更多值都将被忽略,如var (a, b, ) = (1, 2, 3, 4);。如果我们想后对齐来进行快速赋值,可以以逗号开头,如var (,e) = (7, 8, 9);。另外我们可以随时用空白来跳过不感兴趣的值。但是不能像var(, i, ) = (16, 17, 18);那样前后都用逗号分隔,会报错Stack too deep, try removing local variables

2.1 解构赋值的应用场景

2.1.1 交换变量

解构赋值可以用来做什么呢,一个最简单的实践场景是交换两个变量值,下面是一个使用元组交换对象值的例子。

pragma solidity ^0.4.0;

contract TupleExchange{
  function f() returns (uint8, uint8){
    var a = 1;
    var b = 2;

    (a, b) = (b, a);

    return (a, b);//2,1
  }
}

上面的例子返回的结果将为21,实现了变量的值交换。

2.1.2 函数返回值

使用元组,函数可以同时返回多个值,所以我们可以使用元组的解构赋值来方便的取出元组值。

pragma solidity ^0.4.0;

contract FunctionTuple{

   string public recevier;
   uint8 public recevierAge;

  function f()  private returns(string, uint8){
    string memory name = "jack";
    uint8 age = 12;
    return (name, age);
  }

  function  g(){
    //更新其它值
    (recevier, recevierAge) = f();
  }
}

上面的例子中,函数的返回元组值,被解构赋值给了状态变量recevierrecevierAge

关于作者

专注基于以太坊(Ethereum)的相关区块链(Blockchain)技术,了解以太坊,Solidity,Truffle,web3.js。

个人博客: http://tryblockchain.org
版权所有,转载注明出处

参考资料

处于某些特定的环境下,可以看到评论框,欢迎留言交流^_^。

友情链接: 区块链技术中文社区    深入浅出区块链