Skip to content

Latest commit

 

History

History
265 lines (216 loc) · 7.48 KB

File metadata and controls

265 lines (216 loc) · 7.48 KB

合约继承

继承通过关键字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。