1 結(jié)構(gòu)體
1.1 簡(jiǎn)介
Go 語(yǔ)言中數(shù)組可以存儲(chǔ)同一類(lèi)型的數(shù)據(jù)头朱,但在結(jié)構(gòu)體中我們可以為不同項(xiàng)定義不同的數(shù)據(jù)類(lèi)型。
結(jié)構(gòu)體是由一系列具有相同類(lèi)型或不同類(lèi)型的數(shù)據(jù)構(gòu)成的數(shù)據(jù)集合龄减。
1.2 定義結(jié)構(gòu)體
結(jié)構(gòu)體定義需要使用 type
和 struct
語(yǔ)句项钮。struct
語(yǔ)句定義一個(gè)新的數(shù)據(jù)類(lèi)型,結(jié)構(gòu)體中有一個(gè)或多個(gè)成員希停。type 語(yǔ)句設(shè)定了結(jié)構(gòu)體的名稱(chēng)烁巫。結(jié)構(gòu)體的格式如下:
type struct_variable_type struct {
member definition
member definition
...
member definition
}
一旦定義了結(jié)構(gòu)體類(lèi)型,它就能用于變量的聲明宠能,語(yǔ)法格式如下:
variable_name := structure_variable_type {value1, value2...valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}
實(shí)例如下:
package main
import "fmt"
type Books struct {
title string
author string
subject string
book_id int
}
func main() {
// 創(chuàng)建一個(gè)新的結(jié)構(gòu)體
fmt.Println(Books{"Go 語(yǔ)言", "www.test.com", "Go 語(yǔ)言教程", 6495407})
// 也可以使用 key => value 格式
fmt.Println(Books{title: "Go 語(yǔ)言", author: "www.test.com", subject: "Go 語(yǔ)言教程", book_id: 6495407})
// 忽略的字段為 0 或 空
fmt.Println(Books{title: "Go 語(yǔ)言", author: "www.test.com"})
}
1.3 聲明結(jié)構(gòu)體
1.3.1 new聲明
在 Go 中亚隙,結(jié)構(gòu)體可以通過(guò) new
函數(shù)直接創(chuàng)建。new
是 Go 內(nèi)置的函數(shù)违崇,用來(lái)分配內(nèi)存并返回指向該類(lèi)型的指針阿弃。
new 的使用,其中 T
是結(jié)構(gòu)體類(lèi)型羞延。
p := new(T)
new
為類(lèi)型 T
分配一塊內(nèi)存(零值初始化)渣淳,返回指向 T
的指針(類(lèi)型為 *T
)
1.3.2 直接聲明
除了使用 new
,還可以通過(guò)直接聲明或者字面量初始化結(jié)構(gòu)體:
var b Book
fmt.Println(b) // 輸出:{ 0}
字面量初始化
b := Book{Title: "Go Programming", Author: "John Doe", Pages: 300}
nick := Person{"nick", 28, "nickli@xxx.com"}
fmt.Println(b) // 輸出:{Go Programming John Doe 300}
1.3.3 與 new 的對(duì)比
方法 | 分配內(nèi)存 | 返回值 | 是否為指針 |
---|---|---|---|
new | 是 | 返回指針伴箩,類(lèi)型為 *T | 是 |
直接聲明 | 是 | 返回值入愧,類(lèi)型為 T | 否 |
字面量初始化 | 是 | 返回值,類(lèi)型為 T 或 *T(取決于語(yǔ)法) | 可選 |
1.3.4 new 和 & 操作符的區(qū)別
通過(guò) &
也可以創(chuàng)建結(jié)構(gòu)體指針,它們的行為與 new
類(lèi)似砂客,但更靈活泥张。
b := &Book{Title: "Go Programming", Author: "John Doe", Pages: 300}
fmt.Println(b) // 輸出:&{Go Programming John Doe 300}
區(qū)別:
-
new
只能分配內(nèi)存并返回零值的結(jié)構(gòu)體指針。 -
&
可以用于字面量初始化鞠值,并返回指針媚创,語(yǔ)法更簡(jiǎn)潔
1.3.5 結(jié)構(gòu)體指針
可以定義指向結(jié)構(gòu)體的指針類(lèi)似于其他指針變量,格式如下:var struct_pointer *Books
定義的指針變量可以存儲(chǔ)結(jié)構(gòu)體變量的地址彤恶。查看結(jié)構(gòu)體變量地址钞钙,可以將 &
符號(hào)放置于結(jié)構(gòu)體變量前:struct_pointer = &Book1
使用結(jié)構(gòu)體指針訪問(wèn)結(jié)構(gòu)體成員,使用 .
操作符:struct_pointer.title
1.4 結(jié)構(gòu)體標(biāo)簽
在 Go 中声离,結(jié)構(gòu)體字段后面的反引號(hào)(`)部分是 結(jié)構(gòu)體標(biāo)簽(Tag)芒炼,用于為字段添加額外的元信息。這些標(biāo)簽可以在運(yùn)行時(shí)通過(guò)反射(reflect 包)讀取术徊,以實(shí)現(xiàn)特定功能本刽。
1.4.1 標(biāo)簽的語(yǔ)法
標(biāo)簽是一個(gè)字符串
,必須用反引號(hào)包裹
赠涮,一般格式是:key:"value"
子寓,多個(gè)標(biāo)簽可以用空格
分隔:
type Person struct {
Name string `json:"name" xml:"name"`
Age int `json:"age" db:"age_column"`
}
1.4.2 標(biāo)簽的工作機(jī)制
讀取標(biāo)簽: 使用 Go 的 reflect
包可以讀取結(jié)構(gòu)體的標(biāo)簽。
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string `json:"name" xml:"name_xml"`
Age int `json:"age"`
}
func main() {
t := reflect.TypeOf(Person{})
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("Field: %s, JSON Tag: %s, XML Tag: %s\n",
field.Name,
field.Tag.Get("json"),
field.Tag.Get("xml"),
)
}
}
輸出:
Field: Name, JSON Tag: name, XML Tag: name_xml
Field: Age, JSON Tag: age, XML Tag:
1.4.3 常見(jiàn)用途
1.4.3.1 JSON 序列化和反序列化
標(biāo)簽 json:"name"
用于指定字段在 JSON
格式中的鍵名笋除。常見(jiàn)于 encoding/json
包:
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
p := Person{Name: "Alice", Age: 30}
// 序列化為 JSON
jsonData, _ := json.Marshal(p)
fmt.Println(string(jsonData)) // 輸出:{"name":"Alice","age":30}
// 反序列化 JSON
var p2 Person
json.Unmarshal([]byte(`{"name":"Bob","age":25}`), &p2)
fmt.Println(p2) // 輸出:{Bob 25}
}
1.4.3.2 數(shù)據(jù)庫(kù)映射
標(biāo)簽用于指定字段與數(shù)據(jù)庫(kù)表中列名的映射斜友,常見(jiàn)于 ORM
框架(如 GORM
):
type User struct {
ID int `gorm:"primaryKey"`
Name string `gorm:"column:user_name"`
}
1.4.3.3 表單處理
用于指定字段的表單名稱(chēng),常見(jiàn)于 net/http
包的表單解析:
type LoginForm struct {
Username string `form:"username"`
Password string `form:"password"`
}
1.5 結(jié)構(gòu)體作為函數(shù)參數(shù)
可以像其他數(shù)據(jù)類(lèi)型一樣將結(jié)構(gòu)體類(lèi)型作為參數(shù)傳遞給函數(shù)垃它。
注意
:結(jié)構(gòu)體是作為參數(shù)的值傳遞
package main
import "fmt"
type Books struct {
title string
author string
subject string
book_id int
}
func changeBook(book Books) {
book.title = "book1_change"
}
func main() {
var book1 Books
book1.title = "book1"
book1.author = "zuozhe"
book1.book_id = 1
changeBook(book1)
fmt.Println(book1)
}
如果想在函數(shù)里面改變結(jié)果體數(shù)據(jù)內(nèi)容鲜屏,需要傳入指針
package main
import "fmt"
type Books struct {
title string
author string
subject string
book_id int
}
func changeBook(book *Books) {
book.title = "book1_change"
}
func main() {
var book1 Books
book1.title = "book1"
book1.author = "zuozhe"
book1.book_id = 1
changeBook(&book1)
fmt.Println(book1)
}
1.6 嵌套結(jié)構(gòu)體
在結(jié)構(gòu)體中嵌套結(jié)構(gòu)體時(shí)使用匿名字段
可以更加簡(jiǎn)便獲取內(nèi)層結(jié)構(gòu)體的字段,當(dāng)內(nèi)層結(jié)構(gòu)體的字段和外層結(jié)構(gòu)體的字段沒(méi)有重復(fù)時(shí)可以直接獲取
国拇,如果有重復(fù)時(shí)需要加上內(nèi)層結(jié)構(gòu)體名
才能正常獲取
這其實(shí)也是 Go 實(shí)現(xiàn)繼承
的方式
如果結(jié)構(gòu)體中的字段也是一個(gè)結(jié)構(gòu)體
洛史,那么這個(gè)結(jié)構(gòu)體字段稱(chēng)為提升字段(Promoted fields)
,因?yàn)榭梢詮?code>外部結(jié)構(gòu)體變量直接訪問(wèn)結(jié)構(gòu)體類(lèi)型中的字段贝奇,就像這些字段原本屬于外部結(jié)構(gòu)體一樣虹菲。
package main
import "fmt"
type foo struct {
field1 int
field2 string
}
type bar struct {
foo
field1 string
field3 int
}
func main() {
foobar := bar{ }
foobar.foo.field1 = 1
foobar.field2 = "hello"
foobar.field1 = "world"
foobar.field3 = 2
fmt.Printf("%v", foobar)
}
1.7 匿名
1.7.1 匿名結(jié)構(gòu)體
可以定義一個(gè)沒(méi)有類(lèi)型名稱(chēng)的結(jié)構(gòu)體,這種結(jié)構(gòu)體叫做匿名結(jié)構(gòu)體(Anonymous Structures
)
var employee struct {
firstName, lastName string
age int
}
給匿名struct類(lèi)型定義方法
var cache = struct {
sync.Mutex
mapping map[string]string
}{
mapping : make(map[string]string),
}
func Lookup(key string) string {
cache.Lock() // sync.Mxter的方法
v := cache.mapping[key]
cache.Unlock()
return v
}
聲明的同時(shí)進(jìn)行賦值
emp3 := struct {
firstName, lastName string
age, salary int
}{
firstName: "Andreah",
lastName: "Nikola",
age: 31,
salary: 5000,
}
1.7.2 匿名字段
在創(chuàng)建結(jié)構(gòu)體時(shí)掉瞳,我們也可以只指定類(lèi)型而不指定字段名
毕源。這些字段被稱(chēng)為匿名字段
。
下面的代碼片就創(chuàng)建了一個(gè) Person陕习,該結(jié)構(gòu)體有倆個(gè)匿名字段 string 和 int霎褐。
盡管匿名字段并沒(méi)有名稱(chēng),默認(rèn)情況下该镣,它的數(shù)據(jù)類(lèi)型
就是它的字段名稱(chēng)
冻璃。因此,Person 結(jié)構(gòu)體擁有倆個(gè)字段,其名稱(chēng)分別為:string 和 int
數(shù)據(jù)類(lèi)型定義省艳,僅能存在一次娘纷,如果有兩個(gè)string 或 兩個(gè)int 的話會(huì)沖突
type Person struct {
string
int
}
func main() {
p := Person{"Naveen", 50}
fmt.Println(p)
}
1.8 構(gòu)造函數(shù)
Go沒(méi)有提供構(gòu)造函數(shù)。如果一個(gè)類(lèi)型的零值是不合法的跋炕,應(yīng)該將該類(lèi)型設(shè)置為不導(dǎo)出赖晶,防止在其他包中導(dǎo)出該類(lèi)型,并且需要提供一個(gè)函數(shù)NewT(parameters)
去初始化帶參數(shù)的T類(lèi)型的的變量辐烂。Go中的一個(gè)約定是遏插,應(yīng)該把創(chuàng)建 T
類(lèi)型變量的函數(shù)命名為 NewT(parameters)
。這就類(lèi)似于構(gòu)造器了纠修。如果一個(gè)包只含有一種類(lèi)型胳嘲,按照 Go
的約定,應(yīng)該把函數(shù)命名為 New(parameters)
扣草, 而不是 NewT(parameters)
了牛。
package employee
import (
"fmt"
)
type employee struct {
firstName string
lastName string
totalLeaves int
leavesTaken int
}
func New(firstName string, lastName string, totalLeave int, leavesTaken int) employee {
e := employee {
firstName, lastName, totalLeave, leavesTaken}
return e
}
func (e employee) LeavesRemaining() {
fmt.Printf("%s %s has %d leaves remaining", e.firstName, e.lastName, (e.totalLeaves - e.leavesTaken))
}
2 接口
2.1 簡(jiǎn)介
接口(interface
)是 Go 語(yǔ)言中的一種類(lèi)型,用于定義行為的集合德召,它通過(guò)描述類(lèi)型必須實(shí)現(xiàn)的方法白魂,規(guī)定了類(lèi)型的行為契約。
接口把所有的具有共性的方法定義在一起上岗,任何其他類(lèi)型只要實(shí)現(xiàn)了這些方法就是實(shí)現(xiàn)了這個(gè)接口。
2.1.1 接口的特點(diǎn)
隱式實(shí)現(xiàn):
- Go 中沒(méi)有關(guān)鍵字顯式聲明某個(gè)類(lèi)型實(shí)現(xiàn)了某個(gè)接口蕴坪。
- 只要一個(gè)類(lèi)型實(shí)現(xiàn)了接口要求的所有方法肴掷,該類(lèi)型就自動(dòng)被認(rèn)為實(shí)現(xiàn)了該接口。
接口類(lèi)型變量:
- 接口變量可以存儲(chǔ)實(shí)現(xiàn)該接口的任意值背传。
- 接口變量實(shí)際上包含了兩個(gè)部分:
- 動(dòng)態(tài)類(lèi)型:存儲(chǔ)實(shí)際的值類(lèi)型呆瞻。
- 動(dòng)態(tài)值:存儲(chǔ)具體的值。
- 零值接口:接口的零值是
nil
一個(gè)未初始化的接口變量其值為nil
径玖,且不包含任何動(dòng)態(tài)類(lèi)型或值痴脾。 - 空接口:定義為
interface{}
,可以表示任何類(lèi)型
2.1.2 常見(jiàn)用法
- 多態(tài):不同類(lèi)型實(shí)現(xiàn)同一接口梳星,實(shí)現(xiàn)多態(tài)行為赞赖。
- 解耦:通過(guò)接口定義依賴(lài)關(guān)系,降低模塊之間的耦合冤灾。
- 泛化:使用空接口
interface{}
表示任意類(lèi)型前域。
2.2 接口操作
2.2.1 接口定義
接口定義使用關(guān)鍵字 interface
,其中包含方法聲明韵吨。
定義接口
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
定義結(jié)構(gòu)體
type struct_name struct {
/* variables */
}
實(shí)現(xiàn)接口方法
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法實(shí)現(xiàn) */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法實(shí)現(xiàn)*/
}
示例
package main
import (
"fmt"
"math"
)
// 定義接口
type Shape interface {
Area() float64
Perimeter() float64
}
// 定義一個(gè)結(jié)構(gòu)體
type Circle struct {
Radius float64
}
// Circle 實(shí)現(xiàn) Shape 接口
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
func main() {
c := Circle{Radius: 5}
var s Shape = c // 接口變量可以存儲(chǔ)實(shí)現(xiàn)了接口的類(lèi)型
fmt.Println("Area:", s.Area())
fmt.Println("Perimeter:", s.Perimeter())
}
2.2.2 空接口
空接口 interface{}
是 Go 的特殊接口
匿垄,表示所有類(lèi)型
的超集
,任意類(lèi)型都實(shí)現(xiàn)了空接口。
常用于需要存儲(chǔ)任意類(lèi)型數(shù)據(jù)的場(chǎng)景椿疗,如泛型容器漏峰、通用參數(shù)等。
當(dāng)接口變量的動(dòng)態(tài)類(lèi)型和動(dòng)態(tài)值都為 nil 時(shí)届榄,接口變量為 nil
接口變量實(shí)際上包含了兩部分:
-
動(dòng)態(tài)類(lèi)型
:接口變量存儲(chǔ)的具體類(lèi)型 -
動(dòng)態(tài)值
:具體類(lèi)型的值
2.2.3 類(lèi)型斷言
類(lèi)型斷言用于從接口類(lèi)型中提取其底層值芽狗。
基本語(yǔ)法:value := iface.(Type)
-
iface
:是接口變量。 -
Type
:是要斷言的具體類(lèi)型痒蓬。
如果類(lèi)型不匹配童擎,會(huì)觸發(fā) panic。
為了避免 panic攻晒,可以使用帶檢查的類(lèi)型斷言:value, ok := iface.(Type)
ok 是一個(gè)布爾值顾复,表示斷言是否成功。如果斷言失敗鲁捏,value 為零值芯砸,ok 為 false。
func main() {
var a interface{ }
var b int
a = b
// 斷言賦值
c := a.(int)
fmt.Printf("類(lèi)型:%T\n",c)
}
2.2.4 類(lèi)型選擇(type switch)
type switch
是 Go 中的語(yǔ)法結(jié)構(gòu)
给梅,用于根據(jù)接口變量的具體類(lèi)型執(zhí)行不同的邏輯假丧。
package main
import "fmt"
func printType(val interface{}) {
switch v := val.(type) {
case int:
fmt.Println("Integer:", v)
case string:
fmt.Println("String:", v)
case float64:
fmt.Println("Float:", v)
default:
fmt.Println("Unknown type")
}
}
func main() {
printType(42)
printType("hello")
printType(3.14)
printType([]int{1, 2, 3})
}
2.3 接口與結(jié)構(gòu)體
接口定義行為:
- 接口定義了一組方法。
- 一個(gè)結(jié)構(gòu)體(或其他類(lèi)型)只要實(shí)現(xiàn)了接口中定義的所有方法动羽,就自動(dòng)滿(mǎn)足接口(稱(chēng)為
接口的實(shí)現(xiàn)
)包帚。 - 不需要顯式地聲明
繼承
或實(shí)現(xiàn)
關(guān)系。
Go 的接口機(jī)制特性
- 隱式實(shí)現(xiàn):
不需要顯式聲明結(jié)構(gòu)體實(shí)現(xiàn)了某個(gè)接口运吓,只要滿(mǎn)足方法簽名
即可渴邦。
這種設(shè)計(jì)避免了類(lèi)和接口之間的緊耦合。 - 多態(tài):
通過(guò)接口實(shí)現(xiàn)動(dòng)態(tài)行為拘哨。
結(jié)構(gòu)體賦值給接口變量后谋梭,接口變量可以動(dòng)態(tài)調(diào)用結(jié)構(gòu)體實(shí)現(xiàn)的方法。 - 接口變量:
接口變量實(shí)際上由兩部分組成:
動(dòng)態(tài)類(lèi)型
:接口實(shí)際存儲(chǔ)的值的類(lèi)型倦青。
動(dòng)態(tài)值
:實(shí)際存儲(chǔ)的值瓮床。
var a Animal
a = Dog{}
fmt.Printf("動(dòng)態(tài)類(lèi)型:%T, 動(dòng)態(tài)值:%v\n", a, a) // 動(dòng)態(tài)類(lèi)型:main.Dog, 動(dòng)態(tài)值:{}
2.4 示例
2.4.1 接口傳參
package main
import "fmt"
type Phone interface {
call(param int) string
takephoto()
}
type Huawei struct {
}
func (huawei Huawei) call(param int) string{
fmt.Println("i am Huawei, i can call you!", param)
return "damon"
}
func (huawei Huawei) takephoto() {
fmt.Println("i can take a photo for you")
}
func main(){
var phone Phone
phone = new(Huawei)
phone.takephoto()
r := phone.call(50)
fmt.Println(r)
}
2.4.2 修改屬性
若想要通過(guò)接口方法修改屬性,需要在傳入指針的結(jié)構(gòu)體才行
type fruit interface{
getName() string
setName(name string)
}
type apple struct{
name string
}
func (a *apple) getName() string{
return a.name
}
func (a *apple) setName(name string) {
a.name = name
}
func testInterface(){
a:=apple{"紅富士"}
fmt.Print(a.getName())
a.setName("樹(shù)頂紅")
fmt.Print(a.getName())
}
2.4.3 多態(tài)示例
為不同數(shù)據(jù)類(lèi)型的實(shí)體提供統(tǒng)一的接口
package main
import "fmt"
// 學(xué)生
type Student struct {
Name string
Age int
Score float32
}
// 教師
type Teacher struct {
Name string
Age int
Class int
}
// 接口定義
type Test interface {
Print()
Sleep()
}
// 學(xué)生實(shí)現(xiàn)接口
func (p Student) Print() {
fmt.Println("name:",p.Name)
fmt.Println("age:",p.Age)
fmt.Println("score:",p.Score)
}
func (p Student) Sleep() {
fmt.Println("學(xué)生在睡覺(jué)")
}
// 教師實(shí)現(xiàn)接口
func (t Teacher) Print() {
fmt.Println("name:",t.Name)
fmt.Println("age:",t.Age)
fmt.Println("class:",t.Class)
}
func (t Teacher) Sleep() {
fmt.Println("教師在休息")
}
func main() {
var t Test
var stu Student = Student {
Name: "zhangsan",
Age: 18,
Score: 90,
}
var tea Teacher = Teacher {
Name: "lisi",
Age: 50,
Class: 01,
}
// 多態(tài)表現(xiàn)
t = stu
t.Print()
t.Sleep()
fmt.Println("---------------------------")
t = tea
t.Print()
t.Sleep()
}
name: zhangsan
age: 18
score: 90
學(xué)生在睡覺(jué)
---------------------------
name: lisi
age: 50
class: 1
教師在休息