什麽是指針
在Go語言中,"指針是一種存儲變量内存地址的數據類型",意味着指針本身是一個特殊的變量,它的值不是數據本身,而是另一個變量在計算機内存中的位置(地址)。形象地說,就像存放了一個數據項在内存中的“門牌号”。
"它允許程序直接操縱内存中的數據",這句話意味着通過指針,程序能夠繞過變量本身,直接到達變量在内存中的存儲位置,并對那裏的數據進行讀取或修改。這種能力非常重要,因爲:
1. **效率**:當數據結構很大時(如大型數組或結構體),直接操作其内存地址可以避免複制整個數據結構,從而節省時間和空間。
2. **靈活性**:在函數調用時,傳遞數據的指針而非數據本身的副本,可以使函數有能力修改調用者的數據,這在很多場景下是必要的,比如更新共享狀态或配置。
3. **控制權**:指針提供了底層的内存訪問能力,這對于系統編程、性能優化和一些高級數據結構的實現至關重要。
例如,如果你有一個很大的數組,想要修改其中的一個元素,直接通過指針定位到那個元素的内存位置并修改它,比起先複制整個數組或結構到函數内部再修改,顯然更高效。此外,通過指針,你還可以創建動态數據結構,如鏈表、樹等,因爲每個節點可以指向下一個節點的位置。
相比之下,C/C++的指針以其高度靈活性聞名,允許自由的偏移和運算,這爲系統級編程和大數據操作提供了強大工具,也是其高性能的來源。然而,這種靈活性也帶來了風險,如内存洩漏、指針懸挂、緩沖區溢出等問題,這些安全漏洞常常成爲黑客攻擊的入口,也是操作系統頻繁更新修複的原因之一。
指針的概念
指針地址和指針類型
在Go語言中,理解和操作指針時,"指針地址"和"指針類型"是兩個核心概念:
指針地址
指針地址指的是一個變量在内存中的實際存儲位置。在Go語言中,你可以使用`&`操作符來獲取一個變量的地址。這個地址是一個無符号整數,但它通常以十六進制形式顯示,代表了變量在内存中的确切位置。例如:
var num int = 10
var ptr *int = &num
在這個例子中,`&num`就是獲取變量`num`的内存地址,并将其賦值給指針變量`ptr`,`ptr`的類型就是指向`int`類型的指針,即`*int`。
指針類型
指針類型定義了指針所指向的數據類型。每個指針都有一個明确的類型,它決定了該指針可以指向哪種類型的變量。在Go語言中,指針類型的聲明語法是在數據類型前加上星号`*`。例如,`*int`表示一個指向整型變量的指針,`*string`表示一個指向字符串變量的指針。
指針類型的重要性在于,它确保了類型安全,意味着你不能錯誤地将一個類型的指針賦值給另一個不匹配類型的指針變量,除非通過類型斷言或類型轉換(在類型兼容的情況下)。
指針的使用
package main
import (
"fmt"
)
func main() {
var money int = 156
var str string = "ppp"
fmt.Printf("%p %p", &money, &str)
}
運行結果:
指針取值
當使用`&`操作符對普通變量進行取地址操作并得到變量的指針後,可以對指針使用`*`操作符,也就是指針取值,代碼如下:
package main
import (
"fmt"
)
func main() {
// 準備一個字符串類型
var day01 = "Hello World"
// 對字符串取地址, tem類型爲*string
tem := &day01
// 打印tem的類型
fmt.Printf("tem type: %T\n", tem)
// 打印ptr的指針地址
fmt.Printf("address: %p\n", tem)
// 對指針進行取值操作
value := *tem
// 取值後的類型
fmt.Printf("value type: %T\n", value)
// 指針取值後就是指向變量的值
fmt.Printf("value: %s\n", value)
}
運行結果:
取地址操作符&和取值操作符*是一對互補操作符,&取出地址,*根據地址取出地址指向的值。
變量、指針地址、指針變量、取地址、取值的相互關系和特性如下:
- 對變量進行取地址操作使用&操作符,可以獲得這個變量的指針變量。
- 指針變量的值是指針地址。
- 對指針變量進行取值操作使用*操作符,可以獲得指針變量指向的原變量的值。
使用指針修改值
通過指針不僅可以取值,也可以修改值:
示例1:
Pythonpackage main import "fmt" // 交換函數 func swap(a, b *int) { // 取a指針的值, 賦給臨時變量t t := *a // 取b指針的值, 賦給a指針指向的變量, *a = *b // 将a指針的值賦給b指針指向的變量 *b = t } func main() { // 準備兩個變量, 賦值1和2 x, y := 1, 2 // 交換變量值 swap(&x, &y) // 輸出變量值 fmt.Println(x, y) }
運行結果:
*操作符作爲右值時,意義是取指針的值,作爲左值時,也就是放在賦值操作符的左邊時,表示 a 指針指向的變量。其實歸納起來,*操作符的根本意義就是操作指針指向的變量。當操作在右值時,就是取指向變量的值,當操作在左值時,就是将值設置給指向的變量。
如果在 swap() 函數中交換操作的是指針值,會發生什麽情況?可以參考下面代碼:
Pythonpackage main import "fmt" func swap(a, b *int) { b, a = a, b } func main() { x, y := 1, 2 swap(&x, &y) fmt.Println(x, y) }
運行結果:
結果表明,交換是不成功的。上面代碼中的 swap() 函數交換的是 a 和 b 的地址,在交換完畢後,a 和 b 的變量值确實被交換。但和 a、b 關聯的兩個變量并沒有實際關聯。這就像寫有兩座房子的卡片放在桌上一字攤開,交換兩座房子的卡片後并不會對兩座房子有任何影響。
示例2:
Pythonpackage main import "fmt" func main() { // 聲明一個整型變量 var num int = 10 // 聲明一個指向整型的指針 var ptr = &num //将50賦給ptr指針指向的變量 *ptr = 50 fmt.Println(num) //輸出50 fmt.Println(*ptr) //輸出50 }
指針的指針
指針的指針,顧名思義,就是一個指針變量,它的值是另一個指針的地址。在Go語言中,正如你可以聲明指向任何基本類型或複合類型的指針一樣,你也可以聲明指向指針的指針。這種多級指針可以用來表示更加複雜的内存關系,或者在某些情況下,爲了通過函數傳遞指針并修改指針本身(而不僅僅是指針指向的值)時使用。
聲明與初始化
假設你有一個整型指針*int,那麽一個指向這個整型指針的指針就會是**int。聲明和初始化一個指針的指針的方式如下:
Pythonpackage main import "fmt" func main() { // 聲明一個整型變量 var num int = 10 // 聲明一個指向整型的指針 var ptr *int = &num // 聲明一個指向指針的指針(即指針的指針) var ptrToPtr **int = &ptr // 修改通過指針的指針訪問的值 **ptrToPtr = 20 fmt.Println(num) // 輸出: 20 fmt.Println(*ptr) // 輸出: 20 fmt.Println(*ptrToPtr) // 輸出: 地址,顯示ptr的地址 }
使用場景
指針的指針在實際編程中的使用相對較少,但在某些特定場景下非常有用,例如:
- 當你需要通過函數修改一個指針變量本身(比如讓指針指向不同的内存地址)時。
- 在配置或設置結構體的指針成員時,特别是這些成員也是指針類型。
- 在某些高級的數據結構或底層系統編程中,用于複雜的數據操作和内存管理。
new() 函數
Go語言還提供了另外一種方法來創建指針變量,格式如下:
new(類型)
一般這樣寫:
Pythonstr := new(string) *str = "Go語言教程" fmt.Println(*str)
new() 函數可以創建一個對應類型的指針,創建過程會分配内存,被創建的指針指向默認值。
參考文章:(6 封私信 / 42 條消息) Go 語言怎麽定義和使用指針? - 知乎 (zhihu.com)