状态变量 #
一、数据位置概述 #
Solidity有三种数据位置:
| 位置 | 生命周期 | 可变性 | Gas成本 |
|---|---|---|---|
| storage | 永久 | 可修改 | 最高 |
| memory | 函数调用 | 可修改 | 中等 |
| calldata | 函数调用 | 只读 | 最低 |
二、storage #
2.1 基本概念 #
storage是永久存储,数据存储在区块链上。
solidity
contract StorageDemo {
// 状态变量默认存储在storage
uint256 public count;
address public owner;
mapping(address => uint256) public balances;
// storage变量永久存在
function increment() public {
count++; // 修改storage
}
}
2.2 storage引用 #
solidity
contract StorageReference {
uint256[] public numbers;
function modifyStorage() public {
// storage引用:指向原始数据
uint256[] storage arr = numbers;
arr.push(1); // 修改原始数据
// 等价于
numbers.push(1);
}
function readStorage() public view returns (uint256[] memory) {
// 从storage复制到memory
return numbers;
}
}
2.3 storage布局 #
solidity
contract StorageLayout {
// storage槽从0开始
uint256 public a; // slot 0
uint256 public b; // slot 1
address public owner; // slot 2
bool public active; // slot 3
// 小于32字节的变量可以打包
uint128 public x; // slot 4 (前16字节)
uint128 public y; // slot 4 (后16字节)
// 数组
uint256[] public arr; // slot 5存储长度,数据从keccak256(5)开始
// mapping
mapping(address => uint256) public map; // slot 6,数据存储在keccak256(key . slot)
}
2.4 storage操作 #
solidity
contract StorageOperations {
struct User {
string name;
uint256 balance;
}
User public user;
uint256[] public numbers;
// 修改storage
function modifyUser(string memory _name, uint256 _balance) public {
user.name = _name;
user.balance = _balance;
}
// 使用storage引用
function modifyWithRef() public {
User storage u = user;
u.balance += 100;
}
// 数组操作
function pushNumber(uint256 value) public {
numbers.push(value);
}
function popNumber() public {
numbers.pop();
}
function updateNumber(uint256 index, uint256 value) public {
numbers[index] = value;
}
// 删除(重置为默认值)
function deleteUser() public {
delete user;
}
function deleteNumber(uint256 index) public {
delete numbers[index];
}
}
三、memory #
3.1 基本概念 #
memory是临时存储,仅在函数执行期间存在。
solidity
contract MemoryDemo {
// memory用于函数内的临时变量
function processArray(uint256[] memory arr) public pure returns (uint256) {
uint256 sum = 0;
for (uint256 i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
// 创建memory数组
function createMemoryArray() public pure returns (uint256[] memory) {
uint256[] memory arr = new uint256[](3);
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
return arr;
}
}
3.2 memory使用场景 #
solidity
contract MemoryUsage {
// 1. 函数参数
function processString(string memory str) public pure returns (string memory) {
return str;
}
// 2. 函数返回值
function getArray() public pure returns (uint256[] memory) {
uint256[] memory arr = new uint256[](3);
return arr;
}
// 3. 函数内临时变量
function tempVariable() public pure returns (uint256) {
uint256[] memory temp = new uint256[](10);
uint256 sum = 0;
for (uint256 i = 0; i < temp.length; i++) {
temp[i] = i;
sum += temp[i];
}
return sum;
}
// 4. 从storage复制
uint256[] public data;
function copyFromStorage() public view returns (uint256[] memory) {
uint256[] memory copy = data; // 从storage复制到memory
return copy;
}
}
3.3 memory操作 #
solidity
contract MemoryOperations {
// 创建memory数组
function createArray(uint256 size) public pure returns (uint256[] memory) {
uint256[] memory arr = new uint256[](size);
for (uint256 i = 0; i < size; i++) {
arr[i] = i;
}
return arr;
}
// 创建memory结构体
struct User {
string name;
uint256 balance;
}
function createUser() public pure returns (User memory) {
User memory u = User("Alice", 100);
return u;
}
// 修改memory数据
function modifyMemory() public pure returns (uint256[] memory) {
uint256[] memory arr = new uint256[](3);
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
// 修改memory数组
arr[0] = 10;
return arr;
}
}
四、calldata #
4.1 基本概念 #
calldata是只读的临时存储,用于外部函数参数。
solidity
contract CalldataDemo {
// calldata参数:只读,更省Gas
function processCalldata(uint256[] calldata arr) public pure returns (uint256) {
uint256 sum = 0;
for (uint256 i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
// calldata不能修改
// function badModify(uint256[] calldata arr) public pure {
// arr[0] = 1; // 错误:calldata是只读的
// }
}
4.2 calldata vs memory #
solidity
contract CalldataVsMemory {
// memory:可修改,消耗更多Gas
function withMemory(uint256[] memory arr) public pure returns (uint256) {
arr[0] = 100; // 可以修改
return arr[0];
}
// calldata:只读,消耗更少Gas
function withCalldata(uint256[] calldata arr) public pure returns (uint256) {
// arr[0] = 100; // 错误:不能修改
return arr[0];
}
// 推荐使用calldata(如果不需要修改)
}
4.3 calldata使用场景 #
solidity
contract CalldataUsage {
// 1. 只读参数
function sumArray(uint256[] calldata arr) public pure returns (uint256) {
uint256 sum = 0;
for (uint256 i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
// 2. 字符串处理
function processString(string calldata str) public pure returns (bytes32) {
return keccak256(bytes(str));
}
// 3. 批量操作
function batchProcess(
address[] calldata recipients,
uint256[] calldata amounts
) public pure returns (uint256) {
require(recipients.length == amounts.length, "Length mismatch");
uint256 total = 0;
for (uint256 i = 0; i < amounts.length; i++) {
total += amounts[i];
}
return total;
}
}
五、数据位置规则 #
5.1 默认位置 #
solidity
contract DefaultLocation {
// 状态变量:默认storage
uint256 public count;
// 函数参数:引用类型默认memory
function process(uint256[] memory arr) public pure {}
// 局部变量:
// - 值类型:栈上
// - 引用类型:需要显式指定
}
5.2 赋值规则 #
solidity
contract AssignmentRules {
uint256[] public storageArray;
function demo() public {
// storage -> storage:引用
uint256[] storage s = storageArray;
s.push(1); // 修改原始数据
// storage -> memory:复制
uint256[] memory m = storageArray;
m[0] = 100; // 不影响storage
// memory -> storage:复制
uint256[] memory temp = new uint256[](1);
temp[0] = 1;
// storageArray = temp; // 需要逐个赋值或使用特定方法
// memory -> memory:复制
uint256[] memory m2 = m;
m2[0] = 200; // 不影响m
// calldata -> memory:复制
// calldata -> storage:需要逐个赋值
}
function fromCalldata(uint256[] calldata arr) public {
// calldata -> memory
uint256[] memory m = arr;
// calldata -> storage(需要逐个赋值)
for (uint256 i = 0; i < arr.length; i++) {
storageArray.push(arr[i]);
}
}
}
六、实际应用示例 #
6.1 数据处理 #
solidity
contract DataProcessing {
uint256[] public data;
// 从calldata批量添加
function batchAdd(uint256[] calldata items) public {
for (uint256 i = 0; i < items.length; i++) {
data.push(items[i]);
}
}
// 处理并返回memory数组
function processData() public view returns (uint256[] memory) {
uint256[] memory result = new uint256[](data.length);
for (uint256 i = 0; i < data.length; i++) {
result[i] = data[i] * 2;
}
return result;
}
// 修改storage
function doubleAll() public {
for (uint256 i = 0; i < data.length; i++) {
data[i] *= 2;
}
}
}
6.2 结构体处理 #
solidity
contract StructProcessing {
struct User {
string name;
uint256 balance;
bool active;
}
User[] public users;
// 使用memory创建
function createUser(string memory _name, uint256 _balance) public {
User memory newUser = User({
name: _name,
balance: _balance,
active: true
});
users.push(newUser);
}
// 使用storage修改
function updateUser(uint256 index, uint256 newBalance) public {
User storage u = users[index];
u.balance = newBalance;
}
// 返回memory副本
function getUser(uint256 index) public view returns (User memory) {
return users[index];
}
}
6.3 字符串处理 #
solidity
contract StringProcessing {
string public storedString;
// 使用memory处理字符串
function processString(string memory str) public pure returns (string memory) {
bytes memory b = bytes(str);
// 处理字符串
return string(b);
}
// 使用calldata节省Gas
function hashString(string calldata str) public pure returns (bytes32) {
return keccak256(bytes(str));
}
// 存储字符串
function storeString(string memory str) public {
storedString = str;
}
}
七、最佳实践 #
7.1 选择正确的数据位置 #
solidity
contract BestPractices {
// 1. 状态变量:storage
uint256 public count;
// 2. 函数参数:
// - 只读:calldata(更省Gas)
// - 需要修改:memory
// 推荐:只读参数使用calldata
function readOnly(uint256[] calldata arr) public pure returns (uint256) {
return arr.length;
}
// 需要:修改参数使用memory
function needModify(uint256[] memory arr) public pure returns (uint256) {
arr[0] = 100;
return arr[0];
}
// 3. 局部变量:
// - 需要修改storage:storage引用
// - 临时数据:memory
}
7.2 Gas优化 #
solidity
contract GasOptimization {
uint256[] public data;
// 不推荐:每次都从storage读取
function badLoop() public view returns (uint256) {
uint256 sum = 0;
for (uint256 i = 0; i < data.length; i++) {
sum += data[i]; // 每次从storage读取
}
return sum;
}
// 推荐:缓存到memory
function goodLoop() public view returns (uint256) {
uint256[] memory arr = data; // 一次性复制到memory
uint256 sum = 0;
for (uint256 i = 0; i < arr.length; i++) {
sum += arr[i]; // 从memory读取
}
return sum;
}
}
八、总结 #
数据位置要点:
| 位置 | 用途 | 特点 |
|---|---|---|
| storage | 状态变量 | 永久存储,Gas最高 |
| memory | 临时数据 | 函数调用,可修改 |
| calldata | 外部参数 | 函数调用,只读 |
使用建议:
- 状态变量使用storage
- 只读参数使用calldata
- 需要修改的参数使用memory
- 缓存storage数据到memory优化性能
接下来,让我们学习事件!
最后更新:2026-03-27