首页 > 后端开发 > Golang > 正文

Golang中哪些类型不支持取地址 解释不可寻址值的概念

P粉602998670
发布: 2025-07-13 10:09:02
原创
231人浏览过

go语言中,并非所有值都支持取地址,不可寻址的值主要包括:1.字面量和常量,它们不占用运行时内存地址;2.函数调用结果,因其为临时值;3.map元素,因扩容可能导致地址失效;4.字符串的字节或字符,因字符串不可变;5.某些表达式的中间结果,如算术运算结果;设计上限制不可寻址是为了保障数据安全、并发安全及编译优化;应对方式包括将不可寻址值赋给变量后再取地址,或在map中存储指针类型以实现修改需求。

Golang中哪些类型不支持取地址 解释不可寻址值的概念

Golang中,并非所有类型都“不支持取地址”,更准确的说法是,某些“值”或“表达式的结果”是不可寻址的。这通常发生在它们不是一个可修改的变量,或者它们的内存地址不稳定时。你可以理解为,你只能获取一个稳定、有归属的“家”的地址,而那些临时的、瞬时的、或者根本就没有固定“家”的东西,自然就无法被“寻址”了。

Golang中哪些类型不支持取地址 解释不可寻址值的概念

解决方案

在Go语言里,当你尝试使用 & 运算符去获取一个值的内存地址时,如果这个值是“不可寻址的”(non-addressable),编译器就会报错。那么,哪些情况会产生不可寻址的值呢?

  1. 字面量(Literals)和常量(Constants): 无论是数字、字符串、布尔值,还是 nil,它们都是字面量。常量在编译时通常会被内联,不占用独立的运行时内存地址。

    立即学习go语言免费学习笔记(深入)”;

    Golang中哪些类型不支持取地址 解释不可寻址值的概念
    // 无效:字面量不可寻址
    // p := &10
    // s := &"hello"
    // b := &true
    
    const MyConst = 100
    // 无效:常量不可寻址
    // pConst := &MyConst
    登录后复制

    这很好理解,你不能改变数字 10 本身代表的意义,它只是一个值。

  2. 函数调用结果(Function Call Results): 当一个函数返回一个值时(而不是一个指针),这个返回值是一个临时拷贝,它没有一个稳定的内存地址。

    Golang中哪些类型不支持取地址 解释不可寻址值的概念
    func getValue() int {
        return 42
    }
    // 无效:函数返回值是临时值
    // p := &getValue()
    登录后复制

    这个 42 就像是快递员暂时放在你手里的包裹,你不能直接给这个包裹贴个永久的地址标签,你得先把它放进你家里(一个变量),才能给你的家贴地址。

  3. Map的元素(Map Elements): 这是Go语言设计中一个重要的安全考量。当你通过 m[key] 访问一个map元素时,你得到的是该元素的一个副本。更重要的是,Go的map在内部实现时,可能会因为扩容等操作而移动其存储的元素。如果允许直接取地址,那么这个地址可能会在map内部重新分配时变得无效,导致悬空指针(dangling pointer)问题,这会非常危险,尤其是在并发场景下。

    m := make(map[string]int)
    m["age"] = 30
    // 无效:map元素不可寻址
    // pAge := &m["age"]
    登录后复制

    如果你需要修改map中某个复杂类型的值的内部字段,通常的做法是先取出值,修改后,再重新赋值回去,或者直接在map中存储指针。

  4. 字符串的字节或字符(Bytes or Characters of a String): 字符串在Go中是不可变的(immutable)。这意味着你不能通过指针去修改字符串的某个字节。当你访问 s[i] 时,你得到的是一个字节的副本。

    s := "hello"
    // 无效:字符串元素不可寻址
    // pByte := &s[0]
    登录后复制

    这和字符串的不可变性是紧密相关的,如果你能拿到某个字符的地址并修改它,那就违背了字符串不可变的原则。

  5. 某些表达式的中间结果: 比如算术运算的结果 (a + b),它们是临时的计算结果,同样不具备稳定的内存地址。

    a, b := 1, 2
    // 无效:表达式结果不可寻址
    // pSum := &(a + b)
    登录后复制

为什么Go语言要限制某些值不可寻址?——深入理解设计哲学

我觉得Go语言在“可寻址性”上的设计,是其“简单”和“安全”哲学的一个缩影。它并非随意设限,而是出于多方面的考量:

首先是数据完整性和安全性。想象一下,如果可以获取并修改一个字面量 10 的地址,那么 *p = 20 之后,所有引用 10 的地方是不是都变成了 20?这显然是荒谬且不可控的。对于常量,它们在编译阶段就已确定,甚至可能直接被编译器内联到使用它们的地方,根本没有运行时地址的概念。限制这些值的寻址,保证了它们的不变性,从而避免了许多潜在的逻辑错误和难以追踪的bug。

其次是并发安全,特别是针对map。Go语言的map不是并发安全的,如果你在多个goroutine中同时读写一个map,需要外部加锁。而如果 m[key] 这样的表达式可以直接取地址,那么程序员可能会误以为可以通过这个指针安全地修改map内部的值。然而,map在扩容或重新哈希时,其内部元素的内存位置可能会发生变化,导致之前获取的指针变成“野指针”或“悬空指针”。Go通过强制要求你不能直接取map元素的地址,间接推动你采用更安全的并发模式(例如,先取出值,修改后再存回,或者直接在map中存储指向值的指针)。这是一种“防患于未然”的设计,它在编译期就阻止了潜在的运行时陷阱。

再者,这与编译器优化也有关系。那些临时值、表达式结果,如果允许取地址,编译器就不得不为它们分配实际的内存空间,这可能会限制一些优化手段,比如寄存器分配、值传递等。通过限制,编译器可以更自由地处理这些瞬时值,提高程序的运行效率。从我的经验来看,Go的编译器在逃逸分析方面做得很好,它会尽量将变量分配在栈上,避免不必要的堆分配。而不可寻址值的概念,正是这种优化策略的一部分。

最后,它也带来了一种清晰的编程模型。当你看到一个值不能取地址时,你就知道它不是一个“变量”或者“可修改的内存位置”,它更多的是一个“数据快照”或者“临时结果”。这种清晰的区分有助于开发者更好地理解数据的生命周期和可变性,减少概念上的混淆。

如何处理不可寻址值的常见场景及实践?——实用技巧与最佳实践

面对不可寻址值的限制,Go语言提供了非常直接且符合逻辑的处理方式,通常只需要稍作变通。

最常见也是最直接的方法,就是将其赋值给一个可寻址的变量。如果你确实需要一个指针指向某个值,那么这个值必须先“安家落户”在一个变量里。

// 场景一:需要一个指向字面量或常量值的指针
// 无效:p := &10
num := 10 // 将字面量赋值给一个变量
p := &num  // 现在可以取地址了

const MaxValue = 100
// 无效:ptr := &MaxValue
val := MaxValue // 将常量赋值给一个变量
ptr := &val     // 现在可以取地址了
登录后复制

这种模式非常普遍,尤其是在需要将基本类型值传入接受指针参数的函数时(例如,某些库函数可能需要 *int 而不是 int)。

处理map元素: 如果你需要在map中存储复杂类型(如结构体),并且希望能够通过指针来修改这些结构体的内部字段,那么最佳实践是在map中存储指向结构体的指针,而不是结构体本身。

type User struct {
    ID   int
    Name string
}

users := make(map[int]*User) // map的值类型是 *User

// 存储一个User的指针
user1 := &User{ID: 1, Name: "Alice"}
users[1] = user1

// 现在可以直接通过map访问并修改User的字段,因为map中存储的是指针
users[1].Name = "Alicia" // 有效!
fmt.Println(users[1].Name) // 输出:Alicia

// 如果map存储的是值类型,你必须取出,修改,再放回
// usersValue := make(map[int]User)
// usersValue[2] = User{ID: 2, Name: "Bob"}
// tempUser := usersValue[2] // 取出副本
// tempUser.Name = "Robert"  // 修改副本
// usersValue[2] = tempUser  // 重新赋值回map
登录后复制

这种模式清晰地表达了你的意图:你希望能够直接操作map中存储的“对象”,而不是其副本。

**结构体字

以上就是Golang中哪些类型不支持取地址 解释不可寻址值的概念的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号