状态变量 #

一、数据位置概述 #

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