了解Go中的数据类型

数据类型指定在编写程序时特定变量将存储的值的类型。数据类型还确定可以对数据执行哪些操作。在本文中,我们将介绍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)
-459

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

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

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

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

如输出所示,数学运算符-116减去整数68 ,得到48 您将在“ 声明变量数据类型”部分中了解有关变量声明的更多信息。

在Go程序中可以以多种方式使用整数。 当您继续学习Go时,您将有很多机会使用整数并基于您对此数据类型的了解。

浮点数字

浮点数浮点数用于表示不能表示为整数的实数 实数包括所有有理数和无理数,因此,浮点数可以包含小数部分,例如9.0或-116.42。 为了考虑Go程序中的float,它是一个包含小数点的数字。

就像我们对整数一样,我们可以用这样一个简单的方式打印出一个浮点数:

fmt.Println(-459.67)
-459.67

我们还可以声明一个代表float的变量,如下所示:

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

就像整数一样,我们也可以用Go中的浮点数做数学:

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

对于整数和浮点数,重要的是要记住3≠3.0,因为3表示整数而3.0表示浮点数。

数字类型的大小

除了整数和浮点数之间的区别,Go还有两种类型的数字数据,这些数据通过其大小的静态或动态特性来区分。 第一种类型是与体系结构无关的类型,这意味着无论代码运行的机器如何,以位为单位的数据大小都不会改变。

今天的大多数系统架构都是32位或64位。 例如,您可能正在开发一种现代Windows笔记本电脑,操作系统在64位架构上运行。 但是,如果您正在开发像健身手表这样的设备,那么您可能正在使用32位架构。 如果使用与int32类似的体系结构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会使您的程序更高效,因为它需要更少的内存。

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

溢出与环绕

当您尝试存储大于设计用于存储的数据类型的值时,Go有可能溢出数字并包围数字,具体取决于值是在编译时还是在运行时计算。 程序在尝试构建程序时发现错误时发生编译时错误。 编译程序后,实际执行时会发生运行时错误。

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

package main

import "fmt"

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

它将编译并运行以下结果:

4294967295

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

0

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

package main

import "fmt"

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

}

在编译时,如果编译器可以确定某个值太大而无法保存在指定的数据类型中,则会抛出overflow错误。 这意味着计算的值对于您指定的数据类型而言太大。

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

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

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

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

布尔

布尔数据类型可以是两个值之一,可以是truefalse ,并在将其声明为数据类型时定义为bool 布尔值用于表示与数学的逻辑分支相关联的真值,其通知计算机科学中的算法。

truefalse将始终分别使用小写的tf ,因为它们是Go中预先声明的标识符。

数学中的许多操作为我们提供了评估为真或假的答案:

  • 比...更棒
    • 500> 100真实
    • 1> 5假
  • 少于
    • 200 <400真实
    • 4 <2假
  • 等于
    • 5 = 5真
    • 500 = 400假

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

myBool := 5 > 8

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

fmt.Println(myBool)

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

false

当您在Go中编写更多程序时,您将更加熟悉布尔值的工作方式以及评估为truefalse不同函数和操作如何改变程序的过程。

字符串

字符串是一个或多个字符(字母,数字,符号)的序列,可以是常量或变量。 字符串存在于Go中的后引号`或双引号"中,具有不同的特征,具体取决于您使用的引号。

如果使用后引号,则表示您正在创建原始字符串文字。 如果使用双引号,则表示正在创建解释的字符串文字。

原始字符串文字

原始字符串文字是后引号之间的字符序列,通常称为后标记。 在引号内,任何字符都将显示在后引号之间,除了后引号字符本身。

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

通常,反斜杠用于表示字符串中的特殊字符。 例如,在解释的字符串中, \n表示字符串中的新行。 但是,反斜杠在原始字符串文字中没有特殊含义:

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

因为反斜杠在字符串文字中没有特殊含义,所以它实际上会打印出\n的值,而不是创建一个新行:

Say "hello" to Go!\n

原始字符串文字也可用于创建多行字符串:

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

在前面的代码块中,新行从字面上从输入到输出。

解释字符串文字

解释的字符串文字是双引号之间的字符序列,如"bar" 在引号内,除了换行符和未转义的双引号外,任何字符都可能出现。 要在解释的字符串中显示双引号,可以使用反斜杠作为转义字符,如下所示:

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

您几乎总是使用解释的字符串文字,因为它们允许在其中使用转义字符。 有关使用字符串的更多信息,请参阅Go中使用字符串简介

带有UTF-8字符的字符串

UTF-8是一种编码方案,用于将可变宽度字符编码为一到四个字节。 Go支持开箱即用的UTF-8字符,无需任何特殊设置,库或包。 诸如字母A类的罗马字符可以用诸如数字65之类的ASCII值表示。但是,对于诸如国际字符的特殊字符,将需要UTF-8。 Go对UTF-8数据使用rune别名类型。

a := "Hello, 世界"

您可以在for循环中使用range关键字来索引Go中的任何字符串,甚至是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循环的当前索引,而cfor循环中的当前字符。

最后,我们使用内置len函数打印变量a的整个长度。

早些时候,我们提到符文是int32的别名,可以由一到四个字节组成。 世界字符需要三个字节来定义,当通过UTF-8字符串进行测距时,索引会相应地移动。 这就是i打印出来时不连续的原因。

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

如您所见,长度超过了字符串的范围。

你不会总是使用UTF-8字符串,但是当你这样做时,你现在就会明白为什么它们是符文而不是单个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)
[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)

输出看起来与我们创建的列表完全相同:

[shark cuttlefish squid mantis shrimp]

我们可以使用append关键字将一个项添加到切片中。 以下命令将将seahorse的字符串值添加到切片:

seaCreatures = append(seaCreatures, "seahorse")

您可以通过打印来验证它是否已添加:

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

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

地图

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

map[key]value{}

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

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

您会注意到除了花括号之外,整个地图中还有冒号。 冒号左边的单词是键。 键可以是Go中的任何类似类型。 可比类型是原始类型,如stringsints等。基本类型由语言定义,而不是通过组合任何其他类型构建。 虽然它们可以是用户定义的类型,但最好的做法是保持它们简单以避免编程错误。 上面字典中的键是: nameanimalcolorlocation

冒号右边的单词是值。 值可以包含任何数据类型。 上面字典中的值是: Sammysharkblueocean

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

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

如果我们想要隔离Sammy的颜色,我们可以通过调用sammy["color"] 让我们打印出来:

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

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

结论

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