0

0

深入理解Go语言切片与sort.Interface:为何无需指针接收器

花韻仙語

花韻仙語

发布时间:2025-12-04 14:10:43

|

593人浏览过

|

来源于php中文网

原创

深入理解go语言切片与sort.interface:为何无需指针接收器

在Go语言中,切片、映射和通道是特殊的“引用类型”。它们的变量值本身是一个包含指向底层数据指针的头部。这意味着即使方法使用值接收器(而非指针接收器),对这些类型实例的修改(例如`sort.Interface`中`Swap`方法对切片元素的交换)也能直接作用于原始数据,因为复制的只是包含指针的头部,而非底层数据本身。

Go语言中的方法接收器与数据修改

在Go语言中,一个常见的设计模式是:如果方法需要修改接收器(即方法所属的类型实例)的状态,通常需要使用指针接收器(*T)。例如,对于一个结构体struct Person,如果有一个方法ChangeName,它需要修改Person的名字字段,那么这个方法通常会被定义为func (p *Person) ChangeName(newName string)。这是因为Go默认是值传递,如果使用值接收器func (p Person) ChangeName(...),方法内部对p的修改只会作用于p的一个副本,而不会影响原始的Person实例。

然而,在实现sort.Interface接口时,我们经常会看到如下示例代码,其中Swap方法(用于交换切片中的元素,显然是修改操作)却使用了值接收器:

type Person struct {
    Name string
    Age  int
}

type ByAge []Person // ByAge 是 Person 切片的一个别名类型

func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] } // 注意:这里是值接收器
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }

这似乎与我们对Go方法接收器的理解相悖。要理解这种行为,我们需要深入探讨Go语言中切片、映射和通道这三种特殊类型的内部机制。

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

理解Go语言的“引用类型”

Go语言中,切片(slice)、映射(map)和通道(channel)被称为内置的“引用类型”。这里的“引用”并非指像C++引用那样是变量的别名,而是指这些类型的值本身是一个头部结构(header),这个头部结构内部包含了一个指向底层数据的指针。

  • 切片 (Slice):切片头部包含三个字段:一个指向底层数组的指针、切片的长度(len)和切片的容量(cap)。
  • 映射 (Map):映射头部包含一个指向底层哈希表数据结构的指针。
  • 通道 (Channel):通道头部包含一个指向底层队列和相关状态的指针。

这意味着,当你传递一个切片、映射或通道的值时,Go语言会复制这个头部结构。虽然头部结构被复制了,但其中包含的那个指向底层数据的指针所指向的内存地址是相同的

ONLYOFFICE
ONLYOFFICE

用ONLYOFFICE管理你的网络私人办公室

下载

示例:切片值传递的内部机制

为了更好地说明这一点,我们可以通过一个简单的例子来观察切片在值传递时的内存地址变化:

package main

import "fmt"

func dumpFirst(s []int) {
    // 打印切片变量本身的地址(即切片头部的地址)
    fmt.Printf("函数内部 - 切片变量s的地址: %p\n", &s)
    // 打印切片第一个元素的地址(即底层数组数据的地址)
    if len(s) > 0 {
        fmt.Printf("函数内部 - 切片第一个元素的地址: %p\n", &s[0])
    } else {
        fmt.Println("函数内部 - 空切片,无元素地址")
    }
    fmt.Println("--------------------")
}

func main() {
    s1 := []int{1, 2, 3}
    fmt.Printf("main函数 - 切片s1的地址: %p\n", &s1)
    fmt.Printf("main函数 - 切片s1第一个元素的地址: %p\n", &s1[0])
    fmt.Println("--------------------")

    dumpFirst(s1) // 将s1作为参数传递给dumpFirst函数

    s2 := s1 // s2复制s1的值
    fmt.Printf("main函数 - 切片s2的地址: %p\n", &s2)
    fmt.Printf("main函数 - 切片s2第一个元素的地址: %p\n", &s2[0])
    fmt.Println("--------------------")
}

运行上述代码,你可能会得到类似以下的输出(具体地址值会因运行环境而异):

main函数 - 切片s1的地址: 0xc00000e020
main函数 - 切片s1第一个元素的地址: 0xc000014060
--------------------
函数内部 - 切片变量s的地址: 0xc00000e038
函数内部 - 切片第一个元素的地址: 0xc000014060
--------------------
main函数 - 切片s2的地址: 0xc00000e050
main函数 - 切片s2第一个元素的地址: 0xc000014060
--------------------

从输出中我们可以清楚地看到:

  1. main函数中的s1、dumpFirst函数中的s以及main函数中的s2,它们各自切片变量本身的地址(即切片头部结构在内存中的地址)是不同的。这证明了切片头部在传递和赋值时确实发生了复制。
  2. 然而,所有这些切片变量的第一个元素的地址却是相同的(0xc000014060)。这表明,尽管切片头部被复制了,但它们内部的指针都指向了同一块底层数组内存

sort.Interface中Swap方法的工作原理

现在,回到sort.Interface的Swap方法。当ByAge类型(它本质上是一个[]Person)的Swap方法被调用时,即使它使用值接收器func (a ByAge) Swap(i, j int):

  1. a是原始ByAge切片的一个头部副本
  2. 这个头部副本中的指针仍然指向原始ByAge切片所引用的同一块底层Person数组
  3. a[i], a[j] = a[j], a[i]操作实际上是修改了这块共享的底层Person数组中的元素。
  4. 因此,这些修改会直接反映在原始的ByAge切片上,无需指针接收器。

总结与注意事项

  • Go语言的“引用类型”特性:切片、映射和通道的值是包含指向底层数据指针的头部结构。当这些类型的值被复制时,复制的是头部,而内部的指针依然指向同一份底层数据。
  • 方法接收器的选择
    • 对于切片、映射、通道:即使使用值接收器,方法内部对元素或键值对的修改也会影响原始数据。然而,如果需要修改切片本身的头部(例如,改变切片的长度、容量或使其指向一个新的底层数组),则仍然需要使用指针接收器(例如,func (s *[]int) Append(val int))。sort.Interface的Swap方法只修改切片元素,不修改切片头部,因此值接收器足够。
    • 对于其他类型(如结构体、数组、基本类型):如果方法需要修改接收器本身的状态,则必须使用指针接收器,因为这些类型的值是完全复制的,没有共享的底层数据。

理解Go语言中切片、映射和通道的这种特殊行为,对于编写高效且正确的Go代码至关重要,尤其是在处理数据结构和并发编程时。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

318

2023.08.02

sort排序函数用法
sort排序函数用法

sort排序函数的用法:1、对列表进行排序,默认情况下,sort函数按升序排序,因此最终输出的结果是按从小到大的顺序排列的;2、对元组进行排序,默认情况下,sort函数按元素的大小进行排序,因此最终输出的结果是按从小到大的顺序排列的;3、对字典进行排序,由于字典是无序的,因此排序后的结果仍然是原来的字典,使用一个lambda表达式作为key参数的值,用于指定排序的依据。

387

2023.09.04

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

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

197

2025.06.09

golang结构体方法
golang结构体方法

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

189

2025.07.04

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

318

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

538

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

53

2025.08.29

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

197

2025.08.29

Java JVM 原理与性能调优实战
Java JVM 原理与性能调优实战

本专题系统讲解 Java 虚拟机(JVM)的核心工作原理与性能调优方法,包括 JVM 内存结构、对象创建与回收流程、垃圾回收器(Serial、CMS、G1、ZGC)对比分析、常见内存泄漏与性能瓶颈排查,以及 JVM 参数调优与监控工具(jstat、jmap、jvisualvm)的实战使用。通过真实案例,帮助学习者掌握 Java 应用在生产环境中的性能分析与优化能力。

3

2026.01.20

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Go 教程
Go 教程

共32课时 | 4万人学习

Go语言实战之 GraphQL
Go语言实战之 GraphQL

共10课时 | 0.8万人学习

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

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