這小節(jié)我們將要介紹如何定義變量、常量、Go內(nèi)置類型以及Go程序設(shè)計(jì)中的一些技巧。
Go語(yǔ)言里面定義變量有多種方式。
使用var
關(guān)鍵字是Go最基本的定義變量方式,與C語(yǔ)言不同的是Go把變量類型放在變量名后面:
//定義一個(gè)名稱為“variableName”,類型為"type"的變量
var variableName type
定義多個(gè)變量
//定義三個(gè)類型都是“type”的變量
var vname1, vname2, vname3 type
定義變量并初始化值
//初始化“variableName”的變量為“value”值,類型是“type”
var variableName type = value
同時(shí)初始化多個(gè)變量
/*
定義三個(gè)類型都是"type"的變量,并且分別初始化為相應(yīng)的值
vname1為v1,vname2為v2,vname3為v3
*/
var vname1, vname2, vname3 type= v1, v2, v3
你是不是覺得上面這樣的定義有點(diǎn)繁瑣?沒關(guān)系,因?yàn)镚o語(yǔ)言的設(shè)計(jì)者也發(fā)現(xiàn)了,有一種寫法可以讓它變得簡(jiǎn)單一點(diǎn)。我們可以直接忽略類型聲明,那么上面的代碼變成這樣了:
/*
定義三個(gè)變量,它們分別初始化為相應(yīng)的值
vname1為v1,vname2為v2,vname3為v3
然后Go會(huì)根據(jù)其相應(yīng)值的類型來幫你初始化它們
*/
var vname1, vname2, vname3 = v1, v2, v3
你覺得上面的還是有些繁瑣?好吧,我也覺得。讓我們繼續(xù)簡(jiǎn)化:
/*
定義三個(gè)變量,它們分別初始化為相應(yīng)的值
vname1為v1,vname2為v2,vname3為v3
編譯器會(huì)根據(jù)初始化的值自動(dòng)推導(dǎo)出相應(yīng)的類型
*/
vname1, vname2, vname3 := v1, v2, v3
現(xiàn)在是不是看上去非常簡(jiǎn)潔了?:=
這個(gè)符號(hào)直接取代了var
和type
,這種形式叫做簡(jiǎn)短聲明。不過它有一個(gè)限制,那就是它只能用在函數(shù)內(nèi)部;在函數(shù)外部使用則會(huì)無法編譯通過,所以一般用var
方式來定義全局變量。
_
(下劃線)是個(gè)特殊的變量名,任何賦予它的值都會(huì)被丟棄。在這個(gè)例子中,我們將值35
賦予b
,并同時(shí)丟棄34
:
_, b := 34, 35
Go對(duì)于已聲明但未使用的變量會(huì)在編譯階段報(bào)錯(cuò),比如下面的代碼就會(huì)產(chǎn)生一個(gè)錯(cuò)誤:聲明了i
但未使用。
package main
func main() {
var i int
}
所謂常量,也就是在程序編譯階段就確定下來的值,而程序在運(yùn)行時(shí)無法改變?cè)撝?。在Go程序中,常量可定義為數(shù)值、布爾值或字符串等類型。
它的語(yǔ)法如下:
const constantName = value
//如果需要,也可以明確指定常量的類型:
const Pi float32 = 3.1415926
下面是一些常量聲明的例子:
const Pi = 3.1415926
const i = 10000
const MaxThread = 10
const prefix = "astaxie_"
賦值后打印出這些常量后的結(jié)果為
Go 常量和一般程序語(yǔ)言不同的是,可以指定相當(dāng)多的小數(shù)位數(shù)(例如200位), 若指定給float32自動(dòng)縮短為32bit,指定給float64自動(dòng)縮短為64bit,詳情參考鏈接
在Go中,布爾值的類型為bool
,值是true
或false
,默認(rèn)為false
。
//示例代碼
var isActive bool // 全局變量聲明
var enabled, disabled = true, false // 忽略類型的聲明
func test() {
var available bool // 一般聲明
valid := false // 簡(jiǎn)短聲明
available = true // 賦值操作
}
整數(shù)類型有無符號(hào)和帶符號(hào)兩種。Go同時(shí)支持int
和uint
,這兩種類型的長(zhǎng)度相同,但具體長(zhǎng)度取決于不同編譯器的實(shí)現(xiàn)。Go里面也有直接定義好位數(shù)的類型:rune
, int8
, int16
, int32
, int64
和byte
, uint8
, uint16
, uint32
,uint64
。其中rune
是int32
的別稱,byte
是uint8
的別稱。
需要注意的一點(diǎn)是,這些類型的變量之間不允許互相賦值或操作,不然會(huì)在編譯時(shí)引起編譯器報(bào)錯(cuò)。
如下的代碼會(huì)產(chǎn)生錯(cuò)誤:invalid operation: a + b (mismatched types int8 and int32)
var a int8
var b int32
c:=a + b
另外,盡管int的長(zhǎng)度是32 bit, 但int 與 int32并不可以互用。
浮點(diǎn)數(shù)的類型有float32
和float64
兩種(沒有float
類型),默認(rèn)是float64
。
這就是全部嗎?No!Go還支持復(fù)數(shù)。它的默認(rèn)類型是complex128
(64位實(shí)數(shù)+64位虛數(shù))。如果需要小一些的,也有complex64
(32位實(shí)數(shù)+32位虛數(shù))。復(fù)數(shù)的形式為RE + IMi
,其中RE
是實(shí)數(shù)部分,IM
是虛數(shù)部分,而最后的i
是虛數(shù)單位。下面是一個(gè)使用復(fù)數(shù)的例子:
var c complex64 = 5+5i
//output: (5+5i)
fmt.Printf("Value is: %v", c)
我們?cè)谏弦还?jié)中講過,Go中的字符串都是采用UTF-8
字符集編碼。字符串是用一對(duì)雙引號(hào)(""
)或反引號(hào)(`)括起來定義,它的類型是string
。
//示例代碼
var frenchHello string // 聲明變量為字符串的一般方法
var emptyString string = "" // 聲明了一個(gè)字符串變量,初始化為空字符串
func test() {
no, yes, maybe := "no", "yes", "maybe" // 簡(jiǎn)短聲明,同時(shí)聲明多個(gè)變量
japaneseHello := "Konichiwa" // 同上
frenchHello = "Bonjour" // 常規(guī)賦值
}
在Go中字符串是不可變的,例如下面的代碼編譯時(shí)會(huì)報(bào)錯(cuò):cannot assign to s[0]
var s string = "hello"
s[0] = 'c'
但如果真的想要修改怎么辦呢?下面的代碼可以實(shí)現(xiàn):
s := "hello"
c := []byte(s) // 將字符串 s 轉(zhuǎn)換為 []byte 類型
c[0] = 'c'
s2 := string(c) // 再轉(zhuǎn)換回 string 類型
fmt.Printf("%s\n", s2)
Go中可以使用+
操作符來連接兩個(gè)字符串:
s := "hello,"
m := " world"
a := s + m
fmt.Printf("%s\n", a)
修改字符串也可寫為:
s := "hello"
s = "c" + s[1:] // 字符串雖不能更改,但可進(jìn)行切片操作
fmt.Printf("%s\n", s)
如果要聲明一個(gè)多行的字符串怎么辦?可以通過' '來聲明:
m := `hello
world`
``` 括起的字符串為Raw字符串,即字符串在代碼中的形式就是打印時(shí)的形式,它沒有字符轉(zhuǎn)義,換行也將原樣輸出。例如本例中會(huì)輸出:
hello
world
Go內(nèi)置有一個(gè)error
類型,專門用來處理錯(cuò)誤信息,Go的package
里面還專門有一個(gè)包errors
來處理錯(cuò)誤:
err := errors.New("emit macho dwarf: elf header corrupted")
if err != nil {
fmt.Print(err)
}
下面這張圖來源于Russ Cox Blog中一篇介紹Go數(shù)據(jù)結(jié)構(gòu)的文章,大家可以看到這些基礎(chǔ)類型底層都是分配了一塊內(nèi)存,然后存儲(chǔ)了相應(yīng)的值。
map
也就是Python中字典的概念,它的格式為map[keyType]valueType
我們看下面的代碼,map
的讀取和設(shè)置也類似slice
一樣,通過key
來操作,只是slice
的index
只能是`int`類型,而map
多了很多類型,可以是int
,可以是string
及所有完全定義了==
與!=
操作的類型。
// 聲明一個(gè)key是字符串,值為int的字典,這種方式的聲明需要在使用之前使用make初始化
numbers := make(map[string]int)
// 另一種map的聲明方式
var numbers map[string]int
numbers["one"] = 1 //賦值 numbers["tow"] = 2 //賦值
numbers["three"] = 3 //賦值
fmt.Println("第一個(gè)數(shù)字是: ", numbers["one"]) // 讀取數(shù)據(jù)
fmt.Println("第二個(gè)數(shù)字是: ", numbers["tow"]) // 讀取數(shù)據(jù)
fmt.Println("第三個(gè)數(shù)字是: ", numbers["three"]) // 讀取數(shù)據(jù)
按照上面的輸入,輸出結(jié)果為
這個(gè)map
就像我們平??吹降谋砀褚粯樱筮吜惺?code>key,右邊列是值
使用map過程中需要注意的幾點(diǎn):
map
是無序的,每次打印出來的map
都會(huì)不一樣,它不能通過index
獲取,而必須通過key
獲取map
的長(zhǎng)度是不固定的,也就是和slice
一樣,也是一種引用類型len
函數(shù)同樣適用于map
,返回map
擁有的key
的數(shù)量map
的值可以很方便的修改,通過numbers["one"]=11
可以很容易的把key為one
的字典值改為11
map
和其他基本型別不同,它不是thread-safe,在多個(gè)go-routine存取時(shí),必須使用mutex lock機(jī)制map
的初始化可以通過key:val
的方式初始化值,同時(shí)map
內(nèi)置有判斷是否存在key
的方式
通過delete
刪除map
的元素:
// 初始化一個(gè)字典
rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 }
// map有兩個(gè)返回值,第二個(gè)返回值,如果不存在key,那么ok為false,如果存在ok為true
csharpRating, ok := rating["C#"]
if ok {
fmt.Println("C# is in the map and its rating is ", csharpRating)
} else {
fmt.Println("We have no rating associated with C# in the map")
}
delete(rating, "C") // 刪除key為C的元素
上面說過了,map
也是一種引用類型,如果兩個(gè)map
同時(shí)指向一個(gè)底層,那么一個(gè)改變,另一個(gè)也相應(yīng)的改變:
m := make(map[string]string)
m["Hello"] = "Bonjour"
m1 := m
m1["Hello"] = "Salut" // 現(xiàn)在m["hello"]的值已經(jīng)是Salut了
make
用于內(nèi)建類型(map
、slice
和channel
)的內(nèi)存分配。new
用于各種類型的內(nèi)存分配。
內(nèi)建函數(shù)new
本質(zhì)上說跟其它語(yǔ)言中的同名函數(shù)功能一樣:new(T)
分配了零值填充的T
類型的內(nèi)存空間,并且返回其地址,即一個(gè)*T
類型的值。用Go的術(shù)語(yǔ)說,它返回了一個(gè)指針,指向新分配的類型T
的零值。有一點(diǎn)非常重要:
new
返回指針。
內(nèi)建函數(shù)make(T, args)
與new(T)
有著不同的功能,make只能創(chuàng)建slice
、map
和channel
,并且返回一個(gè)有初始值(非零)的T
類型,而不是*T
。本質(zhì)來講,導(dǎo)致這三個(gè)類型有所不同的原因是指向數(shù)據(jù)結(jié)構(gòu)的引用在使用前必須被初始化。例如,一個(gè)slice
,是一個(gè)包含指向數(shù)據(jù)(內(nèi)部array
)的指針、長(zhǎng)度和容量的三項(xiàng)描述符;在這些項(xiàng)目被初始化之前,slice
為nil
。對(duì)于slice
、map
和channel
來說,make
初始化了內(nèi)部的數(shù)據(jù)結(jié)構(gòu),填充適當(dāng)?shù)闹怠?/p>
make
返回初始化后的(非零)值。
下面這個(gè)圖詳細(xì)的解釋了new
和make
之間的區(qū)別。
更多建議: