# 继承 **Published by:** [startha](https://paragraph.com/@starha/) **Published on:** 2023-09-27 **URL:** https://paragraph.com/@starha/rn4wx1be1AigFqGSWCv4 ## Content 继承 继承是面向对象编程最重要的组成部分,可以显著的减少重复代码。如果把合约当做对象的话,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合约。 ## Publication Information - [startha](https://paragraph.com/@starha/): Publication homepage - [All Posts](https://paragraph.com/@starha/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@starha): Subscribe to updates