Go语言指针学习

Go语言指针详解

一、指针的基本概念

指针是Go语言中的一种特殊类型,它的实例保存的是被引用对象的内存地址,而非对象自身的值。当指针变量声明后若未获取到有效的内存地址,其默认值为nil(空引用)。

示例代码

package main

import "fmt"

func main() {
    var p1 *bool
    fmt.Println("p1 的默认值:", p1)

    var p2 *int8
    fmt.Println("p2 的默认值:", p2)

    var p3 *string
    fmt.Println("p3 的默认值:", p3)
}

运行结果

p1 的默认值: <nil>
p2 的默认值: <nil>
p3 的默认值: <nil>

在Go语言中,在类型名称前面加上“*”(星号)运算符,就是对应的指针类型。

示例代码

package main

func main() {
    // 指向float32类型的指针
    var pt1 *float32
    // 指向int类型的指针
    var pt2 *int
    // 指向int64类型的指针
    var pt3 *int64
}

二、获取变量的内存地址

在变量名称前面加上“&”运算符,就能获取该变量值的内存地址,其运算结果为指向该变量类型的指针。

示例代码

package main

import "fmt"

func main() {
    // 声明字符串类型的变量并初始化
    var k = "abcdefg"
    // 使用 & 取址运算符获取变量k的内存地址
    // 内存示意图:
    // k变量(值"abcdefg")地址:0xc0000a2080
    // pk指针变量存储这个地址值
    var pk = &k
    fmt.Printf("k的内存地址: %p\n", pk)
    fmt.Printf("pk的类型: %T\n", pk)
}

运行结果

k的内存地址: 0xc0000a2080
pk的类型: *string

三、何时使用指针类型

案例一:函数参数传递问题

以下面代码为例,原本期望通过addOne函数将变量n的值加1,但实际结果并非如此。

package main

import "fmt"

func addOne(x int) {
    x++
}

func main() {
    var n = 7
    fmt.Printf("调用 addOne 函数前, n的值: %d\n", n)
    // 调用addOne函数
    addOne(n)
    fmt.Printf("调用 addOne 函数后, n的值: %d\n", n)
}

运行结果

调用 addOne 函数前, n的值: 7
调用 addOne 函数后, n的值: 7

问题原因在于:当将变量n传递给参数x时,变量n会自我复制一个int值(副本),随后参数x所引用的是复制后的int值。因此,在addOne函数内部被加上1的是复制后的值,而不是变量n所引用的原值。如下图所示↓

解决办法是使用指针,修改addOne函数如下:

package main

import "fmt"

func addOne(x *int) {
    *x++
}

func main() {
    var n = 7
    fmt.Printf("调用 addOne 函数前, n的值: %d\n", n)
    // 调用addOne函数
    addOne(&n)
    fmt.Printf("调用 addOne 函数后, n的值: %d\n", n)
}

运行结果

调用 addOne 函数前, n的值: 7
调用 addOne 函数后, n的值: 8

在这里插入图片描述

案例二:结构体方法修改问题

定义一个结构体test及相关方法setTag,原本期望通过setTag方法修改结构体实例的tag字段值,但实际未成功。

package main

import "fmt"

// 定义新的结构体
type test struct {
    tag string
}

// test的方法
func (o test) setTag(v string) {
    o.tag = v
}

func main() {
    var s test = test{tag: "abc"}
    fmt.Printf("调用 setTag 方法前, tag字段的值: %s\n", s.tag)
    s.setTag("xyz")
    fmt.Printf("调用 setTag 方法后, tag字段的值: %s\n", s.tag)
}

运行结果

调用 setTag 方法前, tag字段的值: abc
调用 setTag 方法后, tag字段的值: abc

原因是调用setTag方法时,变量s引用的test实例自我复制了一份,被修改的tag字段属于复制后的新实例,而不是原来的test实例。

解决方法是在定义setTag方法时使用指针类型:

package main

import "fmt"

// 定义新的结构体
type test struct {
    tag string
}

// test的方法,使用指针类型
func (o *test) setTag(v string) {
    o.tag = v
}

func main() {
    var s test = test{tag: "abc"}
    fmt.Printf("调用 setTag 方法前, tag字段的值: %s\n", s.tag)
    s.setTag("xyz")
    fmt.Printf("调用 setTag 方法后, tag字段的值: %s\n", s.tag)
}

运行结果

调用 setTag 方法前, tag字段的值: abc
调用 setTag 方法后, tag字段的值: xyz

四、new函数

new函数用于为指定的类型分配内存空间,并使用类型的默认值进行初始化,最后返回新分配内存空间的地址。new函数的定义如下:

func new(Type) *Type

示例代码

package main

import "fmt"

func main() {
    var px *int16 = new(int16)
    fmt.Printf("px指向的内存地址: %p\n", px)
    fmt.Printf("px所指向对象的值: %v\n", *px)
}

运行结果

px指向的内存地址: 0xc0000100a8
px所指向对象的值: 0

需要注意以下几点:

  1. new(string)所分配的值并不是nil,而是空字符串""
  2. 结构体分配内存空间后,会为其字段分配默认值。
package main

import "fmt"

type demo struct {
    a int
    b bool
}

func main() {
    var ptr = new(demo)
    fmt.Printf("字段a的值: %d\n", ptr.a)
    fmt.Printf("字段b的值: %v\n", ptr.b)
}

运行结果

字段a的值: 0
字段b的值: false
  1. 接口类型使用new函数分配内存空间后,其默认值为nil(空指针)。
package main

import "fmt"

type sender interface {
    Lineout(data []byte) int
}

func main() {
    var pi = new(sender)
    fmt.Printf("接口类型指针pi的值: %v\n", pi)
}

运行结果

接口类型指针pi的值: <nil>

豆包:

Go语言指针学习

一、指针的基本概念

指针是Go语言中的一种特殊类型,它的实例保存的是被引用对象的内存地址,而非对象自身的值。当指针变量声明后若未获取到有效的内存地址,其默认值为nil(空引用)。

示例代码

package main

import "fmt"

func main() {
    var p1 *bool
    fmt.Println("p1 的默认值:", p1)

    var p2 *int8
    fmt.Println("p2 的默认值:", p2)

    var p3 *string
    fmt.Println("p3 的默认值:", p3)
}

运行结果

p1 的默认值: <nil>
p2 的默认值: <nil>
p3 的默认值: <nil>

在Go语言中,在类型名称前面加上“*”(星号)运算符,就是对应的指针类型。

示例代码

package main

func main() {
    // 声明三种不同类型的指针变量
    var pt1 *float32  // 指向float32类型的指针(8字节浮点数)
    var pt2 *int     // 指向int类型的指针(8字节整数)
    var pt3 *int64   // 指向int64类型的指针(8字节长整数)
    // 内存结构示意图:
    // [指针变量] → [8字节内存块存储实际值的地址]
}

二、获取变量的内存地址

在变量名称前面加上“&”运算符,就能获取该变量值的内存地址,其运算结果为指向该变量类型的指针。

示例代码

package main

import "fmt"

func main() {
    // 声明字符串类型的变量并初始化
    var k = "abcdefg"
    // 使用 & 取址运算符获取变量k的内存地址
    // 内存示意图:
    // k变量(值"abcdefg")地址:0xc0000a2080
    // pk指针变量存储这个地址值
    var pk = &k
    fmt.Printf("k的内存地址: %p\n", pk)
    fmt.Printf("pk的类型: %T\n", pk)
}

运行结果

k的内存地址: 0xc0000a2080
pk的类型: *string

三、何时使用指针类型

案例一:函数参数传递问题

以下面代码为例,原本期望通过addOne函数将变量n的值加1,但实际结果并非如此。

package main

import "fmt"

func addOne(x int) {
    x++
}

func main() {
    var n = 7
    fmt.Printf("调用 addOne 函数前, n的值: %d\n", n)
    // 调用addOne函数
    addOne(n)
    fmt.Printf("调用 addOne 函数后, n的值: %d\n", n)
}

运行结果

调用 addOne 函数前, n的值: 7
调用 addOne 函数后, n的值: 7

问题原因在于:当将变量n传递给参数x时,变量n会自我复制一个int值(副本),随后参数x所引用的是复制后的int值。因此,在addOne函数内部被加上1的是复制后的值,而不是变量n所引用的原值。

解决办法是使用指针,修改addOne函数如下:

package main

import "fmt"

func addOne(x *int) {
    *x++
}

func main() {
    var n = 7
    fmt.Printf("调用 addOne 函数前, n的值: %d\n", n)
    // 调用addOne函数
    addOne(&n)
    fmt.Printf("调用 addOne 函数后, n的值: %d\n", n)
}

运行结果

调用 addOne 函数前, n的值: 7
调用 addOne 函数后, n的值: 8

案例二:结构体方法修改问题

定义一个结构体test及相关方法setTag,原本期望通过setTag方法修改结构体实例的tag字段值,但实际未成功。

package main

import "fmt"

// 定义新的结构体
type test struct {
    tag string
}

// test的方法
func (o test) setTag(v string) {
    o.tag = v
}

func main() {
    var s test = test{tag: "abc"}
    fmt.Printf("调用 setTag 方法前, tag字段的值: %s\n", s.tag)
    s.setTag("xyz")
    fmt.Printf("调用 setTag 方法后, tag字段的值: %s\n", s.tag)
}

运行结果

调用 setTag 方法前, tag字段的值: abc
调用 setTag 方法后, tag字段的值: abc

原因是调用setTag方法时,变量s引用的test实例自我复制了一份,被修改的tag字段属于复制后的新实例,而不是原来的test实例。

解决方法是在定义setTag方法时使用指针类型:

package main

import "fmt"

// 定义新的结构体
type test struct {
    tag string
}

// test的方法,使用指针类型
func (o *test) setTag(v string) {
    o.tag = v
}

func main() {
    var s test = test{tag: "abc"}
    fmt.Printf("调用 setTag 方法前, tag字段的值: %s\n", s.tag)
    s.setTag("xyz")
    fmt.Printf("调用 setTag 方法后, tag字段的值: %s\n", s.tag)
}

运行结果

调用 setTag 方法前, tag字段的值: abc
调用 setTag 方法后, tag字段的值: xyz

四、new函数

new函数用于为指定的类型分配内存空间,并使用类型的默认值进行初始化,最后返回新分配内存空间的地址。new函数的定义如下:

func new(Type) *Type

示例代码

package main

import "fmt"

func main() {
    var px *int16 = new(int16)
    fmt.Printf("px指向的内存地址: %p\n", px)
    fmt.Printf("px所指向对象的值: %v\n", *px)
}

运行结果

px指向的内存地址: 0xc0000100a8
px所指向对象的值: 0

需要注意以下几点:

  1. new(string)所分配的值并不是nil,而是空字符串""
  2. 结构体分配内存空间后,会为其字段分配默认值。
package main

import "fmt"

type demo struct {
    a int
    b bool
}

func main() {
    var ptr = new(demo)
    fmt.Printf("字段a的值: %d\n", ptr.a)
    fmt.Printf("字段b的值: %v\n", ptr.b)
}

运行结果

字段a的值: 0
字段b的值: false
  1. 接口类型使用new函数分配内存空间后,其默认值为nil(空指针)。
package main

import "fmt"

type sender interface {
    Lineout(data []byte) int
}

func main() {
    var pi = new(sender)
    fmt.Printf("接口类型指针pi的值: %v\n", pi)
}

运行结果

接口类型指针pi的值: <nil>

五、指针的运算与操作

在Go语言中,指针的运算不像C/C++ 那样支持随意的算术运算(如指针加减整数来遍历数组等),Go语言对指针运算进行了限制,以增强程序的安全性。但我们可以通过指针来访问和修改其指向的变量的值。

示例代码

package main

import "fmt"

func main() {
    num := 10
    ptr := &num
    fmt.Printf("修改前num的值: %d\n", num)
    *ptr = 20
    fmt.Printf("修改后num的值: %d\n", num)
}

运行结果

修改前num的值: 10
修改后num的值: 20

在这个例子中,我们先定义了一个变量num,然后创建了一个指向它的指针ptr。通过*ptr的方式可以访问并修改num的值。

六、指针与切片、映射和结构体

(一)指针与切片

虽然在Go语言中直接使用指针操作切片的情况并不常见,但了解其原理也很重要。切片本身是一个结构体,包含指向底层数组的指针、切片的长度和容量信息。当将切片作为参数传递给函数时,实际上是按值传递这个切片结构体,由于结构体中包含指向底层数组的指针,所以在函数内部对切片元素的修改可能会影响到原始切片。

示例代码

package main

import "fmt"

func modifySlice(s []int) {
    s[0] = 100
}

func main() {
    slice := []int{1, 2, 3}
    fmt.Printf("修改前切片: %v\n", slice)
    modifySlice(slice)
    fmt.Printf("修改后切片: %v\n", slice)
}

运行结果

修改前切片: [1 2 3]
修改后切片: [100 2 3]

(二)指针与映射

映射是引用类型,当将映射传递给函数时,不需要使用指针也能在函数内部修改映射内容。但在某些情况下,比如需要在函数中改变映射本身(例如重新分配一个新的映射),可以使用指向映射的指针。

示例代码

package main

import "fmt"

func changeMap(m *map[string]int) {
    *m = make(map[string]int)
    (*m)["newKey"] = 1
}

func main() {
    myMap := map[string]int{"key": 2}
    fmt.Printf("修改前映射: %v\n", myMap)
    changeMap(&myMap)
    fmt.Printf("修改后映射: %v\n", myMap)
}

运行结果

修改前映射: map[key:2]
修改后映射: map[newKey:1]

(三)指针与结构体

使用指针指向结构体可以避免在传递结构体时进行值复制,尤其是对于较大的结构体,这样能提高程序的效率。同时,通过指针可以方便地修改结构体的字段值。

示例代码

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func updatePerson(p *Person) {
    p.Name = "New Name"
    p.Age = 30
}

func main() {
    person := Person{Name: "Old Name", Age: 25}
    fmt.Printf("修改前结构体: %+v\n", person)
    updatePerson(&person)
    fmt.Printf("修改后结构体: %+v\n", person)
}

运行结果

修改前结构体: {Name:Old Name Age:25}
修改后结构体: {Name:New Name Age:30}

七、指针的注意事项

  1. 空指针引用:在使用指针之前,一定要确保指针不是nil,否则在访问或修改指针指向的值时会导致程序崩溃。
  2. 指针的生命周期:要注意指针所指向的变量的生命周期。如果指针指向的变量在其之前被销毁,那么该指针就成为了悬空指针,使用悬空指针也会导致程序出现未定义行为。
  3. 指针与并发安全:在并发编程中,多个 goroutine 同时访问和修改同一个指针指向的内容时,可能会出现数据竞争问题,需要使用合适的同步机制(如互斥锁)来保证并发安全。

通过对Go语言指针的学习,我们可以更好地理解内存管理、函数参数传递以及复杂数据结构的操作,从而编写出更高效、灵活和健壮的Go语言程序。

package main

import "fmt"

// 定义 Person 结构体
type Person struct {
    Name string
    Age  int
}

// updatePerson 函数用于修改 Person 结构体的字段
func updatePerson(p *Person) {
    p.Name = "New Name"
    p.Age = 30
}

func main() {
    person := Person{Name: "Old Name", Age: 25}
    fmt.Printf("修改前结构体: %+v\n", person)
    updatePerson(&person)
    fmt.Printf("修改后结构体: %+v\n", person)
}    
  • 结构体定义:Person 结构体包含 Name 和 Age 两个字段。
  • updatePerson 函数:该函数接收一个指向 Person 结构体的指针,借助这个指针来修改结构体的字段。
  • main 函数:创建一个 Person 结构体实例,先输出修改前的结构体内容,接着调用 updatePerson
    函数修改结构体,最后输出修改后的结构体内容

运行结果

修改前结构体: {Name:Old Name Age:25}
修改后结构体: {Name:New Name Age:30}

七、指针的注意事项

  1. 空指针引用:在使用指针之前,一定要确保指针不是nil,否则在访问或修改指针指向的值时会导致程序崩溃。
  2. 指针的生命周期:要注意指针所指向的变量的生命周期。如果指针指向的变量在其之前被销毁,那么该指针就成为了悬空指针,使用悬空指针也会导致程序出现未定义行为。
  3. 指针与并发安全:在并发编程中,多个 goroutine 同时访问和修改同一个指针指向的内容时,可能会出现数据竞争问题,需要使用合适的同步机制(如互斥锁)来保证并发安全。

通过对Go语言指针的学习,我们可以更好地理解内存管理、函数参数传递以及复杂数据结构的操作,从而编写出更高效、灵活和健壮的Go语言程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值