引言
数据类型指定了在编写程序时特定变量将存储的值的种类。数据类型还决定了可以对数据执行的操作。
本文将介绍Go语言中重要的数据类型。虽然这不是对数据类型的详尽探讨,但将帮助你熟悉Go中可用的选项。理解一些基本数据类型将使你能够编写执行效率高且清晰的代码。
背景
思考数据类型的一种方式是考虑我们在现实世界中使用的不同类型的数据。现实世界中的数据示例包括数字:我们可能使用整数(0, 1, 2, …)、整数(…, -1, 0, 1, …)和无理数(π),例如。
通常,在数学中,我们可以组合不同类型的数字,并得到某种答案。例如,我们可能想将5加到π上:
5 + π
我们可以将方程作为答案,以考虑无理数,或者将π四舍五入到一个带有简化的十进制位数的数字,然后将这些数字相加:
5 + π = 5 + 3.14 = 8.14
但是,如果我们开始尝试用另一种数据类型(如单词)来评估数字,事情就开始变得不太合理了。我们该如何解以下这个方程呢?
shark + 8
对于计算机而言,每种数据类型都截然不同——就像单词和数字。因此,我们必须谨慎对待如何使用不同的数据类型来赋值,以及如何通过操作来处理它们。
整数
与数学中一样,计算机编程中的整数是正数、负数或零的完整数字(…, -1, 0, 1, …)。在Go语言中,整数被称为int
。与其他编程语言一样,不应在四位或更多位数的数字中使用逗号,因此当你在程序中写1,000时,应写作1000
。
我们可以用一种简单的方式打印出一个整数,如下所示:
Output-459
或者,我们可以声明一个变量,在这种情况下,它是我们正在使用或操作的数字的符号,如下所示:
Output-459
我们也可以在Go中对整数进行数学运算。在下面的代码块中,我们将使用:=
赋值运算符来声明并实例化变量sum
:
Output48
正如输出所示,数学运算符-
从116
中减去了整数68
,结果为48
。你将在为变量声明数据类型部分了解更多关于变量声明的内容。
整数在 Go 程序中有着多种用途。随着你对 Go 的深入学习,你将有大量机会与整数打交道,并在此基础上拓展你对这一数据类型的理解。
浮点数
浮点数或简称“float”,用于表示无法以整数形式表达的实数。实数包含所有有理数和无理数,因此,浮点数可以包含小数部分,例如 9.0 或 -116.42。在 Go 程序中考虑浮点数时,可以将其视为包含小数点的数字。
如同处理整数一样,我们可以用简单的方式打印出浮点数:
Output-459.67
我们也可以声明一个代表浮点数的变量,如下所示:
Output-459.67
与整数类似,我们也可以在 Go 中使用浮点数进行数学运算:
Output929.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
别名的目的是在程序中使用字节作为字符串元素的通用计算度量时,明确区分与字节数据度量无关的小整数。尽管byte
和uint8
在程序编译后是相同的,但byte
常用于以数字形式表示字符数据,而uint8
则旨在表示程序中的数字。
rune
别名则有所不同。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
设置为其最大值:
它将编译并运行,结果如下:
如果在运行时给这个值加1
,它将环绕回0
:
另一方面,让我们修改程序,在编译前赋值时给变量加1
:
在编译时,如果编译器能够确定一个值对于指定的数据类型来说过大,它会抛出一个overflow
错误。这意味着计算出的值对于你指定的数据类型来说太大了。
因为编译器能够确定该值会溢出,现在它会抛出一个错误:
Outputprog.go:6:36: constant 4294967296 overflows uint32
了解数据的边界将帮助你避免未来程序中可能出现的潜在错误。
既然我们已经讨论了数值类型,接下来让我们看看如何存储布尔值。
布尔类型
布尔数据类型可以取两个值之一,即true
或false
,在声明为数据类型时定义为bool
。布尔值用于表示与数学逻辑分支相关的真值,这些真值指导计算机科学中的算法。
true
和false
值总是分别使用小写的t
和f
,因为它们在Go中是预声明的标识符。
数学中的许多运算结果要么为真,要么为假:
- 大于
- 500 > 100 true
- 1 > 5 false
- 小于
- 200 < 400 true
- 4 < 2 false
- 等于
- 5 = 5 true
- 500 = 400 false
与数字类似,我们可以将布尔值存储在一个变量中:
我们可以通过调用 fmt.Println()
函数来打印布尔值:
由于 5
不大于 8
,我们将得到以下输出:
Outputfalse
随着你在 Go 中编写更多程序,你将更熟悉布尔值的工作原理,以及不同函数和操作如何通过评估为 true
或 false
来改变程序的流程。
字符串
字符串是由一个或多个字符(字母、数字、符号)组成的序列,可以是常量或变量。在 Go 中,字符串存在于反引号 `
或双引号 "
中,具体特性取决于你使用的引号类型。
如果你使用反引号,你创建的是一个 原始 字符串字面量。如果你使用双引号,你创建的是一个 解释型 字符串字面量。
原始字符串字面量
原始字符串字面量是位于反引号之间的字符序列,通常称为反引号。在引号内,任何字符都会按照其在反引号之间的显示方式出现,除了反引号字符本身。
OutputSay "hello" to Go!
通常,反斜杠用于表示字符串中的特殊字符。例如,在解释型字符串中,\n
代表字符串中的换行符。然而,在原始字符串字面量中,反斜杠没有特殊含义:
由于反斜杠在字符串字面量中没有特殊含义,它实际上会输出 \n
的值,而不是创建一个新行:
OutputSay "hello" to Go!\n
原始字符串字面量也可用于创建多行字符串:
OutputThis string is on
multiple lines
within a single back
quote on either side.
在前述代码块中,新行从输入到输出被原样保留。
解释型字符串字面量
解释型字符串字面量是位于双引号之间的字符序列,如 "bar"
。在引号内,除换行符和未转义的双引号外,任何字符都可以出现。要在解释型字符串中显示双引号,可以使用反斜杠作为转义字符,如下所示:
OutputSay "hello" to Go!
你几乎总是会使用解释型字符串字面量,因为它们允许在内部使用转义字符。更多关于字符串操作的内容,请参阅 Go 语言中字符串操作入门。
包含UTF-8字符的字符串
UTF-8是一种编码方案,用于将变宽字符编码为一至四个字节。Go语言原生支持UTF-8字符,无需特殊设置、库或包。例如,罗马字母A
可以通过ASCII值如数字65来表示。然而,对于世
这样的特殊国际字符,则需要使用UTF-8编码。Go语言使用rune
别名类型来处理UTF-8数据。
在Go中,你可以使用for
循环和range
关键字遍历任何字符串,包括UTF-8字符串。for
循环和range
将在本系列后续部分更详细地介绍;目前,重要的是要知道我们可以利用它们来计算给定字符串中的字节数:
在上面的代码块中,我们声明了变量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
不是连续的。
Output0: 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
:
接着是我们的变量名 pi
:
最后是数据类型 float64
:
我们还可以选择性地指定一个初始值,例如 3.14
:
Go 是一种 静态类型 的语言。静态类型意味着程序中的每个语句在编译时都会被检查。这也意味着数据类型与变量绑定,而在动态类型语言中,数据类型与值绑定。
例如,在 Go 中,类型在声明变量时被声明:
这些变量如果以不同方式声明,可以是不同的数据类型。
这与 PHP 等语言不同,在 PHP 中,数据类型与值关联:
在前面的代码块中,第一个 $s
是一个字符串,因为它被赋予了值 "sammy"
,而第二个是一个整数,因为它有值 123
。
接下来,让我们看看更复杂的数据类型,比如数组。
数组
数组是元素的有序序列。数组的容量在创建时定义,一旦数组分配了其大小,该大小便无法更改。由于数组的大小是静态的,这意味着它只分配一次内存。这使得数组在操作上有些僵硬,但能提高程序的性能。因此,数组通常用于优化程序。切片则更为灵活,构成了你在其他语言中所认为的数组。
数组通过声明数组的大小,然后是数据类型,并在花括号{ }
中定义值来定义。
字符串数组看起来像这样:
我们可以将数组存储在一个变量中并打印出来:
Output[blue coral staghorn coral pillar coral]
如前所述,切片类似于数组,但灵活性大大增强。让我们来看看这种可变数据类型。
切片
切片是长度可变的元素有序序列。切片能够动态增加其大小。当你向切片添加新项时,如果切片没有足够的内存来存储新项,它会根据需要向系统请求更多内存。由于切片可以根据需要扩展以添加更多元素,因此它们比数组更常用。
切片通过在数据类型前加上一对开闭方括号 `[]
` 并在大括号 `{}
` 内包含值来定义。
一个整数切片看起来像这样:
一个浮点数切片看起来像这样:
一个字符串切片看起来像这样:
让我们将字符串切片定义为 `seaCreatures
`:
我们可以通过调用变量来打印它们:
输出将与我们创建的列表完全一样:
Output[shark cuttlefish squid mantis shrimp]
我们可以使用 `append
` 关键字向切片中添加一个项目。以下命令将字符串值 `seahorse
` 添加到切片中:
你可以通过打印出来验证它是否被添加:
Output[shark cuttlefish squid mantis shrimp seahorse]
正如你所见,如果你需要管理未知大小的元素,切片将比数组更加灵活多变。
映射
映射是 Go 语言内置的哈希表或字典类型。映射使用键和值作为一对来存储数据。这在编程中非常有用,可以快速通过索引(在这种情况下是键)查找值。例如,你可能想要维护一个用户映射,按用户 ID 索引。键将是用户 ID,而用户对象将是值。映射通过使用关键字 `map
` 后跟方括号 `[]
` 中的键数据类型,接着是值数据类型以及大括号中的键值对来构建。
map[key]value{}
通常用于存储相关数据,例如包含在ID中的信息,映射看起来像这样:
你会注意到,除了大括号外,映射中还遍布着冒号。冒号左边的词是键。键可以是Go中任何可比较的类型。可比较类型是像字符串
、整数
等基本类型。基本类型由语言定义,不是由其他类型组合而成。虽然它们可以是用户自定义类型,但保持简单被认为是最佳实践,以避免编程错误。上述字典中的键是:名字
、动物
、颜色
和地点
。
冒号右边的词是值。值可以由任何数据类型组成。上述字典中的值是:萨米
、鲨鱼
、蓝色
和海洋
。
让我们将映射存储在一个变量中并打印出来:
Outputmap[animal:shark color:blue location:ocean name:Sammy]
如果我们想单独提取萨米的颜色,可以通过调用sammy["颜色"]
来实现。让我们打印出来:
Outputblue
由于映射提供了用于存储数据的键值对,它们可以成为你Go程序中的重要元素。
结论
至此,您应该对 Go 语言中可用的几种主要数据类型有了更深的理解。随着您在 Go 语言中开发编程项目,这些数据类型将变得至关重要。
一旦您牢固掌握了 Go 中可用的数据类型,您就可以学习如何转换数据类型,以便根据实际情况调整数据类型。
Source:
https://www.digitalocean.com/community/tutorials/understanding-data-types-in-go