mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2024-12-25 22:38:56 +00:00
ch4-4 review
This commit is contained in:
parent
8c9ddc9bfa
commit
0e9ae4ab00
@ -1,6 +1,6 @@
|
|||||||
### 4.4.1. 結構體面值
|
### 4.4.1. 結構體面值
|
||||||
|
|
||||||
結構體值可以用結構體面值表示,結構體面值可以指定每個成員的值。
|
結構體值也可以用結構體面值表示,結構體面值可以指定每個成員的值。
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
type Point struct{ X, Y int }
|
type Point struct{ X, Y int }
|
||||||
@ -8,7 +8,7 @@ type Point struct{ X, Y int }
|
|||||||
p := Point{1, 2}
|
p := Point{1, 2}
|
||||||
```
|
```
|
||||||
|
|
||||||
這里有兩種形式的結構體面值語法,上面的是第一種寫法,要求以結構體成員定義的順序爲每個結構體成員指定一個面值。它要求寫代碼和讀代碼的人要記住結構體的每個成員的類型和順序,併且結構體成員有細微的調整就可能導致上述代碼不能編譯。因此,上述的語法一般隻在定義結構體的包內部使用,或者是在較小的結構體中使用,這些結構體的成員排列比較規則,比如image.Point{x, y}或color.RGBA{red, green, blue, alpha}。
|
這里有兩種形式的結構體面值語法,上面的是第一種寫法,要求以結構體成員定義的順序爲每個結構體成員指定一個面值。它要求寫代碼和讀代碼的人要記住結構體的每個成員的類型和順序,不過結構體成員有細微的調整就可能導致上述代碼不能編譯。因此,上述的語法一般隻在定義結構體的包內部使用,或者是在較小的結構體中使用,這些結構體的成員排列比較規則,比如image.Point{x, y}或color.RGBA{red, green, blue, alpha}。
|
||||||
|
|
||||||
其實更常用的是第二種寫法,以成員名字和相應的值來初始化,可以包含部分或全部的成員,如1.4節的Lissajous程序的寫法:
|
其實更常用的是第二種寫法,以成員名字和相應的值來初始化,可以包含部分或全部的成員,如1.4節的Lissajous程序的寫法:
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ func Bonus(e *Employee, percent int) int {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
如果要在函數內部脩改結構體成員的話,用指針傳入是必鬚的;因爲在Go語言中,所有的函數參數都是值拷貝出入的,函數參數將不再是函數調用時的原始變量。
|
如果要在函數內部脩改結構體成員的話,用指針傳入是必鬚的;因爲在Go語言中,所有的函數參數都是值拷貝傳入的,函數參數將不再是函數調用時的原始變量。
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
func AwardAnnualRaise(e *Employee) {
|
func AwardAnnualRaise(e *Employee) {
|
||||||
@ -72,5 +72,3 @@ pp := new(Point)
|
|||||||
```
|
```
|
||||||
|
|
||||||
不過&Point{1, 2}寫法可以直接在表達式中使用,比如一個函數調用。
|
不過&Point{1, 2}寫法可以直接在表達式中使用,比如一個函數調用。
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
### 4.4.3. 結構體嵌入和匿名成員
|
### 4.4.3. 結構體嵌入和匿名成員
|
||||||
|
|
||||||
在本節中,我們將看到如果使用Go語言提供的不同尋常的結構體嵌入機製讓一個命名的結構體包含另一個結構體類型的匿名成員,這樣就可以通過簡單的點運算符x.f來訪問匿名成員鏈中嵌套的x.d.e.f成員。
|
在本節中,我們將看到如何使用Go語言提供的不同尋常的結構體嵌入機製讓一個命名的結構體包含另一個結構體類型的匿名成員,這樣就可以通過簡單的點運算符x.f來訪問匿名成員鏈中嵌套的x.d.e.f成員。
|
||||||
|
|
||||||
考慮一個二維的繪圖程序,提供了一個各種圖形的庫,例如矩形、橢圓形、星形和輪形等幾何形狀。這里是其中兩個的定義:
|
考慮一個二維的繪圖程序,提供了一個各種圖形的庫,例如矩形、橢圓形、星形和輪形等幾何形狀。這里是其中兩個的定義:
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ w.Circle.Radius = 5
|
|||||||
w.Spokes = 20
|
w.Spokes = 20
|
||||||
```
|
```
|
||||||
|
|
||||||
Go語言有一個特性讓我們隻聲明一個成員對應的數據類型而不指名成員的名字;這類成員就叫匿名成員。匿名成員的數據類型必鬚是命名的類型或指向一個命名的類型的指針。下面的代碼中,Circle和Wheel各自都有一個匿名成員。我們可以説Point類型被嵌入了Circle結構體,同時Circle類型被嵌入了Wheel結構體。
|
Go語言有一個特性讓我們隻聲明一個成員對應的數據類型而不指名成員的名字;這類成員就叫匿名成員。匿名成員的數據類型必鬚是命名的類型或指向一個命名的類型的指針。下面的代碼中,Circle和Wheel各自都有一個匿名成員。我們可以説Point類型被嵌入到了Circle結構體,同時Circle類型被嵌入到了Wheel結構體。
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
type Circle struct {
|
type Circle struct {
|
||||||
@ -119,9 +119,8 @@ fmt.Printf("%#v\n", w)
|
|||||||
w.X = 8 // equivalent to w.circle.point.X = 8
|
w.X = 8 // equivalent to w.circle.point.X = 8
|
||||||
```
|
```
|
||||||
|
|
||||||
但是在包外部,因爲circle和point沒有導出不能訪問它們的成員,因此簡短語法也是禁止的。
|
但是在包外部,因爲circle和point沒有導出不能訪問它們的成員,因此簡短的匿名成員訪問語法也是禁止的。
|
||||||
|
|
||||||
到目前未知,我們看到匿名成員特性隻是對訪問嵌套成員的點運算符提供了簡短的語法醣。稍後,我們將會看到匿名成員併不要求是結構體類型;其實任何命令的類型都可以作爲結構體的匿名成員。但是爲什麽要嵌入一個沒有任何子成員類型的匿名成員類型呢?
|
到目前未知,我們看到匿名成員特性隻是對訪問嵌套成員的點運算符提供了簡短的語法醣。稍後,我們將會看到匿名成員併不要求是結構體類型;其實任何命令的類型都可以作爲結構體的匿名成員。但是爲什麽要嵌入一個沒有任何子成員類型的匿名成員類型呢?
|
||||||
|
|
||||||
答案是匿名類型的方法集。簡短的點運算符語法可以用於選擇匿名成員嵌套的成員,也可以用於訪問它們的方法。實際上,外層的結構體不僅僅是獲得了匿名成員類型的所有成員,而且也獲得了該類型導出的全部的方法。這個機製可以用於將一個有簡單行爲的對象組合成有複雜行爲的對象。組合是Go語言中面向對象編程的核心,我們將在6.3節中專門討論。
|
答案是匿名類型的方法集。簡短的點運算符語法可以用於選擇匿名成員嵌套的成員,也可以用於訪問它們的方法。實際上,外層的結構體不僅僅是獲得了匿名成員類型的所有成員,而且也獲得了該類型導出的全部的方法。這個機製可以用於將一個有簡單行爲的對象組合成有複雜行爲的對象。組合是Go語言中面向對象編程的核心,我們將在6.3節中專門討論。
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
## 4.4. 結構體
|
## 4.4. 結構體
|
||||||
|
|
||||||
結構體是一種聚合的數據類型,由零個或多個任意類型的值聚合成的實體。每個值稱爲結構體的成員。是用結構體的經典案例處理公司的員工信息,每個員工信息包含一個唯一的員工編號、員工的名字、家庭住址、出生日期、工作崗位、薪資、上級領導等等。所有的這些成員都需要綁定到一個實體,可以作爲一個整體單元被複製,作爲函數的參數或返迴值,或者是被存儲到數組中,等等。
|
結構體是一種聚合的數據類型,是由零個或多個任意類型的值聚合成的實體。每個值稱爲結構體的成員。用結構體的經典案例處理公司的員工信息,每個員工信息包含一個唯一的員工編號、員工的名字、家庭住址、出生日期、工作崗位、薪資、上級領導等等。所有的這些信息都需要綁定到一個實體中,可以作爲一個整體單元被複製,作爲函數的參數或返迴值,或者是被存儲到數組中,等等。
|
||||||
|
|
||||||
下面兩個語句分别聲明了一個叫Employee的結構體類型,併且聲明了一個Employee類型的變量dilbert:
|
下面兩個語句聲明了一個叫Employee的命名的結構體類型,併且聲明了一個Employee類型的變量dilbert:
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
type Employee struct {
|
type Employee struct {
|
||||||
@ -18,7 +18,7 @@ type Employee struct {
|
|||||||
var dilbert Employee
|
var dilbert Employee
|
||||||
```
|
```
|
||||||
|
|
||||||
dilbert結構體變量的成員可以通過點操作符訪問,比如dilbert.Name和dilbert.DoB。因爲dilbert是一個變量,它所有的成員也同樣是變量,我們可以對每個成員賦值:
|
dilbert結構體變量的成員可以通過點操作符訪問,比如dilbert.Name和dilbert.DoB。因爲dilbert是一個變量,它所有的成員也同樣是變量,我們可以直接對每個成員賦值:
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
dilbert.Salary -= 5000 // demoted, for writing too few lines of code
|
dilbert.Salary -= 5000 // demoted, for writing too few lines of code
|
||||||
@ -44,7 +44,7 @@ employeeOfTheMonth.Position += " (proactive team player)"
|
|||||||
(*employeeOfTheMonth).Position += " (proactive team player)"
|
(*employeeOfTheMonth).Position += " (proactive team player)"
|
||||||
```
|
```
|
||||||
|
|
||||||
EmployeeByID函數將根據給定的員工ID返迴對應的員工信息結構體的指針。我們可以使用點操作符來訪問它里面的成員:
|
下面的EmployeeByID函數將根據給定的員工ID返迴對應的員工信息結構體的指針。我們可以使用點操作符來訪問它里面的成員:
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
func EmployeeByID(id int) *Employee { /* ... */ }
|
func EmployeeByID(id int) *Employee { /* ... */ }
|
||||||
@ -55,7 +55,7 @@ id := dilbert.ID
|
|||||||
EmployeeByID(id).Salary = 0 // fired for... no real reason
|
EmployeeByID(id).Salary = 0 // fired for... no real reason
|
||||||
```
|
```
|
||||||
|
|
||||||
後面的語句通過EmployeeByID返迴的結構體指針更新了Employee結構體的成員。如果將EmployeeByID函數的返迴值從`*Employee`指針類型改爲Employee值類型,那麽更新語句將不能編譯通過,因爲在賦值語句的左邊併不確定是一個變量。
|
後面的語句通過EmployeeByID返迴的結構體指針更新了Employee結構體的成員。如果將EmployeeByID函數的返迴值從`*Employee`指針類型改爲Employee值類型,那麽更新語句將不能編譯通過,因爲在賦值語句的左邊併不確定是一個變量(譯註:調用函數返迴的是值,併不是一個可取地址的變量)。
|
||||||
|
|
||||||
通常一行對應一個結構體成員,成員的名字在前類型在後,不過如果相鄰的成員類型如果相同的話可以被合併到一行,就像下面的Name和Address成員那樣:
|
通常一行對應一個結構體成員,成員的名字在前類型在後,不過如果相鄰的成員類型如果相同的話可以被合併到一行,就像下面的Name和Address成員那樣:
|
||||||
|
|
||||||
@ -70,13 +70,13 @@ type Employee struct {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
結構體成員的輸入順序也有重要的意義。我們也可以將Position成員合併(因爲也是字符串類型),或者是交換Name和Address出現的先後順序,那樣的話就是定義了不同的結構體類型。通常,我們隻是將相關的成員合併到一起。
|
結構體成員的輸入順序也有重要的意義。我們也可以將Position成員合併(因爲也是字符串類型),或者是交換Name和Address出現的先後順序,那樣的話就是定義了不同的結構體類型。通常,我們隻是將相關的成員寫到一起。
|
||||||
|
|
||||||
如果結構體成員名字是以大寫字母開頭的,那麽該成員就是導出的;這是Go語言導出規則決定的。一個結構體可能同時包含導出和未導出的成員。
|
如果結構體成員名字是以大寫字母開頭的,那麽該成員就是導出的;這是Go語言導出規則決定的。一個結構體可能同時包含導出和未導出的成員。
|
||||||
|
|
||||||
結構體類型往往是冗長的,因爲它的每個成員可能都會占一行。雖然我們每次都可以重寫整個結構體成員,但是重複會令人厭煩。因此,完整的結構體寫法通常隻在類型聲明語句的地方出現,就像Employee類型聲明語句那樣。
|
結構體類型往往是冗長的,因爲它的每個成員可能都會占一行。雖然我們每次都可以重寫整個結構體成員,但是重複會令人厭煩。因此,完整的結構體寫法通常隻在類型聲明語句的地方出現,就像Employee類型聲明語句那樣。
|
||||||
|
|
||||||
一個命名爲S的結構體類型將不能再包含S類型的成員:一個聚合的值不能包含它自身。(該限製同樣適應於數組。)但是S類型的結構體可以包含`*S`指針類型的成員,這可以讓我們創建遞歸的數據結構,比如鏈表和樹結構等。在下面的代碼中,我們使用一個二叉樹來實現一個插入排序:
|
一個命名爲S的結構體類型將不能再包含S類型的成員:因爲一個聚合的值不能包含它自身。(該限製同樣適應於數組。)但是S類型的結構體可以包含`*S`指針類型的成員,這可以讓我們創建遞歸的數據結構,比如鏈表和樹結構等。在下面的代碼中,我們使用一個二叉樹來實現一個插入排序:
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
gopl.io/ch4/treesort
|
gopl.io/ch4/treesort
|
||||||
@ -122,7 +122,7 @@ func add(t *tree, value int) *tree {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
結構體類型的零值是每個成員都對是零值。通常會將零值作爲最合理的默認值。例如,在bytes.Buffer類型,結構體初始值就是一個隨時可用的空緩存,還有在第9章將會講到的sync.Mutex的零值也是有效的未鎖狀態。有時候這種零值可用的特性是自然獲得的,但是也有些類型需要一些額外的工作。
|
結構體類型的零值是每個成員都對是零值。通常會將零值作爲最合理的默認值。例如,對於bytes.Buffer類型,結構體初始值就是一個隨時可用的空緩存,還有在第9章將會講到的sync.Mutex的零值也是有效的未鎖定狀態。有時候這種零值可用的特性是自然獲得的,但是也有些類型需要一些額外的工作。
|
||||||
|
|
||||||
如果結構體沒有任何成員的話就是空結構體,寫作struct{}。它的大小爲0,也不包含任何信息,但是有時候依然是有價值的。有些Go語言程序員用map帶模擬set數據結構時,用它來代替map中布爾類型的value,隻是強調key的重要性,但是因爲節約的空間有限,而且語法比較複雜,所有我們通常避免避免這樣的用法。
|
如果結構體沒有任何成員的話就是空結構體,寫作struct{}。它的大小爲0,也不包含任何信息,但是有時候依然是有價值的。有些Go語言程序員用map帶模擬set數據結構時,用它來代替map中布爾類型的value,隻是強調key的重要性,但是因爲節約的空間有限,而且語法比較複雜,所有我們通常避免避免這樣的用法。
|
||||||
|
|
||||||
@ -140,4 +140,3 @@ if _, ok := seen[s]; !ok {
|
|||||||
{% include "./ch4-04-2.md" %}
|
{% include "./ch4-04-2.md" %}
|
||||||
|
|
||||||
{% include "./ch4-04-3.md" %}
|
{% include "./ch4-04-3.md" %}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user