Part4

變量的聲明

var s string
var s2 = string("shijie")
s1 := "wenxuwan"
fmt.Println(s,s1,s2)

第一種方式是最傳統的變量聲明方式,可以顯式的看到變量的類型。
第二種是利用go語言的類型推斷,在聲明s2的時候我們不需要定義s2的類型,它會根據後面表達式返回類型來自動判斷s2類型
第三種是在go語言的推斷上加了點語法糖,只能在函數內部使用,或者寫forif,switch語句的時候用在初始化語句中來聲明一些臨時的變量。不能作爲全局聲明。

go語言的類型推斷有哪些好處

1. 可以不用自己敲那麼多聲明
2. 代碼重構的時候(代碼重構指的是“不改變某個程序與外界的交互方式和歸結,只改變函數內部實現。也就是暴露出來的接口的參數,名字,返回值不變”)方便,不需要關注函數內部返回值的變化。
3. 因爲go語言的是靜態語言,變量類型在編譯期間就確定了,所以不會影響到函數的執行效率。
  • 函數重聲明主要是用短變量聲明對同一個代碼塊下面的變量進行聲明

重聲明的前提條件

1.再次聲明的時候要和前面聲明的類型一致
2.聲明和重聲明要在同一代碼塊下,不然就相當於局部變量覆蓋全局變量
3.重聲明只有在短變量聲明的時候纔會發生,不然用var聲明兩個同名的變量會提示衝突
4.重聲明的時候一定要保證至少有一個變量是新的。
    func main() {
        var s string
        var s2 = string("old")
        s2 := string("new") //error,no new variables on left side of :=
        fmt.Println(s,s2)
    }

思考題目

如果與當前變量重名的是外層代碼中的變量,這意味着什麼?

var s2 = string("out")
func main() {
    var s string
    {
        s2 := string("internal")
        fmt.Println(s,s2)
    }
    fmt.Println(s2)
}

output: internal  out
局部變量會隱藏外部變量

小結

使用關鍵字var和短變量聲明,都可以實現對變量的“聲明並賦值”。
前者可以被用在任何地方,而後者只能被用在函數或者其他更小的代碼塊中。
前者無法對已有的變量進行聲明,就是無法處理新舊變量混在一起的情況。可以使用後者的變量重聲明實現。
共同點是,都是基於“類型推斷”。

package main

import (
	"flag"
	"fmt"
)

func main() {
	var name string                                                   // [1]
	flag.StringVar(&name, "name", "everyone", "The greeting object.") // [2]

	// 方式1。
	//var name = flag.String("name", "everyone", "The greeting object.")

	// 方式2。
	//name := flag.String("name", "everyone", "The greeting object.")

	flag.Parse()
	fmt.Printf("Hello, %v!\n", name)

	// 適用於方式1和方式2。
	//fmt.Printf("Hello, %v!\n", *name)
}
package main

import (
	"flag"
	"fmt"
)

func main() {
	var name = getTheFlag()
	flag.Parse()
	fmt.Printf("Hello, %v!\n", *name)
}

func getTheFlag() *string {
	return flag.String("name", "everyone", "The greeting object.")
}

//上面函數的實現也可以是這樣的。
//func getTheFlag() *int {
//	return flag.Int("num", 1, "The number of greeting object.")
//}

引用變量時的查找過程

首先,會在當前代碼塊中查找變量。不包含任何的子代碼塊。
其次,如果當前代碼塊沒有什麼此變量名,一層一層往上層的代碼塊查找。
最後,如果都找不到,則編譯器會報錯。

不同代碼塊的變量可以重名,並且類型也可以不同。必要時,在使用之前,要先對變量的類型進行檢查。

示例
下面代碼中的container變量,雖然類型不同,但是都可以使用下標[0]、[1]、[2],獲取到值:

package main

import "fmt"

var container = []string{"ZERO", "ONE", "TWO"}

func main() {
    container := map[int]string{0: "zero", 1: "one", 2: "two"}
    fmt.Println(container[0], container[1], container[2])
}

如果,要判斷變量的類型,就要使用“類型斷言”表達式。

類型斷言
語法:x.(T)
其中的x代表要被判斷類型的那個值。T是要判斷的類型。針對上面示例中的類型斷言:

value, ok := interface{}(container).([]string)
上面是一條賦值語句,賦值符號的右邊,就是一個類型斷言表達式。
先把變量container的值轉換爲空接口的值interface{}(container)。然後再判斷他的類型是否爲後面.()中的類型。
有2個返回值,value和ok。ok是布爾類型,代碼類型判斷的結果:

如果是true,被判斷的值自動轉換爲.()中的類型的值,並且賦值給value。
如果是false,value會賦值爲nil,就是空。
不接收ok
這裏ok也是可以沒有的:

value := interface{}(container).([]string)
這樣的話,如果類型不對,就是引發異常panic。

轉爲空接口的語法
在Go語言中,interface{}代表空接口。任何類型的值都可以很方便地被轉換成空接口的值,語法:interface{}(x)。
一對不包裹任何東西的花括號,除了可以代表空的代碼塊之外,還可以用於表示不包含任何內容的數據結構(或者說數據類型)。

字面量

小括號中[]string是一個類型字面量。所謂類型字面量,就是用來表示數據類型本身的若干個字符。
比如:string是表示字符串類型的字面量,uint8是表示8位無符號整數類型的字面量。

優化示例代碼
修改開始的示例,在打印前,先對變量的類型進行判斷,只有map或切片類型才進行打印:

package main

import "fmt"

var container = []string{"ZERO", "ONE", "TWO"}

func main() {
    container := map[int]string{0: "zero", 1: "one", 2: "two"}
    // 打印之前先要做判斷,只有map或者切片類型才能通過
    _, ok1 := interface{}(container).([]string)
    _, ok2 := interface{}(container).(map[int]string)
    if !(ok1 || ok2) {
        fmt.Printf("ERROR: 類型斷言失敗 %T\n", container)
        return
    }
    fmt.Println(container[0], container[1], container[2])
}

另外還有一種switch語句的實現形式:

package main

import "fmt"

var container = []string{"ZERO", "ONE", "TWO"}

func main() {
    container := map[int]string{0: "zero", 1: "one", 2: "two"}
    switch v := interface{}(container).(type) {
    case []string:
        fmt.Println("[]string:", v)
    case map[int]string:
        fmt.Println("map[int]string:", v)
    default:
        fmt.Printf("ERROR: 類型斷言失敗 %T\n", container)
        return
    }
}

類型轉換的坑
類型轉換表達式的語法:T(x)
其中的x可以是一個變量,也可以是一個代表值的字面量(比如1.23和struct{}),還可以是一個表達式。如果是表達式,表達式的結果只能是一個值。
x被叫做源值,它的類型就是源類型。T代表的類型是目標類型。

數值類型間互轉

對於整數類型值、整數常量之間的類型轉換,原則上只要源值在目標類型的可表示範圍內就是合法的。
上面說的只是語法上合法,但是轉換後的結果可能是可坑。比如,如果源整數類型的可表示範圍大,而目標類型的可表示範圍小:

package main

import "fmt"

func main() {
    var srcInt = int16(-255)  // 1111111100000001
    dstInt := int8(srcInt)  // 00000001,簡單粗暴的截掉最前面的8位
    fmt.Println(srcInt, dstInt)
}
/* 執行結果
PS H:\Go\src\Go36\article06\example04> go run main.go
-255 1
PS H:\Go\src\Go36\article06\example04>
*/

在計算機系統中,數值一律用補碼來表示和存儲。原因在於,使用補碼,可以將符號位和數值域統一處理;同時,加法和減法也可以統一處理。補碼就是原碼的各位求反再加1。比如-255:
原碼: 1000 0000 1111 1111
反碼: 1111 1111 0000 0000 最高位是符號位,不反轉。
補碼: 1111 1111 0000 0001
類型轉換的很簡單粗暴,直接把最高的8位截掉,並不處理符號位,結果就是0000 0001,所以轉換後的值就變成1了。

浮點類型轉換
如果把浮點數轉換爲整數,則小數部分會被全部截掉:

package main

import "fmt"

func main() {
    var x = float64(1.9999999)
    y := int(x)
    fmt.Println(x, y)
}
/* 執行結果
PS H:\Go\src\Go36\article06\example05> go run main.go
1.9999999 1
PS H:\Go\src\Go36\article06\example05>
*/

整數轉字符串

直接把一個整數值轉換爲一個string類型的值是可行的。但是,被轉換的整數值應該是一個有效的Unicode碼點,否則轉換的結果將會是"�"。字符’�’的Unicode碼點是U+FFFD。它是Unicode標準中定義的Replacement Character,專用於替換那些未知的、不被認可的以及無法展示的字符。無效的碼點有很多,如果自己要搞一個測試,那麼就用-1吧:

package main

import "fmt"

func main() {
    fmt.Println(string(-1))  // 一個無效的Unicode碼點
    fmt.Println(string(65))  // 字符A
    fmt.Println(string(24464))  // 中文
}

字符串與切片

一個值在從string類型轉爲[]byte類型時,其中UTF-8編碼的字符串會被拆分成零散、獨立的字節。這樣只有ASCII碼的那部分字符是一個字節代碼一個字符的。而其他字符,比如中文(UTF-8裏中文字符用3個字節表示),會被拆開成3個字節。而且由於UTF-8的長度是可變的,這樣還要想辦法判斷那幾個字節應該是一個字符。
可以轉爲[]rune類型,這樣轉換時,每個字符會被拆開成一個個的Unicode字符。

package main

import "fmt"

func main() {
    s := "你好"
    s1 := []byte(s)
    fmt.Println(s1)
    s2 := []rune(s)
    fmt.Println(s2)
    for _, v := range(s1) {
        fmt.Print(string(v))  // 亂碼
    }
    fmt.Println()
    for _, v := range(s2) {
        fmt.Print(string(v))
    }
    fmt.Println()
}
/* 執行結果
PS H:\Go\src\Go36\article06\example07> go run main.go
[228 189 160 229 165 189]
[20320 22909]
ä½ å¥½
你好
PS H:\Go\src\Go36\article06\example07>
*/

別名類型 和 潛在類型

別名類型聲明與類型再定義之間的區別,以及由此帶來的它們的值在類型轉換、判等、比較和賦值操作方面的不同。

別名類型
可以用關鍵字type聲明自定義的各種類型。比如,可以聲明別名類型:

type MyString = string
上面的聲明語句表示,MyString是string類型的別名類型。別名類型與其源類型除了在名稱上以外,都是完全相同的。別名類型主要是爲了代碼重構而存在的。
Go語言的基本類型中就存在兩個別名類型。byte是uint8的別名類型,而rune是int32的別名類型。

潛在類型
另外一種聲明:

type MyString2 string// 注意,這裏沒有等號
這種方式也可以被叫做對類型的再定義。這裏MyString2是一個新的類型,和string是不同的類型。string可以被稱爲MyString2的潛在類型。
潛在類型相同的不同類型的值之間是可以進行類型轉換的。因此,MyString2類型的值與string類型的值可以使用類型轉換表達式進行互轉。
但是,[]MyStrings 和 []string 是不同的潛在類型,不能做類型轉換。
另外,即使是相同的潛在類型,也不能進行判等或比較,變量之間不能賦值。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章