亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Go?代碼塊作用域變量遮蔽問(wèn)題解析

 更新時(shí)間:2023年10月15日 10:24:56   作者:賈維斯Echo  
這篇文章主要為大家介紹了Go?代碼塊作用域變量遮蔽問(wèn)題解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

一、引入

首先我們從一個(gè) Go 變量遮蔽(Variable Shadowing)的問(wèn)題說(shuō)起。

什么是變量遮蔽呢?

變量遮蔽(Variable Shadowing)是指在程序中一個(gè)作用域內(nèi)的變量名(或標(biāo)識(shí)符)隱藏(遮蔽)了外部作用域中相同名稱的變量。這會(huì)導(dǎo)致在遮蔽內(nèi)部作用域內(nèi),無(wú)法直接訪問(wèn)外部作用域的變量,因?yàn)榫幾g器或解釋器將優(yōu)先選擇內(nèi)部作用域的變量,而不是外部的。

我們來(lái)看下面這段示例代碼:

package main
import "fmt"
var x = 10 // 包級(jí)作用域的變量
func main() {
    x := 5 // 函數(shù)內(nèi)的局部變量,遮蔽了包級(jí)作用域的 x
    fmt.Println(x) // 輸出:5
}
func anotherFunction() {
    fmt.Println(x) // 在這個(gè)函數(shù)中,外部包級(jí)作用域的 x 是可見(jiàn)的,輸出:10
}

你可以看到,在這段代碼中,函數(shù)main內(nèi)部有一個(gè)局部變量 x,它遮蔽了包級(jí)作用域的 x。因此,在main函數(shù)內(nèi)部,通過(guò)變量 x 訪問(wèn)的是局部變量,而不是外部包級(jí)作用域的變量。然而,在anotherFunction中,沒(méi)有局部變量 x,因此外部包級(jí)作用域的 x 是可見(jiàn)的。

二、代碼塊 (Block)

2.1 代碼塊介紹

在Go語(yǔ)言中,代碼塊是包裹在一對(duì)大括號(hào){} 包圍的聲明和語(yǔ)句序列。

2.2 顯式代碼塊

這些代碼塊是你在代碼中明確可見(jiàn)的,由一對(duì)大括號(hào) {} 包圍。比如函數(shù)的函數(shù)體、for循環(huán)的循環(huán)體、以及其他控制結(jié)構(gòu)內(nèi)部的代碼塊。這些代碼塊明確定義了它們的作用域,包括變量的可見(jiàn)性:

func Foo() {
    // 這里是顯式代碼塊,包裹在函數(shù)的函數(shù)體內(nèi)
    // ...

    for {
        // 這里是顯式代碼塊,包裹在for循環(huán)體內(nèi)
        // 該代碼塊也是嵌套在函數(shù)體顯式代碼塊內(nèi)部的代碼塊
        // ...
    }

    if true {
        // 這里是顯式代碼塊,包裹在if語(yǔ)句的true分支內(nèi)
        // 該代碼塊也是嵌套在函數(shù)體顯式代碼塊內(nèi)部的代碼塊
        // ...
    }
}

2.3 隱式代碼塊

隱式代碼塊沒(méi)有顯式代碼塊那樣的肉眼可見(jiàn)的配對(duì)大括號(hào)包裹,我們無(wú)法通過(guò)大括號(hào)來(lái)識(shí)別隱式代碼塊。

雖然隱式代碼塊身著“隱身衣”,但我們也不是沒(méi)有方法來(lái)識(shí)別它,因?yàn)?Go 語(yǔ)言規(guī)范對(duì)現(xiàn)存的幾類隱式代碼塊做了明確的定義,我們可以看下這張圖:

我們按代碼塊范圍從大到小,逐一說(shuō)明:

  • 宇宙(Universe)代碼塊:它囊括的范圍最大,所有 Go 源碼都在這個(gè)隱式代碼塊中,你也可以將該隱式代碼塊想象為在所有 Go 代碼的最外層加一對(duì)大括號(hào),就像圖中最外層的那對(duì)大括號(hào)那樣。
  • 包代碼塊:在宇宙代碼塊內(nèi)部嵌套了包代碼塊(Package Block),每個(gè) Go 包都對(duì)應(yīng)一個(gè)隱式包代碼塊,每個(gè)包代碼塊包含了該包中的所有 Go 源碼,不管這些代碼分布在這個(gè)包里的多少個(gè)的源文件中。
  • 文件代碼塊:在包代碼塊的內(nèi)部嵌套著若干文件代碼塊(File Block),每個(gè) Go 源文件都對(duì)應(yīng)著一個(gè)文件代碼塊,也就是說(shuō)一個(gè) Go 包如果有多個(gè)源文件,那么就會(huì)有多個(gè)對(duì)應(yīng)的文件代碼塊。
  • 再下一個(gè)級(jí)別的隱式代碼塊就在控制語(yǔ)句層面了,包括 if、for 與 switch。我們可以把每個(gè)控制語(yǔ)句都視為在它自己的隱式代碼塊里。不過(guò)你要注意,這里的控制語(yǔ)句隱式代碼塊與控制語(yǔ)句使用大括號(hào)包裹的顯式代碼塊并不是一個(gè)代碼塊。你再看一下前面的圖,switch 控制語(yǔ)句的隱式代碼塊的位置是在它顯式代碼塊的外面的。
  • 最后,位于最內(nèi)層的隱式代碼塊是 switch 或 select 語(yǔ)句的每個(gè) case/default 子句中,雖然沒(méi)有大括號(hào)包裹,但實(shí)質(zhì)上,每個(gè)子句都自成一個(gè)代碼塊。

2.4 空代碼塊

如果一對(duì)大括號(hào)內(nèi)部沒(méi)有任何聲明或其他語(yǔ)句,我們就把它叫做空代碼塊

空代碼塊在Go語(yǔ)言中是有效的,并且在某些情況下可以有一定的用途,尤其是在控制結(jié)構(gòu)中,如if語(yǔ)句、for循環(huán)或switch語(yǔ)句的特定分支。它們充當(dāng)了占位符,允許你將來(lái)添加代碼而不需要改變代碼的結(jié)構(gòu)。

以下是一個(gè)示例,演示了空代碼塊的使用:

func main() {
    x := 10
    if x > 5 {
        // 非空代碼塊
        fmt.Println("x 大于 5")
    } else {
        // 空代碼塊,什么都不做
    }
    for i := 0; i < 5; i++ {
        // 空代碼塊,什么都不做
    }
}

2.5 支持嵌套代碼塊

Go 代碼塊支持嵌套,我們可以在一個(gè)代碼塊中嵌入多個(gè)層次的代碼塊,如下面示例代碼所示:

func foo() { //代碼塊1
    { // 代碼塊2
        { // 代碼塊3
            { // 代碼塊4

            }
        }
    }
}

三、作用域 (Scope)

3.1 作用域介紹

作用域的概念是針對(duì)標(biāo)識(shí)符的,不局限于變量。每個(gè)標(biāo)識(shí)符都有自己的作用域,而一個(gè)標(biāo)識(shí)符的作用域就是指這個(gè)標(biāo)識(shí)符在被聲明后可以被有效使用的源碼區(qū)域。

顯然,作用域是一個(gè)編譯期的概念,也就是說(shuō),編譯器在編譯過(guò)程中會(huì)對(duì)每個(gè)標(biāo)識(shí)符的作用域進(jìn)行檢查,對(duì)于在標(biāo)識(shí)符作用域外使用該標(biāo)識(shí)符的行為會(huì)給出編譯錯(cuò)誤的報(bào)錯(cuò)。

3.2 作用域劃定原則

我們可以使用代碼塊的概念來(lái)劃定每個(gè)標(biāo)識(shí)符的作用域。一般劃定原則就是聲明于外層代碼塊中的標(biāo)識(shí)符,其作用域包括所有內(nèi)層代碼塊。而且,這一原則同時(shí)適于顯式代碼塊與隱式代碼塊。

3.3 標(biāo)識(shí)符的作用域范圍

3.3.1 預(yù)定義標(biāo)識(shí)符作用域

首先,我們來(lái)看看位于最外層的宇宙隱式代碼塊的標(biāo)識(shí)符。這一區(qū)域是 Go 語(yǔ)言預(yù)定義標(biāo)識(shí)符的自留地。你可以看看下面這張表是Go 語(yǔ)言當(dāng)前版本定義里的所有預(yù)定義標(biāo)識(shí)符:

由于這些預(yù)定義標(biāo)識(shí)符位于包代碼塊的外層,所以它們的作用域是范圍最大的,對(duì)于開(kāi)發(fā)者而言,它們的作用域就是源代碼中的任何位置。不過(guò),這些預(yù)定義標(biāo)識(shí)符不是關(guān)鍵字,我們同樣可以在內(nèi)層代碼塊中聲明同名的標(biāo)識(shí)符。

3.3.2 包代碼塊級(jí)作用域

包頂層聲明中的常量、類型、變量或函數(shù)(不包括方法)對(duì)應(yīng)的標(biāo)識(shí)符的作用域是包代碼塊。

不過(guò),對(duì)于作用域?yàn)榘a塊的標(biāo)識(shí)符,我需要你知道一個(gè)特殊情況。那就是當(dāng)一個(gè)包 A 導(dǎo)入另外一個(gè)包 B 后,包 A 僅可以使用被導(dǎo)入包包 B 中的導(dǎo)出標(biāo)識(shí)符(Exported Identifier)。

按照 Go 語(yǔ)言定義,一個(gè)標(biāo)識(shí)符要成為導(dǎo)出標(biāo)識(shí)符需同時(shí)具備兩個(gè)條件:一是這個(gè)標(biāo)識(shí)符聲明在包代碼塊中,或者它是一個(gè)字段名或方法名;二是它名字第一個(gè)字符是一個(gè)大寫的 Unicode 字符。這兩個(gè)條件缺一不可。

// 包 A
package A
import "B"
func SomeFunction() {
    // 可以訪問(wèn)包 B 中的導(dǎo)出標(biāo)識(shí)符
    B.ExportFunction()
}
// 這里無(wú)法訪問(wèn)包 B 中的非導(dǎo)出標(biāo)識(shí)符

3.3.3 文件代碼塊作用域(包的導(dǎo)入作用域)

在Go語(yǔ)言中,除了大多數(shù)在包頂層聲明的標(biāo)識(shí)符具有包代碼塊范圍的作用域外,還有一個(gè)特殊情況,即導(dǎo)入的包名。導(dǎo)入的包名的作用域是文件代碼塊范圍,這意味著它在包含它的源代碼文件中可見(jiàn),但對(duì)其他源文件不可見(jiàn)。

考慮以下示例,其中一個(gè)包A有兩個(gè)源文件,它們都依賴包B中的標(biāo)識(shí)符:

// 文件1:source1.go
package A
import "B"
func FunctionInSource1() {
    B.SomeFunctionFromB() // 可以使用導(dǎo)入的包名 B
}
// 文件2:source2.go
package A
import "B"
func FunctionInSource2() {
    B.AnotherFunctionFromB() // 可以使用導(dǎo)入的包名 B
}

在這個(gè)示例中,兩個(gè)源文件都導(dǎo)入了包B,但每個(gè)文件內(nèi)的包名 B 在文件級(jí)別可見(jiàn)。這意味著FunctionInSource1FunctionInSource2函數(shù)都可以訪問(wèn)B包中的導(dǎo)出標(biāo)識(shí)符(以大寫字母開(kāi)頭的標(biāo)識(shí)符),但對(duì)于其他包和源文件而言,它們不可見(jiàn)。

3.3.4 函數(shù)體的作用域

函數(shù)體內(nèi)的標(biāo)識(shí)符的作用域被限制在函數(shù)的開(kāi)始和結(jié)束之間。這意味著函數(shù)體內(nèi)的局部變量只能在函數(shù)體內(nèi)部訪問(wèn)。

func exampleFunction() {
    var localVar = 42
    fmt.Println(localVar) // 可以訪問(wèn)局部變量 localVar
}

fmt.Println(localVar) // 這里無(wú)法訪問(wèn)局部變量 localVar

3.3.5 流程控制作用域

流程控制結(jié)構(gòu),如if語(yǔ)句、for循環(huán)和switch語(yǔ)句,也會(huì)引入新的作用域。在這些結(jié)構(gòu)中聲明的局部變量的作用域限制在結(jié)構(gòu)內(nèi)部,不會(huì)泄漏到外部。

if x := 10; x > 5 {
    // x 只能在 if 語(yǔ)句塊內(nèi)訪問(wèn)
    fmt.Println(x)
}

fmt.Println(x) // 這里無(wú)法訪問(wèn) x

在上面的示例中,變量 x 在if語(yǔ)句內(nèi)部有一個(gè)新的局部作用域,因此它只在if語(yǔ)句塊內(nèi)可見(jiàn)。

四、避免變量遮蔽的原則

4.1 變量遮蔽的根本原因

變量是標(biāo)識(shí)符的一種,通過(guò)以上我們知道,一個(gè)變量的作用域起始于其聲明所在的代碼塊,并且可以一直擴(kuò)展到嵌入到該代碼塊中的所有內(nèi)層代碼塊,而正是這樣的作用域規(guī)則,成為了滋生“變量遮蔽問(wèn)題”的土壤。

變量遮蔽問(wèn)題的根本原因,就是內(nèi)層代碼塊中聲明了一個(gè)與外層代碼塊同名且同類型的變量,這樣,內(nèi)層代碼塊中的同名變量就會(huì)替代那個(gè)外層變量,參與此層代碼塊內(nèi)的相關(guān)計(jì)算,我們也就說(shuō)內(nèi)層變量遮蔽了外層同名變量?,F(xiàn)在,我們先來(lái)看一下這個(gè)示例代碼,它就存在著多種變量遮蔽的問(wèn)題:

... ...
 var a int = 2020
 func checkYear() error {
     err := errors.New("wrong year")
     switch a, err := getYear(); a {
     case 2020:
         fmt.Println("it is", a, err)
     case 2021:
         fmt.Println("it is", a)
         err = nil
     }
     fmt.Println("after check, it is", a)
     return err
 }
 type new int
 func getYear() (new, error) {
     var b int16 = 2021
     return new(b), nil
 }
 func main() {
     err := checkYear()
     if err != nil {
         fmt.Println("call checkYear error:", err)
         return
     }
     fmt.Println("call checkYear ok")
 }

這個(gè)變量遮蔽的例子還是有點(diǎn)復(fù)雜的,我們首先運(yùn)行一下這個(gè)例子:

$go run complex.go
it is 2021
after check, it is 2020
call checkYear error: wrong year

我們可以看到,第 20 行定義的 getYear 函數(shù)返回了正確的年份 (2021),但是 checkYear 在結(jié)尾卻輸出“after check, it is 2020”,并且返回的 err 并非為 nil,這顯然是變量遮蔽的“鍋”!

根據(jù)我們前面給出的變量遮蔽的根本原因,看看上面這段代碼究竟有幾處變量遮蔽問(wèn)題(包括標(biāo)識(shí)符遮蔽問(wèn)題)。

4.2 變量遮蔽問(wèn)題分析

4.2.1 第一個(gè)問(wèn)題:遮蔽預(yù)定義標(biāo)識(shí)符

面對(duì)上面代碼,我們一眼就看到了位于第 18 行的 new,這本是 Go 語(yǔ)言的一個(gè)預(yù)定義標(biāo)識(shí)符,但上面示例代碼呢,卻用 new 這個(gè)名字定義了一個(gè)新類型,于是 new 這個(gè)標(biāo)識(shí)符就被遮蔽了。如果這個(gè)時(shí)候你在 main 函數(shù)下方放上下面代碼:

p := new(int)
*p = 11

你就會(huì)收到 Go 編譯器的錯(cuò)誤提示:“type int is not an expression”,如果沒(méi)有意識(shí)到 new 被遮蔽掉,這個(gè)提示就會(huì)讓你不知所措。不過(guò),在上面示例代碼中,遮蔽 new 并不是示例未按預(yù)期輸出結(jié)果的真實(shí)原因,我們還得繼續(xù)往下看。

4.2.2 第二個(gè)問(wèn)題:遮蔽包代碼塊中的變量

你看,位于第 7 行的 switch 語(yǔ)句在它自身的隱式代碼塊中,通過(guò)短變量聲明形式重新聲明了一個(gè)變量 a,這個(gè)變量 a 就遮蔽了外層包代碼塊中的包級(jí)變量 a,這就是打印“after check, it is 2020”的原因。包級(jí)變量 a 沒(méi)有如預(yù)期那樣被 getYear 的返回值賦值為正確的年份 2021,2021 被賦值給了遮蔽它的 switch 語(yǔ)句隱式代碼塊中的那個(gè)新聲明的 a。

4.2.3 第三個(gè)問(wèn)題:遮蔽外層顯式代碼塊中的變量

同樣還是第 7 行的 switch 語(yǔ)句,除了聲明一個(gè)新的變量 a 之外,它還聲明了一個(gè)名為 err 的變量,這個(gè)變量就遮蔽了第 4 行 checkYear 函數(shù)在顯式代碼塊中聲明的 err 變量,這導(dǎo)致第 12 行的 nil 賦值動(dòng)作作用到了 switch 隱式代碼塊中的 err 變量上,而不是外層 checkYear 聲明的本地變量 err 變量上,后者并非 nil,這樣 checkYear 雖然從 getYear 得到了正確的年份值,但卻返回了一個(gè)錯(cuò)誤給 main 函數(shù),這直接導(dǎo)致了 main 函數(shù)打印了錯(cuò)誤:“call checkYear error: wrong year”。

通過(guò)這個(gè)示例,我們也可以看到,短變量聲明與控制語(yǔ)句的結(jié)合十分容易導(dǎo)致變量遮蔽問(wèn)題,并且很不容易識(shí)別,因此在日常 go 代碼開(kāi)發(fā)中你要尤其注意兩者結(jié)合使用的地方。

五、利用工具檢測(cè)變量遮蔽問(wèn)題

依靠肉眼識(shí)別變量遮蔽問(wèn)題終歸不是長(zhǎng)久之計(jì),所以Go 官方提供了 go vet 工具可以用于對(duì) Go 源碼做一系列靜態(tài)檢查,在 Go 1.14 版以前默認(rèn)支持變量遮蔽檢查,Go 1.14 版之后,變量遮蔽檢查的插件就需要我們單獨(dú)安裝了,安裝方法如下:

go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest

安裝成功后,我們就可以通過(guò) go vet 掃描代碼并檢查這里面有沒(méi)有變量遮蔽的問(wèn)題了。我們檢查一下前面的示例代碼,看看效果怎么樣。執(zhí)行檢查的命令如下:

$go vet -vettool=$(which shadow) -strict complex.go 
./complex.go:13:12: declaration of "err" shadows declaration at line 11

以上就是Go 代碼塊作用域變量遮蔽問(wèn)題解析的詳細(xì)內(nèi)容,更多關(guān)于Go作用域變量遮蔽的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go開(kāi)源項(xiàng)目分布式唯一ID生成系統(tǒng)

    Go開(kāi)源項(xiàng)目分布式唯一ID生成系統(tǒng)

    這篇文章主要為大家介紹了Go開(kāi)源項(xiàng)目分布式唯一ID生成系統(tǒng)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • golang基于errgroup實(shí)現(xiàn)并發(fā)調(diào)用的方法

    golang基于errgroup實(shí)現(xiàn)并發(fā)調(diào)用的方法

    這篇文章主要介紹了golang基于errgroup實(shí)現(xiàn)并發(fā)調(diào)用,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-09-09
  • go語(yǔ)言fasthttp使用實(shí)例小結(jié)

    go語(yǔ)言fasthttp使用實(shí)例小結(jié)

    fasthttp?是一個(gè)使用?Go?語(yǔ)言開(kāi)發(fā)的?HTTP?包,主打高性能,針對(duì)?HTTP?請(qǐng)求響應(yīng)流程中的?hot?path?代碼進(jìn)行了優(yōu)化,下面我們就來(lái)介紹go語(yǔ)言fasthttp使用實(shí)例小結(jié),感興趣的朋友跟隨小編一起看看吧
    2024-03-03
  • Golang打印復(fù)雜結(jié)構(gòu)體兩種方法詳解

    Golang打印復(fù)雜結(jié)構(gòu)體兩種方法詳解

    在?Golang?語(yǔ)言開(kāi)發(fā)中,我們經(jīng)常會(huì)使用結(jié)構(gòu)體類型,如果我們使用的結(jié)構(gòu)體類型的變量包含指針類型的字段,我們?cè)谟涗浫罩镜臅r(shí)候,指針類型的字段的值是指針地址,將會(huì)給我們?debug?代碼造成不便
    2022-10-10
  • 一文帶你深入探究Go語(yǔ)言中的sync.Map

    一文帶你深入探究Go語(yǔ)言中的sync.Map

    在?Go?語(yǔ)言中,有一個(gè)非常實(shí)用的并發(fā)安全的?Map?實(shí)現(xiàn):sync.Map,它是在?Go?1.9?版本中引入的。本文我們將深入探討?sync.Map?的基本原理,幫助讀者更好地理解并使用這個(gè)并發(fā)安全的?Map
    2023-04-04
  • 使用Golang快速構(gòu)建出命令行應(yīng)用程序

    使用Golang快速構(gòu)建出命令行應(yīng)用程序

    在日常開(kāi)發(fā)中,大家對(duì)命令行工具(CLI)想必特別熟悉了,如果說(shuō)你不知道命令工具,那你可能是個(gè)假開(kāi)發(fā)。每天都會(huì)使用大量的命令行工具,例如最常用的Git、Go、Docker等,這篇文章主要介紹了使用Golang快速構(gòu)建出命令行應(yīng)用程序,需要的朋友可以參考下
    2023-02-02
  • Golang學(xué)習(xí)之反射機(jī)制的用法詳解

    Golang學(xué)習(xí)之反射機(jī)制的用法詳解

    反射的本質(zhì)就是在程序運(yùn)行的時(shí)候,獲取對(duì)象的類型信息和內(nèi)存結(jié)語(yǔ)構(gòu),反射是把雙刃劍,功能強(qiáng)大但可讀性差。本文將詳細(xì)講講Golang中的反射機(jī)制,感興趣的可以了解一下
    2022-06-06
  • Go網(wǎng)絡(luò)編程TCP抓包實(shí)操示例探究

    Go網(wǎng)絡(luò)編程TCP抓包實(shí)操示例探究

    作為一名軟件開(kāi)發(fā)者,網(wǎng)絡(luò)編程是必備知識(shí),本文通過(guò)?Go?語(yǔ)言實(shí)現(xiàn)?TCP?套接字編程,并結(jié)合?tcpdump?工具,展示它的三次握手、數(shù)據(jù)傳輸以及四次揮手的過(guò)程,幫助讀者更好地理解?TCP?協(xié)議與?Go?網(wǎng)絡(luò)編程
    2024-01-01
  • golang 函數(shù)返回chan類型的操作

    golang 函數(shù)返回chan類型的操作

    這篇文章主要介紹了golang 函數(shù)返回chan類型的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-04-04
  • golang的基礎(chǔ)語(yǔ)法和常用開(kāi)發(fā)工具詳解

    golang的基礎(chǔ)語(yǔ)法和常用開(kāi)發(fā)工具詳解

    這篇文章主要介紹了golang的基礎(chǔ)語(yǔ)法和常用開(kāi)發(fā)工具,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-12-12

最新評(píng)論