第95篇 笔记-solidity中的继承(Inheritance)

目录

1. 继承的本质

2. 继承的定义

3. 继承的可见性

4. 父合约传参

5. 重写

6. 最远继承

7. super关键字

8. 版本 0.6.x 之后的更新


Solidity中,继承是扩展合约功能的一种方式。Solidity支持单继承和多继承。

合约继承的重要特点

  • 派生合约可以访问父合约的所有非私有成员,包括内部方法和状态变量。但是不允许使用this
  • 如果函数输出参数保持不变,则允许函数重写。如果输出参数不同,编译将失败。
  • 可以使用super关键字或父合约名称调用父合约的函数。

1. 继承的本质

  • 继承的实现方案是代码拷贝,所以合约继承后,部署到网络时,将变成一个合约。
  • 代码将从父类拷贝到子类中。

2. 继承的定义

  1. 继承使用 is,一个合约可以继承多个合约,用逗号分开。
  2. 如果继承的合约之间也有父子关系,那么合约要按照先父后子的顺序排序。

比如:

contract A {}
contract B is A {}      // B继承了A
contract C is A, B {}   // 先A,再B
contract C is B, A {}   // 错误

3. 继承的可见性

  1. 子合约不能访问父合约的 private私有成员。
  2. 子合约可以访问父合约所有的非私有成员(包括 internal的函数和状态变量)。

4. 父合约传参

继承的子合约,必须提供父合约构造方法需要的所有参数。

有两种方式实现,如下:

   contract Base {
        uint x;
        function Base(uint _x) { 
            x = _x; 
        }
    }

    // 方式一,在定义继承的时候传参数
    contract DerivedA is Base(7) {}             

    // 方式二,在合约构造函数中传参数
    contract DerivedB is Base {                 // 仅指定继承关系而不提供参数,请不要加括号
        function DerivedB(uint _y) Base(_y) {}  // 0.4.x之前旧版本的构造函数写法
        constructor(uint _y) Base(_y) {}        // 0.5.x之后新版本的构造函数写法
    }

5. 重写

在子类中允许重写函数,但不允许重写返回参数类型。

下面的代码:

pragma solidity ^0.4.24;

contract Base{
  function data() public pure returns(uint){
    return 1;
  }
}
 
contract InheritOverride is Base{
  function data(uint) public pure {}
  function data() public pure returns(uint){}
  // function data() public pure returns(string){}       // 报错
}

上面代码中的 function data() returns(string){} 将导致报错:

// 本文实测,报错信息为:
TypeError: Overriding function return types differ.

因为不能修改返回类型。

6. 最远继承原则

在继承链中,由于继承实现是代码复制。如果出现函数重写,最终使用的是继承链上哪个合约定义的代码呢?

实际执行时,依据的是最远继承原则(most derived)。

示例:

pragma solidity ^0.4.24;

contract Base1 {
    function data() public view returns(uint){ 
        return 1;
    }
}

contract Base2 {
    function data() public view returns(uint){ 
        return 2;
    }
}

contract Derived1 is Base1,Base2 {
    function call() public view returns(uint) {
        return data();     // 本文实测,返回 2
    }
}

contract Derived2 is Base2,Base1 {
    function call() public view returns(uint) {
        return data();     // 本文实测,返回 1
    }
}

7. super关键字

有些时候,我们希望继承链条上每一个函数都能被调用;这个时候,需要用到super关键字。

示例:

pragma solidity ^0.4.24;

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

contract mortal is owned {
    function kill() public {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

contract Base1 is mortal {
    function kill() public { /* do cleanup 1 */ 
        mortal.kill(); 
    }
}

contract Base2 is mortal {
    function kill() public { /* do cleanup 2 */ 
        mortal.kill(); 
    }
}

contract Final is Base1, Base2 {}

在 Final 中调用 kill() ,将仅仅触发 Base2.kill() 被调用,因为它是最远继承合约,从而跳过Base1.kill()。

如果我们想也触发 Base1.kill(),解决方案是使用 super:

pragma solidity ^0.4.24;

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

contract mortal is owned {
    function kill() public {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

contract Base1 is mortal {
    function kill() public { /* do cleanup 1 */ 
        super.kill(); 
    }
}

contract Base2 is mortal {
    function kill() public { /* do cleanup 2 */ 
        super.kill(); 
    }
}

contract Final is Base1, Base2 {}

8. 版本 0.6.x 之后的更新

8.1 显式使用 virtualoverride

在 solidity 0.5 版本中,所有函数都是隐式虚函数,从而可以在继承结构中进一步重写。这在大型继承中尤其危险,在这种情况下,这种歧义可能导致意外的行为和错误。

例如:

pragma solidity ^0.5.17;

contract A {
    uint public x;
    function setValue(uint _x) public {
        x = _x;
    }
}

contract B {
    uint public y;
    function setValue(uint _y) public {
        y = _y;
    }
}

contract C is A, B {}

合约 C中,调用 setValue会调用最后派生合约 B的实现(因为 B 是继承关系的最后一个),合约编译部署正常;

使用 0.6.0 版编译时,编译器会报这样一个错误:

TypeError: Derived contract must override function "setValue". Two or more base classes define function with same name and parameter types. contract C is A, B {} ^-------------------^ 

意思是:因为父合约定义具有相同名称和参数类型的函数,派生合约必须重写(override)函数“setValue”。

在上面多重继承的示例中,有同一个函数是从多个父合约(合约A和B)继承。在这种情况下,必须要重写,并且必须 override 修饰符中列出父合约。

注意重要的一点,override(A,B) 中的顺序无关紧要, 它不会改变 super的行为, super仍然由继承图的线性化决定,即继承关系由 contract C is A, B { ... }声明的顺序决定。

将上述示例合约修改为:

//SPDX-License-Identifier: MIT
pragma solidity ^0.6.10;

contract A {
    uint public x;
    function setValue(uint _x) public virtual {        //增加 virtual
        x = _x;
    }
}

contract B {
    uint public y;
    function setValue(uint _y) public virtual {        //增加 virtual
        y = _y;
    }
}

contract C is A, B {
    function setValue(uint _x) public override(A,B) {  // 重写 setValue
        A.setValue(_x);
    }
}

本文实测,编译部署正常;

结论:只有标记为 virtual的函数才可以重写它们。任何重写的函数都必须标记为override 。如果重写后依旧是可重写的,则仍然需要标记为 virtual

8.2 不再有状态变量遮蔽

在 0.5 版本编译器中允许继承具有相同名称的可见状态变量。

示例:

pragma solidity ^0.5.17;

contract A {
    uint public x;
    function setValue1(uint _x) public { x = _x; }
}

contract B is A {
    uint public x;
    function setValue2(uint _x) public { x = _x; }
}

在上面的例子中,A、B 各自有自己的 x, 因此:

调用 B.setValue2(100)的结果将是将 B.x 设置为 100,而调用 B.setValue1(200)的设置将是将 A.x 设置为 200。

现在 0.6.x 版本禁止这种用法,并会引发编译器错误提示:

DeclarationError: Identifier already declared         // 意思是变量已经声明

结论:合约继承时不要使用相同名称的可见状态变量。

8.3 接口可以继承

这个是 solidity 0.6 新增的功能,允许接口继承接口。

派生的接口是的所有接口函数的组合。实现合约必须实现的所有继承接口的函数。

示例:

//SPDX-License-Identifier: MIT
pragma solidity ^0.6.10;

interface X {
    function setValue(uint _x) external;
}

interface Y is X {
    function getValue() external returns (uint);
}

contract Z is Y {
    uint x;

    function setValue(uint _x) external override { 
        x = _x; 
    }

    function getValue() external override returns (uint) { 
        return x; 
    }
}

注意:如果合约未实现所有函数,则必须将合约标记为 abstract

abstract contract Z is Y {
    uint x;
    function setValue(uint _x) external override { x = _x; }
}

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wonderBlock

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值