作者:Sutirtha Chakraborty 翻译于2023/8/12
指针是编写优秀代码的最重要基础之一。在这篇文章中,我们将探讨指针,它们是什么,以及在Go中如何使用它们来编写高质量代码。
什么是指针?
指针是存储它所指向地址的变量。特定类型的指针只能指向该类型。
Golang指针语法
指针的语法非常简单。以下是Go中声明指针的语法。
var ptr *type
var ptrint *int // 指向int的指针
指针的零值是nil。
在Go中初始化指针
某种类型的指针可以通过变量地址(&)运算符对该类型的指针进行初始化。以下是实现方法。
package main
import (
"fmt"
)
func main() {
var q int = 42
var p *int // 声明指针
p = &q // 初始化指针
fmt.Println(p) // 0x40e020
}
- Go中的指针解引用
解引用指针意味着获取指针所持有的地址内的值。如果我们有一个内存地址,我们可以解引用指针以获取其内部的值。以下是使用星号(*)运算符显示解引用操作的相同示例。
package main
import (
"fmt"
)
func main() {
var q int = 42
var p *int
p = &q
fmt.Println(p) // 0x40e020
fmt.Println(*p) // 42
}
Go语言中的指向指针
指针变量甚至可以存储指针的地址,因为指针也是像其他变量一样的变量。因此,我们可以指向指针并创建级联间接性。这些级联间接性有时可能会引起不必要的混淆,因此在使用时要小心。
package main
import (
"fmt"
)
func main() {
i := 64
j := &i // j是指向int的指针
k := &j // k是指向指向int的指针(指向另一个指针)
fmt.Println(i) // 64
fmt.Println(j) // 0x40e020
fmt.Println(*j) // 64(地址内的值)
fmt.Println(k) // 0x40c138
fmt.Println(*k) // 0x40e020(j的地址)
}
指向接口的指针
指针甚至可以指向任何内容,甚至是接口。当指向空接口时,返回值为nil。
package main
import (
"fmt"
)
func main() {
var a interface{}
b := &a
fmt.Println(b) // 0x40c138
fmt.Println(*b) // <nil>
}
以下是带有指针的接口的示例。
package main
import (
"fmt"
)
// 声明接口
type Bird interface {
fly()
}
type B struct {
name string
}
// 实现接口
func (b B) fly() {
fmt.Println("飞翔中...")
}
func main() {
var a Bird = B{"孔雀"}
b := &a
fmt.Println(b) // 0x40c138
fmt.Println(*b) // {孔雀}
}
在这段代码中,"a"是类型为Bird的结构体。这就是多态性在实际中的应用。Go允许使用接口进行多态性。因此,可以看出在Go中,指向结构体或接口的指针是一种重要的工具。
指针作为函数参数
指针可以像值一样用于函数参数。与直接使用值本身相比,指针具有一些优势。这是将大型对象传递给函数的一种非常高效的方式。使用指针可以优化程序性能。
package main
import (
"fmt"
)
// 声明指针作为参数
func f(a *int) {
fmt.Println(*a)
}
func main() {
var a int = 42
// 传递地址
f(&a) // 42
}
直接调用大型对象可能会拉长执行时间,以下是将指向结构体的指针传递的示例。
package main
import (
"fmt"
)
type Human struct {
name string
age int
place string
}
func f(h *Human) {
fmt.Println("用户", (*h).name, "年龄为", (*h).age, "岁,来自", (*h).place)
}
func main() {
john := Human{"约翰", 36, "拉斯维加斯"}
f(&john) // 用户约翰年龄为36岁,来自拉斯维加斯
}
在解析结构体指针时要小心。如果写成*structname.field1,会报错。正确的写法是(*structname).field1。因为指针的对象是struct,而不是struct内部的值。
在函数内部使用指针可以修改外部值(值不是常量)。当我们想要更改一个值时,我们应该使用指向该值的指针作为函数参数,然后进行必要的修改。
Go中的“new”函数
Go中的new函数返回一个指向类型的指针。
package main
import (
"fmt"
)
func main() {
ptri := new(int)
*ptri = 67
fmt.Println(ptri) // 0x40e020
fmt.Println(*ptri) // 67
}
函数返回指针
任何类型的指针都可以作为值从函数中返回。与直接返回值不同,我们只需返回该值的地址。
package main
import (
"fmt"
)
func p() *int { // 指定返回类型为指针
v := 101
// 返回地址
return &v
}
func main() {
n := p()
fmt.Println(n) // 0x40e020
fmt.Println(*n) // 101
}
函数指针
在Go中,函数指针隐式工作。这意味着我们不需要将其声明为指针。
package main
import (
"fmt"
)
func main() {
f := func() {
fmt.Println("一个函数")
}
pf := f
pf() // 一个函数
}
在使用Go中的指针时要注意的事项
Go不允许指针算术。因此,我们不能像在C/C++中那样进行一元递增或递减。
总结
有时候会用到指向数组的指针,但这里建议使用切片。切片比指向数组的指针更加灵活。代码更加简洁,便于摸鱼。
总的来说,指针在Go语言中扮演着非常重要的角色,它们可以用于提高内存效率、实现数据共享、传递函数以及创建动态数据结构等。然而,在使用指针时,需要谨慎处理,确保正确地管理内存和指针引用,以避免潜在的问题。