结构体(struct)是Go语言中最核心的自定义复合类型,也是Go实现面向对象编程思想的基础------Go语言没有类(class)、继承、多态的概念,而是通过"结构体+方法+接口"的组合,实现数据封装、代码复用和面向对象的所有核心能力。本文从基础入门到进阶实战,逐知识点拆解,含示例代码、注意事项和易错点总结,覆盖学生考试、作业及入门开发所需的全部内容,确保看完就能掌握、会用。
一、什么是结构体?
结构体是一种用户自定义的数据类型,本质是"多个不同类型变量的集合",可以将一组相关联的数据(字段)打包在一起,形成一个新的、有意义的类型,用于描述一个具体的事物(比如学生、老师、商品等)。
举个通俗的例子:描述"学生",需要包含姓名、年龄、学号、成绩等信息,这些信息分属不同类型(姓名是string,年龄是int,成绩是float64),无法用单一的基本类型表示,此时就可以用结构体将这些信息"封装"起来,形成一个"Student"结构体类型,后续就可以用这个类型创建具体的学生实例。
结构体的核心作用:
-
封装一组相关联的数据,让代码逻辑更清晰、更具可读性;
-
为数据绑定方法,实现"数据+行为"的封装(类似其他语言的"类");
-
实现代码复用,通过结构体嵌套组合已有结构,避免重复编码;
-
支持与JSON、数据库等进行数据映射(实际开发中最常用的场景之一)。
二、结构体的定义与声明(基础必掌握)
结构体的定义遵循固定语法,核心是"type关键字+结构体名+struct关键字+字段列表",字段列表中需明确每个字段的"字段名"和"字段类型"。
1. 基本定义格式
// 语法格式:type 结构体名 struct { 字段名 字段类型; 字段名 字段类型... }
type 结构体名 struct {
字段名1 类型1
字段名2 类型2
// 可添加多个字段,字段类型可以是基本类型、引用类型,甚至是其他结构体
}
说明:
-
结构体名:遵循Go语言标识符规则,首字母大写表示包外可访问(公开),首字母小写表示包内私有(仅当前包可用);
-
字段名:同样遵循标识符规则,首字母大写表示该字段可被包外访问,首字母小写仅包内访问;
-
字段类型:可以是任意Go语言类型,包括int、string、bool等基本类型,slice、map、pointer等引用类型,也可以是其他自定义结构体、接口等。
2. 基础示例(实战常用)
定义3个不同场景的结构体,覆盖基础用法:
package main
import "fmt"
// 示例1:描述学生(基础字段,全公开)
type Student struct {
Name string // 姓名(string类型,公开)
Age int // 年龄(int类型,公开)
ID string // 学号(string类型,公开)
Score float64 // 成绩(float64类型,公开)
}
// 示例2:描述商品(包含引用类型字段)
type Goods struct {
Name string // 商品名称
Price float64 // 商品价格
Tags []string// 商品标签(切片类型,引用类型)
Stock int // 库存
}
// 示例3:描述地址(包内私有结构体,首字母小写)
type address struct {
Province string // 省份(包内私有,仅当前包可用)
City string // 城市
Detail string // 详细地址
}
3. 结构体的零值
当我们声明一个结构体变量但未给其赋值时,结构体的所有字段都会被自动赋予对应类型的"零值",这是Go语言的特性,也是初学者容易忽略的点。
常见类型的零值回顾:
-
基本类型:int→0,string→""(空字符串),bool→false,float64→0.0;
-
引用类型:slice→nil,map→nil,pointer→nil;
-
结构体类型:其内部所有字段均为对应类型的零值。
func main() {
// 声明一个Student类型变量,未赋值
var s Student
fmt.Println(s)
// 输出:{ 0 0} → Name为空字符串,Age为0,ID为空字符串,Score为0.0
}}
三、结构体实例的创建
定义结构体后,需要创建"结构体实例"(类似其他语言的"对象")才能使用其字段和方法。Go语言提供4种创建方式,各有适用场景,重点掌握第3、4种。
方式1:声明变量后,逐个赋值(最基础,适合字段较少的场景)
func main() {
// 1. 声明Student类型变量s
var s Student
// 2. 逐个给字段赋值(使用"."访问字段)
s.Name = "张三"
s.Age = 18
s.ID = "2024001"
s.Score = 95.5
// 3. 打印实例
fmt.Println(s) // 输出:{张三 18 2024001 95.5}
}
方式2:直接初始化(按字段顺序赋值,不推荐)
这种方式需要严格按照结构体定义的"字段顺序"赋值,一旦字段顺序修改,代码会报错,可读性差,日常开发不推荐使用。
func main() {
// 格式:结构体名{字段值1, 字段值2, 字段值3...}
s := Student{"李四", 19, "2024002", 88.0}
fmt.Println(s) // 输出:{李四 19 2024002 88}
// 注意:必须按字段顺序赋值,少传、多传都会报错
// 错误示例:s2 := Student{"王五", 20} → 字段数量不匹配,编译报错
}
方式3:指定字段名初始化(推荐,最常用)
这种方式无需遵循字段顺序,可只给部分字段赋值,未赋值的字段会自动取零值,可读性强、不易出错,是日常开发中最常用的创建方式。
func main() {
// 格式:结构体名{字段名1: 字段值1, 字段名2: 字段值2...}
s := Student{
Name: "王五",
Age: 20,
ID: "2024003",
// Score未赋值,自动取零值0.0
}
fmt.Println(s) // 输出:{王五 20 2024003 0}
// 也可以只给部分核心字段赋值
s2 := Student{
Name: "赵六",
Score: 92.5,
}
fmt.Println(s2) // 输出:{赵六 0 92.5}
}
方式4:指针方式创建(推荐,适合需要修改原实例的场景)
使用"&结构体名{}"创建结构体指针实例,返回的是结构体的内存地址,后续通过指针操作,可以直接修改原结构体的字段值(后续方法绑定会详细讲解)。
func main() {
// 方式4.1:指针方式+指定字段初始化(最常用)
s1 := &Student{
Name: "小明",
Age: 17,
ID: "2024004",
}
fmt.Println(s1) // 输出:&{小明 17 2024004 0}(带&表示指针)
fmt.Println(*s1) // 输出:{小明 17 2024004 0}(解引用,获取指针指向的实例)
// 方式4.2:先声明指针,再赋值
var s2 *Student
s2 = &Student{} // 初始化指针,指向一个空的Student实例
s2.Name = "小红" // Go自动解引用,无需写(*s2).Name
s2.Age = 18
fmt.Println(s2) // 输出:&{小红 18 0}
}
关键注意点:Go语言中,结构体指针可以直接用"."访问字段,无需手动解引用(即无需写(*s2).Name),编译器会自动处理,简化代码编写。
四、结构体字段的访问与修改(核心操作)
无论是结构体实例,还是结构体指针实例,都使用"."操作符访问和修改字段,这是Go语言的简化设计,无需区分值和指针。
1. 普通结构体实例(值类型)的字段操作
普通结构体实例是值类型,修改字段时,修改的是"实例副本",不会影响原实例(后续方法的"值接收器"会详细讲解)。
func main() {
// 普通结构体实例(值类型)
s := Student{Name: "张三", Age: 18}
fmt.Println("修改前:", s) // 输出:修改前: {张三 18 0}
// 修改字段
s.Age = 19
s.Score = 96.0
fmt.Println("修改后:", s) // 输出:修改后: {张三 19 96}
}
2. 结构体指针实例(引用类型)的字段操作
结构体指针实例是引用类型,修改字段时,直接操作原实例的内存地址,修改后会影响原实例,这是日常开发中最常用的方式(尤其是结构体较大时)。
func main() {
// 结构体指针实例(引用类型)
s := &Student{Name: "李四", Age: 19}
fmt.Println("修改前:", *s) // 输出:修改前: {李四 19 0}
// 直接通过指针修改字段(Go自动解引用)
s.Age = 20
s.ID = "2024005"
fmt.Println("修改后:", *s) // 输出:修改后: {李四 20 2024005 0}
}
3. 字段的访问权限
Go语言中,字段的访问权限由"字段名首字母大小写"决定,这是结构体封装的核心,也是初学者容易踩坑的点:
-
字段名首字母大写:公开字段,可被其他包访问、修改;
-
字段名首字母小写:私有字段,仅能在当前包内访问、修改,其他包无法访问(即使导入了该包)。
// 示例:当前包为main包
type Student struct {
Name string // 首字母大写,公开字段,其他包可访问
age int // 首字母小写,私有字段,仅main包可访问
}func main() {
s := Student{Name: "张三", age: 18}
fmt.Println(s.Name) // 正常访问,输出:张三
fmt.Println(s.age) // 正常访问,输出:18(当前包内)
}// 若在其他包(比如test包)中导入main包,尝试访问:
// var s main.Student
// s.Name = "李四" // 正常,公开字段
// s.age = 19 // 编译报错,私有字段,其他包无法访问
五、结构体方法(面向对象核心)
Go 语言没有传统的类,但可以为结构体绑定方法 ,实现 "数据 + 行为" 的封装,这是 Go 面向对象编程的核心。方法本质是带有接收器 的特殊函数,接收器分为值接收器 和指针接收器两种。
1. 方法定义格式
func (接收器变量 接收器类型) 方法名(参数) 返回值 {
函数体
}
接收器变量类似于其他语言的this或self,代表调用该方法的结构体实例。
2. 值接收器
值接收器会复制一份结构体副本,方法内部无法修改原结构体,适合仅读取、展示数据的场景。
// 值接收器:展示学生信息
func (s Student) Show() {
fmt.Printf("姓名:%s,年龄:%d,学号:%s\n", s.Name, s.Age, s.ID)
}
3. 指针接收器(推荐使用)
指针接收器传递结构体内存地址,方法内部可以直接修改原结构体,且避免大结构体复制带来的性能损耗,是开发中最常用的方式。
// 指针接收器:修改学生年龄
func (s *Student) SetAge(age int) {
s.Age = age
}
Go 语言语法糖支持:无论是值实例还是指针实例,都可以直接调用指针接收器方法,编译器会自动处理地址转换。
4. 使用示例
func main() {
s := Student{Name: "小明", Age: 17}
s.Show() // 调用值接收器方法
s.SetAge(18)// 调用指针接收器方法,修改原值
s.Show() // 年龄已更新
}
六、结构体嵌套(组合复用)
Go 语言不支持继承,通过结构体嵌套 实现代码复用,分为匿名嵌套 和命名嵌套,匿名嵌套最常用。
1. 匿名嵌套
只写类型不写字段名,被嵌套结构体的字段和方法可以直接访问,模拟继承效果。
type Person struct {
Name string
Age int
}
type Student struct {
Person // 匿名嵌套
ID string
}
使用时可直接访问匿名结构体字段:
go
运行
s := Student{}
s.Name = "小红" // 等价于 s.Person.Name
2. 命名嵌套
明确指定字段名,适合多个同类型结构体嵌套,避免字段冲突。
type Student struct {
Father Person
Mother Person
ID string
}
访问方式:s.Father.Name
七、结构体与 JSON 序列化(开发高频)
结构体常用于与 JSON 数据互相转换,是接口开发、数据存储的必备技能,依赖encoding/json包和结构体标签实现。
1. 结构体标签
通过反引号定义标签,指定 JSON 键名、序列化规则:
type Student struct {
Name string `json:"name"` // 指定JSON键名为name
Age int `json:"age,omitempty"`// 零值忽略
Score float64 `json:"-"` // 忽略该字段
}
2. 序列化(结构体→JSON)
使用json.Marshal()完成转换:
s := Student{Name: "张三", Age: 18}
data, _ := json.Marshal(s)
fmt.Println(string(data))
3. 反序列化(JSON→结构体)
使用json.Unmarshal()解析数据:
str := `{"name":"李四","age":19}`
var s Student
json.Unmarshal([]byte(str), &s)
八、结构体易错点总结
- 访问权限:字段 / 结构体名首字母大写公开,小写私有,小写字段无法 JSON 序列化;
- 实例创建:按顺序赋值易出错,推荐指定字段名初始化;
- 指针访问:结构体指针可直接用
.访问字段,无需手动解引用; - 方法选择:需要修改结构体或提升性能时,使用指针接收器;
- 嵌套冲突:同名字段优先访问外层结构体字段;
- 零值特性:未赋值字段自动取类型零值,而非 nil。
九、总结
结构体是 Go 语言自定义类型的基石,通过封装数据、绑定方法、嵌套组合,完美替代了传统面向对象的类。从基础定义、实例创建,到方法绑定、JSON 转换,再到指针优化,结构体贯穿 Go 开发全过程。
学习结构体的核心是理解封装 与组合思想,牢记访问权限、方法接收器、指针操作三大要点。熟练掌握结构体,能大幅提升代码的可读性、复用性和扩展性,是成为 Go 开发者必须夯实的核心能力。