引用类型详解:数组、结构体、映射、字符串
学习目标
掌握 Solidity 中四种引用类型(数组、结构体、映射、字符串/变长字节数组)的定义和使用方法。
前置知识
已学习值类型(整型、布尔、地址、定长字节数组等)。
数组(Array)
storage 中的数组
Solidity 中数组分为两种:
- 静态数组
T[K]:长度固定,编译时确定 - 动态数组
T[]:长度可变,运行时动态增减
关键规则:
push()/pop()只能操作 storage 中的动态数组public数组自动生成的 getter 函数,参数是下标(index)- 数组元素可以是任何类型,包括 struct 和 mapping
memory 中的数组
- 必须用
new关键字初始化,且长度在创建时确定 - 创建后长度固定,不能使用 push / pop
- 适用于函数内部的临时计算
完整示例
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
contract ArrayType {
uint8[3] data; // storage 静态数组
uint8[] ddata; // storage 动态数组
// 返回静态数组(拷贝到 memory 返回)
function testStaticArray() public view returns(uint8[3] memory) {
return data;
}
// 读取动态数组
function testReadDynamicArray() public view returns(uint8[] memory) {
return ddata;
}
// 动态数组写操作
function testWriteDynamicArray() public {
ddata.push(12);
ddata.pop();
ddata.push(90);
}
// 内存中的动态数组
function testMemoryDynamicArray(uint8 size) public pure returns(uint8[] memory) {
uint8[] memory mdata = new uint8[](size); // 必须初始化,不能 push/pop
return mdata;
}
}
结构体(Struct)
基本概念
结构体用来自定义数据类型,与 contract、enum 类似,是一种用户定义类型。
特点:
- 可作为状态变量、局部变量、函数参数和返回值
- 可放在 mapping 和数组中
- 成员可以是 mapping 或数组
基础示例
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
contract StructType {
struct Person {
string name;
uint8 age;
}
Person master;
function readPerson() public view returns(Person memory) {
return master;
}
function writePerson(Person memory p) public {
master = p;
}
function writePersonName(string memory name) public {
master.name = name;
}
// memory 中的 struct 不需要 new
function testMemoryStruct() public pure returns(Person memory) {
Person memory p;
p.name = "zhangsan";
p.age = 25;
return p;
}
// storage 局部变量指向成员变量的数据块
function testStorageLocalStruct() public view returns(Person memory) {
Person storage p = master;
// 修改 p 会直接修改 master!
return p;
}
}
进阶示例:结构体与数组、映射的组合
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
contract Structs {
struct Car {
string model;
uint year;
address owner;
}
Car public car;
Car[] public cars;
mapping(address => Car[]) public carsByOwner;
function examples() external {
// 三种初始化方式
Car memory toyota = Car("Toyota", 1990, msg.sender);
Car memory lambo = Car({year: 1980, model: "Lamborghini", owner: msg.sender});
Car memory tesla;
tesla.model = "Tesla";
tesla.year = 2010;
tesla.owner = msg.sender;
cars.push(toyota);
cars.push(lambo);
cars.push(tesla);
carsByOwner[msg.sender].push(toyota);
cars.push(Car("Ferrari", 2020, msg.sender));
// storage 引用可以修改原始数据
Car storage _car = cars[0];
_car.year = 1999;
// 删除字段(重置为默认值)
delete _car.owner;
delete cars[1];
}
}
注意:
delete不会从数组中移除元素,而是将对应位置重置为默认值(零值)。数组长度不变。
映射(Mapping)
声明语法
mapping(keyType => valueType) visibility variableName;
类型约束
- keyType:任何基本类型(
uint、address、bool、bytes、string等),不能是用户自定义的复杂类型(struct、mapping、数组) - valueType:任何类型,包括 mapping(嵌套映射)
使用限制
- 只能作为状态变量、storage 局部变量、库函数参数
- 不能作为 public 函数的参数和返回值
public的 mapping 自动生成 getter 函数(以 key 作为参数)- 无法遍历(由于 storage layout 的设计,key 经过哈希后分散存储)
示例
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
contract MappingType {
mapping(string => uint8) public ages;
function getAge(string memory name) public view returns(uint8) {
return ages[name];
}
function setAge(string memory name, uint8 age) public {
ages[name] = age;
}
}
提示:访问 mapping 中不存在的 key 不会报错,而是返回 valueType 的默认值(如
uint返回 0,address返回0x0)。
变长字节数组与字符串
bytes 与 string
bytes 和 string 都是引用类型(不是值类型),它们的底层存储结构类似动态数组。
关键区别:
string是 UTF-8 编码的字节数组,不可通过索引访问单个字符bytes是原始字节数组,可以通过索引访问- Solidity 没有内置字符串操作函数(拼接、截取等需要借助库或转为
bytes操作) string和bytes之间可以互相转换(不拷贝数据)
使用规则
| 场景 | 推荐类型 |
|---|---|
| 任意长度的原始数据 | bytes |
| 任意长度的字符串(文本) | string |
| 固定长度的数据(1~32 字节) | bytes1 ~ bytes32(值类型,更省 gas) |
示例
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
contract BytesAndString {
string name = "BytesAndString";
bytes name1 = "BytesAndString1";
function testStringAndBytes() public view returns(string memory) {
string memory data = "xyz";
bytes memory data1 = "abc";
// storage 到 memory 的拷贝
data = name;
data1 = name1;
// 类型转换
data1 = bytes(data);
data = string(data1);
return data;
}
}
小结
| 类型 | 关键特点 |
|---|---|
| 数组 | storage 可动态增减,memory 定长;push/pop 仅限 storage 动态数组 |
| 结构体 | 自定义复合类型;memory 中无需 new;storage 局部变量是引用 |
| 映射 | 键值对存储;不可遍历;不能作为函数参数/返回值 |
| string/bytes | 引用类型;string 不可索引;固定长度优先用 bytesN 节省 gas |
下一篇:存储位置与拷贝机制 —— 理解 storage、memory、calldata 的区别及引用类型的拷贝规则。
主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://walker-learn.xyz/archives/7489