引用类型

引用类型相比值类型更复杂。由于其长度灵活,并不总能放入 256 字节之内,需要更小心地处理。普遍更高昂的拷贝成本也使得选择合适的存储位置变得异常重要。

存储位置

如上一章所提到,存储位置包括 storagememory 以及为外部函数所用的 calldata

数组和结构体这些较为复杂的类型,都有额外标注存储位置。根据上下文的不同,默认的存储位置也并不相同:函数参数默认的存储位置memory局部变量默认的存储位置storage,而状态变量的存储位置显然只能是 storage

变量存储位置的不同会导致赋值行为的不同:

  • storagememory 或从任意到状态变量的赋值,总是会创建与原来相独立的变量副本。
  • 将变量赋值给局部的 storage 变量,该变量是前者的引用。若前者是状态变量,前者改变,后者依旧指向前者。
  • 另一方面,将一个 memory 引用类型变量赋值给另一个 memory 引用类型的变量,不会创建副本。
数组 T[]T[k]bytesstring

数组在编译时的长度可以固定,也可以是动态的。位于 storage 的数组,数组元素的类型没有限制(也包括数组、映射和结构体)。而对于位于 memory 中的数组而言,其元素的类型不能是映射。如果此数组位于公开可见的函数的参数当中,其元素类型还必须是 ABI 中规定的类型之一。

定长为 k 元素为 T 的数组写作 T[k],而动态长度的数组则是 T[]。例如,一个长度为 10 的 int 数组为 int[10],一个长度不定的 bool 数组为 bool[]。数组可以向嵌套,例如,包含不定个数个长度为 10 的 uint 数组的数组为 uint[10][]。若要访问数组元素,需要使用索引来访问,如:x[3]y[3][3]

数组包含的成员
  • length

length 存有数组元素的个数。

对于位于 storage 中的动态数组,可以通过赋值的形式调整数组的长度。不过,数组的长度不会在访问数组越界时自动扩大。

对于位于 memory 中的可变长数组,数组长度在创建数组时便已固定。

  • push

位于 storage 中的动态数组与 bytes(但不包括 string)有一个名为 push 的成员函数。该函数在数组末尾添加所给的新元素,并返回数组新的长度。

public 修饰符

作为状态变量的数组同样可以添加 public 修饰符。此时,getter 的参数将作为数组的索引,参数的个数为数组的维数。例如:

contract A {
    int[3][3][3] public arr;
    constructor() public {
        arr[0][1][2] = 100;
    }
}

contract B {
    function test() public returns(int) {
        A a = new A();
        // 返回 100
        return a.arr(0, 1, 2);
    }
}
bytesstring

bytesstring 类型的变量是特殊的数组。bytes 行为上与 byte[] 相似,但经过紧密打包 (packed tightly),极大程度降低了填充导致的空间浪费,因此,总是应该使用 bytes 而非 byte[]stringbytes 基本一致,除了字符串(目前)不能通过索引访问其元素,也不能获得其长度。

提示:

如果想要访问字节表示的字符串,可以将 s 转换为字节数组再进行操作:bytes(s).length / bytes(s)[7] = 'x';。但是需要注意,此处访问的是底层的 UTF-8 字节编码。

memory 上分配不定长数组

通过 new 关键词,可以在 memory 中创建不定长的数组,方法如下:

function test() public pure {
    uint[] memory a = new uint[](10);
    bytes memory b = new bytes(a.length);
}
数组字面量 / 内联数组

数组字面量的表示为:[ 项1, 项2, ...]

数组字面量的类型是定长,以所有元素共有的类型为基,存储于 memory 的数组。例如,[1, 2, 3] 的类型是 uint8[3] memory[-1, 2, 3]的类型是 int8[3] memory,而 [uint(1), 2, 3] 的类型则是 uint256[3] memory

目前,memory 上的定长数组无法赋值给 storage 上的动态数组。Solidity 社区打算在未来去掉这一条限制。

注意

目前数组还不支持在外部函数中使用。

注意

受到 EVM 的限制,无法实现从调用的外部函数返回动态内容。

目前唯一的 workaround 是用非常大的静态数组作为代替。

动态数组在 storage 中的存储形式
结构体 (struct)

Solidity 以结构体的形式提供了定义新数据类型的方法。定义结构体的形式为:

struct 结构体名 {
    类型 成员名
    类型 成员名
    ...
}

例如:

struct Voter {
    bool hsaRight;
    uint votedProposal;
}
struct Group {
    Member leader;
    bytes32 shortName;
    uint numMembers;
    mapping(uint => Member) members;
}

struct Member {
    bytes32 name;
    uint memberID;
    uint numFriends;

    // 无法通过编译,结构体循环定义
    // Member bestFriend;

    mapping(uint => Member) friends;
}

如上所示,结构体无法包含与自己相同类型的成员,不过,结构体的成员可以是值类型与结构体自身相同的映射。

以上面的代码为例,初始化类型为 Group,位于 memory 的变量:

Group memory g1 = Group(Member("abc", 1, 0), "g1", 0);
// 或
Group memory g2 = Group({
    leader: Member("abc", 1, 0),
    shortName: "g2",
    numMembers: 0
});

可见,结构体中的映射成员无需初始化,而其他类型的成员在初始化时都不能缺省。

results matching ""

    No results matching ""