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 语言的多重继承采用线性继承方式。继承顺序很重要,判断顺序的一个简单规则是按照“最类似基类”到“ ...