0

0

Go语言结构体:何时使用值类型,何时使用指针类型?

DDD

DDD

发布时间:2025-09-24 13:16:01

|

176人浏览过

|

来源于php中文网

原创

Go语言结构体:何时使用值类型,何时使用指针类型?

在Go语言中,选择使用结构体值类型(Struct)还是结构体指针类型(*Struct)是常见的决策点。核心考量在于结构体的大小、以及是否需要实现数据的共享与修改。当结构体较大时,使用指针可以避免不必要的内存复制,提升性能;而当需要多个地方引用并修改同一份数据时,指针是必需的。对于小型结构体或需要独立副本的场景,直接使用值类型通常更为简洁和安全。

理解值语义与指针语义

go语言中,变量的赋值和函数参数的传递默认是按值进行的。这意味着当你将一个结构体变量赋值给另一个变量,或者将其作为参数传递给函数时,实际上是创建了一个原始结构体的完整副本。对副本的任何修改都不会影响原始结构体。这就是所谓的“值语义”。

而指针则提供了一种“引用语义”。当你使用结构体指针时,变量存储的不是结构体本身,而是结构体在内存中的地址。通过这个地址,你可以访问并修改原始结构体。因此,多个指针可以指向同一个结构体实例,对其中任何一个指针解引用并修改,都会影响到所有指向该实例的指针。

为了更好地理解,可以将其类比为Go语言中int类型与*int类型的使用。大多数情况下,我们直接使用int,因为我们希望得到一个独立的数值副本。但有时,我们需要传递*int,以便接收方能够修改我们原始的int变量。结构体的选择原理与此类似。

何时使用结构体指针(*Struct)

使用结构体指针主要基于以下两个核心场景:

  1. 处理大型结构体以优化性能和内存 当结构体包含大量字段或字段本身占用大量内存时(例如,包含大型数组、切片或映射),每次按值传递或赋值都会导致整个结构体的深拷贝。这种复制操作会消耗显著的CPU时间和内存资源,尤其是在循环或高频调用的函数中。 通过传递结构体指针,实际上只传递了一个内存地址(通常是8字节),而不是整个结构体的数据。这大大减少了内存复制的开销,从而提升了程序的性能。

  2. 实现共享与修改 如果你希望程序的不同部分能够共享同一个结构体实例,并且对该实例的修改能够相互可见,那么必须使用结构体指针。例如,在一个并发系统中,多个Goroutine可能需要访问并更新同一个配置对象或状态数据。此时,将配置对象作为指针传递,可以确保所有Goroutine操作的是同一个实例。

    package main
    
    import "fmt"
    
    type Counter struct {
        Value int
    }
    
    // IncrementByPointer 接收一个Counter指针,可以直接修改原始Counter
    func IncrementByPointer(c *Counter) {
        c.Value++
    }
    
    // IncrementByValue 接收一个Counter值,修改的是副本
    func IncrementByValue(c Counter) {
        c.Value++
    }
    
    func main() {
        // 使用指针实现共享和修改
        myCounter := &Counter{Value: 0}
        fmt.Printf("原始计数器 (指针): %d\n", myCounter.Value) // 0
        IncrementByPointer(myCounter)
        fmt.Printf("递增后计数器 (指针): %d\n", myCounter.Value) // 1
    
        // 使用值类型,修改的是副本
        anotherCounter := Counter{Value: 0}
        fmt.Printf("原始计数器 (值): %d\n", anotherCounter.Value) // 0
        IncrementByValue(anotherCounter)
        fmt.Printf("递增后计数器 (值): %d\n", anotherCounter.Value) // 0 (未改变)
    }

何时使用结构体值(Struct)

在以下情况下,直接使用结构体值类型通常是更好的选择:

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

  1. 小型结构体的默认选择 对于字段数量少、内存占用小的结构体(例如,Go Tour中的Vertex结构体,只包含两个float64字段),按值传递或赋值的开销可以忽略不计。在这种情况下,使用值类型可以使代码更简洁,避免不必要的指针解引用操作。

  2. 需要独立副本与值语义时 当你希望每个变量都拥有其自身的独立数据副本,并且对一个副本的修改不影响其他副本时,应使用结构体值类型。这通常用于表示不可变数据或在函数内部操作时不需要影响外部状态的场景。

    例如,标准库中的time.Time结构体就是典型的按值使用的例子。当你获取一个time.Time实例并对其进行操作(如Add、Sub)时,这些方法通常会返回一个新的time.Time实例,而不是修改原始实例。

    Python开发网站指南 WORD版
    Python开发网站指南 WORD版

    本文档主要讲述的是Python开发网站指南;HTML是网络的通用语言,一种简单、通用的全置标记语言。它允许网页制作人建立文本与图片相结合的复杂页面,这些页面可以被网上任何其他人浏览到,无论使用的是什么类型的电脑或浏览器 Python和其他程序语言一样,有自身的一套流程控制语句,而且这些语句的语法和其它程序语言类似,都有for, if ,while 类的关键字来表达程序流程。希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看

    下载
    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        t1 := time.Now()
        t2 := t1.Add(time.Hour) // Add方法返回一个新的Time实例,不修改t1
    
        fmt.Printf("原始时间 t1: %s\n", t1)
        fmt.Printf("增加一小时后的时间 t2: %s\n", t2)
        fmt.Printf("t1 是否被修改? %v\n", t1 == time.Now()) // 验证t1未被修改
    }

Go Tour示例解析与最佳实践

在Go Tour的某些示例中,可能会看到像Vertex这样的小型结构体被用作指针。例如,如果Scaled函数定义为接收一个指针:

type Vertex struct {
    X, Y float64
}

// ScaledByPointer 接收Vertex指针,直接修改原始Vertex
func (v *Vertex) ScaledByPointer(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

这种做法是有效的,它会直接修改调用者传入的Vertex实例。然而,对于像Vertex这样的小型结构体,如果预期行为是返回一个新值而不是修改原值,那么使用值接收者并返回一个新的结构体可能更符合直觉和Go语言的惯例,尤其是在需要类似数学运算中“新结果”的场景:

type Vertex struct {
    X, Y float64
}

// ScaledByValue 接收Vertex值,返回一个新的Vertex实例
func (v Vertex) ScaledByValue(f float64) Vertex {
    v.X = v.X * f // 这里的v是原始Vertex的副本
    v.Y = v.Y * f
    return v // 返回修改后的副本
}

func main() {
    v1 := Vertex{3, 4}
    fmt.Printf("原始顶点 v1: %+v\n", v1) // {X:3 Y:4}

    // 使用指针方法,修改v1
    v1.ScaledByPointer(5)
    fmt.Printf("指针方法修改后 v1: %+v\n", v1) // {X:15 Y:20}

    // 重新初始化v1
    v1 = Vertex{3, 4}
    // 使用值方法,返回新顶点,v1不受影响
    v2 := v1.ScaledByValue(5)
    fmt.Printf("值方法返回新顶点 v2: %+v\n", v2) // {X:15 Y:20}
    fmt.Printf("值方法调用后 v1: %+v\n", v1) // {X:3 Y:4} (未改变)
}

在上述ScaledByValue的例子中,v2 := v1.ScaledByValue(5)的行为类似于var f2 float32 = f1 * 5,它创建了一个新的结果,而不会修改原始值。这种模式对于小型、可复制的结构体非常常见,并且通常更易于理解和维护,因为它避免了副作用。

总结与决策考量

选择使用结构体值类型还是指针类型,并没有绝对的规则,更多是基于具体的使用场景和对程序行为的预期。以下是一些关键的决策考量:

  • 结构体大小:结构体越大,使用指针的性能优势越明显。对于小型结构体,值类型通常足够。
  • 共享与修改:如果需要多个部分共享同一个实例并对其进行修改,必须使用指针。如果每个变量都应该拥有独立的数据副本,则使用值类型。
  • 方法接收者
    • 值接收者 (func (v Struct) Method()):方法操作的是结构体的副本。如果方法需要修改结构体的状态,这些修改只对副本有效,不会影响原始结构体。通常用于查询操作或返回新实例的转换操作。
    • *指针接收者 (`func (v Struct) Method()`)**:方法操作的是原始结构体。如果方法需要修改结构体的状态,这些修改将直接影响原始结构体。通常用于改变对象状态的操作。
  • 代码清晰度与意图:有时,即使结构体很小,使用指针也可能更明确地表达“我正在操作一个特定的对象实例”的意图,例如在构建链表或树形结构时。反之,如果意图是“我正在计算一个新值”,那么值类型可能更清晰。

在实际开发中,Go语言的标准库提供了很多优秀范例。例如,bytes.Buffer通常通过指针使用,因为它是一个可变且可能很大的数据结构;而time.Time则通常通过值使用,因为它代表一个不可变的时间点。理解这些原则并结合实际需求,将有助于你做出正确的选择。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

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

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

240

2025.06.09

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

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

192

2025.07.04

string转int
string转int

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

483

2023.08.02

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

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

544

2024.08.29

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

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

113

2025.08.29

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

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

200

2025.08.29

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

539

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

21

2025.12.22

C++ 设计模式与软件架构
C++ 设计模式与软件架构

本专题深入讲解 C++ 中的常见设计模式与架构优化,包括单例模式、工厂模式、观察者模式、策略模式、命令模式等,结合实际案例展示如何在 C++ 项目中应用这些模式提升代码可维护性与扩展性。通过案例分析,帮助开发者掌握 如何运用设计模式构建高质量的软件架构,提升系统的灵活性与可扩展性。

14

2026.01.30

热门下载

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

精品课程

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

共32课时 | 4.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号