继承通过关键字is
实现,下面是一个例子;
pragma solidity ^0.5.0;
contract Person {
string name;
uint age;
}
contract Manager is Person {}
上面的例子中,合约Manager
继承了Person
合约,继承的合约Manager
可以访问所有的非私有成员。
pragma solidity ^0.5.0;
contract A{
uint stateVar;
function somePublicFun() public{}
function someInternalFun() internal{}
function somePrivateFun() private{}
}
contract AccessBaseContract is A{
function call() public {
//访问父类的状态变量
stateVar = 10;
//访问父类的`public`方法
somePublicFun();
//访问父类的`internal`方法
someInternalFun();
//不能访问`private`
//somePrivateFun();
}
}
从上面的例子中,我们可以看到,子类可以访问父类的public,internal权限控制变量或函数,不能访问private权限控制的变量和函数。在子类中可以直接访问状态变量,原因是因为状态变量默认是internal的。
0.4.22颁布起,废除function+合约名称来定义构造函数
pragma solidity ^0.4.22;
contract DemoTest{
constructor() public{
}
}
继承时可以有两种方式传参数到父类。下面来看第一种方式:
pragma solidity ^0.5.0;
contract Base{
uint a;
constructor(uint _a) public {
a = _a;
}
}
contract InheritPara is Base(1){
function getBasePara() public view returns(uint){
return a;
}
}
另外一种方式是类似修改器的语法,来直接看一个例子:
pragma solidity ^0.5.0;
contract Base{
uint a;
constructor(uint _a) public {
a = _a;
}
}
contract InheritParaModifier is Base{
constructor(uint _a) public Base(_a * _a){}
function getBasePara() public view returns (uint){
return a;
}
}
如果要传入到基类的是简单的常量,第一种方式会更加简洁。但如果传入的参数与子类的输入参数有关,那么你应该使用第二种方式,以获取参数值。如果你同时使用了这两种方式,后一种方式将最终生效。
继承中不允许出现相同的函数名,事件名,修改器名,或互相重名。
pragma solidity ^0.4.0;
contract Base1{
address owner;
modifier ownd(){
if(msg.sender == owner) _;
}
event dupEvent(address, bytes);
function dupFunc(){
dupEvent(msg.sender, msg.data);
}
}
contract Base2{
address owner;
modifier ownd(){
if(msg.sender == owner) _;
}
event dupEvent(address, bytes);
function dupFunc(){
dupEvent(msg.sender, msg.data);
}
}
//失败,将会报错 Identifier already declared
//contract DuplicateNames is Base1, Base2{}
contract DuplicateNames is Base1{}
由于Base1,Base2拥有同样的修改器,方法,事件。如果同时继承将会报错。
还有一种比较隐蔽的情况,默认状态变量的getter函数导致的重名,下面来看一个例子:
pragma solidity ^0.4.0;
contract Base1{
uint public data = 10;
}
contract Base2{
function data() returns(uint){
return 1;
}
}
//一种隐蔽的情况,默认getter与函数名重名了也不行
//contract GetterOverride is Base1, Base2{}
contract GetterOverride is Base1{}
上面的例子中,Base1中的data由于生成了默认的getter函数data(),继承中也需要注意不能重名。
在子类中允许重写函数,但不允许重写返回参数签名,一起来看看下面的代码:
pragma solidity ^0.5.0;
contract Base{
function data() public pure returns(uint){
return 1;
}
}
contract InheritOverride is Base{
function data(uint) public {}
function data() public pure returns(uint){}
//TypeError: Overriding function return types differ.
//function data() public returns(string memory){}
}
上面代码中的function data() returns(string){}将导致Override changes extended function signature报错,因为不能修改返回签名。
由于继承的实现方案是代码拷贝,所以合约继承后,部署到网络时,将变成一个合约。代码将从父类拷贝到子类中。
在继承链中,由于继承实现是代码复制。如果出现函数名重写,最终使用的是继承链上哪个合约定义的代码呢?实际执行时,依据的是最远继承的原则(most derived)。下面来看一个例子:
pragma solidity ^0.5.0;
contract Base1{
function data() public returns(uint){
return 1;
}
}
contract Base2{
function data() public returns(uint){
return 2;
}
}
contract MostDerived1 is Base1, Base2{
function call() public returns(uint){
return data();
}
}
contract MostDerived2 is Base2, Base1{
function call() public returns(uint){
return data();
}
}
上面的例子中,根据最远继承原则,大家可以想想MostDerived1,和MostDerived2中调用call()方法的运行结果。
实际上呢,MostDerived1,MostDerived2的call()将分别返回2和1。因为对于MostDerived1的最远继承合约是Base2,所以会使用其对应的函数,对于MostDerived2最远的继承者将是Base1。
当一个合约从多个其它合约那里继承,在区块链上仅会创建一个合约,在父合约里的代码会复制来形成继承合约。当一个合约继承了多个合约,因为存在最远继承原则,当一个合约调用一个重写方法时只会调用最远的的合约方法。
pragma solidity ^0.5.0;
contract mortal {
function number() public returns(uint8) {
return 1;
}
}
contract Base1 is mortal {
function number() public returns(uint8) {
return 2;
}
}
contract Base2 is mortal {
function number() public returns(uint8) {
return 3;
}
}
contract Final is mortal, Base1 {
function number() public returns(uint8) {
return mortal.number();
}
}
当部署合约Final
调用number
方法时,返回的数值是1,并不会返回Base1的2,但是当我们有一个清理作用的函数,想依次调用最远路径上的同名方法来帮我们清理一些数据,这个时候就需要用到一个关键字super
。
contract Final is mortal, Base1 {
function number() public returns(uint8) {
return super.number();
}
}
最终的调用顺序也会变成Final,Base2,Base1,motal。
在Solidity中,允许多继承,你可以同时继承多个合约。实现多继承的编程语言需要解决几个问题,其中之一是菱形继承问题又称钻石问题,如下图。
Solidity的解决方案参考Python,使用C3_linearization来强制将基合约转换一个有向无环图(DAG)的特定顺序。结果是我们希望的单调性,但却禁止了某些继承行为。特别是基合约在is后的顺序非常重要。下面的代码,Solidity会报错Linearization of inheritance graph impossible。
pragma solidity ^0.5.0;
contract X {}
contract A is X {}
contract C is A, X {}
原因是C会请求X来重写A(因为继承定义的顺序是A,X),但A自身又是重写X的,所以这是一个不可解决的矛盾。
所以正确的写法应该如下;
pragma solidity ^0.5.0;
contract X {}
contract A is X {}
contract C is X, A {}
一个简单解决这种矛盾的原则是,总是指定基合约的继承顺序是从most base-like到most derived。