文章目录
- [Go 语言进阶:构造函数、父子结构体与组合复用详解](#Go 语言进阶:构造函数、父子结构体与组合复用详解)
-
- [一、Go 中的构造函数(无官方关键字,约定实现)](#一、Go 中的构造函数(无官方关键字,约定实现))
-
- [1.1 核心概念](#1.1 核心概念)
- [1.2 构造函数命名规范(业界统一)](#1.2 构造函数命名规范(业界统一))
- [1.3 基础构造函数示例](#1.3 基础构造函数示例)
- [1.4 带默认值的构造函数(工程常用,解决无重载问题)](#1.4 带默认值的构造函数(工程常用,解决无重载问题))
- [1.5 为什么构造函数优先返回指针](#1.5 为什么构造函数优先返回指针)
- 二、父子结构体(基于匿名成员实现组合,替代继承)
-
- [2.1 前置回顾](#2.1 前置回顾)
- [2.2 父子结构体基础定义示例](#2.2 父子结构体基础定义示例)
- [2.3 父子结构体 + 构造函数结合(工程标准写法)](#2.3 父子结构体 + 构造函数结合(工程标准写法))
- [2.4 父子结构体同名字段冲突(就近原则)](#2.4 父子结构体同名字段冲突(就近原则))
- [2.5 方法提升:父结构体方法,子结构体直接调用](#2.5 方法提升:父结构体方法,子结构体直接调用)
- [三、核心总结(构造函数 + 父子结构体)](#三、核心总结(构造函数 + 父子结构体))
Go 语言进阶:构造函数、父子结构体与组合复用详解
上一篇我们完整学习了结构体指针、new 关键字、匿名结构体、匿名成员基础概念 ,其中匿名成员是实现父子结构体复用的核心前提。
本篇继续无缝衔接,重点讲解 Go 中构造函数 的设计思想与标准写法,同时说明 Go 无函数重载 的特性,以及利用匿名成员实现父子结构体(结构体组合),完成 Go 面向对象式的代码复用,替代传统面向对象的继承。
一、Go 中的构造函数(无官方关键字,约定实现)
1.1 核心概念
Go 语言没有 class 类,也没有 constructor 构造函数关键字 ,不能像 Java/C++ 一样直接定义构造方法。
工程上约定:使用普通函数模拟构造函数,专门用来实例化结构体、初始化字段,统一返回结构体指针。
补充重要知识点:Go 语言不支持函数重载
函数重载:指函数名相同、参数不同,实现多个同名函数。
Go 中不允许两个同名函数 ,哪怕参数不一样也不行,因此不能通过重载实现多版本构造函数,一般使用可变参数、不同函数名实现多构造逻辑。
1.2 构造函数命名规范(业界统一)
- 函数名以
New开头:New结构体名(),例如NewUser()、NewAnimal() - 入参:接收需要初始化的字段
- 返回值:结构体指针(优先指针,避免值拷贝,方便后续修改)
1.3 基础构造函数示例
go
package main
import "fmt"
// 定义用户结构体
type User struct {
Name string
Age int
}
// 构造函数:NewUser,返回 *User 指针
func NewUser(name string, age int) *User {
// 内部使用 new 或字面量取地址初始化
return &User{
Name: name,
Age: age,
}
}
func main() {
// 直接调用构造函数实例化
u := NewUser("张三", 22)
fmt.Printf("用户信息:%+v,内存地址:%p\n", u, u)
}
终端输出结果:
用户信息:&{Name:张三 Age:22},内存地址:0xc000010200
代码解释:
- 定义
User普通结构体,包含姓名、年龄两个字段 NewUser作为构造函数,接收姓名、年龄参数,返回结构体指针main中直接调用构造函数创建对象,打印完整信息与内存地址- 返回指针避免值拷贝,外部可直接修改原结构体数据
1.4 带默认值的构造函数(工程常用,解决无重载问题)
因为 Go 没有函数重载 ,不能写两个 NewUser,所以业务中常用可变参数实现可选传参、默认值逻辑:
go
package main
import "fmt"
type User struct {
Name string
Age int
}
// 年龄不传默认 18 岁,用可变参数实现多构造效果
func NewUser(name string, age ...int) *User {
defaultAge := 18
if len(age) > 0 {
defaultAge = age[0]
}
return &User{
Name: name,
Age: defaultAge,
}
}
func main() {
u1 := NewUser("李四") // 年龄默认18
u2 := NewUser("王五", 25)
fmt.Println(u1, u2)
}
终端输出结果:
&{李四 18} &{王五 25}
代码解释:
- 使用可变参数
age ...int模拟多构造逻辑,规避 Go 不支持函数重载的限制 - 未传入年龄时,默认赋值 18 岁
- 传入年龄则使用自定义值,统一初始化逻辑,减少重复代码
1.5 为什么构造函数优先返回指针
- 避免结构体值拷贝,提升性能
- 外部拿到指针后,可调用指针接收者方法修改原数据
- 符合 Go 工程开发规范,统一内存使用方式
二、父子结构体(基于匿名成员实现组合,替代继承)
2.1 前置回顾
上一节我们学习了匿名成员基础概念 :只有类型、没有字段名。
当一个结构体,把另一个结构体作为匿名成员嵌入 时,就形成了父子结构体:
- 父结构体:被嵌入的公共基础结构体
- 子结构体:嵌入父结构体、扩展新字段的业务结构体
Go 语言没有继承 ,不支持
extends,通过**结构体组合(匿名成员嵌入)**实现代码复用。
2.2 父子结构体基础定义示例
以动物为父结构体,狗、猫为子结构体演示:
go
package main
import "fmt"
// 父结构体:公共基础结构体(父类)
type Animal struct {
Eat string // 食物
Color string // 颜色
Age int // 年龄
}
// 子结构体 Dog:匿名嵌入父结构体 Animal,扩展自己的字段
type Dog struct {
Animal // 匿名成员,嵌入父结构体,实现父子关系
Breed string // 狗独有:品种
}
// 子结构体 Cat:同样嵌入父结构体
type Cat struct {
Animal
Character string // 猫独有:性格
}
func main() {
// 实例化子结构体 Dog
dog := Dog{
Animal: Animal{
Eat: "骨头",
Color: "黄色",
Age: 3,
},
Breed: "金毛",
}
// 直接访问父结构体字段(字段提升)
fmt.Println(dog.Eat)
fmt.Println(dog.Color)
fmt.Println(dog.Breed)
}
终端输出结果:
骨头
黄色
金毛
代码解释:
Animal为父结构体,存放所有动物公共属性Dog、Cat作为子结构体,通过匿名成员嵌入父结构体- 父结构体字段自动提升,子结构体可直接点调用,无需嵌套
2.3 父子结构体 + 构造函数结合(工程标准写法)
给父、子结构体分别定义构造函数,统一初始化:
go
package main
import "fmt"
// 父结构体
type Animal struct {
Eat string
Color string
Age int
}
// 父结构体构造函数
func NewAnimal(eat, color string, age int) *Animal {
return &Animal{
Eat: eat,
Color: color,
Age: age,
}
}
// 子结构体 Dog
type Dog struct {
Animal
Breed string
}
// 子结构体构造函数:内部调用父构造函数
func NewDog(eat, color string, age int, breed string) *Dog {
return &Dog{
Animal: *NewAnimal(eat, color, age),
Breed: breed,
}
}
func main() {
dog := NewDog("骨头", "黑色", 2, "哈士奇")
fmt.Printf("狗信息:%+v\n", dog)
fmt.Println("直接访问父字段:", dog.Eat, dog.Color)
}
终端输出结果:
狗信息:{Animal:{Eat:骨头 Color:黑色 Age:2} Breed:哈士奇}
直接访问父字段: 骨头 黑色
代码解释:
- 父、子分别定义构造函数,各司其职
- 子构造函数内部调用父构造函数,完成公共字段初始化
- 外部只需调用子构造函数,即可完成全部属性赋值,代码高度解耦
2.4 父子结构体同名字段冲突(就近原则)
如果子结构体和父结构体存在同名字段 ,访问时优先使用子结构体自身字段;如需访问父结构体字段,显式指定父结构体类型。
go
package main
import "fmt"
type Animal struct {
Name string
Age int
}
type Dog struct {
Animal
Name string // 和父结构体字段同名
}
func main() {
d := Dog{
Animal: Animal{Name: "动物", Age: 3},
Name: "小狗",
}
fmt.Println(d.Name)
fmt.Println(d.Animal.Name)
}
终端输出结果:
小狗
动物
代码解释:
- 父子结构体存在同名字段
Name - 直接访问默认优先子结构体(就近原则)
- 访问父结构体同名字段,必须显式指定父结构体名
2.5 方法提升:父结构体方法,子结构体直接调用
不仅字段会提升,父结构体绑定的方法也会自动提升,子结构体实例可直接调用父结构体方法,完美实现复用。
go
package main
import "fmt"
type Animal struct {
Name string
}
// 父结构体绑定方法
func (a *Animal) Speak() {
fmt.Printf("%s 发出叫声\n", a.Name)
}
type Dog struct {
Animal
}
func main() {
d := Dog{Animal: Animal{Name: "旺财"}}
// 子结构体直接调用父结构体方法(方法提升)
d.Speak()
}
终端输出结果:
旺财 发出叫声
代码解释:
- 父结构体绑定
Speak方法 - 方法随匿名成员自动提升,子结构体无需重写即可调用
- 实现方法复用,替代传统面向对象的继承逻辑
三、核心总结(构造函数 + 父子结构体)
- 构造函数 :Go 约定用
NewXxx()函数模拟,返回结构体指针,用于统一初始化、设置默认值,是项目标配。 - 函数重载 :Go 不支持函数重载 ,多构造场景用可变参数或不同函数名实现。
- 父子结构体本质 :通过匿名成员嵌入父结构体实现组合,替代传统继承,无强耦合。
- 字段/方法提升:父结构体字段、方法自动提升到子结构体,直接调用,简化代码。
- 同名冲突规则:就近原则,优先子结构体,显式指定父结构体可访问父字段。
至此,Go 从结构体基础 → 指针 → new → 匿名结构体/成员 → 构造函数 → 父子结构体完整体系全部讲完,完全覆盖企业级开发中结构体的所有核心用法。