跳转到主要内容

介绍


数据类型指定在编写程序时特定变量将存储的值的种类。数据类型还决定了可以对数据执行哪些操作。

在本文中,我们将介绍 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,您将有很多机会使用整数并建立在您对这种数据类型的知识的基础上。

浮点数字


浮点数或浮点数用于表示不能表示为整数的实数。实数包括所有有理数和无理数,因此,浮点数可以包含小数部分,例如 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 和 uint8 是相同的,但 byte 通常用于以数字形式表示字符数据,而 uint8 旨在成为程序中的数字。

符文别名有点不同。其中 byte 和 uint8 是完全相同的数据,一个 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 中通常使用 int 或 uint 等实现类型,而不是 int64 或 uint64。这通常会为您的目标架构带来最快的处理速度。例如,如果您使用 int64 并编译为 32 位架构,则处理这些值所需的时间至少是在架构中移动数据所需的额外 CPU 周期的两倍。如果您改用 int,程序会将其定义为 32 位架构的 32 位大小,并且处理速度会明显加快。

如果您知道不会超出特定的大小范围,那么选择与体系结构无关的类型既可以提高速度又可以减少内存使用量。例如,如果您知道您的数据不会超过 100 的值,并且只是一个正数,那么选择 uint8 将使您的程序更高效,因为它需要更少的内存。

现在我们已经查看了数值数据类型的一些可能范围,让我们看看如果我们在程序中超出这些范围会发生什么。

溢出与环绕


当您尝试存储大于设计存储的数据类型的值时,Go 可能会溢出数字和环绕数字,具体取决于该值是在编译时还是在运行时计算的。当程序在尝试构建程序时发现错误时,会发生编译时错误。程序编译后发生运行时错误,而它实际上正在执行。

在以下示例中,我们将 maxUint32 设置为其最大值:

package main

import "fmt"

func main() {
    var maxUint32 uint32 = 4294967295 // Max uint32 size
    fmt.Println(maxUint32)
}

它将编译并运行,结果如下:

Output
4294967295


如果我们在运行时将值加 1,它将回绕为 0:

Output
0


另一方面,让我们在编译前更改程序,在分配变量时将其加 1:

package main

import "fmt"

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

}

在编译时,如果编译器可以确定一个值太大而无法保存在指定的数据类型中,它将引发溢出错误。这意味着计算的值对于您指定的数据类型来说太大了。

因为编译器可以确定它会溢出值,所以它现在会抛出一个错误:

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


了解数据的边界将帮助您避免将来程序中的潜在错误。

现在我们已经介绍了数字类型,让我们看看如何存储布尔值。

布尔值


boolean 数据类型可以是两个值之一,true 或 false,并且在将其声明为数据类型时定义为 bool。布尔值用于表示与数学逻辑分支相关的真值,它为计算机科学中的算法提供信息。

值 true 和 false 将始终分别使用小写的 t 和 f,因为它们是 Go 中预先声明的标识符。

数学中的许多运算为我们提供了评估结果为真或假的答案:

  • greater than
    • 500 > 100 true
    • 1 > 5 false
  • less than
    • 200 < 400 true
    • 4 < 2 false
  • equal
    • 5 = 5 true
    • 500 = 400 false

与数字一样,我们可以将布尔值存储在变量中:

myBool := 5 > 8


然后我们可以通过调用 fmt.Println() 函数来打印布尔值:

fmt.Println(myBool)


由于 5 不大于 8,我们将收到以下输出:

Output
false


随着您在 Go 中编写更多程序,您将更加熟悉布尔值的工作方式以及不同的函数和操作评估为真或假如何改变程序的进程。

字符串


字符串是一个或多个字符(字母、数字、符号)的序列,可以是常量或变量。字符串存在于 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 对 UTF-8 数据使用 rune 别名类型。

a := "Hello, 世界"


您可以在 for 循环中使用 range 关键字来索引 Go 中的任何字符串,甚至是 UTF-8 字符串。 for 循环和范围将在本系列后面更深入地介绍;现在,重要的是要知道我们可以使用它来计算给定字符串中的字节数:

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 is automatically a string
$s = 123;             // $s is automatically an integer


在前面的代码块中,第一个 $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]


如您所见,如果您需要管理未知大小的元素,切片将比数组更通用。

Maps


map 是 Go 的内置哈希或字典类型。映射使用键和值作为一对来存储数据。这在编程以通过索引或在本例中为键快速查找值时很有用。例如,您可能希望保留一张用户地图,按用户 ID 编制索引。键是用户 ID,用户对象是值。映射是通过使用关键字 map 后跟方括号 [ ] 中的键数据类型,然后是值数据类型和花括号中的键值对来构造的。

map[key]value{}


通常用于保存相关数据,例如 ID 中包含的信息,映射如下所示:

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


您会注意到,除了大括号之外,整个地图中还有冒号。冒号左边的词是键。键可以是 Go 中任何可比较的类型。可比较类型是原始类型,如字符串、整数等。原始类型由语言定义,而不是通过组合任何其他类型构建而成。虽然它们可以是用户定义的类型,但最好的做法是让它们保持简单以避免编程错误。上面字典中的键是:名称、动物、颜色和位置。

冒号右边的词是值。值可以由任何数据类型组成。上面字典中的值是:Sammy、shark、blue 和 ocean。

让我们将地图存储在一个变量中并打印出来:

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["color"] 来实现。让我们打印出来:

fmt.Println(sammy["color"])

Output
blue


由于地图提供了用于存储数据的键值对,它们可以成为 Go 程序中的重要元素。

结论


至此,您应该对 Go 中可用的一些主要数据类型有了更好的了解。当您使用 Go 语言开发编程项目时,这些数据类型中的每一种都将变得很重要。

一旦您牢牢掌握了 Go 中可用的数据类型,您就可以学习如何转换数据类型,以便根据情况更改数据类型。

文章链接