1. 程式人生 > 實用技巧 >go語言學習——結構體,方法,介面

go語言學習——結構體,方法,介面

結構體

概念:是由一系列相同型別或不同型別的資料構成的資料集合。結構體成員是由一系列的成員變數構成。這些成員變數被稱為欄位。

結構體的定義:

type Person struct {
name string
age int
sex string
address string
}

初始化結構體的方法:

1.

var p1 Person
p1.name = "Lisa"
p1.age = 23
p1.sex = "女"
p1.address = "深圳"
2.
p2:= Person{}
p2.name = "Tom"
p2.age = 22
p2.sex = "男"
p2.address = "上海"
3.
p3:= Person{
name:"Daisy",
age:24,
sex:"女",
address:"北京"}

4.(3的省略寫法,要按定義的順序寫)

p4:= Person{"Daisy",24,"女","北京"}

go中的資料型別分類:

值型別:int,float,bool,string,array,struct

值型別在複製的時候傳遞的是值,即只把資料複製了一份

引用型別:slice,map,function,pointer

引用型別在複製的時候傳遞的是地址

用陣列和切片來直觀對比一下:

var nums1 = [5] int{1,2,3,4,5}
nums2 := nums1
fmt.Println(nums1)//[1 2 3 4 5]
fmt.Println(nums2)//[1 2 3 4 5]
nums2[0]=100
fmt.Println(nums1)//[1 2 3 4 5]
fmt.Println(nums2)//[100 2 3 4 5]

fmt.Println("----------------------------")

var nums3 = [] int{1,2,3,4,5}
nums4 := nums3
fmt.Println(nums3)//[1 2 3 4 5]
fmt.Println(nums4)//[1 2 3 4 5]
nums4[0]=100
fmt.Println(nums3)//[100 2 3 4 5]
fmt.Println(nums4)//[100 2 3 4 5]
我們可以看到,陣列的複製只會複製值,但是切片的複製複製的是地址。

結構體和陣列一樣,傳遞的是值

如何定義一個結構體的指標:

var pp1 *Person
pp1 = &p1
fmt.Println(pp1) //&{Lisa 23 女 深圳}
fmt.Println(*pp1) //{Lisa 23 女 深圳}
利用指標來改變結構體的值,達到淺拷貝:
pp1.name = "Mike"//這裡是省略寫法,完整寫法為(*pp1).name = "Mike"
fmt.Println(pp1)//&{Mike 23 女 深圳}
fmt.Println(*pp1)//{Mike 23 女 深圳}
還可以用new來建立指標
pp2 := new(Person)

這句話與

var pp1 *Person

是等價的。


匿名結構體的定義

s :=struct{
name string
age int
}{
name:"Lisa",
age:20,
}
fmt.Println(s)//{Lisa 20}

結構體的匿名欄位

定義方法:

type Worker struct{
string
int
}

初始化方法:

w :=Worker{"Tom",28}
fmt.Println(w)//{Lisa 20}
如果用匿名欄位,欄位的型別是不能重複的(好不方便啊)

結構體的巢狀

has a關係:

一個結構體的欄位可以是另一個結構體,如:

type Book struct{
name string
price int
}
type Student struct{
id int
book Book
age int
}

學生結構體中有一個欄位叫Book,它也是一個結構體

含巢狀的結構體的初始化:

b:=Book{}
b.name="go語言聖經"
b.price=50
fmt.Println(b)//{go語言聖經 50}

s:=Student{}
s.id=1001
s.book = b
s.age = 24
fmt.Println(s)//{1001 {go語言聖經 50} 24}

is a關係:(模擬繼承)

一個結構體作為另一個結構體的匿名欄位,如:

type Person struct{
name string
age int

}
type Student struct{
Person
school string
}

在這裡,Person結構體中的name和age欄位是Student結構體的提升欄位,Student結構體的物件可以直接訪問,不需要通過Person物件:

s:= Student{Person{"Lisa",24},"北京大學"}
fmt.Println(s.age)//24
如上,我們直接通過Student物件s訪問了age。

方法:一個帶有接受者的函式。

方法名是可以重複的,只要他們的接受者不同即可。

方法的定義:

type Person struct{
name string
age int
}

func (p Person) eat(){
fmt.Println(p.name,"吃飯")
}

方法的呼叫:

p1:=Person{"Lisa",20}
p1.eat()//Lisa 吃飯
方法的呼叫者可以是結構體的指標:
type Person struct{
name string
age int
}

func (p *Person) eat(){
fmt.Println(p.name,"吃飯")
}

呼叫:

p1:=&Person{"Lisa",20}
fmt.Println(p1)//&{Lisa 20}
p1.eat()//Lisa 吃飯

繼承中的方法

首先定義兩個結構體,Person是Student的子類,注意要使用匿名欄位

type Person struct{
name string
age int
}

type Student struct{
Person
school string
}

然後我們定義一個父類的方法:

func (p Person) eat(){
fmt.Println(p.name,"父類方法,吃飯")
}

我們建立一個父類的物件,呼叫剛剛的父類的方法,這個當然是沒有問題的:

p1:=Person{"Lisa",24}
p1.eat()//Lisa 父類方法,吃飯
然後我們試著建立一個子類的物件,它可以訪問自己的屬性,也可以直接訪問父類的屬性(因為父類的欄位就是子類物件的提升欄位):
s1:=Student{Person{"Tom",24},"北京大學"}
fmt.Println(s1.name,s1.age,s1.school)//Tom 24 北京大學
子類物件可以呼叫父類方法:
s1:=Student{Person{"Tom",24},"北京大學"}
s1.eat()//Tom 父類方法,吃飯
子類可以新增自己的方法:
func (s Student) study(){
fmt.Println(s.name,"子類新增方法,學習")
}

子類物件可以呼叫自己的方法:

s1:=Student{Person{"Tom",24},"北京大學"}
s1.study()//Tom 子類新增方法,學習
子類可以重寫父類方法:
func (s Student) eat(){
fmt.Println(s.name,"子類重寫父類方法,吃好吃的")
}

子類呼叫重寫後的方法:

s1:=Student{Person{"Tom",24},"北京大學"}
s1.eat()//Tom 子類重寫父類方法,吃好吃的

介面:

介面就是一組方法的簽名(這裡的方法只有宣告,沒有實現)

某個結構體實現了這個介面中的所有方法,那麼這個結構體就是這個介面的實現。

定義一個介面:

type USB interface {
start()
end()
}

我們寫一個結構體,如果它實現了這個介面中的所有方法,那麼它就算這個介面的實現:

type mouse struct {
mname string
}
func (m mouse) start(){
fmt.Println("滑鼠開始工作")
}
func (m mouse) end(){
fmt.Println("滑鼠結束工作")
}

實現了這個介面有什麼用呢?當其他函式中傳入這個介面型別作為引數時(返回值也一樣),我們就可以傳入這個結構體的物件,這樣具體實現的方法就根據實現類為準。

還有,如果一個型別被定義為介面型別,那麼實際上可以傳入它的實現類的物件。

m1:=mouse{"可愛滑鼠"}
work(m1)//滑鼠開始工作 滑鼠結束工作
如果一個介面沒有實現類,那麼也就無法傳入任何引數了。

空介面:

空介面的定義:

type A interface {

}

任何型別都實現了空介面。

我們可以定義兩個結構體來測試一下:

type Cat struct{
name string
age int
}

type Student struct{
sex string
school string
}

測試程式碼中,我們傳入了剛剛定義的兩個結構體,又用了字串型別來測試,發現他們都可以作為空介面的實現:

var a1 A = Cat{"小白",3}
fmt.Println(a1)//{小白 3}
var a2 A = Student{"女","北京大學"}
fmt.Println(a2)//{女 北京大學}
var a3 A = "我是一個字串"
fmt.Println(a3)//我是一個字串

介面的巢狀

介面是可以多繼承的,一個介面可以繼承多個介面,一個實現類也可以實現多個介面:

先寫AB兩個介面,C介面繼承了AB兩個介面,同時它還有一個自己的函式:

type A interface {
test1()
}

type B interface{
test2()
}

type C interface{
A
B
test3()
}

然後我們可以寫一個實現類,它實現了3個方法,也就是說它同時實現了ABC三個介面。

type Student struct{
name string
age int
}

func (s Student) test1() {
fmt.Println("實現test1方法")
}
func (s Student) test2() {
fmt.Println("實現test1方法")
}
func (s Student) test3() {
fmt.Println("實現test1方法")
}

也就是說,當出現這三個介面的型別時,均可由Student這個結構體的物件s來代替。但是需要注意的是,把它視作不同的型別,它能夠呼叫的方法是不一樣的,如:

var a A = Student{"Lisa",24}
a.test1()

這裡a只能呼叫test1。b跟a類似,只能呼叫test2。

var c C = Student{"Lisa",24}
c.test1()
c.test2()
c.test3()

這裡c能呼叫全部的3個方法。

var s Student = Student{"Lisa",24}
s.test1()
s.test2()
s.test3()

直接定義為Student結構體型別,它也可以呼叫3個方法。


介面斷言:

如果一個函式的形參是介面,那麼我們需要在函式體中對它進行斷言。

這樣呼叫這個函式時,我們就可以知道傳入的到底是哪個實現類。

首先我們定義一個介面:

type Work interface {
gowork()
offwork()
}

然後我們寫兩個實現類:

學生實現類:

type Student struct {
name string
age int
}

func (s Student) gowork(){
fmt.Println("學生上學")
}
func (s Student) offwork(){
fmt.Println("學生放學")
}

老師實現類:

type Teacher struct {
sex string
school string
}

func (t Teacher) gowork(){
fmt.Println("老師上班")
}
func (t Teacher) offwork(){
fmt.Println("老師下班")
}

寫一個包含斷言的測試方法:

func GetType(w Work){
instance,ok:=w.(Student)
if !ok{
fmt.Println(ok)
}
fmt.Println(instance)
}

我們在main函式中定義一個學生型別的物件,傳入測試方法進行測試:

s1:=Student{"Lisa",24}
GetType(s1)//{Lisa 24}
斷言成功,輸出了s1物件的資訊。