solidity中的继承
1.继承
继承是面向对象编程很重要的组成部分,可以显著减少重复代码。如果把合约看作是对象的话,solidity也是面向对象的编程,也支持继承。
规则
virtual
: 父合约中的函数,如果希望子合约重写,需要加上virtual
关键字。override
:子合约重写了父合约中的函数,需要加上override
关键。
// SPDX-License-Identifier: MIT
// 指定了许可证类型,MIT许可证,这是一种常用的开源许可证。
pragma solidity ^0.8.22;
// 指定了Solidity编译器的版本范围,^表示与0.8.22版本兼容的最新版本。
contract Yeye{
event Log(string msg);
function hip() public virtual{
emit Log("Yeye");
}
function pop() public virtual{
emit Log("Yeye");
}
function yeye() public virtual{
emit Log("Yeye");
}
}
contract Baba is Yeye{
function hip() public virtual override{
emit Log("Baba");
}
function pop() public virtual override{
emit Log("Baba");
}
function baba() public virtual {
emit Log("Baba");
}
}
在这个例子中,Baba继承了Yeye,并且重写了hip()和pop()函数,同时添加了一个新的函数baba()。override关键字用于明确指出函数重写了父合约中的虚拟函数。
2.多重继承
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() public virtual override(Yeye,Baba){
emit Log("Erzi");
}
function pop() public virtual override(Yeye,Baba){
emit Log("Erzi");
}
}
在这个例子中,Erzi继承了Yeye和Baba,并且必须重写hip()和pop()函数,因为这两个函数在Yeye和Baba中都存在。
3.砖石继承(菱形继承)
菱形继承 (钻石继承) 是多重继承的一种特殊情况。
A
/ \
B C
\ /
D
contract A {
function foo() public pure virtual returns (string memory) {
return "A";
}
}
contract B is A {
function foo() public pure virtual override returns (string memory) {
return "B";
}
}
contract C is A {
function foo() public pure virtual override returns (string memory) {
return "C";
}
}
// 根据继承顺序, B C 平级, 所以顺序随意
contract D1 is B, C {
function foo() public pure override(B, C) returns (string memory) {
return "D1";
}
}
contract D2 is C, B {
function foo() public pure override(C, B) returns (string memory) {
return "D2";
}
}
调用父合约的构造函数
// SPDX-License-Identifier: MIT
// 指定了许可证类型,MIT许可证,这是一种常用的开源许可证。
pragma solidity ^0.8.22;
// 指定了Solidity编译器的版本范围,^表示与0.8.22版本兼容的最新版本。
contract A{
string public name;
constructor(string memory _name){
name=_name;
}
}
contract B{
uint public number;
constructor(uint _number){
number=_number;
}
}
//方法1 在声明的继承的时候调用
contract D is A("Hello"),B(42){}
//方法2-在声明构造函数的时候调用
contract C is A,B{
constructor(string memory _name,uint _number) A(_name) B(_number){}
}
//方法1和2可混用
contract E is A("Hello"), B{
constructor(uint _number) B(_number) {}
}
构造函数的调用顺序:先继承的父合约 → 后继承的父合约 → 当前合约
所以,demo 1 是 A → B → C、 demo 2 是 A → B → D、 demo 3 是 A → B → E
子合约有两种方式调用父合约的函数,直接调用和利用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();
}
4.总结
讲解一下完整的使用方法:
// SPDX-License-Identifier: MIT
// 这是一个许可证标识符,用于指定代码的许可证类型。MIT许可证是一种宽松的自由软件许可证。
pragma solidity ^0.8.26;
// 这行代码指定了Solidity编译器的版本。`^`符号表示与0.8.26版本兼容的最新版本。
/* Inheritance tree
A / \
B C \
/ D
*/
// 这是一个继承树的注释,说明了合约之间的继承关系。A是基类,B和C继承自A,D继承自B和C。
contract A {
// 定义了一个名为A的合约。
// 这是一个事件。你可以在函数中触发事件,并将它们记录在交易日志中。
// 在我们的例子中,这将有助于追踪函数调用。
event Log(string message);
// 事件Log被定义,它接受一个字符串参数message。
function foo() public virtual {
emit Log("A.foo called");
}
// 函数foo被定义为public,并且被标记为virtual,这意味着它可以被重写。
// 当foo被调用时,它触发了事件Log,并传递了字符串"A.foo called"。
function bar() public virtual {
emit Log("A.bar called");
}
// 函数bar也被定义为public和virtual,当bar被调用时,它触发了事件Log,并传递了字符串"A.bar called"。
}
contract B is A {
// 定义了一个名为B的合约,它继承自合约A。
function foo() public virtual override {
emit Log("B.foo called");
A.foo();
}
// 函数foo在B中被重写。它触发了事件Log,表示"B.foo called",然后调用了父合约A中的foo函数。
function bar() public virtual override {
emit Log("B.bar called");
super.bar();
}
// 函数bar在B中被重写。它触发了事件Log,表示"B.bar called",然后使用super关键字调用了父合约A中的bar函数。
}
contract C is A {
// 定义了一个名为C的合约,它继承自合约A。
function foo() public virtual override {
emit Log("C.foo called");
A.foo();
}
// 函数foo在C中被重写。它触发了事件Log,表示"C.foo called",然后调用了父合约A中的foo函数。
function bar() public virtual override {
emit Log("C.bar called");
super.bar();
}
// 函数bar在C中被重写。它触发了事件Log,表示"C.bar called",然后使用super关键字调用了父合约A中的bar函数。
}
contract D is B, C {
// 定义了一个名为D的合约,它同时继承自合约B和C。
// Try:
// - Call D.foo and check the transaction logs.
// Although D inherits A, B and C, it only called C and then A.
// - Call D.bar and check the transaction logs
// D called C, then B, and finally A.
// Although super was called twice (by B and C) it only called A once.
function foo() public override(B, C) {
super.foo();
}
// 函数foo在D中被重写。它使用super关键字调用了父合约中的foo函数。
// 由于D继承自B和C,而B和C都重写了foo函数,这里的super.foo()将调用C中的foo函数,然后C中的foo会调用A中的foo。
function bar() public override(B, C) {
super.bar();
}
// 函数bar在D中被重写。它使用super关键字调用了父合约中的bar函数。
// 由于D继承自B和C,而B和C都重写了bar函数,这里的super.bar()将首先调用C中的bar函数,然后C中的bar会调用B中的bar,最后B中的bar会调用A中的bar。
}