---
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,無法跳過。