Go语言结构体,这一篇就够了
- 1.结构体的概念
- 2.结构体的定义
- 3.结构体的实例化
- 4.结构体初始化
- 5.构造函数
- 6.方法和接收者
- 方法
- 接收者
- 7.嵌套结构体
- 8.结构体的“继承”
- 9.结构体与JSON序列化
- 10.结构体标签(Tag)
Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。
1.结构体的概念
Go语言中的基础数据类型可以表示一些事物的基本属性,但是当我们想表达一个事物的全部或部分属性时,这时候再用单一的基本数据类型明显就无法满足需求了,Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct。 也就是我们可以通过struct来定义自己的类型了。
Go语言中通过struct来实现面向对象🩳
2.结构体的定义
使用type和struct关键字来定义结构体
举个例子,我们定义一个Person(人)结构体,代码如下:
type person struct {name stringcity stringage int8
}
同样类型的字段也可以写在一行:
type person struct {name, city stringage int8
}
3.结构体的实例化
只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。
package mainimport "fmt"type person struct {name stringcity stringage int8
}func main() {var p personp.name = "大河"p.age = 18p.city = "长春"fmt.Println(p) // {大河 长春 18}fmt.Println(p.name) // 大河
}
在定义一些临时数据结构等场景下还可以使用匿名结构体
package mainimport ("fmt"
)func main() {var user struct{Name string; Age int}user.Name = "小王子"user.Age = 18fmt.Printf("%#v\n", user)
}
4.结构体初始化
使用键值对对结构体进行初始化时,键对应结构体的字段,值对应该字段的初始值
package mainimport "fmt"type person struct {name stringcity stringage int8
}func main() {p := person{name: "大河",city: "北京",}fmt.Println(p) // {大河 北京 0}
}
初始化结构体的时候可以简写,也就是初始化的时候不写键,直接写值:
package mainimport "fmt"type person struct {name stringcity stringage int8
}func main() {p := person{"张三","北京",18,}fmt.Println(p) // {张三 北京 18}
}
使用这种格式初始化时,需要注意:
- 必须初始化结构体的所有字段。
- 初始值的填充顺序必须与字段在结构体中的声明顺序一致。
- 该方式不能和键值初始化方式混用。
5.构造函数
Go语言的结构体没有构造函数,我们可以自己实现。 例如,下方的代码就实现了一个person的构造函数。 因为struct是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大,所以该构造函数返回的是结构体指针类型。
package mainimport "fmt"type person struct {name stringcity stringage int8
}/*
构造函数
*/
func newPerson(name, city string, age int8) *person {return &person{name: name,city: city,age: age,}
}func main() {p := newPerson("大河", "北京", 22)fmt.Println(p) // &{大河 北京 22}
}
6.方法和接收者
方法
Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。接收者的概念就类似于其他语言中的this或者 self。
方法的定义格式如下:
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {函数体
}
- 接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名称首字母的小写,而不是self、this之类的命名。例如,Person类型的接收者变量应该命名为p,Connector类型的接收者变量应该命名为c等。
- 接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。
- 方法名、参数列表、返回参数:具体格式与函数定义相同。
方法使用实例:
package mainimport "fmt"type Person struct {name stringcity stringage int8
}/*
构造函数
*/
func newPerson(name, city string, age int8) *Person {return &Person{name: name,city: city,age: age,}
}func (p Person) say() {fmt.Println("我要说话了!")
}func main() {p := newPerson("大河", "北京", 22)p.say()
}
方法与函数的区别是,函数不属于任何类型,方法属于特定的类型🧣
接收者
指针类型的接收者
指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其他语言中面向对象中的this或者self。 例如我们为Person添加一个SetAge方法,来修改实例变量的年龄。
package mainimport "fmt"type Person struct {name stringcity stringage int8
}/*
构造函数
*/
func newPerson(name, city string, age int8) *Person {return &Person{name: name,city: city,age: age,}
}func (p *Person) setAge(newAge int8) {p.age = newAge
}func main() {p := newPerson("大河", "北京", 22)p.setAge(33)fmt.Println(p) // &{大河 北京 33}
}
值类型的接收者
当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。
package mainimport "fmt"type Person struct {name stringcity stringage int8
}/*
构造函数
*/
func newPerson(name, city string, age int8) *Person {return &Person{name: name,city: city,age: age,}
}func (p Person) setAge(newAge int8) {p.age = newAge
}func main() {p := newPerson("大河", "北京", 22)p.setAge(33)fmt.Println(p) // &{大河 北京 22}
}
什么时候应该使用指针类型接收者?
- 需要修改接收者中的值
- 接收者是拷贝代价比较大的大对象
- 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者
7.嵌套结构体
一个结构体中可以嵌套包含另一个结构体或结构体指针,就像下面的示例代码那样。
package mainimport "fmt"type Address struct {Province stringCity string
}// User 用户结构体
type User struct {Name stringGender stringAddress Address
}func main() {user := User{Name: "小王子",Gender: "男",Address: Address{Province: "山东",City: "威海",},}fmt.Println(user) // {小王子 男 {山东 威海}}
}
嵌套结构体内部可能存在相同的字段名。在这种情况下为了避免歧义需要通过指定具体的内嵌结构体字段名
package mainimport "fmt"// Address 地址结构体
type Address struct {Province stringCity stringCreateTime string
}// Email 邮箱结构体
type Email struct {Account stringCreateTime string
}// User 用户结构体
type User struct {Name stringGender stringAddressEmail
}func main() {var user Useruser.Name = "dahe"user.Gender = "1"user.Address.CreateTime = "2022"user.Email.CreateTime = "2023"fmt.Println(user) // {dahe 1 { 2022} { 2023}}
}
结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)🧥
8.结构体的“继承”
Go语言中使用结构体也可以实现其他编程语言中面向对象的继承
例如:
package mainimport "fmt"type Animal struct {name string
}func (a *Animal) move() {fmt.Printf("%s会动!\n", a.name)
}type Dog struct {Feet int8*Animal // 通过嵌套匿名结构体实现继承
}func (d *Dog) wang() {fmt.Printf("%s会汪汪汪~\n", d.name)
}func main() {d := &Dog{Feet: 4,Animal: &Animal{ // 注意嵌套的是结构体指针name: "乐乐",},}d.move() // 乐乐会动!d.wang() // 乐乐会汪汪汪~
}
9.结构体与JSON序列化
JSON是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。JSON键值对是用来保存JS对象的一种方式,键/值对组合中的键名写在前面并用双引号""
包裹,使用冒号:
分隔,然后紧接着值;多个键值之间使用英文,
分隔
序列化和反序列化程序实例:
package mainimport ("encoding/json""fmt"
)// Student 学生
type Student struct {ID intGender stringName string
}// Class 班级
type Class struct {Title stringStudents []*Student
}func main() {c := &Class{Title: "101",Students: make([]*Student, 0, 200),}for i := 0; i < 10; i++ {stu := &Student{Name: fmt.Sprintf("stu%02d", i),Gender: "男",ID: i,}c.Students = append(c.Students, stu)}// JSON序列化:结构体-->JSON格式的字符串data, err := json.Marshal(c)if err != nil {fmt.Println("json marshal failed")return}fmt.Printf("json:%s\n", data)// JSON反序列化:JSON格式的字符串-->结构体str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"Gender":"男","Name":"stu01"},{"ID":2,"Gender":"男","Name":"stu02"},{"ID":3,"Gender":"男","Name":"stu03"},{"ID":4,"Gender":"男","Name":"stu04"},{"ID":5,"Gender":"男","Name":"stu05"},{"ID":6,"Gender":"男","Name":"stu06"},{"ID":7,"Gender":"男","Name":"stu07"},{"ID":8,"Gender":"男","Name":"stu08"},{"ID":9,"Gender":"男","Name":"stu09"}]}`c1 := &Class{}err = json.Unmarshal([]byte(str), c1)if err != nil {fmt.Println("json unmarshal failed!")return}fmt.Printf("%#v\n", c1)
}
10.结构体标签(Tag)
Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。 Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:
`key1:"value1" key2:"value2"`
为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格👔
package mainimport ("encoding/json""fmt"
)// Student 学生
type Student struct {ID int `json:"uid"` // 通过指定tag实现json序列化该字段时的keyGender string // json序列化是默认使用字段名作为keyname string // 私有不能被json包访问
}func main() {s := Student{ID: 1,Gender: "男",name: "沙河娜扎",}data, err := json.Marshal(s)if err != nil {fmt.Println("json marshal failed!")return}fmt.Printf("json str:%s\n", data)// json str:{"uid":1,"Gender":"男"}
}
版权声明:本文教程基于李文周的Go语言博客 - 结构体篇