WTF-Solidity 教程精讲:Solidity 函数定义、可见性与 pure/view/payable 关键字完全指南
2026/9/14 11:18:29 网站建设 项目流程

WTF-Solidity 教程精讲:Solidity 函数定义、可见性与 pure/view/payable 关键字完全指南

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

本篇以 WTF-Solidity 教程第 3 讲《函数》为主体,系统讲解 Solidity 函数的完整声明格式、四种可见性说明符(public/private/internal/external)、以及pure/view/payable等状态变更类关键字的区别与适用场景。读完本文,你能读懂仓库中 Function.sol 的每一个函数声明,理解为什么对状态变量的读取与改写权限会影响 gas 消耗,并能在 Remix 或 Foundry 中独立编写、部署和调试自己的函数。

Solidity 函数的声明格式

教程原文先给出了 Solidity 函数的“总形式”,这是理解后面所有细节的基础:

function <function name>([parameter types[, ...]]) {internal|external|public|private} [pure|view|payable] [virtual|override] [<modifiers>] [returns (<return types>)]{ <function body> }

其中方括号[]内的部分为可写可不写的可选关键字。教程要求读者“从前往后逐个解释”,这里完整继承其讲解脉络:

  1. function:声明函数的固定关键字,编写函数必须以此开头。
  2. <function name>:函数名。
  3. ([parameter types[, ...]]):圆括号内是函数的输入参数,即写入函数的变量类型与名称。
  4. {internal|external|public|private}:可见性说明符,共 4 种,见下一节。
  5. [pure|view|payable]:决定函数权限/功能的关键字,payable表示允许向合约转入 ETH,pureview见后文详解。
  6. [virtual|override]:方法是否可以被重写,或本身是否为重写方法。
  7. <modifiers>:自定义修饰器,可以有 0 个或多个(教程在 11_Modifier 中会专门展开)。
  8. [returns (<return types>)]:函数返回值的类型与名称。
  9. <function body>:函数体。

下面逐点深入。

四种可见性说明符

函数可见性说明符共有 4 种,语义如下:

  • public:内部和外部均可见。
  • private:只能从本合约内部访问,继承的合约也不能使用
  • external:只能从合约外部访问,但在合约内部可以通过this.f()调用(f是函数名)。
  • internal:只能从合约内部访问,但继承的合约可以使用

这里有两条重要注意事项,原文特别强调:

  • 注意 1:合约中定义的函数必须明确指定可见性,它们没有默认值。漏写会直接编译报错。
  • 注意 2public/private/internal同样可用于修饰状态变量(定义可参考 05_DataStorage/readme.md 的状态变量一节)。public变量会自动生成同名的 getter 函数用于查询数值;未标明可见性的状态变量默认为internal

源码印证:仓库中的 FunctionTypes 合约

仓库中 03_Function/Function.sol 与教程代码一一对应,其状态变量声明本身就演示了“public 状态变量自动生成 getter”这一规则:

// SPDX-License-Identifier: MIT pragma solidity ^0.8.34; contract FunctionTypes{ uint256 public number = 5; constructor() payable {} // ... }

uint256 public number = 5声明了一个公开状态变量。部署合约后,Remix 侧边栏会自动出现一个名为number的查询按钮(见下文 3-1 截图),这正是 Solidity 编译器为public变量自动生成的 getter 函数,无需手写任何查询代码。此外,src/03_Function/Function.sol 是同一份合约在仓库src/目录下的镜像,配合根目录 foundry.toml 中的solc = "0.8.34"配置,可以直接用forge工具链编译运行,适用前提是本地已安装 Foundry。

到底什么是 pure 和 view?

这是本讲最核心、也是初学 Solidity 时最容易困惑的部分。pureview在其他编程语言中没有对应关键字,它们的出现源于以太坊的 gas 费用机制:合约的状态变量存储在链上,写入链上数据要支付昂贵的 gas fee;如果一次计算不改变链上状态,就不需要付 gas。pureview函数都不改写链上状态,因此用户直接在链下(如钱包、Remix 侧边栏)调用它们时不需要付 gas

这里有一个容易忽略的细节(原文特别标注):合约中非pure/view函数调用pure/view函数时仍需付 gas——“免费”的前提是调用方本身不发起状态变更交易。

教程同时列出了以太坊中被视为“修改链上状态”的 8 种操作:

  1. 写入状态变量。
  2. 释放事件(emit event)。
  3. 创建其他合约。
  4. 使用selfdestruct
  5. 通过调用发送以太币。
  6. 调用任何未标记viewpure的函数。
  7. 使用低级调用(low-level calls)。
  8. 使用包含某些操作码的内联汇编。

作者为帮助理解画了一个马里奥插图:把合约中存储在链上的状态变量比作碧琪公主,三种角色对应三种关键字——

  • pure,中文意思是“纯”,可以理解为“纯打酱油的”:pure函数既不能读取也不能写入链上状态变量,就像小怪一样,看不到也摸不到碧琪公主。
  • view,“看”,可以理解为“看客”:view函数能读取但不能写入状态变量。类似马里奥,能看到碧琪公主,但终究是看客,不能入洞房。
  • pure也非view的普通函数,既可以读取也可以写入状态变量,类似马里奥里的boss,可以对碧琪公主为所欲为。

实战代码 1:pure 与 view

基础合约与默认函数

按教程,先在合约里定义状态变量number,初始化为 5:

contract FunctionTypes{ uint256 public number = 5; }

再定义一个add()函数,每次调用让number增加 1:

// 默认function function add() external{ number = number + 1; }

这就是一个最“默认”的函数:external可见、不带pure/view/payable,因此它可以读写状态变量——调用它就会修改链上数据,用户调用需要付 gas。

pure:纯纯牛马

如果把add()标记为pure,例如function add() external pure,编译器会报错——因为pure不配读取合约里的状态变量,更不配改写。那pure函数能做什么?教程给出的做法是:给函数传递一个参数_number,返回_number + 1。这个操作既不读取也不写入任何状态变量:

// pure: 纯纯牛马 function addPure(uint256 _number) external pure returns(uint256 new_number){ new_number = _number + 1; }

从截图可以看到:调用addPure时输入参数_number = 1,decoded output 显示0: uint256 new_number 2,即返回 1 + 1 = 2。注意执行记录中显示 execution cost 仅约 22000+ gas 且标注 “Cost only applies when called by a contract”,印证了 pure 函数在独立外部调用时不计 gas 费。

view:看客

如果add()被标记为view,即function add() external view,同样会报错,因为view能读取但不能改写状态变量。教程的解法是改写函数:只读取number,把结果存到新的局部变量中返回,而不写回状态变量:

// view: 看客 function addView() external view returns(uint256 new_number) { new_number = number + 1; }

截图中addView()无参数,返回0: uint256 new_number 6——它读取了链上状态变量number(此时为 5),返回 5 + 1 = 6,而链上的number本身保持不变。这正是 view 语义的典型体现:可读链上状态、不可写

实战代码 2:internal 对比 external

教程第二部分演示可见性差异,定义一个internalminus()函数,每次调用使number减少 1:

// internal: 内部函数 function minus() internal { number = number - 1; } // 合约内的函数可以调用内部函数 function minusCall() external { minus(); }

由于internal函数只能由合约内部调用,外部无法直接触发minus(),所以教程额外定义了一个externalminusCall()作为“外壳”,通过它间接调用内部的minus()

截图清楚地印证了这一规则:合约部署后,Remix 侧边栏自动生成的按钮只有addminusCallminusPayableaddPureaddViewnumber(getter),没有minus按钮——internalminus()无法被链上外部调用,只能经由minusCall()间接执行。这也是为什么合约中大量“辅助逻辑”会被写成internal:既能让外部入口受控,又能被多个 public 函数复用。

实战代码 3:payable

第三部分引入payable关键字:

// payable: 递钱,能给合约支付eth的函数 function minusPayable() external payable returns(uint256 balance) { minus(); balance = address(this).balance; }

minusPayable()external payable的函数,它内部间接调用minus(),并返回合约当前的 ETH 余额。其中this关键字让我们引用当前合约地址address(this).balance即该合约的 ETH 余额。

由于函数带payable,调用minusPayable()时可以在消息(msg)中附带转账,例如转入 1 个 ETH。教程中的 Remix 演示结果显示:调用minusPayable并附带 1 ETH 后,返回值balance为 1 ETH(即1000000000000000000wei),说明转账成功落袋,合约余额变为 1 ETH。

需要注意的隐含规则:只有构造函数或标记了payable的函数才能接收 ETH。如果调用方给一个非payable函数附带 ETH,交易会回滚。这也是 03_Function/Function.sol 中构造函数写成constructor() payable {}的原因——部署时即允许携带 ETH。

总结

本讲完整覆盖了 WTF-Solidity 第 3 讲《函数》的全部内容,并对照仓库源码做了印证:

  • 函数声明格式function 名字(参数) {可见性} [pure|view|payable] [virtual|override] [修饰器] [returns(返回)] { 函数体 },其中可见性必须显式指定,没有默认值。
  • 四种可见性public内外均可见;private仅本合约可见(连继承合约也不行);external仅外部可见(内部可用this.f()调用);internal本合约及继承合约可见。
  • pure/view/payablepure函数既不能读也不能写状态变量,view函数可读不可写,二者在用户直接调用时免 gas;payable允许调用方随函数调用向合约转入 ETH,配合address(this).balance可以查询合约余额。
  • 状态变量public状态变量自动生成同名 getter 函数,未标注可见性的状态变量默认internal

仓库中的完整可运行代码见 03_Function/Function.sol(src/03_Function/Function.sol 为 Foundry 工作区下的镜像),英文对照版见 Languages/en/03_Function_en/readme.md。

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询