news 2026/9/15 16:55:02

WTF Solidity 极简教程(葡萄牙语版)第 5 讲:变量数据存储与作用域 storage/memory/calldata 实战详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
WTF Solidity 极简教程(葡萄牙语版)第 5 讲:变量数据存储与作用域 storage/memory/calldata 实战详解

WTF Solidity 极简教程(葡萄牙语版)第 5 讲:变量数据存储与作用域 storage/memory/calldata 实战详解

【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程,供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity

本文基于 Languages/pt-br/05_DataStorage/readme.md 编写,配套源码见 Languages/pt-br/05_DataStorage/DataStorage.sol(与 05_DataStorage/DataStorage.sol 逻辑一致)。本讲属于 WTF Solidity 极简入门教程的“基础语法”部分,是理解合约存储模型与 Gas 优化的第一道门槛。读完本文,你将掌握 Solidity 三大数据位置(storage/memory/calldata)的语义差异与赋值规则,理清状态变量、局部变量、全局变量三种作用域,并会用以太单位与时间单位在合约中做精确的数值与时间计算。

一、引言:为什么 Solidity 要显式声明数据位置

在 Solidity 中,引用类型(Reference Type)——包括数组(array)和结构体(struct)——与值类型(如uintaddress)不同,它们内部可能包含任意数量的元素,结构复杂、占用存储空间大。因此在声明和使用这类变量时,必须显式指定其数据存储位置(data location),编译器才能确定该变量应存放于链上持久存储还是临时内存,从而决定对应的 Gas 开销与可修改性。

这一设计直接服务于两条核心诉求:

  1. 节省链上有限的存储空间:链上storage空间昂贵且不可随意挥霍;
  2. 降低 Gas 消耗:合理利用内存型数据位置(memory/calldata),可显著减少每次交易的花费。

本讲的实战重点,就是理解storagememorycalldata三个关键字的语义、赋值行为差异,以及它们与变量作用域的配合方式。

二、数据位置:storage / memory / calldata

Solidity 共有三种数据存储位置,其 Gas 成本从高到低依次为:

  • storage>memory>calldata
数据位置存储介质是否上链可修改性典型用途Gas 成本
storage链上持久存储可读写合约状态变量(默认位置)
memory内存(临时)可读写函数参数、函数内临时变量
calldata内存(临时)只读(immutable)函数参数

1.storage:链上持久存储

合约里的状态变量默认就是storage,数据直接写入链上存储,如同计算机的“硬盘”,读取与写入都消耗较多 Gas。它是唯一可以在声明时省略数据位置关键字的位置(见 Languages/pt-br/05_DataStorage/DataStorage.sol 中uint[] public x = [1,2,3];的写法,注释明确说明 “The data location of x is storage. This is the only place where the data location can be omitted.”)。

2.memory:内存中的临时数据

函数里的参数和临时变量一般用memory,数据临时存储在内存中、不上链,Gas 消耗远低于storage尤其当返回类型是变长类型时,必须加memory修饰,例如:stringbytesarray以及自定义结构体。这是因为变长数据无法直接塞进 EVM 的返回值槽位,必须指明其在内存中的位置。

3.calldata:只读的函数参数数据

calldatamemory类似,数据同样存储在内存中、不上链,但区别在于calldata变量不能修改(不可变/immutable),一般专用于函数参数。由于它直接引用调用方传入的原始 calldata,无需额外拷贝,因此 Gas 成本最低。示例(来自源码 Languages/pt-br/05_DataStorage/DataStorage.sol):

function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){ // 参数为 calldata 数组,不能被修改 // _x[0] = 0 // 这样修改会报错 return(_x); }

从源码注释可以确认:一旦尝试写入_x[0] = 0,编译器会直接抛出错误——即上图中验证的Calldata arrays are read-only编译期错误。需要特别说明的是:在 Remix 等开发环境中,“编译报错”验证的是 calldata 的只读约束,而当前仓库的foundry.tomlsolc固定为0.8.34,Solidity 0.8.x 及以上版本在编译期即可拦截此类写入,这与文档示例的pragma solidity ^0.8.34完全一致。

数据位置与赋值规则

赋值本质上是创建引用(reference)指向本体,因此修改本体或引用,变化可以被同步;但在某些位置组合下,赋值创建的是独立副本。规则如下:

创建引用(修改新变量会影响原变量)的情形:

  • storage(合约的状态变量)赋值给本地storage(函数内的):创建引用,改变新变量会影响原变量。示例(源码 Languages/pt-br/05_DataStorage/DataStorage.sol):
uint[] x = [1,2,3]; // 状态变量:数组 x function fStorage() public{ // 声明一个 storage 变量 xStorage,指向 x;修改 xStorage 也会影响 x uint[] storage xStorage = x; xStorage[0] = 100; }

调用fStorage()后,状态变量x[0]被改为100——上图调试面板中Solidity Statex[0]值变化即为佐证。

  • memory赋值给memory:同样创建引用,改变新变量会影响原变量。

创建副本(修改互不影响)的情形:

除上述两种情况外,其余位置组合的赋值都会创建本体的副本,对二者之一的修改不会同步到另一方。这在实际开发中容易踩坑,例如:从storage读取数据赋值给memory变量后,修改了memory数据,但如果没有把memory数据写回storage,那么链上storage的数据并不会改变。源码中的fMemory()函数(Languages/pt-br/05_DataStorage/DataStorage.sol)正是该场景的完整演示:

function fMemory() public view{ // 声明 memory 变量 xMemory,复制 x;修改 xMemory 不会影响 x uint[] memory xMemory = x; xMemory[0] = 100; xMemory[1] = 200; uint[] memory xMemory2 = x; xMemory2[0] = 300; }

执行后,内存中的xMemory被改写为[100, 200, 3],而链上状态变量x依然保持[1, 2, 3]

三、变量的作用域:状态变量 / 局部变量 / 全局变量

Solidity 中变量按作用域划分共有三种:状态变量(state variable)局部变量(local variable)全局变量(global variable)

1. 状态变量(State Variable)

状态变量是数据存储在链上的变量,合约内所有函数都可以访问,Gas 消耗高。状态变量在合约内、函数外声明:

contract Variables { uint public x = 1; uint public y; string public z; }

可以在函数里更改状态变量的值(对应源码 Languages/pt-br/05_DataStorage/DataStorage.sol):

function foo() external{ // 可以在函数里更改状态变量的值 x = 5; y = 2; z = "0xAA"; }

2. 局部变量(Local Variable)

局部变量仅在函数执行过程中有效,函数退出后变量即失效。局部变量的数据存储在内存中、不上链,Gas 低。局部变量在函数内声明:

function bar() external pure returns(uint){ uint xx = 1; uint yy = 3; uint zz = xx + yy; return(zz); }

注意这里使用了pure修饰符:函数既不读取也不修改状态变量,仅依赖入参与局部变量完成计算。

3. 全局变量(Global Variable)

全局变量是全局范围有效的变量,均为 Solidity 预留关键字,可以在函数内不声明直接使用。示例(源码 Languages/pt-br/05_DataStorage/DataStorage.sol):

function global() external view returns(address, uint, bytes memory){ address sender = msg.sender; uint blockNum = block.number; bytes memory data = msg.data; return(sender, blockNum, data); }

上例使用了 3 个常用全局变量:msg.sender(请求发起地址)、block.number(当前区块高度)和msg.data(请求数据)。由于函数体读取了区块链与消息上下文,因此使用view修饰符。下面列出常用全局变量(完整列表以官方 Solidity 文档为准):

全局变量类型含义
blockhash(uint blockNumber)bytes32给定区块的哈希值,只适用于最近的 256 个区块,不包含当前区块
block.coinbaseaddress payable当前区块矿工的地址
block.gaslimituint当前区块的 gaslimit
block.numberuint当前区块的 number
block.timestampuint当前区块的时间戳,为 unix 纪元以来的秒
gasleft()uint256剩余 gas
msg.databytes calldata完整 call data
msg.senderaddress payable消息发送者(当前 caller)
msg.sigbytes4calldata 的前四个字节(function identifier)
msg.valueuint当前交易发送的wei

此外,Cancun 升级还新增了两个与 EIP-4844 Blob 相关的全局变量,可在最新版编译器中直接使用:

  • block.blobbasefee:当前区块的 blob 基础费用。
  • blobhash(uint index):返回与当前交易关联的第index个 blob 的版本化哈希;若当前交易不包含 blob,则返回空字节。

四、以太单位与时间单位

1. 以太单位(Ether Units)

Solidity不存在小数点,用0表示小数位,以确保交易的精确度并防止精度损失。利用以太单位可以避免误算问题,方便在合约中处理货币交易。常用以太单位换算关系:

  • wei: 1
  • gwei: 1e9 = 1000000000
  • ether: 1e18 = 1000000000000000000

源码中的验证函数(Languages/pt-br/05_DataStorage/DataStorage.sol)通过assert断言保证换算正确,任何单位换算偏差都会导致交易回滚:

function weiUnit() external pure returns(uint) { assert(1 wei == 1e0); assert(1 wei == 1); return 1 wei; } function gweiUnit() external pure returns(uint) { assert(1 gwei == 1e9); assert(1 gwei == 1000000000); return 1 gwei; } function etherUnit() external pure returns(uint) { assert(1 ether == 1e18); assert(1 ether == 1000000000000000000); return 1 ether; }

2. 时间单位(Time Units)

可以在合约中规定“一个操作必须在一周内完成”或“某个事件在一个月后发生”,这让合约的执行更加精确,不因技术上的误差影响合约结果。时间单位是 Solidity 的重要概念,有助于提高合约的可读性和可维护性。换算关系:

  • seconds: 1
  • minutes: 60 seconds = 60
  • hours: 60 minutes = 3600
  • days: 24 hours = 86400
  • weeks: 7 days = 604800

源码中的验证函数(Languages/pt-br/05_DataStorage/DataStorage.sol)同样使用assert层层校验单位间的等价关系:

function secondsUnit() external pure returns(uint) { assert(1 seconds == 1); return 1 seconds; } function minutesUnit() external pure returns(uint) { assert(1 minutes == 60); assert(1 minutes == 60 seconds); return 1 minutes; } function hoursUnit() external pure returns(uint) { assert(1 hours == 3600); assert(1 hours == 60 minutes); return 1 hours; } function daysUnit() external pure returns(uint) { assert(1 days == 86400); assert(1 days == 24 hours); return 1 days; } function weeksUnit() external pure returns(uint) { assert(1 weeks == 604800); assert(1 weeks == 7 days); return 1 weeks; }

五、源码级验证与实操建议

在仓库中验证本讲代码

  • 本讲完整可编译源码位于 Languages/pt-br/05_DataStorage/DataStorage.sol(中文版同逻辑代码位于 05_DataStorage/DataStorage.sol),包含DataStorageVariables两个合约,覆盖fStoragefMemoryfCalldata以及以太/时间单位断言函数。
  • 仓库根目录的 foundry.toml 将默认编译器固定为solc = "0.8.34",与本讲源码的pragma solidity ^0.8.34匹配;如需用 Foundry 编译验证,可执行forge build(配合根目录 scripts/run-forge-tests.sh 可运行全仓测试)。

开发中的三条经验法则

  1. 函数参数优先用calldata:只读入参声明为calldata可省去内存拷贝,Gas 最省;若需在函数内修改入参副本,再改用memory
  2. storage读、到memory改、写回要显式赋值memory副本不会自动同步回链上,任何需要持久化的修改都必须显式写回storage
  3. 用单位常量代替魔法数字:在锁仓、拍卖、时间限制等场景中直接使用daysweeksether等单位,可显著提升合约可读性与可维护性;本仓库后续的 35_DutchAuction(荷兰拍卖)、43_TokenVesting(代币归属)、44_TokenLocker(代币锁仓)等实例中,时间与金额单位的运用正是本讲知识的直接落地。

总结

本讲介绍了 Solidity 中的引用类型、数据位置(storage/memory/calldata)和变量作用域,核心是三个关键字的用法:storage链上持久存储、Gas 高;memory内存临时存储、Gas 适中;calldata内存只读参数、Gas 最低。它们出现的原因,正是为了节省链上有限的存储空间并降低 Gas 消耗。下一讲将进入引用类型中的数组(array),进一步实践本讲的数据位置规则。

【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程,供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 16:51:52

PyTorch+OpenCV实现车牌识别:从两阶段检测到字符分类完整实战

简介:面向高校计算机相关专业学生的初级车牌识别完整项目,基于PyTorch与OpenCV实现,可作为期末大作业、课程设计和毕业设计的参考,也适合初次接触深度学习视觉任务的开发者动手练手。整个zip包共7个文件、25.38MB,其中…

作者头像 李华
网站建设 2026/9/15 16:51:13

用DAX Studio导出Power BI百万级数据:告别复制表,高效生成CSV

做 Power BI 的人应该都遇到过这种场景:表里明明有上百万行明细,业务方一句“把数据导出来发我”,你打开 Power BI 的“数据”视图,右键复制,粘到 Excel 里,结果要么只复制了当前屏幕显示的几千行&#xff…

作者头像 李华
网站建设 2026/9/15 16:50:39

Kubernetes 测试策略实战指南:从测试金字塔到 Prow CI 作业设计

Kubernetes 测试策略实战指南:从测试金字塔到 Prow CI 作业设计 【免费下载链接】community Kubernetes Community Documentation 项目地址: https://gitcode.com/GitHub_Trending/com/community 本文基于 Kubernetes Community 仓库中的 testing-strategy.m…

作者头像 李华
网站建设 2026/9/15 16:49:28

Python分析B站播放量:从数据采集到可视化实战

直接打开搜索引擎敲"python刷B站播放量",能看到一堆脚本,有的号称"多线程换IP稳如老狗",有的截图晒着后台播放量的涨幅曲线。作为一个写了多年Python、也在B站传过视频的人,我在开头先把话说死:这…

作者头像 李华
网站建设 2026/9/15 16:49:05

Pascal Context数据集处理全指南:MAT转PNG并接入PyTorch

做语义分割、场景解析这类任务,Pascal Context是个绕不开的数据集。我第一次接触它是在复现一篇上下文感知分割论文的时候,当时最头疼的不是模型结构本身,而是这个数据集的“数据准备”环节:官方给的标注是MATLAB的.mat文件&#…

作者头像 李华