Solidity支持多重继承,包括多态性。 当一个合约继承自其他合约时,在区块链上只创建一个单一的合约, 所有基础合约的代码被编译到创建的合约中。 这意味着对基础合约的所有内部函数的调用也只是使用内部函数调用 ( super.f(..) 将使用 JUMP 而不是消息调用)。
- virtual: 父合约中的函数,如果希望子合约重写,需要加上virtual关键字。
- override:子合约重写了父合约中的函数,需要加上override关键字。
- 在接口合约之外,没有实现的函数必须被标记为 virtual。 在接口合约中,所有的函数都被自动视为 virtual。
- 从Solidity 0.8.8开始,当重载一个接口函数时, 不需要 override 关键字,除非该函数被定义在多个基础上。
如果基函数被标记为 virtual,则可以通过继承合约来改变其行为。 被重载的函数必须在函数头中使用 override 关键字。 重载函数只能将被重载函数的可见性从 external 改为 public。 可变性可以按照以下顺序改变为更严格的可变性。 nonpayable 可以被 view 和 pure 重载。 view 可以被 pure 重写。 payable 是一个例外,不能被改变为任何其他可变性。
eg:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract Base
{
function foo() virtual external view {}
}
contract Middle is Base {}
contract Inherited is Middle
{
function foo() override public pure {}
}
如果函数被定义在一个共同的基类合约中, 或者在一个共同的基类合约中有一个独特的函数已经重载了所有其他的函数, 则不需要明确的函数重载指定符。
eg:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;
contract A { function f() public pure{} }
contract B is A {}
contract C is A {}
// 无需明确的重载
contract D is B, C {}
对于多重继承,必须在 override 关键字后明确指定定义同一函数的最多派生基类合约。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;
contract Base1
{
function foo() virtual public {}
}
contract Base2
{
function foo() virtual public {}
}
contract Inherited is Base1, Base2
{
// 派生自多个定义 foo() 函数的基类合约,
// 所以我们必须明确地重载它
function foo() public override(Base1, Base2) {}
}
如果函数的参数和返回类型与变量的getter函数匹配,公共状态变量可以重载为外部函数。
eg:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;
contract A
{
function f() external view virtual returns(uint) { return 5; }
}
contract B is A
{
uint public override f;
}
Solidity中的修饰器(Modifier)同样可以继承,用法与函数继承类似,在相应的地方加virtual和override关键字即可。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;
contract Base
{
modifier foo() virtual {_;}
}
contract Inherited is Base
{
modifier foo() override {_;}
}
在多重继承的情况下,必须明确指定所有的直接基类合约。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;
contract Base1
{
modifier foo() virtual {_;}
}
contract Base2
{
modifier foo() virtual {_;}
}
contract Inherited is Base1, Base2
{
modifier foo() override(Base1, Base2) {_;}
}
子合约有两种方法继承父合约的构造函数。
abstract contract A {
uint public a;
constructor(uint _a) {
a = _a;
}
}
- 在继承时声明父构造函数的参数
contract B is A(1)
- 在子合约的构造函数中声明构造函数的参数
contract C is A {
constructor(uint _c) A(_c * _c) {}
}
子合约有两种方式调用父合约的函数,直接调用和利用super关键字。
- 直接调用:子合约可以直接用父合约名.函数名()的方式来调用父合约函数
function callParent() public{
Yeye.pop();
}
- super关键字:子合约可以利用super.函数名()来调用最近的父合约函数。Solidity继承关系按声明时从右到左的顺序是:contract Erzi is Yeye, Baba,那么Baba是最近的父合约,super.pop()将调用Baba.pop()
function callParentSuper() public{
// 将调用最近的父合约函数,Baba.pop()
super.pop();
}
介绍Solidity中的抽象合约(abstract)和接口(interface)
当合约中至少有一个函数没有被实现,或者合约没有为其所有的基本合约构造函数提供参数时, 合约必须被标记为 abstract。
- 没有实现内容的函数的例子(一个函数声明):
function foo(address) external returns (address);
- 类型为函数类型的变量的声明实例:
function(address) external returns (address) foo;
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;
abstract contract Feline {
function utterance() public pure virtual returns (bytes32);
}
contract Cat is Feline {
function utterance() public pure override returns (bytes32) { return "miaow"; }
}
接口(interface)合约类似于抽象(abstract)合约,但是它们不能实现任何函数。并且还有进一步的限制:
-
它们不能继承其他合约,但是它们可以继承其他接口合约。
-
在接口合约中所有声明的函数必须是 external 类型的,即使它们在合约中是 public 类型的。
-
它们不能声明构造函数。
-
它们不能声明状态变量。
-
它们不能声明修饰器。
介绍Solidity三种抛出异常的方法:error,require和assert,并比较三种方法的gas消耗。
Error:方便且高效(省gas)地向用户解释操作失败的原因,同时还可以在抛出异常的同时携带参数
Require:gas随着描述异常的字符串长度增加,比error命令要高。使用方法:require(检查条件,"异常的描述"),当检查条件不成立的时候,就会抛出异常。
Assert:它不能解释抛出异常的原因(比require少个字符串)。它的用法很简单,assert(检查条件),当检查条件不成立的时候,就会抛出异常。
error方法gas最少,其次是assert,require方法消耗gas最多。