Go语言中的基础数据类型可以表示一些事物的基本属性,但是当我们想表达一个事物的全部或部分属性时,这时候再用单一的基本数据类型明显就无法满足需求了。
Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct。 也就是我们可以通过struct来定义自己的类型了。
一、结构体的定义和初始化
1、结构体的定义
结构体的定义使用type
和struct
关键字
type 结构体名 struct {
字段名 字段类型;
字段名 字段类型;
...
字段名 字段类型;
}
其中:
- 类型名:标识自定义结构体的名称,在同一个包内不能重复。通过首字母大小写控制访问。
- 字段名:表示结构体字段名。结构体中的字段名必须唯一。通过首字母大小写控制访问。
- 字段类型:表示结构体字段的具体类型。
package main
// Person 一个结构体,包含name, age两个字段
type Person struct {
Name string // 包外可访问
age int // 包外不可访问
}
func main() {
}
2、结构体的初始化
package main
import "fmt"
// Person 一个结构体,包含name, age两个字段
type Person struct {
Name string // 包外可访问
age int // 包外不可访问
}
func main() {
// 方法一
var p1 Person
p1.Name = "tom"
p1.age = 20
fmt.Println(p1)
// 方法二
p2 := Person{}
p2.Name = "marry"
p2.age = 18
fmt.Println(p2)
// 方法三
p3 := Person{
Name: "jack",
age: 23,
}
fmt.Println(p3)
// 方法四
p4 := Person{
"lili",
24,
}
fmt.Println(p4)
}
运行结果
{tom 20}
{marry 18}
{jack 23}
{lili 24}
二、结构体的使用
1、结构体指针
数据类型:
- 值类型:int,string,bool,float,arry,struct
- 引用类型:slice,map,function,pointer,chan
package main
import "fmt"
// Person 一个结构体,包含name, age两个字段
type Person struct {
Name string // 包外可访问
age int // 包外不可访问
}
func main() {
p := Person{
Name: "tom",
age: 20,
}
// 方法一 直接创建结构体指针
var ptr1 *Person
fmt.Printf("%T, %v\n", ptr1, ptr1)
ptr1 = &p
// (*ptr1).Name = "jack"
ptr1.Name = "jack"
fmt.Println(p)
// 方法二 使用new创建结构体指针
ptr2 := new(Person)
fmt.Printf("%T, %v\n", ptr2, ptr2)
ptr2 = &p
ptr2.Name = "lili"
fmt.Println(p)
// 方法三 使用&地址符创建结构体指针
ptr3 := &Person{}
fmt.Printf("%T, %v\n", ptr3, ptr3)
ptr3 = &p
ptr3.Name = "marry"
fmt.Println(p)
}
运行结果
*main.Person, <nil>
{jack 20}
*main.Person, &{ 0}
{lili 20}
*main.Person, &{ 0}
{marry 20}
2、匿名结构体
没有名字的结构体,在创建结构体的时候同时创建对象。
package main
import "fmt"
func main() {
p := struct {
name string
age int
}{
name: "tom",
age: 20,
}
fmt.Println(p)
}
运行结果
{tom 20}
3、匿名字段
在结构体中,不写字段的名字,只写类型。匿名字段不允许重复。
package main
import "fmt"
type worker struct {
string
int
}
func main() {
w := worker{
"tom",
20,
}
fmt.Println(w)
fmt.Println(w.string, w.int)
}
运行结果
{tom 20}
tom 20
4、结构体嵌套
一个结构体中的字段,是另一个结构体。
package main
import "fmt"
// Address 结构体
type Address struct {
Province string
City string
}
// User 结构体
type User struct {
Name string
Age int
// Address Address
Address //嵌套匿名字段
}
func main() {
u := User{
Name: "tom",
Age: 20,
Address: Address{
Province: "江苏省",
City: "苏州市",
},
}
fmt.Printf("%+v\n", u)
}
运行结果
{Name:tom Age:20 Address:{Province:江苏省 City:苏州市}}
嵌套结构体字段访问
- 在结构体中属于匿名结构体的字段称为提升字段,因为它们可以被访问,就好像它们属于拥有匿名结构字段的结构一样。
- 嵌套结构体内部可能存在相同的字段名。在这种情况下为了避免歧义需要通过指定具体的内嵌结构体字段名。
package main
import "fmt"
// Address 结构体
type Address struct {
Province string
City string
CreateTime string
}
// Email 结构体
type Email struct {
Email string
CreateTime string
}
// User 结构体
type User struct {
Name string
Age int
// Address Address
Address
Email
}
func main() {
u := User{
Name: "tom",
Age: 20,
Address: Address{
Province: "江苏省",
City: "苏州市",
CreateTime: "2020.09.26",
},
Email: Email{
Email: "tom@github.com.cn",
CreateTime: "2020.09.25",
},
}
fmt.Printf("%+v\n", u)
fmt.Println(u.City) //City是提升字段
fmt.Println(u.Email) //Email是提升字段
fmt.Println(u.Email.CreateTime)
fmt.Println(u.Address.CreateTime)
}
运行结果
{Name:tom Age:20 Address:{Province:江苏省 City:苏州市 CreateTime:2020.09.26} Email:{Email:tom@github.com.cn CreateTime:2020.09.25}}
苏州市
{tom@github.com.cn 2020.09.25}
2020.09.25
2020.09.26
5、结构体与json序列化
go语言通过json包下的Marshal(v interface{}) ([]byte, error)
函数实现结构体对象的json序列化。
通过json包下的Unmarshal(data []byte, v interface{}) error
函数实现json的反序列化。
package main
import (
"encoding/json"
"fmt"
)
// Student 结构体
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
}
func main() {
s1 := Student{
Name: "tom",
Age: 20,
Address: "苏州市",
}
data, err := json.Marshal(s1)
if err != nil {
fmt.Println("json.marshal failed, err=", err)
return
}
fmt.Printf("json:%s\n", data)
str := `{"name":"jack","age":21,"address":"上海市"}`
s2 := new(Student)
err = json.Unmarshal([]byte(str), s2) // s2需要传入一个指针
if err != nil {
fmt.Println("json.unmarshal failed, err=", err)
return
}
fmt.Printf("%+v\n", s2)
}
运行结果
json:{"name":"tom","age":20,"address":"苏州市"}
&{Name:jack Age:21 Address:上海市}
三、构造函数
Go语言的结构体没有构造函数,我们可以自己实现。
package main
import "fmt"
// Student 结构体
type Student struct {
Name string
Age int
Address string
}
// NewStudent 构造函数
func NewStudent(name string, age int, address string) *Student {
return &Student{
name,
age,
address,
}
}
func main() {
s := NewStudent("lili", 20, "上海市")
fmt.Println(s)
}
运行结果
&{lili 20 上海市}
四、方法
Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。接收者的概念就类似于其他语言中的this或者 self。
func (t type) methodName(parameter list) (return list) {
}
其中
-
t
接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名称首字母的小写,而不是self、this之类的命名。例如,Person类型的接收者变量应该命名为 p,Connector类型的接收者变量应该命名为c等。 -
type
接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。 - 方法名、参数列表、返回参数:具体格式与函数定义相同。
package main
import "fmt"
// Student 结构体
type Student struct {
Name string
Age int
Address string
}
// NewStudent 构造函数
func NewStudent(name string, age int, address string) *Student {
return &Student{
name,
age,
address,
}
}
// Learn 结构体Student的方法
func (s Student) Learn(class string) {
fmt.Printf("%v正在努力学习%v课\n", s.Name, class)
}
func main() {
s := NewStudent("lili", 20, "上海市")
fmt.Println(s)
s.Learn("english")
}
运行结果
&{lili 20 上海市}
lili正在努力学习english课
方法与函数的区别是,函数不属于任何类型,方法属于特定的类型。
+ 指针类型的接受者
指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。
package main
import "fmt"
// Student 结构体
type Student struct {
Name string
Age int
Address string
}
// SetAge 结构体Student的方法
func (s *Student) SetAge(age int) {
s.Age = age
}
func main() {
s := Student{
"lili",
20,
"苏州市",
}
fmt.Println(s)
s.SetAge(18)
fmt.Println(s)
}
运行结果
{lili 20 苏州市}
{lili 18 苏州市}
+ 值类型接受者
当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。
package main
import "fmt"
// Student 结构体
type Student struct {
Name string
Age int
Address string
}
// SetAddress 结构体Student的方法
func (s Student) SetAddress(address string) {
s.Address = address
}
func main() {
s := Student{
"lili",
20,
"苏州市",
}
fmt.Println(s)
s.SetAddress("上海市")
fmt.Println(s)
}
运行结果
{lili 20 苏州市}
{lili 20 苏州市}
+ 什么时候应该使用指针类型接收者
- 需要修改接收者中的值
- 接收者是拷贝代价比较大的大对象
- 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。