引用类型 #
一、引用类型概述 #
引用类型是指变量存储的是数据的位置(引用),而不是数据本身。修改引用会改变原始数据。
text
引用类型包括:
- 数组(Array)
- 结构体(Struct)
- 映射(Mapping)
存储位置:
- storage:永久存储在区块链上
- memory:临时存储,仅在函数执行期间存在
- calldata:函数参数专用,只读不可修改
二、数组 #
2.1 动态数组 #
solidity
// 动态数组:长度不固定
uint256[] public dynamicArray; // uint256动态数组
address[] public addressArray; // 地址动态数组
bytes public data; // bytes是动态字节数组
string public name; // string是动态字符串
2.2 定长数组 #
solidity
// 定长数组:长度固定
uint256[5] public fixedArray; // 5个uint256
address[3] public users; // 3个地址
bytes32[2] public dataArray; // 2个bytes32
2.3 数组字面量 #
solidity
// 固定长度数组字面量
uint256[3] memory arr1 = [1, 2, 3];
uint256[2] memory arr2 = [uint256(1), 2]; // 显式类型转换
// 动态数组字面量(需要创建)
uint256[] memory dynamicArr = new uint256[](3);
dynamicArr[0] = 1;
dynamicArr[1] = 2;
dynamicArr[2] = 3;
2.4 数组常用操作 #
solidity
contract ArrayDemo {
uint256[] public numbers;
function addNumber(uint256 num) public {
numbers.push(num); // 添加元素
}
function getLength() public view returns (uint256) {
return numbers.length; // 获取长度
}
function getElement(uint256 index) public view returns (uint256) {
require(index < numbers.length, "Out of bounds");
return numbers[index]; // 访问元素
}
function removeLast() public {
require(numbers.length > 0, "Empty array");
numbers.pop(); // 移除最后一个元素
}
function getSlice(uint256 start, uint256 length) public view returns (uint256[] memory) {
uint256[] memory result = new uint256[](length);
for (uint256 i = 0; i < length; i++) {
result[i] = numbers[start + i];
}
return result;
}
}
2.5 数组删除 #
solidity
uint256[] public arr = [1, 2, 3, 4, 5];
// 删除元素(将值重置为默认值,但长度不变)
delete arr[2]; // arr变为 [1, 2, 0, 4, 5]
// 删除整个数组
delete arr; // arr变为 []
2.6 bytes和string特殊操作 #
solidity
bytes public data = "Hello";
function demo() public {
// bytes操作
data.push(0x20); // 添加空格 -> "Hello "
data.length; // 长度
data[0]; // 访问第一个字节
// string需要先转为bytes
string memory s = "Hello";
bytes memory b = bytes(s); // 转换
b[0] = 0x48; // 修改 -> "Hella"
s = string(b); // 转回string
}
2.7 内存数组 #
solidity
function createMemoryArray() public pure returns (uint256[] memory) {
// 创建固定长度的内存数组
uint256[3] memory fixed = [1, 2, 3];
// 创建动态内存数组
uint256[] memory dynamic = new uint256[](5);
for (uint256 i = 0; i < 5; i++) {
dynamic[i] = i;
}
return dynamic;
}
三、结构体 #
3.1 定义结构体 #
solidity
struct User {
address wallet;
string name;
uint256 balance;
bool verified;
uint256 createdAt;
}
3.2 创建结构体 #
solidity
contract StructDemo {
struct User {
address wallet;
string name;
uint256 balance;
bool verified;
}
// 状态变量:结构体数组
User[] public users;
// 状态变量:结构体映射
mapping(address => User) public usersByAddress;
function createUser(string memory _name) public payable {
User memory newUser = User({
wallet: msg.sender,
name: _name,
balance: msg.value,
verified: false
});
// 另一种语法
User newUser2 = User(msg.sender, _name, msg.value, false);
users.push(newUser);
}
function getUser(uint256 index) public view returns (User memory) {
return users[index];
}
function updateUser(uint256 index, string memory _name) public {
require(users[index].wallet == msg.sender, "Not owner");
users[index].name = _name;
}
}
3.3 结构体与存储 #
solidity
struct Data {
uint256 value;
}
Data public data; // storage
Data[] public dataArray;
function demo() public {
// storage引用
Data storage s = data;
s.value = 10; // 修改原始数据
// memory拷贝
Data memory m = Data(10);
m.value = 20; // 不影响原始数据
// 数组中的结构体
Data storage d = dataArray[0];
d.value = 100;
}
3.4 结构体示例:众筹合约 #
solidity
contract Crowdfunding {
struct Campaign {
address owner;
string title;
string description;
uint256 targetAmount;
uint256 currentAmount;
uint256 deadline;
bool claimed;
}
Campaign[] public campaigns;
mapping(uint256 => mapping(address => uint256)) public contributions;
function createCampaign(
string memory _title,
string memory _description,
uint256 _targetAmount,
uint256 _durationInDays
) public {
Campaign memory campaign = Campaign({
owner: msg.sender,
title: _title,
description: _description,
targetAmount: _targetAmount,
currentAmount: 0,
deadline: block.timestamp + _durationInDays * 1 days,
claimed: false
});
campaigns.push(campaign);
}
function contribute(uint256 _campaignId) public payable {
Campaign storage campaign = campaigns[_campaignId];
require(block.timestamp < campaign.deadline, "Campaign ended");
campaign.currentAmount += msg.value;
contributions[_campaignId][msg.sender] += msg.value;
}
}
四、映射 #
4.1 映射基础 #
solidity
// 映射定义:mapping(keyType => valueType)
mapping(address => uint256) public balances; // 地址->余额
mapping(address => bool) public whitelist; // 地址->是否白名单
mapping(address => mapping(address => uint256)) public allowances; // 嵌套映射
// 键类型只能是:address、bytes、uint256、int256、string(仅storage)
// 值类型可以是任何类型
4.2 映射操作 #
solidity
contract MappingDemo {
mapping(address => uint256) public balances;
mapping(string => address) public nameToAddress;
function setBalance(address _addr, uint256 _balance) public {
balances[_addr] = _balance; // 设置值
}
function getBalance(address _addr) public view returns (uint256) {
return balances[_addr]; // 获取值,未设置返回默认值0
}
function deleteBalance(address _addr) public {
delete balances[_addr]; // 删除,恢复默认值
}
function increaseBalance(address _addr) public {
balances[_addr] += 1; // 增加
}
}
4.3 映射迭代 #
solidity
contract IterableMapping {
mapping(address => uint256) public balances;
address[] public keys;
function set(address _key, uint256 _value) public {
if (balances[_key] == 0) {
keys.push(_key);
}
balances[_key] = _value;
}
function getLength() public view returns (uint256) {
return keys.length;
}
function getByIndex(uint256 _index) public view returns (address, uint256) {
address key = keys[_index];
return (key, balances[key]);
}
function getSum() public view returns (uint256) {
uint256 sum = 0;
for (uint256 i = 0; i < keys.length; i++) {
sum += balances[keys[i]];
}
return sum;
}
}
4.4 嵌套映射 #
solidity
contract NestedMapping {
// tokenId => (owner => approved)
mapping(uint256 => mapping(address => bool)) public tokenApprovals;
function approve(address _to, uint256 _tokenId) public {
tokenApprovals[_tokenId][_to] = true;
}
function isApproved(uint256 _tokenId, address _to) public view returns (bool) {
return tokenApprovals[_tokenId][_to];
}
}
五、存储位置详解 #
5.1 storage #
solidity
contract StorageDemo {
uint256[] public arr; // 状态变量,storage
function modify() public {
uint256[] storage s = arr;
s.push(1); // 修改原始状态变量
}
}
5.2 memory #
solidity
contract MemoryDemo {
uint256[] public arr;
function read() public view returns (uint256[] memory) {
uint256[] memory copy = arr; // 拷贝到memory
return copy;
}
function process(string memory _input) public pure returns (string memory) {
string memory temp = _input;
return temp;
}
}
5.3 calldata #
solidity
contract CalldataDemo {
// calldata:只读,不可修改,省Gas
function processCalldata(uint256[] calldata _arr) public pure returns (uint256) {
// _arr.push(1); // 错误:calldata不可修改
return _arr[0]; // 读取
}
// 等价于:
function processMemory(uint256[] memory _arr) public pure returns (uint256) {
return _arr[0];
}
}
5.4 存储位置规则 #
| 场景 | 默认存储位置 | 可选位置 |
|---|---|---|
| 状态变量 | storage | - |
| 函数参数(引用类型) | memory | calldata |
| 函数内局部变量 | memory | storage(仅限引用类型) |
| 返回值 | memory | - |
5.5 存储位置赋值规则 #
solidity
contract AssignmentRules {
uint256[] public storageArray;
function demo() public {
// storage到memory:创建拷贝
uint256[] memory mem = storageArray;
mem[0] = 100; // 只修改memory,不影响storage
// memory到storage:创建拷贝
uint256[] memory mem2 = new uint256[](1);
mem2[0] = 100;
// storageArray = mem2; // 错误:不能直接赋值
// storage引用:指向同一位置
uint256[] storage s = storageArray;
s.push(1); // 直接修改storage
}
}
六、综合示例 #
6.1 代币合约数据结构 #
solidity
contract TokenDataStructures {
struct TokenInfo {
string name;
string symbol;
uint256 totalSupply;
uint256 decimals;
}
struct Account {
uint256 balance;
uint256[] tokenIds;
bool isBlacklisted;
}
TokenInfo public tokenInfo;
mapping(address => Account) public accounts;
address[] public accountList;
constructor() {
tokenInfo = TokenInfo({
name: "MyToken",
symbol: "MTK",
totalSupply: 1000000,
decimals: 18
});
}
function createAccount(address _owner) public {
require(accounts[_owner].balance == 0, "Account exists");
accounts[_owner] = Account({
balance: 0,
tokenIds: new uint256[](0),
isBlacklisted: false
});
accountList.push(_owner);
}
function mint(address _to, uint256 _amount) public {
require(!accounts[_to].isBlacklisted, "Blacklisted");
accounts[_to].balance += _amount;
}
}
七、最佳实践 #
7.1 选择正确的存储位置 #
solidity
// 推荐:函数参数使用memory或calldata
function transfer(address to, uint256 amount) public {
// to和amount是memory
}
// 推荐:引用类型参数使用calldata(如果只读)
function batchTransfer(address[] calldata recipients, uint256 amount) public {
// calldata更省Gas
}
7.2 避免无限数组 #
solidity
// 不推荐:可能达到区块Gas限制
function pushToArray(uint256 value) public {
arr.push(value); // 无限增长
}
// 推荐:限制数组大小
uint256 constant MAX_SIZE = 1000;
function safePush(uint256 value) public {
require(arr.length < MAX_SIZE, "Array full");
arr.push(value);
}
7.3 使用库合约 #
solidity
// OpenZeppelin的数组库
import "@openzeppelin/contracts/utils/Arrays.sol";
contract UsingArraysLib {
using Arrays for uint256[];
uint256[] public arr;
function findIndex(uint256 _value) public view returns (uint256) {
return arr.find(_value);
}
}
八、总结 #
引用类型要点:
| 类型 | 特点 | 示例 |
|---|---|---|
| 数组 | 相同类型元素集合 | uint256[], address[3] |
| 结构体 | 不同类型组合 | struct User {address wallet;} |
| 映射 | 键值对 | mapping(address => uint256) |
存储位置:
| 位置 | 生命周期 | 可变性 | Gas |
|---|---|---|---|
| storage | 永久 | 可修改 | 最贵 |
| memory | 函数调用 | 可修改 | 较便宜 |
| calldata | 函数调用 | 只读 | 最便宜 |
使用建议:
- 状态变量使用storage
- 函数参数使用memory或calldata
- 注意映射不能迭代
- 结构体可以包含所有类型
接下来,让我们学习类型转换!
最后更新:2026-03-27