在整理函數之前先整理一下關於指針
指針
普通類型變量存的就是值,也叫值類型。指針類型存的是地址,即指針的值是一個變量的地址。
一個指針指示值所保存的位置,不是所有的值都有地址,但是所有的變量都有。使用指針可以在無序知道
變量名字的情況下,間接讀取或更新變量的值。
獲取變量的地址,用&,例如:var a int 獲取a的地址:&a,&a(a的地址)這個表達式獲取一個指向整形變量的指針,它的類型是整形指針(*int),如果值叫做p,我們說p指向x,或者p包含x的地址,p指向的變量寫成
*p ,而*p獲取變量的值,這個時候*p就是一個變量,所以可以出現在賦值操作符的左邊,用於更新變量的值
指針類型的零值是nil
兩個指針當且僅當指向同一個變量或者兩者都是nil的情況才相等
通過下面小例子進行理解指針:
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func test() { 8 x := 1 9 // &x 獲取的是變量x的地址,並賦值給p,這個時候p就是一個指針 10 p := &x 11 // p是指針,所以*p獲取的就是變量的值,指針指向的是變量x的值,即*p為1 12 fmt.Println(*p) 13 // 這里*p 進行賦值,也就是更改了變量x的值,即實現不知道變量的名字更改變量的值 14 *p = 2 15 fmt.Println(x) 16 } 17 18 func main() { 19 test() 20 }
再看一個關於通過一個函數來修改變量值的問題:
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func modify(num int) { 8 num = 100 9 } 10 11 func main() { 12 a := 10 13 modify(a) 14 fmt.Println(a) 15 }
這個例子是修改變量的值,但是最后打印變量a的值是還是10,所以這里就需要知道,當通過定義的函數modify來修改變量的值時,傳入變量a其實會進行一次拷貝,傳入的其實是a變量的一個副本,所以當通過
modify修改的時候修改的是副本的值,並沒有修改變量a的值。
當我們理解指針的之后,就可以通過指針的的方法來解決上面的這個問題,將代碼更改為:
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func modify(num *int) { 8 *num = 100 9 } 10 11 func main() { 12 a := 10 13 modify(&a) 14 fmt.Println(a) 15 }
這里定義modify函數的時候參數設置的是一個指針,所以我們傳入參數時,傳入的是&a即變量a的地址,而這個地址指向的值是10,雖然這次傳入的參數也是進行了傳入的指針進行了一次拷貝,但是即使是拷貝了副本指向的值還是10,所以當我們通過指針*num修改值的時候其實就是在修改變量a的值。
內置函數
len: 用於求長度,比如string、array、slice、map、channel
new: 來分配內存,主要來分配值類型,如int、struct。返回的是指針
make: 來分配內存,主要 來分配引 類型, 如chan、map、slice
append: 來追加元素到數組、slice中
panic和recover: 來做錯誤(這個后續整理)
下面重點整理new和make
new函數
func new(Type) *Type
先看一下官網對這個內置函數的介紹:
內置函數 new 用來分配內存,它的第一個參數是一個類型,不是一個值,它的返回值是一個指向新分配類型零值的指針。這里要特別注意new返回的是一個指針
new函數也是創建變量的一種方式。表達式new(T)創建一個未命名的T類型變量,初始化T類型的零值,並返回其地址(地址類型為*T)
通過下面例子進行理解:
1 package main 2 3 import "fmt" 4 5 func newFunc() { 6 p := new(int) 7 fmt.Println(p) //打印是地址 8 fmt.Println(*p) //int類型的零值為0這里打印0 9 *p = 2 10 fmt.Println(*p) //*p已經為其地址指向了一個變量2,所以這里打印為2 11 } 12 13 func main() { 14 newFunc() 15 }
這里我們要知道new創建的變量和取其地址的普通局部變量沒有什么不同,只是語法上的便利
下面是兩種方式的例子:
1 func newInt() *int { 2 return new(int) 3 } 4 5 func newInt2() *int { 6 var res int 7 return &res 8 }
如果我們定義一個指針是不能直接給這個指針賦值的,而是需要先給這個指針分配內存,然后才能賦值
下面例子先不初始化分配內存,直接賦值:
正確的做法是我們需要先通過new初始化,正確代碼如下:
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func test(){ 8 var p *int 9 p = new(int) 10 *p = 10 11 fmt.Println(*p) 12 } 13 14 func main(){ 15 test() 16 }
make函數
func make(Type, size IntegerType) Type
先看一下官網對這個內置函數的介紹:
內置函數make用來為slice,map或chan類型分配內存或初始化一個對象(這里需要注意:只能是這三種類型)
第一次參數也是一個類型而不是一個值
返回的是類型的引用而不是指針,而且返回值也依賴具體傳入的類型
注意:make返回初始化后的(非零)值。
其實在上一篇整理切片slice的時候就用到了make如:
make([]type,len)
當時通過make來初始化slice的時候,第二個參數指定了它的長度,如果嗎,沒有第三個參數,它的容量和長度相等,當然也可以傳入第三個參數來指定不同的容量值,但是注意不能比長度值小
這里提前說一下通過make初始化map的時候,根據size大小來初始化分配內存,不過分配后的map長度為0,如果size被忽略了,會在初始化分配內存的時候分配一個小的內存
關於new和make的一個小結:
new 的作用是初始化一個指向類型的指針 (*T),make的作用是為slice,map或者channel初始化,並且返回引用 T
函數
函數的聲明語法:func 函數名 (參數 表) [(返回值 表)] {}
這了要注意第一個花括號必須和func在一行
常見的幾種聲明函數的方法:
func add(){
}
func add(a int,b int){
}
func add(a int,b int) int{
}
func add(a int, b int)(int,int){
}
func add(a ,b int)(int,int){
}
golang函數的特點:
- 不支持重載,即一個包不能有兩個名字一樣的函數
- 函數也是一種類型,一個函數可以賦值給變量
- 匿名函數
- 多返回值
演示一些函數的例子:
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func add(a, b int) int { 8 return a + b 9 } 10 11 func main() { 12 c := add //這里把函數名賦值給變量c 13 fmt.Printf("%p %T", c, add) 14 sum := c(10, 20) //調用c其實就是在調用add 15 fmt.Println(sum) 16 17 }
golang函數還有一個用法例子:
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 type addFunc func(int, int) int 8 9 func add(a, b int) int { 10 return a + b 11 } 12 13 func operator(op addFunc, a int, b int) int { 14 return op(a, b) 15 } 16 17 func main() { 18 c := add 19 sum := operator(c, 100, 200) 20 fmt.Println(sum) 21 }
變量作用域
在函數外面的變量是全局變量
函數內部的變量是局部變量
go中變量的作用域有多種情況:
函數級別的,代碼塊級別的
通過下面例子理解:
關於函數的可變參數
變長函數被調用的時候可以有可變的參數個數
在參數列表最后的類型名稱前使用省略號...可以聲明一個變長的函數,
例如:
0個或多個參數
func add(arg...int) int{
}
1個或多個參數
func add(a int,arg...int) int{
}
2個或多個參數
func add(a int,b int,arg...int)int{
}
關於函數參數的傳遞
不管是值類型還是引用傳遞,傳遞給函數的都是變量的副本
注意:map,slice,chan,指針,interface默認以引用方式傳遞
延遲函數defer的調用
語法上,一個defer語句就是一個普通的函數或者方法調用,在調用之前加上關鍵字defer。函數和參數表達式會在語句執行時求值,但是無論是正常情況還是執行return語句或者函數執行完畢,以及不正常情況下,如程序發生宕機,實際的調用推遲到包含defer語句的函數結束后才執行,defer語句沒有限制使用次數。
defer用途:
- 當函數返回時,執行defer語句,因此可以用來做資源清理
- 多個defer語句,按先進后出的方式執行
- defer語句中的變量,在defer聲明時就決定了
先通過一個小例子理解defer:
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 8 func testDefer(){ 9 a := 100 10 fmt.Printf("before defer:a=%d\n",a) 11 defer fmt.Println(a) 12 a = 200 13 fmt.Printf("after defer:a=%d\n",a) 14 15 } 16 17 func main(){ 18 testDefer() 19 }
這里我們可以這樣理解當我們執行defer語句的時a=100,這個時候壓入到棧中,等程序最后結束的時候才會調用defer語句,所以打印的順序是最后才打印一個數字100
defer語句經常使用成對的操作,比如打開和關閉,連接和斷開,加鎖和解鎖
下面拿關閉一個打開文件操作為例子,當我們通過os.Open()打開一個文件的時候可以在后面添加defer f.Close() 這樣在函數結束時就可以幫我們自動關閉一個打開的文件
Map類型
key-value的數據結構,又叫字典
聲明
var map1 map[keytype]valuetype
例子:
var a map[string]string
var a map[string]int
注意:聲明是不會分配內存的需要make初始化
初始化的兩種方式:
var map[string]string = map[string][string]{"hello","world"}
或:
var a = make(map[string]string,10)
插入和更新
a["hello"] = "world"
查找
val,ok := a["hello"]
遍歷
for k,v := range a{
fmt.println(k,v)
}
刪除
delete(a,"hello")
這個操作是安全的,及時這個元素不存在也不會報錯,如果一個查找失敗將返回value類型對應的零值
長度
len(a)
map是引用類型
注意:map中的元素並不是一個變量,所以我們不能對map的元素進行取址操作