# 繼承(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智能合約文件結構
- 智能合約源文件的基本要素概覽
- 值類型
- 類型
- 布爾
- 整型
- 地址
- 字節數組
- 小數
- 字符串
- 十六進制字面量
- 枚舉
- 函數
- 引用類型
- 引用類型
- 數據位置
- 數組
- 數據結構
- 雜項
- 映射
- 左值運算符
- 類型間的轉換
- 類型推斷
- 單位
- 貨幣單位
- 時間單位
- 語言內置特性
- 特殊變量及函數
- 數學和加密函數
- 地址相關
- 進階
- 入參和出參
- 控制結構
- 函數調用
- 創建合約實例
- 表達式的執行順序
- 賦值
- 作用范圍和聲明
- 異常
- 內聯匯編
- 合約詳解
- 合約
- 可見性或權限控制
- 訪問函數
- 函數修改器
- 常狀態變量
- 回退函數
- 事件
- 繼承
- 接口
- 其它
- 庫
- 狀態變量的存儲模型
- 內存變量的存局
- 調用數據的布局