Go 語言編程 — 變量與常量

目錄

值語義和引用語義

在 C 語言中,所有變量都是值語義的。變量名所指向的內存位置,就是變量值所儲存的地方。指針變量亦如此,指針變量名所指向的內存位置,就是儲存指針變量值(一個內存地址)的地方。

在 Python 中,所有變量都是引用語義的。變量名所指向的內存位置所存儲的內容,實際上是變量值在內存中的地址。

而 Golang 則顯式的值語義和引用語義都引入了程序設計中,布爾型、數字類型、字符串類型都屬於值語義,變量名指向變量值。當使用 = 賦值運算符將一個變量的值賦值給另一個變量時,如:j = i,實際上是在內存中將變量 i 的數值進行了拷貝:
在這裏插入圖片描述
而 Golang 中更復雜的數據類型通常使用引用語義。一個引用類型的變量 r1 存儲的是 r1 的值所在的內存地址,或內存地址中的首地址。當使用賦值語句 r2 = r1 時,只有引用(地址)被複制,如果 r1 的值被改變了,那麼這個值的所有引用都會指向被修改後的內容,在這個例子中,r2 也會受到影響:

在這裏插入圖片描述

變量(var)

Golang 使用關鍵字 var 來聲明一個變量。格式如下:

var identifier type

也可以一次聲明多個變量,稱爲 “並行賦值”,也可用於接受多返回值函數的返回值:

var identifier1, identifier2 type

聲明全局變量:

// 這種因式分解關鍵字的寫法一般用於聲明全局變量
var (
    identifier1 type
    identifier2 type
)

注意

  1. 不可以在同一個代碼塊中重複聲明一個同名變量。
  2. 在函數塊中,不可以僅聲明,但不可以一個變量。
  3. 全局變量是可以僅聲明,而不使用的。
  4. 並行賦值時,可以在賦值運算符的左側使用空白標識符 “_”,表示拋棄值,常用接受多返回值的函數調用,進行對齊補位。空白標識符的本質是一個只寫(wo)變量,你不能得到它的值。這樣做是因爲 Golang 中你必須使用所有被聲明的變量,但有時你並不需要使用從一個函數返回中得到的所有返回值。

指定數據類型的變量聲明

與 C 語言一般,可以在一條語句中完成變量的聲明、定義以及初始化。如果沒有初始化,則變量默認爲零值。

示例:

package main

import "fmt"

func main() {
    var num int
    fmt.Println(num)
    
    var name string = "fanguiju"
    fmt.Println(name)
}

注意,不同數據類型的零值亦不相同。例如:布爾類型的零值爲 false,數值類型的零值爲 0,字符串的零值爲空(""),以下幾種類型爲 nil:

var a *int                   // 指針
var a []int                  // 數組
var a map[string] int        // Map 集合
var a chan int               // channel
var a func(string) int       // 函數
var a error                  // 接口

根據初始化數值自動判定數據類型的變量定義

示例:

package main

import "fmt"

func main() {
    var b = true
    fmt.Println(b)
}

簡短形似,使用 := 賦值運算符的變量聲明

上述可知,Golang 支持在初始化變量的時候省略顯式書寫數據類型。基於這樣的前提,聲明語句寫上 var 關鍵字就顯得有些多餘了。所以,Golang 可以使用 := 賦值運算符來省略書寫 var 關鍵字和數據類型的預定義標識符,是一種簡易的寫法,通過初始化的數值來確定變量的數據類型。

注意,:= 的簡短形式只能在函數中使用,否則編譯錯誤。

示例:

package main

import "fmt"

func main() {
    f := "fanguiju"  // 相對於:var f string = "fanguiju"
    fmt.Println(f)
}

但需要注意的是,:= 運算符的左值必須是新聲明的變量。不能使用 := 來爲已經聲明或定義過的變量進行賦值,會導致編譯錯誤。

var intVal int

intVal := 1    // 編譯錯誤

同時聲明多個變量

例如:

var vname1, vname2, vname3 string = "A", "B", "C"
// or
var vname1, vname2, vname3 = "A", "B", "C"
// or
vname1, vname2, vname3 := "A", "B", "C"

常量(const)

Golang 中使用 const 關鍵字來聲明常量,常量的數據類型只可以是:布爾類型、數字類型和字符串類型。

聲明格式:

const identifier type = value

與變量聲明類似的,可以省略顯式書寫數據類型:

const b = "abc"

也可以進行並行賦值:

const c_name1, c_name2 = value1, value2

Golang 內置的標準函數中:len()、cap()、unsafe.Sizeof() 返回的都是一個常量,所以應該直接賦值與一個常量:

package main

import "unsafe"

const (
    a = "abc"
    b = len(a)
    c = unsafe.Sizeof(a)
)

func main() {
    println(a, b, c)
}

使用 iota 常量來實現 “枚舉類型”

Golang 原生不具備 C 語言中的枚舉類型關鍵字 enum,但可以使用 const 關鍵字和 iota 常量來實現。

通常的,使用因式分解的方式聲明多個常量時,後一個常量成員會繼承前一個常量成員的數值(若前一個成員有初始化,後一個成員沒有初始化的情況下),如下:

package main

import "fmt"

const (
    Unknown = 1
    Female
    Male
)

func main() {
    fmt.Println(Unknown, Female, Male)
}

結果:

1 1 1

再者,iota 是一個特殊的常量,其數值可以被編譯器所修改。iota 在 const 關鍵字出現時會被重置爲 0,const 中每新增一行常量聲明會使 iota 計數一次。所以,iota 的本質可理解爲 const 語句塊中的行索引。

package main

import "fmt"

const (
    a = iota
    b = iota
    c = iota
)

func main() {
    fmt.Println(a, b, c)
}

結果:

0 1 2

也可以簡寫爲如下形式:

const (
    a = iota
    b
    c
)

示例:

package main

import "fmt"

func main() {
    const (
            a = iota   // 0
            b          // 1
            c          // 2
            d = "ha"   // 獨立值,iota += 1
            e          // "ha"   iota += 1
            f = 100    // iota +=1
            g          // 100  iota +=1
            h = iota   // 7,恢復計數
            i          // 8
    )
    
    fmt.Println(a, b, c, d, e, f, g, h, i)
}

運行結果:

0 1 2 ha ha 100 100 7 8

再看個有趣的的 iota 示例:

package main

import "fmt"
const (
    i = 1 << iota    // << 爲位左移運算符
    j = 3 << iota
    k
    l
)

func main() {
    fmt.Println("i=", i)
    fmt.Println("j=", j)
    fmt.Println("k=", k)
    fmt.Println("l=", l)
}

結果:

i= 1
j= 6
k= 12
l= 24

iota 表示從 0 開始自動加 1,所以 i=1<<0, j=3<<1(<< 表示左移的意思),即:i=1, j=6,這沒問題,關鍵在 k 和 l,從輸出結果看 k=3<<2,l=3<<3。

簡單表述:

  • i=1:左移 0 位,二進制 001,不變仍爲 1;
  • j=3:左移 1 位,二進制 110,即 6;
  • k=3:左移 2 位,二進制 1100,即 12;
  • l=3:左移 3 位,二進制 11000,即 24。

作用域

作用域是已聲明標識符所表示的常量、類型、變量、函數或包在源代碼中的作用範圍。

局部變量

在函數體內聲明的變量稱之爲局部變量,它們的作用域只在函數體內,形式參數和返回值變量也是局部變量。

package main

import "fmt"

func main() {
   /* 聲明局部變量 */
   var a, b, c int

   /* 初始化參數 */
   a = 10
   b = 20
   c = a + b

   fmt.Printf ("結果: a = %d, b = %d and c = %d\n", a, b, c)
}

全局變量

在函數體外聲明的變量稱之爲全局變量,全局變量可以在整個包甚至外部包(被導出後)使用。全局變量可以在任何函數中使用。

package main

import "fmt"

/* 聲明全局變量 */
var g int

func main() {

   /* 聲明局部變量 */
   var a, b int

   /* 初始化參數 */
   a = 10
   b = 20
   g = a + b

   fmt.Printf("結果: a = %d, b = %d and g = %d\n", a, b, g)
}

注意:當全局變量和局部變量重名時,優先考慮局部變量。

package main

import "fmt"

/* 聲明全局變量 */
var g int = 20

func main() {
   /* 聲明局部變量 */
   var g int = 10

   fmt.Printf ("結果: g = %d\n",  g)
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章