欢迎光临
我们一直在努力

WTF-Solidity 第13讲:Solidity 合约继承实战——从简单继承、多重继承到钻石继承与 C3 线性化

WTF-Solidity 第13讲:Solidity 合约继承实战——从简单继承、多重继承到钻石继承与 C3 线性化

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

本讲围绕 WTF-Solidity 教程第 13 讲(对应 Languages/es/13_Herencia_es/readme.md)展开,系统讲解 Solidity 合约继承的全部核心用法:virtual/override 关键字、简单继承与多重继承、修饰器与构造函数的继承、父合约函数调用,以及容易踩坑的钻石继承与 C3 线性化机制。读者学完后,将能够在自己的合约中正确设计继承层级、安全地覆写函数,并理解 super 在复杂继承链上的真实调用顺序。

WTF-Solidity 第13讲 简单继承部署验证:Father 合约继承并覆写 Grandfather 的函数

继承概述:为什么 Solidity 需要继承

继承是面向对象编程(OOP)的核心概念之一,可以显著减少重复代码。合约可以看作对象,因此 Solidity 天然支持继承:一个合约可以从另一个合约派生,从而形成共享属性与方法的分层结构。在 WTF-Solidity 仓库中,本讲的所有可运行示例位于 Languages/es/13_Herencia_es/ 目录(西班牙语版),中文版源码位于 13_Inheritance/ 目录,包含 Inheritance.sol、ModifierInheritance.sol、DiamondInheritance.sol 等文件,可直接在 Remix 或 Foundry 中编译运行。

继承的两大核心关键字:virtual 与 override

Solidity 继承的规则由两个关键字控制:

  • virtual:父合约中的函数,如果希望被子合约重写,必须加上 virtual 关键字;
  • override:子合约重写了父合约中的函数,必须加上 override 关键字。

两条易错注意点:

  • 可被继续覆写:如果一个函数既覆写了父合约函数,又希望被更下层的子合约再次覆写,需要同时标注 virtual override。例如仓库源码 Languages/es/13_Herencia_es/Inheritance.sol 中,Father 的 hip() 与 pop() 均声明为 public virtual override,因此它们既可以覆写 Grandfather,也能被 Son 再次覆写。
  • public 变量的 getter 覆写:用 override 修饰 public 状态变量时,会覆写与该变量同名的自动生成的 getter 函数,例如 ERC-20 中常见的写法:
  • mapping(address => uint256) public override balanceOf;

    简单继承:contract Father is Grandfather

    先看一个最简单的一对一继承。定义爷爷合约 Grandfather,包含 1 个 Log 事件和 3 个函数 hip()、pop()、grandfather(),输出均为 "Grandfather":

    contract Grandfather {
    event Log(string msg);

    function hip() public virtual{
    emit Log("Grandfather");
    }

    function pop() public virtual{
    emit Log("Grandfather");
    }

    function grandfather() public virtual {
    emit Log("Grandfather");
    }
    }

    再定义爸爸合约 Father,继承语法为 contract Father is Grandfather,非常直观。在 Father 中,用 override 关键字覆写 hip() 和 pop(),将输出改为 "Father",并新增一个函数 father():

    contract Father is Grandfather{
    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"。这一现象在 Remix 部署验证截图中可以直观看到(见上图及 13-2.png)。

    多重继承:排序、强制覆写与 override 列表

    Solidity 合约可以同时继承多个合约,但有三条硬性规则:

  • 按辈分从高到低排序:父合约必须按优先级从高到低书写。例如 contract Son is Grandfather, Father,顺序写反(is Father, Grandfather)会直接报错。
  • 同名函数必须覆写:如果某个函数在多个父合约中都存在(如 hip()、pop()),子合约必须显式覆写,否则编译报错。
  • override 后跟所有父合约名:覆写多个父合约中的同名函数时,override 关键字后必须列出全部父合约名称,例如 override(Grandfather, Father)。
  • 示例(源码见 Languages/es/13_Herencia_es/Inheritance.sol):

    contract Son is Grandfather, Father{
    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、Father 继承来的 grandfather()、father() 保持原样。

    修饰器的继承

    Solidity 中的修饰器(modifier)同样可以继承,规则与函数继承完全一致:父合约中的修饰器加 virtual,子合约覆写时加 override。示例源码见 Languages/es/13_Herencia_es/ModifierInheritance.sol:

    contract Base1 {
    modifier exactDividedBy2And3(uint _a) virtual {
    require(_a % 2 == 0 && _a % 3 == 0);
    _;
    }
    }

    contract Identifier is Base1 {
    // 计算一个数分别被 2 除和被 3 除的值,但传入的参数必须是 2 和 3 的倍数
    function getExactDividedBy2And3(uint _dividend) public exactDividedBy2And3(_dividend) pure returns(uint, uint) {
    return getExactDividedBy2And3WithoutModifier(_dividend);
    }

    function getExactDividedBy2And3WithoutModifier(uint _dividend) public pure returns(uint, uint){
    uint div2 = _dividend / 2;
    uint div3 = _dividend / 3;
    return (div2, div3);
    }
    }

    Identifier 合约因为继承了 Base1,可以直接在函数声明处使用 exactDividedBy2And3 修饰器。同样也可以用 override 重写修饰器(注意 _ 占位符的位置决定前置检查还是后置检查):

    modifier exactDividedBy2And3(uint _a) override {
    _;
    require(_a % 2 == 0 && _a % 3 == 0);
    }

    从源码注释可以看到一个实用的验证场景:在不重写修饰器时,传入 9 调用 getExactDividedBy2And3 会因 require 检查不通过而 revert;注释掉重写代码后传入 9 则调用成功。

    构造函数的继承:两种传参方式

    构造函数同样可以被继承。以父合约 A 为例,它有一个由构造参数初始化的状态变量 a(源码见 Languages/es/13_Herencia_es/ConstructorInheritance.sol):

    abstract contract A {
    uint public a;

    constructor(uint _a) {
    a = _a;
    }
    }

    子合约继承父构造函数有两种方式:

  • 在继承声明时直接指定父构造函数参数:
  • contract B is A(1) {}

  • 在子合约构造函数内显式调用父构造函数:
  • contract C is A {
    constructor(uint _c) A(_c * _c) {}
    }

    方式二更灵活:子合约可以在调用父构造函数前对参数做任意计算(如 _c * _c)。

    调用父合约的函数:直接调用与 super 关键字

    子合约调用父合约函数有两种方式:

  • 直接调用:用 父合约名.函数名() 的形式调用指定父合约的函数:
  • function callParent() public{
    Grandfather.pop();
    }

  • super 关键字:super.函数名() 调用继承层级中最近的父合约函数。Solidity 的继承关系按声明顺序从右到左解析:对于 contract Son is Grandfather, Father,Father 比 Grandfather 更近,因此 Son 中的 super.pop() 调用的是 Father.pop() 而非 Grandfather.pop():
  • function callParentSuper() public{
    // 调用层级中最近一级的父合约函数,即 Father.pop()
    super.pop();
    }

    这两个函数在 Languages/es/13_Herencia_es/Inheritance.sol 的 Son 合约中均已实现,可直接部署验证。

    钻石继承与 C3 线性化:super 的真实调用链

    钻石继承(菱形继承)指一个派生类同时有两个或两个以上基类。在"多重 + 钻石"继承链上使用 super 时有一个极易踩坑的特性:super 会调用继承链上每一个合约的相关函数,而不仅仅是最近的父合约。

    本讲经典示例(源码见 Languages/es/13_Herencia_es/DiamondInheritance.sol)构造了如下继承树:

    God
    / \\
    Adam Eve
    \\ /
    people

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.34;

    contract God {
    event Log(string message);
    function foo() public virtual { emit Log("God.foo llamado"); }
    function bar() public virtual { emit Log("God.bar llamado"); }
    }

    contract Adam is God {
    function foo() public virtual override { emit Log("Adam.foo llamado"); }
    function bar() public virtual override {
    emit Log("Adam.bar llamado");
    super.bar();
    }
    }

    contract Eve is God {
    function foo() public virtual override { emit Log("Eve.foo llamado"); }
    function bar() public virtual override {
    emit Log("Eve.bar llamado");
    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 llamado
    Adam.bar llamado
    God.bar llamado

    这与普通多重继承"只调用最近父合约"的行为截然不同。关键点在于:尽管 Eve 和 Adam 都是 God 的子合约,整个调用过程中 God 只会被执行一次。这是因为 Solidity 借鉴了 Python 的做法,强制基类构成一个有向无环图(DAG),并通过 C3 线性化(C3 Linearization) 保证唯一的确定性调用顺序。钻石继承的完整执行结果见仓库截图 13-10.png。

    WTF-Solidity 第13讲 钻石继承执行结果:super 沿继承链依次调用 Eve、Adam 与 God

    在 Remix 中验证:完整对照实验清单

    以下验证步骤与仓库源码一一对应,可在 Remix 中按序执行:

  • 简单继承:编译部署 Languages/es/13_Herencia_es/Inheritance.sol 中的 Father,观察其函数列表与 Grandfather 的函数对比,调用 hip()、pop()、grandfather() 查看 Log 输出(见 13-1.png、13-2.png)。
  • 多重继承:按同样的步骤部署 Son,观察暴露的函数(含 callParent、callParentSuper),调用后查看日志输出。
  • 修饰器继承:部署 Languages/es/13_Herencia_es/ModifierInheritance.sol 中的 Identifier,分别传入 6(通过检查)与 9(触发 revert),再尝试覆写修饰器后重试(见 13-3.png 至 13-5.png)。
  • 构造函数继承:部署 ConstructorInheritance.sol 中的 B(a 为 1)与 C(a 为 _c 的平方),读取 a 验证(见 13-6.png、13-7.png)。
  • 调用父合约函数:在 Son 中调用 callParent()(调用 Grandfather.pop())与 callParentSuper()(调用 Father.pop()),对比日志输出(见 13-8.png、13-9.png)。
  • 钻石继承:部署 DiamondInheritance.sol 中的 people,调用 bar() 观察依次触发的 Eve、Adam、God 日志(见 13-10.png)。
  • 仓库根目录的 foundry.toml 配置了 solc = "0.8.34" 与 src = "src",本讲示例使用的 pragma solidity ^0.8.34 与之兼容,也可直接通过 Foundry 统一编译验证。

    总结

    本讲完整覆盖了 Solidity 继承的全部基础用法:

    • 简单继承(contract Father is Grandfather)与 virtual/override 关键字规则;
    • 多重继承的三条规则(排序、强制覆写、override(父合约列表));
    • 修饰器与构造函数的继承方式及两种传参写法;
    • 父合约函数的两种调用方式(直接调用与 super);
    • 钻石继承中 super 沿继承链逐级调用、且公共基类只执行一次的特性,以及其背后的 C3 线性化原理。

    理解这些规则不仅有助于编写复用性强的合约,更是阅读 OpenZeppelin 等大型合约库、排查覆写与初始化顺序问题的必备基础。相关可运行源码均在本仓库 Languages/es/13_Herencia_es/ 与 13_Inheritance/ 目录下,建议动手部署并逐项观察 Log 输出,以加深对继承链调用顺序的理解。

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

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

    赞(0)
    未经允许不得转载:171主机测评 » WTF-Solidity 第13讲:Solidity 合约继承实战——从简单继承、多重继承到钻石继承与 C3 线性化
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址