最近在復(fù)習(xí)golang贮勃,學(xué)習(xí)的東西,如果不使用苏章,很快就會(huì)忘記寂嘉。所以奏瞬,準(zhǔn)備復(fù)習(xí)完golang,做一個(gè)練手的小項(xiàng)目泉孩,加深對(duì)golang的學(xué)習(xí)硼端。今天開始公司,進(jìn)入封閉式開發(fā)寓搬,所以每天晚上回來(lái)珍昨,學(xué)習(xí)golang時(shí)間比較少了。所以句喷,爭(zhēng)取一天一章的學(xué)習(xí)镣典。
學(xué)習(xí)資源: https://github.com/Unknwon, 無(wú)聞唾琼,golang的大牛兄春,他的文檔網(wǎng)站很不錯(cuò)。教學(xué)也很好锡溯。golang的在線測(cè)試 https://play.golang.org赶舆,直接在瀏覽器中測(cè)試。
struct的主要內(nèi)容:
結(jié)構(gòu)體的定義和使用
使用字面量初始化
匿名結(jié)構(gòu)和字段
結(jié)構(gòu)體之間的比較和賦值
嵌入結(jié)構(gòu)
提升字段
字段屬性的訪問使用
引用傳遞和值傳遞
什么是結(jié)構(gòu)體struct祭饭?
結(jié)構(gòu)體是用戶定義的類型芜茵,表示若干個(gè)字段(Field)的集合。有時(shí)應(yīng)該把數(shù)據(jù)整合在一起倡蝙,而不是讓這些數(shù)據(jù)沒有聯(lián)系夕晓。這種情況下可以使用結(jié)構(gòu)體。就是面向?qū)ο缶幊陶Z(yǔ)言中的類class
例如悠咱,一個(gè)職員有 firstName
、lastName
和 age
三個(gè)屬性征炼,而把這些屬性組合在一個(gè)結(jié)構(gòu)體 employee
中就很合理析既。
結(jié)構(gòu)體的聲明
type Employee struct {
firstName string
lastName string
age int
}
在上面的代碼片段里,聲明了一個(gè)結(jié)構(gòu)體類型 Employee
谆奥,它有 firstName
眼坏、lastName
和 age
三個(gè)字段。通過(guò)把相同類型的字段聲明在同一行酸些,結(jié)構(gòu)體可以變得更加緊湊宰译。在上面的結(jié)構(gòu)體中,firstName
和 lastName
屬于相同的 string
類型魄懂,于是這個(gè)結(jié)構(gòu)體可以重寫為:
type Employee struct {
firstName, lastName string
age, salary int
}
上面的結(jié)構(gòu)體 Employee
稱為 命名的結(jié)構(gòu)體(Named Structure)沿侈。我們創(chuàng)建了名為 Employee
的新類型,而它可以用于創(chuàng)建 Employee
類型的結(jié)構(gòu)體變量市栗。
聲明結(jié)構(gòu)體時(shí)也可以不用聲明一個(gè)新類型缀拭,這樣的結(jié)構(gòu)體類型稱為 匿名結(jié)構(gòu)體(Anonymous Structure)咳短。
var employee struct {
firstName, lastName string
age int
}
上述代碼片段創(chuàng)建一個(gè)匿名結(jié)構(gòu)體 employee
。匿名結(jié)構(gòu)體是使用字面量直接初始化的蛛淋。
創(chuàng)建命名的結(jié)構(gòu)體
通過(guò)下面代碼咙好,我們定義了一個(gè)命名的結(jié)構(gòu)體 Employee。
package main
import (
"fmt"
)
type Employee struct {
firstName, lastName string
age, salary int
}
func main() {
//creating structure using field names
emp1 := Employee{
firstName: "youdi",
age: 25,
salary: 500,
lastName: "liang",
}
//creating structure without using field names
emp2 := Employee{"hui", "liang", 26, 800}
fmt.Println("Employee 1", emp1)
fmt.Println("Employee 2", emp2)
}
在上述程序的第 7 行褐荷,我們創(chuàng)建了一個(gè)命名的結(jié)構(gòu)體 Employee
勾效。而在第 15 行,通過(guò)指定每個(gè)字段名的值叛甫,我們定義了結(jié)構(gòu)體變量 emp1
层宫。字段名的順序不一定要與聲明結(jié)構(gòu)體類型時(shí)的順序相同。在這里合溺,我們改變了 lastName
的位置卒密,將其移到了末尾。這樣做也不會(huì)有任何的問題棠赛。
在上面程序中哮奇,定義 emp2
時(shí)我們省略了字段名。在這種情況下睛约,就需要保證字段名的順序與聲明結(jié)構(gòu)體時(shí)的順序相同鼎俘。
該程序?qū)⑤敵觯?/p>
Employee 1 {youdi liang 25 500}
Employee 2 {hui liang 26 800}
創(chuàng)建匿名結(jié)構(gòu)體
package main
import (
"fmt"
)
func main() {
emp3 := struct {
firstName, lastName string
age, salary int
}{
firstName: "Andreah",
lastName: "Nikola",
age: 31,
salary: 5000,
}
fmt.Println("Employee 3", emp3)
}
在上述程序的第 3 行,我們定義了一個(gè)匿名結(jié)構(gòu)體變量 emp3
辩涝。上面我們已經(jīng)提到贸伐,之所以稱這種結(jié)構(gòu)體是匿名的,是因?yàn)樗皇莿?chuàng)建一個(gè)新的結(jié)構(gòu)體變量 em3
怔揩,而沒有定義任何結(jié)構(gòu)體類型捉邢。
該程序會(huì)輸出:
Employee 3 {Andreah Nikola 31 5000}
結(jié)構(gòu)體的零值(Zero Value)
當(dāng)定義好的結(jié)構(gòu)體并沒有被顯式地初始化時(shí),該結(jié)構(gòu)體的字段將默認(rèn)賦為零值商膊。
package main
import (
"fmt"
)
type Employee struct {
firstName, lastName string
age, salary int
}
func main() {
var emp4 Employee //zero valued structure
fmt.Println("Employee 4", emp4)
}
該程序定義了 emp4
伏伐,卻沒有初始化任何值。因此 firstName
和 lastName
賦值為 string 的零值(""
)晕拆。零值是對(duì)應(yīng)類型的零值藐翎。 而 age
和 salary
賦值為 int 的零值(0)。該程序會(huì)輸出:
Employee 4 { 0 0}
當(dāng)然還可以為某些字段指定初始值实幕,而忽略其他字段吝镣。這樣,忽略的字段名會(huì)賦值為零值昆庇。
package main
import (
"fmt"
)
type Employee struct {
firstName, lastName string
age, salary int
}
func main() {
emp5 := Employee{
firstName: "John",
lastName: "Paul",
}
fmt.Println("Employee 5", emp5)
}
在上面程序中的第 14 行和第 15 行末贾,我們初始化了 firstName
和 lastName
,而 age
和 salary
沒有進(jìn)行初始化凰锡。因此 age
和 salary
賦值為零值未舟。該程序會(huì)輸出:
Employee 5 {John Paul 0 0}
訪問結(jié)構(gòu)體的字段
點(diǎn)號(hào)操作符 .
用于訪問結(jié)構(gòu)體的字段圈暗。
package main
import (
"fmt"
)
type Employee struct {
firstName, lastName string
age, salary int
}
func main() {
emp6 := Employee{"Sam", "Anderson", 55, 6000}
fmt.Println("First Name:", emp6.firstName)
fmt.Println("Last Name:", emp6.lastName)
fmt.Println("Age:", emp6.age)
fmt.Printf("Salary: $%d", emp6.salary)
}
上面程序中的 emp6.firstName 訪問了結(jié)構(gòu)體 emp6
的字段 firstName
。該程序輸出:
First Name: Sam
Last Name: Anderson
Age: 55
Salary: $6000
還可以創(chuàng)建零值的 struct
裕膀,以后再給各個(gè)字段賦值员串。
package main
import (
"fmt"
)
type Employee struct {
firstName, lastName string
age, salary int
}
func main() {
var emp7 Employee
emp7.firstName = "Jack"
emp7.lastName = "Adams"
fmt.Println("Employee 7:", emp7)
}
在上面程序中,我們定義了 emp7
昼扛,接著給 firstName
和 lastName
賦值寸齐。該程序會(huì)輸出:
Employee 7: {Jack Adams 0 0}
結(jié)構(gòu)體的指針
還可以創(chuàng)建指向結(jié)構(gòu)體的指針。
package main
import (
"fmt"
)
type Employee struct {
firstName, lastName string
age, salary int
}
func main() {
emp8 := &Employee{"Sam", "Anderson", 55, 6000}
fmt.Println("First Name:", (*emp8).firstName)
fmt.Println("Age:", (*emp8).age)
}
在上面程序中抄谐,emp8 是一個(gè)指向結(jié)構(gòu)體 Employee
的指針渺鹦。(*emp8).firstName
表示訪問結(jié)構(gòu)體 emp8
的 firstName
字段。該程序會(huì)輸出:
First Name: Sam
Age: 55
Go 語(yǔ)言允許我們?cè)谠L問 firstName 字段時(shí)蛹含,可以使用 emp8.firstName 來(lái)代替顯式的解引用 (*emp8).firstName毅厚。
package main
import (
"fmt"
)
type Employee struct {
firstName, lastName string
age, salary int
}
func main() {
emp8 := &Employee{"Sam", "Anderson", 55, 6000}
fmt.Println("First Name:", emp8.firstName)
fmt.Println("Age:", emp8.age)
}
在上面的程序中,我們使用 emp8.firstName
來(lái)訪問 firstName
字段浦箱,該程序會(huì)輸出:
First Name: Sam
Age: 55
匿名字段
當(dāng)我們創(chuàng)建結(jié)構(gòu)體時(shí)吸耿,字段可以只有類型,而沒有字段名酷窥。這樣的字段稱為匿名字段(Anonymous Field)咽安。
以下代碼創(chuàng)建一個(gè) Person
結(jié)構(gòu)體,它含有兩個(gè)匿名字段 string
和 int
蓬推。
type Person struct {
string
int
}
我們接下來(lái)使用匿名字段來(lái)編寫一個(gè)程序妆棒。
package main
import (
"fmt"
)
type Person struct {
string
int
}
func main() {
p := Person{"Naveen", 50}
fmt.Println(p)
}
在上面的程序中,結(jié)構(gòu)體 Person
有兩個(gè)匿名字段沸伏。p := Person{"Naveen", 50}
定義了一個(gè) Person
類型的變量糕珊。該程序輸出 {Naveen 50}
。
雖然匿名字段沒有名稱毅糟,但其實(shí)匿名字段的名稱就默認(rèn)為它的類型放接。比如在上面的 Person
結(jié)構(gòu)體里,雖說(shuō)字段是匿名的留特,但 Go 默認(rèn)這些字段名是它們各自的類型。所以 Person
結(jié)構(gòu)體有兩個(gè)名為 string
和 int
的字段玛瘸。
package main
import (
"fmt"
)
type Person struct {
string
int
}
func main() {
var p1 Person
p1.string = "naveen"
p1.int = 50
fmt.Println(p1)
}
在上面程序中蜕青,我們?cè)L問了 Person
結(jié)構(gòu)體的匿名字段,我們把字段類型作為字段名糊渊,分別為 "string" 和 "int"右核。上面程序的輸出如下:
{naveen 50}
嵌套結(jié)構(gòu)體(Nested Structs)
結(jié)構(gòu)體的字段有可能也是一個(gè)結(jié)構(gòu)體。這樣的結(jié)構(gòu)體稱為嵌套結(jié)構(gòu)體渺绒。
package main
import (
"fmt"
)
type Address struct {
city, state string
}
type Person struct {
name string
age int
address Address
}
func main() {
var p Person
p.name = "Naveen"
p.age = 50
p.address = Address {
city: "Chicago",
state: "Illinois",
}
fmt.Println("Name:", p.name)
fmt.Println("Age:",p.age)
fmt.Println("City:",p.address.city)
fmt.Println("State:",p.address.state)
}
上面的結(jié)構(gòu)體 Person
有一個(gè)字段 address
贺喝,而 address
也是結(jié)構(gòu)體菱鸥。該程序輸出:
Name: Naveen
Age: 50
City: Chicago
State: Illinois
提升字段(Promoted Fields)
如果是結(jié)構(gòu)體中有匿名的結(jié)構(gòu)體類型字段,則該匿名結(jié)構(gòu)體里的字段就稱為提升字段躏鱼。這是因?yàn)樘嵘侄尉拖袷菍儆谕獠拷Y(jié)構(gòu)體一樣氮采,可以用外部結(jié)構(gòu)體直接訪問。我知道這種定義很復(fù)雜染苛,所以我們直接研究下代碼來(lái)理解吧鹊漠。
type Address struct {
city, state string
}
type Person struct {
name string
age int
Address
}
在上面的代碼片段中,Person
結(jié)構(gòu)體有一個(gè)匿名字段 Address
茶行,而 Address
是一個(gè)結(jié)構(gòu)體∏牛現(xiàn)在結(jié)構(gòu)體 Address
有 city
和 state
兩個(gè)字段,訪問這兩個(gè)字段就像在 Person
里直接聲明的一樣畔师,因此我們稱之為提升字段娶靡。
package main
import (
"fmt"
)
type Address struct {
city, state string
}
type Person struct {
name string
age int
Address
}
func main() {
var p Person
p.name = "Naveen"
p.age = 50
p.Address = Address{
city: "Chicago",
state: "Illinois",
}
fmt.Println("Name:", p.name)
fmt.Println("Age:", p.age)
fmt.Println("City:", p.city) //city is promoted field
fmt.Println("State:", p.state) //state is promoted field
}
在上面代碼中的第 26 行和第 27 行,我們使用了語(yǔ)法 p.city
和 p.state
看锉,訪問提升字段 city
和 state
就像它們是在結(jié)構(gòu)體 p
中聲明的一樣姿锭。該程序會(huì)輸出:
Name: Naveen
Age: 50
City: Chicago
State: Illinois
導(dǎo)出結(jié)構(gòu)體和字段
如果結(jié)構(gòu)體名稱以大寫字母開頭,則它是其他包可以訪問的導(dǎo)出類型(Exported Type)度陆。同樣艾凯,如果結(jié)構(gòu)體里的字段首字母大寫,它也能被其他包訪問到懂傀。
讓我們使用自定義包趾诗,編寫一個(gè)程序來(lái)更好地去理解它。
在你的 Go 工作區(qū)的 src
目錄中蹬蚁,創(chuàng)建一個(gè)名為 structs
的文件夾恃泪。另外在 structs
中再創(chuàng)建一個(gè)目錄 computer
。
在 computer
目錄中犀斋,在名為 spec.go
的文件中保存下面的程序贝乎。
package computer
type Spec struct { //exported struct
Maker string //exported field
model string //unexported field
Price int //exported field
}
上面的代碼片段中,創(chuàng)建了一個(gè) computer
包叽粹,里面有一個(gè)導(dǎo)出結(jié)構(gòu)體類型 Spec
览效。Spec
有兩個(gè)導(dǎo)出字段 Maker
和 Price
,和一個(gè)未導(dǎo)出的字段 model
虫几。接下來(lái)我們會(huì)在 main 包中導(dǎo)入這個(gè)包锤灿,并使用 Spec
結(jié)構(gòu)體。
package main
import "structs/computer"
import "fmt"
func main() {
var spec computer.Spec
spec.Maker = "apple"
spec.Price = 50000
fmt.Println("Spec:", spec)
}
包結(jié)構(gòu)如下所示:
src
structs
computer
spec.go
main.go
在上述程序的第 3 行辆脸,我們導(dǎo)入了 computer
包但校。在第 8 行和第 9 行,我們?cè)L問了結(jié)構(gòu)體 Spec
的兩個(gè)導(dǎo)出字段 Maker
和 Price
啡氢。執(zhí)行命令 go install structs
和 workspacepath/bin/structs
状囱,運(yùn)行該程序术裸。
如果我們?cè)噲D訪問未導(dǎo)出的字段 model
,編譯器會(huì)報(bào)錯(cuò)亭枷。將 main.go
的內(nèi)容替換為下面的代碼袭艺。
package main
import "structs/computer"
import "fmt"
func main() {
var spec computer.Spec
spec.Maker = "apple"
spec.Price = 50000
spec.model = "Mac Mini"
fmt.Println("Spec:", spec)
}
在上面程序,我們?cè)噲D訪問未導(dǎo)出的字段 model
奶栖。如果運(yùn)行這個(gè)程序匹表,編譯器會(huì)產(chǎn)生錯(cuò)誤:spec.model undefined (cannot refer to unexported field or method model)。
結(jié)構(gòu)體相等性(Structs Equality)
結(jié)構(gòu)體是值類型宣鄙。如果它的每一個(gè)字段都是可比較的袍镀,則該結(jié)構(gòu)體也是可比較的。如果兩個(gè)結(jié)構(gòu)體變量的對(duì)應(yīng)字段相等冻晤,則這兩個(gè)變量也是相等的苇羡。
package main
import (
"fmt"
)
type name struct {
firstName string
lastName string
}
func main() {
name1 := name{"Steve", "Jobs"}
name2 := name{"Steve", "Jobs"}
if name1 == name2 {
fmt.Println("name1 and name2 are equal")
} else {
fmt.Println("name1 and name2 are not equal")
}
name3 := name{firstName:"Steve", lastName:"Jobs"}
name4 := name{}
name4.firstName = "Steve"
if name3 == name4 {
fmt.Println("name3 and name4 are equal")
} else {
fmt.Println("name3 and name4 are not equal")
}
}
在上面的代碼中,結(jié)構(gòu)體類型 name
包含兩個(gè) string
類型鼻弧。由于字符串是可比較的设江,因此可以比較兩個(gè) name
類型的結(jié)構(gòu)體變量。
上面代碼中 name1
和 name2
相等攘轩,而 name3
和 name4
不相等叉存。該程序會(huì)輸出:
name1 and name2 are equal
name3 and name4 are not equal
如果結(jié)構(gòu)體包含不可比較的字段,則結(jié)構(gòu)體變量也不可比較度帮。
package main
import (
"fmt"
)
type image struct {
data map[int]int
}
func main() {
image1 := image{data: map[int]int{
0: 155,
}}
image2 := image{data: map[int]int{
0: 155,
}}
if image1 == image2 {
fmt.Println("image1 and image2 are equal")
}
}
在上面代碼中歼捏,結(jié)構(gòu)體類型 image
包含一個(gè) map
類型的字段。由于 map
類型是不可比較的笨篷,因此 image1
和 image2
也不可比較瞳秽。如果運(yùn)行該程序,編譯器會(huì)報(bào)錯(cuò):main.go:18: invalid operation: image1 == image2 (struct containing map[int]int cannot be compared)率翅。