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 := nums1fmt.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物件的資訊。