Go语言特性
- Go函数只有 值传递
- Go指针 不能运算
- 指针类型的定义与基础数据类型有关,即指针类型是与其指向的变量的类型相关联的。
Go语言指针
使用new函数: new函数用于分配内存,并返回一个指向该类型的新零值的指针。
- new(int)分配了一个int类型的内存,并返回一个指向该内存的指针。
package main
import "fmt"
func main() {
var p *int
p = new(int)
fmt.Println(*p, p, &p) // 输出: 0 0xc00009e018 0xc0000a4018
}
取地址符号(&): 取地址符号&可以获取一个变量的内存地址。
- &a获取变量a的内存地址,并将其赋值给指针变量p。
package main
import "fmt"
func main() {
var a int = 42
var p *int = &a
fmt.Println(*p, p, &p) // 输出: 42 0xc000112008 0xc000108018
}
- *p 是指针p所指向的变量的值,即a的值,输出42。
- p 是指针变量,存储的是变量a的内存地址,这个地址可能是像 0xc000112008 这样的值(实际地址在每次运行时可能会不同)。
- &p 是指针变量p本身的内存地址,因为p是定义在函数栈上的一个变量,所以它也有自己的内存地址,类似 0xc000108018 这样的值(实际地址在每次运行时可能会不同)。
使用*操作符: 指针也可以直接通过*操作符进行解引用或指向具体的值。
- *p = 42将值42赋值给指针p所指向的内存地址。
package main
import "fmt"
func main() {
var p *int
p = new(int)
*p = 42
fmt.Println(p, *p, &p) // 输出:0xc00009e018 42 0xc0000a4018
}
作为函数参数: 函数参数也可以是指针类型,允许在函数内修改变量的值。Go语言函数只有值传递,所以常用指针作为函数参数。
package main
import "fmt"
func setToZero(p *int) {
*p = 0
}
func main() {
var x int = 5
setToZero(&x)
// x 现在是 0
fmt.Println(x) // 输出:0
}
指向结构体的指针: 结构体(struct)是用户定义的类型,用于将一组数据组合在一起。指向结构体的指针在处理复杂数据和需要修改结构体字段的场景中非常有用。
package main
import "fmt"
func main() {
type Person struct {
Name string
Age int
}
var person Person = Person{Name: "Alice", Age: 30}
var pPerson *Person = &person
fmt.Println(pPerson) // 输出: &{Alice 30}
fmt.Println(*pPerson) // 输出: {Alice 30}
fmt.Println(&pPerson) // 输出: 0xc000054020
}
结构体中的指针字段: 结构体字段可以是指针类型,这使得我们可以更灵活地操作复杂的数据结构。
package main
import "fmt"
type Node struct {
Value int
Next *Node
}
func main() {
node1 := &Node{Value: 1}
node2 := &Node{Value: 2}
node1.Next = node2
fmt.Println(node1) // 输出: &{1 0xc000014080}
fmt.Println(node1.Next) // 输出: &{2 <nil>}
}
指向数组的指针: 数组的指针可以通过指向数组的第一个元素来获得。
package main
import "fmt"
func main() {
var arr [3]int = [3]int{1, 2, 3}
var p *[3]int = &arr
fmt.Println(p) // 输出: &[1 2 3]
fmt.Println(*p) // 输出: [1 2 3]
fmt.Println(&p) // 输出: 0xc000054020
}
指针数组: 指针数组是一个存储指针的数组,每个元素都是一个指向某类型的指针。
package main
import "fmt"
func main() {
var arr [3]*int
a, b, c := 1, 2, 3
arr[0] = &a
arr[1] = &b
arr[2] = &c
fmt.Println(arr) // 输出: [0xc00009e018 0xc00009e020 0xc00009e028]
}
指向切片的指针: 切片(slice)是一种动态数组,可以在程序运行时动态调整大小。虽然切片本身已经是一个引用类型,通常情况下不需要使用指向切片的指针,但在某些特殊场景下,指向切片的指针可以更高效或更简洁地处理一些操作。
package main
import "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 main
import "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 main
import "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 0xc00009e018
fmt.Println(double) // 输出: 0x5558980
}
Go指针示例
package main
import "fmt"
type Person struct {
Name string
Age int
}
func add(a, b int) int {
return a + b
}
func main() {
var pInt *int
pInt = new(int)
fmt.Println("整型指针:", &pInt, *pInt, pInt)
var pString *string
pString = new(string)
fmt.Println("字符串指针:", &pString, *pString, pString)
var pArr *[2]int
pArr = new([2]int)
fmt.Println("数组指针:", &pArr, *pArr, pArr)
var pSlice *[]int
pSlice = new([]int)
fmt.Println("切片指针:", &pSlice, *pSlice, pSlice)
slice := []int{1, 2, 3}
var pointSlice *[]int = &slice
fmt.Println("切片指针:", &pointSlice, *pointSlice, pointSlice)
var person Person = Person{Name: "Alice", Age: 20}
var pPerson *Person = &person
fmt.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 main
import (
"fmt"
)
type LargeStruct struct {
Field1 [1024]int
Field2 [1024]int
}
func modifyLargeStruct(ls *LargeStruct) {
ls.Field1[0] = 999
}
func main() {
ls := LargeStruct{}
modifyLargeStruct(&ls)
fmt.Println(ls.Field1[0]) // 输出: 999
}
共享内存: 在多线程或并发编程中,指针用于共享数据和同步状态。
package main
import (
"fmt"
"sync"
)
func increment(counter *int, wg *sync.WaitGroup) {
*counter++
wg.Done() // 完成时调用Done
}
func main() {
var counter int
var 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 main
import (
"fmt"
)
type Node struct {
Value int
Next *Node
}
func main() {
head := &Node{Value: 1}
second := &Node{Value: 2}
third := &Node{Value: 3}
head.Next = second
second.Next = third
for n := head; n != nil; n = n.Next {
fmt.Println(n.Value)
}
// 输出:
// 1
// 2
// 3
}
修改变量值: 通过指针,可以在函数中修改传入变量的值,而不仅仅是函数内部的局部副本。
package main
import (
"fmt"
)
func setToZero(x *int) {
*x = 0
}
func main() {
a := 5
setToZero(&a)
fmt.Println(a) // 输出: 0
}
减少内存分配: 在高性能场景中,频繁的内存分配和释放会影响性能,指针可以帮助减少这种开销。
package main
import (
"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 main
import (
"fmt"
)
func apply(f func(int) int, x *int) {
*x = f(*x)
}
func double(n int) int {
return n * 2
}
func main() {
x := 5
apply(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 main
import (
"fmt"
"sync"
)
var pool = sync.Pool{
New: func() interface{} {
return &Object{}
},
}
type Object struct {
Value int
}
func main() {
obj := pool.Get().(*Object)
obj.Value = 42
pool.Put(obj)
anotherObj := pool.Get().(*Object)
fmt.Println(anotherObj.Value) // 输出: 42
}