引用类型 #

一、引用类型概述 #

引用类型是指变量存储的是数据的位置(引用),而不是数据本身。修改引用会改变原始数据。

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