go基礎之--函數和map


在整理函數之前先整理一下關於指針

指針

普通類型變量存的就是值,也叫值類型。指針類型存的是地址,即指針的值是一個變量的地址。
一個指針指示值所保存的位置,不是所有的值都有地址,但是所有的變量都有。使用指針可以在無序知道
變量名字的情況下,間接讀取或更新變量的值。

獲取變量的地址,用&,例如: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. 不支持重載,即一個包不能有兩個名字一樣的函數
  2. 函數也是一種類型,一個函數可以賦值給變量
  3. 匿名函數
  4. 多返回值

演示一些函數的例子:

 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用途:

  1. 當函數返回時,執行defer語句,因此可以用來做資源清理
  2. 多個defer語句,按先進后出的方式執行
  3. 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的元素進行取址操作


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM