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

關(guān)于golang指針的有限操作詳解

 更新時(shí)間:2023年08月24日 08:52:49   作者:m舊褲子  
傳統(tǒng)意義上來說,指針是一個(gè)指向某個(gè)確切的內(nèi)存地址的值,這個(gè)內(nèi)存地址可以是任何數(shù)據(jù)或代碼的起始地址,在Go語(yǔ)言中有幾種東西可以代表"指針",本文給大家介紹的是關(guān)于golang指針的有限操作,感興趣的同學(xué)可以參考一下

傳統(tǒng)意義上來說,指針是一個(gè)指向某個(gè)確切的內(nèi)存地址的值。這個(gè)內(nèi)存地址可以是任何數(shù)據(jù)或代碼的起始地址。在Go語(yǔ)言中有幾種東西可以代表"指針"。其中最貼切傳統(tǒng)意義的當(dāng)屬uintptr類型的了。該類型實(shí)際上是一個(gè)數(shù)值類型,也是Go語(yǔ)言內(nèi)建的數(shù)據(jù)類型之一。

根據(jù)當(dāng)前計(jì)算機(jī)的計(jì)算架構(gòu)的不同,它可以存儲(chǔ)32位或64位的無符號(hào)整數(shù),可以代表任何指針的位(bit)模式,也就是原始的內(nèi)存地址。

Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)中的unsafe包,unsafe包中有一個(gè)類型叫做Pointer,也代表任何指針的位(bit)模式,也就是原始的內(nèi)存地址。

unsafe.Pointer可以表示任何指向可尋址的值的指針,同時(shí)它也是前面提到的指針值和uintptr值之間的橋梁。通過它,我們可以在這兩種值之上進(jìn)行雙向的轉(zhuǎn)換。這里有一個(gè)很關(guān)鍵的詞——可尋址的(addressable)。

在我們繼續(xù)說unsafe.Pointer之前,需要先搞清楚這個(gè)詞的確切含義。

Go語(yǔ)言中的哪些值是不可尋址的?

  • 常量的值。
  • 基本類型值的字面量。
  • 算術(shù)操作的結(jié)果值。
  • 對(duì)各種字面量的索引表達(dá)式和切片表達(dá)式的結(jié)果值。不過有一個(gè)例外,對(duì)切片字面量的索引結(jié)果值卻是可尋址的。
  • 對(duì)字符串變量的索引表達(dá)式和切片表達(dá)式的結(jié)果值。
  • 對(duì)字典變量的索引表達(dá)式的結(jié)果值。
  • 函數(shù)字面量和方法字面量,以及對(duì)它們的調(diào)用表達(dá)式的結(jié)果值。
  • 結(jié)構(gòu)體字面量的字段值,也就是對(duì)結(jié)構(gòu)體字面量的選擇表達(dá)式的結(jié)果值。
  • 類型轉(zhuǎn)換表達(dá)式的結(jié)果值。
  • 類型斷言表達(dá)式的結(jié)果值。
  • 接收表達(dá)式的結(jié)果值。
// 示例1。
	const num = 123
	//_ = &num // 常量不可尋址。
	//_ = &(123) // 基本類型值的字面量不可尋址。
	var str = "abc"
	_ = str
	//_ = &(str[0]) // 對(duì)字符串變量的索引結(jié)果值不可尋址。
	//_ = &(str[0:2]) // 對(duì)字符串變量的切片結(jié)果值不可尋址。
	str2 := str[0]
	_ = &str2 // 但這樣的尋址就是合法的。
	//_ = &(123 + 456) // 算術(shù)操作的結(jié)果值不可尋址。
	num2 := 456
	_ = num2
	//_ = &(num + num2) // 算術(shù)操作的結(jié)果值不可尋址。
	//_ = &([3]int{1, 2, 3}[0]) // 對(duì)數(shù)組字面量的索引結(jié)果值不可尋址。
	//_ = &([3]int{1, 2, 3}[0:2]) // 對(duì)數(shù)組字面量的切片結(jié)果值不可尋址。
	_ = &([]int{1, 2, 3}[0]) // 對(duì)切片字面量的索引結(jié)果值卻是可尋址的。
	//_ = &([]int{1, 2, 3}[0:2]) // 對(duì)切片字面量的切片結(jié)果值不可尋址。
	//_ = &(map[int]string{1: "a"}[0]) // 對(duì)字典字面量的索引結(jié)果值不可尋址。
	var map1 = map[int]string{1: "a", 2: "b", 3: "c"}
	_ = map1
	//_ = &(map1[2]) // 對(duì)字典變量的索引結(jié)果值不可尋址。
	//_ = &(func(x, y int) int {
	//	return x + y
	//}) // 字面量代表的函數(shù)不可尋址。
	//_ = &(fmt.Sprintf) // 標(biāo)識(shí)符代表的函數(shù)不可尋址。
	//_ = &(fmt.Sprintln("abc")) // 對(duì)函數(shù)的調(diào)用結(jié)果值不可尋址。
	dog := Dog{"little pig"}
	_ = dog
	//_ = &(dog.Name) // 標(biāo)識(shí)符代表的函數(shù)不可尋址。
	//_ = &(dog.Name()) // 對(duì)方法的調(diào)用結(jié)果值不可尋址。
	//_ = &(Dog{"little pig"}.name) // 結(jié)構(gòu)體字面量的字段不可尋址。
	//_ = &(interface{}(dog)) // 類型轉(zhuǎn)換表達(dá)式的結(jié)果值不可尋址。
	dogI := interface{}(dog)
	_ = dogI
	//_ = &(dogI.(Named)) // 類型斷言表達(dá)式的結(jié)果值不可尋址。
	named := dogI.(Named)
	_ = named
	//_ = &(named.(Dog)) // 類型斷言表達(dá)式的結(jié)果值不可尋址。
	var chan1 = make(chan int, 1)
	chan1 <- 1
	//_ = &(<-chan1) // 接收表達(dá)式的結(jié)果值不可尋址。

常量的值總是會(huì)被存儲(chǔ)到一個(gè)確切的內(nèi)存區(qū)域中,并且這種值肯定是不可變的?;绢愋椭档淖置媪恳彩且粯?,其實(shí)它們本就可以被視為常量,只不過沒有任何標(biāo)識(shí)符可以代表它們罷了。

第一個(gè)關(guān)鍵詞:不可變的。由于 Go 語(yǔ)言中的字符串值也是不可變的,所以對(duì)于一個(gè)字符串類型的變量來說,基于它的索引或切片的結(jié)果值也都是不可尋址的,因?yàn)榧词鼓玫搅诉@種值的內(nèi)存地址也改變不了什么。

算術(shù)操作的結(jié)果值屬于一種臨時(shí)結(jié)果。在我們把這種結(jié)果值賦給任何變量或常量之前,即使能拿到它的內(nèi)存地址也是沒有任何意義的。

第二個(gè)關(guān)鍵詞:臨時(shí)結(jié)果。這個(gè)關(guān)鍵詞能被用來解釋很多現(xiàn)象。我們可以把各種對(duì)值字面量施加的表達(dá)式的求值結(jié)果都看做是臨時(shí)結(jié)果。

我們都知道,Go 語(yǔ)言中的表達(dá)式有很多種,其中常用的包括以下幾種。

  • 用于獲得某個(gè)元素的索引表達(dá)式。
  • 用于獲得某個(gè)切片(片段)的切片表達(dá)式。
  • 用于訪問某個(gè)字段的選擇表達(dá)式。
  • 用于調(diào)用某個(gè)函數(shù)或方法的調(diào)用表達(dá)式。
  • 用于轉(zhuǎn)換值的類型的類型轉(zhuǎn)換表達(dá)式。
  • 用于判斷值的類型的類型斷言表達(dá)式。
  • 向通道發(fā)送元素值或從通道那里接收元素值的接收表達(dá)式。

我們把以上這些表達(dá)式施加在某個(gè)值字面量上一般都會(huì)得到一個(gè)臨時(shí)結(jié)果。比如,對(duì)數(shù)組字面量和字典字面量的索引結(jié)果值,又比如,對(duì)數(shù)組字面量和切片字面量的切片結(jié)果值。它們都屬于臨時(shí)結(jié)果,都是不可尋址的。

一個(gè)需要特別注意的例外是,對(duì)切片字面量的索引結(jié)果值是可尋址的。因?yàn)椴徽撛鯓樱總€(gè)切片值都會(huì)持有一個(gè)底層數(shù)組,而這個(gè)底層數(shù)組中的每個(gè)元素值都是有一個(gè)確切的內(nèi)存地址的。

那么對(duì)切片字面量的切片結(jié)果值為什么卻是不可尋址的?這是因?yàn)榍衅磉_(dá)式總會(huì)返回一個(gè)新的切片值,而這個(gè)新的切片值在被賦給變量之前屬于臨時(shí)結(jié)果。

如果針對(duì)的是數(shù)組類型或切片類型的變量,那么索引或切片的結(jié)果值就都不屬于臨時(shí)結(jié)果了,是可尋址的。

這主要因?yàn)?code>變量的值本身就不是“臨時(shí)的”。對(duì)比而言,值字面量在還沒有與任何變量(或者說任何標(biāo)識(shí)符)綁定之前是沒有落腳點(diǎn)的,我們無法以任何方式引用到它們。這樣的值就是“臨時(shí)的”。

我們通過對(duì)字典類型的變量施加索引表達(dá)式,得到的結(jié)果值不屬于臨時(shí)結(jié)果,可是,這樣的值卻是不可尋址的。原因是,字典中的每個(gè)鍵 - 元素對(duì)的存儲(chǔ)位置都可能會(huì)變化,而且這種變化外界是無法感知的。

字典中總會(huì)有若干個(gè)哈希桶用于均勻地儲(chǔ)存鍵 - 元素對(duì)。當(dāng)滿足一定條件時(shí),字典可能會(huì)改變哈希桶的數(shù)量,并適時(shí)地把其中的鍵 - 元素對(duì)搬運(yùn)到對(duì)應(yīng)的新的哈希桶中。在這種情況下,獲取字典中任何元素值的指針都是無意義的,也是不安全的。我們不知道什么時(shí)候那個(gè)元素值會(huì)被搬運(yùn)到何處,也不知道原先的那個(gè)內(nèi)存地址上還會(huì)被存放什么別的東西。所以,這樣的值就應(yīng)該是不可尋址的。

第三個(gè)關(guān)鍵詞:不安全的。“不安全的”操作很可能會(huì)破壞程序的一致性,引發(fā)不可預(yù)知的錯(cuò)誤,從而嚴(yán)重影響程序的功能和穩(wěn)定性。

函數(shù)在 Go 語(yǔ)言中是一等公民,所以我們可以把代表函數(shù)或方法的字面量或標(biāo)識(shí)符賦給某個(gè)變量、傳給某個(gè)函數(shù)或者從某個(gè)函數(shù)傳出。但是,這樣的函數(shù)和方法都是不可尋址的。一個(gè)原因是函數(shù)就是代碼,是不可變的。

另一個(gè)原因是,拿到指向一段代碼的指針是不安全的。此外,對(duì)函數(shù)或方法的調(diào)用結(jié)果值也是不可尋址的,這是因?yàn)樗鼈兌紝儆谂R時(shí)結(jié)果。至于典型回答中最后列出的那幾種值,由于都是針對(duì)值字面量的某種表達(dá)式的結(jié)果值,所以都屬于臨時(shí)結(jié)果,都不可尋址。

  1. 不可變的值不可尋址。常量、基本類型的值字面量、字符串變量的值、函數(shù)以及方法的字面量都是如此。其實(shí)這樣規(guī)定也有安全性方面的考慮。
  2. 絕大多數(shù)被視為臨時(shí)結(jié)果的值都是不可尋址的。算術(shù)操作的結(jié)果值屬于臨時(shí)結(jié)果,針對(duì)值字面量的表達(dá)式結(jié)果值也屬于臨時(shí)結(jié)果。但有一個(gè)例外,對(duì)切片字面量的索引結(jié)果值,雖然也屬于臨時(shí)結(jié)果,但卻是可尋址的。
  3. 若拿到某值的指針可能會(huì)破壞程序的一致性,那么就是不安全的,該值就不可尋址。由于字典的內(nèi)部機(jī)制,對(duì)字典的索引結(jié)果值的取址操作都是不安全的。另外,獲取由字面量或標(biāo)識(shí)符代表的函數(shù)或方法的地址顯然也是不安全的。

不可尋址的值在使用上有哪些限制?

首當(dāng)其沖的當(dāng)然是無法使用取址操作符&獲取它們的指針了。不過,對(duì)不可尋址的值施加取址操作都會(huì)使編譯器報(bào)錯(cuò),所以倒是不用太擔(dān)心,你只要記住我在前面講述的那幾條規(guī)律,并在編碼的時(shí)候提前注意一下就好了。

func New(name string) Dog {
     return Dog{name}
}

我們?cè)贋樗帉懸粋€(gè)函數(shù)New。這個(gè)函數(shù)會(huì)接受一個(gè)名為namestring類型的參數(shù),并會(huì)用這個(gè)參數(shù)初始化一個(gè)Dog類型的值,最后返回該值。我現(xiàn)在要問的是:如果我調(diào)用該函數(shù),并直接以鏈?zhǔn)降氖址ㄕ{(diào)用其結(jié)果值的指針方法SetName,那么可以達(dá)到預(yù)期的效果嗎?

 New("little pig").SetName("monster")

由于New函數(shù)的調(diào)用結(jié)果的值是不可尋址的,所以無法對(duì)它進(jìn)行取址操作。因此,上邊這行鏈?zhǔn)秸{(diào)用會(huì)讓編譯器報(bào)告兩個(gè)錯(cuò)誤,一個(gè)是果,即:不能在New{"little pig"}的結(jié)果值上調(diào)用指針方法。一個(gè)是因,即:不能取得New{"little pig"}的地址。

除此之外,我們都知道,Go 語(yǔ)言中的++和–并不屬于操作符,而分別是自增語(yǔ)句和自減語(yǔ)句的重要組成部分。
雖然 Go 語(yǔ)言規(guī)范中的語(yǔ)法定義是,只要在++或–的左邊添加一個(gè)表達(dá)式,就可以組成一個(gè)自增語(yǔ)句或自減語(yǔ)句,但是,它還明確了一個(gè)很重要的限制,那就是這個(gè)表達(dá)式的結(jié)果值必須是可尋址的。這就使得針對(duì)值字面量的表達(dá)式幾乎都無法被用在這里。

不過這有一個(gè)例外,雖然對(duì)字典字面量和字典變量索引表達(dá)式的結(jié)果值都是不可尋址的,但是這樣的表達(dá)式卻可以被用在自增語(yǔ)句和自減語(yǔ)句中。

與之類似的規(guī)則還有兩個(gè)。一個(gè)是,在賦值語(yǔ)句中,賦值操作符左邊的表達(dá)式的結(jié)果值必須可尋址的,但是對(duì)字典的索引結(jié)果值也是可以的。

另一個(gè)是,在帶有range子句的for語(yǔ)句中,在range關(guān)鍵字左邊的表達(dá)式的結(jié)果值也都必須是可尋址的,不過對(duì)字典的索引結(jié)果值同樣可以被用在這里。

怎樣通過unsafe.Pointer操縱可尋址的值?

unsafe.Pointer是像 * Dog 類型的值這樣的指針值和uintptr值之間的橋梁,那么我們?cè)鯓永?code>unsafe.Pointer的中轉(zhuǎn)和uintptr的底層操作來操縱像dog這樣的值呢?

首先說明,這是一項(xiàng)黑科技。它可以繞過 Go 語(yǔ)言的編譯器和其他工具的重重檢查,并達(dá)到潛入內(nèi)存修改數(shù)據(jù)的目的。這并不是一種正常的編程手段,使用它會(huì)很危險(xiǎn),很有可能造成安全隱患。

我們總是應(yīng)該優(yōu)先使用常規(guī)代碼包中提供的 API 去編寫程序,當(dāng)然也可以把像reflect以及go/ast這樣的代碼包作為備選項(xiàng)。作為上層應(yīng)用的開發(fā)者,請(qǐng)謹(jǐn)慎地使用unsafe包中的任何程序?qū)嶓w。

dog := Dog{"little pig"}
dogP := &dog
dogPtr := uintptr(unsafe.Pointer(dogP))

我先聲明了一個(gè)Dog類型的變量dog,然后用取址操作符&,取出了它的指針值,并把它賦給了變量dogP。

最后,我使用了兩個(gè)類型轉(zhuǎn)換,先把dogP轉(zhuǎn)換成了一個(gè)unsafe.Pointer類型的值,然后緊接著又把后者轉(zhuǎn)換成了一個(gè)uintptr的值,并把它賦給了變量dogPtr。這背后隱藏著一些轉(zhuǎn)換規(guī)則,如下:

  1. 一個(gè)指針值(比如* Dog類型的值)可以被轉(zhuǎn)換為一個(gè)unsafe.Pointer類型的值,反之
    亦然。
  2. 一個(gè)uintptr類型的值也可以被轉(zhuǎn)換為一個(gè)unsafe.Pointer類型的值,反之亦然。
  3. 一個(gè)指針值無法被直接轉(zhuǎn)換成一個(gè)uintptr類型的值,反過來也是如此。

所以,對(duì)于指針值和uintptr類型值之間的轉(zhuǎn)換,必須使用unsafe.Pointer類型的值作為中轉(zhuǎn)。那么,我們把指針值轉(zhuǎn)換成uintptr類型的值有什么意義嗎?

namePtr := dogPtr + unsafe.Offsetof(dogP.name)
nameP := (*string)(unsafe.Pointer(namePtr))

這里需要與unsafe.Offsetof函數(shù)搭配使用才能看出端倪。unsafe.Offsetof函數(shù)用于獲取兩個(gè)值在內(nèi)存中的起始存儲(chǔ)地址之間的偏移量,以字節(jié)為單位。

這兩個(gè)值一個(gè)是某個(gè)字段的值,另一個(gè)是該字段值所屬的那個(gè)結(jié)構(gòu)體值。我們?cè)谡{(diào)用這個(gè)函數(shù)的時(shí)候,需要把針對(duì)字段的選擇表達(dá)式傳給它,比如dogP.name

有了這個(gè)偏移量,又有了結(jié)構(gòu)體值在內(nèi)存中的起始存儲(chǔ)地址(這里由dogPtr變量代表),把它們相加我們就可以得到dogPname字段值的起始存儲(chǔ)地址了。這個(gè)地址由變量namePtr代表。

此后,我們可以再通過兩次類型轉(zhuǎn)換把namePtr的值轉(zhuǎn)換成一個(gè)* string類型的值,這樣就得到了指向dogPname字段值的指針值。

你可能會(huì)問,我直接用取址表達(dá)式&(dogP.name)不就能拿到這個(gè)指針值了嗎?干嘛繞這么大一圈呢?你可以想象一下,如果我們根本就不知道這個(gè)結(jié)構(gòu)體類型是什么,也拿不到dogP這個(gè)變量,那么還能去訪問它的name字段嗎?

答案是,只要有namePtr就可以。它就是一個(gè)無符號(hào)整數(shù),但同時(shí)也是一個(gè)指向了程序內(nèi)部數(shù)據(jù)的內(nèi)存地址。它可能會(huì)給我們帶來一些好處,比如可以直接修改埋藏得很深的內(nèi)部數(shù)據(jù)。

但是,一旦我們有意或無意地把這個(gè)內(nèi)存地址泄露出去,那么其他人就能夠肆意地改動(dòng)dogP.name的值,以及周圍的內(nèi)存地址上存儲(chǔ)的任何數(shù)據(jù)了。

以上就是關(guān)于golang指針的有限操作詳解的詳細(xì)內(nèi)容,更多關(guān)于golang指針操作的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • golang 執(zhí)行命令行的實(shí)現(xiàn)

    golang 執(zhí)行命令行的實(shí)現(xiàn)

    本文主要介紹了golang 執(zhí)行命令行的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • 深入淺出go依賴注入工具Wire的使用

    深入淺出go依賴注入工具Wire的使用

    但隨著項(xiàng)目規(guī)模的增長(zhǎng),組件之間的依賴關(guān)系變得復(fù)雜,手動(dòng)管理可能會(huì)很繁瑣,所以本文將深入探討一個(gè)備受歡迎的?Go?語(yǔ)言依賴注入工具——?Wire,感興趣的可以了解下
    2023-09-09
  • Go錯(cuò)誤處理之panic函數(shù)和recover函數(shù)使用及捕獲異常方法

    Go錯(cuò)誤處理之panic函數(shù)和recover函數(shù)使用及捕獲異常方法

    這篇文章主要介紹了Go錯(cuò)誤處理之panic函數(shù)使用及捕獲,本篇探討了如何使用 panic 和 recover 來處理 Go 語(yǔ)言中的異常,需要的朋友可以參考下
    2023-03-03
  • viper配置框架的介紹支持zookeeper的讀取和監(jiān)聽

    viper配置框架的介紹支持zookeeper的讀取和監(jiān)聽

    這篇文章主要介紹了viper配置框架的介紹支持zookeeper的讀取和監(jiān)聽,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-05-05
  • Go命令行參數(shù)解析flag 包使用示例詳解

    Go命令行參數(shù)解析flag 包使用示例詳解

    這篇文章主要介紹了Go命令行參數(shù)解析flag 包使用,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2024-01-01
  • go swagger生成接口文檔使用教程

    go swagger生成接口文檔使用教程

    這篇文章主要為大家介紹了go swagger生成接口文檔使用教程示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • Go語(yǔ)言時(shí)間相關(guān)操作合集(超詳細(xì))

    Go語(yǔ)言時(shí)間相關(guān)操作合集(超詳細(xì))

    在開發(fā)應(yīng)用程序的過程中,經(jīng)常需要記錄某些操作的時(shí)間或者格式化時(shí)間戳,因此大部分編程語(yǔ)言都會(huì)有操作時(shí)間的庫(kù),Go語(yǔ)言當(dāng)然也不例外,本文我們就一起來學(xué)習(xí)一下time包的使用
    2023-08-08
  • golang根據(jù)URL獲取文件名的示例代碼

    golang根據(jù)URL獲取文件名的示例代碼

    這篇文章主要為大家詳細(xì)介紹了golang根據(jù)URL獲取文件名,文中的示例代碼講解詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-01-01
  • go語(yǔ)言編程之select信道處理示例詳解

    go語(yǔ)言編程之select信道處理示例詳解

    這篇文章主要為大家介紹了go語(yǔ)言編程之select信道處理示例詳解,<BR>有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2022-04-04
  • 關(guān)于Golang中range指針數(shù)據(jù)的坑詳解

    關(guān)于Golang中range指針數(shù)據(jù)的坑詳解

    這篇文章主要給大家介紹了關(guān)于Golang中range指針數(shù)據(jù)的坑的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-02-02

最新評(píng)論