变量、常量和基础类型
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^{6}+...+2^{0}=2552的十进制数 7 +2 6 +...+2 0 =255。
使用 16 位(2 个字节),我们可以存储从 0 到 2^{15}+2^{14}+...+2^{0}=65,535
使用 32 位(4 个字节),我们可以存储从 0 到 2^{31}+2^{30}+...+2^{0}=4,294,967,295
使用 64 位(8 个字节),我们可以存储从 0 到 2^{63}+2^{62}+...+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 里有两类常量
有类型常量
无类型常量 没有类型,只有一个默认类型,并且可以溢出它们的默认类型
作者:Zioyi
链接:https://juejin.cn/post/7032145082748928014