Go語言入門系列(五)之指標和結構體的使用

行人觀學發表於2020-08-08

Go語言入門系列前面的文章:

1. 指標

如果你使用過C或C++,那你肯定對指標這個概念不陌生。

我們需要先介紹兩個概念:記憶體和地址。

1.1. 記憶體和地址

我們寫的程式碼都儲存在外存(C盤、D盤)中,比如我存在了D:\Work\Program\go目錄下。如果你想要執行你的程式碼,必須先把你的程式碼載入進記憶體中,然後交給CPU執行計算,而CPU計算的結果也會存到記憶體中。

記憶體的存取速度快,其中有許多儲存單元用來儲存資料,CPU能在記憶體中直接找到這些資料。這是因為記憶體中的每個位置都有一個獨一無二的地址標識。可以把記憶體看成一幢有許多房間的大樓,每個儲存單元是一個房間,儲存的資料是房間中的物品,地址就是房間號。

所以對CPU來說,如果想找到某個房間中的物品(從記憶體中取資料),或者向某個房間中放物品(向記憶體中存資料),我們必須知道房間號(記憶體地址)。

記憶體地址通常是一串16進位制的數字,如果寫程式碼時存個整數1或取個整數1都需要寫這麼一串數字,那太麻煩了。所以高階語言為我們提供了一個便利,用我們人能記住的“名字”來代替這串數字。

這些“名字”就是變數名

var a int = 1
var b int = 2
var c int = 333
var d int = 6666

變數名和地址的關聯由編譯器替我們做,硬體訪問的仍然是記憶體地址。

1.2. 什麼是指標?

簡單地來說,指標也是一個變數,只不過這個變數中存的不是我們平常用到的1、2、3、"Hello"、true等值,而是其他變數的地址。

之所以取名指標,是因為指標變數b中儲存了變數a的地址,我們可以通過該指標變數b找到變數a,如果畫圖看起來,看起來就像是指標b指向了變數a

還可以有指標的指標:

1.3. 指標的使用

宣告一個指標:

var p *int

*int表示p是一個int型別指標,p指標中存的是一個int型別變數的地址,這意味著p中不能存其他型別變數的地址。

如何獲取某個變數的地址呢?使用操作符&

var a int = 66 //a是值為66的int變數
p = &a //將a的地址賦給指標p

那麼如何根據指標中的地址找到對應的變數呢?使用操作符*

var b = *p //根據p中的值找到a,將其值賦給b
fmt.Println(b) //66

*p = 99 //根據p中的值找到a,改變a的值
fmt.Println(a) //99

一定要注意指標的初始化,如果不初始化,則指標的的值是其零值——nil。對未初始化的指標賦值,則會出問題:

var p *int //只宣告,未初始化
*p = 12 //報錯:invalid memory address or nil pointer dereference

原因是指標p中沒值,是個nil,自然就無法根據地址找到變數。如果你想使用指標,必須先確保你的指標中有合法的記憶體地址才行。應當這樣寫:

var a int
var p *int = &a //p被初始化為a的地址
*p = 12 //根據p的值找到a,12賦值給a
//或者
var a int
var p *int
p = &a //a的地址賦給p
*p = 12 //根據p的值找到a,12賦值給a

下面是一個完整的例子:

package main

import "fmt"

func main() {
	var a int = 66 //變數a
	var p *int = &a //指標p指向a
	var b = *p //獲取p指向的變數a的值
	fmt.Println("a =",a, ", b =", b, ", p =", p)
	fmt.Println("a的地址 =", &a, ", b的地址 =", &b, ", p的地址 =", &p)

	*p = 12 //改變p指向的變數a的值

	fmt.Println("a =",a, ", b =", b, ", p =", p)
	fmt.Println("a的地址 =", &a, ", b的地址 =", &b, ", p的地址 =", &p)

	var pp **int = &p //指標pp指向指標p
	var c = *pp //獲取pp指向的p的值
	var d = **pp //獲取pp指向的p指向的a的值
	fmt.Println("pp =", pp, ", c =", c, ", d =", d)
	fmt.Println("pp的地址 =", &pp, ", c的地址 =", &c, ", d的地址 =", &d)
}

2. 結構體 (struct)

2.1. 基本使用

和C語言一樣,Go語言中也有結構體。

結構體就是一組欄位/屬性的集合。有了結構體,我們可以根據自己的需求定義自己的型別。比如狗,肯定不能用基本資料型別來表示,因為狗身上有許多屬性:string型別的姓名、int型別的年齡等等,狗是一個擁有許多屬性的集合,換句話說,狗是一個結構體。我們可以定義一個dog型別的結構體來表示狗。

結構體的宣告方式:

type 結構體名字 struct {
    欄位名1 型別1
    欄位名2 型別2
    ...
}

下面是結構體dog的宣告:

type dog struct {
    name string
    age int
}

宣告瞭結構體後,就可以使用它。

首先,只要你正確宣告瞭結構體後,你就能像使用intstring等基本型別宣告變數一樣去宣告dog型別的變數,然後,你就能給宣告的變數d的欄位賦值了,通過點號.來訪問結構體的欄位

var d dog //宣告一個dog型別的變數d
d.name = "哮天犬"
d.age = 3

除此之外,還有幾種宣告方式。

你可以按照欄位順序直接賦值

d := dog{"哮天犬", 3}

或者指定欄位賦值,這樣可以忽略欄位順序:

d := dog{age:3, name:"哮天犬"}

下面是一個完整的例子:

package main

import "fmt"

type dog struct {
	name string
	age int
}

func main() {
	var d dog //宣告一個dog型別的變數d
	d.name = "哮天犬"
	d.age = 3

	d1 := dog{"哮地犬", 2}
	
	d2 := dog{age:4, name:"哮人犬"}

	fmt.Println(d, d1, d2)
}

2.2. 結構體指標

我們可以獲取結構體的指標:

d := dog{"哮地犬", 2}
p := &d //獲取到d的地址

可以根據結構體指標訪問其欄位:

n := (*p).name
fmt.Println(n) //哮天犬

這種方式比較麻煩,Go語言提供了隱式間接引用:

n := p.name //這樣也行
fmt.Println(n)

我們可以通過new函式給結構體分配一個指標。

先介紹一下new函式:new函式用於給各種型別的記憶體分配。new(T)會給T型別分配對其合適的記憶體空間,用T型別的零值填充,並返回其地址,是一個*T型別的值。換句話說,該函式會返回一個指向T型別零值的指標。

p := new(dog)
fmt.Printf("%T\n", p) //*main.dog
fmt.Println(p) //&{ 0}
fmt.Println(*p) //{ 0}

從上面列印的三行語句中也可以看出,new(dog)返回的是一個指標。

2.3. 結構體巢狀

一個結構體也可以作為另一個結構體的欄位,下面是一個例子:

package main

import "fmt"

type people struct {
	name string
	age int
	d dog
}

type dog struct {
	name string
	age int
}

func main() {
	a := people{"行小觀", 18, dog{"小狗", 2}}
	fmt.Println(a) //{行小觀 18 {小狗 2}}
	fmt.Println(a.d) //{小狗 2}
	fmt.Println(a.name) //行小觀
	fmt.Println(a.d.name) //小狗
}

也可以使用匿名欄位,何為匿名欄位?顧名思義,只提供型別,不寫欄位名:

package main

import "fmt"

type people struct {
	name string
	age int
	dog //匿名欄位
}

type dog struct {
	name string
	age int
}

func main() {
	a := people{"行小觀", 18, dog{"小狗", 2}}
	fmt.Println(a) //{行小觀 18 {小狗 2}}
	fmt.Println(a.dog) //{小狗 2}
	fmt.Println(a.name) //行小觀
	fmt.Println(a.dog.name) //小狗
}

作者簡介

我是「行小觀」,於千萬人中的一個普通人。陰差陽錯地走上了程式設計這條路,既然走上了這條路,那麼我會盡可能遠地走下去。

我會在公眾號『行人觀學』中持續更新「Java」、「Go」、「資料結構和演算法」、「計算機基礎」等相關文章。

歡迎關注,我們一起踏上行程。

本文章屬於系列文章《Go語言入門系列》。

如有錯誤,還請指正。

相關文章