ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

WTF Solidity 教程:Solidity 继承完全指南——简单继承、多重继承、修饰器与构造函数继承

2026/9/14 14:11:13 拓冰建站 浏览量
WTF Solidity 教程:Solidity 继承完全指南——简单继承、多重继承、修饰器与构造函数继承 WTF Solidity 教程Solidity 继承完全指南——简单继承、多重继承、修饰器与构造函数继承【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-SoliditySolidity 是支持面向对象编程的合约语言继承Inheritance是其减少代码冗余、实现逻辑复用的核心机制。本教程基于 WTF-Solidity 第 13 讲的英文文档 Languages/en/13_Inheritance_en/readme.md系统讲解 Solidity 中的简单继承、多重继承、修饰器与构造函数的继承、调用父合约函数的两种方式以及多重继承中容易踩坑的菱形继承问题。读完本文你将掌握virtual/override关键字的使用规则能够设计出层次清晰、可扩展的合约继承体系。继承的基本概念与规则继承是面向对象编程中最重要的组成部分之一可以显著减少重复代码。如果把合约看作对象那么 Solidity 同样是面向对象的编程语言天然支持继承。通过继承子合约可以获得父合约的状态变量、函数、事件和修饰器并能够按需重写override其中的实现。在 Solidity 中实现继承依赖两个核心关键字virtual父合约中的函数如果希望子合约重写必须加上virtual关键字。只有被标记为virtual的函数才能被重写。override子合约重写了父合约中的函数必须加上override关键字编译器据此校验重写关系的正确性。两个容易被忽略的细节如果一个函数既重写了父合约的函数又希望继续被子合约重写那么它需要同时标记为virtual override两者缺一不可。用override修饰public状态变量时会重写与该变量同名的自动getter函数。例如mapping(address uint256) public override balanceOf;这在 ERC20 / ERC721 等代币合约的实现中非常常见仓库内的 ERC20.sol 等标准实现均采用了这一写法。简单继承让我们从一个简单的祖父Grandfather合约开始它包含 1 个Log事件和 3 个函数hip()、pop()、Grandfather()输出均为字符串Grandfathercontract Grandfather { event Log(string msg); // 3 个函数hip()、pop()、Grandfather()输出均为 Grandfather function hip() public virtual{ emit Log(Grandfather); } function pop() public virtual{ emit Log(Grandfather); } function Grandfather() public virtual { emit Log(Grandfather); } }再定义一个父亲Father合约让它继承Grandfather合约。继承语法为contract Father is Grandfather非常直观。在Father合约中我们用override关键字重写hip()和pop()两个函数将其输出改为Father并新增一个输出同样为Father的函数father()contract Father is Grandfather{ // 重写 hip() 和 pop()输出改为 Father function hip() public virtual override{ emit Log(Father); } function pop() public virtual override{ emit Log(Father); } function father() public virtual{ emit Log(Father); } }部署合约后可以看到Father合约中共有 4 个函数hip()和pop()的输出被成功改写为Father而继承自Grandfather的Grandfather()函数输出仍是Grandfather说明未重写的函数被原样继承。这一组示例的完整可编译代码位于仓库的 Inheritance.sol英文文档同款示例见 Languages/en/13_Inheritance_en/Inheritance.sol中文版源码使用Yeye/Baba/Erzi命名逻辑完全一致编译要求为pragma solidity ^0.8.34。多重继承Solidity 合约可以同时继承多个合约但需要遵守三条硬性规则按辈分排序继承时父合约必须按从高辈分到低辈分的顺序书写。例如contract Son is Grandfather, Father合法而contract Son is Father, Grandfather会直接报错Linearization of inheritance graph impossible。重名函数必须重写如果某个函数在多个被继承的合约中都存在例如下例中的hip()和pop()子合约必须重写它否则编译报错。override后跟所有父合约名重写多个父合约中的同名函数时override关键字后面必须列出所有定义过该函数的父合约名例如override(Grandfather, Father)。示例contract Son is Grandfather, Father{ // 重写 hip() 和 pop()输出改为 Son function hip() public virtual override(Grandfather, Father){ emit Log(Son); } function pop() public virtual override(Grandfather, Father) { emit Log(Son); } }部署Son合约后可以看到hip()和pop()的输出被改写为Son而继承自Grandfather的Grandfather()与继承自Father的father()保持不变。也就是说多重继承会把各父合约中未冲突的函数都聚合到子合约中。修饰器的继承修饰器Modifier在 Solidity 中同样可以继承用法与函数继承类似只需在相应位置添加virtual和override关键字。先看父合约Base1中定义的一个校验修饰器exactDividedBy2And3它要求参数_a同时是 2 和 3 的倍数校验通过后再执行函数体_;占位符contract Base1 { modifier exactDividedBy2And3(uint _a) virtual { require(_a % 2 0 _a % 3 0); _; } }子合约Identifier继承Base1后可以直接使用这个修饰器。下面的函数在计算_dividend / 2和_dividend / 3之前会先通过继承来的修饰器做倍数校验contract Identifier is Base1 { // 计算 _dividend 分别除以 2 和 3 的值但 _dividend 必须是 2 和 3 的倍数 function getExactDividedBy2And3(uint _dividend) public exactDividedBy2And3(_dividend) pure returns(uint, uint) { return getExactDividedBy2And3WithoutModifier(_dividend); } // 直接计算 _dividend 分别除以 2 和 3 的值 function getExactDividedBy2And3WithoutModifier(uint _dividend) public pure returns(uint, uint){ uint div2 _dividend / 2; uint div3 _dividend / 3; return (div2, div3); } }修饰器也支持重写。通过override关键字可以把校验逻辑从执行函数体之前改为执行函数体之后甚至完全替换校验逻辑modifier exactDividedBy2And3(uint _a) override { _; require(_a % 2 0 _a % 3 0); }仓库中的 ModifierInheritance.sol 完整保留了上述示例并在注释中给出一个可自行验证的实验不重写修饰器时用参数9调用getExactDividedBy2And3会因无法通过倍数检查而 revert若取消注释重写后的修饰器校验为空则传入9也能调用成功。构造函数的继承构造函数Constructor同样可以被继承。先看一个父合约A它有一个状态变量a由构造函数参数初始化// 构造函数继承示例 abstract contract A { uint public a; constructor(uint _a) { a _a; } }子合约继承父合约构造函数有两种方式方式一在继承声明时直接给出父构造函数的参数contract B is A(1){}部署后B合约的a将被初始化为1。方式二在子合约的构造函数中声明父构造函数的参数contract C is A { constructor(uint _c) A(_c * _c) {} }部署时传入_c父合约构造函数收到的参数是_c * _c即C合约的a值为传入参数的平方。两种方式对应的可编译代码同样位于 Inheritance.sol。调用父合约的函数子合约调用父合约函数有两种方式1. 直接调用使用父合约名.函数名()的语法。例如在子合约中调用Grandfather的pop()function callParent() public{ Grandfather.pop(); }2.super关键字使用super.函数名()调用继承层级中最近的父合约函数。Solidity 继承关系按声明时从右到左的顺序判断对于contract Son is Grandfather, FatherFather比Grandfather更近因此Son合约中的super.pop()调用的是Father.pop()而不是Grandfather.pop()function callParentSuper() public{ // 调用继承层级中高一级的父合约函数 super.pop(); }上述两个函数在 Inheritance.sol 的Erzi合约中均有实现可直接部署验证调用日志的差异。菱形继承与 C3 线性化在面向对象编程中菱形钻石继承指一个派生类同时拥有两个或两个以上共享同一祖先的基类。它的继承树形如菱形God / \ Adam Eve \ / people在菱形继承链上使用super关键字时需要特别注意super会调用继承链上每一个合约的相关函数而不是只调用最近的父合约。下面用仓库中的 DiamondInheritance.sol 完整示例说明。基类God定义foo()与bar()两个virtual函数Adam和Eve均继承God并重写这两个函数people同时继承Adam与Eve// SPDX-License-Identifier: MIT pragma solidity ^0.8.34; /* 继承树 God / \ Adam Eve \ / people */ contract God { event Log(string message); function foo() public virtual { emit Log(God.foo called); } function bar() public virtual { emit Log(God.bar called); } } contract Adam is God { function foo() public virtual override { emit Log(Adam.foo called); super.foo(); } function bar() public virtual override { emit Log(Adam.bar called); super.bar(); } } contract Eve is God { function foo() public virtual override { emit Log(Eve.foo called); super.foo(); } function bar() public virtual override { emit Log(Eve.bar called); super.bar(); } } contract people is Adam, Eve { function foo() public override(Adam, Eve) { super.foo(); } function bar() public override(Adam, Eve) { super.bar(); } }在这个例子中调用people合约的super.bar()会依次触发Eve.bar、Adam.bar最后是God.bar的执行日志依次输出Eve.bar called、Adam.bar called、God.bar called。这里有两个值得深挖的细节为什么不是从最近的父合约Adam开始因为 Solidity 的多重继承遵循从右到左的线性化顺序contract people is Adam, Eve中Eve排在Adam右侧优先级更高所以super先落到Eve。为什么God只被调用一次虽然Eve和Adam都是God的子合约但 Solidity 借鉴了 Python 的范式强制将基类组织成一个有向无环图DAG并依据C3 线性化C3 Linearization算法保证基类只被线性化处理一次从而避免同一个祖先的函数被重复执行。这正是菱形继承与普通多重继承最大的行为差异。在 Remix 上验证上述示例均为可直接复制到 Remix IDE 编译部署的完整代码。推荐验证流程如下新建文件粘贴 Inheritance.sol 与 DiamondInheritance.sol 中的任一合约代码编译后分别部署。部署简单继承示例依次部署Grandfather与Father合约观察Father合约中聚合了来自Grandfather的函数见下图左点击各函数按钮并查看Log事件输出确认重写行为。部署Identifier合约验证修饰器继承用12调用getExactDividedBy2And3成功返回(6, 4)用9调用则 revert。部署B、C合约验证构造函数继承读取a的值对比两种初始化方式。部署people合约调用bar()查看日志验证菱形继承下super沿Eve → Adam → God的调用链。下面是简单继承部署界面与菱形继承运行日志的截图图片位于英文版教程目录 Languages/en/13_Inheritance_en/img/总结本教程介绍了 Solidity 继承的核心用法继承规则virtual声明可重写override声明重写行为二者需配套使用public变量的override会覆盖其getter函数。简单继承contract Child is Parent即可获得父合约全部非私有成员。多重继承父合约按辈分从高到低排列重名函数必须重写且override后要列出所有相关父合约名。修饰器与构造函数继承分别通过virtual/override与两种参数传递方式实现复用与定制。调用父合约函数支持父合约名.函数名()直接调用与super.函数名()就近调用两种方式。菱形继承super沿 C3 线性化顺序调用整条继承链上的相关函数且共享祖先只执行一次。完整的可编译示例代码参见仓库 13_Inheritance/ 目录下的 Inheritance.sol、ModifierInheritance.sol 与 DiamondInheritance.sol英文版见 Languages/en/13_Inheritance_en/。熟练掌握这些继承机制是编写可复用、可升级的复杂 Solidity 合约的基础。【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考