继承是面向对象编程最重要的组成部分,可以显著的减少重复代码。如果把合约当做对象的话,solidity也是面向对象的编程,也支持继承。
virtual:父合约中的函数,如果希望子合约重写,需要加上virtual关键字。override:子合约重写父合约中的函数,需要加上override关键字再。
注意:用override修饰public变量,会重写与变量同名的getter函数,例如:
mapping(address => uint256) public override balanceOf;
我们先简单写一个爷爷合约Yeye,里面包含一个Log事件和三个函数,hip(),pop(),yeye(),输出都是Yeye。
contract Yeye {
event Log(string msg);
//定义3个函数:hip(),pop(),man(),Log值为Yeye
function hip() public virtual {
emit Log("Yeye");
}
function pop() public virtual{
emit Log("Yeye");
}
function yeye() public virtual {
emit Log("Yeye");
}
}
我们在定义一个爸爸合约Baba,让它继承Yeye合约,语法就是contract Baba is Yeye,非常直观。在Baba合约里,我们重写一下hip()和pop()这两个函数,加上override关键字,并将它们输出改为Baba,并且加一个新函数baba(),输出也是Baba。
contract Baba is Yeye{
// 继承两个function: hip()和pop(),输出改为Baba。
function hip() public virtual override{
emit Log("Baba");
}
function pop() public virtual override{
emit Log("Baba");
}
function baba() public virtual{
emit Log("Baba");
}
}
我们部署合约,可以看到Baba合约有4个函数,其中hip()和pop()的输出被成功改写成Baba,而继承来的yeye()的输出仍然是Yeye。
solidity的合约可以继承多个合约,规则
继承时按备份最高到最低的顺序排。比如我们写一个
Erzi合约,继承Yeye合约和Baba合约,那么就写成contract Erzi is Yeye, Baba,而不是写成contract Erzi is Baba, Yeye,不然会报错。如果某一个函数在多个继承的合约里存在,比如合约里的
hip()和pop(),在子合约里必须重写,不然会报错。重写在多个父合约都重名的函数时,
override关键字后面要加上所有父合约名字,例如`override(Yeye, Baba)。
contract Erzi is Yeye, Baba{
// 继承两个function: hip()和pop(),输出改为Erzi。
function hip() public virtual override(Yeye, Baba){
emit Log("Erzi");
}
function pop() public virtual override(Yeye, Baba) {
emit Log("Erzi");
}
function callParent() public{
Yeye.pop();
}
function callParentSuper() public{
super.pop();
}
}
我们可以看到,Erzi合约里面重写了hip()和pop()两个函数,并且还分别从Yeye和Baba合约继承了yeye()和baba()两个函数。
solidity中的修饰器(modifier)同样可以继承,用法与函数类似,相应的地方加上override和virtual关键字即可。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
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);
}
//计算一个数分别被2除和被3除的值
function getExactDividedBy2And3WithoutModifier(uint _dividend) public pure returns(uint, uint){
uint div2 = _dividend / 2;
uint div3 = _dividend / 3;
return (div2, div3);
}
//重写Modifier: 不重写时,输入9调用getExactDividedBy2And3,会revert,因为无法通过检查
//删掉下面三行注释重写Modifier,这时候输入9调用getExactDividedBy2And3, 会调用成功
// modifier exactDividedBy2And3(uint _a) override {
// _;
// }
}
Identifier合约可以直接在代码中使用父合约中的exactDividedBy2And3修改器,也可以利用override关键字重写修改器。
modifier exactDividedBy2And3(uint _a) override {
_;
require(_a % 2 == 0 && _a % 3 == 0);
}
子合约有两种方法继承父合约的构造函数。举个简单的例子,父合约A里面有一个状态变量a,并有构造函数的参数来确定:
abstract contract A {
uint public a;
constructor(uint _a) {
a = _a;
}
}
在继承时声明父构造函数参数,例如:
contract B is A(1)。在子合约的构造函数中声明构造函数的参数。
contract B is A(1) {
}
contract C is A {
constructor(uint _c) A(_c * _c) {}
}
子合约有两种方法调用父合约的函数,直接调用和利用super关键字。
直接调用:子合约可以直接用
父合约.函数名的方式来调用父合约函数,例如Yeye.pop()。
function callParent() public{
Yeye.pop();
}
super关键字:子合约可以利用super.函数名()来调用最近的父合约函数。solidity继承关系声明时从右到左的顺序是contract Erzi is Yeye, Baba,那么Baba是最近的父合约,super.pop()将调用Baba.pop()而不是Yeye.pop()。
function callParentSuper() public{
// 将调用最近的父合约函数,Baba.pop()
super.pop();
}
在面向对象编程中,钻石继承(菱形继承)值一个派生类同时又两个或两个以上的基类。
在多重加菱形继承链条上使用关键字super时,需要注意的是使用super会调用继承链条上的每一个合约的相关函数,而不是只调用最近的父合约。
我们先写一个God合约,再写两个Adam和Eve两个合约继承God合约,最后让创建合约people继承Adam和Eve,每个合约都有foo和bar两个函数。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
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");
}
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");
God.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、Adm,最后是God合约。

