WTF-Solidity 极简入门:Solidity 构造函数(constructor)与修饰器(modifier)实战指南
【免费下载链接】WTF-Solidity WTF Solidity 极简入门教程,供小白们使用。Now supports English! 官网: https://wtf.academy 项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity
本文以 WTF-Solidity 教程第 11 讲的日文版文档(Languages/ja/11_Modifier_ja/readme.md)为骨架,围绕 Solidity 中的构造函数(constructor)与修饰器(modifier)两个核心语法展开,并以经典的权限控制合约 Ownable 为实战载体。读完本文,你将掌握构造函数的初始化用途与版本演进、修饰器的定义与 _; 执行语义、如何用 onlyOwner 实现合约访问控制,并能对照仓库中的 Owner.sol 源码与 OpenZeppelin 官方实现理解其底层原理。
一、构造函数(constructor):合约部署时自动执行一次的初始化器
构造函数是一种特殊函数,每个合约最多可以定义一个,它会在合约部署的那一刻自动运行且仅运行一次。它的典型用途是初始化合约的状态变量,例如把部署者地址写入 owner 变量,从而为后续的权限控制奠定基础。
以下是最朴素的构造函数示例(对应仓库 Owner.sol 第 8-11 行):
address owner; // 定义 owner 变量
// 构造函数
constructor() {
owner = msg.sender; // 合约部署时,将部署者地址写入 owner
}
在无参构造函数中,msg.sender 就是发起部署交易的用户(部署者),因此上述写法等价于「合约的初始所有者 = 部署者」。
构造函数也可以接收参数,在部署时由外部传入,例如中文版文档 11_Modifier/readme.md 中的写法:
address owner; // 定义 owner 变量
// 构造函数
constructor(address initialOwner) {
owner = initialOwner; // 在部署合约的时候,将 owner 设置为传入的 initialOwner 地址
}
带参构造函数在部署时需要额外传入初始化值:在 Remix 的 "DEPLOY & RUN TRANSACTIONS" 面板中,部署框会生成对应的输入框,填好参数后再点击 "transact" 完成部署;在 Foundry 等测试框架中则通过 new Owner(address) 传入。
1.1 构造函数的版本演进:为什么 0.4.22 前后语法不同
构造函数在不同 Solidity 版本中语法并不一致,这是新手极易踩坑的历史遗留问题:
- Solidity 0.4.22 之前:没有 constructor 关键字,构造函数是一个与合约名同名的函数。
- Solidity 0.4.22 及之后:改用全新的 constructor 关键字。
旧写法的风险在于极易拼写失误:例如合约名为 Parents,开发者却把构造函数写成 parents,导致它不再被识别为构造函数,而变成一个可被任意调用的普通公开函数,进而引发严重漏洞。旧写法示例(源自日文文档第 42-48 行):
pragma solidity = 0.4.21;
contract Parents {
// 与合约名 Parents 同名的函数即为构造函数
function Parents () public {
}
}
从 Owner.sol 第 2 行的 pragma solidity ^0.8.34; 可以看出,本教程使用现代版本,一律采用 constructor 关键字,无需再考虑旧语法。
二、修饰器(modifier):给函数穿上「钢铁侠盔甲」
修饰器(modifier)是 Solidity 特有的语法,功能上类似于面向对象编程中的装饰器(decorator):它可以为函数声明附加特性,将重复的检查逻辑抽离出来,从而显著减少代码冗余。修饰器最主流的应用场景是函数执行前的检查,例如地址校验、变量校验、余额校验等——它就像钢铁侠的智能盔甲,被修饰的函数会附带特定的行为约束。
2.1 定义一个最简单的 onlyOwner 修饰器
下面定义 onlyOwner 修饰器(对应 Owner.sol 第 14-18 行):
// 定义 modifier
modifier onlyOwner {
require(msg.sender == owner); // 检查调用者是否为 owner 地址
_; // 若是,则继续执行函数主体;否则报错并 revert 交易
}
关键点在于其中的 _;(下划线分号)占位符:
- 修饰器中的代码在函数体执行前先运行,相当于检查前置条件;
- _; 表示「插入被修饰函数的函数体」,修饰器逻辑运行通过后,程序才会执行 _; 处展开的函数主体;
- 若 require(msg.sender == owner) 条件不满足,则会抛出错误并回滚(revert)整笔交易,函数体不会被执行。
2.2 用修饰器约束 changeOwner 函数
接下来定义一个带 onlyOwner 修饰器的 changeOwner 函数(对应 Owner.sol 第 21-24 行):
// 定义带 onlyOwner 修饰器的函数
function changeOwner(address _newOwner) external onlyOwner{
owner = _newOwner; // 只有 owner 地址能运行此函数并修改 owner
}
这个函数可以改变合约的 owner,但由于 onlyOwner 修饰器的存在,只有当前的 owner 地址才能成功调用,其他人调用都会触发 require 检查失败而报错。这是智能合约中最常用的访问控制手段,本讲正是以 Ownable(可拥有者)权限控制合约作为示例来讲解这两个语法点的。
三、对照源码完整解读 Owner 合约
将上述片段组合起来,就得到本讲完整的示例合约。日文版位于 Languages/ja/11_Modifier_ja/Owner.sol,中文版位于 11_Modifier/Owner.sol,两者逻辑一致。日文版完整源码如下:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.34;
contract Owner {
address public owner; // 定义 owner 变量
// 构造函数
constructor() {
owner = msg.sender; // 合约部署时,将部署者地址写入 owner
}
// 定义 modifier
modifier onlyOwner {
require(msg.sender == owner); // 检查调用者是否为 owner 地址
_; // 若是,则继续执行函数主体;否则报错并 revert 交易
}
// 定义带 onlyOwner 修饰器的函数
function changeOwner(address _newOwner) external onlyOwner{
owner = _newOwner; // 只有 owner 地址能运行此函数并修改 owner
}
}
几个值得留意的实现细节:
- owner 声明为 address public owner:public 会自动生成一个同名的只读 getter 函数,因此在 Remix 部署后左侧会出现 owner 按钮,点击即可查看当前所有者地址;
- changeOwner 声明为 external:只允许外部调用,配合 onlyOwner 修饰器形成「仅 owner 可改 owner」的闭环;
- 本合约构造函数无参数(直接取 msg.sender),与日文文档演示流程一致;若需支持部署时指定初始所有者,可改为带 address initialOwner 参数的版本。
四、OpenZeppelin 的 Ownable 标准实现(仓库源码佐证)
日文文档指出,OpenZeppelin 是一个维护 Solidity 标准化代码库的组织,其 Ownable 是经过审计、广泛使用的标准实现。本仓库自带的 OpenZeppelin 库中就有该实现,完整源码位于 lib/openzeppelin-contracts/contracts/access/Ownable.sol(v5.0.0)。
对照仓库中的官方实现,可以看到它与本讲 Owner 合约同源,但做了更多工程化增强:
abstract contract Ownable is Context {
address private _owner;
error OwnableUnauthorizedAccount(address account);
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
modifier onlyOwner() {
_checkOwner();
_;
}
function owner() public view virtual returns (address) {
return _owner;
}
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
从源码结构可以提炼出官方实现与本讲教学版的核心差异,这些也正是生产级写法与入门写法的对照要点:
| owner 可见性 | address public owner | address private _owner + 显式 owner() getter |
| 权限检查方式 | require(msg.sender == owner) | onlyOwner 内部调用 _checkOwner(),失败时 revert OwnableUnauthorizedAccount(…) |
| 错误提示 | 无自定义错误信息 | 自定义 error 类型(更省 gas、便于前端解析) |
| 所有权转移 | 仅 changeOwner | transferOwnership + renounceOwnership(放弃所有权),均受 onlyOwner 保护 |
| 事件通知 | 无 | 每次转移触发 OwnershipTransferred 事件,便于链下索引 |
| 构造函数校验 | 直接赋 msg.sender | 拒绝 address(0) 作为初始 owner |
其中 onlyOwner 修饰器的骨架(_checkOwner(); _;)与本讲定义的 modifier onlyOwner { require(…); _; } 在语义上完全一致,可见本讲教学合约正是对官方模式的极简还原。
五、Remix 实战演示:亲手验证访问控制
下面以 Owner.sol 为例,在 Remix IDE 中完整走一遍「部署 → 查看 owner → 授权调用成功 → 非授权调用失败」的流程(与日文文档第 77-85 行的演示一致)。日文版的演示截图位于 Languages/ja/11_Modifier_ja/img/ 目录。
步骤 1:编译并部署合约。 在 Remix 中新建文件粘贴 Owner.sol 源码,选择对应编译器版本(如 ^0.8.34 允许的版本)完成编译,然后在 "DEPLOY & RUN TRANSACTIONS" 面板点击 Deploy。本合约构造函数无参,部署后会自动把部署者地址写入 owner。
步骤 2:点击 owner 按钮查看当前所有者。 部署成功后,左侧 "Deployed Contracts" 区域会出现 owner 按钮(public 变量自动生成的 getter),点击即可读出当前 owner 地址,应等于部署账户地址:

步骤 3:以 owner 身份调用 changeOwner,交易成功。 在 changeOwner 输入框中填入一个新的地址(如 0x407…7118),使用部署账户(即 owner 地址)发起交易,交易记录显示绿色对勾表示成功,之后再次点击 owner 按钮可确认 owner 已更新为新地址:

步骤 4:以非 owner 身份调用 changeOwner,交易失败。 在 Remix 的账户下拉框中切换到一个非 owner 地址,再次调用 changeOwner,由于 onlyOwner 修饰器中的 require(msg.sender == owner) 检查不通过,交易被 revert,输出区域显示红色报错,且 owner 变量保持不变(仍为原部署者地址):

通过上述四步,可以直观地看到 modifier 在交易执行前拦截了未授权调用,这正是合约访问控制的可视化验证。若使用带参构造函数版本,则需要在部署时向 initialOwner 参数传入指定地址。
六、总结
本讲以 Ownable 权限控制合约为主线,介绍了 Solidity 两个核心语法点:
结合仓库中 Owner.sol 的教学实现与 lib/openzeppelin-contracts/contracts/access/Ownable.sol 的生产级实现,读者既能快速上手语法,也能理解从教学版演进到工业标准的工程化路径。想继续巩固的读者,可回到 Languages/ja/11_Modifier_ja/readme.md 对照原文,或进一步阅读本仓库中大量使用 onlyOwner 的实际合约(如 32_Faucet/Faucet.sol 等)来观察修饰器的真实应用。
【免费下载链接】WTF-Solidity WTF Solidity 极简入门教程,供小白们使用。Now supports English! 官网: https://wtf.academy 项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
