第八章:变量、常量和基础类型

image

本篇翻译自《Practical Go Lessons》 Chapter 8: Variables, constants and basic types

1 你将在本章中学到什么?

  • 什么是变量?我们为什么需要它们?
  • 什么是类型?
  • 如何创建变量?
  • 如何给变量赋值?
  • 什么是常量?常量和变量有什么区别?
  • 如何定义常量?
  • 如何使用常量?

2 涵盖的技术概念

  • 变量
  • 常量
  • 类型
  • 无类型常量

3 变量是内存中的一个空间

变量是计算机内存中的一个空间,可以包含一段可更改的数据。“variable”一词来自拉丁语“variabilis”,意思是“可变的”。在程序中,我们可以创建变量用来存储信息,以备后用。

例如,我们想要记录酒店的客人数量,“客人数量”会是一个可变的数值。我们可以创建变量来存储这类信息,如图:
image

4 变量存储在哪里?

我们之前讨论过 ROM、RAM 和辅助存储器。那么要把 GO 的变量存储在哪里呢?答案很简单,你无法选择,编译器会帮你处理好!

5 变量标识符(变量名)

在大多数编程语言(以及 Go 中)中,当我们创建一个变量时,我们将它与一个标识符(identifier) 相关联。标识符是变量的“名称”。标识符是变量的“名称”。我们在程序中使用标识符来快速访问变量。标识符由字母和数字组成。变量的标识符将在程序内部使用,以指定存储在其中的值。标识符必须简短且具有描述性。

要创建标识符,程序员可以随意明明。但他们必须遵守那些简单的规则:

  1. 标识符只能由 Unicode 字符和数字组成,如:1,2,3,A, B, b, Ô ...
  2. 标识符的开头必须是 Unicode 字符或者下划线 "_",不能以数字开头
  3. 某些标识符无法使用,因为它们被语言用作保留词

    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 声明变量时执行的三个动作

当你声明一个变量时,它会进行:

  1. 标识符绑定到变量
  2. 类型绑定到变量
  3. 将变量值初始化类型默认值

当你定义变量并设定类型时,Go 会为你初始化变量的值设为类型默认值。

7.2 没有初始化的变量声明

image

在上图中,你可以看到如何声明变量。在第一个示例中,我们声明了一个名为 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 初始化的变量声明

image

你还可以声明一个变量并直接初始化其值。上图描述了可能的语法。让我们举个例子:

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 更简短的变量声明

image
简短的语法消除了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”。类型是明确定义的(这里是字符串)以及常量的值(以表达式的形式)。
image

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 为什么要使用常量

  1. 可以提升你程序的可读性
    如果选择得当,常量标识符将为读者提供比原始值更多的信息
    比较一下:
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”。我们向读者隐藏了时区字符串的复杂性。另外,读者会明白我们的意图:我们要加载英国的位置。

  1. 你提供付出该值的潜在可能(由另一个程序或在你的程序中)
  2. 编译器可能会改进生成的机器代码。你对编译器说这个值永远不会改变;如果编译器很聪明(确实如此),它就会巧妙地使用它。

11 选择标识符(变量名、常量名)

命名变量和常量不是一件容易的事。当你选择一个名称时,你必须确保所选名称提供了有关其名称的正确信息量。如果你选择了一个好的标识符名称,在同一项目上工作的其他开发人员会很感激,因为阅读代码会更容易。我们说“传达正确的信息”时,“正确”这个词是含糊的。命名程序结构没有科学规则。即使没有科学规则,我也可以给你一些我们社区共享的建议。

  1. 避免使用一个字母命名:它传达的有关存储内容的信息太少。
    例外情况是计数器通常被命名为 k、i 和 j(在循环内部,我们将在后面介绍)

  2. 使用驼峰格式命名:这是 Go 社区中一个完善的约定。
    相比于 occupancy_limitoccupancy-limitoccupancyLimit 更好。

  3. 不要超过两个词
    profitValue 就很好了, profitValueBeforeTaxMinusOperationalCosts 就太长了

  4. 避免在名称中提及类型
    descriptionString不好,description更值得选择。
    Go 已经是静态类型的;你不需要向读者提供类型信息。

12 实际应用

12.1 任务

12.1.1 编程

编写一个程序,它要做下面的事:

  • 创建一个名为 hotelName 且值为 "Gopher Hotel" 的字符串常量
  • 创建两个分别包含 24.806078 和 -78.243027 的无类型常量。这两个常量的名称是 longitudelatitude。 (巴哈马的某个地方)
  • 创建一个名为 occupancyint 类型变量,其初始化值为 12。
  • 打印 hotelNamelongitudelatitude,并在新行打印 occupancy

12.1.2 有奖问答

  1. longitudelatitude 的默认类型是什么?
  2. latitude 的类型是什么?
  3. 变量 occupancyint 类型,它是 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 的定义和 longitudelatitude(非类型化)的定义开始。然后我们定义变量 occupancy(类型:int)并将其值设置为 12。请注意,另一种语法也是可以的:

  • 简短的变量声明
occupancy := 12
  • 类型可以在标准声明中省略
var occupancy = 12
  • 我们可以分两步进行变量的声明和赋值
var occupancy int
occupancy = 12

12.2.2 有奖问答答案

  1. longitudelatitude 的默认类型是什么?
    float64
  2. latitude 的类型是什么?
    没有类型,latitude也一样(它们都是无类型常量)
  3. 变量 occupancyint 类型,它是 64 位、32 位还是 8 位整型?
    取决于编译的计算机。int 是一种在 32 位计算机上具有特定于实现的大小的类型,它将是一个 32 位整数 (int32);在 64 位计算机上,它将是一个 64 位整数 (int64)。

13 随堂测试

13.1 问题

  1. 什么是标识符?
  2. 标识符应该以哪种类型的字符开头?
  3. 变量的类型是什么?
  4. 什么是字节?
  5. 当你声明一个类型 bool 变量时,它的值是什么(在初始化之后)?
  6. 无类型常量的三个主要特征是什么?

13.2 答案

  1. 什么是标识符?
    标识符是由字母和数字组成的一组字符,用于在程序中访问变量。
  2. 标识符应该以哪种类型的字符开头?
    以字母或下划线开头
  3. 变量的类型是什么?
    变量的类型是允许的变量值的集合
  4. 什么是字节?
    一个字节由 8 位二进制数字组成
  5. 当你声明一个类型 bool 变量时,它的值是什么(在初始化之后)?
    false,当你声明一个变量时,它被初始化为其类型的默认值(零值)。
  6. 无类型常量的三个主要特征是什么?
    • 无类型
    • 有默认类型
    • 无限制(如:可以溢出整型最大值)

14 关键要点

  • 变量或常量的名称称为标识符。
  • 标识符一般用驼峰写法
  • 变量和常量允许你在内存中保存一个值
  • 常量是不可变的,这意味着我们不能在程序执行期间改变他们的值
  • 变量有一个类型,它定义了它们可以保存的值集
  • 当你创建一个变量时,它的值被初始化为它的类型的零值
    • 这一点很重要
    • 它可能是错误的来源
    • Go 中没有未初始化的变量
  • Go 里有两类常量
    • 有类型常量
    • 无类型常量
      没有类型,只有一个默认类型,并且可以溢出它们的默认类型
posted @ 2021-11-29 11:40  Zioyi  阅读(393)  评论(0编辑  收藏  举报