文档首页> 云计算> 【Solidity】引用类型, array, struct

【Solidity】引用类型, array, struct

发布时间:2025-09-01 00:00       

【从0学习Solidity】引用类型:array 与 struct 一次讲透

Solidity 引用类型的灵魂在于数据位置(data location)与深浅拷贝语义。只要吃透 storage / memory / calldata 的差异,再掌握数组与结构体的创建、读写与返回方式,就能写出既安全又省 gas 的合约。🚀

1. 数据位置与拷贝规则(核心原则)

位置 存在位置 读写成本 典型用法 拷贝/引用语义
storage 链上持久化 高 状态变量、映射、存储数组/结构体 S storage x = ...为引用;S memory y = x为深拷贝到内存
memory 函数执行期内存 中 外部返回值、临时计算、返回 struct/数组 memory之间赋值为深拷贝
calldata 只读调用数据区 低 external函数入参(数组/字符串/struct) 只读视图,无拷贝(最省 gas)

结论:能用 calldata 就别用 memory 作入参;修改状态请拿到 storage 引用再写。


2. 数组(array):定长、动态、bytes/string

分类与基本操作

  • 定长数组:uint[3] a; 长度编译期确定;不可 push/pop,越界即异常。
  • 动态数组(storage):uint[] a; 支持 push(value)、pop()、delete a[i](置默认值)、delete a(清零长度)。
  • 动态数组(memory):new T[](len) 分配后不可 push/pop,只能按下标赋值。
  • bytes / string:本质是动态字节数组;string 语义化为 UTF-8 文本,二进制更推荐 bytes。

    读多写少的外部函数,参数请优先使用 bytes calldata / string calldata。

示例:数组入参/返回、storage 修改

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

contract Arrays {
    uint256[] private store; // storage 动态数组

    // 读多写少:用 calldata 最省 gas
    function sum(uint256[] calldata xs) external pure returns (uint256 s) {
        for (uint i; i < xs.length; i++) s += xs[i];
    }

    // 返回 memory 数组(深拷贝)
    function snapshot() external view returns (uint256[] memory out) {
        out = new uint256[](store.length);
        for (uint i; i < store.length; i++) out[i] = store[i];
    }

    // 修改 storage:必须拿 storage 引用或直接操作状态变量
    function append(uint256 v) external { store.push(v); }
    function popLast() external {
        require(store.length > 0, "EMPTY");
        store.pop();
    }
}
​

高频坑位

  1. memory 数组不能 push/pop:只能 new T[](len) 后按下标赋值。
  2. 不要给数组 .length 赋值(storage 动态数组):请用 push/pop。
  3. 大数组返回昂贵且可能超限:优先设计分页、事件(event)或仅返回摘要哈希。
  4. delete a[i] 不等于移除:它仅置零,不会改变顺序或长度;“删除并压缩”请自行移动元素或使用“末尾覆盖 + pop”技巧。

3. 结构体(struct):组合数据与存储布局

定义与使用要点

  • struct 可包含值类型、bytes/string、数组、mapping(仅 storage)。
  • storage 引用修改原数据;memory 变量是拷贝。
  • 返回给外部时,struct 会被拷贝到 memory 并 ABI 编码。
  • 存储打包:按 32 字节槽位打包,小类型(如 bool/uint8/uint16)相邻更省空间。

示例:结构体 + 动态数组 + 安全更新

pragma solidity ^0.8.25;

contract Students {
    struct Student {
        uint256 id;
        uint64 joinedAt;   // 小类型靠拢有助于打包
        uint8  age;
        bytes32 tag;
        string name;       // 动态数据单独存储
        uint256[] scores;  // 动态数组
    }

    Student[] private list;                 // 学生表
    mapping(uint256 => uint256) idxPlus1;   // id => index+1(0 表示不存在)

    function add(Student memory s) external {
        require(idxPlus1[s.id] == 0, "EXISTS");
        list.push();                         // 先扩容
        Student storage st = list[list.length - 1]; // 取 storage 引用
        st.id = s.id;
        st.joinedAt = uint64(block.timestamp);
        st.age = s.age;
        st.tag = s.tag;
        st.name = s.name;                    // memory → storage 拷贝
        st.scores = s.scores;                // 动态数组内容拷贝
        idxPlus1[s.id] = list.length;
    }

    function addScore(uint256 id, uint256 v) external {
        uint256 k = idxPlus1[id]; require(k != 0, "NOT_FOUND");
        list[k - 1].scores.push(v);          // 直接改 storage
    }

    // 对外返回 struct:自动拷贝为 memory 再编码
    function get(uint256 id)