Solidity 合约继承
Solidity 语言是一种面向对象的编程语言,提供了对合约继承的支持,继承是扩展合约功能的一种方式。
Solidity 语言的合约继承通过关键字 is 来实现。
1. 继承示例
继承通过关键字 is 来实现,例如:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Person{ string public name; uint public age; function getSalary() external pure returns(uint){ return 0; } } contract Employee is Person{ }
合约 Employee 继承了合约 Person,运行后,我们看到 Employee 继承了状态变量 name、age 和方法 getSalary。
2. virtual 和 override
solidity 引入了 virtual,override 关键字,用于重写函数。
父合约可以使用 virtual 关键字声明一个虚函数,子合约使用 override 关键字来覆盖父合约的方法,例如:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Person{ string public name; uint public age; function getSalary() external pure virtual returns(uint){ return "unkown"; } } contract Employee is Person{ function getSalary() external pure override returns(uint){ return 3000; } }
子合约 Employee 的 uint 方法覆盖了父合约 Person 的 getSalary 方法,调用子合约 Employee getSalary 方法,输出结果为 3000。
如果合约 Manager 又继承了 Employee,而且还需要覆盖 getSalary 方法,那么需要如下写法:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Person{ string public name; uint public age; function getSalary() external pure virtual returns(uint){ return 0; } } contract Employee is Person{ function getSalary() external pure virtual override returns(uint){ return 3000; } } contract getSalary is Employee{ function getSex() external pure override returns(uint){ return 20000; } }
3. abstract
solidity 还允许在基类中只声明函数原型,没有实现,而在派生类中再去实现。
solidity 使用 abstract 关键字来标记基类。
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; abstract contract Employee { function getSalary() public pure virtual returns(int); } contract Manager is Employee { function getSalary() public pure override returns(int){ return 20000; } }
抽象合约 abstract 的作用是将函数定义和具体实现分离,从而实现解耦、可拓展性,其使用规则为:
- 当合约中有未实现的函数时,则合约必须修饰为abstract;
- 当合约继承的base合约中有构造函数,但是当前合约并没有对其进行传参时,则必须修饰为abstract;
- abstract合约中未实现的函数必须在子合约中实现,即所有在abstract中定义的函数都必须有实现;
- abstract合约不能单独部署,必须被继承后才能部署;
抽象合约示例 abstract
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.6.0 <0.9.0; abstract contract Animal { string public species; constructor(string memory _base) { species = _base; } } abstract contract Feline { uint public num; function utterance() public pure virtual returns (bytes32); function base(uint _num) public returns(uint, string memory) { num = _num; return (num, "hello world!"); } } // 由于Animal中的构造函数没有进行初始化,所以必须修饰为abstract abstract contract Cat1 is Feline, Animal { function utterance() public pure override returns (bytes32) { return "miaow"; } } contract Cat2 is Feline, Animal("Animal") { function utterance() public pure override returns (bytes32) { return "miaow"; } }
4. 子类访问父类权限
子类访问父类权限修饰符包括:public、internal、private,例如:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract A{ uint stateVar; function somePublicFun() public{} function someInternalFun() internal{} function somePrivateFun() private{} } contract B is A{ function call(){ //访问父类的`public`方法 somePublicFun(); //访问父类的状态变量(状态变量默认是internal权限) stateVar = 10; //访问父类的`internal`方法 someInternalFun(); //不能访问`private` //somePrivateFun(); } }
5. 传参数到父类
子类传参数到父类有两种方式:
直接传递
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Base{ uint a; constructor(uint _a){ a = _a; } } contract Derive is Base(1){ function getBasePara() external view returns(uint){ return a; } }
根据输入值传递
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Base{ uint a; constructor(uint _a){ a = _a; } } contract T is Base{ constructor(uint _a) Base(_a) { } function getBasePara() external view returns (uint){ return a; } }
4. 多重继承中的重名
多重继承中不允许出现相同的函数名、事件名、修改器名以及状态变量名等。
示例以下:
pragma solidity ^0.8.0; contract Employee1 { function getSalary() public pure returns(int){ return 1; } } contract Employee2 { function getSalary() public pure returns(int){ return 1; } } contract Manager is Employee1,Employee2 { }
由于基类 Employee1、Employee2 中同时包含函数 getSalary,构成重名,所以以上代码会出现编译错误。
pragma solidity ^0.8.0; contract Employee { function getSalary() public pure returns(int){ return 1; } } contract Manager is Employee { function getSalary() public pure returns(int){ return 2; } }
由于基类 Employee 和 父类 Manager 中同时包含函数 getSalary,构成重名,所以以上代码会出现编译错误。
还有一种比较隐蔽的情况,默认状态变量的getter
函数导致的重名。
示例以下:
pragma solidity ^0.8.0; contract Employee1 { uint public data = 10; } contract Employee2 { function data() returns(uint){ return 1; } } contract Manager is Employee1, Employee2{}
由于 Employee1 的状态变量 data,会默认生成 getter,函数名为 data(),于是 Employee1 和 Employee2 函数重名出错。
下一章:Solidity 多重继承
Solidity 语言提供了对合约继承的支持,而且支持多重继承。Solidity 语言的多重继承采用线性继承方式。继承顺序很重要,判断顺序的一个简单规则是按照“最类似基类”到“ ...