0

0

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

P粉602998670

P粉602998670

发布时间:2025-07-13 10:09:02

|

298人浏览过

|

来源于php中文网

原创

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),它们是临时的计算结果,同样不具备稳定的内存地址。

    Ideogram
    Ideogram

    Ideogram是一个全新的文本转图像AI绘画生成平台,擅长于生成带有文本的图像,如LOGO上的字母、数字等。

    下载
    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如何定义变量
golang如何定义变量

golang定义变量的方法:1、声明变量并赋予初始值“var age int =值”;2、声明变量但不赋初始值“var age int”;3、使用短变量声明“age :=值”等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

178

2024.02.23

golang有哪些数据转换方法
golang有哪些数据转换方法

golang数据转换方法:1、类型转换操作符;2、类型断言;3、字符串和数字之间的转换;4、JSON序列化和反序列化;5、使用标准库进行数据转换;6、使用第三方库进行数据转换;7、自定义数据转换函数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

226

2024.02.23

golang常用库有哪些
golang常用库有哪些

golang常用库有:1、标准库;2、字符串处理库;3、网络库;4、加密库;5、压缩库;6、xml和json解析库;7、日期和时间库;8、数据库操作库;9、文件操作库;10、图像处理库。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

337

2024.02.23

golang和python的区别是什么
golang和python的区别是什么

golang和python的区别是:1、golang是一种编译型语言,而python是一种解释型语言;2、golang天生支持并发编程,而python对并发与并行的支持相对较弱等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

208

2024.03.05

golang是免费的吗
golang是免费的吗

golang是免费的。golang是google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的开源编程语言,采用bsd开源协议。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

388

2024.05.21

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

194

2025.06.09

golang相关判断方法
golang相关判断方法

本专题整合了golang相关判断方法,想了解更详细的相关内容,请阅读下面的文章。

189

2025.06.10

golang数组使用方法
golang数组使用方法

本专题整合了golang数组用法,想了解更多的相关内容,请阅读专题下面的文章。

192

2025.06.17

c++主流开发框架汇总
c++主流开发框架汇总

本专题整合了c++开发框架推荐,阅读专题下面的文章了解更多详细内容。

80

2026.01.09

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
golang socket 编程
golang socket 编程

共2课时 | 0.1万人学习

nginx浅谈
nginx浅谈

共15课时 | 0.8万人学习

golang和swoole核心底层分析
golang和swoole核心底层分析

共3课时 | 0.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

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