【Go语言基础【16】】结构体:方法接受者、组合(代码复用)、内存对齐、json序列化

零、概述

特性说明
字段可见性首字母大小写控制包访问权限
方法接收者值接收者(操作副本) vs 指针接收者(操作原始实例)
组合嵌套命名嵌套(显式字段) vs 匿名嵌入(字段提升)
内存对齐字段顺序影响内存占用,编译器自动处理对齐
JSON序列化通过json标签自定义字段名和序列化规则

注意:

  1. 字段可见性控制
    • 公共结构体(首字母大写)的字段若需对外暴露,设为公共字段(首字母大写)。
    • 私有字段(首字母小写)通过公共方法访问,实现封装。
  2. 组合优于继承
    利用匿名嵌入实现代码复用,避免继承带来的复杂性。
type Animal struct {
    Name string
}
type Dog struct {
    Animal // 嵌入Animal结构体,继承Name字段
    Breed  string
}
  1. 指针接收者的一致性
    若结构体方法使用指针接收者,建议所有方法均使用指针接收者,避免值类型和指针类型实例的行为差异。

 

一、结构体基础:复合数据类型的基础

Go语言中的结构体(struct)是一种用户自定义复合类型,用于将不同类型的数据组合成一个逻辑整体。它类似其他语言中的“类”,但更灵活,支持面向对象编程中的组合、方法绑定等特性,且不依赖继承机制。

 

1、定义语法

type 结构体名 struct {
    字段名1 字段类型1
    字段名2 字段类型2
    ...
}
  • 字段命名规则
    • 首字母大写:公共字段(可跨包访问)。
    • 首字母小写:私有字段(仅限包内访问)。
  • 字段类型:支持内置类型(如intstring)、指针、切片、接口、其他结构体等。

示例:定义人物结构体

type Person struct {
    Name      string   // 姓名(公共字段)
    age       int      // 年龄(私有字段,包外不可见)
    Hobbies   []string // 爱好(切片类型)
}

 

2、实例化与初始化

声明实例

var p Person // 声明实例,字段为默认零值(Name="",age=0,Hobbies=nil)

初始化方式
(1)字段名显式赋值

p := Person{
    Name:    "Alice",
    age:     30,       // 包内可访问私有字段
    Hobbies: []string{"reading", "music"},
}

(2)按顺序赋值(需提供全部字段)

p := Person{"Bob", 25, []string{"sports"}} // 与字段声明顺序一致

(3)创建指针实例

p := &Person{Name: "Charlie"} // 等价于 new(Person) 并赋值

(4)匿名结构体(临时定义,无需命名)

// 直接声明并初始化一个匿名结构体实例
data := struct {
    ID   int
    Name string
}{
    ID:   1,
    Name: "Temp",
}

 

3、字段访问与修改

访问字段

  • 值类型实例:通过.操作符访问。
    p := Person{Name: "Alice"}
    fmt.Println(p.Name) // 输出:Alice
    
  • 指针类型实例:自动解引用,直接通过.访问(无需*)。
    p := &Person{Name: "Bob"}
    fmt.Println(p.Name) // 等价于 (*p).Name,输出:Bob
    

修改字段

p := Person{Name: "Alice"}
p.Name = "Bob" // 修改公共字段
// p.age = 30    // 私有字段不可直接修改(包外报错)

 

二、结构体方法:接收者参数 给结构体绑定方法

在 Go 语言中,接收者参数是用于将函数与结构体绑定的特殊参数,使得该函数成为类型的方法(Method)。
接收者参数是 Go 实现面向对象编程(OOP)中 “方法绑定” 的核心机制。

type 结构体名 struct { ... }

// 值接收者方法(操作副本,不影响原始实例)
func (r 结构体名) 方法名(参数列表) 返回值 { ... }

// 指针接收者方法(操作原始实例,可修改字段)
func (r *结构体名) 方法名(参数列表) 返回值 { ... }

示例:值接收者(!!!实例副本传递) vs 指针接收者

package main

type Counter struct {
	Value int
}

// 值接收者:修改的是副本
func (c Counter) IncrementByValue(n int) {
	c.Value += n // 原始实例不受影响
}

// 指针接收者:修改原始实例
func (c *Counter) IncrementByPointer(n int) {
	c.Value += n // 直接修改原始值
}

func main() {
	c := Counter{Value: 10}
	c.IncrementByValue(5) // 副本修改,c.Value仍为10
	println(c.Value)
	c.IncrementByPointer(5) // 原始修改,c.Value变为15
	println(c.Value)
}

 
选择接收者类型的原则:

场景值接收者指针接收者
结构体体积小推荐(如包含几个基础类型字段)可选,但无必要
结构体体积大不推荐(拷贝开销大)推荐(避免内存拷贝)
需要修改原始数据不可行必须使用
接口实现一致性若使用指针接收者方法,实例需为指针值类型和指针类型均实现接口

 

三、结构体组合:实现代码复用

Go语言通过组合实现代码复用,而非传统的继承。结构体可嵌套其他结构体,分为命名嵌套匿名嵌套(嵌入)。

1. 命名嵌套(显式字段名)

package main

import "fmt"

type Address struct {
	City  string
	State string
}

type Person struct {
	Name string
	Age  int
	Home Address // 命名嵌套:字段名为Home,类型为Address
}

func main() {
	// 访问嵌套字段
	p := Person{
		Name: "Alice",
		Home: Address{City: "Shanghai", State: "CN"},
	}
	fmt.Println(p.Home.City) // 输出:Shanghai
}

 

2. 匿名嵌套:直接声明为字段(无字段名)

将结构体类型直接声明为字段(无字段名),嵌套的字段会被提升为外层结构体的字段。

type Address struct {
    City  string
    State string
}

type Person struct {
    Name string
    Age  int
    Address // 匿名嵌入:Address的字段提升为Person的字段
}

// 直接访问嵌入字段
p := Person{
    Name:    "Bob",
    City:    "Beijing", // 等价于 p.Address.City
    State:   "CN",      // 等价于 p.Address.State
}

 

3. 冲突处理

若嵌入结构体与外层结构体有同名字段,外层字段会覆盖内层字段(就近原则)。

type A struct {
    X int
}
type B struct {
    A       // 嵌入A
    X string // 与A.X冲突,B.X覆盖A.X
}
func main() {
    b := B{A: A{X: 100}, X: "hello"}
    fmt.Println(b.X)   // 输出:hello(外层字段)
    fmt.Println(b.A.X) // 输出:100(通过嵌入结构体访问内层字段)
}

 

四、结构体的内存对齐

Go编译器会自动对结构体字段进行内存对齐,以提高内存访问效率。对齐规则基于字段类型的大小和对齐标签(unsafe.Alignof)。

示例:字段顺序影响内存占用

import "unsafe"

type A struct {
    a bool   // 1字节,对齐边界1,地址任意
    b int32  // 4字节,对齐边界4,地址是4的倍数
    c string // 8字节,对齐边界8,地址是8的倍数
}

type B struct {
    b int32  // 4字节,对齐边界4
    c string // 8字节,对齐边界8
    a bool   // 1字节,对齐边界1
}

func main() {
    fmt.Println(unsafe.Sizeof(A{})) // 16字节(1+3填充+4+8)
    fmt.Println(unsafe.Sizeof(B{})) // 24字节(4+8+8+4填充)
}

优化建议:

  • 将相同或相近大小的字段放在一起,减少填充字节。
  • 按需使用unsafe包(谨慎使用,破坏类型安全)。

 

五、结构体的拷贝与比较

1. 值拷贝

结构体是值类型,赋值或传参会完整拷贝所有字段(包括嵌套结构体)。

p1 := Person{Name: "Alice"}
p2 := p1           // 拷贝p1的副本
p2.Name = "Bob"    // 不影响p1的Name

2. 深度拷贝

若结构体包含指针、切片等引用类型字段,需手动拷贝其指向的数据(值拷贝仅复制指针地址)。

type Data struct {
    List []int // 切片类型,值拷贝后新旧实例共享底层数组
}

func deepCopy(d *Data) Data {
    newList := make([]int, len(d.List))
    copy(newList, d.List) // 手动拷贝切片数据
    return Data{List: newList}
}

 

六、结构体与JSON序列化

Go结构体可通过encoding/json包轻松实现JSON序列化与反序列化,需注意字段可见性和标签(json tag)。

示例:

package main

import (
	"encoding/json"
	"fmt"
)

type User struct {
	ID   int    `json:"user_id"` // 标签:指定JSON字段名
	Name string `json:"user_name"`
	Age  int    `json:"age,omitempty"` // omitempty:字段为零值时不输出
}

func main() {
	// 序列化(结构体→JSON)
	u := User{ID: 1, Name: "Bob"}
	jsonData, _ := json.Marshal(u)
	fmt.Println(string(jsonData)) // 输出:{"user_id":1,"user_name":"Bob"}

	// 反序列化(JSON→结构体)
	var u2 User
	json.Unmarshal(jsonData, &u2)
	fmt.Println(u2.Name) // 输出:Bob
}

 

七、结构体对象

1. Car{}:实例化对象

  • 本质:它是Car结构体的一个实例,属于值类型。
  • 示例
    type Car struct {
        Brand string
        Price int
    }
    
    car := Car{Brand: "Tesla", Price: 80000}
    
  • 特点
    • 直接在内存里创建一个Car对象。
    • car的字段进行修改时,不会影响到其他对象。

2. &Car{}:实例化对象的指针

  • 本质:这是指向Car结构体实例的指针,也就是引用类型。
  • 示例
    carPtr := &Car{Brand: "Tesla", Price: 80000}
    
  • 特点
    • 先创建一个Car对象,接着返回该对象的内存地址。
    • 通过carPtr对字段进行修改,会影响到原对象。
    • 与手动创建对象再取地址的效果是一样的:
      car := Car{Brand: "Tesla", Price: 80000}
      carPtr := &car
      

3. *Car:指针类型

本质:它仅仅是一个指针类型的声明,并非具体的对象。

var carPtr *Car // 声明一个指向 Car 的指针,此时值为 nil

特点

  • 可用于声明函数参数、返回值或者结构体字段等。
  • 在使用之前,必须先赋值,否则会出现空指针异常。
  • 赋值方式有以下几种:
    // 方式一:通过 &Car{} 赋值
    carPtr = &Car{Brand: "Tesla", Price: 80000}
    
    // 方式二:先创建对象,再取地址
    car := Car{}
    carPtr = &car
    
    // 方式三:使用 new 函数
    carPtr = new(Car) // 等同于 &Car{}
    
资源下载链接为: https://pan.quark.cn/s/ddc62c5d4a5d Windows Mobile 是微软在 0200 年代至 2010 年代初推出的移动操作系统,曾广泛应用于智能手机和平板电脑。开发者可以借助各种库和框架为其开发功能丰富的应用,其中 “32feet.NET” 是一个开源的 .NET 库,专为 .NET Framework 和 .NET Compact Framework 提供蓝牙开发支持。它包含多个命名空间,例如 InTheHand.Devices.Bluetooth、InTheHand.Net.Personal 和 InTheHand.Phone.Bluetooth,用于实现蓝牙设备交互功能。 InTheHand.Devices.Bluetooth 命名空间用于执行基础蓝牙操作,比如扫描附近设备、建立连接以及发现蓝牙服务等。InTheHand.Net.Personal 提供了更高级的功能,例如创建个人区域网络(PAN)、文件传输和串行端口模拟,便于开发者开发跨设备的数据共享应用。而 InTheHand.Phone.Bluetooth 主要针对 Windows Phone 平台,支持蓝牙配对、消息收发和蓝牙耳机控制等功能,不过由于 Windows Mobile 已停止更新,该命名空间更多适用于旧设备或项目。 压缩包中的文件列表看似是维基页面的渲染文件,可能是关于 32feet.NET 的使用教程、API 参考或示例代码。文件名如 13632.html、563803.html 等可能是页面 ID,涵盖蓝牙设备搜索、连接和数据传输等不同主题。 使用 32feet.NET 进行蓝牙开发时,开发者需要注意以下几点:首先,确保开发环境已安装 .NET Framework 或 .NET Compact Framework,以及 32feet.NET
资源下载链接为: https://pan.quark.cn/s/d8a2bf0af1ac Mask R-CNN 是一种在实例分割任务中表现优异的深度学习模型,它融合了 Faster R-CNN 的目标检测功能和 CNN 的像素级分类能力,能够实现图像中每个目标的定位、识别与分割。本指南将指导你如何使用 Mask R-CNN 训练自定义数据集。 你需要准备包含图像(JPEG 或 PNG 格式)和标注文件(XML 或 JSON 格式)的数据集,标注文件需包含物体类别、坐标和掩模信息。数据集应按照 COCO 标准组织,分为训练集、验证集和可选的测试集。可以使用工具如 COCO API 或 labelme 将原始数据转换为 COCO 格式,并确保图像文件名与标注文件名一致且在同一目录下。通常按 8:2 或 9:1 的比例划分训练集和验证集。 从提供的压缩包中安装所需库。运行 pip install -r requirements.txt 安装依赖,包括 TensorFlow、Keras、Cython、COCO API 等。 修改 train_test.py 和 test_model.py 中的路径,使其指向你的数据集目录,确保 ROOT_DIR 指向数据集根目录,ANNOTATION_DIR 指向标注文件所在目录。在 config.py 中根据硬件资源和训练目标调整学习率、批大小、迭代次数等参数。 运行 train_test.py 开始训练。训练时会加载预训练权重并进行微调,期间会定期保存模型,便于评估和恢复。 使用 test_model.py 或 test.py 对模型进行验证和测试。这些脚本会加载保存的模型权重,将其应用于新图像并生成预测结果。 预测结果为二进制掩模,需进一步处理为可读图像。可借助 COCO API 或自定义脚本将掩模合并到原始图像上,生成可视化结果。 若模型性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

roman_日积跬步-终至千里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值