本文主要内容有:
- virtual与override
- abstract合约
- abstract合约与interface的对比
一、virtual与override
1.普通类继承
在solidity0.6.0版本之前,函数默认是虚函数,但是在这个版本之后,默认情况下,函数不再是虚函数。所以要想后续继承类能够重载,必须手动指定是虚函数!
所谓虚函数,即virtual
函数就是允许继承类进行override
的关键词声明。
这些东西就和传统的面向对象语言一样。
pragma solidity ^0.8.0;
contract Person {
string public gender;
constructor() {
gender = "unknown";
}
function changeGender() public virtual {}
}
contract Man is Person {
string public name;
constructor(string memory _name) Person() {
name = _name;
}
function changeGender() public virtual override {
gender = "male";
}
}
contract Trans is Man {
constructor(string memory _name) Man(_name) {
Man.changeGender();
}
function changeGender() public override {
gender = "female";
}
}
以上案例中,基类为Person
类,人类,默认初始化gender
为unknown,且有一个changeGender
的函数,这里我们声明为virtual
函数,就是允许后续的继承类进行修改。
后续的继承类为Man
,即男性,因此,我们要重载changeGender
函数,将性别改为male,此时就可以看到,这个继承类的关键词有override
。此外,这个函数还有关键词virtual
,这是考虑到后面还有可能继承Man
类的函数来重载。
我们定义了一个Trans
类,该类指的是男性变为女性的变性人,因此仍然需要再对changeGender
进行override
。
2.接口继承
在interface
中,所有的函数都默认是virtual
的,无需显示写明virtual关键字,但是在实现借口时,则必须写明override
,否则编译通不过!
interface IDemo {
function foo() external;
}
contract Demo is IDemo {
function foo() external override {}
}
3.override不允许改变可见性
如上,Person类中changeGender是public
的,如果我们在继承类Man中改成external
,这样是不行的:
contract Person {
string public gender;
constructor() {
gender = "unknown";
}
function changeGender() public virtual {}
}
contract Man is Person {
string public name;
constructor(string memory _name) Person() {
name = _name;
}
function changeGender() external virtual override {
gender = "male";
}
}
这样编译时是会报错的:
TypeError: Overriding function visibility differs.
所以我们得避免这种情况。
4.多基类含有相同函数
这种情况时,继承类要重载多个基类中的相同函数:
pragma solidity ^0.8.0;
abstract contract SleepAnimal {
function Sleep() public virtual;
function Move() external virtual {}
}
abstract contract SwimAnimal {
function Swim() public virtual;
function Move() external virtual {}
}
contract Person is SleepAnimal, SwimAnimal{
string public gender;
constructor() {
gender = "unknown";
}
function Sleep() public override {}
function Swim() public override {}
function Move() external override(SleepAnimal, SwimAnimal) {}
}
要重载多个基类的相同函数,就要在override
后面加上(SleepAnimal, SwimAnimal)
,否则会报错。
二、abstract合约
abstract合约,在官方文档中是这样定义的:
Contracts must be marked as abstract when at least one of their functions is not implemented or when they do not provide arguments for all of their base contract constructors.
当至少其中一个功能未实现或未为其所有基本合约构造函数提供参数时,合约必须标记为抽象。
1.至少1个方法未实现
pragma solidity ^0.8.0;
contract Person {
string public gender;
constructor() {
gender = "unknown";
}
function changeGender() public virtual;
}
如上面的Person类,注意我们在changeGender
函数后面没有给出具体实现(没有{}
包裹的函数体),仅仅给了个函数声明,这样编译是会报错的:
TypeError: Contract "Person" should be marked as abstract.
Note: Missing implementation
这里的function就1个,而为了体现文档中的至少1个未实现的方法,我们加入另一个已经实现了的方法sleep
:
contract Person {
string public gender;
constructor() {
gender = "unknown";
}
function changeGender() public virtual;
function sleep() external {}
}
再次编译,仍然出错,提示你使用abstract
关键字。
2.未给所有基类提供构造函数参数
有一个示例如下,首先创造两个抽象类,SleepAnimal
和SwimAnimal
,然后Person继承自这两个抽象类:
pragma solidity ^0.8.0;
abstract contract SleepAnimal {
uint256 public sleepDuration;
constructor(uint256 _sleepDuration) {
sleepDuration = _sleepDuration;
}
function Sleep() public virtual;
}
abstract contract SwimAnimal {
uint256 public swimDistance;
constructor(uint256 _swimDistance) {
swimDistance = _swimDistance;
}
function Swim() public virtual;
}
(1) Person类的constructor不给基类参数
contract Person is SleepAnimal, SwimAnimal{
string public gender;
constructor() {
gender = "unknown";
}
function Sleep() public override {}
function Swim() public override {}
}
在Person类中,我们override
了SleepAnimal
和SwimAnimal
中所有的抽象函数,但是没有给任何基类提供构造函数参数,结果就是报错:
TypeError: Contract "Person" should be marked as abstract.
告诉我们,这应该是一个abstract合约。
(2) Person类的constructor不给所有基类参数
contract Person is SleepAnimal, SwimAnimal{
string public gender;
constructor(uint256 _duration, uint256 _distance) SwimAnimal(_distance) {
gender = "unknown";
}
function Sleep() public override {}
function Swim() public override {}
}
这里我们就给了SwimAnimal
的constructor参数,没有给SleepAnimal
的constructor参数,结果也是报错:
TypeError: Contract "Person" should be marked as abstract.
告诉我们,这应该是一个abstract合约。
(3)给所有基类初始化参数后,不是abstract合约了
contract Person is SleepAnimal, SwimAnimal{
string public gender;
constructor(uint256 _duration, uint256 _distance) SleepAnimal(_duration) SwimAnimal(_distance) {
gender = "unknown";
}
function Sleep() public override {}
function Swim() public override {}
}
这一次成功编译,即说明我们在未给所有基类提供构造函数参数时,合约都是abstract合约。
三、abstract合约和interface的对比
总的来说,abstract合约比interface更加强大。
相比abstract合约,interface不能做:
- 无法继承其他合约,只能继承其他接口
- 所有的函数都需要是external的
- 无法定义构造函数
- 无法定义状态变量
- 所有函数都默认为virtual,不需要手动写
网友评论