了解Go中的數據類型

簡介

資料類型指定在編寫程式時特定變數將存儲的值的種類。資料類型還決定了可以對資料執行的操作。

在本文中,我們將介紹Go語言中重要的資料類型。這不是對資料類型的全面調查,但將幫助您熟悉Go中可用的選項。理解一些基本資料類型將使您能夠編寫更清晰且高效執行的代碼。

背景

思考資料類型的一種方式是考慮我們在現實世界中使用的不同類型的數據。現實世界中數據的一個例子是數字:例如,我們可能使用整數(0, 1, 2, …)、整數(…, -1, 0, 1, …)和無理數(π)。

通常,在數學中,我們可以結合不同類型的數字,並得到某種答案。例如,我們可能想要將5加到π上:

5 + π

我們可以將方程式作為答案,以考慮無理數,或者將π四捨五入到一個縮短的小數位數,然後將數字相加:

5 + π = 5 + 3.14 = 8.14 

然而,如果我们开始尝试用另一种数据类型(如文字)来评估数字,事情就开始变得不那么合理了。我们该如何解以下方程呢?

shark + 8

对于计算机而言,每种数据类型都相当不同——就像文字和数字。因此,我们必须谨慎对待如何使用不同的数据类型来赋值以及如何通过操作来处理它们。

整數

如同在数学中,整数在计算机编程中是正数、负数或0的完整数字(…, -1, 0, 1, …)。在Go语言中,整数被称为int。与其他编程语言一样,你不应在四位或更多位数的数字中使用逗号,因此当你在程序中写1,000时,应写作1000

我们可以用一种简单的方式打印出一个整数,如下所示:

fmt.Println(-459)
Output
-459

或者,我们可以声明一个变量,在这种情况下,它是我们使用或操作的数字的符号,如下所示:

var absoluteZero int = -459
fmt.Println(absoluteZero)
Output
-459

我们也可以在Go中用整数进行数学运算。在下面的代码块中,我们将使用:=赋值操作符来声明并实例化变量sum

sum := 116 - 68
fmt.Println(sum)
Output
48

正如输出所示,数学操作符-116中减去了整数68,结果为48。你将在声明变量的数据类型部分了解更多关于变量声明的内容。

整數在 Go 程式中有多種用途。隨著你繼續學習 Go,你將有許多機會使用整數並建立在此數據類型上的知識。

浮點數

浮點數float用於表示不能表示為整數的實數。實數包括所有有理數和無理數,因此,浮點數可以包含小數部分,例如 9.0 或 -116.42。對於在 Go 程式中思考浮點數的目的,它是一個包含小數點的數字。

就像我們對整數所做的那樣,我們可以簡單地打印出一個浮點數,如下所示:

fmt.Println(-459.67)
Output
-459.67

我們也可以聲明一個代表浮點數的變量,如下所示:

absoluteZero := -459.67
fmt.Println(absoluteZero)
Output
-459.67

就像整數一樣,我們也可以在 Go 中用浮點數進行數學運算:

var sum = 564.0 + 365.24
fmt.Println(sum)
Output
929.24

對於整數和浮點數,重要的是要記住 3 ≠ 3.0,因為 3 指的是整數,而 3.0 指的是浮點數。

數值類型的大小

除了整數和浮點數的區別外,Go 還有兩種數值數據類型,它們根據大小的靜態或動態性質來區分。第一種類型是架構無關類型,這意味著無論代碼運行在哪種機器上,數據的位大小都不會改變。

如今大多數系統架構要麼是 32 位,要麼是 64 位。例如,你可能正在為一台現代 Windows 筆記本電腦開發,其操作系統運行在 64 位架構上。然而,如果你正在為健身手錶這樣的設備開發,你可能會使用 32 位架構。如果你使用像 int32 這樣的架構無關類型,無論你編譯的目標架構是什麼,該類型都將具有固定大小。

第二種類型是實現特定類型。在這種類型中,位大小可以根據程序構建的架構而變化。例如,如果我們使用 int 類型,當 Go 編譯為 32 位架構時,數據類型的大小將是 32 位。如果程序編譯為 64 位架構,變量的大小將是 64 位。

除了數據類型具有不同大小外,整數類型還有兩種基本類型:有符號無符號。一個 int8 是有符號整數,其值範圍從 -128 到 127。一個 uint8 是無符號整數,其值只能是 0 到 255。

範圍是根據位元大小來定的。對於二進位資料,8位元可以表示總共256個不同的值。因為int型別需要支援正負值,所以8位元整數(int8)的範圍是-128到127,總共256個獨特的可能值。

Go有以下與架構無關的整數型別:

uint8       unsigned  8-bit integers (0 to 255)
uint16      unsigned 16-bit integers (0 to 65535)
uint32      unsigned 32-bit integers (0 to 4294967295)
uint64      unsigned 64-bit integers (0 to 18446744073709551615)
int8        signed  8-bit integers (-128 to 127)
int16       signed 16-bit integers (-32768 to 32767)
int32       signed 32-bit integers (-2147483648 to 2147483647)
int64       signed 64-bit integers (-9223372036854775808 to 9223372036854775807)

浮點數和複數也有不同的尺寸:

float32     IEEE-754 32-bit floating-point numbers
float64     IEEE-754 64-bit floating-point numbers
complex64   complex numbers with float32 real and imaginary parts
complex128  complex numbers with float64 real and imaginary parts

還有幾個別名數字型別,它們為特定資料型別賦予了有用的名稱:

byte        alias for uint8
rune        alias for int32

byte別名的目的是在您的程式使用位元組作為字串元素中的常見計算度量時,使其明確,而不是與位元組資料度量無關的小整數。儘管byteuint8在編譯後是相同的,byte常用來表示數字形式的字元資料,而uint8則是您程式中的一個數字。

rune別名略有不同。byteuint8是完全相同的資料,而rune可以是一個位元組或四個位元組,這個範圍由int32決定。rune用來表示Unicode字元,而只有ASCII字元可以僅由int32資料型別表示。

此外,Go還有以下實作特定的型別:

uint     unsigned, either 32 or 64 bits
int      signed, either 32 or 64 bits
uintptr  unsigned integer large enough to store the uninterpreted bits of a pointer value 

實作特定的型別將由編譯程式的架構來定義其大小。

選擇數字數據類型

通常,選擇正確的大小更多地與您所編程的目標架構的性能有關,而不是您正在處理的數據的大小。然而,無需了解對您的程序性能的具體影響,您可以遵循一些基本指南來開始。

如本文前面所討論的,存在架構無關的類型和實現特定的類型。對於整數數據,在 Go 中通常使用實現類型如 intuint 而不是 int64uint64。這通常會使您的目標架構處理速度最快。例如,如果您使用 int64 並編譯到 32 位架構,處理這些值將至少需要兩倍時間,因為需要額外的 CPU 周期來在架構之間移動數據。如果您改用 int,程序將在 32 位架構上定義為 32 位大小,處理速度將顯著加快。

如果您知道不會超過特定的大小範圍,那麼選擇架構無關的類型可以同時提高速度和減少內存使用。例如,如果您知道您的數據不會超過 100,並且只會是正數,那麼選擇 uint8 將使您的程序更高效,因為它將需要更少的內存。

既然我們已經探討了一些數值數據類型的可能範圍,讓我們來看看如果我們的程序超出了這些範圍會發生什麼。

溢出 vs. 回繞

Go 語言在嘗試存儲超出數據類型設計存儲範圍的值時,有可能會導致數字溢出回繞,具體取決於該值是在編譯時計算還是在運行時計算。編譯時錯誤發生在程序在嘗試構建程序時發現錯誤的情況。運行時錯誤則是在程序編譯完成後,在實際執行過程中發生的。

在以下示例中,我們將 `maxUint32` 設置為其最大值:

package main

import "fmt"

func main() {
	var maxUint32 uint32 = 4294967295 // uint32 的最大值
	fmt.Println(maxUint32)
}

它將編譯並運行,結果如下:

Output
4294967295

如果我們在運行時將 `1` 加到該值上,它將回繞至 `0`:

Output
0

另一方面,讓我們修改程序,在編譯前賦值時將變量加 `1`:

package main

import "fmt"

func main() {
	var maxUint32 uint32 = 4294967295 + 1
	fmt.Println(maxUint32)

}

在編譯時,如果編譯器能夠確定某個值對於指定的數據類型來說過大,它將拋出一個 `overflow` 錯誤。這意味著計算出的值對於您指定的數據類型來說過大。

由於編譯器能夠判斷該值將會溢出,因此它現在會拋出一個錯誤:

Output
prog.go:6:36: constant 4294967296 overflows uint32

理解資料的界限將有助於您避免未來程式中可能出現的錯誤。

現在我們已經討論了數值類型,讓我們看看如何儲存布林值。

布林值

布林資料類型可以是兩個值之一,即truefalse,在宣告為資料類型時定義為bool。布林值用於表示與數學的邏輯分支相關的真值,這些真值指導了電腦科學中的演算法。

truefalse將始終分別使用小寫的tf,因為它們在Go語言中是預先宣告的標識符。

數學中的許多運算給出的答案都會評估為真或假:

  • 大於
    • 500 > 100 true
    • 1 > 5 false
  • 小於
    • 200 < 400 true
    • 4 < 2 false
  • 等於
    • 5 = 5 true
    • 500 = 400 false

與數字一樣,我們可以將布林值儲存在變數中:

myBool := 5 > 8

我們可以通過調用 fmt.Println() 函數來打印布爾值:

fmt.Println(myBool)

由於 5 不大於 8,我們將收到以下輸出:

Output
false

隨著你在 Go 中編寫更多程序,你將更加熟悉布爾值的工作原理以及不同函數和操作評估為 truefalse 如何改變程序的流程。

字符串

字符串是由一個或多個字符(字母、數字、符號)組成的序列,可以是常量或變量。字符串在 Go 中存在於反引號 ` 或雙引號 " 內,並且根據你使用的引號具有不同的特性。

如果你使用反引號,你創建的是一個 原始 字符串字面量。如果你使用雙引號,你創建的是一個 解釋型 字符串字面量。

原始字符串字面量

原始字符串字面量是位於反引號之間的字符序列,通常稱為反勾號。在引號內,任何字符都將顯示為反引號之間的樣子,除了反引號字符本身。

a := `Say "hello" to Go!`
fmt.Println(a)
Output
Say "hello" to Go!

通常,反斜杠用於表示字符串中的特殊字符。例如,在解釋型字符串中,\n代表字符串中的一個新行。然而,反斜杠在原始字符串字面量中沒有特殊含義:

a := `Say "hello" to Go!\n`
fmt.Println(a)

由於反斜杠在字符串字面量中沒有特殊含義,它實際上會輸出\n的值,而不是創建一個新行:

Output
Say "hello" to Go!\n

原始字符串字面量也可以用於創建多行字符串:

a := `This string is on 
multiple lines
within a single back 
quote on either side.`
fmt.Println(a)
Output
This string is on multiple lines within a single back quote on either side.

在前面的代碼塊中,新行從輸入到輸出被逐字傳遞。

解釋型字符串字面量

解釋型字符串字面量是雙引號之間的字符序列,如"bar"。在引號內,除了換行符和未轉義的雙引號外,任何字符都可以出現。要在解釋型字符串中顯示雙引號,可以使用反斜杠作為轉義字符,如下所示:

a := "Say \"hello\" to Go!"
fmt.Println(a)
Output
Say "hello" to Go!

您幾乎總是使用解釋型字符串字面量,因為它們允許在內部使用轉義字符。有關字符串操作的更多信息,請查看Go中處理字符串的介紹

含UTF-8字元的字串

UTF-8是一種編碼方案,用於將可變寬度的字元編碼為一到四個字節。Go語言原生支援UTF-8字元,無需特殊設置、庫或包。例如,羅馬字元如字母A可以用ASCII值如數字65來表示。然而,對於特殊字元如國際字元,則需要使用UTF-8。Go使用rune別名類型來處理UTF-8數據。

a := "Hello, 世界"

你可以在Go中使用range關鍵字在for循環中遍歷任何字串,包括UTF-8字串。for循環和range將在本系列後續部分更深入地介紹;目前,重要的是要知道我們可以使用這種方法來計算給定字串中的字節數:

package main

import "fmt"

func main() {
	a := "Hello, 世界"
	for i, c := range a {
		fmt.Printf("%d: %s\n", i, string(c))
	}
	fmt.Println("length of 'Hello, 世界': ", len(a))
}

在上面的代碼塊中,我們聲明了變量a並將值Hello, 世界賦給它。賦值的文本中包含UTF-8字元。

然後,我們使用了一個標準的for循環以及range關鍵字。在Go中,range關鍵字將逐個遍歷字串,返回每個字元及其在字串中的字節索引位置。

使用 fmt.Printf 函數,我們提供了一個格式字符串 %d: %s\n%d 是數字(在此情況下為整數)的打印動詞,而 %s 是字符串的打印動詞。然後,我們提供了 i 的值,這是 for 循環的當前索引,以及 c,這是 for 循環中的當前字符。

最後,我們通過使用內置的 len 函數打印了變量 a 的整個長度。

早些時候,我們提到 rune 是 int32 的別名,可以由一到四個字節組成。字符 需要三個字節來定義,當在 UTF-8 字符串中進行範圍遍歷時,索引會相應移動。這就是為什麼打印出來的 i 不是連續的。

Output
0: H 1: e 2: l 3: l 4: o 5: , 6: 7: 世 10: 界 length of 'Hello, 世界': 13

如你所見,長度比遍歷字符串所需的次數要長。

你並不總是處理 UTF-8 字符串,但當你處理時,你現在會明白為什麼它們是 rune 而不是單個 int32

為變量聲明數據類型

現在你已經了解了不同的基本數據類型,我們將介紹如何在 Go 中為變量分配這些類型。

在 Go 中,我們可以使用關鍵字 var 後跟變量名和所需數據類型來定義變量。

在以下範例中,我們將宣告一個名為 `pi` 的變數,其類型為 `float64`。

首先宣告的是關鍵字 `var`:

var pi float64

接著是我們變數的名稱 `pi`:

var pi float64

最後是資料類型 `float64`:

var pi float64

我們也可以選擇性地指定一個初始值,例如 `3.14`:

var pi float64 = 3.14

Go 是一種 靜態類型 的程式語言。靜態類型意味著程式中的每個語句在編譯時都會被檢查。這也意味著資料類型與變數綁定,而在動態連結的語言中,資料類型與值綁定。

例如,在 Go 中,類型在宣告變數時宣告:

var pi float64 = 3.14
var week int = 7

這些變數如果以不同方式宣告,可以是不同的資料類型。

這與 PHP 等語言不同,後者的資料類型與值相關聯:

$s = "sammy";         // $s 自動成為字串
$s = 123;             // $s 自動成為整數

在前述程式碼塊中,第一個 `$s` 是字串,因為它被賦予了值 `"sammy"`,而第二個是整數,因為它有值 `123`。

接下來,讓我們看看更複雜的資料類型,如陣列。

陣列

陣列是一個有序的元素序列。陣列的容量在創建時被定義。一旦陣列分配了其大小,該大小便無法再更改。由於陣列的大小是靜態的,這意味著它只分配一次內存。這使得陣列在使用上有些僵硬,但能提高程序的性能。因此,陣列通常在優化程序時使用。切片,接下來會介紹,更具靈活性,並構成了你在其他語言中所認為的陣列。

陣列是通過宣告陣列的大小,然後是數據類型,並在花括號 { } 中定義值來定義的。

一個字符串陣列看起來像這樣:

[3]string{"blue coral", "staghorn coral", "pillar coral"}

我們可以將一個陣列存儲在一個變量中並打印出來:

coral := [3]string{"blue coral", "staghorn coral", "pillar coral"}
fmt.Println(coral)
Output
[blue coral staghorn coral pillar coral]

如前所述,切片與陣列相似,但靈活性更大。讓我們來看看這種可變的數據類型。

切片

切片是一個可以改變長度的有序元素序列。切片可以動態增加其大小。當你向切片添加新項目時,如果切片沒有足夠的內存來存儲新項目,它將根據需要從系統請求更多內存。由於切片可以擴展以在需要時添加更多元素,因此它們比陣列更常用。

切片是通過在數據類型前加上開合方括號 `[]` 並在花括號 `{ } ` 之間包含值來定義的。

一個整數切片看起來像這樣:

[]int{-3, -2, -1, 0, 1, 2, 3}

一個浮點數切片看起來像這樣:

[]float64{3.14, 9.23, 111.11, 312.12, 1.05}

一個字符串切片看起來像這樣:

[]string{"shark", "cuttlefish", "squid", "mantis shrimp"}

讓我們將字符串切片定義為 `seaCreatures`:

seaCreatures := []string{"shark", "cuttlefish", "squid", "mantis shrimp"}

我們可以通過調用變量來打印它們:

fmt.Println(seaCreatures)

輸出將與我們創建的列表完全一樣:

Output
[shark cuttlefish squid mantis shrimp]

我們可以使用 `append` 關鍵字向切片添加項目。以下命令將字符串值 `seahorse` 添加到切片中:

seaCreatures = append(seaCreatures, "seahorse")

您可以通過打印出來驗證它是否已添加:

fmt.Println(seaCreatures)
Output
[shark cuttlefish squid mantis shrimp seahorse]

如您所見,如果您需要管理未知大小的元素,切片將比數組更加靈活。

映射

映射是 Go 的內置哈希或字典類型。映射使用作為一對來存儲數據。這在編程中用於通過索引(在這種情況下是鍵)快速查找值非常有用。例如,您可能希望保留一個用戶映射,按用戶 ID 索引。鍵將是用戶 ID,而用戶對象將是值。映射是通過使用關鍵字 `map` 後跟方括號 `[ ]` 中的鍵數據類型,然後是值數據類型和花括號中的鍵值對來構造的。

map[key]value{}

通常用於保存相關數據,例如包含在ID中的信息,地圖看起來像這樣:

map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}

你會注意到除了大括號之外,地圖中還遍布著冒號。冒號左邊的詞是鍵。鍵可以是Go中任何可比較的類型。可比較的類型是像字串整數等基本類型。基本類型由語言定義,而不是由其他類型的組合構建。雖然它們可以是用户定義的類型,但最好保持簡單以避免編程錯誤。上面字典中的鍵是:名稱動物顏色地點

冒號右邊的詞是值。值可以由任何數據類型組成。上面字典中的值是:Sammy鯊魚藍色海洋

讓我們將地圖存儲在一個變量中並打印出來:

sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
fmt.Println(sammy)
Output
map[animal:shark color:blue location:ocean name:Sammy]

如果我們想單獨提取Sammy的顏色,可以通過調用sammy["顏色"]來實現。讓我們打印出來:

fmt.Println(sammy["color"])
Output
blue

由於地圖提供了用於存儲數據的鍵值對,它們可以成為你的Go程序中的重要元素。

結論

此時,您應該對 Go 中可用的一些主要數據類型有了更好的理解。隨著您在 Go 語言中開發編程項目,這些數據類型將變得重要。

一旦您對 Go 中可用的數據類型有了堅實的掌握,您就可以學習如何轉換數據類型,以便根據情況更改您的數據類型。

Source:
https://www.digitalocean.com/community/tutorials/understanding-data-types-in-go