--- title: 'Solidity WTF 101 13 單元' lang: zh-tw --- Solidity WTF 101 13 單元 === :::info :date: 2024/09/30 ::: [TOC] # 課程學習 ## 繼承 分別為`簡單繼承`、`多重繼承`、`修飾器繼承`、`構造函數繼承`。 會提到兩個參數: - `virtual`: 父合約中的函數,如果想要子合約覆寫,那就需要加上。 - `override`: 子合約如果已經覆寫那就需要加上。 ### 簡單繼承 這邊使用課程的`code` - 者是`Yeye`合約 ```xml= contract Yeye { event Log(string msg); // 定义3个function: hip(), pop(), man(),Log值为Yeye。 function hip() public virtual{ emit Log("Yeye"); } function pop() public virtual{ emit Log("Yeye"); } function yeye() public virtual { emit Log("Yeye"); } } ``` - 這是`Baba`合約,他繼承了`Yeye` ```xml= contract Baba is Yeye{ // 继承两个function: hip()和pop(),输出改为Baba。 function hip() public virtual override{ emit Log("Baba"); } function pop() public virtual override{ emit Log("Baba"); } function baba() public virtual{ emit Log("Baba"); } } ``` 這些輸出結果都會是`Baba`的結果,因為被覆寫。 ### 多重繼承 這邊同時繼承了`Yeye`和`Baba`兩個合約,在多重繼承會有幾個規則。 - 按照輩分: 因為`Baba`繼承了`Yeye`,所以`Yeye`輩分比`Baba`高,那在`Erzi`多重繼承下就要按照下面`Code`的方式給予繼承。 - 重写在多个父合约中都重名的函数时,`override`关键字后面要加上所有父合约名字,例如`override(Yeye, Baba)`,需要讓合約知道我要重寫這兩個合約的方法。 ```xml= contract Erzi is Yeye, Baba{ // 继承两个function: hip()和pop(),输出值为Erzi。 function hip() public virtual override(Yeye, Baba){ emit Log("Erzi"); } function pop() public virtual override(Yeye, Baba) { emit Log("Erzi"); } } ``` ### 修飾器的繼承 Modifier 一樣可以繼承 ```xml= contract Base1 { modifier exactDividedBy2And3(uint _a) virtual { require(_a % 2 == 0 && _a % 3 == 0); _; } } contract Identifier is Base1 { //计算一个数分别被2除和被3除的值,但是传入的参数必须是2和3的倍数 function getExactDividedBy2And3(uint _dividend) public exactDividedBy2And3(_dividend) pure returns(uint, uint) { return getExactDividedBy2And3WithoutModifier(_dividend); } //计算一个数分别被2除和被3除的值 function getExactDividedBy2And3WithoutModifier(uint _dividend) public pure returns(uint, uint){ uint div2 = _dividend / 2; uint div3 = _dividend / 3; return (div2, div3); } <!-- 如果今天你要重構,方式就直接加入 override 跟上面 pop 方法相同 --> modifier exactDividedBy2And3(uint _a) override { _; require(_a % 2 == 0 && _a % 3 == 0); } } ``` ### 構造函數的繼承 有分為兩種方法 ```xml= // 构造函数的继承 abstract contract A { uint public a; constructor(uint _a) { a = _a; } } ``` ```xml= <!-- 第一種在子合约的构造函数中声明构造函数的参数 --> contract C is A { constructor(uint _c) A(_c * _c) {} } <!-- 在继承时声明父构造函数的参数 --> contract B is A(1) <!-- 這種方法就是繼承同時直接帶參數 --> ``` ### 调用父合约的函数 在子合約中有兩種方式可以調用父合約中的函數,那`super`這種方式也是在其他語言向是java還有js中比較常見的方式 - 直接調用: 子合約用`parent.functionName()`來調用父合約中的函數。 - `super`: 子合約使用`super.functionName()`來調用父合約中的函數。 ```xml= contract B { function _thisIsTest() public view{ return "this is test"; } } contract A is B { <!-- 這是直接調用 --> B._thisIsTest(); <!-- super調用 --> super._thisIsTest(); <!-- 以上兩種方式相同結果 --> } ``` ### 鑽石繼承 這個與有向無環圖有關(DAG),有向無環圖的意思是,當你走過就不會在走。 那這邊運用課程中的範例 ```xml= /* 继承树: God / \ Adam Eve \ / people */ <!-- 這邊代表有四個合約,Adam 和 Eve 繼承了 God 合約,people 則繼承了 Adam 和 Eve, 那如果今天我的程式碼如下。--> contract God { event Log(string message); function foo() public virtual { emit Log("God.foo called"); } function bar() public virtual { emit Log("God.bar called"); } } contract Adam is God { function foo() public virtual override { emit Log("Adam.foo called"); super.foo(); } function bar() public virtual override { emit Log("Adam.bar called"); super.bar(); } } contract Eve is God { function foo() public virtual override { emit Log("Eve.foo called"); super.foo(); } function bar() public virtual override { emit Log("Eve.bar called"); super.bar(); } } contract people is Adam, Eve { function foo() public override(Adam, Eve) { super.foo(); } function bar() public override(Adam, Eve) { super.bar(); } } ``` :::warning 實際部屬會發現,他的印出順序會是`Eve` -> `Adam` -> `God`,因為`DAG`緣故,他會按照編譯順序去走,也就是說即使都是相同繼承`God`,但是會因為編譯順序不同而`super`的結果也會不一樣。 ::: :::success 如果今天是Eve合約在上,Adam合約在Eve下面,那結果就會是`Adam` -> `Eve` -> `God` ::: #### 有向無環圖(Directed Acyclic Graph, DAG) 即不存在任何環的有向圖。DAG 比一般的有向圖存在更多可利用的性質,在有向圖相關問題中十分重要。 ### DAG 的一些特性 由於不存在任何的環,代表從任何一個 vertex 出發,不論怎麼走,最終都會走到無路可去的 vertex 而停下。沒有環的話路徑長度會是有限的。 沒有環也就不會導致像剪刀石頭布一樣的循環依賴,和 DP 的相性十分良好 ## 重點經驗 這一章重點是在鑽石繼承與DAG的部分 - 鑽石繼承順序會依照編譯順序 - DAG是有向無環圖,他只會一直往下走並且遵循這個順序,如果現在位置在3但你要到第1個,那你必須-> 2 -> 1,無法跳過。