Solidity語言學習筆記————34、繼承
繼承
Solidity通過複製包括多型性的程式碼來支援多重繼承。
除非合約是顯式給出的,所有的函式呼叫都是虛擬的,絕大多數派生函式可被呼叫。
當一個合約繼承自多個合約時,只會在區塊鏈上建立單個合約,並將所有父合約中的程式碼複製到建立的合約中。
Solidity的繼承與Python非常相似,特別是多繼承。
以下是個例子:
pragma solidity ^0.4.16;
contract owned {
function owned() { owner = msg.sender; }
address owner;
}
// 使用`is`繼承另一個合約。
// 子合約可以訪問所有非私有成員,包括
// 內部函式和狀態變數。
// 不過,不能通過`this`來外部訪問這些。
contract mortal is owned {
function kill() {
if (msg.sender == owner) selfdestruct(owner);
}
}
// 這些抽象合約僅用於建立編譯器已知的介面。
// 注意函式沒有函式體。
// 如果合約沒有實現全部函式,那它只能是介面。
contract Config {
function lookup(uint id) public returns (address adr);
}
contract NameReg {
function register(bytes32 name) public;
function unregister() public;
}
// 可以多重繼承。
// 請注意,`owned`也是`mortal`的父類,
// 但只有一個`owned`的例項(類似C++中的虛擬繼承)。
contract named is owned, mortal {
function named(bytes32 name) {
Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
NameReg(config.lookup(1)).register(name);
}
// 函式可以被另一個具有相同名稱
// 和相同數量/型別的輸入引數的函式覆蓋。
// 如果重寫函式有不同輸出引數的型別,會發生錯誤。
// 本地和基於訊息的函式呼叫都會將這些覆蓋函式考慮在內。
function kill() public {
if (msg.sender == owner) {
Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
NameReg(config.lookup(1)).unregister();
// 仍然可以呼叫特定的重寫函式。
mortal.kill();
}
}
}
// 如果建構函式接受引數,引數需要在頭部中提供。
// 或者在派生合約的構造器裡
// 使用修飾符呼叫方式modifier-invocation-style見下文
contract PriceFeed is owned, mortal, named("GoldFeed") {
function updateInfo(uint newInfo) public {
if (msg.sender == owner) info = newInfo;
}
function get() public view returns(uint r) { return info; }
uint info;
}
注意:在上文中,我們使用mortal.kill() 來“forward” 析構請求。這種做法是有問題的,請看下面的例子:
pragma solidity ^0.4.0;
contract owned {
function owned() public { owner = msg.sender; }
address owner;
}
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
。這種情況下要使用 super
pragma solidity ^0.4.0;
contract owned {
function owned() public { owner = msg.sender; }
address owner;
}
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 {
}
若Base1
呼叫了super
函式,它不是簡單地呼叫基本合約之一的函式, 它是呼叫最後繼承關係的下一個基本合約的函式。所以它會呼叫Base1.kill()
(注意,最後的繼承順序是–從最後的派生合約開始:Final, Base1, Base2, mortal, owned)。當使用類的上下文中super不知道的情況下,真正的函式將被呼叫,雖然它的型別已經知道。這個和普通的virtual方法的查詢相似。
父建構函式的引數(Arguments for Base Constructors)
派生的合約需要為父建構函式提供所有的引數。有兩種方式:
pragma solidity ^0.4.0;
contract Base {
uint x;
function Base(uint _x) public { x = _x; }
}
contract Derived is Base(7) {
function Derived(uint _y) Base(_y * _y) public {
}
}
- 第一種是直接在繼承列表裡實現
is Base(7)
- 第二種是在派生的構造器的頭部,修飾符被呼叫時實現
Base(_y * _y)
。
使用原則:
- 如果建構函式引數是一個常量,並且定義了合約的行為或描述了它的行為,第一種方式比較方便。
- 如果父建構函式引數依賴於派生合約的建構函式,則必須使用第二種方法。
- 如果在這個荒謬的例子中,這兩個地方都被使用,修飾符樣式的引數優先。
多繼承和線性化(Multiple Inheritance and Linearization)
允許多重繼承的程式語言必須處理這幾個問題,其中一個是Diamond問題。Solidity是沿用Python的方式, 使用C3線性化,在基類的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”。
繼承不同型別的同名成員(Inheriting Different Kinds of Members of the Same Name)
When the inheritance results in a contract with a function and a modifier of the same name, it is considered as an error. This error is produced also by an event and a modifier of the same name, and a function and an event of the same name. As an exception, a state variable getter can override a public function.
當繼承導致存在有相同名稱的函式和修飾符的合約時,會發生錯誤。 這個錯誤也會由同名的事件和修飾符以及同名的函式和事件產生的。
例外是狀態變數getter可以覆蓋public 函式。
相關文章
- Solidity語言學習筆記————1、初識Solidity語言Solid筆記
- Solidity語言學習筆記————38、Solidity彙編Solid筆記
- Solidity語言學習筆記————36、 庫Solid筆記
- Solidity語言學習筆記————37、Using forSolid筆記
- Solidity語言學習筆記————4、常量Solid筆記
- Solidity語言學習筆記————33、事件(Events)Solid筆記事件
- Solidity語言學習筆記————12、陣列Solid筆記陣列
- Solidity語言學習筆記————43、安全考量Solid筆記
- Solidity語言學習筆記————42、提示和技巧Solid筆記
- Solidity語言學習筆記————28、純函式Solid筆記函式
- Solidity語言學習筆記————26、回退函式Solid筆記函式
- Solidity語言學習筆記————16、對映MappingSolid筆記APP
- Solidity語言學習筆記————32、建立合約Solid筆記
- Solidity語言學習筆記————41、記憶體佈局Solid筆記記憶體
- Solidity語言學習筆記————39、獨立彙編Solid筆記
- Solidity語言學習筆記————25、作用域和宣告Solid筆記
- Solidity語言學習筆記————27、檢視函式Solid筆記函式
- Solidity語言學習筆記————10、布林型、整型Solid筆記
- Solidity語言學習筆記————3、Remix的基本使用Solid筆記REM
- Solidity語言學習筆記————15、結構體StructSolid筆記結構體Struct
- Solidity語言學習筆記————18、字串和函式Solid筆記字串函式
- Solidity語言學習筆記————14、左值運算子Solid筆記
- Solidity語言學習筆記————9、左值運算子Solid筆記
- Solidity語言學習筆記————17、原始檔對映Solid筆記
- Solidity語言學習筆記————5、全域性變數Solid筆記變數
- Solidity語言學習筆記————2、使用編譯器Solid筆記編譯
- Solidity語言學習筆記————30、函式過載Solid筆記函式
- Solidity語言學習筆記————35、抽象合約和介面Solid筆記抽象
- Solidity語言學習筆記————26、Assert, Require, Revert 和 ExceptionsSolid筆記UIException
- Solidity語言學習筆記————22、可見性和GettersSolid筆記
- Solidity語言學習筆記————20、函式修飾符Solid筆記函式
- Solidity語言學習筆記————23、函式呼叫和賦值Solid筆記函式賦值
- Solidity語言學習筆記————24、輸入輸出引數Solid筆記
- Solidity語言學習筆記————13、固定大小位元組陣列Solid筆記陣列
- Solidity語言學習筆記————14、動態位元組陣列Solid筆記陣列
- Solidity語言學習筆記————8、運算子優先順序Solid筆記
- C++學習筆記——C++ 繼承C++筆記繼承
- Solidity語言學習筆記————44、合約的後設資料Solid筆記