目录
什么是结构体
定义结构体
基本的方式实例化结构体
访问结构体的成员变量
指针类型的方式实例化结构体
取结构体地址的方式实例化
知识扩展:*号 和 &号
构造函数
成员函数(成员方法)
匿名成员变量
方法传入指针类型的结构体
结构体嵌套
深拷贝与浅拷贝
- Go 语言通过用自定义的方式形成新的类型,结构体是类型中带有成员的复合类型。Go 语言使用结构体和结构体成员来描述真实世界的实体和实体对应的各种属性。
- Go 语言中的结构体类型可以被实例化,使用
new
或&
构造的类型实例的类型是类型的指针。- 结构体成员是由一系列的成员变量构成,这些成员变量也被称为“字段”。字段有以下特性:
- 字段拥有自己的类型和值。
- 字段名必须唯一。
- 字段的类型也可以是结构体,甚至是字段所在结构体的类型。
- 关于 Go 语言的类(class)
- Go 语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。
- Go 语言的结构体与“类”都是复合结构体,但 Go 语言中结构体的内嵌配合接口比面向对象具有更高的扩展性和灵活性。
- Go 语言不仅认为结构体能拥有方法,且每种自定义类型也可以拥有自己的方法。
自定义的结构体名称在同一个包内不允许重复。结构体的定义只是一种内存布局的描述,只有当结构体实例化时,才会真正地分配内存,因此必须在定义结构体并实例化后才能使用结构体的字段。
语法
// type 关键字表示定义普通结构体
// 把 type 关键字换成 var 表示定义的是匿名结构体,匿名结构体通常用于只使用一次的情况
type 自定义结构体名称 struct {
字段1 数据类型
字段n 数据类型
}
示例
// type 和 struct 是定义结构体的关键字
type user struct {
// id
id int64
// 生日
birthday time.Time
// 账号,密码,姓名
account, password, username string // 类型相同的字段可以简写在同一行
}
实例化就是根据结构体定义的格式创建一份与格式一致的内存区域,结构体实例与实例间的内存是完全独立的。
语法
var 自定义的变量名称 结构体名称
示例
var u user //声明, 结构体中的每一个字段,都会被相应的类型的默认值初始化
u = user{} //结构体中的每一个字段,都会被相应的类型的默认值初始化
u = user{id: 1, account: "ZhangSan"} // 初始化时赋值
u = user{1, time.Now(), "ZhangSan", "123456", "张三"} // 赋值初始化时可以省略字段名,但是需要与结构体定义里的字段顺序保持一致
使用.来访问结构体的成员变量
示例
u.id
u.username
u.account
Go语言中,还可以使用 new 关键字对类型(包括结构体、整型、浮点数、字符串等)进行实例化,结构体在实例化后会形成指针类型的结构体。
示例
var u = new(user)
fmt.Println(u.id)
u.password = "654321"
注:u 的类型属于 *user,属于指针类型。
在Go语言中,对结构体进行
&
取地址操作时,视为对该类型进行一次 new 的实例化操作。
示例
// 定义一个结构体
type command struct {
Name string // 名称
Var *int // 绑定的变量,使用整型指针绑定一个指针,变量值可以与绑定的值随时保持同步
}
var version int = 1 //绑定的目标变量
cmd := &command{} // 对结构体取地址实例化
cmd.Name = "haha" // 初始化成员变量
cmd.Var = &version // 初始化成员变量
变量是什么?变量是一块内存的别名,即给这块内存起一个名字,这块内存在计算机内存中有具体的地址。
&:取地址符号,表示取出这个变量所在内存的地址。
*:指针符号,表示指向变量的内存地址。指针变量就是指向内存地址的变量
*是指,&是取,可以理解成 &取得的值赋给*,即 *p = &a
一个变量由变量地址和地址上的数据组成,在内存中物理表示的方式如下图
例如
var a = 10
var p *int // 第 2 行
p = &a
fmt.Printf("%T \n", p) // *int
fmt.Println(&a) // 0xc00001e0a8
fmt.Println(p) // 0xc00001e0a8
fmt.Println(a) // 10
fmt.Println(*&a) // 10 // &a = p,那么是不是 *&a 和 *p 是一样的
fmt.Println(*p) // 10 // 第 10 行
// 注意第2行和第10行*号的区别,第2行表示这是一个指针变量,第10行表示取这个指针地址上的数据
Go语言的类型或结构体没有构造函数的功能,可以使用结构体初始化的过程来模拟实现构造函数。
示例
// Cat 定义结构体
type Cat struct {
Color string
Name string
}
// NewCatByName 定义构造函数,只初始化名称
func NewCatByName(name string) *Cat {
return &Cat{
Name: name,
}
}
// NewCatByColor 定义构造函数,只初始化颜色
func NewCatByColor(color string) *Cat {
return &Cat{
Color: color,
}
}
// 调用构造函数,生成构造实例
var c = NewCatByName("大花")
fmt.Println(c.Name)
注:返回 * 指针类型,和返回普通构造类型的区别在于对象流转在内存中是通过拷贝进行的,返回普通构造类型拷贝的是对象的实例数据,返回指针的话只用拷贝对象的地址(一个int32的数值)
成员方法可以写在不同的文件中,但是必须是在一个包下面。
语法
func (结构体实例 结构体类型) 方法名(入参) 返回类型 {
方法体
}
示例
type user struct {
id int64
birthday time.Time
account, password, username string
}
// 成员方法,如果是(_ user) 或者 (user) 表示匿名成员方法
func (u user) hello(man string) {
fmt.Println("你好 " + man + ",我的名字是 " + u.username)
}
// 调用成员方法
var u = user{username: "ZhangSan"}
u.hello("LiSi")
匿名字段,直接使用类型作为字段名,所以匿名字段中不能出现重复的数据类型。
示例
// 定义
type Student struct {
id int
string // 匿名字段
float32 // 匿名字段
}
// 使用
var stu = Student{id: 1, string: "ZhangSan", float32: 88.3}
fmt.Println(stu.string)
- 方法入参是普通的结构体时,在函数里修改入参结构体对象不影响原来的结构体数据。因为传入的是数据的拷贝,不影响原来的数据。(深拷贝)
- 方法入参是指针类型的结构体时,在函数里修改入参结构体对象时,会影响原来的结构体数据,因为传入的是指针,修改会直接修改指针地址上的数据。(浅拷贝)
示例
func hello(u *user) {
u.username = "LiSi"
}
func (u *user) hello() {
u.username = "LiSi"
}
示例
type user struct {
name string
}
type book struct {
name string
author user
}
func main() {
b := new(book)
b.name = "三国演义"
b.author.name = "罗贯中"
}
深拷贝:会拷贝元素的整个内存空间的数据,把数据赋值给另一块内存空间,修改数据互不影响。
浅拷贝:只拷贝元素的内存地址,拷贝完成后修改新的元素,会影响原来的元素,传slice数浅拷贝。