第八章:变量、常量和基础类型
本篇翻译自《Practical Go Lessons》 Chapter 8: Variables, constants and basic types
1 你将在本章中学到什么?
- 什么是变量?我们为什么需要它们?
- 什么是类型?
- 如何创建变量?
- 如何给变量赋值?
- 什么是常量?常量和变量有什么区别?
- 如何定义常量?
- 如何使用常量?
2 涵盖的技术概念
- 变量
- 常量
- 类型
- 无类型常量
3 变量是内存中的一个空间
变量是计算机内存中的一个空间,可以包含一段可更改的数据。“variable”一词来自拉丁语“variabilis”,意思是“可变的”。在程序中,我们可以创建变量用来存储信息,以备后用。
例如,我们想要记录酒店的客人数量,“客人数量”会是一个可变的数值。我们可以创建变量来存储这类信息,如图:
4 变量存储在哪里?
我们之前讨论过 ROM、RAM 和辅助存储器。那么要把 GO 的变量存储在哪里呢?答案很简单,你无法选择,编译器会帮你处理好!
5 变量标识符(变量名)
在大多数编程语言(以及 Go 中)中,当我们创建一个变量时,我们将它与一个标识符(identifier) 相关联。标识符是变量的“名称”。标识符是变量的“名称”。我们在程序中使用标识符来快速访问变量。标识符由字母和数字组成。变量的标识符将在程序内部使用,以指定存储在其中的值。标识符必须简短且具有描述性。
要创建标识符,程序员可以随意明明。但他们必须遵守那些简单的规则:
- 标识符只能由 Unicode 字符和数字组成,如:1,2,3,A, B, b, Ô ...
- 标识符的开头必须是 Unicode 字符或者下划线 "_",不能以数字开头
- 某些标识符无法使用,因为它们被语言用作保留词
Go 语言中的保留词由:break, default, func, interface, select, case, defer, go, map, struct, chan, else, goto, package, switch, const, fallthrough, if, range, type, continue, for, import, return, var
numberOfGuests
就是一个合法的变量标识符,反过来,113Guests
是不合法,因为它以数字作为开头。其实,变量的命名也是一门学问,要想出“见起名,知其义”的变量标识符是很困难。
6 基础类型
我们可以将信息存储到变量中。但“信息”太宽泛了,我们必须更精确才行。我们是否需要存储数字 (1, 2000, 3)、浮点数 (2.45665)、文本(“Room 112 non-smoking”)?我们需要类型的概念来规定该类型的变量能被分配的值是什么。
Go 语言预先声明了一组你可以立即在程序中使用的基本类型。你也可以定义你的类型(我们稍后会看到)。现在,我们来看一下最常用的类型:
- 字符串
- 类型名:string
- 例:"management office", "room 265",...
- 无符号整型
- 类型名:uint, uint8, uint16, uint32, uint64
- 例:2445, 676, 0, 1, ...
- 整型
- 类型名:int, int8, int16, int32, int64
- 例:-1245, 65, 78, ...
- 布尔类型
- 类型名:bool
- 例:true, false
- 浮点数
- 类型名:float32, float64
- 例:12.67
6.1 关于数字 8、16、32 和 64
你可能已经注意到我们有五种类型的整数:int、int8、int16、int32、int64。无符号整数也是如此,我们有 uint、uint8、uint16、uint32 和 uint64。浮点数的选择更加有限:我们可以使用 float32 或 float64。
如果要存储没有符号的数字,可以使用无符号整数类型。这里有 5 种可供选择:
- uint8
- uint16
- uint32
- uint64
- uint
除了最后一个,每一个都附加了一个数字。该数字对应于分配给存储它的内存位数。
如果你读过第一部分,你就会知道:
- 8位内存,我们可以存储从0到2{7}+2+...+2^{0}=2552的十进制数 7 +2 6 +...+2 0 =255。
- 使用 16 位(2 个字节),我们可以存储从 0 到 2{15}+2+...+2^{0}=65,535
- 使用 32 位(4 个字节),我们可以存储从 0 到 2{31}+2+...+2^{0}=4,294,967,295
- 使用 64 位(8 个字节),我们可以存储从 0 到 2{63}+2+...+2^{0}=18,446,744,073,709,551,615
你可以注意到 64 位的最大十进制值非常高。记住!如果你需要存储不超过 255 的值,请使用 uint8 而不是 uint64。否则,你将浪费存储空间(因为你将只使用内存中分配的 64 位中的 8 位!)
最后一种类型是 uint。如果你在程序中使用此类型,则为你的无符号整数分配的内存将至少为 32 位。最终是多少位将取决于将运行该程序的系统。如果是32位系统,就相当于 uint32。如果系统是 64 位,那么 uint 的存储容量将与 uint64 相同。(为了更好的理解32位和64位的区别,可以看前一章)
7 变量声明
如果你想在你的程序中使用一个变量,你需要先声明它。
7.1 声明变量时执行的三个动作
当你声明一个变量时,它会进行:
- 将标识符绑定到变量
- 将类型绑定到变量
- 将变量值初始化为类型的默认值
当你定义变量并设定类型时,Go 会为你初始化变量的值设为类型默认值。
7.2 没有初始化的变量声明
在上图中,你可以看到如何声明变量。在第一个示例中,我们声明了一个名为 roomNumber 的 int 类型变量。在第二个中,我们在同一行中声明了两个变量:roomNumber 和 floorNumber。它们是 int 类型。它们的值为 0(这是 int 类型的零值)。
package main
import "fmt"
func main() {
var roomNumber, floorNumber int
fmt.Println(roomNumber, floorNumber)
var password string
fmt.Println(password)
}
程序输出:
0 0
字符串类型的变量 password 用字符串类型的零值初始化,即空字符串""。变量 roomNumber 和 floorNumber 被初始化为 int 类型的零值,即 0。
程序输出的第一行是 fmt.Println(roomNumber,floorNumber) 的结果。第二行是 fmt.Println(password) 的结果。
7.3 初始化的变量声明
你还可以声明一个变量并直接初始化其值。上图描述了可能的语法。让我们举个例子:
package main
import "fmt"
func main() {
var roomNumber, floorNumber int = 154, 3
fmt.Println(roomNumber, floorNumber)
var password = "notSecured"
fmt.Println(password)
}
在 main 函数中,第一条语句声明了两个变量 roomNumber 和 floorNumber。它们是 int 类型并用值 154 和 3 初始化。然后程序将打印这些变量。
在等号的左边有一个表达式或一个表达式列表。我们将在另一部分详细介绍表达式。
然后我们定义变量password
,我们用值“notSecured”初始化它。注意这里没有写类型,Go 将赋予变量初始化值的类型。这里“notSecured”的类型是一个字符串;因此,变量password
的类型是字符串。
7.4 更简短的变量声明
简短的语法消除了var
关键字,=
符号换成了:=
。你还可以使用此语法一次定义多个变量:
roomNumber := 154
类型没有明确写入,编译器将从表达式(或表达式列表)中推断出它。
这里有一个例子:
package main
import "fmt"
func main() {
roomNumber, floorNumber := 154, 3
fmt.Println(roomNumber, floorNumber)
}
警告:短变量声明不能用在函数外部!
// will not compile
package main
vatRat := 20
func main(){
}
警告:你不能将值 nil 用于短变量声明,编译器无法推断变量的类型。
8 什么是常量?
constant 来自拉丁语“constare”,意思是“站稳脚跟”。常量是程序中的一个值,它会保持不变,在执行过程中不会改变。一个变量可以在运行时改变;一个常量不会改变;它将保持不变。
例如,我们可以在一个常量中存储:
- 我们程序的版本。例如:“1.3.2”。该值将在程序运行时保持稳定。当我们编译另一个版本的程序时,我们将更改此值。
- 程序的构建时间。
- 电子邮件模板(如果我们的应用程序无法配置)。
- 一条报错信息。
总之,当你确定在程序执行期间永远不需要更改值时,请使用常量存储,常量是不可变的。常量有两种形式:类型化和非类型化。
9 有类型常量
这就是一个有类型常量:
const version string = "1.3.2"
关键字 const 向编译器表明我们将定义一个常量。在 const 关键字之后,设置了常量的标识符。在上面的例子中,标识符是 “version”。类型是明确定义的(这里是字符串)以及常量的值(以表达式的形式)。
10 无类型常量
这就是一个无类型常量:
const version = "1.3.2"
一个无类型常量:
- 没有类型
- 有一个默认值
- 没有限制
10.1 一个无类型常量没有类型 ...
举个例子来说明第一点(无类型常量没有类型)
package main
import "fmt"
func main() {
const occupancyLimit = 12
var occupancyLimit1 uint8
var occupancyLimit2 int64
var occupancyLimit3 float32
// assign our untyped const to an uint8 variable
occupancyLimit1 = occupancyLimit
// assign our untyped const to an int64 variable
occupancyLimit2 = occupancyLimit
// assign our untyped const to an float32 variable
occupancyLimit3 = occupancyLimit
fmt.Println(occupancyLimit1, occupancyLimit2, occupancyLimit3)
}
程序输出:
12 12 12
在这个程序中,我们首先定义一个无类型常量,它的名称是 occupancyLimit,其值为 12。此处,常量没有特定的类型,只是被设成了一个整数值。
然后我们定义了 3 个变量:occupancyLimit1、occupancyLimit2、occupancyLimit2(这些变量的类型是 uint8、int64、float32)。
然后我们将 occupancyLimit 的值分配给这些变量。我们的程序编译完成,说明我们常量的值可以放入不同类型的变量中!
如果将 occupancyLimit 的值改为 256,编译会报错,想想为什么吧?
10.2 ... 但必要时有默认类型
为了理解默认类型的概念,让我们再举一个例子
package main
import "fmt"
func main() {
const occupancyLimit = 12
var occupancyLimit4 string
occupancyLimit4 = occupancyLimit
fmt.Println(occupancyLimit4)
}
在这个程序中,我们定义了一个常量 occupancyLimit,它的值为 12(一个整数)。我们定义了一个字符串类型的变量 occupancyLimit4。然后我们尝试将常量的值分配给 occupancyLimit4。
我们尝试将整数转换为字符串。这个程序会编译吗?答案是不!编译错误是:
./main.go:10:19: cannot use occupancyLimit (type int) as type string in assignment
无类型常量具有默认类型,该类型由编译时分配给它的值定义。在我们的示例中,occupancyLimit 的默认类型为 int 。不能将 int 分配给字符串变量。
无类型常量默认类型是:
- bool(布尔值)
- rune
- int(整数值)
- float64(浮点值)
- complex128(复数值)
- string(字符串值)
package main
func main() {
// default type is bool
const isOpen = true
// default type is rune (alias for int32)
const MyRune = 'r'
// default type is int
const occupancyLimit = 12
// default type is float64
const vatRate = 29.87
// default type is complex128
const complexNumber = 1 + 2i
// default type is string
const hotelName = "Gopher Hotel"
}
### 10.3 无类型常量没有限制
无类型常量在需要时没有类型和默认类型。无类型常量的值可能会溢出其默认类型。这样的常量没有类型;因此,它不依赖于任何类型限制。让我们举个例子:
```Go
package main
func main() {
// maximum value of an int is 9223372036854775807
// 9223372036854775808 (max + 1 ) overflows int
const profit = 9223372036854775808
// the program compiles
}
在这个程序中,我们创建了一个无类型常量叫 profit,它的值是在这个程序中,我们创建了一个无类型常量叫 profit,它的值是 9223372036854775808 ,这个数值超过了 int(在 64 位机器上是 int64) 可允许的最大值:9223372036854775807。这个程序可以完美编译。但是当我们尝试将此常量值分配给类型化变量时,程序将无法编译。让我们举一个例子来证明它:
package main
import "fmt"
func main() {
// maximum value of an int is 9223372036854775807
// 9223372036854775808 (max + 1 ) overflows int
const profit = 9223372036854775808
var profit2 int64 = profit
fmt.Println(profit2)
}
该程序定义了一个 int64 类型的变量 profit2。然后,我们尝试将无类型的常量 profit 的值分配给 profit2。
让我们试试编译程序:
$ go build main.go
# command-line-arguments
./main.go:9:7: constant 9223372036854775808 overflows int64
我们得到一个编译错误,这很好。我们试图做的事情是非法的。
10.4 为什么要使用常量
- 可以提升你程序的可读性
如果选择得当,常量标识符将为读者提供比原始值更多的信息
比较一下:
loc, err := time.LoadLocation(UKTimezoneName)
if err != nil {
return nil, err
}
loc, err := time.LoadLocation("Europe/London")
if err != nil {
return nil, err
}
我们使用常量 UKTimezoneName 而不是原始值“Europe/London”。我们向读者隐藏了时区字符串的复杂性。另外,读者会明白我们的意图:我们要加载英国的位置。
- 你提供付出该值的潜在可能(由另一个程序或在你的程序中)
- 编译器可能会改进生成的机器代码。你对编译器说这个值永远不会改变;如果编译器很聪明(确实如此),它就会巧妙地使用它。
11 选择标识符(变量名、常量名)
命名变量和常量不是一件容易的事。当你选择一个名称时,你必须确保所选名称提供了有关其名称的正确信息量。如果你选择了一个好的标识符名称,在同一项目上工作的其他开发人员会很感激,因为阅读代码会更容易。我们说“传达正确的信息”时,“正确”这个词是含糊的。命名程序结构没有科学规则。即使没有科学规则,我也可以给你一些我们社区共享的建议。
-
避免使用一个字母命名:它传达的有关存储内容的信息太少。
例外情况是计数器通常被命名为 k、i 和 j(在循环内部,我们将在后面介绍) -
使用驼峰格式命名:这是 Go 社区中一个完善的约定。
相比于occupancy_limit
和occupancy-limit
,occupancyLimit
更好。 -
不要超过两个词
profitValue
就很好了,profitValueBeforeTaxMinusOperationalCosts
就太长了 -
避免在名称中提及类型
descriptionString
不好,description
更值得选择。
Go 已经是静态类型的;你不需要向读者提供类型信息。
12 实际应用
12.1 任务
12.1.1 编程
编写一个程序,它要做下面的事:
- 创建一个名为
hotelName
且值为"Gopher Hotel"
的字符串常量 - 创建两个分别包含 24.806078 和 -78.243027 的无类型常量。这两个常量的名称是
longitude
和latitude
。 (巴哈马的某个地方) - 创建一个名为
occupancy
的int
类型变量,其初始化值为 12。 - 打印
hotelName
、longitude
、latitude
,并在新行打印occupancy
。
12.1.2 有奖问答
longitude
和latitude
的默认类型是什么?latitude
的类型是什么?- 变量
occupancy
是int
类型,它是 64 位、32 位还是 8 位整型?
12.2 参考答案
12.2.1 编程
package main
import "fmt"
func main() {
const hotelName string = "Gopher Hotel"
const longitude = 24.806078
const latitude = -78.243027
var occupancy int = 12
fmt.Println(hotelName, longitude, latitude)
fmt.Println(occupancy)
}
我们从类型常量 hotelName
的定义和 longitude
和 latitude
(非类型化)的定义开始。然后我们定义变量 occupancy
(类型:int
)并将其值设置为 12。请注意,另一种语法也是可以的:
- 简短的变量声明
occupancy := 12
- 类型可以在标准声明中省略
var occupancy = 12
- 我们可以分两步进行变量的声明和赋值
var occupancy int
occupancy = 12
12.2.2 有奖问答答案
longitude
和latitude
的默认类型是什么?
float64latitude
的类型是什么?
没有类型,latitude
也一样(它们都是无类型常量)- 变量
occupancy
是int
类型,它是 64 位、32 位还是 8 位整型?
取决于编译的计算机。int
是一种在 32 位计算机上具有特定于实现的大小的类型,它将是一个 32 位整数 (int32);在 64 位计算机上,它将是一个 64 位整数 (int64)。
13 随堂测试
13.1 问题
- 什么是标识符?
- 标识符应该以哪种类型的字符开头?
- 变量的类型是什么?
- 什么是字节?
- 当你声明一个类型 bool 变量时,它的值是什么(在初始化之后)?
- 无类型常量的三个主要特征是什么?
13.2 答案
- 什么是标识符?
标识符是由字母和数字组成的一组字符,用于在程序中访问变量。 - 标识符应该以哪种类型的字符开头?
以字母或下划线开头 - 变量的类型是什么?
变量的类型是允许的变量值的集合 - 什么是字节?
一个字节由 8 位二进制数字组成 - 当你声明一个类型 bool 变量时,它的值是什么(在初始化之后)?
false,当你声明一个变量时,它被初始化为其类型的默认值(零值)。 - 无类型常量的三个主要特征是什么?
- 无类型
- 有默认类型
- 无限制(如:可以溢出整型最大值)
14 关键要点
- 变量或常量的名称称为标识符。
- 标识符一般用驼峰写法
- 变量和常量允许你在内存中保存一个值
- 常量是不可变的,这意味着我们不能在程序执行期间改变他们的值
- 变量有一个类型,它定义了它们可以保存的值集
- 当你创建一个变量时,它的值被初始化为它的类型的零值
- 这一点很重要
- 它可能是错误的来源
- Go 中没有未初始化的变量
- Go 里有两类常量
- 有类型常量
- 无类型常量
没有类型,只有一个默认类型,并且可以溢出它们的默认类型