Go 基礎(chǔ)

2022-05-13 17:54 更新

這小節(jié)我們將要介紹如何定義變量、常量、Go內(nèi)置類型以及Go程序設(shè)計中的一些技巧。

定義變量

Go語言里面定義變量有多種方式。

使用var關(guān)鍵字是Go最基本的定義變量方式,與C語言不同的是Go把變量類型放在變量名后面:

//定義一個名稱為“variableName”,類型為"type"的變量
var variableName type

定義多個變量

//定義三個類型都是“type”的變量
var vname1, vname2, vname3 type

定義變量并初始化值

//初始化“variableName”的變量為“value”值,類型是“type”
var variableName type = value

同時初始化多個變量

/*
    定義三個類型都是"type"的變量,并且分別初始化為相應(yīng)的值
    vname1為v1,vname2為v2,vname3為v3
*/
var vname1, vname2, vname3 type= v1, v2, v3

你是不是覺得上面這樣的定義有點繁瑣?沒關(guān)系,因為Go語言的設(shè)計者也發(fā)現(xiàn)了,有一種寫法可以讓它變得簡單一點。我們可以直接忽略類型聲明,那么上面的代碼變成這樣了:

/*
    定義三個變量,它們分別初始化為相應(yīng)的值
    vname1為v1,vname2為v2,vname3為v3
    然后Go會根據(jù)其相應(yīng)值的類型來幫你初始化它們
*/
var vname1, vname2, vname3 = v1, v2, v3

你覺得上面的還是有些繁瑣?好吧,我也覺得。讓我們繼續(xù)簡化:

/*
    定義三個變量,它們分別初始化為相應(yīng)的值
    vname1為v1,vname2為v2,vname3為v3
    編譯器會根據(jù)初始化的值自動推導(dǎo)出相應(yīng)的類型
*/
vname1, vname2, vname3 := v1, v2, v3

現(xiàn)在是不是看上去非常簡潔了?:=這個符號直接取代了vartype,這種形式叫做簡短聲明。不過它有一個限制,那就是它只能用在函數(shù)內(nèi)部;在函數(shù)外部使用則會無法編譯通過,所以一般用var方式來定義全局變量。

_(下劃線)是個特殊的變量名,任何賦予它的值都會被丟棄。在這個例子中,我們將值35賦予b,并同時丟棄34

_, b := 34, 35

Go對于已聲明但未使用的變量會在編譯階段報錯,比如下面的代碼就會產(chǎn)生一個錯誤:聲明了i但未使用。

package main

func main() {
    var i int
}

常量

所謂常量,也就是在程序編譯階段就確定下來的值,而程序在運行時無法改變該值。在Go程序中,常量可定義為數(shù)值、布爾值或字符串等類型。

它的語法如下:

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 常量和一般程序語言不同的是,可以指定相當(dāng)多的小數(shù)位數(shù)(例如200位), 若指定給float32自動縮短為32bit,指定給float64自動縮短為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      // 簡短聲明
    available = true    // 賦值操作
}

數(shù)值類型

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

需要注意的一點是,這些類型的變量之間不允許互相賦值或操作,不然會在編譯時引起編譯器報錯。

如下的代碼會產(chǎn)生錯誤:invalid operation: a + b (mismatched types int8 and int32)

var a int8

var b int32

c:=a + b

另外,盡管int的長度是32 bit, 但int 與 int32并不可以互用。

浮點數(shù)的類型有float32float64兩種(沒有float類型),默認(rèn)是float64。

這就是全部嗎?No!Go還支持復(fù)數(shù)。它的默認(rèn)類型是complex128(64位實數(shù)+64位虛數(shù))。如果需要小一些的,也有complex64(32位實數(shù)+32位虛數(shù))。復(fù)數(shù)的形式為RE + IMi,其中RE是實數(shù)部分,IM是虛數(shù)部分,而最后的i是虛數(shù)單位。下面是一個使用復(fù)數(shù)的例子:

var c complex64 = 5+5i
//output: (5+5i)
fmt.Printf("Value is: %v", c)

字符串

我們在上一節(jié)中講過,Go中的字符串都是采用UTF-8字符集編碼。字符串是用一對雙引號("")或反引號(`)括起來定義,它的類型是string。

//示例代碼
var frenchHello string  // 聲明變量為字符串的一般方法
var emptyString string = ""  // 聲明了一個字符串變量,初始化為空字符串
func test() {
    no, yes, maybe := "no", "yes", "maybe"  // 簡短聲明,同時聲明多個變量
    japaneseHello := "Konichiwa"  // 同上
    frenchHello = "Bonjour"  // 常規(guī)賦值
}
        

在Go中字符串是不可變的,例如下面的代碼編譯時會報錯:cannot assign to s[0]

var s string = "hello"
s[0] = 'c'

但如果真的想要修改怎么辦呢?下面的代碼可以實現(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中可以使用+操作符來連接兩個字符串:

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)

如果要聲明一個多行的字符串怎么辦?可以通過' '來聲明:

m := `hello
    world`

``` 括起的字符串為Raw字符串,即字符串在代碼中的形式就是打印時的形式,它沒有字符轉(zhuǎn)義,換行也將原樣輸出。例如本例中會輸出:

hello
    world

錯誤類型

Go內(nèi)置有一個error類型,專門用來處理錯誤信息,Go的package里面還專門有一個包errors來處理錯誤:

err := errors.New("emit macho dwarf: elf header corrupted")
if err != nil {
    fmt.Print(err)
}

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

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


map

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

我們看下面的代碼,map的讀取和設(shè)置也類似slice一樣,通過key來操作,只是sliceindex只能是`int`類型,而map多了很多類型,可以是int,可以是string及所有完全定義了==!=操作的類型。

// 聲明一個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("第一個數(shù)字是: ", numbers["one"]) // 讀取數(shù)據(jù)

    fmt.Println("第二個數(shù)字是: ", numbers["tow"]) // 讀取數(shù)據(jù)

    fmt.Println("第三個數(shù)字是: ", numbers["three"]) // 讀取數(shù)據(jù)

按照上面的輸入,輸出結(jié)果為


這個map就像我們平??吹降谋砀褚粯?,左邊列是key,右邊列是值

使用map過程中需要注意的幾點:

  • map是無序的,每次打印出來的map都會不一樣,它不能通過index獲取,而必須通過key獲取
  • map的長度是不固定的,也就是和slice一樣,也是一種引用類型
  • 內(nèi)置的len函數(shù)同樣適用于map,返回map擁有的key的數(shù)量
  • map的值可以很方便的修改,通過numbers["one"]=11可以很容易的把key為one的字典值改為11
  • map和其他基本型別不同,它不是thread-safe,在多個go-routine存取時,必須使用mutex lock機(jī)制

map的初始化可以通過key:val的方式初始化值,同時map內(nèi)置有判斷是否存在key的方式

通過delete刪除map的元素:

// 初始化一個字典
rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 }
// map有兩個返回值,第二個返回值,如果不存在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也是一種引用類型,如果兩個map同時指向一個底層,那么一個改變,另一個也相應(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)建類型(map、slice 和channel)的內(nèi)存分配。new用于各種類型的內(nèi)存分配。

內(nèi)建函數(shù)new本質(zhì)上說跟其它語言中的同名函數(shù)功能一樣:new(T)分配了零值填充的T類型的內(nèi)存空間,并且返回其地址,即一個*T類型的值。用Go的術(shù)語說,它返回了一個指針,指向新分配的類型T的零值。有一點非常重要:

new返回指針。

內(nèi)建函數(shù)make(T, args)new(T)有著不同的功能,make只能創(chuàng)建slice、mapchannel,并且返回一個有初始值(非零)的T類型,而不是*T。本質(zhì)來講,導(dǎo)致這三個類型有所不同的原因是指向數(shù)據(jù)結(jié)構(gòu)的引用在使用前必須被初始化。例如,一個slice,是一個包含指向數(shù)據(jù)(內(nèi)部array)的指針、長度和容量的三項描述符;在這些項目被初始化之前,slicenil。對于slice、mapchannel來說,make初始化了內(nèi)部的數(shù)據(jù)結(jié)構(gòu),填充適當(dāng)?shù)闹怠?/p>

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

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

    


       


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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號