溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Go36-4,5,6-變量

發(fā)布時(shí)間:2020-07-02 22:55:13 來(lái)源:網(wǎng)絡(luò) 閱讀:233 作者:騎士救兵 欄目:編程語(yǔ)言

上篇

Go語(yǔ)言中的程序?qū)嶓w包括變量、常量、函數(shù)、結(jié)構(gòu)體和接口。Go語(yǔ)言是靜態(tài)類型的編程語(yǔ)言,所以我們?cè)诼暶髯兞炕虺A康臅r(shí)候都需要指定它們類型,或者給予足夠的信息以使Go語(yǔ)言能夠推導(dǎo)出它們的類型。

聲明變量

聲明變量的方式:

var name string
var name = "Adam"
name := "Bob"  // 短變量聲明

第三種短變量聲明只能在函數(shù)體內(nèi)部使用。

知識(shí)點(diǎn)

類型推斷
后兩種在聲明的同時(shí)還進(jìn)行了賦值,沒(méi)有顯示的指定類型,而是利用了Go的類型推斷。

代碼重構(gòu)
我們通常把“不改變某個(gè)程序與外界的任何交互方式和規(guī)則,而只改變其內(nèi)部實(shí)現(xiàn)”的代碼修改方式,叫做對(duì)該程序的重構(gòu)。

代碼塊
在Go語(yǔ)言中,代碼塊一般就是一個(gè)由花括號(hào)括起來(lái)的區(qū)域,里面可以包含表達(dá)式和語(yǔ)句。
Go語(yǔ)言本身以及我們編寫的代碼共同形成了一個(gè)非常大的代碼塊,也叫全域代碼塊。

空代碼塊

func main() {}

變量重聲明

變量重聲明,對(duì)已經(jīng)聲明過(guò)的變量再次聲明:

  1. 由于變量的類型在其初始化時(shí)就已經(jīng)確定了,所以對(duì)它再次聲明時(shí)賦予的類型必須與其原本的類型相同,否則會(huì)產(chǎn)生編譯錯(cuò)誤。
  2. 變量的重聲明只能發(fā)生在某一個(gè)代碼塊中。如果與當(dāng)前的變量重名的是外層代碼塊中的變量,那么就是另外一種含義了。
  3. 變量的重聲明只有在使用短變量聲明時(shí)才會(huì)發(fā)生,否則也無(wú)法通過(guò)編譯。如果要聲明新變量,就使用var關(guān)鍵字聲明,用新的變量名。
  4. 被“聲明并賦值”的變量必須有多個(gè),并且其中至少有一個(gè)是新的變量。這時(shí)才可以說(shuō)對(duì)其中的舊變量進(jìn)行了重聲明。

重聲明只在短變量聲明中出現(xiàn),并且是多個(gè)變量的聲明中出現(xiàn)。給新的變量賦值,給舊的變量賦新值。
變量重聲明,允許我們?cè)偈褂枚套兞柯暶鲿r(shí)不用理會(huì)被賦值的多個(gè)變量中是否有包含舊變量。好處是寫代碼時(shí)的便利。

package main

import (
    "os"
    "io"
    "fmt"
)

func main() {
    n1, err := io.WriteString(os.Stdout, "Test1")
    if err != nil {
        fmt.Printf("ERROR: %v\n", err)
    }
    fmt.Println("寫入字節(jié)(byte)數(shù):", n1)

    n2, err := io.WriteString(os.Stdout, "測(cè)試2")  // 對(duì)err進(jìn)行了重聲明
    if err != nil {
        fmt.Printf("ERROR: %v\n", err)
    }
    fmt.Println("寫入字節(jié)(byte)數(shù):", n2)

    n2, err = io.WriteString(os.Stdout, "測(cè)試三")  // 這里都是舊變量,沒(méi)有新變量,所以用的是賦值=
    if err != nil {
        fmt.Printf("ERROR: %v\n", err)
    }
    fmt.Println("寫入字節(jié)(byte)數(shù):", n2)
}

小結(jié)

使用關(guān)鍵字var和短變量聲明,都可以實(shí)現(xiàn)對(duì)變量的“聲明并賦值”。
前者可以被用在任何地方,而后者只能被用在函數(shù)或者其他更小的代碼塊中。
前者無(wú)法對(duì)已有的變量進(jìn)行聲明,就是無(wú)法處理新舊變量混在一起的情況??梢允褂煤笳叩淖兞恐芈暶鲗?shí)現(xiàn)。
共同點(diǎn)是,都是基于“類型推斷”。

中篇

一個(gè)程序?qū)嶓w的作用域總是會(huì)被限制在某個(gè)代碼塊中。而這個(gè)作用域最大的用處,就是對(duì)程序?qū)嶓w的訪問(wèn)權(quán)限的控制。對(duì)“高內(nèi)聚,低耦合”這種程序設(shè)計(jì)思想的實(shí)踐恰恰可以從這里開始。

變量作用域

變量重名的示例:

package main

import "fmt"

var block = "package"

func main() {
    block := "function"
    {
        block := "inner"
        fmt.Printf("block here is %s\n", block)
    }
    fmt.Printf("block here is %s\n", block)
}

/* 執(zhí)行結(jié)果
PS H:\Go\src\Go36\article05\example01> go run main.go
block here is inner
block here is function
PS H:\Go\src\Go36\article05\example01>
*/

上面的代碼中有4個(gè)代碼塊:

  • 全域代碼塊
  • main包代表的代碼塊,var block = "package"
  • main函數(shù)代表的代碼塊,block := "function"
  • main函數(shù)中用大括號(hào)包起來(lái)的代碼塊,block := "inner"

在后3個(gè)代碼塊中都聲明了一個(gè)block的變量,賦值為不同的字符串。
聲明重名的變量是無(wú)法編譯通過(guò)的,但是這是對(duì)同一代碼塊內(nèi)部而言的。上面的例子中是在不同的代碼塊中進(jìn)行的聲明。

引用變量時(shí)的查找過(guò)程
首先,會(huì)在當(dāng)前代碼塊中查找變量。不包含任何的子代碼塊。
其次,如果當(dāng)前代碼塊沒(méi)有什么此變量名,一層一層往上層的代碼塊查找。
最后,如果都找不到,則編譯器會(huì)報(bào)錯(cuò)。

下篇

不同代碼塊的變量可以重名,并且類型也可以不同。必要時(shí),在使用之前,要先對(duì)變量的類型進(jìn)行檢查。

示例

下面代碼中的container變量,雖然類型不同,但是都可以使用下標(biāo)[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])
}

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

類型斷言

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

value, ok := interface{}(container).([]string)

上面是一條賦值語(yǔ)句,賦值符號(hào)的右邊,就是一個(gè)類型斷言表達(dá)式。
先把變量container的值轉(zhuǎn)換為空接口的值interface{}(container)。然后再判斷他的類型是否為后面.()中的類型。
有2個(gè)返回值,value和ok。ok是布爾類型,代碼類型判斷的結(jié)果:

  • 如果是true,被判斷的值自動(dòng)轉(zhuǎn)換為.()中的類型的值,并且賦值給value。
  • 如果是false,value會(huì)賦值為nil,就是空。

不接收ok
這里ok也是可以沒(méi)有的:

value := interface{}(container).([]string)

這樣的話,如果類型不對(duì),就是引發(fā)異常panic。

轉(zhuǎn)為空接口的語(yǔ)法
在Go語(yǔ)言中,interface{}代表空接口。任何類型的值都可以很方便地被轉(zhuǎn)換成空接口的值,語(yǔ)法:interface{}(x)。
一對(duì)不包裹任何東西的花括號(hào),除了可以代表空的代碼塊之外,還可以用于表示不包含任何內(nèi)容的數(shù)據(jù)結(jié)構(gòu)(或者說(shuō)數(shù)據(jù)類型)。

字面量
小括號(hào)中[]string是一個(gè)類型字面量。所謂類型字面量,就是用來(lái)表示數(shù)據(jù)類型本身的若干個(gè)字符。
比如:string是表示字符串類型的字面量,uint8是表示8位無(wú)符號(hào)整數(shù)類型的字面量。

優(yōu)化示例代碼

修改開始的示例,在打印前,先對(duì)變量的類型進(jìn)行判斷,只有map或切片類型才進(jìn)行打?。?/p>

package main

import "fmt"

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

func main() {
    container := map[int]string{0: "zero", 1: "one", 2: "two"}
    // 打印之前先要做判斷,只有map或者切片類型才能通過(guò)
    _, 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語(yǔ)句的實(shí)現(xiàn)形式:

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
    }
}

類型轉(zhuǎn)換的坑

類型轉(zhuǎn)換表達(dá)式的語(yǔ)法:T(x)。
其中的x可以是一個(gè)變量,也可以是一個(gè)代表值的字面量(比如1.23和struct{}),還可以是一個(gè)表達(dá)式。如果是表達(dá)式,表達(dá)式的結(jié)果只能是一個(gè)值。
x被叫做源值,它的類型就是源類型。T代表的類型是目標(biāo)類型。

數(shù)值類型間互轉(zhuǎn)

對(duì)于整數(shù)類型值、整數(shù)常量之間的類型轉(zhuǎn)換,原則上只要源值在目標(biāo)類型的可表示范圍內(nèi)就是合法的。
上面說(shuō)的只是語(yǔ)法上合法,但是轉(zhuǎn)換后的結(jié)果可能是可坑。比如,如果源整數(shù)類型的可表示范圍大,而目標(biāo)類型的可表示范圍?。?/p>

package main

import "fmt"

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

在計(jì)算機(jī)系統(tǒng)中,數(shù)值一律用補(bǔ)碼來(lái)表示和存儲(chǔ)。原因在于,使用補(bǔ)碼,可以將符號(hào)位和數(shù)值域統(tǒng)一處理;同時(shí),加法和減法也可以統(tǒng)一處理。補(bǔ)碼就是原碼的各位求反再加1。比如-255:
原碼: 1000 0000 1111 1111
反碼: 1111 1111 0000 0000 最高位是符號(hào)位,不反轉(zhuǎn)。
補(bǔ)碼: 1111 1111 0000 0001
類型轉(zhuǎn)換的很簡(jiǎn)單粗暴,直接把最高的8位截掉,并不處理符號(hào)位,結(jié)果就是0000 0001,所以轉(zhuǎn)換后的值就變成1了。

浮點(diǎn)類型轉(zhuǎn)換
如果把浮點(diǎn)數(shù)轉(zhuǎn)換為整數(shù),則小數(shù)部分會(huì)被全部截掉:

package main

import "fmt"

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

整數(shù)轉(zhuǎn)字符串

直接把一個(gè)整數(shù)值轉(zhuǎn)換為一個(gè)string類型的值是可行的。但是,被轉(zhuǎn)換的整數(shù)值應(yīng)該是一個(gè)有效的Unicode碼點(diǎn),否則轉(zhuǎn)換的結(jié)果將會(huì)是"?"。字符'?'的Unicode碼點(diǎn)是U+FFFD。它是Unicode標(biāo)準(zhǔn)中定義的Replacement Character,專用于替換那些未知的、不被認(rèn)可的以及無(wú)法展示的字符。無(wú)效的碼點(diǎn)有很多,如果自己要搞一個(gè)測(cè)試,那么就用-1吧:

package main

import "fmt"

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

字符串與切片

一個(gè)值在從string類型轉(zhuǎn)為[]byte類型時(shí),其中UTF-8編碼的字符串會(huì)被拆分成零散、獨(dú)立的字節(jié)。這樣只有ASCII碼的那部分字符是一個(gè)字節(jié)代碼一個(gè)字符的。而其他字符,比如中文(UTF-8里中文字符用3個(gè)字節(jié)表示),會(huì)被拆開成3個(gè)字節(jié)。而且由于UTF-8的長(zhǎng)度是可變的,這樣還要想辦法判斷那幾個(gè)字節(jié)應(yīng)該是一個(gè)字符。
可以轉(zhuǎn)為[]rune類型,這樣轉(zhuǎn)換時(shí),每個(gè)字符會(huì)被拆開成一個(gè)個(gè)的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()
}
/* 執(zhí)行結(jié)果
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>
*/

別名類型 和 潛在類型

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

別名類型

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

type MyString = string

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

潛在類型

另外一種聲明:

type MyString2 string  // 注意,這里沒(méi)有等號(hào)

這種方式也可以被叫做對(duì)類型的再定義。這里MyString2是一個(gè)新的類型,和string是不同的類型。string可以被稱為MyString2的潛在類型。
潛在類型相同的不同類型的值之間是可以進(jìn)行類型轉(zhuǎn)換的。因此,MyString2類型的值與string類型的值可以使用類型轉(zhuǎn)換表達(dá)式進(jìn)行互轉(zhuǎn)。
但是,[]MyStrings 和 []string 是不同的潛在類型,不能做類型轉(zhuǎn)換。
另外,即使是相同的潛在類型,也不能進(jìn)行判等或比較,變量之間不能賦值。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI