golang-指針,函數,map
指針
普通類型變量存的就是值,也叫值類型。指針類型存的是地址,即指針的值是一個變量的地址。
一個指針只是值所保存的位置,不是所有的值都有地址,但是所有的變量都有。使用指針可以在無需知道
變量名字的情況下,間接讀取或更新變量的值。
獲取變量的地址,用&,例如:var a int 獲取a的地址:&a,&a(a的地址)這個表達式獲取一個指向整型變量的指針,它的類型是整形指針(*int),如果值叫做p,我們說p指向x,或者p包含x的地址,p指向的變量寫成
*p ,而*p獲取變量的值,這個時候*p就是一個變量,所以可以出現在賦值操作符的左邊,用於更新變量的值
指針類型的零值是nil
兩個指針當且僅當指向同一個變量或者兩者都是nil的情況才相等
通過下面小例子進行理解指針:
package main import "fmt" func main() { x := 1 //&x 獲取的是變量x的地址,並賦值給p,這個時候p就是一個指針 //p是指針,所以*p獲取的就是變量的值,指針指向的是變量x的值,即*p為1 p := &x fmt.Println(*p) //這裏*p 進行賦值,也就是更改了變量x的值,即實現不知道變量的名字更改變量的值 *p = 2 fmt.Println(x) }
再看一個關於通過一個函數來修改變量值的問題:
package main import"fmt" func modify(num int) { num = 1000 } func main() { a := 1 modify(a) fmt.Println(a) }
這個例子是修改變量的值,但是最後打印變量a的值是還是10,所以這裏就需要知道,當通過定義的函數modify來修改變量的值時,傳入變量a其實會進行一次拷貝,傳入的其實是a變量的一個副本,所以當通過
modify修改的時候修改的是副本的值,並沒有修改變量a的值。
當我們理解指針的之後,就可以通過指針的的方法來解決上面的這個問題,將代碼更改為:
package main import "fmt" func modify(num *int) { *num = 1000 } func main() { a := 1 modify(&a) fmt.Println(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)
通過下面例子進行理解:
package main import "fmt" func newFunc() { p := new(int) fmt.Println(p) //打印是地址 fmt.Println(*p) //int類型的零值為0這裏打印0 *p = 2 fmt.Println(*p) //*p已經為其地址指向了一個變量2,所以這裏打印為2 } func main() { newFunc() }
這裏我們要知道new創建的變量和取其地址的普通局部變量沒有什麽不同,只是語法上的便利
如果我們定義一個指針是不能直接給這個指針賦值的,而是需要先給這個指針分配內存,然後才能賦值
下面例子先不初始化分配內存,直接賦值會報錯,如下:
package main import "fmt" //new創建變量的方式 func newIntFunc() *int { return new(int) } //取地址的普通局部變量 func newIntFunc2() *int { var res int return &res } func main() { var p1 *int /** 註意下面這句p1 = newIntFunc()必須有,否則會報錯 <nil> panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x108d723] goroutine 1 [running]: main.main() /Users/zhangjinyu/learngo/src/go_dev/day04/new1/new1.go:18 +0x63 exit status 2 */ p1 = newIntFunc() fmt.Println(p1) *p1 = 22 fmt.Println(*p1) var p2 *int p2 = newIntFunc2() fmt.Println(p2) *p2 = 33 fmt.Println(*p2) }
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函數的特點:
- 不支持重載,即一個包不能有兩個名字一樣的函數
- 函數也是一種類型,一個函數可以賦值給變量
- 匿名函數
- 多返回值
演示一些函數的例子:
package main import ( "fmt" ) func add(a, b int) int { return a + b } func main() { c := add //這裏把函數名賦值給變量c fmt.Printf("%p %T", c, add) sum := c(10, 20) //調用c其實就是在調用add fmt.Println(sum) }
還有一個:
package main import ( "fmt" ) type addFunc func(int, int) int func add(a, b int) int { return a + b } func operator(op addFunc, a int, b int) int { return op(a, b) } func main() { c := add sum := operator(c, 100, 200) fmt.Println(sum) }
變量作用域
在函數外面的變量是全局變量
函數內部的變量是局部變量
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:
package main import ( "fmt" ) func testDefer(){ a := 100 fmt.Printf("before defer:a=%d\n",a) defer fmt.Println(a) a = 200 fmt.Printf("after defer:a=%d\n",a) } func main(){ testDefer() }
這裏我們可以這樣理解當我們執行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的元素進行取址操作
如下:
package main import "fmt" func createMap() { /*創建map的四種方式 1) make(map[KeyType]ValueType, initialCapacity) 2) make(map[KeyType]ValueType) 3) map[KeyType]ValueType{} 4) map[KeyType]ValueType{key1 : value1, key2 : value2, ... , keyN : valueN} */ map1 := make(map[string]string, 5) map2 := make(map[string]string) map3 := map[string]string{} map4 := map[string]string{"a": "1", "b": "2", "c": "3", "d": "4"} fmt.Println(map1, map2, map3, map4) } //填充和遍歷 func traversalMap() { map1 := make(map[string]string) map1["a"] = "1" map1["b"] = "2" map1["c"] = "3" for index, val := range map1 { fmt.Printf("%s->%s\n", index, val) } } //更新,查找,刪除 func updateMap() { fmt.Println("-----------------------------") map4 := map[string]string{"a": "1", "b": "2", "c": "3", "d": "4"} //查找,返回key對應的value,和是否存在的布爾值 value, exist := map4["a"] fmt.Printf("%v->%v\n", exist, value) fmt.Println("=============================") //更新 map4["a"] = "8" fmt.Printf("%v\n", map4) //刪除 delete(map4, "b") fmt.Printf("%v\n", map4) } func main() { createMap() traversalMap() updateMap() }
轉自https://www.cnblogs.com/zhaof/p/8129424.html
golang-指針,函數,map