引言
数据类型指定了编写程序时特定变量存储的值的类型。数据类型还决定了可以对数据执行哪些操作。
在本文中,我们将介绍Go的重要数据类型。这不是对数据类型的详尽研究,但将帮助您熟悉Go中可用的选项。理解一些基本的数据类型能让你写出更清晰、性能更高效的代码。
背景
理解数据类型的一种方法是考虑我们在现实世界中使用的不同类型的数据。现实世界中数据的一个例子是数字:例如,我们可以使用整数(0,1,2,…)、整数(…,-1,0,1,…)和无理数(π)。
通常,在数学中,我们可以组合不同类型的数字,并得到某种答案。我们可能想将π加5,例如:
5 + π
我们可以保留这个方程作为无理数的答案,也可以将π四舍五入到一个小数位较少的数字中,然后再将它们相加:
5 + π = 5 + 3.14 = 8.14
但是,如果我们开始尝试用另一种数据类型(如单词)计算数字,就会变得不那么有意义。我们如何解下面的方程?
shark + 8
对于计算机来说,每种数据类型都非常不同,就像单词和数字一样。因此,我们必须小心如何使用不同的数据类型来赋值,以及如何通过操作操作它们。
Integers
与数学一样,计算机编程中的整数是整数,可以是正、负或0(…,- 1,0,1,…)。在Go中,整数被称为int
。与其他编程语言一样,在4位或4位以上的数字中不应该使用逗号,因此,在程序中编写1000时,请将其写成1000
。
可以像下面这样简单地打印一个整数:
fmt.Println(-459)
Output-459
或者,我们可以声明一个变量,在本例中,它是我们正在使用或操作的数字的符号,如下所示:
var absoluteZero int = -459
fmt.Println(absoluteZero)
Output-459
我们也可以在Go中使用整数进行数学运算。在下面的代码块中,我们将使用:=
赋值操作符来声明和实例化变量sum
:
sum := 116 - 68
fmt.Println(sum)
Output48
如输出所示,数学运算符-
将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)
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 无符号8位整数 (0 to 255)
uint16 无符号16位整数 (0 to 65535)
uint32 无符号32位整数 (0 to 4294967295)
uint64 无符号64位整数 (0 to 18446744073709551615)
int8 带符号的8位整数 (-128 to 127)
int16 带符号的16位整数 (-32768 to 32767)
int32 带符号的32位整数 (-2147483648 to 2147483647)
int64 带符号的64位整数 (-9223372036854775808 to 9223372036854775807)
浮点数和复数的大小也各不相同:
float32 IEEE-754 32位浮点数
float64 IEEE-754 64位浮点数
complex64 具有float32类型变量实部和虚部的复数
complex128 具有float64类型变量实部和虚部的复数
还有一些别名编号类型,可以为特定的数据类型指定有用的名称。
byte uint8的别名
rune int32的别名
byte
别名的目的是,当你的程序使用字节作为字符串元素的常见计算度量时,而不是与字节数据度量无关的小整数时,可以明确这一点。即使byte
和uint8
在程序编译时是相同的,byte
通常用于表示数字形式的字符数据,而uint8
在程序中是一个数字。
rune
别名有点不同。其中byte
和uint8
是完全相同的数据,rune
可以是一个字节或四个字节,一个范围由int32
决定。rune
用于表示Unicode字符,而只有ASCII字符可以只用int32
数据类型表示。
此外,Go有以下特定于实现的类型:
uint 无符号,32或64位
int 有符号,32或64位
uintptr 大到足以存储指针值的未解释位的无符号整数
特定于实现的类型的大小由编译程序的体系结构定义。
选择数值数据类型
选择正确的大小通常与您正在编程的目标架构的性能有关,而不是与您正在使用的数据的大小有关。不过,在不需要知道程序性能的具体影响的情况下,你可以在刚开始时遵循这些基本指导方针。
如本文前面所讨论的,有体系结构无关的类型和特定于实现的类型。对于整数数据,通常在Go中使用int
或uint
等实现类型,而不是int64
或uint64
。这通常会为您的目标架构带来最快的处理速度。例如,如果你使用int64
并编译为32位架构,那么处理这些值需要至少两倍的时间,因为在架构中移动数据需要额外的CPU周期。如果你使用int
,程序会将其定义为32位体系结构的32位大小,处理速度会明显更快。
如果您知道不会超过特定的大小范围,那么选择一种体系结构无关的类型可以提高速度并减少内存使用。例如,如果你知道你的数据不会超过100
的值,并且只会是一个正数,那么选择uint8
将使你的程序更高效,因为它将需要更少的内存。
我们已经了解了数值数据类型的一些可能的范围,下面来看看在程序中超出这些范围会发生什么。
溢出vs.环绕
当您尝试存储大于设计数据类型的值时,Go有可能溢出数字和包装数字,这取决于该值是在编译时还是在运行时计算的。当程序在尝试构建程序时发现错误时,就会发生编译时错误。运行时错误发生在程序被编译之后,但它实际上正在执行。
在下面的例子中,我们将maxint32
设置为最大值:
package mainimport "fmt"func main() {var maxUint32 uint32 = 4294967295 // Max uint32 sizefmt.Println(maxUint32)
}
它将编译并运行,结果如下:
Output4294967295
如果我们在运行时给值加上1
,它会被转换成0
:
Output0
另一方面,让我们修改程序,在编译前给变量赋值时加上1
:
package mainimport "fmt"func main() {var maxUint32 uint32 = 4294967295 + 1fmt.Println(maxUint32)}
在编译时,如果编译器可以确定指定的数据类型容纳的值太大,它将抛出overflow
错误。这意味着计算的值对于指定的数据类型来说太大。
因为编译器可以确定它会溢出这个值,所以它会抛出一个错误:
Outputprog.go:6:36: constant 4294967296 overflows uint32
理解数据的边界将帮助你在将来的程序中避免潜在的错误。
我们已经介绍了数值类型,下面来看看如何存储布尔值。
布尔值
boolean数据类型可以是两个值之一,true
或false
,当声明它为数据类型时,它被定义为bool
。布尔值用于表示与数学逻辑分支相关的真值,它指示计算机科学中的算法。
值true
和false
将始终分别是小写的t
和f
,因为它们是Go中预先声明的标识符。
许多数学运算的结果要么为true,要么为false:
- 大于
- 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
,我们将收到以下输出:
Outputfalse
随着你在Go中编写更多的程序,你将更加熟悉布尔值的工作原理,以及计算为true
或false
的不同函数和操作如何改变程序的进程。
字符串
字符串是由一个或多个字符(字母、数字、符号)组成的序列,可以是常量,也可以是变量。在Go中,字符串存在于反引号`
或双引号"
中,并根据您使用的引号具有不同的特征。
如果你使用反引号,你就是在创建一个原始字符串字面量。如果你使用双引号,你是在创建一个解释字符串字面量。
原始字符串字面量
原始字符串字面量是位于反引号之间的字符序列,通常称为反引号。在引号内,任何字符都将与反引号之间显示的字符一样,除了反引号字符本身。
a := `Say "hello" to Go!`
fmt.Println(a)
OutputSay "hello" to Go!
通常,反斜杠用于表示字符串中的特殊字符。例如,在解释字符串中,\n
表示字符串中的新行。然而,反斜杠在原始字符串字面量中没有特殊含义:
a := `Say "hello" to Go!\n`
fmt.Println(a)
因为反斜杠在字符串字面量中没有特殊的含义,它实际上会打印出\n
的值,而不是新建一行:
OutputSay "hello" to Go!\n
原始字符串字面量也可以用来创建多行字符串:
a := `This string is on
multiple lines
within a single back
quote on either side.`
fmt.Println(a)
OutputThis string is on
multiple lines
within a single back
quote on either side.
在前面的代码块中,新行从输入转移到输出。
解释的字符串字面量
解释字符串字面量是双引号之间的字符序列,如"bar"
。在引号中,除了换行符和未转义的双引号外,任何字符都可以出现。要在解释过的字符串中显示双引号,可以使用反斜杠作为转义字符,如下所示:
a := "Say \"hello\" to Go!"
fmt.Println(a)
OutputSay "hello" to Go!
几乎总是使用解释过的字符串字面量,因为它们允许转义字符。
包含UTF-8字符的字符串
UTF-8是一种编码方案,用于将可变宽度字符编码为1到4个字节。Go支持开箱即用的UTF-8字符,不需要任何特殊设置、库或包。罗马字符(如字母 A
)可以用ASCII值(如数字65)表示。但是,对于特殊字符,如国际字符世
,则需要使用UTF-8。Go对UTF-8数据使用rune
别名类型。
a := "Hello, 世界"
你可以在for
循环中使用range
关键字来索引Go中的任何字符串,即使是UTF-8字符串。for
循环和range
将在本系列的后面部分更深入地介绍;现在,重要的是要知道,我们可以使用它来对给定字符串中的字节数进行计数:
package mainimport "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
的值,i
是for
循环的当前索引,c
是for
循环的当前字符。
最后,我们使用内置的len
函数打印出变量a
的整个长度。
前面我们提到过,rune是int32
的别名,由1到4个字节组成。世
字符需要三个字节来定义,在UTF-8字符串范围内,索引相应地移动。这就是i
在打印出来时不是顺序的原因。
Output0: H
1: e
2: l
3: l
4: o
5: ,
6:
7: 世
10: 界
length of 'Hello, 世界': 13
如你所见,这个长度比在字符串上进行范围搜索的次数要长。
你不会总是使用UTF-8字符串,但当你使用时,你现在会明白为什么它们是runes而不是单个的int32
。
声明变量的数据类型
现在您已经了解了不同的基本数据类型,我们将介绍如何在go中将这些类型分配给变量。
在Go中,我们可以使用关键字var
定义一个变量,后跟变量名和所需的数据类型。
在下面的例子中,我们将声明一个类型为float64
的名为pi
的变量。
关键字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等语言不同,PHP的数据类型与值相关:
$s = "sammy"; // $s 自动为字符串类型
$s = 123; // $s 自动为数字类型
在上面的代码块中,第一个$s
是一个字符串,因为它被赋值为"sammy"
,第二个$s
是一个整数,因为它的值为123
。
接下来,让我们看看更复杂的数据类型,如数组。
数组
An array of strings looks like this:
数组是一组有序的元素序列。数组的容量在创建时定义。一旦数组分配了大小,就不能再改变它的大小。因为数组的大小是静态的,这意味着它只分配一次内存。这使得数组的使用有些死板,但提高了程序的性能。因此,优化程序时通常会用到数组。接下来介绍的切片更灵活,在其他语言中构成了你所认为的数组。
数组通过声明数组的大小,然后在花括号{ }
中定义数据类型来定义。
字符串数组如下所示:
[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内置的哈希或字典类型。map使用键和值作为一对来存储数据。这在编程中很有用,可以通过索引(本例中是键)快速查找值。例如,你可能想保存一个用户映射,按用户ID索引。键是用户ID, user对象是值。map的构造方法是使用关键字map
,后面是方括号[]
中的键数据类型,后面是花括号中的值数据类型和键值对。
map[key]value{}
map通常用于保存相关的数据,例如ID中包含的信息,如下所示:
map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
您会注意到,除了大括号之外,整个映射中还有冒号。冒号左边的单词是键。在Go中键可以是任何可比较的类型。可比类型是基本类型,如strings
、ints
等。基本类型是由语言定义的,不是由任何其他类型组合而成的。虽然它们可以是用户定义的类型,但保持简单是最佳实践,以避免编程错误。上述字典中的键是:name
、animal
、color
和location
。
冒号右边的单词是值。值可以由任何数据类型组成。上述字典中的值是:Sammy
、shark
、blue
和ocean
。
让我们将map存储在一个变量中并打印出来:
sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
fmt.Println(sammy)
Outputmap[animal:shark color:blue location:ocean name:Sammy
如果我们想要分离萨米的颜色,我们可以通过调用sammy ["color"]
来实现。我们把它打印出来:
fmt.Println(sammy["color"])
Outputblue
由于map提供了用于存储数据的键值对,因此它们可以成为Go程序中的重要元素。
总结
此时,您应该更好地了解Go中可供使用的一些主要数据类型。当您使用Go语言开发编程项目时,这些数据类型中的每一种都将变得非常重要。