Solidity 引用类型
引用类型相比值类型更复杂。由于其长度灵活,并不总能放入 256 字节之内,需要更小心地处理。普遍更高昂的拷贝成本也使得选择合适的存储位置变得异常重要。
存储位置
如上一章所提到,存储位置包括 storage
、 memory
以及为外部函数所用的 calldata
。
数组和结构体这些较为复杂的类型,都有额外标注存储位置。根据上下文的不同,默认的存储位置也并不相同:函数参数默认的存储位置是 memory
,局部变量默认的存储位置是 storage
,而状态变量的存储位置显然只能是 storage
。
变量存储位置的不同会导致赋值行为的不同:
- 从
storage
到memory
或从任意到状态变量的赋值,总是会创建与原来相独立的变量副本。 - 将变量赋值给局部的
storage
变量,该变量是前者的引用。若前者是状态变量,前者改变,后者依旧指向前者。 - 另一方面,将一个
memory
引用类型变量赋值给另一个memory
引用类型的变量,不会创建副本。
数组 T[]
、 T[k]
、 bytes
、 string
数组在编译时的长度可以固定,也可以是动态的。位于 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);
}
}
bytes
与 string
bytes
与 string
类型的变量是特殊的数组。 bytes
行为上与 byte[]
相似,但经过紧密打包 (packed tightly),极大程度降低了填充导致的空间浪费,因此,总是应该使用 bytes
而非 byte[]
。 string
和 bytes
基本一致,除了字符串(目前)不能通过索引访问其元素,也不能获得其长度。
提示:
如果想要访问字节表示的字符串,可以将
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 技术交流群。
上一篇: Solidity 值类型
下一篇: 彻底找到 Tomcat 启动速度慢的元凶
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论