Solidity 引用类型

发布于 2024-06-10 15:13:35 字数 4657 浏览 17 评论 0

引用类型相比值类型更复杂。由于其长度灵活,并不总能放入 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
});

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

0 文章
0 评论
21 人气
更多

推荐作者

新人笑

文章 0 评论 0

mb_vYjKhcd3

文章 0 评论 0

小高

文章 0 评论 0

来日方长

文章 0 评论 0

哄哄

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文