当前位置: 首页 > article >正文

【go语言】指针

一、指针的定义和使用

       在 Go 语言中,指针是一种变量,用来存储另一个变量的内存地址。通过指针,我们可以间接地操作其他变量的值。Go 语言中的指针与其他语言(如 C 或 C++)的指针有所不同,它不支持指针算术,但提供了简洁和安全的指针操作方式。

1.1 指针的定义

在 Go 中,指针通过 * 来声明,它指向某种类型的变量。

  • * 用于声明指针类型。
  • & 用于取变量的地址(即获取指针)。
package main

import "fmt"

func main() {
    var a int = 58
    var ptr *int = &a  // ptr 是指向 a 的指针

    fmt.Println("a 的值:", a)
    fmt.Println("ptr 指向的地址:", ptr)  // 输出 a 的地址
    fmt.Println("ptr 指向的值:", *ptr)   // 输出 a 的值
}

解释:

  • var ptr *int:声明一个 ptr 变量,类型为指向 int 的指针。
  • &a:取 a 的地址,并将其赋值给指针 ptr
  • *ptr:解引用,获取 ptr 指向的地址上的值。

1.2 使用指针

1.2.1 取地址(&)

通过 & 符号,我们可以获取一个变量的内存地址。这个内存地址可以赋给一个指针。

x := 10
p := &x // p 是指向 x 的指针

1.2.2 解引用(*)

通过 * 符号,我们可以获取指针指向的变量的值,称为解引用。

value := *p // 通过解引用获取 p 指向的值

1.3 修改变量的值

       通过指针,可以直接修改原始变量的值,因为指针存储的是变量的地址,间接操作该地址即能修改变量的值。

package main

import "fmt"

func changeValue(x *int) {
    *x = 20  // 通过指针修改 x 指向的变量的值
}

func main() {
    a := 10
    fmt.Println("修改前 a 的值:", a)
    changeValue(&a)  // 传入 a 的地址
    fmt.Println("修改后 a 的值:", a)
}

1.4 指针与函数参数

       指针在函数参数中有重要应用,特别是在传递较大的数据结构(如数组、切片、结构体)时,使用指针可以避免数据的复制。它还可以用于函数修改外部变量的值。

package main

import "fmt"

func swap(x, y *int) {
    *x, *y = *y, *x  // 交换指针指向的值
}

func main() {
    a, b := 10, 20
    fmt.Println("交换前:", a, b)
    swap(&a, &b)  // 传递 a 和 b 的地址
    fmt.Println("交换后:", a, b)
}

1.5 nil 指针

       Go 语言中的指针可以是 nil,表示它没有指向任何有效的内存地址。一个 nil 指针是一个空指针,它没有指向任何对象。

var ptr *int  // ptr 默认值为 nil
fmt.Println(ptr)  // 输出 nil

使用 nil 指针时,如果尝试解引用,程序会发生 运行时错误(panic)。

var ptr *int
fmt.Println(*ptr)  // 会引发运行时错误:invalid memory address or nil pointer dereference

1.6 指针的应用

1.6.1 高效地传递数据

通过使用指针,可以避免将大型结构体或数组等数据类型进行值拷贝,从而提高性能。

type Person struct {
    Name string
    Age  int
}

func updatePerson(p *Person) {
    p.Age += 1
}

func main() {
    person := Person{Name: "Alice", Age: 30}
    updatePerson(&person)  // 传递指针,避免拷贝
    fmt.Println(person)     // Age 被修改为 31
}

1.6.2 与接口结合

       在 Go 中,指针和接口结合使用时,能够有效修改接口类型的值。例如,可以通过指针来修改结构体字段的值。

type Dog struct {
    Name string
}

func (d *Dog) Speak() {
    fmt.Println(d.Name + " says Woof!")
}

func main() {
    dog := &Dog{Name: "Buddy"}
    dog.Speak()  // 使用指针调用方法
}

二、指针的初始化

       在 Go 语言中,指针的初始化可以通过几种不同的方式进行,主要取决于如何获取一个变量的地址或使用内置的 new 函数。

2.1 通过去变量的地址来初始化指针

       最常见的初始化指针的方法是通过取一个变量的地址。使用 & 运算符来获取变量的地址并赋值给指针。

package main

import "fmt"

func main() {
    a := 42
    var ptr *int = &a  // 通过取变量 a 的地址初始化 ptr

    fmt.Println("a 的值:", a)
    fmt.Println("ptr 指向的地址:", ptr)
    fmt.Println("ptr 指向的值:", *ptr)
}
a 的值: 42
ptr 指向的地址: <地址>
ptr 指向的值: 42

2.2 使用 new 函数初始化指针

       Go 提供了 new 函数来为变量分配内存并返回一个指向该变量的指针。new 会初始化变量为该类型的零值。

package main

import "fmt"

func main() {
    ptr := new(int)  // 使用 new 函数初始化一个指向 int 的指针

    fmt.Println("ptr 指向的值:", *ptr)  // 输出 0,因为 int 的零值是 0

    *ptr = 58  // 修改指针指向的值
    fmt.Println("ptr 指向的值修改后:", *ptr)  // 输出 58
}

2.3 零值指针初始化

       Go 中的指针默认值是 nil,这意味着如果声明一个指针但没有显式初始化它,它的值是 nil。这通常表示该指针尚未指向任何有效的内存地址。

package main

import "fmt"

func main() {
    var ptr *int  // 声明一个指向 int 的指针,但未初始化
    fmt.Println("ptr 的零值:", ptr)  // 输出 nil
}

2.4 通过数组或者切片初始化指针

如果你有一个数组或切片,可以通过取数组元素的地址来初始化指针。

package main

import "fmt"

func main() {
    arr := [3]int{1, 2, 3}
    ptr := &arr[0]  // 取数组第一个元素的地址初始化指针

    fmt.Println("ptr 指向的值:", *ptr)  // 输出 1
}

三、nil 在 go 中的细节

       在 Go 语言中,nil 是一个非常重要的概念,它代表着“无值”或“空值”。它可以赋给不同类型的变量(如指针、切片、映射、通道等),表示这些变量当前没有指向任何有效的值或资源。理解 nil 的细节对编写高效、正确的 Go 代码至关重要。以下是关于 nil 在 Go 中的几个关键细节:

3.1 指针和 nil

       指针类型的变量默认值为 nil,这意味着它没有指向任何有效的内存地址。当你声明一个指针但不对其进行初始化时,它会自动初始化为 nil。你可以显式地将指针设为 nil,这表示该指针不指向任何有效的内存。

package main

import "fmt"

func main() {
    var ptr *int  // 声明一个 int 类型的指针,默认值为 nil
    fmt.Println(ptr) // 输出: <nil>

    var x int = 10
    ptr = &x  // 让指针指向变量 x 的地址
    fmt.Println(ptr) // 输出: 地址值
}

3.2 切片和 nil

       切片是 Go 中的一种动态数组类型,它由三部分组成:指向底层数组的指针、切片的长度和切片的容量。如果一个切片没有被初始化(例如没有使用 make 或直接赋值),它的默认值是 nil。一个 nil 切片的长度和容量都为 0。

package main

import "fmt"

func main() {
    var slice []int  // 声明一个切片,默认值为 nil
    fmt.Println(slice == nil)  // 输出: true
    fmt.Println(len(slice))    // 输出: 0
    fmt.Println(cap(slice))    // 输出: 0

    slice = append(slice, 1, 2, 3)  // 给切片赋值
    fmt.Println(slice)  // 输出: [1 2 3]
}

3.3 映射和 nil

       在 Go 中,映射(map)是一个无序的键值对集合。声明一个映射时,如果没有使用 make 或字面量进行初始化,它的默认值也是 nil。一个 nil 映射不能进行任何操作(如添加、删除键值对),如果你对一个 nil 映射进行读操作,结果是返回类型的零值,但写操作则会引发运行时错误。

package main

import "fmt"

func main() {
    var m map[string]int  // 声明一个映射,默认值为 nil
    fmt.Println(m == nil)  // 输出: true

    // 读取 nil 映射,返回零值
    fmt.Println(m["key"])  // 输出: 0

    // 尝试对 nil 映射进行写操作会引发错误
    // m["key"] = 10  // 运行时错误: assignment to entry in nil map
}

3.4 通道和 nil

       Go 的通道用于在 goroutine 之间进行通信。当通道没有被初始化时,它的默认值为 nil。一个 nil 通道无法发送或接收数据。如果你尝试通过 nil 通道进行通信,程序会阻塞,直到有一个有效的通道。

package main

import "fmt"

func main() {
    var ch chan int  // 声明一个通道,默认值为 nil
    fmt.Println(ch == nil)  // 输出: true

    // 使用 nil 通道会导致阻塞
    // ch <- 1  // 运行时会死锁
}

3.5 接口和 nil

       接口在 Go 中是一种特殊类型,它表示某种类型的集合。如果一个接口的具体值是 nil,并且该接口的类型也是 nil,那么接口本身就是 nil。但是,如果一个接口的类型非 nil 且值是 nil,那么这个接口不等于 nil

package main

import "fmt"

func main() {
    var i interface{}  // 声明一个空接口,默认值为 nil
    fmt.Println(i == nil)  // 输出: true

    var ptr *int
    i = ptr  // i 的类型是 *int,值为 nil
    fmt.Println(i == nil)  // 输出: false,因为接口类型是 *int(非 nil)

    // 将空接口值赋给 nil 的接口
    i = nil
    fmt.Println(i == nil)  // 输出: true
}

3.6 与 nil 的比较

   nil 可以与指针、切片、映射、通道和接口类型进行比较。需要注意的是,nil 是可以与这些类型的零值进行比较的,但对于不同类型的 nil,Go 会认为它们是不同的。例如,nil 切片与 nil 映射是不同的,因为它们的底层类型不同。

package main

import "fmt"

func main() {
    var p1 *int
    var p2 *string

    fmt.Println(p1 == nil)  // 输出: true
    fmt.Println(p2 == nil)  // 输出: true

    var m1 map[string]int
    var m2 map[int]string

    fmt.Println(m1 == nil)  // 输出: true
    fmt.Println(m2 == nil)  // 输出: true
}

 


http://www.kler.cn/a/524352.html

相关文章:

  • 動態住宅IP提升網站訪問成功率
  • 《一文读懂!Q-learning状态-动作值函数的直观理解》
  • DeepSeek理解概率的能力
  • Spring AI 在微服务中的应用:支持分布式 AI 推理
  • GPU上没程序在跑但是显存被占用
  • AndroidCompose Navigation导航精通2-过渡动画与路由切换
  • 2025 = 1^3 + 2^3 + 3^3 + 4^3 + 5^3 + 6^3 + 7^3 + 8^3 + 9^3
  • mac安装dockerdesktop优化
  • ECMAScript--promise的使用
  • AutoDL 云服务器:普通 用户 miniconda 配置
  • 二叉树介绍
  • Java多线程与高并发专题——JMM
  • 实验作业管理系统的设计与实现
  • Leetcode刷题-不定长滑动窗口
  • Vue 组件开发:构建高效可复用的前端界面要素
  • Spark Streaming的背压机制的原理与实现代码及分析
  • 力扣面试150 快乐数 循环链表找环 链表抽象 哈希
  • Java中实现ECDSA算法介绍、应用场景和示例代码
  • 机器人介绍
  • 《HelloGitHub》第 106 期
  • 扣子平台音频功能:让声音也能“智能”起来。扣子免费系列教程(14)
  • 【Linux权限】—— 于虚拟殿堂,轻拨密钥启华章
  • YOLOv11-ultralytics-8.3.67部分代码阅读笔记-head.py
  • 基于SpringBoot的假期周边游平台的设计与实现(源码+SQL脚本+LW+部署讲解等)
  • JAVA实战开源项目:企业客户管理系统(Vue+SpringBoot) 附源码
  • C++并发编程指南04