继承
[TOC]
- 当一个合约从多个合约继承时,在区块链上只有一个合约被创建,所有基类合约(或称为父合约)的代码被编译到创建的合约中。
- 状态变量覆盖被视为错误。 派生合约不可以在声明已经是基类合约中可见的状态变量具有相同的名称
x
- 当继承时合约出现了一下相同名字会被认为是一个错误:
- 函数和 修改器 modifier 同名
- 函数和事件同名
- 事件和 修改器 modifier 同名
- 有一种例外情况,状态变量的 getter 函数可以覆盖 external 函数。
多重继承与线性化
- Solidity 借鉴了 Python 的方式并且使用“ C3 线性化 ”强制一个由基类构成的 DAG(有向无环图)保持一个特定的顺序
- 基类在 is 后面的顺序很重要:列出基类合约的 顺序从 “最基类” 到 “最派生类” 。
例如以下用例:
X 是最基类,Y,A 同时继承 X,B 继承 A,Z 继承 Y 和 A
X
/ \
Y A
| |
| B
\ /
Z
写法 is 后面各合约顺序如下,其中 Y 和 A 可以互换,因为他们两个属于同一级继承关系。如果有从写的两个合约里都有相同的方法,最后继承的方法需要用override(A,B,..)
contract X {
function test() public virtual {}
}
contract Y is X {
function test() public override virtual {}
}
contract A is X {}
contract B is A {}
contract Z is X,Y,A,B {
function test() public override(X,Y) {}
}
函数重写
- 父合约标记为
virtual
函数可以在继承合约里重写(overridden)以更改他们的行为。重写的函数需要使用关键字override
修饰 - 重写函数只能将覆盖函数的可见性从
external
更改为public
。 - 可变性可以按照以下顺序更改为更严格的一种: nonpayable 可以被
view
和pure
覆盖。view
可以被pure
覆盖。payable
是一个例外,不能更改为任何其他可变性 - 如果函数没有标记为
virtual
, 那么派生合约将不能更改函数的行为(即不能重写) - 对于多重继承,如果有多个父合约有相同定义的函数,
override
关键字后必须指定所有父合约名。
例如:
pragma solidity >=0.7.0 <0.9.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) {}
}
- 如果(重写的)函数继承自一个公共的父合约, override 是可以不用显示指定的
pragma solidity >=0.7.0 <0.9.0;
contract A { function f() public pure{} }
contract B is A {}
contract C is A {}
// 不用显示 override
contract D is B, C {}
- private 的函数是不可以标记为
virtual
的。 - 除接口之外(因为接口会自动作为 virtual ),没有实现的函数必须标记为
virtual
- 从 Solidity 0.8.8 开始, 在重写接口函数时不再要求
override
关键字,除非函数在多个父合约定义。 - 尽管 public 的状态变量可以重写外部函数,但是 public 的状态变量不能被重写。
修改器重写
- 修改器重写也可以被重写,工作方式和 函数重写 类似。 需要被重写的修改器也需要使用 virtual 修饰, override 则同样修饰重载
- 如果是多重继承,所有直接父合约必须显示指定 override
pragma solidity >=0.7.0 <0.9.0;
contract Base
{
modifier foo() virtual {_;}
}
contract Inherited is Base
{
modifier foo() override {_;}
}