1. 變量 2. 命名 3. 常量 4. 基本類型 5. 引用類型 6. 類型轉換 7. 自定義類型
變量
Go語言有兩種方式定義變量:
var 關鍵字 := 短變量聲明符
var關鍵字
var x int //自動初始化爲0 var y = false //自動推斷爲bool類型
和C語言不同,類型被放在變量名之後,並且在運行時,爲了避免出現不可預測行爲,內存分配器會初始化變量爲二進制零值。 如果顯示初始化變量的值,可以省略變量類型,由編譯器推斷。
Go語言一次可以定義多個變量,並可以對其初始化成不同的類型,比如
var x,y int //相同類型多個變量 var a, s = 10010, "hello go" //不同類型多個變量
按照Go語言的編碼規範,建議以組的方式整理多行變量定義,比如:
var ( x,y int a,s = 10010, "hello go" )
短變量聲明符
在介紹詞法的章節中已經看到過這種用法,比如:
x := 10010 a , s := 10010, "hello go"
這種方式很簡單,但日常開發中新手經常犯下的一個錯誤,比如:
var x = 10010 func main(){ println(&x, x) ... x := "hello go" //對全局變量重新定義並初始化 println(&x, x) }
可想而知最終釀成的後果;爲了正確使用這種簡單的短變量聲明方式,請遵循如下限制:
1. 定義變量的同時,顯示初始化 2. 不能提供數據類型 3. 只能在函數體內部使用 4. 函數體內不能使用短變量重複聲明 5. 不能使用短變量聲明這種方式來設置字段值 6. 不能使用nil初始化一個未指定類型的變量
思考下面的代碼片段,輸出結果是什麼?
func test(){ x := 1 fmt.Println(x) { fmt.Println(x) x = 3 x := 2 fmt.Println(x) x = 5 } fmt.Println(x) }
如何檢查你的代碼中是否存在這樣的聲明?
# go tool vet -shadow yourfile.go
Go編譯器會將未使用的局部變量當作錯誤,比如:
func main() { x := 1 var a int //a declared and not used fmt.Println(x) }
那麼思考一下下面的代碼,會不會報錯?
func main() { x := 1 const a = 10010 // ??? fmt.Println(x) }
所以要記住函數體中存在未使用的常量,不會引發編譯器報錯;
常量
常量表示運行時恆定不變的值,由const關鍵字聲明,常量值必須是在編譯期可確定的字符、字符串、數字或布爾值。 聲明常量的同時可以指定其常量類型,或者由Go編譯器通過初始化值推斷,如果顯示指定其常量類型,那麼就必須保證常量左右值類型一致。 必要時要做類型轉換。並且右值不能超出常量類型的取值範圍,否則會造成溢出錯誤。比如:
const( x , y = 100, -10010 b byte = byte(x) //類型不一致需要做類型轉換 c uint8 = uint8(y) //溢出 )
思考:修改一下上面的代碼,只進行聲明而沒有對其初始化和指定類型,結果會怎樣?
const( x = 100 y s string = "hello go" c ) fmt.Printf("%T , %v\n",y,y) fmt.Printf("%T , %v\n",c,c)
結論證明: 在常量組中如果不指定常量的類型和初始化值,則與上一行非空常量的右值相同
常量值可以是Go編譯器能計算出結果的表達式,如unsafe.Sizeof, len,cap,iota等。
枚舉
Go語言沒有明確上的定義枚舉enum類型,不過可以通過iota關鍵字來實現一組遞增常量的定義,比如:
const ( _ = iota KB = 1 << (10 * iota) // 1<<(10 * 1) MB // 1<<(10 * 2) GB // 1<<(10 * 3) )
多常量定義中也可以使用iota,它們各自單獨計算,確保常量組中每行常量的列數量相等即可,比如:
const( a ,b = iota,iota *10 //0 , 0 * 10 c ,d //1 , 1 * 10 )
如果中斷iota自增,需要顯示恢復,且後續的常量值將按行序遞增,比如:
const( a = iota b //b = ??? c = 100 d //d = ??? e = iota //c = ??? f //f = ???)
自增常量可以指定常量類型,甚至可以自定義類型,比如:
const( f float = iota //指定常量類型)type color byteconst( black color = iota //自定義類型 red blue )
常量VS變量
常量是“只讀”屬性 變量在運行期分配內存,而常量是在編譯期間直接展開,作爲指令數據使用 數字常量不會分配存儲空間,不能像變量那樣可以進行內存尋址
比如:
const x = 10 const y byte = x // 相當於const y byte = 100const a int = 10 //顯示指定常量類型,編譯器做強類型檢查const b byte = a // cannot use a (type int) as type byte in const initializer
基本類型
引用類型
特指slice,map和channel這三種預定義類型,他們具有比數字、數組等更復雜的存儲結構; 引用類型的創建必須是用make函數來創建,因爲除了分配內存之外,還有一系列屬性需要初始化,比如指針, 長度,甚至包括哈希分佈,數據隊列等。 而內置函數new只是按照指定類型長度分配零值內存,返回指針。比如:
p := new(map[string]int) //new函數返回指針 m := *p m["go"] = 1 //panic: assignment to entry in nil map(運行時)
自定義類型
使用關鍵字type可以定義用戶自定義類型,包括基於基本數據類型創建,或者結構體和函數等類型,比如:
type flags byteconst ( exec flags = 1 << iota write read )
與var、const類似,多個type定義也可以合併成組,比如:
type ( user struct{ name string age int } f func(user) ) //自定義一組類型 u := user{"Tom",20} var say f = func(s user){ fmt.Println(s.name, s.age) } say(u)
自定義類型與基礎類型相比,它們具有相同的數據結構,但它們不存在任何關係,除操作符外,自定義類型不會繼承基礎類型的其它信息,它們屬於完全不同的兩種類型, 即它們之間不能做隱式轉換,不能視爲別名甚至不能用於比較表達式。比如:
type( data int ) var d data = 10 var x int = d //cannot use d (type data) as type int in assignment println(x == d) //invalid operation: x == d (mismatched types int and data)
未命名類型
與有明確標誌符的bool, int, string等類型相比,數組、切片、字典、通道等類型與具體元素類型或長度等屬性有關,所以稱作 未命名類型(unnamed type)。當然可以用type爲其提供具體名稱,將其改變爲命名類型(named type)。
具有相同聲明的未命名類型視作同一類型,比如:
1. 具有相同基類型的指針 2. 具有相同元素類型和長度的數組(array) 3. 具有相同元素類型的切片(slice) 4. 具有相同鍵值類型的字典(map) 5. 具有相同數據類型及操作方向的通道(channel) 6. 具有相同字段序列(字段名、字段類型、標籤以及字段順序)的結構體(struct) 7. 具有相同簽名(參數類型,參數順序和返回值列表,不包括參數名)的函數(func) 8. 具有相同方法集(方法名、方法簽名、不包括順序)的接口(interface)
結構體的tag經常被初忽視,它也屬於結構體類型組成的一部分,而不僅僅是元數據描述。
類型轉換
類型轉換在日常開發中是不可避免的問題,Go語言屬於強類型語言,除常量、別名以及未命名類型之外,其它不同類型之間的轉換都需要顯示類型轉換;好處是我們至少能知道語句及表達式的確切含義,比如:
a := 10 //int b := byte(a) //byte c := a + int(b) //確保類型一致性 d := b + byte(a)//確保類型一致性
如果類型轉換的目標是指針、單向通道、沒有返回值的函數類型,那麼爲了避免語法歧義帶來的類型轉換錯誤,必須使用括號,比如:
x := 100 p := *int(&x) //cannot convert &x (type *int) to type int //invalid indirect of int(&x) (type int) pc := <-chan int(c) //invalid operation: pc <- 3 (send to non-chan type int)
正確做法如下:
p := (*int)(&x) //(*int)(&x) (<-chan int)(c) //單向通道 (func()) (f) //無返回值的函數 (func()int) (f) //有返回值的函數,但是也可以不用括號:func()int(f),但是爲了影響程序可讀性,建議加上括號