1.自定义类型

Go语言中可以使用type关键字来定义自定义类型。

  1. type MyInt int
  2. func main() {
  3. var a MyInt=100
  4. fmt.Printf("%T",a) //main.MyInt
  5. }
  6. //通过type关键字的定义,MyInt就是一种新的类型,它具有int的特性

2.类型别名

我们之前见过的rune和byte就是类型别名,他们的定义如下

  1. type TypeAlias = Type
  1. type byte = uint8
  2. type rune = int32
  1. type NewInt int //类型定义
  2. type MyInt = int //类型别名
  3. func main() {
  4. var a NewInt
  5. var b MyInt
  6. fmt.Printf("%T\n",a) //main.NewInt
  7. fmt.Printf("%T\n",b) //int
  8. }

a的类型是main.NewInt,表示main包下定义的NewInt类型。
b的类型是int。MyInt类型只会在代码中存在,编译完成时并不会有MyInt类型。

3.结构体的实例化

3.1结构体的定义

使用type和struct关键字来定义结构体

  1. type 类型名 struct {
  2. 字段名 字段类型
  3. 字段名 字段类型
  4. }
  1. type person struct {
  2. name string
  3. city string
  4. age int8
  5. }
  1. type person struct {
  2. name,city string
  3. age int8
  4. }

3.2基本实例化

  1. type person struct {
  2. name,city string
  3. age int8
  4. }
  5. func main() {
  6. var p person
  7. p.name = "cheng"
  8. p.city = "北京"
  9. p.age = 18
  10. fmt.Println(p) //{cheng 北京 18}
  11. fmt.Printf("%v\n",p) //{cheng 北京 18}
  12. fmt.Printf("%#v",p) //main.person{name:"cheng", city:"北京", age:18}
  13. }
  1. type person struct {
  2. name,city string
  3. age int8
  4. }
  5. func main() {
  6. p :=person{name: "cheng",city: "汕头",age: 18}
  7. fmt.Println(p)
  8. }
  1. p :=person{}
  2. p.name = "cheng"
  3. p.city = "北京"
  4. p.age = 18
  5. fmt.Println(p)

3.3匿名结构体

  1. var user struct{Name string;Age int}
  2. user.Name = "cheng"
  3. user.Age = 18
  4. fmt.Println(user)

3.4指针结构体

  1. type person struct {
  2. name,city string
  3. age int8
  4. }
  5. func main() {
  6. var p = new(person)
  7. fmt.Printf("%T\n",p) //*main.person
  8. fmt.Printf("%v\n",*p) //{ 0}
  9. fmt.Printf("%v\n",p) //&{ 0}
  10. }
  1. var p = new(person)
  2. p.name = "cheng"
  3. p.city = "汕头"
  4. p.age = 18
  5. fmt.Println(p) //&{cheng 汕头 18}
  6. fmt.Println(*p) //{cheng 汕头 18}
  7. fmt.Printf("%#v",p) //&main.person{name:"cheng", city:"汕头", age:18}

3.5地址实例化

使用&对结构体进行取地址操作相当于对该结构体类型进行了一次new实例化操作

  1. p1 :=&person{}
  2. p2 := new(person)
  3. fmt.Println(p1) //&{ 0}
  4. fmt.Println(p2) //&{ 0}
  5. p1.name="cheng"
  6. p1.age = 18
  7. p1.city = "广州"
  8. fmt.Println(p1) //&{cheng 广州 18}

4.结构体初始化

没有初始化的结构体,其成员变量都是对应其类型的零值

  1. var p person
  2. fmt.Printf("%#v",p) //main.person{name:"", city:"", age:0}

4.1使用键值对初始化

  1. p := person{
  2. name:"cheng",
  3. age:18,
  4. city:"汕头",
  5. }
  6. fmt.Println(p)
  1. p := &person{
  2. name:"cheng",
  3. city:"深圳",
  4. age:18,
  5. }
  6. fmt.Println(p)

4.2使用值的列表初始化

  1. p := &person{
  2. "cheng",
  3. "深圳",
  4. 18,
  5. }
  6. fmt.Println(p)
  1. p := person{
  2. "cheng",
  3. "深圳",
  4. 18,
  5. }
  6. fmt.Println(p)

使用这种格式初始化时,需要注意:

  1. 必须初始化结构体的所有字段。
  2. 初始值的填充顺序必须与字段在结构体中的声明顺序一致。
  3. 该方式不能和键值初始化方式混用。

4.3结构体内存布局

  1. //结构体占用一块连续的内存
  2. type test struct {
  3. a int8
  4. b int8
  5. c byte
  6. d byte
  7. }
  8. func main() {
  9. n :=test{
  10. 1,2,3,4,
  11. }
  12. fmt.Printf("%p\n",&n.a) //0xc00000a088
  13. fmt.Printf("%p\n",&n.b) //0xc00000a089
  14. fmt.Printf("%p\n",&n.c) //0xc00000a08a
  15. fmt.Printf("%p\n",&n.d) //0xc00000a08b
  16. }
  1. //空结构体是不占用空间的
  2. var v struct{}
  3. func main() {
  4. fmt.Println(unsafe.Sizeof(v))
  5. }

5.构造函数

Go语言的结构体没有构造函数,我们可以自己实现。
struct是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大
所以该构造函数返回的是结构体指针类型

  1. type person struct {
  2. name string
  3. city string
  4. age int8
  5. }
  6. func newPerson(name,city string,age int8) *person{
  7. return &person{
  8. name: name,
  9. city: city,
  10. age: age,
  11. }
  12. }
  1. p :=newPerson("cheng","汕头",18)
  2. fmt.Println(p)

6.方法和接收者

Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。
接收者的概念就类似于其他语言中的this或者 self

6.1方法

  1. //结构体
  2. type Person struct {
  3. name string
  4. age int8
  5. }
  6. //构造函数
  7. func NewPerson(name string, age int8) *Person {
  8. return &Person{
  9. name:name,
  10. age:age,
  11. }
  12. }
  13. //方法
  14. func (p Person) Eat() {
  15. fmt.Println("吃饭")
  16. }
  17. func main() {
  18. p :=NewPerson("cheng",20)
  19. p.Eat()
  20. }

方法与函数的区别是,函数不属于任何类型,方法属于特定的类型

6.2指针类型的接收者

指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其他语言中面向对象中的this或者self

  1. func (p *Person) SetAge(newAge int8) {
  2. p.age = newAge
  3. }
  1. p :=NewPerson("cheng",20)
  2. fmt.Println(p)
  3. p.SetAge(18)
  4. fmt.Println(p)

6.3值类型的接收者

当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。

  1. func (p Person) SetAge(age int8) {
  2. p.age = age
  3. }
  4. func main() {
  5. p :=NewPerson("cheng",20)
  6. p.Eat()
  7. fmt.Println(p.age) //20
  8. p.SetAge(18)
  9. fmt.Println(p.age) //20
  10. }

7.任意类型添加方法

int类型使用type关键字可以定义新的自定义类型,然后为我们的自定义类型添加方法

  1. type MyInt int
  2. func (m MyInt) sayHello() {
  3. fmt.Println("HelloMyInt")
  4. }
  5. func main() {
  6. var my MyInt
  7. my.sayHello() //HelloMyInt
  8. my = 100
  9. fmt.Printf("%#v %T\n",my,my) //100 main.MyInt
  10. }

8.结构体的匿名字段

结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段

  1. type Person struct {
  2. string
  3. int
  4. }
  5. func main() {
  6. p := Person{
  7. "cheng",
  8. 18,
  9. }
  10. fmt.Println(p) //{cheng 18}
  11. fmt.Println(p.int) //18
  12. fmt.Println(p.string) //cheng
  13. }

9.嵌套结构体

一个结构体中可以嵌套包含另一个结构体或结构体指针

  1. //地址结构体
  2. type Address struct {
  3. Province string
  4. City string
  5. }
  6. //用户结构体
  7. type User struct {
  8. Name string
  9. Gender string
  10. Address Address
  11. }
  12. func main() {
  13. user :=User{
  14. Name: "cheng",
  15. Gender: "男",
  16. Address: Address{
  17. Province: "广东",
  18. City: "汕头",
  19. },
  20. }
  21. fmt.Println(user)
  22. fmt.Printf("%#v\n",user)
  23. }

嵌套匿名字段

  1. //地址结构体
  2. type Address struct {
  3. Province string
  4. City string
  5. }
  6. //用户结构体
  7. type User struct {
  8. Name string
  9. Gender string
  10. Address //匿名字段
  11. }
  12. func main() {
  13. var user User
  14. user.Name = "cheng"
  15. user.Gender= "男"
  16. user.Address.Province = "广东" // 匿名字段默认使用类型名作为字段名
  17. user.City="汕头" // 匿名字段可以省略
  18. fmt.Println(user)
  19. }

嵌套结构体的字段名冲突

  1. //地址结构体
  2. type Address struct {
  3. Province string
  4. City string
  5. CreateTime string
  6. }
  7. type Email struct {
  8. Account string
  9. CreateTime string
  10. }
  11. //用户结构体
  12. type User struct {
  13. Name string
  14. Gender string
  15. Address //匿名字段
  16. Email
  17. }
  18. func main() {
  19. var user User
  20. user.Name = "cheng"
  21. user.Gender="男"
  22. //user.CreateTime="2021"
  23. user.Address.CreateTime = "2020"
  24. user.Email.CreateTime="2020"
  25. fmt.Println(user)
  26. }

10.结构体的继承

使用结构体也可以实现其他编程语言中面向对象的继承

  1. type Animal struct {
  2. name string
  3. }
  4. func (a *Animal) move() {
  5. fmt.Printf("%s移动\n",a.name)
  6. }
  7. type Dog struct {
  8. Age int8
  9. Animal
  10. }
  11. func (d *Dog) wang() {
  12. fmt.Printf("%s汪汪汪\n",d.name)
  13. }
  14. func main() {
  15. d :=Dog{
  16. Age: 10,
  17. Animal:Animal{
  18. name: "狗",
  19. },
  20. }
  21. d.wang()
  22. d.move()
  23. fmt.Println(d)
  24. }
  1. type Animal struct {
  2. name string
  3. }
  4. func (a *Animal) move() {
  5. fmt.Printf("%s移动\n",a.name)
  6. }
  7. type Dog struct {
  8. Age int8
  9. *Animal
  10. }
  11. func (d *Dog) wang() {
  12. fmt.Printf("%s汪汪汪\n",d.name)
  13. }
  14. func main() {
  15. d :=&Dog{
  16. Age: 10,
  17. Animal:&Animal{
  18. name: "狗",
  19. },
  20. }
  21. d.wang()
  22. d.move()
  23. fmt.Println(d)
  24. }

11.结构体字段的可见性

结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。

12.JSON序列化

  1. type Student struct {
  2. ID int
  3. Gender string
  4. Name string
  5. }
  6. type Class struct {
  7. Title string
  8. Students []*Student
  9. }
  10. func main() {
  11. c :=&Class{
  12. Title: "110",
  13. Students: make([]*Student,0,200),
  14. }
  15. for i := 0; i < 10; i++ {
  16. stu :=&Student{
  17. Name: fmt.Sprintf("stu%02d",i),
  18. Gender: "男",
  19. ID: i,
  20. }
  21. c.Students = append(c.Students,stu)
  22. }
  23. fmt.Println(c)
  24. //JSON序列化:结构体-->JSON格式的字符串
  25. data,err:=json.Marshal(c)
  26. if err!=nil{
  27. fmt.Println("json marshal failed")
  28. return
  29. }
  30. fmt.Printf("json:%s\n",data)
  31. //JSON反序列化:JSON格式的字符串-->结构体
  32. str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"Gender":"男","Name":"stu01"},
  33. {"ID":2,"Gender":"男","Name":"stu02"},{"ID":3,"Gender":"男","Name":"stu03"},{"ID":4,"Gender":"男","Name":"stu04"},
  34. {"ID":5,"Gender":"男","Name":"stu05"},{"ID":6,"Gender":"男","Name":"stu06"},{"ID":7,"Gender":"男","Name":"stu07"},
  35. {"ID":8,"Gender":"男","Name":"stu08"},{"ID":9,"Gender":"男","Name":"stu09"}]}`
  36. class :=&Class{}
  37. err = json.Unmarshal([]byte(str), class)
  38. if err!=nil {
  39. fmt.Println("json unmarshal failed!!!")
  40. return
  41. }
  42. fmt.Printf("%#v\n",class)
  43. }
  1. type Student struct {
  2. ID int
  3. Gender string
  4. Name string
  5. }
  6. type Class struct {
  7. Title string
  8. Students []Student
  9. }
  10. func main() {
  11. c:=Class{
  12. Title: "101",
  13. Students: make([]Student,0,200),
  14. }
  15. for i := 0; i < 10; i++ {
  16. stu := Student{
  17. Name: fmt.Sprintf("stu%02d",i),
  18. Gender: "男",
  19. ID: i,
  20. }
  21. c.Students = append(c.Students,stu)
  22. }
  23. //JSON序列化:结构体-->JSON格式的字符串
  24. data, err := json.Marshal(c)
  25. if err!=nil {
  26. fmt.Println("json marshal failed")
  27. return
  28. }
  29. fmt.Printf("%s\n",data)
  30. //JSON反序列化:JSON格式的字符串-->结构体
  31. str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"Gender":"男","Name":"stu01"},
  32. {"ID":2,"Gender":"男","Name":"stu02"},{"ID":3,"Gender":"男","Name":"stu03"},{"ID":4,"Gender":"男","Name":"stu04"},
  33. {"ID":5,"Gender":"男","Name":"stu05"},{"ID":6,"Gender":"男","Name":"stu06"},{"ID":7,"Gender":"男","Name":"stu07"},
  34. {"ID":8,"Gender":"男","Name":"stu08"},{"ID":9,"Gender":"男","Name":"stu09"}]}`
  35. class:=&Class{}
  36. error := json.Unmarshal([]byte(str), class)
  37. if error!=nil {
  38. fmt.Println("json unmarshal failed!!!")
  39. return
  40. }
  41. fmt.Println(class)
  42. }

13.结构体标签(Tag)

Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。
Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:

  1. `key1:"value1" key2:"value2"`
  1. type Student struct {
  2. ID int `json:"id"` //通过指定tag实现json序列化该字段时的key
  3. Gender string //json序列化是默认使用字段名作为key
  4. name string //私有不能被json包访问
  5. }
  6. func main() {
  7. s:=Student{
  8. ID: 1,
  9. Gender: "男",
  10. name: "cheng",
  11. }
  12. data, err := json.Marshal(s)
  13. if err != nil {
  14. fmt.Println("json marshal failed!!!")
  15. return
  16. }
  17. fmt.Printf("%s\n",data)
  18. }

结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误。

14.结构体和方法补充知识点

因为slice和map这两种数据类型都包含了指向底层数据的指针,因此我们在需要复制它们时要特别注意。

  1. type Person struct {
  2. name string
  3. age int8
  4. dreams []string
  5. }
  6. func (p *Person) SetDreams(dreams []string) {
  7. p.dreams=dreams
  8. }
  9. func main() {
  10. p := Person{name:"cheng",age:18}
  11. data := []string{"Java","Golang","Python"}
  12. p.SetDreams(data)
  13. fmt.Println(p.dreams) //[Java Golang Python]
  14. data[0]="C#"
  15. fmt.Println(p.dreams) //[C# Golang Python]
  16. }
  1. func (p *Person) SetDreams(dreams []string) {
  2. //p.dreams=dreams
  3. p.dreams = make([]string,len(dreams))
  4. copy(p.dreams,dreams)
  5. }
  6. p := Person{name:"cheng",age:18}
  7. data := []string{"Java","Golang","Python"}
  8. p.SetDreams(data)
  9. fmt.Println(p.dreams) //[Java Golang Python]
  10. data[0]="C#"
  11. fmt.Println(p.dreams) //[Java Golang Python]

同样的问题也存在于返回值slice和map的情况,在实际编码过程中一定要注意这个问题