【Go语言】Go语言结构体全面解析
✨✨ 欢迎大家来到景天科技苑✨✨
🎈🎈 养成好习惯,先赞后看哦~🎈🎈
🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
🏆《博客》:Python全栈,Golang开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。所属的专栏:Go语言开发零基础到高阶实战
景天的主页:景天科技苑
文章目录
- Go语言中的结构体
- 一、结构体的基本概念
- 1.1 结构体的定义
- 1.2 结构体字段的访问
- 1.3 初始化
- 1.3.1 使用字面量初始化
- 1.3.2 使用关键字初始化
- 二、结构体的使用
- 2.1 访问结构体成员
- 2.2 结构体作为函数参数
- 2.3 结构体切片
- 三、结构体的进阶用法
- 3.1 结构体的嵌套
- 3.2 结构体方法
- 3.3 结构体和JSON序列化
- 3.4 结构体标签(Tag)
- 3.5 匿名结构体
- 3.6 匿名字段(字段提升)
- 3.7 结构体指针
- 3.8 结构体的零值
- 四、结构体导出规则
- 五、结构体的工厂模式
- 六、实际案例:图书管理系统
- 6.1 定义图书结构体
- 6.2 创建图书列表
- 6.3 实现图书管理系统功能
- 七、总结
Go语言中的结构体
在Go语言中,结构体(Struct)是一个核心概念,它允许开发者将多个相关或不同类型的数据项组合成一个单一的复合类型。这种特性使得结构体成为Go语言中非常强大和灵活的数据组织方式,特别适用于表示复杂的数据结构和对象。
本文将结合实际案例,详细阐述Go语言中结构体的定义、使用、方法定义以及高级特性,如匿名结构体、嵌套结构体、结构体指针,结构体导出规则等。
一、结构体的基本概念
结构体是Go语言中的一种复合数据类型,可以视为一个字段的集合。这些字段可以是不同的数据类型,包括基本数据类型(如int、float64、string等)、指针、数组、切片、其他结构体等。结构体是用户自定义的类型,它们使得数据处理更加模块化,并增强了代码的可读性和可维护性。
1.1 结构体的定义
结构体的定义使用type
关键字和struct
关键字。定义结构体时,需要在{}
中列出所有的字段及其类型。
type Person struct {Name stringAge intEmail string
}
上面的代码定义了一个名为Person
的结构体,它有三个字段:Name
(字符串类型)、Age
(整型)和Email
(字符串类型)。
1.2 结构体字段的访问
一旦定义了结构体,就可以创建结构体的实例(即变量),并通过点(.
)操作符访问或修改其字段。
func main() {var p Personp.Name = "Alice"p.Age = 30p.Email = "alice@example.com"fmt.Println(p.Name) // 输出: Alice
}
1.3 初始化
1.3.1 使用字面量初始化
结构体可以使用字面量进行初始化,即直接在声明变量时指定字段的值。
p1 := Person{Name: "Alice",Age: 30,Job: "Engineer",Salary: 6000,
}
1.3.2 使用关键字初始化
也可以指定字段名来初始化结构体,这样可以忽略字段的声明顺序。
p2 := Person{Name: "Bob",Age: 25,Job: "Designer",Salary: 5000,
}// 或者只初始化部分字段
p3 := Person{Name: "Charlie",Age: 35,
}
p3.Job = "Artist"
p3.Salary = 4500
二、结构体的使用
2.1 访问结构体成员
通过结构体变量名和点(.
)运算符访问结构体成员。
fmt.Println(p1.Name) // 输出: Alice
fmt.Println(p2.Age) // 输出: 25
2.2 结构体作为函数参数
结构体可以作为函数的参数传递,既可以传递值也可以传递指针。传递值会复制整个结构体,而传递指针则只会传递结构体的地址。
func printPerson(p Person) {fmt.Println("Name:", p.Name)fmt.Println("Age:", p.Age)
}func printPersonPtr(p *Person) {fmt.Println("Name:", p.Name)p.Age++ // 修改指针指向的结构体中的字段
}func main() {p := Person{Name: "David", Age: 40}printPerson(p)printPersonPtr(&p)fmt.Println("Age after function call:", p.Age) // 输出: 41
}
2.3 结构体切片
结构体切片是一个包含多个结构体实例的切片,用于管理结构体的集合。
var people []Person
people = append(people, p1)
people = append(people, p2)for _, person := range people {fmt.Printf("Name: %s, Age: %d\n", person.Name, person.Age)
}
三、结构体的进阶用法
3.1 结构体的嵌套
结构体可以嵌套使用,即一个结构体的字段可以是另一个结构体类型。这有助于构建更复杂的数据结构。
type Address struct {City stringCountry string
}type Person struct {Name stringAge intAddress Address
}func main() {var p Personp.Name = "Bob"p.Age = 25p.Address.City = "New York"p.Address.Country = "USA"fmt.Println(p.Address.City) // 输出: New York
}
3.2 结构体方法
结构体方法是在结构体上定义的函数,用于实现对该结构体实例的操作。在Go语言中,通过接收者(receiver)实现这一功能。接收者可以是值接收者或指针接收者。
type Person struct {Name stringAge int
}// 使用值接收者
func (p Person) Describe() string {return fmt.Sprintf("Name: %s, Age: %d", p.Name, p.Age)
}// 使用指针接收者
func (p *Person) SetAge(age int) {p.Age = age
}func main() {p := Person{Name: "Charlie", Age: 35}fmt.Println(p.Describe()) // 输出: Name: Charlie, Age: 35p.SetAge(40)fmt.Println(p.Describe()) // 输出: Name: Charlie, Age: 40
}
在上面的例子中,Describe
方法是一个值接收者方法,它返回Person
实例的描述信息。而SetAge
方法是一个指针接收者方法,它允许我们修改Person
实例的Age
字段。
3.3 结构体和JSON序列化
在实际开发中,经常需要将结构体实例转换为JSON格式的字符串,以便于网络传输或数据存储。Go标准库中的encoding/json
包提供了这样的功能。
package mainimport ("encoding/json""fmt"
)type Person struct {Name string `json:"name"`Age int `json:"age"`
}func main() {p := Person{Name: "David", Age: 45}data, err := json.Marshal(p)if err != nil {fmt.Println("json encode failed:", err)return}fmt.Println(string(data)) // 输出: {"name":"David","age":45}
}
在上面的例子中,通过json:"name"
和json:"age"
这样的标签(Tag),我们指定了结构体字段在JSON字符串中的键名。这些标签是Go语言结构体独有的特性,使得Go能够轻松地与其他语言进行JSON数据的交换。
3.4 结构体标签(Tag)
结构体标签是结构体字段后面的元信息,它由一对反引号`
包裹,格式通常为键值对形式,键值之间用冒号:
分隔,值被双引号"
包围。结构体标签主要用于为结构体字段提供额外的信息,这些信息在运行时可以通过反射机制读取。
除了上述的JSON序列化场景,结构体标签还可以用于数据库ORM映射、XML序列化等场景。
type Product struct {ID int `db:"id"` // 数据库字段映射Name string `json:"name"` // JSON序列化Price float64 `xml:"price"` // XML序列化
}
3.5 匿名结构体
匿名结构体是没有名称的结构体,通常用于一次性使用的情况。
hobby := struct {HobbyId intHobbyName string
}{HobbyId: 1,HobbyName: "Basketball",
}
fmt.Println(hobby)
fmt.Println(hobby.HobbyName) // 输出: Basketball
3.6 匿名字段(字段提升)
当结构体中嵌入一个匿名结构体时,匿名结构体的字段会被提升,可以直接通过外部结构体访问。
结构体中的匿名字段,没有名字的字段,这个时候属性类型不能重复。
如何打印这个匿名字段,默认使用数据类型当做字段名称。
// Teacher 结构体中的匿名字段,没有名字的字段,这个时候属性类型不能重复
type Teacher struct {stringint
}// 匿名字段
t1 := Teacher{"dashan", 18}
fmt.Println(t1)
// 如何打印这个匿名字段,默认使用数据类型当做字段名称
fmt.Println(t1.string) // dashan
fmt.Println(t1.int) // 18
3.7 结构体指针
结构体指针是指向结构体变量的指针,通过&
操作符获取结构体的地址。
package mainimport "fmt"// User2 定一个结构体 type User struct
type User2 struct {name stringage intsex string
}func main() {// 结构体类型 包.struct名user1 := User2{"dajiang", 18, "男"}fmt.Println(user1)fmt.Printf("%T,%p\n", user1, &user1) // main.User2,0xc00007e4b0// 结构体是值类型的,赋值后,重新开辟内存空间user2 := user1fmt.Println(user2)fmt.Printf("%T,%p\n", user2, &user2) // main.User2,0xc00007e540user2.name = "tywin"fmt.Println(user1)fmt.Println(user2)fmt.Println("========================")// 指针解决值传递的问题var user_ptr *User2user_ptr = &user1// *user_ptr 等价于 user1fmt.Println(*user_ptr)(*user_ptr).name = "jingtian"fmt.Println(user1)// 语法糖user_ptr.name = "jingtian222222222"fmt.Println(user1)// 内置函数 new 创建对象。 new 关键字创建的对象,都返回指针,而不是结构体对象。// func new(Type) *Type// 通过这种方式创建的结构体对象更加灵活,突破了结构体是值类型的限制。user3 := new(User2)fmt.Println(user3)//初始化user3(*user3).name = "小红"user3.sex = "女"user3.age = 18fmt.Println(user3)updateUser(user3)fmt.Println(user3)
}// 传递User2类型指针
func updateUser(user *User2) {//(*user).age = 100//语法糖写法user.age = 100
}
3.8 结构体的零值
当结构体变量被声明但未初始化时,它的字段会被设置为对应类型的零值。
var p Person
fmt.Println(p) // 输出: { 0}
四、结构体导出规则
在其他语言中,有public 公开的,所有地方都可以使用 、priavte私有的,只能自己使用。
结构体:结构体名字,属性名字 。首大写字母,可以导出使用,首字母小写,不能导出使用。
如果结构体名称首字母小写,则结构体不会被导出。这时,即使结构体成员字段名首字母大写,也不会被导出。
如果结构体名称首字母大写,则结构体可被导出,但只会导出大写首字母的成员字段,那些小写首字母的成员字段不会被导出。
如果存在嵌套结构体,即使嵌套在内层的结构体名称首字母小写,外部也能访问到其中首字母大写的成员字段。
我们建个base包,里面有个pojo包,里面有个User.go
我们创建个main.go。可以看到money不可以被导出
package mainimport ("fmt""jingtian/yufa/结构体/base/pojo"
)func main() {user := pojo.User{Name: "jingtian",Age: 18,//money : 900}fmt.Println(user)
}
打印user,可以打印能导出的字段
money存在,但不可以被访问和修改
五、结构体的工厂模式
在Go语言中,由于没有传统的构造函数,我们常常使用工厂模式来创建结构体的实例。工厂模式是一种创建型设计模式,它提供了一种创建对象的最佳方式。
type Person struct {Name stringAge int
}// 工厂函数
func NewPerson(name string, age int) *Person {return &Person{Name: name,Age: age,}
}func main() {p := NewPerson("Eve", 28)fmt.Println(p.Name) // 输出: Eve
}
在这个例子中,NewPerson
函数就是一个工厂函数,它接受参数并返回一个指向Person
结构体的指针。
六、实际案例:图书管理系统
下面我们将通过一个实际的图书管理系统案例,来演示Go语言中结构体的应用。
6.1 定义图书结构体
首先,我们定义一个Book
结构体来表示图书的信息。
type Book struct {ID string `json:"id"`Title string `json:"title"`Author string `json:"author"`Published string `json:"published"`ISBN string `json:"isbn"`
}
6.2 创建图书列表
接下来,我们可以创建一个包含多个Book
结构体的切片来表示图书列表。
var books []Bookfunc initBooks() {books = append(books, Book{ID: "001",Title: "Go语言编程之旅",Author: "Alice",Published: "2023-01-01",ISBN: "978-7-121-34567-8",})// ... 可以继续添加其他图书
}func main() {initBooks()// 对books进行操作,如显示图书列表等
}
6.3 实现图书管理系统功能
为了演示,我们可以添加一个功能来遍历图书列表并打印每本书的详细信息。
func printBooks() {for _, book := range books {fmt.Printf("ID: %s, Title: %s, Author: %s, Published: %s, ISBN: %s\n",book.ID, book.Title, book.Author, book.Published, book.ISBN)}
}func main() {initBooks()printBooks()
}
七、总结
结构体是Go语言中非常重要和强大的数据组织方式,通过结构体可以方便地表示复杂的数据结构和对象。本文详细介绍了结构体的定义、使用、方法定义以及高级特性,如匿名结构体、嵌套结构体、结构体指针,结构体导出规则等。通过实际案例的演示,希望能够让读者对Go语言中的结构体有更深入的理解和掌握。结构体不仅提高了代码的可读性和可维护性,还使得数据组织更加清晰和易于管理。