介绍
数据类型指定特定变量在编写程序时会存储的值类型,数据类型还决定可以对数据执行哪些操作。
在本文中,我们将讨论 Go 原生的重要数据类型,这不是对数据类型的全面调查,但会帮助您熟悉 Go 中的可用选项,了解一些基本的数据类型将使您能够编写更清晰的代码,以高效地执行。
背景
一个方法来思考数据类型是考虑我们在现实世界中使用的不同类型的数据,一个真实世界中的数据的例子是数字:我们可以使用整数(0,1,2,...),整数(..., -1,0,1,...)和非理数(π),例如。
通常,在数学中,我们可以组合不同类型的数字,并获得某种答案。
15 + π
我们可以把方程式作为对非理数的答案来计算,或者将 π 围绕到具有缩写数十位数的数字上,然后将数字加在一起:
15 + π = 5 + 3.14 = 8.14
但是,如果我们开始试图用另一种数据类型来评估数字,例如单词,事情就会变得不那么有意义。
1shark + 8
对于计算机来说,每个数据类型都非常不同,就像单词和数字一样,因此我们必须小心我们如何使用不同的数据类型来分配值,以及如何通过操作来操纵它们。
整合者
与数学一样,计算机编程中的 _integers 是可以是正数、负数或 0(..., -1, 0, 1,...)的整数,在 Go 中,一个整数被称为int
。
我们可以以这样简单的方式打印整数:
1fmt.Println(-459)
1[secondary_label Output]
2-459
或者,我们可以声明一个变量,在这种情况下,它是我们使用或操纵的数字的象征:
1var absoluteZero int = -459
2fmt.Println(absoluteZero)
1[secondary_label Output]
2-459
在以下代码块中,我们将使用 :=
分配运算符来声明和实例化变量 sum
:
1sum := 116 - 68
2fmt.Println(sum)
1[secondary_label Output]
248
正如输出显示的那样,数学运算符 -
将整数 68 从
116 中扣除,结果是 `48 。
整合符可以在Go程序中以多种方式使用,当您继续了解Go时,您将有许多机会使用整数工作,并利用您对此数据类型的知识。
浮动点数
一个 floating-point number 或一个 float 被用来表示不能作为整数表达的 real numbers。
与整数一样,我们可以这样简单地打印浮点数:
1fmt.Println(-459.67)
1[secondary_label Output]
2-459.67
我们还可以声明代表浮动的变量,如下:
1absoluteZero := -459.67
2fmt.Println(absoluteZero)
1[secondary_label Output]
2-459.67
与整数一样,我们也可以在Go中用浮点做数学:
1var sum = 564.0 + 365.24
2fmt.Println(sum)
1[secondary_label Output]
2929.24
对于整数和浮点数,重要的是要记住 3 ≠ 3.0,因为3 指整数,而 3.0 指浮点。
数值类型的大小
除了区分整数和浮数之外,Go还有两种类型的数字数据,它们的尺寸是静态的或动态的,而第一个类型是 architecture-independent 类型,这意味着数据的尺寸在位数中不会改变,无论代码在哪台运行。
例如,您可能正在为现代的 Windows 笔记本电脑开发,其中操作系统运行在 64 位架构上。但是,如果您正在为一个设备开发,如健身手表,您可能正在使用 32 位架构。
第二种类型是 implementation-specific 类型. 在这种类型中,比特大小可以根据程序所构建的架构而有所不同.例如,如果我们使用 int
类型,当 Go 编译 32 位架构时,数据类型的大小将是 32 位。
除了具有不同大小的数据类型外,类型如整数也存在两种基本类型: signed 和 unsigned. 一个int8
是一个签名的整数,可以有从 -128 到 127 的值。
范围基于比特大小. 对于二进制数据,8位可以代表共256个不同的值. 因为一个int
类型需要支持积极和负值,一个8位的整数(int8
)将有从128到127的范围,共256个可能的独特值。
Go 有以下结构独立的整数类型:
1uint8 unsigned 8-bit integers (0 to 255)
2uint16 unsigned 16-bit integers (0 to 65535)
3uint32 unsigned 32-bit integers (0 to 4294967295)
4uint64 unsigned 64-bit integers (0 to 18446744073709551615)
5int8 signed 8-bit integers (-128 to 127)
6int16 signed 16-bit integers (-32768 to 32767)
7int32 signed 32-bit integers (-2147483648 to 2147483647)
8int64 signed 64-bit integers (-9223372036854775808 to 9223372036854775807)
浮动和复杂数也以不同的尺寸出现:
1float32 IEEE-754 32-bit floating-point numbers
2float64 IEEE-754 64-bit floating-point numbers
3complex64 complex numbers with float32 real and imaginary parts
4complex128 complex numbers with float64 real and imaginary parts
还有几种名称数类型,这些类型将有用的名称分配给特定的数据类型:
1byte alias for uint8
2rune alias for int32
byte
字母的目的是清楚地说明你的程序在字符串元素中使用比特作为常见的计算测量,而不是与比特数据测量无关的小整数,尽管byte
和uint8
在程序编译后是相同的,但byte
通常用于以数字形式表示字符数据,而uint8
在你的程序中是指数。
在byte
和uint8
是完全相同的数据中,一个rune
可以是单个字节或四个字节,一个由int32
确定的范围。
此外,Go 还有以下实施特定的类型:
1uint unsigned, either 32 or 64 bits
2int signed, either 32 or 64 bits
3uintptr unsigned integer large enough to store the uninterpreted bits of a pointer value
实施特定的类型将由程序编译的架构定义为其大小。
选择数值数据类型
选择正确的大小通常与您编程的目标架构的性能有关,而不是您正在使用的数据的大小。
正如本文早些时候讨论的那样,有架构独立的类型和实施特定的类型。对于整数数据,Go 通常会使用int
或uint
等实现类型,而不是int64
或uint64
。这通常会导致目标架构的最快的处理速度。例如,如果您使用int64
并将其编译成 32 位架构,则需要至少两倍的时间来处理这些值,因为需要额外的 CPU 周期来移动整个架构的数据。
如果你知道你不会超过一个特定的大小范围,那么选择一个独立于架构的类型可以提高速度和减少内存使用量,例如,如果你知道你的数据不会超过100
的值,并且只会是一个正数,那么选择uint8
将使你的程序更有效,因为它需要更少的内存。
现在我们已经研究了数值数据类型的一些可能范围,让我们看看如果我们在我们的程序中超过这些范围会发生什么。
转移 vs. 转移
Go 有潜力为一个数字和一个数字 overflow 当您尝试存储比数据类型设计存储更大的值时,取决于该值是否在编译时间或运行时计算。
在下面的示例中,我们将maxUint32
设置为其最大值:
1package main
2
3import "fmt"
4
5func main() {
6 var maxUint32 uint32 = 4294967295 // Max uint32 size
7 fmt.Println(maxUint32)
8}
它将编译和运行,结果如下:
1[secondary_label Output]
24294967295
如果我们在运行时将1
添加到值,它将被包围到0
:
1[secondary_label Output]
20
另一方面,让我们在编译时间之前,在我们分配变量时更改程序以添加1
:
1package main
2
3import "fmt"
4
5func main() {
6 var maxUint32 uint32 = 4294967295 + 1
7 fmt.Println(maxUint32)
8
9}
在编译时,如果编译器能够确定一个值会太大,无法在指定的数据类型中存储,则会引发过流
错误,这意味着计算的值对于您指定的数据类型太大。
由于编译器可以确定它会超过值,它现在会投出一个错误:
1[secondary_label Output]
2prog.go:6:36: constant 4294967296 overflows uint32
了解您的数据的界限将有助于您在未来避免程序中的潜在错误。
现在我们已经涵盖了数字类型,让我们看看如何存储布尔值。
布莱尔
boolean 数据类型可以是两个值中的一个,无论是真
还是假
,并且在将其声明为数据类型时被定义为bool
。
值 true
和false
将始终分别为t
和f
,因为它们是Go中的预先声明标识符。
数学中的许多操作给了我们评估为真或假的答案:
- 比 大 - 500 > 100 true
- 1 > 5 false
- 少于
- 200 < 400 true
- 4 < 2 false
- 等于
- 5 = 5 true
- 500 = 400 false
与数字一样,我们可以在变量中存储一个布尔值:
1myBool := 5 > 8
然后,我们可以打印布尔值,呼叫函数 fmt.Println()
:
1fmt.Println(myBool)
由于5
不大于8
,我们将获得以下输出:
1[secondary_label Output]
2false
随着您在 Go 中编写更多的程序,您将更加熟悉布尔语的运作方式,以及如何将不同的函数和操作评估为真
或假
可以改变程序的进程。
严格
一个字符串是由一个或多个字符(字母,数字,符号)组成的序列,它可以是常数或变量。
如果你使用后引文,你正在创建一个 raw 字符串,如果你使用双引文,你正在创建一个 interpreted 字符串。
Raw String 字典
原始字符串字符串是后引文之间的字符序列,通常被称为后引文,在引文中,任何字符都将出现在后引文之间,除了后引文字符本身。
1a := `Say "hello" to Go!`
2fmt.Println(a)
1[secondary_label Output]
2Say "hello" to Go!
通常,backslash 被用来代表字符串中的特殊字符,例如,在被解释的字符串中,\n 表示字符串中的新字符串,但是,backslash 没有在原始字符串中具有特殊的含义:
1a := `Say "hello" to Go!\n`
2fmt.Println(a)
由于反射在字母字符串中没有特别的含义,因此它实际上会打印出 `\n 的值,而不是创建一个新的行:
1[secondary_label Output]
2Say "hello" to Go!\n
原始字符串也可以用来创建多行字符串:
1a := `This string is on
2multiple lines
3within a single back
4quote on either side.`
5fmt.Println(a)
1[secondary_label Output]
2This string is on
3multiple lines
4within a single back
5quote on either side.
在之前的代码块中,新行从输入转移到输出。
解读字母字符串
解读字符串字符串是双重引文之间的字符序列,如在bar
中。在引文中,除了新行和未逃避的双重引文之外,任何字符都可以出现在引文中。
1a := "Say \"hello\" to Go!"
2fmt.Println(a)
1[secondary_label Output]
2Say "hello" to Go!
您将几乎总是使用解读的字符串字母,因为它们允许它们中的字符逃脱。
使用 UTF-8 字符的字符串
UTF-8 是一种编码方案,用于将可变宽字符编码为 1 到 4 个字节. Go 支持 UTF-8 字符,而不需要任何特别的设置、库或包。 罗马字符,如字母A
可以用 ASCII 值,如数字 65 来表示。
1a := "Hello, 世界"
您可以在for
循环中使用range
关键字,通过Go中的任何字符串进行索引,甚至是 UTF-8字符串。
1package main
2
3import "fmt"
4
5func main() {
6 a := "Hello, 世界"
7 for i, c := range a {
8 fmt.Printf("%d: %s\n", i, string(c))
9 }
10 fmt.Println("length of 'Hello, 世界': ", len(a))
11}
在上面的代码块中,我们宣布变量a
并分配了Hello, 世界
的值,分配的文本中包含 UTF-8 字符。
然后我们使用了标准的for
循环以及range
关键字,在Go中,range
关键字将通过一个字符串进行索引,一次返回一个字符串,以及字符串中的字节索引。
使用fmt.Printf
函数,我们提供了一个格式字符串的%d: %s\n
。%d
是数字的印刷字符串(在这种情况下是一个整数),而%s
是字符串的印刷字符串。
最后,我们用内置的len
函数打印了变量a
的整个长度。
早些时候,我们提到,一个鲁尼是int32
的代名词,可以由一个到四个字节组成。世
字符需要三个字节来定义,而索引在通过 UTF-8 字符串时相应地移动。
1[secondary_label Output]
20: H
31: e
42: l
53: l
64: o
75: ,
86:
97: 世
1010: 界
11length of 'Hello, 世界': 13
正如你所看到的,长度比它跨越字符串所需要的次数更长。
您不会总是使用 UTF-8 字符串,但当您使用它时,您现在将了解它们为何是 Run,而不是单个int32
。
声明变量的数据类型
现在您已经了解了不同的原始数据类型,我们将讨论如何在 Go 中将这些类型分配给变量。
在Go中,我们可以用关键字var
来定义变量,然后是变量的名称和所需的数据类型。
在下面的示例中,我们将声明一个名为pi
的变量类型为float64
。
关键字var
是第一个被声明的东西:
1var pi float64
其次是我们变量的名称,‘pi’:
1var pi float64
最后是数据类型「float64」:
1var pi float64
我们还可以选择指定一个初始值,例如‘3.14’:
1var pi float64 = 3.14
Go 是一个 statically typed 语言. Statically typed 意味着程序中的每个语句在编译时被检查,这也意味着数据类型与变量有关,而在动态相关的语言中,数据类型与值有关。
例如,在 Go 中,类型在声明变量时被声明:
1var pi float64 = 3.14
2var week int = 7
这些变量中的每一个都可能是不同的数据类型,如果您以不同的方式声明它们。
这与PHP等语言不同,其中数据类型与值有关:
1$s = "sammy"; // $s is automatically a string
2$s = 123; // $s is automatically an integer
在前一个代码块中,第一个$s
是一个字符串,因为它被分配到值sammy
,而第二个字符串是整数,因为它具有值123
。
接下来,让我们来看看更复杂的数据类型,如数组。
拉拉斯
一个 array 是一个有序的元素序列。 一个数组的容量在创建时被定义。 一旦一个数组分配了其大小,大小就无法更改了。 因为一个数组的大小是静态的,这意味着它只分配了记忆一次。 这使得数组有点僵硬,但提高了你的程序的性能。 由于这个原因,数组通常在优化程序时使用。
数组是通过声明数组的大小,然后是数据类型,其中在弯曲的数组之间定义的值是 { }
。
一个系列的 string 看起来像这样:
1[3]string{"blue coral", "staghorn coral", "pillar coral"}
我们可以将一个数组存储在一个变量中并打印出来:
1coral := [3]string{"blue coral", "staghorn coral", "pillar coral"}
2fmt.Println(coral)
1[secondary_label Output]
2[blue coral staghorn coral pillar coral]
如前所述,切片类似于数组,但更灵活,让我们看看这种可变数据类型。
切片
一个 slice 是可以改变长度的元素的顺序序。切片可以动态地增加其大小。当您将新元素添加到切片时,如果切片没有足够的内存来存储新元素,它将根据需要向系统请求更多的内存。
切片被定义为通过声明数据类型以打开和关闭的方块支架 []
之前,并在弯曲的支架之间有值 { }
。
一个整数的片段看起来像这样:
1[]int{-3, -2, -1, 0, 1, 2, 3}
一个浮动的片段看起来像这样:
1[]float64{3.14, 9.23, 111.11, 312.12, 1.05}
一个串子的片段看起来像这样:
1[]string{"shark", "cuttlefish", "squid", "mantis shrimp"}
让我们将我们的字符串定义为海生物
:
1seaCreatures := []string{"shark", "cuttlefish", "squid", "mantis shrimp"}
我们可以通过调用变量来打印它们:
1fmt.Println(seaCreatures)
输出将完全类似于我们创建的列表:
1[secondary_label Output]
2[shark cuttlefish squid mantis shrimp]
我们可以使用附加
的关键字来添加一个项目到我们的片段. 以下命令将添加seahorse
的字符串值到片段:
1seaCreatures = append(seaCreatures, "seahorse")
您可以通过打印它来验证它被添加了:
1fmt.Println(seaCreatures)
1[secondary_label Output]
2[shark cuttlefish squid mantis shrimp seahorse]
正如您所看到的,如果您需要管理一个未知的元素大小,一个片段将比一个数组更具通用性。
地图
该 map 是 Go 的内置哈希或字典类型。 地图使用 keys 和 values 作为存储数据的对。 这在编程中是有用的,以快速搜索索引值,或者在这种情况下,是一个密钥。 例如,你可能想要保持用户的地图,索引他们的用户ID。
1map[key]value{}
通常用于存储相关的数据,例如ID中包含的信息,地图看起来像这样:
1map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
你会注意到,除了弯曲的轴承之外,整个地图上也有列。列左侧的单词是键。键可以是Go中的任何类型。相似类型是原始类型,如字符串
,插图
等。原始类型是由语言定义的,而不是通过组合其他类型来构建的。虽然它们可以是用户定义的类型,但它被认为是最好的做法来保持它们简单,以避免编程错误。上面的字典中的关键是:名称
,动物
,颜色
和位置
。
列右侧的单词是值. 值可以由任何数据类型组成. 上面的字典中的值是:Sammy
,鲨鱼
,蓝色
和海洋
。
让我们将地图存储在变量中,并打印出来:
1sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
2fmt.Println(sammy)
1[secondary_label Output]
2map[animal:shark color:blue location:ocean name:Sammy]
如果我们想分离Sammy的颜色,我们可以这样做,称之为sammy
(颜色
)。
1fmt.Println(sammy["color"])
1[secondary_label Output]
2blue
由于地图提供了存储数据的关键值对,因此它们可以在您的 Go 程序中成为重要的元素。
结论
在这一点上,您应该更好地了解一些可用于 Go 中的主要数据类型,这些数据类型中的每个类型在您开发 Go 语言的编程项目时都将变得重要。
一旦您在 Go 中掌握了可用的数据类型,您可以学习 如何转换数据类型以根据情况更改数据类型。