# 继承(Inheritance)
Solidity通过复制包括多态的代码来支持多重继承。
所有函数调用是`虚拟(virtual)`的,这意味着最远的派生方式会被调用,除非明确指定了合约。
当一个合约从多个其它合约那里继承,在区块链上仅会创建一个合约,在父合约里的代码会复制来形成继承合约。
基本的继承体系与`python`有些类似,特别是在处理多继承上面。
下面用一个例子来详细说明:
```
pragma solidity ^0.4.0;
contract owned {
function owned() { 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) returns (address adr);
}
contract NameReg {
function register(bytes32 name);
function unregister();
}
// 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 {
function named(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() {
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) {
if (msg.sender == owner) info = newInfo;
}
function get() constant returns(uint r) { return info; }
uint info;
}
```
上面的例子的`named`合约的`kill()`方法中,我们调用了`motal.kill()`调用父合约的`销毁函数(destruction)`。但这样可能什么引发一些小问题。
```
pragma solidity ^0.4.0;
contract mortal is owned {
function kill() {
if (msg.sender == owner) selfdestruct(owner);
}
}
contract Base1 is mortal {
function kill() { /* do cleanup 1 */ mortal.kill(); }
}
contract Base2 is mortal {
function kill() { /* do cleanup 2 */ mortal.kill(); }
}
contract Final is Base1, Base2 {
}
```
对`Final.kill()`的调用只会调用`Base2.kill()`,因为派生重写,会跳过`Base1.kill`,因为它根本就不知道有`Base1`。一个变通方法是使用`super`。
```
pragma solidity ^0.4.0;
contract mortal is owned {
function kill() {
if (msg.sender == owner) selfdestruct(owner);
}
}
contract Base1 is mortal {
function kill() { /* do cleanup 1 */ super.kill(); }
}
contract Base2 is mortal {
function kill() { /* do cleanup 2 */ super.kill(); }
}
contract Final is Base2, Base1 {
}
```
如果`Base1`调用了函数`super`,它不会简单的调用基类的合约函数,它还会调用继承关系图谱上的下一个基类合约,所以会调用`Base2.kill()`。需要注意的最终的继承图谱将会是:Final,Base1,Base2,mortal,owned。使用super时会调用的实际函数在使用它的类的上下文中是未知的,尽管它的类型是已知的。这类似于普通虚函数查找`(ordinary virtual method lookup)`
## 基类构造器的方法(Arguments for Base Constructors)
派生的合约需要提供所有父合约需要的所有参数,所以用两种方式来做,见下面的例子:
```
pragma solidity ^0.4.0;
contract Base {
uint x;
function Base(uint _x) { x = _x; }
}
contract Derived is Base(7) {
function Derived(uint _y) Base(_y * _y) {
}
}
```
或者直接在继承列表中使用`is Base(7)`,或像`修改器(modifier)`使用方式一样,做为派生构造器定义头的一部分`Base(_y * _y)`。第一种方式对于构造器是常量的情况比较方便,可以大概说明合约的行为。第二种方式适用于构造的参数值由派生合约的指定的情况。在上述两种都用的情况下,第二种方式优先(一般情况只用其中一种方式就好了)。
## 多继承与线性化(Multiple Inheritance and Linearization)
实现多继承的编程语言需要解决几个问题,其中之一是`菱形继承问题`又称`钻石问题`,如下图。<br/>
<img src="media/14825855458212/14826257952604.png" alt=""/>
Solidity的解决方案参考`Python`,使用[C3_linearization](https://en.wikipedia.org/wiki/C3_linearization)来强制将基类合约转换一个有向无环图(DAG)的特定顺序。结果是我们希望的单调性,但却禁止了某些继承行为。特别是基类合约在`is`后的顺序非常重要。下面的代码,Solidity会报错`Linearization of inheritance graph impossible`。
```
pragma solidity ^0.4.0;
contract X {}
contract A is X {}
contract C is A, X {}
```
原因是`C`会请求`X`来重写`A`(因为继承定义的顺序是`A,X`),但`A`自身又是重写`X`的,所以这是一个不可解决的矛盾。
一个简单的指定基类合约的继承顺序原则是从`most base-like`到`most derived`。
## 继承有相同名字的不同类型成员
当继承最终导致一个合约同时存在多个相同名字的修改器或函数,它将被视为一个错误。同新的如果事件与修改器重名,或者函数与事件重名都将产生错误。作为一个例外,状态变量的`getter`可以覆盖一个`public`的函数。
- Solidity语言
- 入门说明
- Solidity智能合约文件结构
- 智能合约源文件的基本要素概览
- 值类型
- 类型
- 布尔
- 整型
- 地址
- 字节数组
- 小数
- 字符串
- 十六进制字面量
- 枚举
- 函数
- 引用类型
- 引用类型
- 数据位置
- 数组
- 数据结构
- 杂项
- 映射
- 左值运算符
- 类型间的转换
- 类型推断
- 单位
- 货币单位
- 时间单位
- 语言内置特性
- 特殊变量及函数
- 数学和加密函数
- 地址相关
- 进阶
- 入参和出参
- 控制结构
- 函数调用
- 创建合约实例
- 表达式的执行顺序
- 赋值
- 作用范围和声明
- 异常
- 内联汇编
- 合约详解
- 合约
- 可见性或权限控制
- 访问函数
- 函数修改器
- 常状态变量
- 回退函数
- 事件
- 继承
- 接口
- 其它
- 库
- 状态变量的存储模型
- 内存变量的存局
- 调用数据的布局