Go語言之路————指針、結構體、方法
- 前言
- 指針
- 結構體
- 聲明
- 初始化
- 使用
- 組合引用
- 結構體和指針
- 結構體的標簽
- 方法
- 例子
- 結合結構體
- 總結
前言
- 我是一名多年Java開發人員,因為工作需要現在要學習go語言,Go語言之路是一個系列,記錄著我從0開始接觸Go,到后面能正常完成工作上的業務開發的過程,如果你也是個小白或者轉Go語言的,希望我這篇文章對你有所幫助。
- 有關go其他基礎的內容的文章大家可以查看我的主頁,接下來主要就是把這個系列更完,更完之后我會在每篇文章中掛上連接,方便大家跳轉和復習。
go中的指針,通常在結構體中用的特別多,而方法又是結構體一部分,所以我把這三個知識點放在一起來說,這樣大家可以連貫起來方便理解和吸收。
指針
指針你只需要記住兩個操作符,一個是取地址符&,另一個是解引用符 *。對一個變量進行取地址,會返回對應類型的指針,下面我簡單舉個例子:
我們先看取地址符號:&
package mainimport "fmt"func main() {a := 1fmt.Printf("%T\n", a)b := &afmt.Println(b)fmt.Printf("%T\n", b)
}console打印:
int
*int
0xc00008c0a8
我們對變量a用&符號取地址得到變量b,打印出來b的值就是a的地址,打印出b的類型,就是一個指針,這時候再回顧一下上面這句話:對一個變量進行取地址,會返回對應類型的指針。指針b存儲的是變量a的地址
我們再看看解引用符:*
解引用符第一個用處,就跟它的命名一樣,解除引用,就是解除指針的引用而獲得具體的值,下面我們看個例子:
import "fmt"func main() {a := 123b := &aresult := *bfmt.Println(result)fmt.Printf("%T", result)
}console打印:
123
int
通過這個代碼可以看到,我們通過&取得了指向a地址的指針b,但是通過解引符號*,用*b就可以解除指針引用直接獲得這個地址對應的值,也就是a的值,打印result的數據類型也是int類型。
解引用符第一個用處:聲明一個變量的類型為指針類型。
這里我們指定一個變量a它的類型為int類型的指針
var a *int
打印一下a看看呢
<nil>
因為我們沒有給a賦值或者初始化,所以打印出來的為nil,要么使用取地址符將其他變量的地址賦值給該指針,要不就使用內置函數:new
說到:new。go中的new和Java中的new有區別的是,go中的new是專門為指針服務的,它的用處就是新建或者說初始化一個指針
看看代碼
func main() {var a *intfmt.Println(a)a = new(int)fmt.Println(a)
}
我們用new去初始化了a,看看輸出呢:
<nil>
0xc00000a120
為啥輸出來是一個地址呢,因為該函數會為該指針分配內存,并且指針指向對應類型的零值
用上面的知識點,接引符*來驗證下是不是零值呢:
func main() {var a *intfmt.Println(a)a = new(int)fmt.Println(a)fmt.Println(*a)
}
console打印:
<nil>
0xc00000a120
0
這么一看還真是對的,我們點進new函數,看看源碼怎么寫的:
func new(Type) *Type
通過代碼分析,我們定義的a為int,這里new中傳入的是int,那么返回的就是int,正好和a類型一致,是不是就是初始化了啊,是不是很簡單啊。
而上面的示例代碼,我們一般使用短賦值,簡單一點:
func main() {a:=new(int)fmt.Println(a)
}
ps:在go中指針是不能運算的,而且這里我們還要區分一下new和make,前者是為指針服務器的,后者是為具體數據類型的值服務的,不要搞混了。
結構體
go中的結構體,你可以理解為Java中的實體類,但是他們又有細微的差別,但是不是很多,下面我就一一道來。
既然是結構體,那么定義它的關鍵詞就是:struct。我們先通過一個例子簡單看下。
定義一個UserInfo的結構體,里面分別有name、age、phone三個字段:
聲明
type UserInfo struct {name stringage intphone int
}
這是一個簡單的聲明,跟函數一樣,如果遇到相同的數據類型,也可以寫一起,所以上面的age和phone可以這樣寫:
type UserInfo struct {name stringage, phone int
}
初始化
注意,上面只是聲明,在Java中,是以new關鍵詞創建一個類,比如這里:new UserInfo(),但是在go中沒有那么復雜,直接調用傳參,看下面例子:
type UserInfo struct {name stringage, phone int
}func main() {//這里需要注意點,為了方便閱讀,或者靈活傳參,這里盡量用這種格式字段名稱:字段值,//也可以省略字段名稱,但是就要傳所有參數并且可讀性很差,不推薦。var user = UserInfo{name: "John",age: 42,phone: 1000,}fmt.Println(user)
}console打印:
{John 42 1000}
注意:這里的結構體命名和里面字段的命名,都遵循首字母大小寫的規則,可能有同學忘了,這里提一下,go中首字母大寫的方法就是public,小寫的就是private,切記。
使用
我們訪問和結構體和修改結構體中的值也很簡單,直接用.就行:
獲取值:
func main() {var user = UserInfo{name: "John",age: 42,phone: 1000,}fmt.Println(user.name)fmt.Println(user.age)
}打印:
John
42Process finished with the exit code 0
賦值或修改值:
func main() {var user = UserInfo{name: "John",age: 42,phone: 1000,}user.name = "一顆知足的心"user.age = 18fmt.Println(user.name)fmt.Println(user.age)
}打印:
一顆知足的心
18Process finished with the exit code 0
如果實例化過程比較復雜,可以編寫一個函數來實例化結構體,就像下面這樣,你也可以把它理解為一個構造函數,但是go中函數不能重載,所以你想像Java那樣通過參數不同用多個一樣的函數名是不行的。
func main() {user := NewUser("一顆知足的心", 18, 9527)fmt.Println(user)
}func NewUser(name string, age int, phone int) *UserInfo {return &UserInfo{name: name, age: age, phone: phone}
}
組合引用
和Java一樣,直接在內部字段聲明就行,請看下面例子:
type Person struct {name stringage int
}type Student struct {p Personschool string
}
看看使用:
student := Student{p: Person{name: "jack", age: 18},school: "lili school",
}
fmt.Println(student.p.name)
結構體和指針
結構體的指針和值類型的指針使用上有個小的區別,就是結構體指針在使用的時候不用解引,請看下面例子:
type UserInfo struct {name stringage, phone int
}func main() {user := &UserInfo{name: "一顆知足的心",age: 18,phone: 9527,}fmt.Println(user.name)
}
可以看到我們直接用user.name就可以調用,和普通的結構體調用一樣,因為這是go的語法糖,編譯器會自動編譯成(*user).name
結構體的標簽
這里簡單提一點,了解一下就行,標簽就是在結構體定義字段的時候,在后面打上標簽
type UserInfo struct {Name string `json:"name"`Age int `yaml:"age"`
}
結構體標簽最廣泛的應用就是在各種序列化格式中的別名定義,標簽的使用需要結合反射才能完整發揮出其功能。
方法
方法與函數的區別在于,方法擁有接收者,而函數沒有,且只有自定義類型能夠擁有方法。先來看一個例子。
例子
type IntSlice []intfunc (i IntSlice) Get(index int) int {return i[index]
}
func (i IntSlice) Set(index, val int) {i[index] = val
}func (i IntSlice) Len() int {return len(i)
}
先聲明了一個類型IntSlice,其底層類型為[]int,再聲明了三個方法Get,Set和Len,方法的長相與函數并無太大的區別,只是多了一小段(i IntSlice) 。i就是接收者,IntSlice就是接收者的類型,接收者就類似于其他語言中的this或self,只不過在 Go 中需要顯示的指明。
func main() {var intSlice IntSliceintSlice = []int{1, 2, 3, 4, 5}fmt.Println(intSlice.Get(0))intSlice.Set(0, 2)fmt.Println(intSlice)fmt.Println(intSlice.Len())
}
結合結構體
根據上面的例子,我們把方法和結構體結合一下。這里補充一點,接收者也分兩種類型,值接收者和指針接收者
我們先看值接受者:
type UserInfo struct {name stringage, phone int
}func main() {user := &UserInfo{name: "一顆知足的心",age: 18,phone: 9527,}user.updateAge(20)fmt.Println(user.age)
}func (receiver UserInfo) updateAge(age int) {receiver.age = age
}console打印:
18Process finished with the exit code 0
我們可以看到,雖然我們在代碼中,將age改為了20,但是最后user結構體中還是18,也就是說值接收者的方法,并不能改變接收者本身的屬性
那要改變接收者本身的屬性,就到了指針接收者,我們還是直接看代碼:
func main() {user := &UserInfo{name: "一顆知足的心",age: 18,phone: 9527,}user.updateAge(20)fmt.Println(user.age)
}func (receiver *UserInfo) updateAge(age int) {receiver.age = age
}console打印:
20Process finished with the exit code 0
看到變化沒,我們只是把receiver UserInfo改為receiver *UserInfo,變成指針接受者,就可以改變接收者本身的屬性。
這是為什么呢:因為值接收者可以簡單的看成一個形參,而修改一個形參的值,并不會對方法外的值造成任何影響而用指針接收者,Go 會將其解釋為(&receiver).age = age。所以方法的接收者為指針時,不管調用者是不是指針,都可以修改內部的值
總結
函數的參數傳遞過程中,是值拷貝的,如果傳遞的是一個整型,那就拷貝這個整型,如果是一個切片,那就拷貝這個切片,但如果是一個指針,就只需要拷貝這個指針,顯然傳遞一個指針比起傳遞一個切片所消耗的資源更小,接收者也不例外,值接收者和指針接收者也是同樣的道理。在大多數情況下,都推薦使用指針接收者,不過兩者并不應該混合使用,要么都用,要么就都不用