一、結構體(struct)
相當於Java中的類,結構體名稱首字母需大寫,結構體名稱首字母大寫是公有的,包外可訪問;首字母小寫是私有的,僅在包內可訪問
結構體命名需要使用駝峰命名法,且不能出現下划線
Go語言提供了一種自定義數據類型,可以封裝多個基本數據類型,這種數據類型叫結構體(struct),Go語言中通過結構體來實現面向對象
go語言中數組可以存儲同一類型的數據,但在結構體中我們可以為不同項定義不同的數據類型
結構體是由一系列具有相同類型或不同類型的數據構成的數據集合
1)結構體定義
type struct_name struct{ var_name1 type1 var_name2 type2 ... }
如,定義一個User結構體:
type User struct { name string gender string address string age int }
同樣類型的字段也可以寫在一行
type User struct { name, gender string address string age int }
注意:結構體定義時結構體字段的可見性規則
結構體中字段大寫開頭表示可公開訪問,小寫表示私有(僅在定義當前結構體的包中可訪問)
2)結構體實例化
只有當結構體實例化時,才會真正地分配內存。
也就是必須實例化后才能使用結構體的字段
結構體本身也是一種類型,我們可以像聲明內置類型一樣使用var關鍵字聲明結構體類型。
結構體聲明:
var 結構體實例 結構體類型
基本實例化,直接使用結構體變量:
// 聲明結構體類型 var user User fmt.Println(reflect.TypeOf(user)) // main.User fmt.Println(user) // { 0} // 為結構體的成員變量賦值 user.name = "yangyongjie" fmt.Println(user) // {yangyongjie 0} // 訪問結構體中的成員變量 fmt.Println(user.name) // yangyongjie
3)訪問結構體成員
結構體名.成員名
4)匿名結構體
var user struct{ Name string; Age int} 或 var user struct{ name string gender string }
5)結構體指針
結構體指針類似於其他指針變量
var struct_pointer *struct_name
以上定義的結構體指針可以存儲結構體變量的地址
struct_pointer = &struct_var_name // 取結構體變量的地址賦給結構體指針
6)創建指針類型結構體
使用new關鍵字對結構體進行實例化,得到的是結構體的內存地址
var user1 = new(User) fmt.Println(user1) // &{ 0} fmt.Println(reflect.TypeOf(user1)) // *main.User 返回的是指針類型 // Go語言中支持對結構體指針直接使用.來訪問結構體的成員,user1.name其實在底層是(*user1).name = "hello",這是Go語言幫我們實現的語法糖 user1.name = "hello" fmt.Println(user1) // &{hello 0}
7)取結構體的地址實例化
使用&對結構體進行取地址操作相當於對該結構體類型進行了一次new實例化操作
user2 := &User{} fmt.Println(reflect.TypeOf(user2)) // *main.User //user2.address 其實在底層是(*user2).name = "nanjing",這是Go語言幫我們實現的語法糖 user2.address = "nanjing" fmt.Println(user2) // &{ nanjing 0}
8)結構體初始化
①:直接使用結構體變量
初始化的是結構體類型,直接打印返回的是結構體的值
var user1 User
user1.name = "test"
user1.address = "beijing"
fmt.Println(reflect.TypeOf(user1)) // main.User
fmt.Println(user1) // {test 0 beijing}
②:使用new關鍵字
初始化的是指針類型,直接打印返回的是內存地址
user := new(User)
user.name = "yangyongjie"
user.age = 18
fmt.Println(reflect.TypeOf(user)) // *main.User
fmt.Println(user) // &{yangyongjie 18 }
③:使用鍵值對初始化
對結構體使用鍵值對初始化時,鍵對應結構體的字段,值對應該字段的初始值
初始化的是結構體類型,直接打印返回的是結構體的值
user3 := User{ name: "yangyongjie", age: 27, gender: "male", address: "nanjing", } fmt.Println(reflect.TypeOf(user3)) // main.User fmt.Println(user3) // {yangyongjie male nanjing 27}
④:也可以對結構體指針進行鍵值對初始化,
初始化的是指針類型,直接打印返回的是內存地址。如:
user4 := &User{ name: "yangyongjie", age: 27, gender: "male", address: "nanjing", } fmt.Println(reflect.TypeOf(user4)) // *main.User fmt.Println(user4) // &{yangyongjie male nanjing 27}
工作中一般使用 u:= &User{name: "yangyongjie",...} 這種方式實例化並初始化
9)構造函數
Go語言的結構體沒有構造函數,但是我們可以自己實現,如:
func newUser(name, gender, address string, age int) *User { return &User{ name: name, age: age, gender: gender, address: address, } }
調用構造函數:
user5 := newUser("yangyongjie", "male", "nanjing", 27) fmt.Println(user5) // &{yangyongjie male nanjing 27}
10)結構體作為函數參數
可以像其他數據類型一樣將結構體類型作為參數傳遞給函數。並以以上實例的方式訪問結構體變量
type Books struct { title string author string subject string book_id int } func printBook(book Books) { fmt.Printf( "Book title : %s\n", book.title) }
11)方法和接收者
Go 語言中同時有函數和方法。一個方法就是一個包含了接收者的函數。
方法可以將類型和方法封裝在一起,實現強耦合。
接收者可以是命名類型或者結構體類型的一個值或者是一個指針。所有給定類型的方法屬於該類型的方法集
Go語言中的方法時一種作用於特定類型變量的函數。這種特定類型變量叫做接收者,接收者的概念類似於Java語言中的this,和Python語言中的self。只不過Go語言中需要將this顯式的聲明出來。
接收者可以理解為當前的對象,即方法所在類型的對象,可以理解為結構體的方法,類似於Java中的this,顯式的聲明了出來
方法的定義格式如下:
func (接收者變量 接收者類型) 方法名(參數列表) (返回類型) {
方法體
}
示例:
// 定義結構體
type User struct {
name string
gender string
address string
age int
}
// 該方法屬於User對象中的方法
// 值類型的接收者
func (user User) getName() string {
// name屬性即user對象中的屬性
return user.name
}
// 指針類型的接收者
func (user *User) setName(name string) {
user.name = name
}
func main() {
user := &User{
name: "yangyongjie",
age: 27,
gender: "male",
address: "nanjing",
}
name := user.getName() // 該方法只能User結構體類型的變量或指針才能調用
fmt.Println(name) // yangyongjie
user1 := &User{}
user1.setName("yyj")
fmt.Println(user1.name) // yyj
}
值類型的接收者和值類型的接收者方法的區別:
指針類型的接收者由一個結構體的指針組成,由於指針的特性,調用方法時修改接收者指針的任意成員變量,在方法結束后,修改都是有效的。這種方式十分接近於Java語言中的this,和Python語言中的self
當方法作用於值類型的接收者時,Go語言會在代碼運行時將接收者的值復制一份。在值類型接收者的方法中可以獲取接收者的成員值,但是修改操作只是針對副本,無法修改接收者變量本身。
import "fmt" // 定義結構體 type User struct { name string gender string address string age int } // 值類型的接收者 func (user User) setAddress(address string) { user.address = address } // 指針類型的接收者 func (user *User) setName(name string) { user.name = name } func main() { user := &User{ name: "yangyongjie", age: 27, gender: "male", address: "nanjing", } // 接收值類型的方法,user變量本身值沒有被修改 user.setAddress("beijing") fmt.Println(user.address) // nanjing // 接收指針類型的方法,user變量本身值沒有被修改 user.setName("yyj") fmt.Println(user.name) // yyj }
什么時候應該使用指針類型接收者?
①:需要修改接收者中的值
②:接收者是拷貝代價比較大的大對象
③:保證一致性,如果有某個方法使用了指針接收者,那么其他的方法也應該使用指針接收者
結構體變量和結構體指針的理解:
結構體指針指向的是結構體變量的內存地址
結構體變量是結構體類型的變量本身的值
12)結構體的"繼承"
一個結構體中可以嵌套包含另一個結構體或結構體指針
通過嵌套匿名結構體(結構體指針)實現繼承
//Animal 動物 type Animal struct { name string } //Dog 狗 type Dog struct { Feet int8 *Animal //通過嵌套匿名結構體實現繼承 }
13)結構體與JSON序列化
user := &User{ Name: "yangyongjie", Age: 27, Gender: "male", Address: "nanjing", } // 結構體JSON序列化 data, err := json.Marshal(user) if err != nil { fmt.Println("json marshal failed") return } fmt.Printf("%s\n", data) // {"Name":"yangyongjie","Gender":"male","Address":"nanjing","Age":27} // JSON反序列化結構體 user1 := &User{} err = json.Unmarshal(data, user1) if err != nil { fmt.Println("json unmarshal failed!") return } fmt.Print(user1) // &{yangyongjie male nanjing 27}
二、接口
接口(interface,相當於Java中的接口,接口名稱首字母大寫)
go語言提供的一種數據類型,接口將所有的具有共性的方法定義在一起,任何其他類型只要實現了這些方法就是實現了這個接口。
在Go語言中接口(interface)是一種類型,一種抽象的類型。請牢記在Go語言中接口(interface)是一種類型
1)定義接口:
type interface_name interface{
method1() [return_type]
method2() [return_type]
method3() [return_type]
}
2)實現接口方法:
func (struct_name_variable struct_name) method1() [return_type] { // 方法實現 }
3)需要注意:
空接口可以作為任何類型數據的容器
一個類型可實現多個接口
接口命名習慣以 er 結尾
接口只有方法聲明,沒有實現,沒有數據字段
當方法名首字母是大寫且這個接口類型名首字母也是大寫時,這個方法可以被接口所在的包(package)之外的代碼訪問
接口也可實現類似OOP中的多態
只有當有兩個或兩個以上的具體類型必須以相同的方式進行處理時才需要定義接口。不要為了接口而寫接口,那樣只會增加不必要的抽象,導致不必要的運行時損耗
4)接口嵌套
接口與接口間可以通過嵌套創造出新的接口
需要注意的是,嵌入其他接口類型不能有同名方法,也不能嵌入自身或循環嵌入
// Sayer 接口 type Sayer interface { say() } // Mover 接口 type Mover interface { move() } // 接口嵌套 type animal interface { Sayer Mover }
5)空接口(interface{})
空接口是指沒有定義任何方法的接口。因此任何類型都實現了空接口,空接口可以存儲任意類型的值。
因為空接口可以存儲任意類型值的特點,所以空接口在Go語言中的使用十分廣泛
空接口應用:
①:空接口作為函數的參數
使用空接口實現可以接收任意類型的函數參數
// 空接口作為函數參數 func show(a interface{}) { ... }
②:空接口作為map的值
map使用空接口可以保存任意類型的值
// 空接口作為map值 var studentInfo = make(map[string]interface{}) studentInfo["name"] = "李白" studentInfo["age"] = 18 studentInfo["married"] = false fmt.Println(studentInfo)
類型斷言:
一個接口類型的值(簡稱接口值)是由一個具體類型和具體類型的值兩部分組成的。
這兩部分分別稱為接口的動態類型和動態值
想要判斷空接口中的值的動態類型是否滿足指定的類型可以使用類型斷言,語法格式為:
x.(T)
x:表示類型為interface{}的變量
T:表示斷言x可能是的類型。
該語法返回兩個參數,第一個參數是x轉化為T類型后的變量,第二個值是一個布爾值,若為true則表示斷言成功,為false則表示斷言失敗
func main() { var x interface{} x = "pprof.cn" v, ok := x.(string) if ok { fmt.Println(v) } else { fmt.Println("類型斷言失敗") } }
6)接口使用技巧
①:盡量定義包含方法少的接口,建議控制接口方法數量不超過 3 個
我們可以在一些 Golang 語言標准庫中發現,很多接口包含的方法數量都不超過 3 個,也有很多接口僅包含 1 個方法
控制接口包含方法的數量盡量少的好處是接口包含的方法越少,越容易實現和組合
②:盡量不使用空接口類型作為函數參數
Golang 語言是強類型靜態語言,Golang 編譯器在編譯期間會對變量做類型檢查。如果函數或方法接收的參數類型是空接口 interface{},編譯器將收不到任何信息,也就不會對空接口類型的變量進行類型檢查,接收參數的類型將需要開發者自己做類型檢查。所以開發者盡量不要使用空接口 interface{} 變量作為接收參數
但是空接口 interface{} 類型也並非完全無用武之地,因為目前 Golang 語言(v1.16.4)還未支持泛型,當需要處理未知類型的參數時,可以使用空接口 interface{} 類型,在 Golang 語言標准庫中也有該使用方式,比如 fmt 包
END.