为什么引入结构体?
在Go语言中,引入结构体(struct)是为了更灵活地组织和操作数据。结构体是一种自定义的复合数据类型,可以包含不同类型的字段(成员变量),用于描述一个具体的实体或对象。
- 数据组织:结构体允许将多个相关的数据字段组织在一起,形成一个逻辑上的单元。通过定义结构体,可以将相关的数据封装在一起,提高代码的可读性和可维护性。
- 类似对象的概念:结构体可以用来表示对象的状态和行为。通过在结构体中定义字段和方法,可以实现一些面向对象的特性,如封装、继承和多态。
- 数据传递:结构体可以作为函数的参数和返回值,方便地传递复杂的数据结构。通过传递结构体,可以将相关的数据打包成一个单独的实参,简化函数的调用和处理逻辑。
- 内存布局控制:结构体允许精确地控制内存布局,包括字段的对齐、填充和内存对齐等。这在与外部系统进行数据交互、网络通信和数据持久化等方面非常有用。
- 可扩展性:结构体可以通过嵌套其他结构体或使用指针来实现复杂的数据结构。这使得可以创建具有层次结构和递归特性的数据类型,满足不同业务需求。
- JSON和XML序列化:结构体对于JSON和XML等数据序列化和反序列化操作非常友好。通过为结构体添加标签(tag),可以控制序列化和反序列化过程中的字段名称、格式和映射等。
总体而言,结构体提供了一种组织和操作数据的方式,更加灵活和可扩展。结构体在Go语言中被广泛应用于各种场景,包括定义自定义数据类型、创建数据模型、表示实体对象等。
Golang 语言面向对象编程说明
- Golang 也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所以我们说 Golang 支持面向对象编程特性是比较准确的。
- Golang 没有类(class),Go 语言的结构体(struct)和其它编程语言的类(class)有同等的地位,你可以理解 Golang 是基于 struct 来实现 OOP 特性的。
- Golang 面向对象编程非常简洁,去掉了传统 OOP 语言的继承、方法重载、构造函数和析构函数、隐藏的 this 指针等等。
- Golang 仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它 OOP 语言不一样,比如继承 :Golang 没有 extends 关键字,继承是通过匿名字段来实现。
- Golang 面向对象(OOP)很优雅,OOP 本身就是语言类型系统(type system)的一部分,通过接口(interface)关联,耦合性低,也非常灵活。在Golang 中面向接口编程是非常重要的特性。
结构体
- 结构体是自定义的数据类型,代表一类事物.
- 结构体变量(实例)是具体的,实际的,代表一个具体变量
//定义一个Cat结构体,将Cat的各个字段/属性信息,放入到Cat结构体进行管理
type Cat struct {
Name string
Age int
Color string
Hobby string
Scores [3]int // 字段是数组...
}
结构体声明
type 结构体名称 struct {
field1 type
field2 type
}
例如
type Person struct {
name string
age int
}
创建结构体变量
- var定义
var person Person
定义了一个名为person的Person结构体;
- 推导定义(定义的同时初始化)
p2 := Person{"mary", 20}
// p2.Name = "mary"
// p2.Age = 20
fmt.Println(p2)
- & : var person *Person = new (Person)
var p3 *Person= new(Person)
//因为p3是一个指针,因此标准的给字段赋值方式
//(*p3).Name = "smith" 也可以这样写 p3.Name = "smith"
//原因: go的设计者 为了程序员使用方便,底层会对 p3.Name = "smith" 进行处理
//会给 p3 加上 取值运算 (*p3).Name = "smith"
(*p3).Name = "smith"
p3.Name = "john"
(*p3).Age = 30
p3.Age = 100
fmt.Println(*p3)
- 案例: var person *Person = &Person{}
//案例: var person *Person = &Person{}
//下面的语句,也可以直接给字符赋值
//var person *Person = &Person{"mary", 60}
var person *Person = &Person{}
//因为person 是一个指针,因此标准的访问字段的方法
// (*person).Name = "scott"
// go的设计者为了程序员使用方便,也可以 person.Name = "scott"
// 原因和上面一样,底层会对 person.Name = "scott" 进行处理, 会加上 (*person)
(*person).Name = "scott"
person.Name = "scott~~"
(*person).Age = 88
person.Age = 10
fmt.Println(*person)
第 3 种和第 4 种方式返回的是 结构体指针
结构体指针访问字段的标准方式应该是:(*结构体指针).字段名 ,比如 (*person).Name = “tom”,但 go 做了一个简化,也支持 结构体指针.字段名, 比如 person.Name = “tom”。更加符合程序员使用的习惯,go 编译器底层 对 person.Name 做了转化 (*person).Name。
结构体字段
- 从概念或叫法上看: 结构体字段 = 属性 = field (即授课中,统一叫字段)
- 字段是结构体的一个组成部分,一般是基本数据类型、数组,也可是引用类型
- 字段声明语法同变量,示例:字段名 字段类型
- 字段的类型可以为:基本类型、数组或引用类型
- 在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值) :
布尔类型是 false ,数值是 0 ,字符串是 “”。
数组类型的默认值和它的元素类型相关,比如 score [3]int 则为[0, 0, 0]
指针,slice,和 map 的零值都是 nil ,即还没有分配空间。
package main
import (
"fmt"
)
//如果结构体的字段类型是: 指针,slice,和map的零值都是 nil ,即还没有分配空间
//如果需要使用这样的字段,需要先make,才能使用.
type Person struct{
Name string
Age int
Scores [5]float64
ptr *int //指针
slice []int //切片
map1 map[string]string //map
}
func main() {
//定义结构体变量
var p1 Person
fmt.Println(p1)
//{ 0 [0 0 0 0 0] <nil> [] map[]}
if p1.ptr == nil {
fmt.Println("ok1")
}
//ok1
if p1.slice == nil {
fmt.Println("ok2")
}
//ok2
if p1.map1 == nil {
fmt.Println("ok3")
}
//ok3
//使用slice, 再次说明,一定要make
p1.slice = make([]int, 10)
p1.slice[0] = 100 //ok
//使用map, 一定要先make
p1.map1 = make(map[string]string)
p1.map1["key1"] = "tom~"
fmt.Println(p1)
//{ 0 [0 0 0 0 0] <nil> [100 0 0 0 0 0 0 0 0 0] map[key1:tom~]}
}
- 不同结构体变量的字段是独立,互不影响,一个结构体变量字段的更改,不影响另外一个, 结构体是值类型。
//不同结构体变量的字段是独立,互不影响,一个结构体变量字段的更改,
//不影响另外一个, 结构体是值类型
var monster1 Monster
monster1.Name = "牛魔王"
monster1.Age = 500
monster2 := monster1 //结构体是值类型,默认为值拷贝
monster2.Name = "青牛精"
fmt.Println("monster1=", monster1) //monster1= {牛魔王 500}
fmt.Println("monster2=", monster2) //monster2= {青牛精 500}
结构体类型的内存访问机制
结构体使用注意事项和细节
- 结构体的所有字段在内存中是连续的
- 结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类
型) - 结构体进行 type 重新定义(相当于取别名),Golang 认为是新的数据类型,但是相互间可以强转