本篇翻譯自《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{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 里有兩類常量
- 有類型常量
- 無類型常量
沒有類型,只有一個默認類型,並且可以溢出它們的默認類型