Solidity原理(一):继承(Inheritance)

首先看一段官网的描述:

Solidity supports multiple inheritance by copying code including polymorphism.

When a contract inherits from multiple contracts, only a single contract is created on the blockchain, and the code from all the base contracts is copied into the created contract.

The general inheritance system is very similar to Python’s, especially concerning multiple inheritance.

翻译过来是:Solidity支持多继承和多态,实验的方式是通过直接的代码拷贝。当一个合约从多个合约继承的时候,只有一个合约(子类)会被部署到区块链上,而其他的代码会被拷贝到这个单一的合约中去。Solidity的继承方式和Python的极为类似,主要的区别在与Solidity支持多继承,而Python不支持。

重点:Solidity的继承原理是代码拷贝,因此换句话说,继承的写法总是能够写成一个单独的合约。下面举几个例子来说明:(判断两个合约完全相同的原理是编译两个合约,比较它们的bytecode除了swarm hash部分是否相同)

例子一:没有重写和重载,上下两种情况是等价的,直接把父类(A)的函数拷贝到子类(B)中

contract A{
    function test1(uint[16] a) returns(uint){
       return a[0];
    }
   function test2(bytes a) returns(byte){
       return a[0];
    }
}
contract B is A{ //Solidity的继承是通过is关键字实现的,支持多继承
    function test3(address a) returns(address){
       return a;
    }
}
==========================
contract B{
    function test1(uint[16] a) returns(uint){
       return a[0];
    }
   function test2(bytes a) returns(byte){
       return a[0];
    }
    function test3(address a) returns(address){
       return a;
    }
}

例子二:没有重写,但是有函数重载,上下两个合约是完全等价的,和例子一其实也是几乎一样

原因解释:首先,先解释一下EVM如何定位一个hash值。当源码编译成了bytecode后,Solidity对每个public函数会有一个唯一标识的hash,这个hash值是通过函数名,参数类型来确定的(参数顺序有关)。函数的hash值与是否有返回值无关,与参数名字无关。因此对于EVM来说,重载的函数如果参数类型不一样,其实是两个完全不一样的函数,哪怕都叫test2。也就和情况一完全相同了。

contract A{
    function test1(uint[16] a) returns(uint){
       return a[0];
    }
   function test2(bytes a) returns(byte){
       return a[0];
    }
}
contract B is A{
    function test2(address a) returns(address){
       return a;
    }
}
==================
contract B {
    function test1(uint[16] a) returns(uint){
       return a[0];
    }
   function test2(bytes a) returns(byte){
       return a[0];
    }
    function test2(address a) returns(address){
       return a;
    }
}

情况三:父类函数被重写,但是子类未调用父类被重写的函数。下面的例子中,父类A和子类B中有一个完全一样的test2,也就是说他们有相同的hash值。他们不存在调用关系,因此父类A中的test2被抛弃,等同于下面单个函数B的写法

contract A{
    uint public variable = 0;
    function test1(uint a)  returns(uint){
        variable++;
       return variable;
    }
   function test2(uint a)  returns(uint){
       variable += a;
       return variable;
    }
}
contract B is A{
    function test2(uint a) returns(uint){
        variable++;
        return variable;
    }
}
==========================
contract B {
    uint public variable = 0;
    function test1(uint a)  returns(uint){
       variable++;
       return variable;
    }
    function test2(uint a) returns(uint){
        variable++;
        return variable;
    }
}

情况四:子类重写父类的函数,并且调用重写的函数。 可以看到子类B重写了父类A的test1函数。两个函数是具有相同的hash值的,因此EVM的做法是把父类中被调用的函数转换成一个private函数,copy到子类中。以调用private函数的形式来实现继承。可以看到上下两种写法的bytecode是相同的,被调用的父类A中的test1函数被改成了private函数(private函数不具备hash值,因此叫test2,test3不对对bytecode有任何影响)。

contract A{
    function test1(uint a) returns(uint){
        a += 6;
        return a; 
    }
}
contract B is A{
    uint v = 0;
    function test1(uint a) returns(uint){
        uint tmp = A.test1(a);
        v+=tmp;
    }
}
==========================
contract B {
    uint v = 0;
    function test2(uint a) private returns(uint){
        a += 6;
        return a;
    }
    function test1(uint a) returns(uint){
        uint tmp = test2(a);
        v+=tmp;
    }
}

情况五:子类父类有相同名字的变量。 父类A的test1操纵父类中的variable,子类B中的test2操纵子类中的variable,父类中的test2因为没被调用所以不存在。

解释:对EVM来说,每个storage variable都会有一个唯一标识的slot id。在下面的例子说,虽然都叫做variable,但是从bytecode角度来看,他们是由不同的slot id来确定的,因此也和变量叫什么没有关系。

contract A{
    uint variable = 0;
    function test1(uint a)  returns(uint){
       variable++;
       return variable;
    }
   function test2(uint a)  returns(uint){
       variable += a;
       return variable;
    }
}
contract B is A{
    uint variable = 0;
    function test2(uint a) returns(uint){
        variable++;
        return variable;
    }
}
====================
contract B{
    uint variable1 = 0;
    uint variable2 = 0;
    function test1(uint a)  returns(uint v){
        variable1++;
       return variable1;
    }
    function test2(uint a) returns(uint v){
        variable2++;
        return variable2;
    }
}

多继承:

Solidity的继承和Python类似,但是支持多继承, 接下来用几个例子分析一下多继承

第一个例子:

pragma solidity ^0.4.22;

contract owned {
    constructor() { owner = msg.sender; }
    address owner;
}

// Use `is` to derive from another contract. Derived
// contracts can access all non-private members including
// internal functions and state variables. These cannot be
// accessed externally via `this`, though.
contract mortal is owned {
    function kill() {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

// These abstract contracts are only provided to make the
// interface known to the compiler. Note the function
// without body. If a contract does not implement all
// functions it can only be used as an interface.
contract Config {
    function lookup(uint id) public returns (address adr);
}

contract NameReg {
    function register(bytes32 name) public;
    function unregister() public;
 }

// Multiple inheritance is possible. Note that `owned` is
// also a base class of `mortal`, yet there is only a single
// instance of `owned` (as for virtual inheritance in C++).
contract named is owned, mortal {
    constructor(bytes32 name) {
        Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
        NameReg(config.lookup(1)).register(name);
    }

    // Functions can be overridden by another function with the same name and
    // the same number/types of inputs.  If the overriding function has different
    // types of output parameters, that causes an error.
    // Both local and message-based function calls take these overrides
    // into account.
    function kill() public {
        if (msg.sender == owner) {
            Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
            NameReg(config.lookup(1)).unregister();
            // It is still possible to call a specific
            // overridden function.
            mortal.kill();
        }
    }
}

// If a constructor takes an argument, it needs to be
// provided in the header (or modifier-invocation-style at
// the constructor of the derived contract (see below)).
contract PriceFeed is owned, mortal, named("GoldFeed") {
   function updateInfo(uint newInfo) public {
      if (msg.sender == owner) info = newInfo;
   }

   function get() public view returns(uint r) { return info; }

   uint info;
}

第二个例子:下面的这个例子中,Final同时继承了Base1和Base2,当final执行test()函数后,v0=1,v1=0,v2=1;也就是说Base1中的test()函数没有被执行。通过查看Bytecode发现,Base1中的test()根本没有被继承,而是直接被忽略了。为了解决这个问题,可以用super.来解决。请看第三个例子

pragma solidity ^0.4.22;
contract set {
    uint public v0 = 0;
    function test() public {
        v0++;
    }
}

contract Base1 is set {
    uint public v1 = 0;
    function test() public { 
        v1++;
        set.test();
    }
}

contract Base2 is set {
    uint public v2 = 0;
    function test() public {
        v2++;
        set.test();
    }
}

contract Final is Base1, Base2 {
}

例子三:这个例子只是把上个例子的set.set()换成了super.test(),执行后v0,v1,v2都等于1,也就是说全都被执行了1次。通过分析bytecode可知执行顺序是Base2->Base1->set

pragma solidity ^0.4.22;
contract set {
    uint public v0 = 0;
    function test() public {
        v0++;
    }
}

contract Base1 is set {
    uint public v1 = 0;
    function test() public { 
        v1++;
        super.test();
    }
}

contract Base2 is set {
    uint public v2 = 0;
    function test() public {
        v2++;
        super.test();
    }
}

contract Final is Base1, Base2 {
}




你可能感兴趣的:(区块链)