当前位置: 首页 > news >正文

第五章 | 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 投票系统基础框架

 

相关文章:

  • Mysql的锁
  • lodash 学习笔记/使用心得
  • 2.企业级AD活动目录架构与设计原则实战指南
  • C# 调用 VITS,推理模型 将文字转wav音频net8.0 跨平台
  • Python FastApi(3):路径参数
  • 使用AI一步一步实现若依前端(16)
  • Elasticsearch 中的数据分片问题
  • Deepseek浪潮下,汽车芯片开启“大变局”,谁将领跑?
  • 进程地址空间(上)【Linux】
  • libc.so.6: version `GLIBC_2.29‘ not found, 如何解决这个错误
  • Python `is` 关键字深度解析
  • CCF-CSP认证 202209-2何以包邮?
  • 文件上传的小点总结
  • JVM如何处理Java中的精度转换: 从源码到字节码
  • 查看自己的公有ip
  • 深度解析 | Android 13 Launcher3分页指示器改造:横线变圆点实战指南
  • 玄机-第四章 windows实战-emlog的测试报告
  • 初识Brainstorm(matlab)
  • JSON在AutoCAD二次开发中应用场景及具体案例
  • PHP 应用后台模块SessionCookieToken身份验证唯一性
  • 第1现场 | 50多年来首次!印度举行大规模民防演习
  • 李公明︱一周书记:浪漫主义为什么……仍然重要?
  • 公募基金改革八大要点:建立浮动管理费收取机制、降低规模排名考核权重
  • 上海:下调个人住房公积金贷款利率
  • 习近平离京赴莫斯科对俄罗斯进行国事访问并出席纪念苏联伟大卫国战争胜利80周年庆典
  • 首家股份行旗下AIC来了,兴银金融资产投资有限公司获批筹建