文章目录
- Go语言特性
- Go语言指针
- Go指针示例
- Go应用场景
Go语言特性
- Go函数只有 值传递
- Go指针 不能运算
- 指针类型的定义与基础数据类型有关,即指针类型是与其指向的变量的类型相关联的。
Go语言指针
使用new函数: new函数用于分配内存,并返回一个指向该类型的新零值的指针。
- new(int)分配了一个int类型的内存,并返回一个指向该内存的指针。
package mainimport "fmt"func main() {var p *intp = new(int)fmt.Println(*p, p, &p) // 输出: 0 0xc00009e018 0xc0000a4018
}
取地址符号(&): 取地址符号&可以获取一个变量的内存地址。
- &a获取变量a的内存地址,并将其赋值给指针变量p。
package mainimport "fmt"func main() {var a int = 42var p *int = &afmt.Println(*p, p, &p) // 输出: 42 0xc000112008 0xc000108018
}
- *p 是指针p所指向的变量的值,即a的值,输出42。
- p 是指针变量,存储的是变量a的内存地址,这个地址可能是像 0xc000112008 这样的值(实际地址在每次运行时可能会不同)。
- &p 是指针变量p本身的内存地址,因为p是定义在函数栈上的一个变量,所以它也有自己的内存地址,类似 0xc000108018 这样的值(实际地址在每次运行时可能会不同)。
使用*操作符: 指针也可以直接通过*操作符进行解引用或指向具体的值。
- *p = 42将值42赋值给指针p所指向的内存地址。
package mainimport "fmt"func main() {var p *intp = new(int)*p = 42fmt.Println(p, *p, &p) // 输出:0xc00009e018 42 0xc0000a4018
}
作为函数参数: 函数参数也可以是指针类型,允许在函数内修改变量的值。Go语言函数只有值传递,所以常用指针作为函数参数。
package mainimport "fmt"func setToZero(p *int) {*p = 0
}func main() {var x int = 5setToZero(&x)// x 现在是 0fmt.Println(x) // 输出:0
}
指向结构体的指针: 结构体(struct)是用户定义的类型,用于将一组数据组合在一起。指向结构体的指针在处理复杂数据和需要修改结构体字段的场景中非常有用。
package mainimport "fmt"func main() {type Person struct {Name stringAge int}var person Person = Person{Name: "Alice", Age: 30}var pPerson *Person = &personfmt.Println(pPerson) // 输出: &{Alice 30}fmt.Println(*pPerson) // 输出: {Alice 30}fmt.Println(&pPerson) // 输出: 0xc000054020
}
作为函数返回值: 返回指针可以用于创建和返回函数内部的变量,或者用于修改调用者传入的数据。使用指针能够提高性能,避免不必要的值拷贝,但需要注意避免悬空指针和内存泄漏等问题。
package mainimport "fmt"// 定义一个返回指针的函数
func createIntPointer(value int) *int {// 在函数内部创建一个变量v := value// 返回该变量的指针return &v
}func main() {// 调用返回指针的函数p := createIntPointer(42)// 打印指针和值fmt.Println("指针:", p) // 输出: 指针: 0xc0000121a8fmt.Println("值:", *p) // 输出: 值: 42
}
- 返回结构体指针是一种常见的实践,尤其是在需要创建新的结构体实例并将其返回给调用者时。这样可以避免结构体的值拷贝,直接操作内存中的数据,提高效率。
package mainimport "fmt"// 定义一个结构体类型
type Person struct {Name stringAge int
}// 定义一个返回结构体指针的函数
func NewPerson(name string, age int) *Person {// 创建结构体实例person := Person{Name: name,Age: age,}// 返回结构体的指针return &person
}func main() {// 调用返回结构体指针的函数p := NewPerson("Alice", 30)// 访问和修改结构体字段fmt.Println("Name:", p.Name) // 输出: Name: Alicefmt.Println("Age:", p.Age) // 输出: Age: 30// 修改结构体字段p.Age = 31fmt.Println("Updated Age:", p.Age) // 输出: Updated Age: 31
}
结构体中的指针字段: 结构体字段可以是指针类型,这使得我们可以更灵活地操作复杂的数据结构。
package mainimport "fmt"type Node struct {Value intNext *Node
}func main() {node1 := &Node{Value: 1}node2 := &Node{Value: 2}node1.Next = node2fmt.Println(node1) // 输出: &{1 0xc000014080}fmt.Println(node1.Next) // 输出: &{2 <nil>}
}
指向数组的指针: 数组的指针可以通过指向数组的第一个元素来获得。
package mainimport "fmt"func main() {var arr [3]int = [3]int{1, 2, 3}var p *[3]int = &arrfmt.Println(p) // 输出: &[1 2 3]fmt.Println(*p) // 输出: [1 2 3]fmt.Println(&p) // 输出: 0xc000054020
}
指针数组: 指针数组是一个存储指针的数组,每个元素都是一个指向某类型的指针。
package mainimport "fmt"func main() {var arr [3]*inta, b, c := 1, 2, 3arr[0] = &aarr[1] = &barr[2] = &cfmt.Println(arr) // 输出: [0xc00009e018 0xc00009e020 0xc00009e028]
}
指向切片的指针: 切片(slice)是一种动态数组,可以在程序运行时动态调整大小。虽然切片本身已经是一个引用类型,通常情况下不需要使用指向切片的指针,但在某些特殊场景下,指向切片的指针可以更高效或更简洁地处理一些操作。
package mainimport "fmt"func main() {slice := []int{1, 2, 3}// 定义一个指向切片的指针var p *[]int = &slice// 使用指向切片的指针修改切片*p = append(*p, 4, 5, 6)// 输出切片内容fmt.Println(slice) // 输出: [1 2 3 4 5 6]fmt.Println(p, *p, &p) // 输出: &[1 2 3 4 5 6] [1 2 3 4 5 6] 0xc000054020
}
指向映射的指针: 映射(map)是一种内置的数据结构,用于存储键值对。与切片类似,映射本质上是引用类型,因此在大多数情况下,直接传递映射就足够了。然而,在某些特定的场景下,使用指向映射的指针(map pointer)可以提供更高的灵活性和性能优化。
package mainimport "fmt"func main() {// 定义一个映射m := map[string]int{"one": 1, "two": 2}// 定义一个指向映射的指针var p *map[string]int = &m// 使用指向映射的指针修改映射(*p)["three"] = 3// 输出映射内容fmt.Println(m) // 输出: map[one:1 three:3 two:2]fmt.Println(p, *p, &p) // 输出: &map[one:1 three:3 two:2] map[one:1 three:3 two:2] 0xc000054028
}
指向函数的指针: 函数也是一种类型,因此可以创建指向函数的指针。这种功能在实现回调机制、函数作为参数传递以及动态调用函数等场景中非常有用。
package mainimport "fmt"// 定义一个类型为函数指针的回调函数
type Callback func(int) int// 应用回调函数的函数
func applyCallback(x int, callback Callback) int {return callback(x)
}// 回调函数示例
func double(n int) int {return n * 2
}func main() {result := applyCallback(5, double)fmt.Println(result, &result) // 输出: 10 0xc00009e018fmt.Println(double) // 输出: 0x5558980
}
Go指针示例
package mainimport "fmt"type Person struct {Name stringAge int
}func add(a, b int) int {return a + b
}func main() {var pInt *intpInt = new(int)fmt.Println("整型指针:", &pInt, *pInt, pInt)var pString *stringpString = new(string)fmt.Println("字符串指针:", &pString, *pString, pString)var pArr *[2]intpArr = new([2]int)fmt.Println("数组指针:", &pArr, *pArr, pArr)var pSlice *[]intpSlice = new([]int)fmt.Println("切片指针:", &pSlice, *pSlice, pSlice)slice := []int{1, 2, 3}var pointSlice *[]int = &slicefmt.Println("切片指针:", &pointSlice, *pointSlice, pointSlice)var person Person = Person{Name: "Alice", Age: 20}var pPerson *Person = &personfmt.Println("结构体指针:", &pPerson, *pPerson, pPerson)fmt.Println("函数指针:", add)
}
- 输出结果
整型指针: 0xc0000a6010 0 0xc00009e018
字符串指针: 0xc0000a6020 0xc000090020
数组指针: 0xc0000a6028 [0 0] &[0 0]
切片指针: 0xc0000a6030 [] &[]
切片指针: 0xc0000a6038 [1 2 3] &[1 2 3]
结构体指针: 0xc0000a6040 {Alice 20} &{Alice 20}
函数指针: 0xeecb980
Go应用场景
函数参数传递: 当函数参数是一个较大的结构体或数组时,传递指针而不是副本可以显著提高性能并节省内存。
package mainimport ("fmt"
)type LargeStruct struct {Field1 [1024]intField2 [1024]int
}func modifyLargeStruct(ls *LargeStruct) {ls.Field1[0] = 999
}func main() {ls := LargeStruct{}modifyLargeStruct(&ls)fmt.Println(ls.Field1[0]) // 输出: 999
}
共享内存: 在多线程或并发编程中,指针用于共享数据和同步状态。
package mainimport ("fmt""sync"
)func increment(counter *int, wg *sync.WaitGroup) {*counter++wg.Done() // 完成时调用Done
}func main() {var counter intvar wg sync.WaitGroup // 用于等待一组goroutines完成执行的同步机制for i := 0; i < 1000; i++ {wg.Add(1) // 增加一个需要等待的goroutine计数go increment(&counter, &wg)}wg.Wait() // 等待所有goroutine完成fmt.Println("Counter:", counter) // 输出: Counter: 989
}
实现链表等复杂数据结构: 链表、树等数据结构的实现通常需要使用指针来链接节点。
package mainimport ("fmt"
)type Node struct {Value intNext *Node
}func main() {head := &Node{Value: 1}second := &Node{Value: 2}third := &Node{Value: 3}head.Next = secondsecond.Next = thirdfor n := head; n != nil; n = n.Next {fmt.Println(n.Value)}
// 输出:
// 1
// 2
// 3
}
修改变量值: 通过指针,可以在函数中修改传入变量的值,而不仅仅是函数内部的局部副本。
package mainimport ("fmt"
)func setToZero(x *int) {*x = 0
}func main() {a := 5setToZero(&a)fmt.Println(a) // 输出: 0
}
减少内存分配: 在高性能场景中,频繁的内存分配和释放会影响性能,指针可以帮助减少这种开销。
package mainimport ("fmt"
)type Item struct {Value int
}func processItem(item *Item) {item.Value = 42
}func main() {items := make([]Item, 5)for i := range items {processItem(&items[i])}fmt.Println(items) // 输出: [{42} {42} {42} {42} {42}]
}
实现回调函数: 指针用于传递函数指针,实现回调机制。
package mainimport ("fmt"
)func apply(f func(int) int, x *int) {*x = f(*x)
}func double(n int) int {return n * 2
}func main() {x := 5apply(double, &x)fmt.Println(x) // 输出: 10
}
内存映射文件(mmap): 在操作系统支持内存映射文件的情况下,指针可以用于直接访问文件内容,提高IO操作效率。
- example.txt
Golang
import ("os""fmt""syscall"
)func main() {f, err := os.OpenFile("example.txt", os.O_RDWR, 0644)if err != nil {panic(err)}defer f.Close()data, err := syscall.Mmap(int(f.Fd()), 0, 4096, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)if err != nil {panic(err)}defer syscall.Munmap(data)// 修改内存映射区域的数据data[0] = 'H'data[1] = 'i'fmt.Println(string(data)) // 输出: Hilang
}
高效的缓存和池管理: 指针用于实现对象池,以复用对象,减少GC压力。
package mainimport ("fmt""sync"
)var pool = sync.Pool{New: func() interface{} {return &Object{}},
}type Object struct {Value int
}func main() {obj := pool.Get().(*Object)obj.Value = 42pool.Put(obj)anotherObj := pool.Get().(*Object)fmt.Println(anotherObj.Value) // 输出: 42
}