第八章:變量、常量和基礎類型


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{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 聲明變量時執行的三個動作

當你聲明一個變量時,它會進行:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM