
在go语言中,指针是一个核心概念,它允许程序直接操作内存地址,从而实现更灵活的数据管理和高效的程序设计。理解*(星号)和&(取地址符)这两个操作符是掌握go语言指针的关键。
1. & 操作符:取地址(Address-of Operator)
& 操作符用于获取一个变量的内存地址。当应用于一个变量时,它会返回该变量在内存中的地址,这个地址就是一个指向该变量类型的新指针。
语法: &variable
示例:
package main
import "fmt"
func main() {
value := 100
ptr := &value // ptr 现在是一个指向 value 变量的指针
fmt.Printf("变量 value 的值:%d\n", value)
fmt.Printf("变量 value 的内存地址:%p\n", ptr) // %p 用于打印指针地址
}在上述示例中,ptr 变量存储的不是 100 这个值,而是 value 变量在内存中的具体位置。
立即学习“go语言免费学习笔记(深入)”;
2. * 操作符:指针的声明、解引用与间接赋值
* 操作符在Go语言中扮演了多重角色,其具体含义取决于它出现的位置。
2.1 指针类型声明(Pointer Type Declaration)
当 * 紧跟在一个类型前面时(例如 *int, *string),它表示声明一个指向该类型数据的指针。这意味着这个变量将存储一个内存地址,而这个地址指向的是指定类型的数据。
语法: var variableName *Type
示例:
package main
import "fmt"
func main() {
var p *int // 声明 p 是一个指向 int 类型数据的指针
fmt.Printf("p 的零值(未初始化):%v\n", p) // 指针的零值是 nil
}2.2 指针解引用(Dereferencing)
当 * 放置在一个已声明的指针变量前面时,它被称为解引用操作符。解引用操作用于访问或获取指针所指向的内存地址中存储的实际值。
语法: *pointerVariable
示例:
package main
import "fmt"
func main() {
value := 200
ptr := &value // ptr 指向 value 的地址
fmt.Printf("通过 ptr 解引用获取 value 的值:%d\n", *ptr) // *ptr 获取 ptr 指向的值
}2.3 指针间接赋值(Indirect Assignment)
通过解引用操作符,我们不仅可以读取指针指向的值,还可以修改该值。当 *pointerVariable 出现在赋值语句的左侧时,它表示修改指针所指向内存地址中的值。
语法: *pointerVariable = newValue
示例:
package main
import "fmt"
func main() {
num := 300
ptr := &num // ptr 指向 num 的地址
fmt.Printf("修改前 num 的值:%d\n", num)
*ptr = 400 // 通过 ptr 间接修改 num 的值
fmt.Printf("修改后 num 的值:%d\n", num)
}3. 综合示例分析
为了更深入地理解 & 和 * 的协同工作,我们来分析一个典型的代码片段:
package main
import (
"fmt"
"os"
)
func main() {
s := "hello" // 声明并初始化字符串变量 s
if s[1] != 'e' { // 检查字符串 s 的第二个字符是否为 'e'
os.Exit(1) // 如果不是,则退出程序
}
fmt.Printf("s 初始值: %s, 地址: %p\n", s, &s)
s = "good bye" // 重新赋值 s
fmt.Printf("s 重新赋值后: %s, 地址: %p\n", s, &s)
var p *string = &s // 声明一个字符串指针 p,并用 s 的地址初始化它
fmt.Printf("指针 p 的值 (s 的地址): %p\n", p)
fmt.Printf("通过指针 p 解引用得到的值: %s\n", *p)
*p = "ciao" // 通过指针 p 间接修改 s 的值
fmt.Printf("通过指针 p 修改后 s 的值: %s\n", s)
fmt.Printf("通过指针 p 修改后,通过 p 解引用得到的值: %s\n", *p)
}分析过程:
- s := "hello":变量 s 被创建并赋值为 "hello"。
- s = "good bye":s 的值被更新为 "good bye"。此时 s 的内存地址保持不变,但其内容已更改。
- var p *string = &s:
- &s:获取变量 s 的内存地址。
- *string:声明 p 是一个指向 string 类型的指针。
- =:将 s 的内存地址赋值给 p。现在,p 指向 s 所在的内存位置。
- *p = "ciao":
- *p:解引用 p,表示访问 p 所指向的内存位置(即 s 的内存位置)。
- =:将 "ciao" 这个字符串赋值到 p 所指向的内存位置。这意味着 s 的值被间接修改为 "ciao"。
运行此代码,您会观察到 s 的值最终变为 "ciao",这证明了通过指针可以实现对原始变量的间接修改。
4. Go语言中的“引用”与“值传递”
Go语言中所有函数参数的传递都是值传递(pass by value)。这意味着当您将一个变量作为参数传递给函数时,函数会接收到该变量的一个副本。对副本的任何修改都不会影响原始变量。
然而,当您传递一个指针的值时,函数接收到的是原始变量的内存地址的副本。虽然这个地址本身是一个副本,但它指向的仍然是原始变量在内存中的位置。因此,通过这个指针副本解引用并修改其指向的值,就能够影响到原始变量。这在效果上类似于其他语言中的“引用传递”,但本质上仍是值传递——只是传递的是一个地址值。
package main
import "fmt"
func modifyValue(x int) { // 值传递,x 是 num 的副本
x = 20
}
func modifyValueByPointer(ptr *int) { // 值传递,ptr 是 &num 的副本
*ptr = 20 // 通过指针修改 num
}
func main() {
num := 10
fmt.Println("初始值:", num) // 10
modifyValue(num)
fmt.Println("值传递后:", num) // 10 (未改变)
modifyValueByPointer(&num)
fmt.Println("指针传递后:", num) // 20 (已改变)
}5. 指针的应用场景与注意事项
5.1 典型应用场景
- 修改函数外部变量:如上例所示,当需要在函数内部修改函数外部的变量时,传递变量的指针是标准做法。
- 避免大数据结构复制:当结构体或数组非常大时,将其作为参数传递会导致整个数据结构的复制,消耗大量内存和时间。传递其指针可以避免这种开销,提高性能。
- 实现链表、树等数据结构:这些数据结构天然依赖于节点间的指针连接。
- 方法接收者:Go语言中,方法的接收者可以是值类型也可以是指针类型。使用指针接收者可以修改接收者实例的字段。
5.2 注意事项
- 零值指针(nil):未初始化的指针变量的零值是 nil。尝试解引用 nil 指针会导致运行时错误(panic)。在使用指针前,务必确保其已被正确初始化或检查是否为 nil。
- Go不支持指针算术:与C/C++不同,Go语言不允许对指针进行加减运算来访问相邻内存地址。这是为了提高内存安全性和代码可读性。
- 垃圾回收:Go语言拥有自动垃圾回收机制,开发者无需手动管理内存的分配和释放。当一个内存地址不再有任何指针指向它时,垃圾回收器会在适当的时候自动回收这块内存。
总结
& 和 * 是Go语言中操作指针的两个基本且强大的操作符。& 用于获取变量的内存地址,生成一个指针;而 * 则用于声明指针类型、解引用指针以访问其指向的值,以及通过指针间接修改变量的值。理解并熟练运用它们,是编写高效、安全Go程序的基础。掌握指针不仅能让您更深入地理解Go的内存模型,也能帮助您在特定场景下优化程序性能和实现复杂的数据结构。










