Go 基礎(chǔ)

2022-05-13 17:54 更新

這小節(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)直接取代了vartype,這種形式叫做簡(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é)果為

1594777056(1)

Go 常量和一般程序語(yǔ)言不同的是,可以指定相當(dāng)多的小數(shù)位數(shù)(例如200位), 若指定給float32自動(dòng)縮短為32bit,指定給float64自動(dòng)縮短為64bit,詳情參考鏈接

內(nèi)置基礎(chǔ)類型

Boolean

在Go中,布爾值的類型為bool,值是truefalse,默認(rèn)為false。

//示例代碼
var isActive bool  // 全局變量聲明
var enabled, disabled = true, false  // 忽略類型的聲明
func test() {
    var available bool  // 一般聲明
    valid := false      // 簡(jiǎn)短聲明
    available = true    // 賦值操作
}

數(shù)值類型

整數(shù)類型有無符號(hào)和帶符號(hào)兩種。Go同時(shí)支持intuint,這兩種類型的長(zhǎng)度相同,但具體長(zhǎng)度取決于不同編譯器的實(shí)現(xiàn)。Go里面也有直接定義好位數(shù)的類型:runeint8int16int32int64byteuint8uint16uint32,uint64。其中runeint32的別稱,byteuint8的別稱。

需要注意的一點(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ù)的類型有float32float64兩種(沒有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

錯(cuò)誤類型

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)
}

Go數(shù)據(jù)底層的存儲(chǔ)

下面這張圖來源于Russ Cox Blog中一篇介紹Go數(shù)據(jù)結(jié)構(gòu)的文章,大家可以看到這些基礎(chǔ)類型底層都是分配了一塊內(nèi)存,然后存儲(chǔ)了相應(yīng)的值。


map

map也就是Python中字典的概念,它的格式為map[keyType]valueType

我們看下面的代碼,map的讀取和設(shè)置也類似slice一樣,通過key來操作,只是sliceindex只能是`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一樣,也是一種引用類型
  • 內(nèi)置的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、new操作

make用于內(nèi)建類型(mapslice 和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)建slicemapchannel,并且返回一個(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)目被初始化之前,slicenil。對(duì)于slice、mapchannel來說,make初始化了內(nèi)部的數(shù)據(jù)結(jié)構(gòu),填充適當(dāng)?shù)闹怠?/p>

make返回初始化后的(非零)值。

下面這個(gè)圖詳細(xì)的解釋了newmake之間的區(qū)別。

    


       


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)