您好,登錄后才能下訂單哦!
Go中的結構體(就相當于其它語言里的class):
struct 聲明:
type (標識符) struct {
field1 type
field2 type
}
例子:
type Student struct {
Name string
Age int
Score int
}
結構體中字段的訪問,和其他語言一樣,使用點:
package main
import "fmt"
type Student struct {
Name string
Age int
Score int
}
func main() {
var stu Student
stu.Name = "Adam"
stu.Age = 18
stu.Score = 90
fmt.Println(stu)
fmt.Println(stu.Name)
fmt.Println(stu.Age)
fmt.Println(stu.Score)
}
struct 定義的3種形式:
var stu Student
var stu *Student = new (Student)
var stu *Student = &Student{}
后兩種返回的都是指向結構體的指針,所以需要再跟個等號分配內存空間。并且,有些場景應該是需要用指針的結構體會更加方便。
強調一下, struct 是值類型。這里要用new來創建值類型。不是make,make是用來創建 map 、slice、channel 的。
結構體的訪問形式如下:
stu.Name
(*stu).Name
用上面兩種形式訪問都是可以的,但是定義的時候返回指針的話,標準做法還是應該用指針來訪問的,不過Go做了處理,可以簡化,直接用第一種就夠了,但是要知道調用的本質。
結構體是值類型,里面所有的字段在內存里是連續的:
package main
import "fmt"
type Student struct {
Name string
Age int
Score int
}
func main() {
var stu Student
stu.Name = "Adam"
stu.Age = 18
stu.Score = 90
fmt.Println(stu)
fmt.Printf("%p\n", &stu)
fmt.Printf("%p\n", &stu.Name)
fmt.Printf("%p\n", &stu.Age)
fmt.Printf("%p\n", &stu.Score)
}
/* 執行結果
PS H:\Go\src\go_dev\day5\struct\attribute> go run main.go
{Adam 18 90}
0xc04204a3a0
0xc04204a3a0
0xc04204a3b0
0xc04204a3b8
PS H:\Go\src\go_dev\day5\struct\attribute>
*/
package main
import "fmt"
type Student struct {
Name string
Age int
Score int
}
func main(){
var stu1 Student
stu1.Name = "Adam"
stu1.Age = 16
stu1.Score = 90
var stu2 Student = Student{
Name: "Bob",
Age: 15,
Score: 85,
}
var stu3 *Student = &Student{
Name: "Cara",
Age: 18,
Score: 80,
}
fmt.Println(stu1)
fmt.Println(stu2)
fmt.Println(&stu2)
fmt.Println(stu3)
fmt.Println(*stu3)
}
/* 執行結果
PS H:\Go\src\go_dev\day5\struct\init> go run .\main.go
{Adam 16 90}
{Bob 15 85}
&{Bob 15 85}
&{Cara 18 80}
{Cara 18 80}
PS H:\Go\src\go_dev\day5\struct\init>
*/
也可以在大括號里按位置傳參數進行初始化和定義:
type Student struct {
Name string
Age int
Score int
}
var s1 Student
s1 = Student {"stu1", 18, 90}
var s2 Student = Student{"stu2", 20, 80}
用結構體定義數據類型
每個節點包含下一個節點的地址,這樣就把所有的節點串起來了。通常把鏈表中的第一個節點叫做鏈表頭。
type Link struct {
Name string
Next *Link
}
下面有頭插法和尾插法創建鏈表,還有遍歷鏈表的方法:
package main
import "fmt"
type Student struct {
Name string
next *Student
}
// 遍歷鏈表的方法
func trans(p *Student) {
for p != nil {
fmt.Println(*p)
p = p.next
}
}
// 頭插法,從左邊插入
// 每個新加入的元素都插入到頭部元素的后面,這樣的好處是頭部元素的地址不變
func CreateLinkListLeft(p *Student) {
var head = p
for i := 0; i < 10; i++ {
p := Student{
Name: fmt.Sprintf("stuL%d", i),
}
p.next = head.next
head.next = &p
}
}
// 尾插法,從右邊加入
func CreateLinkListRight(p *Student) {
var tail = p
for i := 0; i < 10; i++ {
p := Student{
Name: fmt.Sprintf("stuR%d", i),
}
tail.next = &p
tail = &p
}
}
func main() {
var headL Student
fmt.Println("頭插法")
CreateLinkListLeft(&headL) // 結構體是值類型,要改變里面的值,就是傳指針
trans(&headL)
var headR Student
fmt.Println("尾插法")
CreateLinkListRight(&headR)
trans(&headR)
}
還有雙鏈表,詳細就不展開了:
type Link struct {
Name string
Next *Link
Prev *Link
}
每個節點都有2個指針,分別用來指向左子樹和右子樹:
type binaryTree struct {
Name string
left *binaryTree
right *binaryTree
}
這里只給一個深度優先的遍歷方法:
// 遍歷二叉樹,深度優先
func trans(root *Student) {
if root == nil {
return
}
// 前序遍歷
fmt.Println(root)
trans(root.left)
trans(root.right)
}
最后3句的相對位置,主要是打印的方法的位置不同又有3種不同的叫法。上面這個是前序遍歷。如果打印放中間就是中序遍歷。如果打印放最后,就是后序遍歷。
廣度優先的遍歷方法,暫時能力還不夠。另外如果要驗證上面的遍歷方法,也只能用笨辦法來創建二叉樹。
看下結構體里的一些高級用法
可以給結構體取別名:
type Student struct {
Name string
}
type Stu Student // 取個別名
下面的代碼,給原生的int類型取了個別名,也是可以像int一樣使用的:
package main
import "fmt"
type integer int
func main() {
var i = integer = 100
fmt.Println(i)
}
但是定義了別名的類型和原來的類型被系統認為不是同一個類型,不能直接賦值。但是是可以強轉類型的:
type integer int
func main() {
var i integer = 100
var j int
// j = i // 不同的類型不能賦值
j = int(i) // 賦值需要強轉類型
}
上面都是用原生的 int 類型演示的,自定義的結構體也是一樣的。
golang 中的 struct 不像其他語言里的 class 有構造函數。struct 沒有構造函數,一般可以使用工廠模式來解決這個問題:
// go_dev/day5/struct/new/model/model.go
package model
// 名稱是小寫,就是不讓你訪問的
type student struct {
Name string
Age int
}
// 外部要調用的是這個工廠函數,返回上面的經過構造函數處理的完成了初始化的結構體,即實例
func NewStudent(name string, age int) *student {
// 這里可以補充其他構造函數里的代碼
return &student{
Name: name,
Age: age,
}
}
// go_dev/day5/struct/new/main/main.go
package main
import (
"../model"
"fmt"
)
func main() {
s := model.NewStudent("Adam", 20)
fmt.Println(*s)
}
可以為 struct 中的每個字段,寫上一個tag。這個 tag 可以通過反射的機制獲取到。
為字段加說明
type student struct {
Name string "This is name field"
Age int "This is age field"
}
json序列化
最常用的場景就是 json 序列化和反序列化。先看一下序列化的用法:
package main
import (
"fmt"
"encoding/json"
)
type Stu1 struct{
name string
age int
score int
}
type Stu2 struct {
Name string
Age int
score int // 這個還是小寫,所以還是會有問題
}
func main() {
var s1 Stu1 = Stu1 {"Adam", 16, 80}
var s2 Stu2 = Stu2 {"Bob", 17, 90}
var data []byte
var err error
data, err = json.Marshal(s1)
if err != nil {
fmt.Println("JSON err:", err)
} else {
fmt.Println(string(data)) // 類型是 []byte 轉成 string 輸出
}
data, err = json.Marshal(s2)
if err != nil {
fmt.Println("JSON err:", err)
} else {
fmt.Println(string(data))
}
}
/* 執行結果
PS H:\Go\src\go_dev\day5\struct\json> go run main.go
{}
{"Name":"Bob","Age":17}
PS H:\Go\src\go_dev\day5\struct\json>
*/
結構體中,小寫的字段外部是訪問不了的,所以第一個輸出是空的。而第二個結構體中只有首字母大寫的字段才做了序列化。
所以一般結構體里的字段名都是首字母大寫的,這樣外部才能訪問到。不過這樣的話,序列化之后的變量名也是首字母大寫的。而json是可以實現跨語言傳遞數據的,但是在其他語言里,都是習慣變量小寫的。這樣go里json序列化出來的數據在別的語言里看就很奇怪。
在go的json包里,通過tag幫我們做了優化。會去讀取字段的tag,去里面找到json這個key,把對應的值,作為字段的別名。具體做法如下:
package main
import (
"fmt"
"encoding/json"
)
type Student struct{
Name string `json:"name"`
Age int `json:"age"`
Score int `json:"score"`
}
func main() {
var stu Student = Student{"Cara", 16, 95}
data, err := json.Marshal(stu)
if err != nil {
fmt.Println("JSON err:", err)
return
}
fmt.Println(string(data)) // 類型是 []byte 轉成 string 輸出
}
/* 執行結果
PS H:\Go\src\go_dev\day5\struct\json_tag> go run main.go
{"name":"Cara","age":16,"score":95}
PS H:\Go\src\go_dev\day5\struct\json_tag>
*/
反引號,作用和雙引號一樣,不過內部不做轉義。
結構體力的字段可以沒有名字,即匿名字段。
type Car struct {
Name string
Age int
}
type Train struct {
Car // 這個Car也是類型,上面定義的。這里沒有名字
Start time.TIme
int // 這個字段也沒有名字,即匿名字段
}
訪問匿名字段
可以直接通過匿名字段的類型來訪問,所以匿名字段的類型不能重復:
var t Train
t.Car.Name = "beemer"
t.Car.Age = 3
t.int = 100
對于結構體類型,還可以在簡化,結構體的名字可以不寫,下面的賦值和上面的效果一樣:
var t Train
t.Name = "beemer"
t.Age = 3
匿名字段沖突處理
type Car struct {
Name string
Age int
}
type Train struct {
Car
Start time.TIme
Age int // 這個字段也叫 Age
}
var t Train
t.Age // 這個Age是Train里的Age
t.Car.Age // Car里的Age現在只能把類型名加上了
通過匿名字段實現繼承
匿名字段在需要有繼承的的場景下很好用:
type Animal struct {
Color string
Age int
Weight int
Type string
}
type Dog Struct {
Animal
Name string
Weight float32
}
定義了一個 Animal 動物類,里面有很多屬性。再定義一個 Dog 狗的類,也屬于動物,需要繼承動物的屬性。這里用匿名字段就方便的繼承過來了。并且有些字段還可以再重新定義覆蓋原先的,比如例子里的 Weight 。這樣 Dog 就有 Animal 的所有的字段,并且 Dog 還能添加自己的字段,也可以利用沖突覆蓋父類里的字段。
Golang 中的方法是作用在特定類型的變量上的。因此自定義類型也可以有方法,而不僅僅是 struct 。
func (變量名 方法所屬的類型) 方法名 (參數列表) (返回值列表) {}
方法和函數的區別就是在func關鍵字后面多了 (變量名 方法所屬的類型) 。這個也別稱為方法的接收器(receiver)。這個是聲明這個方法是屬于哪個類型,這里的類型也包括 struct。
Golang里的接收器沒有 this 或者 self 這樣的特殊關鍵字,所以名字可以任意取的。一般而言,出于對一致性和簡短的需要,我們使用類型的首字母。類比 self ,也就知道這個接收器的變量在方法定義的代碼塊里就是代指當前類型的實例。
package main
import "fmt"
type Student struct {
Name string
Age int
}
func (s *Student) growup () {
s.Age++
}
func (s *Student) rename (newName string) {
s.Name = newName
}
func main() {
var stu Student = Student{"Adam", 17}
fmt.Println(stu)
stu.growup()
fmt.Println(stu)
stu.rename("Bob")
fmt.Println(stu)
}
/* 執行結果
PS H:\Go\src\go_dev\day5\method\beginning> go run main.go
{Adam 17}
{Adam 18}
{Bob 18}
PS H:\Go\src\go_dev\day5\method\beginning>
*/
上面的2個方法里的接收器類型加了星號。如果不用指針的話,傳入的是對象的副本,方法改變是副本的值,不會改變原來的對象。
另外上面調用方法的用法也已經簡寫了,實際是通過結構體的地址調用的 (&stu).growup()
。
匿名字段就是繼承的用法。不但可以繼承字段,方法也是繼承的:
package main
import "fmt"
type Animal struct {
Type string
}
func (a Animal) hello() {
fmt.Println(a.Type, "Woo~~")
}
type Dog struct {
Animal
Name string
}
func main() {
var a1 Animal = Animal{"Tiger"}
var d1 Dog
d1.Type = "Labrador"
d1.Name = "Seven"
a1.hello()
d1.hello() // Dog 也能調用 Animal 的方法
}
多繼承
一個 struct 里用了多個匿名結構體,那么這個結構體就可以直接訪問多個匿名結構體的方法,從而實現了多繼承。
如果一個 struct 嵌套了另一個匿名 struct,這個結果可以直接訪問匿名結構的方法,從而實現了繼承。
如果一個 struct 嵌套了另一個有名 struct,這個模式就叫組合:
package main
import "fmt"
type School struct {
Name string
City string
}
type Class struct {
s School
Name string
}
func main() {
var s1 School = School{"SHHS", "DC"}
var c1 Class
c1.s = s1
c1.Name = "Class One"
fmt.Println(c1)
fmt.Println(c1.s.Name)
}
繼承與組合的區別:
如果一個變量實現了 String() 方法,那么 fmt.Println 默認會調用這個變量的 String() 進行輸出。
package main
import "fmt"
type Animal struct {
Type string
Weight int
}
type Dog struct {
Animal
Name string
}
func (d Dog) String () string{
return d.Name + ": " + d.Type
}
func main(){
var a1 Animal = Animal{"Tiger", 230}
var d1 Dog = Dog{Animal{"Labrador", 100}, "Seven"}
fmt.Println(a1)
fmt.Println(d1)
}
/* 執行結果
PS H:\Go\src\go_dev\day5\method\string_method> go run main.go
{Tiger 230}
Seven: Labrador
PS H:\Go\src\go_dev\day5\method\string_method>
*/
注意傳值還是傳指針,例子里定義的時候沒有星號,是傳值的,打印的時候也是傳值就有想過。打印的使用用 fmt.Println(&d1)
也是一樣的。但是如果定義的時候用了星號,就是傳指針,打印的時候就必須加上&把地址傳進去才有效果。否則就是按照原生的方法打印出來。
這是go語言多態的實現方式
Interface 類型可以定義一組方法,但是這些不需要實現,并且 interface 不能包含任何變量。
這里講接口只是起個頭,下一篇繼續講接口
定義接口使用 interface 關鍵字。然后只需要再里面定義一個或者多個方法就好,不需要實現:
type 接口名 interface {
方法名1(參數列表)
方法名2(參數列表) [返回值]
方法名3(參數列表) [返回值]
}
interface 類型默認是一個指針,默認值是空 nil :
type example interface{
Method()
}
var a example // 這里定義了一個接口,a就是一個指針
// 目前a沒有賦值,a里沒有任何實現,a是一個空指針
a.Method() // a還是一個空指針,里面沒有任何實現,這句會報錯
上面定義了a之后,還缺少一步,給指針a指向一個具體的實現。
Golang 中的接口,不需要顯式的實現,只要一個變量,含有接口類型中的所有方法,那么這個變量就實現了這個接口。因此,golang 中沒有 implement 類型的關鍵字
如果一個變量含有了多個 interface 類型的方法,那么這個變量就實現了多個接口。
下面是一個接口實現的示例:
package main
import "fmt"
// 定義一個接口
type AnimalInterface interface {
Sleep() // 定義一個方法
GetAge() int // 再定義一個有返回值的方法
}
// 定義一個類
type Animal struct {
Type string
Age int
} // 接下來要實現接口里的方法
// 實現接口的一個方法
func (a Animal) Sleep() {
fmt.Printf("%s need sleep\n", a.Type)
}
// 實現了接口的另一個方法
func (a Animal) GetAge() int {
return a.Age
}
// 又定義了一個類,是上面的子類
type Pet struct {
Animal
Name string
}
// 重構了一個方法
func (p Pet) sleep() {
fmt.Printf("%s need sleed\n", p.Name)
} // 有繼承,所以Age方法會繼承父類的
func main() {
var a1 Animal = Animal{"Dog", 5} // 創建一個實例
var aif AnimalInterface // 創建一個接口
aif = a1 // 因為類里實現了接口的方法,所以可以賦值給接口
aif.Sleep() // 可以用接口調用
a1.Sleep() // 使用結構體調用也是一樣的效果,這就是多態
var p1 Pet = Pet{Animal{"Labrador", 4}, "Seven"}
aif = p1
aif.Sleep()
fmt.Println(aif.GetAge())
}
一種事務的多種形態,都可以按照統一的接口進行操作。
多態,簡單點說就是:"一個接口,多種實現"。比如 len(),你給len傳字符串就返回字符串的長度,傳切片就返回切片長度。
package main
import "fmt"
func main() {
var s1 string = "abcdefg"
fmt.Println(len(s1))
var l1 []int = []int{1, 2, 3, 4}
fmt.Println(len(l1))
}
參照上面的,自己寫的方法也可以接收接口作為參數,對不同的類型對應多種實現:
package main
import "fmt"
type Msg interface {
Print()
}
type Message struct {
msg string
}
func (m Message) Print() {
fmt.Println("Message:", m.msg)
}
type Information struct {
msg string
level int
}
func (i Information) Print() {
fmt.Println("Information:", i.level, i.msg)
}
func interfaceUse(m Msg) {
m.Print()
}
func main() {
message := Message{"Hello"} // 定義一個結構體
information := Information{"Hi", 2} // 定義另外一個類型的結構體
// 這里并不需要 var 接口,以及賦值
interfaceUse(message) // 參數不看類型了,而看你是否滿足接口
interfaceUse(information) // 雖然這里的參數和上面不是同一個類型,但是這里對參數的要求是接口
}
這里并不需要顯式的用 var 聲明接口以及賦值。
golang 中并沒有明確的面向對象的說法,可以將 struct 類比作其它語言中的 class。
constructor 構造函數
通過結構體的工廠模式返回實例來實現
Encapsulation 封裝
通過自動的大小寫控制可見
Inheritance 繼承
結構體嵌套匿名結構體
Composition 組合
結構體嵌套有名結構體
Polymorphism 多態
通過接口實現
實現一個圖書管理系統,具有以下功能:
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。