GO語言基本數據類型


一、整型

Go語言的數值類型分為以下幾種:整數、浮點數、復數,其中每一種都包含了不同大小的數值類型,例如有符號整數包含 int8、int16、int32、int64 等,每種數值類型都決定了對應的大小范圍和是否支持正負符號。本節我們主要介紹一下整數類型。

Go語言同時提供了有符號和無符號的整數類型,其中包括 int8、int16、int32 和 int64 四種大小截然不同的有符號整數類型,分別對應 8、16、32、64 bit(二進制位)大小的有符號整數,與此對應的是 uint8、uint16、uint32 和 uint64 四種無符號整數類型。

此外還有兩種整數類型 int 和 uint,它們分別對應特定 CPU 平台的字長(機器字大小),其中 int 表示有符號整數,應用最為廣泛,uint 表示無符號整數。實際開發中由於編譯器和計算機硬件的不同,int 和 uint 所能表示的整數大小會在 32bit 或 64bit 之間變化。

大多數情況下,我們只需要 int 一種整型即可,它可以用於循環計數器(for 循環中控制循環次數的變量)、數組和切片的索引,以及任何通用目的的整型運算符,通常 int 類型的處理速度也是最快的。

用來表示 Unicode 字符的 rune 類型和 int32 類型是等價的,通常用於表示一個 Unicode 碼點。這兩個名稱可以互換使用。同樣,byte 和 uint8 也是等價類型,byte 類型一般用於強調數值是一個原始的數據而不是一個小的整數。

最后,還有一種無符號的整數類型 uintptr,它沒有指定具體的 bit 大小但是足以容納指針。uintptr 類型只有在底層編程時才需要,特別是Go語言和C語言函數庫或操作系統接口相交互的地方。

盡管在某些特定的運行環境下 int、uint 和 uintptr 的大小可能相等,但是它們依然是不同的類型,比如 int 和 int32,雖然 int 類型的大小也可能是 32 bit,但是在需要把 int 類型當做 int32 類型使用的時候必須顯示的對類型進行轉換,反之亦然。

Go語言中有符號整數采用 2 的補碼形式表示,也就是最高 bit 位用來表示符號位,一個 n-bit 的有符號數的取值范圍是從 -2(n-1) 到 2(n-1)-1。無符號整數的所有 bit 位都用於表示非負數,取值范圍是 0 到 2n-1。例如,int8 類型整數的取值范圍是從 -128 到 127,而 uint8 類型整數的取值范圍是從 0 到 255。

哪些情況下使用 int 和 uint

程序邏輯對整型范圍沒有特殊需求。例如,對象的長度使用內建 len() 函數返回,這個長度可以根據不同平台的字節長度進行變化。實際使用中,切片或 map 的元素數量等都可以用 int 來表示。

反之,在二進制傳輸、讀寫文件的結構描述時,為了保持文件的結構不會受到不同編譯目標平台字節長度的影響,不要使用 int 和 uint。

二、浮點型

Go語言提供了兩種精度的浮點數 float32 和 float64,它們的算術規范由 IEEE754 浮點數國際標准定義,該浮點數規范被所有現代的 CPU 支持。

這些浮點數類型的取值范圍可以從很微小到很巨大。浮點數取值范圍的極限值可以在 math 包中找到:

  • 常量 math.MaxFloat32 表示 float32 能取到的最大數值,大約是 3.4e38;
  • 常量 math.MaxFloat64 表示 float64 能取到的最大數值,大約是 1.8e308;
  • float32 和 float64 能表示的最小值分別為 1.4e-45 和 4.9e-324。


一個 float32 類型的浮點數可以提供大約 6 個十進制數的精度,而 float64 則可以提供約 15 個十進制數的精度,通常應該優先使用 float64 類型,因為 float32 類型的累計計算誤差很容易擴散,並且 float32 能精確表示的正整數並不是很大。

var f float32 = 16777216 // 1 << 24
fmt.Println(f == f+1)    // "true"!

浮點數在聲明的時候可以只寫整數部分或者小數部分,像下面這樣:

const e = .71828 // 0.71828
const f = 1.     // 1

很小或很大的數最好用科學計數法書寫,通過 e 或 E 來指定指數部分:

const Avogadro = 6.02214129e23  // 阿伏伽德羅常數
const Planck   = 6.62606957e-34 // 普朗克常數

用 Printf 函數打印浮點數時可以使用“%f”來控制保留幾位小數

package main
import (
    "fmt"
    "math"
)
func main() {
    fmt.Printf("%f\n", math.Pi)
    fmt.Printf("%.2f\n", math.Pi)
}

運行結果如下所示:

3.141593
3.14

三、復數

Go語言擁有兩種復數類型,分別是 complex64(32 位實數和虛數)類型和 complex128(64 位實數和虛數)類型。

內置的 complex 函數用於構建復數,內建的 real 和 imag 函數分別返回復數的實部和虛部:

var x complex128 = complex(1, 2) // 1+2i
var y complex128 = complex(3, 4) // 3+4i
fmt.Println(x*y)                 // "(-5+10i)"
fmt.Println(real(x*y))           // "-5"
fmt.Println(imag(x*y))           // "10"

復數使用 re+imi 來表示,其中 re 代表實數部分,im 代表虛數部分,i 代表根號負 1。如果一個浮點數或一個十進制整數后面跟着一個 i,例如 3.141592i 或 2i,它將構成一個復數的虛部,而復數的實部是 0:

fmt.Println(1i * 1i) // "(-1+0i)", i^2 = -1

如果大家對復數的運算法則不是很了解,可以點擊這里《復數運算法則》,其中詳細的講解了復數的加減乘除操作。

復數支持和其它數值類型一樣的運算,當你使用等號 == 或者不等號 != 對復數進行比較運算時,注意對精確度的把握。cmath 包中包含了一些操作復數的公共方法,如果你對內存的要求不是特別高,最好使用 complex128 作為計算類型,因為相關函數都使用這個類型的參數。

四、布爾類型

一個布爾類型的值只有兩種:true 或 false。if 和 for 語句的條件部分都是布爾類型的值,並且==<等比較操作也會產生布爾型的值。

一元操作符!對應邏輯非操作,因此!true的值為 false,更復雜一些的寫法是(!true==false) ==true,實際開發中我們應盡量采用比較簡潔的布爾表達式,就像用 x 來表示x==true

var aVar = 10
aVar == 5  // false
aVar == 10 // true
aVar != 5  // true
aVar != 10 // false

Go語言對於值之間的比較有非常嚴格的限制,只有兩個相同類型的值才可以進行比較,如果值的類型是接口(interface),那么它們也必須都實現了相同的接口。如果其中一個值是常量,那么另外一個值可以不是常量,但是類型必須和該常量類型相同。如果以上條件都不滿足,則必須將其中一個值的類型轉換為和另外一個值的類型相同之后才可以進行比較。

布爾值可以和 &&(AND)和 ||(OR)操作符結合,並且有短路行為,如果運算符左邊的值已經可以確定整個布爾表達式的值,那么運算符右邊的值將不再被求值,因此下面的表達式總是安全的:

s != "" && s[0] == 'x'

其中 s[0] 操作如果應用於空字符串將會導致 panic 異常。

因為&&的優先級比||高(&& 對應邏輯乘法,|| 對應邏輯加法,乘法比加法優先級要高),所以下面的布爾表達式可以不加小括號:

if 'a' <= c && c <= 'z' ||
    'A' <= c && c <= 'Z' ||
    '0' <= c && c <= '9' {
    // ...ASCII字母或數字...
}

布爾值並不會隱式轉換為數字值 0 或 1,反之亦然,必須使用 if 語句顯式的進行轉換:

i := 0
if b {
    i = 1
}

如果需要經常做類似的轉換,可以將轉換的代碼封裝成一個函數,如下所示:

// 如果b為真,btoi返回1;如果為假,btoi返回0
func btoi(b bool) int {
    if b {
        return 1
    }
    return 0
}

數字到布爾型的逆轉換非常簡單, 不過為了保持對稱, 我們也可以封裝一個函數:

// itob報告是否為非零。
func itob(i int) bool { return i != 0 }

Go語言中不允許將整型強制轉換為布爾型,代碼如下:

var n bool
fmt.Println(int(n) * 2)

編譯錯誤,輸出如下:

cannot convert n (type bool) to type int

布爾型無法參與數值運算,也無法與其他類型進行轉換。

五、字符串

一個字符串是一個不可改變的字節序列,字符串可以包含任意的數據,但是通常是用來包含可讀的文本,字符串是 UTF-8 字符的一個序列(當字符為 ASCII 碼表上的字符時則占用 1 個字節,其它字符根據需要占用 2-4 個字節)。

UTF-8 是一種被廣泛使用的編碼格式,是文本文件的標准編碼,其中包括 XML 和 JSON 在內也都使用該編碼。由於該編碼對占用字節長度的不定性,在Go語言中字符串也可能根據需要占用 1 至 4 個字節,這與其它編程語言如 C++Java 或者 Python 不同(Java 始終使用 2 個字節)。Go語言這樣做不僅減少了內存和硬盤空間占用,同時也不用像其它語言那樣需要對使用 UTF-8 字符集的文本進行編碼和解碼。

字符串是一種值類型,且值不可變,即創建某個文本后將無法再次修改這個文本的內容,更深入地講,字符串是字節的定長數組。

定義字符串

可以使用雙引號""來定義字符串,字符串中可以使用轉義字符來實現換行、縮進等效果,常用的轉義字符包括:

  • \n:換行符
  • \r:回車符
  • \t:tab 鍵
  • \u 或 \U:Unicode 字符
  • \\:反斜杠自身
package main
import (
    "fmt"
)
func main() {
    var str = "C語言中文網\nGo語言教程"
    fmt.Println(str)
}

運行結果為:

C語言中文網
Go語言教程

一般的比較運算符(==、!=、<、<=、>=、>)是通過在內存中按字節比較來實現字符串比較的,因此比較的結果是字符串自然編碼的順序。字符串所占的字節長度可以通過函數 len() 來獲取,例如 len(str)。

字符串的內容(純字節)可以通過標准索引法來獲取,在方括號[]內寫入索引,索引從 0 開始計數:

  • 字符串 str 的第 1 個字節:str[0]
  • 第 i 個字節:str[i - 1]
  • 最后 1 個字節:str[len(str)-1]


需要注意的是,這種轉換方案只對純 ASCII 碼的字符串有效。

注意:獲取字符串中某個字節的地址屬於非法行為,例如 &str[i]。

字符串拼接符“+”

兩個字符串 s1 和 s2 可以通過 s := s1 + s2 拼接在一起。將 s2 追加到 s1 尾部並生成一個新的字符串 s。

可以通過下面的方式來對代碼中多行的字符串進行拼接:

str := "Beginning of the string " +
"second part of the string"

因為編譯器會在行尾自動補全分號,所以拼接字符串用的加號“+”必須放在第一行末尾。

也可以使用“+=”來對字符串進行拼接:

s := "hel" + "lo,"
s += "world!"
fmt.Println(s) //輸出 “hello, world!”

字符串實現基於 UTF-8 編碼

Go語言中字符串的內部實現使用 UTF-8 編碼,通過 rune 類型,可以方便地對每個 UTF-8 字符進行訪問。當然,Go語言也支持按照傳統的 ASCII 碼方式逐字符進行訪問。

關於字符串的 UTF-8 字符訪問的詳細方法,后面的章節將會詳細介紹。

定義多行字符串

在Go語言中,使用雙引號書寫字符串的方式是字符串常見表達方式之一,被稱為字符串字面量(string literal),這種雙引號字面量不能跨行,如果想要在源碼中嵌入一個多行字符串時,就必須使用`反引號,代碼如下:

const str = `第一行
第二行
第三行
\r\n
`
fmt.Println(str)

代碼運行結果:

第一行
第二行
第三行
\r\n

反引號`,是鍵盤上 1 鍵左邊的鍵,兩個反引號間的字符串將被原樣賦值到 str 變量中。

在這種方式下,反引號間換行將被作為字符串中的換行,但是所有的轉義字符均無效,文本將會原樣輸出。

多行字符串一般用於內嵌源碼和內嵌數據等,代碼如下:

const codeTemplate = `// Generated by github.com/davyxu/cellnet/
protoc-gen-msg
// DO NOT EDIT!{{range .Protos}}
// Source: {{.Name}}{{end}}
package {{.PackageName}}
{{if gt .TotalMessages 0}}
import (
    "github.com/davyxu/cellnet"
    "reflect"
    _ "github.com/davyxu/cellnet/codec/pb"
)
{{end}}
func init() {
    {{range .Protos}}
    // {{.Name}}{{range .Messages}}
    cellnet.RegisterMessageMeta("pb","{{.FullName}}", reflect.TypeOf((*{{.Name}})(nil)).Elem(), {{.MsgID}})    {{end}}
    {{end}}
}
`

這段代碼只定義了一個常量 codeTemplate,類型為字符串,使用`定義,字符串的內容為一段代碼生成中使用到的 Go 源碼格式。

`間的所有代碼均不會被編譯器識別,而只是作為字符串的一部分。

字符串類型在業務中的應用可以說是最廣泛的,讀者需要詳細了解字符串的常見用法,請猛擊下面的文章:

六、字符類型(byte和rune)

字符串中的每一個元素叫做“字符”,在遍歷或者單個獲取字符串元素時可以獲得字符。

Go語言的字符有以下兩種:

  • 一種是 uint8 類型,或者叫 byte 型,代表了 ASCII 碼的一個字符。
  • 另一種是 rune 類型,代表一個 UTF-8 字符,當需要處理中文、日文或者其他復合字符時,則需要用到 rune 類型。rune 類型等價於 int32 類型。


byte 類型是 uint8 的別名,對於只占用 1 個字節的傳統 ASCII 編碼的字符來說,完全沒有問題,例如 var ch byte = 'A',字符使用單引號括起來。

在 ASCII 碼表中,A 的值是 65,使用 16 進制表示則為 41,所以下面的寫法是等效的:

var ch byte = 65 或 var ch byte = '\x41'      //(\x 總是緊跟着長度為 2 的 16 進制數)

另外一種可能的寫法是\后面緊跟着長度為 3 的八進制數,例如 \377。

Go語言同樣支持 Unicode(UTF-8),因此字符同樣稱為 Unicode 代碼點或者 runes,並在內存中使用 int 來表示。在文檔中,一般使用格式 U+hhhh 來表示,其中 h 表示一個 16 進制數。

在書寫 Unicode 字符時,需要在 16 進制數之前加上前綴\u或者\U。因為 Unicode 至少占用 2 個字節,所以我們使用 int16 或者 int 類型來表示。如果需要使用到 4 字節,則使用\u前綴,如果需要使用到 8 個字節,則使用\U前綴。

var ch int = '\u0041'
var ch2 int = '\u03B2'
var ch3 int = '\U00101234'
fmt.Printf("%d - %d - %d\n", ch, ch2, ch3) // integer
fmt.Printf("%c - %c - %c\n", ch, ch2, ch3) // character
fmt.Printf("%X - %X - %X\n", ch, ch2, ch3) // UTF-8 bytes
fmt.Printf("%U - %U - %U", ch, ch2, ch3)   // UTF-8 code point

輸出:

65 - 946 - 1053236
A - β - r
41 - 3B2 - 101234
U+0041 - U+03B2 - U+101234

格式化說明符%c用於表示字符,當和字符配合使用時,%v%d會輸出用於表示該字符的整數,%U輸出格式為 U+hhhh 的字符串。

Unicode 包中內置了一些用於測試字符的函數,這些函數的返回值都是一個布爾值,如下所示(其中 ch 代表字符):

    • 判斷是否為字母:unicode.IsLetter(ch)
    • 判斷是否為數字:unicode.IsDigit(ch)
    • 判斷是否為空白符號:unicode.IsSpace(ch)

UTF-8 和 Unicode 有何區別?

Unicode 與 ASCII 類似,都是一種字符集。

字符集為每個字符分配一個唯一的 ID,我們使用到的所有字符在 Unicode 字符集中都有一個唯一的 ID,例如上面例子中的 a 在 Unicode 與 ASCII 中的編碼都是 97。漢字“你”在 Unicode 中的編碼為 20320,在不同國家的字符集中,字符所對應的 ID 也會不同。而無論任何情況下,Unicode 中的字符的 ID 都是不會變化的。

UTF-8 是編碼規則,將 Unicode 中字符的 ID 以某種方式進行編碼,UTF-8 的是一種變長編碼規則,從 1 到 4 個字節不等。編碼規則如下:

  • 0xxxxxx 表示文字符號 0~127,兼容 ASCII 字符集。
  • 從 128 到 0x10ffff 表示其他字符。


根據這個規則,拉丁文語系的字符編碼一般情況下每個字符占用一個字節,而中文每個字符占用 3 個字節。

廣義的 Unicode 指的是一個標准,它定義了字符集及編碼規則,即 Unicode 字符集和 UTF-8、UTF-16 編碼等。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM