Go語言中的常量使用關鍵字const定義,用于存儲不會改變的數據,常量是在編譯時被創建的,即使定義在函數內部也是如此,并且只能是布爾型、數字型(整數型、浮點型和復數)和字符串型。
由于編譯時的限制,定義常量的表達式必須為能被編譯器求值的常量表達式。
聲明格式:
type可以省略
和變量聲明一樣,可以批量聲明多個常量:
const (e = 2.14pi=3.14)
所有常量的運算都可以在編譯期完成,這樣不僅可以減少運行時的工作,也方便其他代碼的編譯優化,當操作數是常量時,一些運行時的錯誤也可以在編譯時被發現,例如整數除零、字符串索引越界、任何導致無效浮點數的操作等。
常量間的所有算術運算、邏輯運算和比較運算的結果也是常量,對常量的類型轉換操作或以下函數調用都是返回常量結果:len、cap、real、imag、complex 和 unsafe.Sizeof。
因為它們的值是在編譯期就確定的,因此常量可以是構成類型的一部分
如果是批量聲明的常量,除了第一個外其它的常量右邊的初始化表達式都可以省略,如果省略初始化表達式則表示使用前面常量的初始化表達式,對應的常量類型也是一樣的。例如:
const (a = 1bc = 2d
)
fmt.Println(a, b, c, d) // "1 1 2 2"
1.1 iota 常量生成器
常量聲明可以使用 iota 常量生成器初始化,它用于生成一組以相似規則初始化的常量,但是不用每行都寫一遍初始化表達式。
在一個 const 聲明語句中,在第一個聲明的常量所在的行,iota 將會被置為 0,然后在每一個有常量聲明的行加1
比如,定義星期日到星期六,從0-6
2. 指針
指針(pointer)在Go語言中可以被拆分為兩個核心概念:
- 類型指針,允許對這個指針類型的數據進行修改,傳遞數據可以直接使用指針,而無須拷貝數據,類型指針不能進行偏移和運算。
- 切片,由指向起始元素的原始指針、元素數量和容量組成。
受益于這樣的約束和拆分,Go語言的指針類型變量即擁有指針高效訪問的特點,又不會發生指針偏移,從而避免了非法修改關鍵性數據的問題。
同時,垃圾回收也比較容易對不會發生偏移的指針進行檢索和回收。
切片比原始指針具備更強大的特性,而且更為安全。
切片在發生越界時,運行時會報出宕機,并打出堆棧,而原始指針只會崩潰。
2.1 如何理解指針
var a int = 10
如果用大白話來解釋上述語句:
在內存中開辟了一片空間,空間內存放著數值10,這片空間在整個內存當中,有一個唯一的地址,用來進行標識,指向這個地址的變量就稱為指針
如果用類比的說明:
內存比作酒店,每個房間就是一塊內存,上述代碼表示為:定了一間房間a,讓10住進了房間,房間有一個門牌號px,這個px就是房間的地址,房卡可以理解為就是指針,指向這個地址。
一個指針變量可以指向任何一個值的內存地址,它所指向的值的內存地址在 32 和 64 位機器上分別占用 4 或 8 個字節,占用字節的大小與所指向的值的大小無關。
當一個指針被定義后沒有分配到任何變量時,它的默認值為 nil。
每個變量在運行時都擁有一個地址,這個地址代表變量在內存中的位置。
Go語言中使用在變量名前面添加&操作符(前綴)來獲取變量的內存地址(取地址操作),格式如下:
//其中 v 代表被取地址的變量,變量 v 的地址使用變量 ptr 進行接收,ptr 的類型為*T,稱做 T 的指針類型,*代表指針。
ptr := &v // v 的類型為 T
var cat int = 1var str string = "hahah"fmt.Printf("%p %p", &cat, &str) //0xc00000a0d8 0xc000020070
變量、指針和地址三者的關系是,每個變量都擁有地址,指針的值就是地址
當使用&操作符對普通變量進行取地址操作并得到變量的指針后,可以對指針使用*操作符,也就是指針取值
var room int = 10ptr := &roomfmt.Println(ptr) //0xc000096068fmt.Printf("%T\n", ptr) //*intfmt.Println(*ptr) //10
取地址操作符&和取值操作符*是一對互補操作符,&取出地址,*根據地址取出地址指向的值
變量、指針地址、指針變量、取地址、取值的相互關系和特性如下:
- 對變量進行取地址操作使用&操作符,可以獲得這個變量的指針變量。
- 指針變量的值是指針地址。
- 對指針變量進行取值操作使用*操作符,可以獲得指針變量指向的原變量的值。
2.3 創建指針的另一種方法
Go語言還提供了另外一種方法來創建指針變量,格式如下:
new(類型)
str := new(string)
*str = "碼神之路Go語言教程"
fmt.Println(*str)
new() 函數可以創建一個對應類型的指針,創建過程會分配內存,被創建的指針指向默認值。
2.4 指針小案例
獲取命令行的輸入信息
Go語言內置的 flag 包實現了對命令行參數的解析,flag 包使得開發命令行工具更為簡單。
3. 變量的生命周期
變量的生命周期指的是在程序運行期間變量有效存在的時間間隔。
變量的生命周期與變量的作用域有不可分割的聯系:
- 全局變量:它的生命周期和整個程序的運行周期是一致的;
- 局部變量:它的生命周期則是動態的,從創建這個變量的聲明語句開始,到這個變量不再被引用為止;
- 形式參數和函數返回值:它們都屬于局部變量,在函數被調用的時候創建,函數調用結束后被銷毀。
go的內存中應用了兩種數據結構用于存放變量:
- 堆(heap):堆是用于存放進程執行中被動態分配的內存段。它的大小并不固定,可動態擴張或縮減。當進程調用 malloc 等函數分配內存時,新分配的內存就被動態加入到堆上(堆被擴張)。當利用 free 等函數釋放內存時,被釋放的內存從堆中被剔除(堆被縮減);
- 棧(stack):棧又稱堆棧, 用來存放程序暫時創建的局部變量,也就是我們函數的大括號{ }中定義的局部變量。
棧是先進后出,往棧中放元素的過程,稱為入棧,取元素的過程稱為出棧。
棧可用于內存分配,棧的分配和回收速度非常快
在程序的編譯階段,編譯器會根據實際情況自動選擇在棧或者堆上分配局部變量的存儲空間,不論使用 var 還是 new 關鍵字聲明變量都不會影響編譯器的選擇。
func f() {x := 10global = &x
}func g() {y := new(int)*y = 1
}
上述代碼中,函數 f 里的變量 x 必須在堆上分配,因為它在函數退出后依然可以通過包一級的 global 變量找到,雖然它是在函數內部定義的。
用Go語言的術語說,這個局部變量 x 從函數 f 中逃逸了。
相反,當函數 g 返回時,變量 y 不再被使用,也就是說可以馬上被回收的。因此,y 并沒有從函數 g 中逃逸,編譯器可以選擇在棧上分配 *y 的存儲空間,也可以選擇在堆上分配,然后由Go語言的 GC(垃圾回收機制)回收這個變量的內存空間。
4. 類型別名
類型別名是 Go 1.9 版本添加的新功能,主要用于解決代碼升級、遷移中存在的類型兼容性問題。
格式:
//TypeAlias 只是 Type 的別名,本質上 TypeAlias 與 Type 是同一個類型,就像一個孩子小時候有小名、乳名,上學后用學名,英語老師又會給他起英文名,但這些名字都指的是他本人。
type TypeAlias = Type
還有一種是類型定義:
//定義Name為Type類型 ,定義之后 Name為一種新的類型
type Name Type
類型別名與類型定義表面上看只有一個等號的差異,那么它們之間實際的區別有哪些呢?
//將newInt定義為int類型type newInt int//將int取一個別名叫intAlicetype intAlice = intvar a newIntfmt.Printf("%T\n", a) //main.newIntvar b intAlice //IntAlias 類型只會在代碼中存在,編譯完成時,不會有 IntAlias 類型。fmt.Printf("%T\n", b) //int
8. 字符串與其他數據類型的轉換
整數 與 字符串
var str1 string = "1"//字符串轉整型res, _ := strconv.Atoi(str1)fmt.Printf("%T\n", res)//整型轉字符串myString := strconv.Itoa(res)fmt.Printf("%T", myString)
浮點數 與字符串