合约继承
本章学习 Solidity
合约的继承。
封装、继承和多态是面向对象编程的三大特性。Solidity
作为一种面向对象的编程语言,它支持合约的继承。
合约继承是指一个智能合约从另一个智能合约中继承功能和属性,前者称为子合约,后者称为父合约。
通过合约继承,子合约能够重用父合约中已经定义的数据结构和代码逻辑,从而减少代码冗余,提高代码的可复用性。
1. 语法
在 Solidity
中,合约通过关键字 is
来继承其它合约:
contract child_contract is parent_contract { // ...... }
其中,child_contract
称为子合约或者继承合约,parent_contract
是父合约或者基础合约。
child_contract
继承了 parent_contract
的状态变量和函数,并根据实际需要,覆盖或扩展这些继承的部分。
我们以下面的智能合约为例,来学习继承合约:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Person{ string public name; uint public age; function getSalary() public pure returns(uint){ return 1000; } } contract Employee is Person { }
在这个例子中,合约 Employee
继承了合约 Person
的所有状态变量和函数。其中,Employee
是子合约,而 Person
是父合约。
我们把合约代码复制到 Remix
,进行编译,并部署到区块链上:
我们可以看到,虽然合约 Employee
中没有定义任何状态变量和方法,但是由于它继承了合约 Person
,所以在 Employee
中就自动拥有了状态变量 name
、age
和方法 getSalary
。
注意:子合约只能继承父合约中可见性为 public
、internal
和 external
的状态变量和方法,而可见性为 private
的状态变量和方法,是不能被子合约继承的。
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; // 使用 virtual 声明此函数可以被子合约覆盖 function getSalary() public pure virtual returns(uint){ return 1000; } } contract Employee is Person { // 使用 override 表示此函数覆盖了父合约的同名函数 function getSalary() public pure override returns(uint){ return 3000; } }
子合约 Employee
的 getSalary
方法覆盖了父合约 Person
的 同名方法。
我们调用子合约 Employee
的 getSalary
方法,输出结果是 3000,而不是父合约中的 1000。
在上面的例子中,如果父合约的方法不声明为 virtual
,或者子合约的方法不声明为 override
,那么在编译合约的时候就会报错。
3. 构造函数的继承
在 Solidity
中,如果父合约没有定义构造函数,或者构造函数没有参数,那么子合约可以直接继承父合约,无需显式调用父合约的构造函数。例如:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Person{ constructor() { // 构造函数没有参数 } } contract Employee is Person { }
如果父合约的构造函数带有参数,那么子合约在继承父合约时,就需要在自己的构造函数中显示地调用父合约的构造函数。
子合约继承合约时,调用父合约的构造函数有两种方式:直接传值 和 传入输入值。
1)直接传值
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Parent { uint public data; constructor(uint _data){ data = _data; } } contract Child is Parent(1) { }
2)传入输入值
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Parent { uint public data; constructor(uint _data){ data = _data; } } contract Child is Parent { constructor(uint _data) Parent(_data) { } }
4. 调用父合约函数
子合约可以随时调用父合约中的函数,包括被覆盖掉的函数。
子合约调用父合约的函数有两种方法:使用关键字 super
和 使用合约名称。
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // 父合约 Person contract Person { function getSalary() public pure virtual returns(uint){ return 1000; } } // 子合约 Employee contract Employee is Person { function getSalary() public pure override returns(uint){ return 3000; } function getPersonSalaryBySuper() public pure returns(uint){ return super.getSalary(); // 使用 super 调用父合约函数 } function getPersonSalaryByName() public pure returns(uint){ return Person.getSalary(); // 使用合约名称调用父合约函数 } }
我们把合约代码复制到 Remix
,进行编译,并将合约 Employee
部署到区块链上,然后调用函数 getSalary
,返回的结果为 3000。
当我们调用函数 getPersonSalaryBySuper
和 getPersonSalaryByName
时,都会返回结果 1000,因为它们都调用了父合约 Person
中的函数 getSalary
。