Solidity 继承

  • DeCert.me
  • 发布于 2025-11-18 14:53
  • 阅读 7

本文介绍了Solidity中合约继承的概念和使用方法,包括单继承和多重继承。Solidity 使用关键字 is 来表示合约的继承关系,virtualoverride用于函数重写,super用于调用父合约函数。合理使用继承可以提高代码重用率和可维护性。

继承

理解继承

继承是面向对象编程中的重要概念之一,它允许一个类(称为子类或派生类)从另一个类(称为父类或基类)继承属性和方法。

Solidity 也支持继承, 当然这里对应的是派生合约(或称子合约)及父合约。

派生合约通过继承获得了父合约的特性,合理的使用继承可以带来这些好处:

  1. 代码更好重用:派生合约可以直接获得父合约的属性和方法,不需要重复编写相同的代码。
  2. 方便扩展:在继承的基础上添加新的属性和方法,更方便扩展和定制父合约的功能。
  3. 提高维护性和可读性:继承可以使合约之间建立清晰的层次关系,使代码更加易于维护和理解。

实际上,正是因为 Solidity 有继承的特性,我们才可以使用大量的第三方合约库(如OpenZepplin)来简化我们的开发。

如下图是一个常见继承图:

solidity-继承

在上面的图中,ERC20 是一个父合约,MyTokenAMyTokenB 是继承自 ERC20 的派生合约,它们继承了 ERC20 的属性和方法,但可以拥有自己的值和方法实现。

使用继承

Solidity 使用关键字 is 来表示合约的继承关系:

pragma solidity ^0.8.0;

contract Base {
    uint public a;
}

// highlight-next-line
contract Sub is Base {
    uint public b ;
    constructor() {
        b = 2;
    }
}

Sub合约部署上链, 可以看到Sub合约有两个属性,其中 a 继承自 Base 合约。

Solidity - 继承 is

派生合约会继承父合约内的所有非私有(private)成员:

public external internal private
可被继承

因此内部(internal)函数和公开的状态变量在派生合约里是可以直接使用,派生合约也会继承 external 方法,但不能在内部访问。

需要注意的是,在部署派生合约时,父合约不会连带被部署,可以理解为,在编译时,编译器会把父合约的代码拷贝进派生合约。因此,不能在派生合约再次声明父合约中已经存在的状态变量。

不过父合约函数可以重写,派生合约可以通过重写(overide)函数来更改父合约中函数的行为。

函数重写(overriding)

只有父合约中的虚函数(使用了virtual 关键字修饰的函数)可以在派生被重写,以更改它们在父合约中的行为,重写函数需要使用关键字override修饰表示正在重写父合约的某个函数。

以下一段代码,试一试调用重写的foo 之后,a 的结果是:

pragma solidity >=0.8.0;

contract Base {
    uint public a;
    function foo() virtual public {
        a += 2;
    }
}

contract Sub is Base {
   // highlight-next-line
      function foo() public override {
          a += 1;
      }
}

a 的结果是1、2 还是 3 呢?

结果是 1 ,是因为当函数被重写后,父合约的函数就会被遮蔽。

重写的函数也可以再次重写,当需要被重写时,需要使用 virtual 来修饰, 例如:

contract Sub is Base {
   // highlight-next-line
      function foo() public virtual override {
          a += 1;
      }
}

contract Inherited is Sub {
   // highlight-next-line
      function foo() public virtual override {
          a += 3;
      }
}

使用 super 调用父合约函数

刚才我们说,当函数被重写后,父合约的函数就会被遮蔽。

有时候,我们会现在父合约函数的基础上,添加一些实现,要如何做呢?

我们可以在重写的函数中显式的用 super调用父合约的函数,例如:

pragma solidity >=0.8.0;

contract Base {
    uint public a;
    function foo() virtual public {
        a += 2;
    }
}

contract Sub is Base {

      function foo() public override {
   // highlight-next-line
        super.foo(); // 或者 Base.foo();
          a += 1;
      }
}

想想看此时调用 Sub 的foo 函数后, a 的结果是多少?

大家自行验证哦~

继承下构造函数

构造函数的处理与函数重写处理的方式不一样,当派生合约继承父合约时,如果父合约实现了构造函数,在部署派生合约时,父合约的构造函数也会执行。

这是一个例子:

contract Base {
    uint public a;
    constructor()  {
        a = 1;
    }
}

contract Sub is Base {
    uint public b ;
    constructor()  {
        b = 2;
    }
}

在部署 Sub 合约后,可以查看到 a 为1,b 为2。

说明父合约的构造函数被自动执行了,同时我们也可以做一些验证,会得到结论:父合约的构造函数代码会先调用而后调用派生合约的构造函数。

刚才父合约构造函数是没有参数的,可以自动执行,如果有参数呢?如何自动执行?如何给对父合约构造函数传参呢?

有两种方法:

  1. 在继承父合约的合约名中指定参数

示例代码如下:

contract Base {
    uint public a;
    constructor(uint _a) {
        a = _a;
    }
}

// highlight-next-line
contract Sub is Base(1) {
    uint public b ;
   constructor() {
     b = 2;
   }
}

代码 contract Sub is Base(1) 对Base 构造函数传参进行初始化。

  1. 在派生构造函数中使用修饰符方式调用父合约

示例代码如下:

contract Sub is Base {
   uint public b ;

   constructor() Base(1)  {
        b = 2;
    }
}

或者是:

    constructor(uint _b) Base(_b / 2)  {
        b = _b;
    }

此时利用部署合约Sub的参数,传入到合约 Base 中。

多重继承

Solidity也支持多重继承,即可以从多个父合约继承,直接在is后面接多个父合约即可,例如:

contract Sub is Base1, Base2 {

}

如果多个父合约之间也有继承关系,那么 is 后面的合约的书写顺序就很重要,正确的顺序应该是:父合约在前,子合约在后,例如:下面的代码将无法编译:

contract X {}
contract A is X {}
// 编译出错
contract C is A, X {}

在多重继承下,如果有多个父合约有相同定义的函数,在函数重写时,override 关键字后必须指定所有的父合约名。

示例代码如下:

pragma solidity >=0.8.0;

contract Base1 {
    function foo() virtual public {}
}

contract Base2 {
    function foo() virtual public {}
}

contract Inherited is Base1, Base2 {
    // 继承自隔两个父合约定义的foo(), 必须显式的指定override
    function foo() public override(Base1, Base2) {}
}

抽象合约

有一些父合约,我们创建他们,只是为了在合约之间建立清晰的结构关系,而不是真实的部署这些父合约。

我们可以在这些不想被部署的合约前加上 abstract 关键字,表示这是一个抽象合约。

下面是抽象合约的示例代码:

abstract contract Base {
    uint public a;
}

抽象合约由于不需要部署,因此可以声明没有具体实现的纯虚函数,纯虚函数声明用";"结尾,而不是"{ }",例如:

pragma solidity >=0.8.0;

abstract contract Base {
    function get() virtual public;
}

这段代码声明了一个 get() 函数,没有函数体,这个函数需要有派生的合约来实现。

小结

  • 继承概念:允许合约从父合约继承属性和方法,实现代码复用
  • 关键字
    • is:继承某合约
    • abstract:表示该合约不可被部署
    • virtual:表示函数可以被重写
    • override:表示重写了父合约函数
    • super:调用父合约函数
  • 多重继承:支持从多个合约继承,但要注意线性化顺序(C3 线性化)
  • 最佳实践:尽量少使用复杂的多重继承,保持继承层次简单明了

继承是面向对象编程的重要特性,让我们能够更好地重用代码,使代码更加易于维护和理解。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
DeCert.me
DeCert.me
https://decert.me/ 面向未来学习,构建链上信用