讲师中心 微信公众号
AI工具推荐 视频效率加速

Go语言结构体初始化:&Struct{}与Struct{}的区别与选择

夜杰大大_2511

夜杰大大_2511

发布时间:2025-09-29 17:12:24

|

347人浏览过

|

来源于php中文网

原创

go语言结构体初始化:&struct{}与struct{}的区别与选择

Go语言中结构体初始化有两种常见方式:Struct{}和&Struct{}。前者创建并返回一个结构体值类型实例,后者则创建结构体值并返回其指针。理解这两种方式的关键在于它们创建的变量类型不同,分别是结构体类型和结构体指针类型,这决定了后续对结构体实例的操作方式,影响内存管理和方法接收者类型。

在Go语言中,结构体(Struct)是组织数据的重要方式。当我们创建一个结构体实例时,有两种主要且在语义上有所区别的初始化语法,它们分别对应于创建结构体的值类型实例和指针类型实例。理解这两种方式的差异对于编写高效、可维护的Go代码至关重要。

两种结构体初始化方式解析

Go语言提供了两种基本的方式来初始化结构体,它们的核心区别在于变量最终持有的是结构体的“值”还是指向该值的“指针”。

方式一:值类型初始化 Struct{}

当使用 Struct{} 语法进行初始化时,Go编译器会创建一个结构体的值副本,并将其赋值给变量。这意味着变量 s 直接包含了结构体的所有字段数据。

示例:

Go语言(Golang)1.26.0
Go语言(Golang)1.26.0

Go语言(Golang)1.26.0版本官方下载,版本号 1.26.0,适合旧项目维护、兼容性测试和指定版本开发环境搭建。

下载

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

package main

import "fmt"

type Rectangle struct {
    Width  int
    Height int
}

func main() {
    // 初始化一个Rectangle值类型实例
    r := Rectangle{Width: 10, Height: 5}
    fmt.Printf("r 的类型: %T, 值: %+v\n", r, r)
    // 输出: r 的类型: main.Rectangle, 值: {Width:10 Height:5}
}

在这种情况下,变量 r 的类型是 main.Rectangle。对 r 的任何修改都只会影响 r 自身的副本,不会影响其他可能存在的 Rectangle 实例。当 r 被作为函数参数传递时,默认会进行值拷贝,函数内部操作的是 r 的一个独立副本。

方式二:指针类型初始化 &Struct{}

当使用 &Struct{} 语法进行初始化时,Go编译器会首先创建一个结构体的值,然后返回这个结构体值的内存地址,即一个指向该结构体的指针。这意味着变量 p 存储的是结构体在内存中的地址,而不是结构体本身。

示例:

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

package main

import (
    "fmt"
    "net/http" // 示例中提到的http.Client
)

func main() {
    // 初始化一个http.Client的指针类型实例
    client := &http.Client{
        // CheckRedirect: redirectPolicyFunc, // 实际使用时可能配置
    }
    fmt.Printf("client 的类型: %T, 值: %+v\n", client, client)
    // 输出: client 的类型: *http.Client, 值: &{} (或包含默认字段)

    // 初始化一个Rectangle的指针类型实例
    p := &Rectangle{Width: 20, Height: 10}
    fmt.Printf("p 的类型: %T, 值: %+v\n", p, p)
    // 输出: p 的类型: *main.Rectangle, 值: &{Width:20 Height:10}
}

在这种情况下,变量 client 的类型是 *http.Client,变量 p 的类型是 *main.Rectangle。它们都是指针类型。通过 client 或 p 访问和修改结构体字段时,实际上是在操作原始结构体在内存中的数据。当 client 或 p 被作为函数参数传递时,传递的是指针的副本(即内存地址的副本),函数内部可以通过这个地址修改原始结构体。

核心区别:变量类型与内存管理

这两种初始化方式最核心的区别在于它们创建的变量类型不同,进而影响了内存管理和数据传递行为:

  1. 变量类型不同:
    • var := Struct{} 会使 var 的类型为 StructType (值类型)。
    • var := &Struct{} 会使 var 的类型为 *StructType (指针类型)。
  2. 内存行为:
    • 值类型实例 (StructType): 每次赋值或作为函数参数传递时,都会创建结构体的完整副本。如果结构体较大,这可能导致显著的内存开销和性能下降。
    • *指针类型实例 (`StructType`):** 赋值或作为函数参数传递时,只拷贝内存地址(一个机器字大小),而不是整个结构体。这对于大型结构体来说,可以显著减少内存开销和提高性能。

何时选择:指导原则

理解了这两种方式的差异后,选择哪种初始化方法取决于具体的应用场景和需求。

何时使用 Struct{} (值类型)

  • 结构体较小: 当结构体包含的字段不多,内存占用较小时,值拷贝的开销可以忽略不计。
  • 需要独立的副本: 当你希望每次操作都是在一个独立的结构体副本上进行,不希望修改原始数据时。例如,函数接收一个结构体值作为参数,修改它不会影响调用者的数据。
  • 作为不可变数据: 如果结构体主要用于存储配置或状态,且不希望其在传递过程中被意外修改,值类型是合适的选择。

何时使用 &Struct{} (指针类型)

  • 结构体较大: 为了避免不必要的内存拷贝,特别是当结构体包含大量字段或大型数据时,使用指针可以优化性能。
  • 需要修改结构体实例的字段: 如果你希望通过变量来修改结构体实例的内部状态,并且这些修改能够反映到原始实例上,那么必须使用指针。
  • 方法定义为指针接收者: 如果结构体的方法被定义为指针接收者 (func (s *Struct) Method()),那么通常需要通过指针来调用这些方法。这是Go语言中修改结构体状态的惯用方式。
  • 资源管理和生命周期: 某些结构体(如 http.Client)可能管理着内部资源(如连接池),这些资源通常需要通过指针来统一管理和维护其生命周期。标准库中很多结构体都是以指针形式返回或使用的,以确保其内部状态的一致性。
  • 接口实现: 当一个方法需要修改结构体的状态,并且该结构体需要实现某个接口时,通常需要使用指针接收者,因此实例化时也倾向于使用指针。

示例代码:行为差异

让我们通过一个具体的例子来演示值类型和指针类型在修改数据时的行为差异。

package main

import "fmt"

type User struct {
    Name string
    Age  int
}

// 值接收者方法:修改的是User的副本
func (u User) SetNameValue(newName string) {
    u.Name = newName
    fmt.Printf("在值接收者方法内: %+v\n", u)
}

// 指针接收者方法:修改的是原始User
func (u *User) SetNamePointer(newName string) {
    u.Name = newName
    fmt.Printf("在指针接收者方法内: %+v\n", u)
}

func main() {
    fmt.Println("--- 值类型初始化 ---")
    userValue := User{Name: "Alice", Age: 30}
    fmt.Printf("初始 userValue: %+v\n", userValue)

    // 调用值接收者方法
    userValue.SetNameValue("Alicia")
    fmt.Printf("调用 SetNameValue 后 userValue: %+v (未改变)\n", userValue)

    // 尝试直接修改字段
    userValue.Name = "Alice_Modified"
    fmt.Printf("直接修改后 userValue: %+v\n", userValue)

    fmt.Println("\n--- 指针类型初始化 ---")
    userPointer := &User{Name: "Bob", Age: 25}
    fmt.Printf("初始 userPointer: %+v\n", userPointer)

    // 调用指针接收者方法
    userPointer.SetNamePointer("Bobby")
    fmt.Printf("调用 SetNamePointer 后 userPointer: %+v (已改变)\n", userPointer)

    // 尝试直接修改字段 (通过指针)
    userPointer.Name = "Bob_Modified"
    fmt.Printf("直接修改后 userPointer: %+v\n", userPointer)
}

输出:

--- 值类型初始化 ---
初始 userValue: {Name:Alice Age:30}
在值接收者方法内: {Name:Alicia Age:30}
调用 SetNameValue 后 userValue: {Name:Alice Age:30} (未改变)
直接修改后 userValue: {Name:Alice_Modified Age:30}

--- 指针类型初始化 ---
初始 userPointer: &{Name:Bob Age:25}
在指针接收者方法内: &{Name:Bobby Age:25}
调用 SetNamePointer 后 userPointer: &{Name:Bobby Age:25} (已改变)
直接修改后 userPointer: &{Name:Bob_Modified Age:25}

从输出可以看出,对于值类型实例 userValue,SetNameValue 方法内部的修改不会影响到 main 函数中的 userValue,因为方法操作的是一个副本。而对于指针类型实例 userPointer,SetNamePointer 方法的修改直接作用于原始结构体,因此 main 函数中的 userPointer 也随之改变。

注意事项与总结

  • Go的垃圾回收: 无论是值类型还是指针类型,Go的垃圾回收机制都会自动管理内存。当一个结构体不再被引用时,它所占用的内存会被自动回收,无需手动释放。
  • 并发安全: 当多个goroutine同时访问和修改同一个结构体指针时,可能会发生数据竞态(data race)。在这种情况下,需要使用互斥锁(sync.Mutex)或其他并发原语来保护共享数据。
  • 方法接收者: 结构体方法的接收者可以是值类型或指针类型。如果方法需要修改结构体的状态,应使用指针接收者。如果方法只需要读取结构体数据,值接收者或指针接收者都可以,但通常会根据结构体大小和性能考量来选择。
  • 一致性: 在一个项目中,对于特定的结构体,最好保持其初始化和使用方式的一致性。例如,如果 http.Client 总是以 *http.Client 的形式使用,那么在所有地方都应该遵循这个模式。

总而言之,选择 Struct{} 还是 &Struct{} 取决于你的具体需求:是需要一个独立的副本,还是需要一个能修改原始数据的引用。理解它们背后的类型差异和内存语义,是掌握Go语言结构体使用的关键。

热门AI工具

更多
DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

Lovart
Lovart Hot

一款面向视觉设计创作的AI设计平台,可通过智能体和画布工作流辅助制作海报、Logo、网页、PPT及其他视觉内容。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

音述AI
音述AI Hot

一款AI音频处理工具,主要用于音述AI是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区,适合需要提升相关任务效率的用户。

蛙蛙写作

一款AI论文写作工具,主要用于超级AI智能写作助手,适合需要提升相关任务效率的用户。

立刻MV
立刻MV Hot

立刻MV是一款AI文本写作工具,AI 音乐视频(MV)创作工具。

UP简历
UP简历 Hot

一款AI办公效率工具,主要用于基于AI技术的免费在线简历制作工具,适合需要提升相关任务效率的用户。

火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

相关专题

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

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

4714

2025.06.09

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

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

4871

2025.07.04

C++ 智能指针与现代内存管理
C++ 智能指针与现代内存管理

深入讲解 C++ 现代内存管理的核心工具——智能指针,涵盖 unique_ptr 独占所有权语义、shared_ptr 引用计数机制与循环引用问题、weak_ptr 弱引用的应用场景、make_unique/make_shared 工厂函数的性能优势、自定义删除器的编写、RAII 资源管理思想的实践,以及从裸指针迁移到智能指针的重构策略,帮助开发者编写安全无泄漏的现代 C++ 代码。

359

2026.04.23

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

3148

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

4909

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

3789

2025.12.29

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

446

2026.01.19

Go中Type关键字的用法
Go中Type关键字的用法

Go中Type关键字的用法有定义新的类型别名或者创建新的结构体类型。本专题为大家提供Go相关的文章、下载、课程内容,供大家免费下载体验。

2889

2023.09.06

OpenJDK JVM参数怎么调优
OpenJDK JVM参数怎么调优

Java应用运行缓慢,如何调整JVM参数?本专题介绍OpenJDK JVM性能调优的基本流程,涵盖性能指标确认、运行数据采集、内存配置和垃圾回收分析,帮助你先定位瓶颈,再通过对照测试验证效果,避免直接套用参数而影响应用稳定性。

80

2026.10.16

热门下载

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

精品课程

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn