Solidity 引用类型

复杂类型。不同于之前值类型,复杂类型占的空间更大,超过256字节,因为拷贝它们占用更多的空间。由此我们需要考虑将它们存储在什么位置内存(memory,数据不是永久存在的)或存储(storage,值类型中的状态变量)
常见的引用类型有:
1. 不定长字节数组(bytes)
2. 字符串(string)
3. 数组(Array)
4. 结构体(Struts)

数据位置(Data location)

复杂类型,如数组(arrays)和数据结构(struct)在Solidity中有一个额外的属性,数据的存储位置。可选为memory和storage。

memory存储位置同我们普通程序的内存一致。即分配,即使用,越过作用域即不可被访问,等待被回收。而在区块链上,由于底层实现了图灵完备,故而会有非常多的状态需要永久记录下来。比如,参与众筹的所有参与者。那么我们就要使用storage这种类型了,一旦使用这个类型,数据将永远存在。

基于程序的上下文,大多数时候这样的选择是默认的,我们可以通过指定关键字storage和memory修改它。

默认的函数参数,包括返回的参数,他们是memory。默认的局部变量是storage的。而默认的状态变量(合约声明的公有变量)是storage。

另外还有第三个存储位置calldata。它存储的是函数参数,是只读的,不会永久存储的一个数据位置。外部函数的参数(不包括返回参数)被强制指定为calldata。效果与memory差不多。

数据位置指定非常重要,因为不同数据位置变量赋值产生的结果也不同。在memory和storage之间,以及它们和状态变量(即便从另一个状态变量)中相互赋值,总是会创建一个完全不相关的拷贝。

将一个storage的状态变量,赋值给一个storage的局部变量,是通过引用传递。所以对于局部变量的修改,同时修改关联的状态变量。但另一方面,将一个memory的引用类型赋值给另一个memory的引用,不会创建另一个拷贝。

pragma solidity ^0.4.0;
contract DataLocation{
    uint valueType;
    mapping(uint => uint) public refrenceType;
    function changeMemory(){
        var tmp = valueType;
        tmp = 100;
    }
    function changeStorage(){
        var tmp = refrenceType;
        tmp[1] = 100;
    }
    function getAll() returns (uint, uint){
        return (valueType, refrenceType[1]);
    }
}

下面来看下官方的例子说明:

pragma solidity ^0.4.0;
contract C {
    uint[] x; // the data location of x is storage
    // the data location of memoryArray is memory
    function f(uint[] memoryArray) {
        x = memoryArray; // works, copies the whole array to storage
        var y = x; // works, assigns a pointer, data location of y is storage
        y[7]; // fine, returns the 8th element
        y.length = 2; // fine, modifies x through y
        delete x; // fine, clears the array, also modifies y
        // The following does not work; it would need to create a new temporary /
        // unnamed array in storage, but storage is "statically" allocated:
        // y = memoryArray;
        // This does not work either, since it would "reset" the pointer, but there
        // is no sensible location it could point to.
        // delete y;
        g(x); // calls g, handing over a reference to x
        h(x); // calls h and creates an independent, temporary copy in memory
    }
    function g(uint[] storage storageArray) internal {}
    function h(uint[] memoryArray) {}
}

总结

强制的数据位置(Forced data location)

  • 外部函数(External function)的参数(不包括返回参数)强制为:calldata
  • 状态变量(State variables)强制为: storage

默认数据位置(Default data location)

  • 函数参数(括返回参数:memory
  • 所有其它的局部变量:storage

更多请查看关于数据位置的进一步挖掘: 这里

说白了就是传值调用引用调用的区别

数组

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

数组声明

一个类型为T,长度为k的数组,可以声明为T[k],而一个变长的数组则声明为T[]。

你还可以声明一个多维数据,如一个类型为uint的数组长度为5的变长数组,可以声明为uint[][5] x。

需要留心的是,相比非区块链语言,多维数组的长度声明是反的。

数组调用

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

bytes和string是一种特殊的数组。bytes类似byte[],但在外部函数作为参数调用中,会进行压缩打包,更省空间,所以应该尽量使用bytes4。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()方法中我们看到可以修改。

字面量及内联数组

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

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属性

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

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

push方法

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;
    }
}