第五章 | Solidity 数据类型深度解析

📚 第五章 | Solidity 数据类型深度解析

——彻底搞清类型用法,合约更稳,Gas 更省!


✅ 本章导读

在 Solidity 合约开发中,“类型”决定了一切。
数据类型不仅影响存储和性能,也直接关系到安全性和 Gas 成本

很多人写合约踩过这些坑:

  • storagememory 傻傻分不清
  • calldata 不会用,导致前端传参效率低
  • addressaddress payable 弄混,合约收不到 ETH
  • 数据结构乱写,Gas 飙升,代码臃肿
  • 没搞懂映射和数组,数据丢失或者被覆盖

这一章我们不打哑谜,手把手讲明白每个数据类型背后的逻辑和最佳实战方案。


✅ 本章你将掌握

  1. Solidity 基础类型细讲
  2. 存储位置:memory、storage、calldata
  3. address 和 address payable
  4. 数组、映射、结构体的进阶用法
  5. 自定义错误(Error)节省 Gas
  6. 实战案例 + 最佳实践

1️⃣ 基础数据类型


👉 布尔类型(bool)

  • 取值只有 truefalse
  • 占用 1 个字节空间(256 位中的 8 位)
bool public isActive = true;

❗ 开发建议

  • 少用多个 bool 紧挨着声明,否则布局不优(占满 32 字节)。
  • 推荐用 uint 表示状态,配合 enum 做状态机。

👉 整型(uint / int)

uint(无符号整数)

  • 只接受正整数
  • 范围:02^256-1
uint256 public totalSupply = 10000;

int(有符号整数)

  • 可以存储正数和负数
  • 范围:-2^2552^255-1
int256 public balance = -100;

❗ 开发建议

  • 默认用 uint256,兼容性最好
  • 不建议用 uint8uint16 拼凑优化(可能引发安全隐患)

👉 枚举(enum)

定义有限状态,比如订单状态、投票状态。

enum Status { Pending, Shipped, Delivered }
Status public status;

function ship() public {
    status = Status.Shipped;
}

❗ 开发建议

  • 枚举用 uint8 存储,更省空间
  • 前端配合展示状态解释,链上只存 enum 索引

2️⃣ 存储位置关键字(storage / memory / calldata)


关键字说明典型应用
storage永久存储在区块链,读写都贵状态变量
memory临时存储在内存,函数结束清除临时变量
calldata外部函数输入参数,只读存储最省 Gas函数参数

👉 storage

  • 读取/写入持久化数据,所有节点都保留副本
  • 需要谨慎使用,否则 Gas 会爆炸
string public name;

function updateName(string memory _newName) public {
    name = _newName; // 写入 storage,消耗较大
}

👉 memory

  • 函数临时变量,生命周期只在当前函数
  • 写复杂逻辑或中间处理常用
function concat(string memory a, string memory b) public pure returns (string memory) {
    return string(abi.encodePacked(a, b));
}

👉 calldata

  • 外部函数输入参数最省 Gas
  • 只读,不能被修改
function register(string calldata _username) external {
    users[msg.sender] = _username; // 节省 Gas,推荐用法
}

❗ 实战结论

  • 外部只读参数优先用 calldata
  • 写复杂逻辑时用 memory
  • 状态变量操作才用 storage
  • calldata 不能直接赋值到 storage,必须先 memory

3️⃣ 地址类型(address / address payable)


👉 address

  • 标准的以太坊地址类型
  • 可以调用合约、查询余额,但不能收 ETH
address public owner = msg.sender;

👉 address payable

  • 可以 transfer() / send() / call{value:} 转账 ETH
address payable public fundReceiver;

constructor() {
    fundReceiver = payable(msg.sender);
}

function withdraw(uint256 _amount) public {
    fundReceiver.transfer(_amount);
}

❗ 实战技巧

  • 任何 addresspayable,加 payable() 强转
  • transfer() 限 Gas(2300),不推荐大额转账
  • 推荐 call{value:} 发送 ETH
(bool success, ) = fundReceiver.call{value: _amount}("");
require(success, "Transfer failed");

4️⃣ 数组、映射、结构体进阶用法


👉 数组(Array)

  • 动态或定长
  • 支持 push() / pop()
uint[] public numbers;

function add(uint _num) public {
    numbers.push(_num);
}

function removeLast() public {
    numbers.pop();
}

❗ 注意

  • 数组删除元素时需要手动处理索引
  • 避免数组循环操作,Gas 昂贵
  • push() 返回值是元素的索引

👉 映射(mapping)

  • 键值对
  • 不支持遍历
  • 默认值 0 / false / 空
mapping(address => uint256) public balances;

function deposit() public payable {
    balances[msg.sender] += msg.value;
}

❗ 注意

  • 不可枚举,前端监听事件收集数据
  • 可嵌套:mapping(address => mapping(uint => bool))
  • 避免层级太深,增加复杂性

👉 结构体(struct)

  • 自定义数据结构
  • 配合 mapping 存储最常用
struct User {
    string name;
    uint age;
}

mapping(address => User) public users;

function register(string memory _name, uint _age) public {
    users[msg.sender] = User(_name, _age);
}

5️⃣ 自定义错误(Error)优化 Gas


👉 什么是自定义错误?

require() 更节省 Gas,推荐用法。
Solidity 0.8.4+ 引入。

✅ 定义错误

error Unauthorized(address caller);

✅ 使用错误

function withdraw() public {
    if (msg.sender != owner) {
        revert Unauthorized(msg.sender);
    }
    // withdraw logic
}

❗ 优势

  • Gas 节省明显
  • 错误信息更清晰
  • 支持自定义参数,前端更友好

6️⃣ 实战案例 | 安全的转账合约


✅ 场景

用户向合约打款,合约 owner 可以提现。

✅ 合约代码

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

contract SafeWallet {
    address public owner;

    error NotOwner(address caller);

    constructor() {
        owner = msg.sender;
    }

    receive() external payable {}

    function withdraw(uint256 _amount) external {
        if (msg.sender != owner) {
            revert NotOwner(msg.sender);
        }

        (bool success, ) = payable(owner).call{value: _amount}("");
        require(success, "Transfer failed");
    }

    function getBalance() external view returns (uint256) {
        return address(this).balance;
    }
}

✅ 合约亮点

  • receive() 接收 ETH
  • call{value:} 发送 ETH
  • 自定义错误 NotOwner 节省 Gas
  • getBalance() 查询合约余额

✅ 小结

已掌握? ✅内容
bool / uint / int / enum 基本类型
memory / storage / calldata 存储优化
address payable ETH 转账最佳实践
数组 / 映射 / 结构体进阶
自定义错误 Error 节省 Gas

🎯 作业挑战

  1. 写一个“用户注册”合约:
  • 用户注册姓名、年龄
  • 注册后不可修改
  • 只能管理员删除用户
  • 每次注册触发事件
  1. 尝试用 calldata 优化函数参数
  2. 加入自定义错误处理
  3. 用 Hardhat 测试注册、删除功能

✅ 下一章预告|第六章

👉 函数与可见性修饰符全面讲解
🚀 函数内部/外部调用优化
🚀 构造函数、回退函数最佳用法
🚀 publicprivateexternal 的安全权衡
🚀 实战构建 DAO 投票系统基础框架

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白马区块Crypto100

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值