在上一节中,我们学习了合约的基本结构,了解了如何定义状态变量和函数。现在让我们深入探索 Solidity 的类型系统。
Solidity 是一门静态类型语言,这意味着每个变量(状态变量或局部变量)的类型都必须在编译时确定。这与 C、C++、Java 等语言类似,需要在编码时为每个变量显式指定类型。
Solidity 提供了丰富的基本类型,你也可以使用基本类型组合出新的复杂类型,如结构体。理解类型系统是编写安全、高效智能合约的基础。
Solidity 类型分:
这篇文章,我们会各种类型的特性过一个概括性介绍,在随后的几篇文章中,我们会对一些重要的类型分别做介绍。
值类型变量表示可以用 32 个字节(或更少)表示的数据。值类型的特点是:在赋值或传参时,总是进行值拷贝。
值类型包含:
值类型示例:
pragma solidity >=0.8.0;
contract ValueTypeExample {
// 值类型赋值会进行拷贝
function testValueType() public pure returns (uint, uint) {
uint a = 10;
uint b = a; // 拷贝 a 的值给 b
b = 20; // 修改 b 不会影响 a
return (a, b); // 返回 (10, 20)
}
}
提示: 对于
public类型的状态变量,Solidity 编译器会自动创建一个同名的访问器函数,用来获取状态变量的值(不用再额外写函数来获取变量的值)。
引用类型用来表示复杂类型,占用的空间通常超过 32 字节,拷贝时开销很大。因此可以使用引用的方式,通过多个不同名称的变量指向同一个值。
引用类型包括:
在定义引用类型时,必须显式指定一个额外的属性来标识数据的存储位置:
memory(内存):变量在运行时存在,其生命周期只存在于函数调用期间。函数执行完毕后,内存中的数据会被释放。
storage(存储):保存状态变量,只要合约存在就一直保存在区块链中。这是最昂贵的存储方式,因为数据永久保存在链上。
calldata(调用数据):用来存储函数参数的特殊数据位置,用来接收外部数据。它是一个不可修改的、非持久的函数参数存储区域,通常用于 external 函数的参数。
提示:在 2024 年 Cancun 升级中新添加的 transient(瞬时存储) 位置不能用来定义引用类型。
核心规则: 引用类型在进行赋值时的行为取决于数据位置:
| 赋值方式 | 行为 | 说明 |
|---|---|---|
| 不同位置赋值 | 创建副本(拷贝) | memory → storage 或 storage → memory |
| 同一位置赋值 | 创建引用(指针) | storage → storage 或 memory → memory |
示例代码:
pragma solidity >=0.8.0;
contract ReferenceTypeExample {
uint[] x; // 状态变量 x 的数据存储位置是 storage
function testAssignment(uint[] memory memoryArray) public {
// 1. 不同位置赋值 - 创建副本
x = memoryArray; // 从 memory 拷贝到 storage
// 2. 同一位置赋值 - 创建引用
uint[] storage y = x; // y 和 x 指向同一个 storage 位置
y.push(100); // 修改 y 也会修改 x
// 3. memory 到 memory - 也是创建引用
uint[] memory z = memoryArray;
z[0] = 999; // 修改 z 也会修改 memoryArray
}
function getReferenceExample() public view returns (uint[] memory) {
return x;
}
}
重要提示:
- 状态变量(state variables)总是存储在
storage中- 函数参数默认存储在
memory中(external函数参数除外,它们在calldata中)- 局部变量如果是引用类型,必须显式指定数据位置
不同数据位置的 Gas 消耗对比(从高到低):
| 存储位置 | Gas 消耗 | 生命周期 | 可修改性 |
|---|---|---|---|
| storage | 最高 | 永久保存(合约存在期间) | 可修改 |
| memory | 中等 | 函数调用期间 | 可修改 |
| transient | 较低 | 单个交易期间 | 可修改 |
| calldata | 最低 | 函数调用期间 | 不可修改(只读) |
选择建议:
storagememoryexternal 函数的参数优先使用 calldata(更省 Gas)transient映射(Mapping)类型在功能上类似于 Java 的 Map 或 Python 的 Dict,它是一种键值对的映射关系存储结构。
定义语法:
mapping(KeyType => ValueType) mappingName;
深入学习: 映射类型的详细用法和高级特性,将在后续的数据结构章节中详细介绍。
本节我们对 Solidity 的类型有一个基础了解:类型分3类:
引用类型我们需要考虑数据位置:
storage - 永久存储,最贵memory - 临时存储,中等开销calldata - 只读参数,最便宜不同位置赋值时进行拷贝,相同位置创建则引用
掌握这些基础知识后,我们就可以开始学习具体的类型用法了。在接下来的章节中,我们将深入学习每一种类型的详细用法和最佳实践。
准备好挑战了吗?前往 Solidity 101:数据结构,完成挑战并获得技能认证 NFT。