Go语言结构体

「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战」。

1 类型别名和自定义类型

1.1 自定义类型

在Go语言中有一些基本的数据类型,如string、整型、浮点型、布尔等数据类型, Go语言中可以使用type关键字来定义自定义类型。

自定义类型是定义了一个全新的类型。我们可以基于内置的基本类型定义,也可以通过struct定义。例如:

//将MyInt定义为int类型
type MyInt int
复制代码

通过type关键字的定义,MyInt就是一种新的类型,它具有int的特性

1.2 类型别名

类型别名是Go1.9版本添加的新功能。

类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型。就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。

 type TypeAlias = Type
复制代码

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

 type byte = uint8
 type rune = int32
复制代码

1.3 类型定义和类型别名的区别

类型别名与类型定义表面上看只有一个等号的差异,我们通过下面的这段代码来理解它们之间的区别。

package main
 import (
 	"fmt"
 	"reflect"
 )
 //类型定义
 type NewInt int
 //类型别名
 type MyInt = int
 func main() {
 	var a NewInt
 	var b MyInt
 	fmt.Printf("变量a的类型是:%T\n", a) //变量a的类型是:main.NewInt
 	fmt.Printf("变量b的类型是:%T\n", b) //变量b的类型是:int
 	//判断a和b类型是否一样
 	fmt.Println(reflect.TypeOf(a).Kind() == reflect.TypeOf(b).Kind()) //true
 }
复制代码

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

2 结构体

::: tip 概述
Go语言中的基础数据类型可以表示一些事物的基本属性,但是当我们想表达一个事物的全部或部分属性时,这时候再用单一的基本数据类型明显就无法满足需求了,Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct。 也就是我们可以通过struct来定义自己的类型了。类似于java中的class
:::

2.1 结构体的定义语法

使用type和struct关键字来定义结构体,具体代码格式如下:

type 类型名 struct {
    字段名 字段类型
    字段名 字段类型
    ……
}
复制代码

::: danger 注意

  • 类型名:标识自定义结构体的名称,在同一个包内不能重复。
  • 字段名:表示结构体字段名。结构体中的字段名必须唯一。
  • 字段类型:表示结构体字段的具体类型。

:::

2.2 结构体定义的三种形式

第一种【基本的实例化】

var stu Student
复制代码

第二种【指针类型的结构体】

var stu *Student = new(Student)
复制代码

第三种【取结构体的地址实例化,通过&的操作】

var stu *Student = &Student{}
复制代码

2.3 结构体实例化

举例如下

//学生
type Student struct {
  Name string   //姓名
  Age int       //年龄
  Sex bool      //性别 true:男    false:女
}
复制代码

2.3.1 基本的实例化

//定义一个变量stu 
var stu Student
//没有初始化的结构体,其成员变量都是对应其类型的零值。
fmt.Println(stu) //{ 0 false}
stu.Name="张三"    //赋值
stu.Age=18
stu.Sex=true
fmt.Println(stu) //{张三 18 true}
fmt.Println(stu.Age)   //访问字段
fmt.Printf("%T",stu)  //main.Student
复制代码

2.3.2 键值对初始化结构体

键值之间以:分隔;键值对之间以,分隔

::: tip 键值对语法如下

变量名 := 结构体类型名{
    字段1: 字段1的值,
    字段2: 字段2的值,
    ......
}
复制代码

:::

键值对初始化结构体举例

stu3 := Student{
  Name: "李四",
  Age: 18}
复制代码

2.3.3 值列表填充结构体

值列表填充结构体【没有字段,按着序列,必须全部填充】
::: tip 值列表填充结构体语法如下

变量名 := 结构体类型名{
    字段1的值,
    字段2的值,
    ......
}
复制代码

:::
值列表填充结构体举例如下

stu4 := Student{
  "王五",
  18,
  true,
}
复制代码

3 匿名结构体

在定义一些临时数据结构等场景下还可以使用匿名结构体。

::: tip 语法如下

//注意:匿名结构体这里是var声明变量, 普通结构体是type在函数外部
var 类型名 struct {
    字段名 字段类型
    字段名 字段类型
    ……
}
复制代码

:::
举例如下

// 匿名结构体定义
var user struct{
   name string
   age int
   address string
}
user.name="福小林"
user.age=29
user.address="成都"
fmt.Println(user) //{福小林 29 成都}
fmt.Printf("%T\n", user) //struct { name string; age int; address string }
复制代码

4 结构体指针

4.1 创建指针类型结构体

需要注意的是在Go语言中支持对结构体指针直接使用.来访问结构体的成员。。

var 变量名 = new(结构名) 
复制代码
package main

import "fmt"
//学生
type Student struct {
	Name string   //姓名
	Age int       //年龄
	Sex bool      //性别 true:男    false:女
}
func main() {
	//创建指针类型的机构体变量
var stu1  = new(Student) //*Student
stu1.Name = "李四" //使用结构体指针访问结构体成员,使用 “.” 操作符。
stu1.Age = 20 //等价于(*stu1).Age=20   Go语言的语法糖,可以使用 “.” 操作结构体成员
stu1.Sex = false
fmt.Println(stu1)
fmt.Printf("%T\n", stu1)     //*main.Student
fmt.Printf("stu1=%v\n", stu1) //stu1=&{李四 20 false}
fmt.Printf("%p\n", stu1)     //stu1保存的值是一个内存地址 0xc0000044c0
fmt.Printf("%p\n", &stu1)     //stu1的内存地址0xc000006028
}
复制代码

4.2 取结构体的地址实例化

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

stu3 := &Student{}
fmt.Printf("%T\n", stu3)     //*main.Student
fmt.Printf("stu3=%#v\n", stu3) //stu3=&main.Student{Name:"", Age:0, Sex:false}
stu3.Name="福小林"
stu3.Age=30
stu3.Sex=true
fmt.Printf("stu3=%v\n", stu3) //stu3=&{福小林 30 true}
复制代码

5 结构体作为函数参数

func 函数名([结构体变量、结构体指针变量]){
    //函数体
}
复制代码

举例如下

//参数是结构体变量
func (stu Student) sayHi() {
	fmt.Println(stu.Name, "Hello")
}

//参数是结构体指针变量
func printStudent(stu *Student) {
	fmt.Println(stu.Name, stu.Age, stu.Sex)
}
复制代码

6 构造函数

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

//学生
type Student struct {
	Name string   //姓名
	Age int       //年龄
	Sex bool      //性别 true:男    false:女
}

func newStudent(Name string,Age int,Sex bool) *Student{

	return &Student{
		Name:Name,
		Age: Age,
		Sex:  Sex,
	}
}
func main() {
    //调用构造函数赋值
	stu5:=newStudent("张三",18,true)
	fmt.Println(stu5)
	fmt.Printf("%p\n", &stu5.Age)
	fmt.Println(stu5)
}
复制代码

7 结构体的匿名字段

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

//Person 结构体Person类型
type Person struct {
	string
	int
}

func main() {
	p1 := Person{
		"福小林",
		18,
	}
	fmt.Printf("%#v\n", p1)        //main.Person{string:"福小林", int:18}
	fmt.Println(p1.string, p1.int) //福小林 18
}
复制代码

匿名字段默认采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。

8 嵌套结构体

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

package main

import "fmt"

//Address 地址结构体
type Address struct {
	Province string
	City     string
}

//User 用户结构体
type User struct {
	Name    string
	Gender  string
	Address Address
}

//company 公司结构体
type company struct {
	Name    string
	Address Address
}

func main() {
	user1 := User{
		Name:   "刘强东",
		Gender: "男",
		Address: Address{
			Province: "江苏",
			City:     "宿迁",
		},
	}
	fmt.Printf("user1=%#v\n", user1)

	company1 := company{
		Name:   "阿里巴巴公司",
		Address: Address{
			Province: "浙江",
			City:     "杭州",
		},
	}
	fmt.Printf("company1=%#v\n", company1)
}
复制代码

9 嵌套匿名结构体

package main

import "fmt"

//Address 地址结构体
type Address struct {
	Province string
	City     string
}

//User 用户结构体
type User struct {
	Name    string
	Gender  string
	Address //匿名字段
}

//company 公司结构体
type company struct {
	Name    string
	Address
}


func main() {
	user1 := User{
		Name:   "刘强东",
		Gender: "男",
		Address: Address{
			Province: "江苏",
			City:     "宿迁",
		},
	}
	fmt.Printf("user1=%#v\n", user1)
	//嵌套结构体访问属性
	fmt.Println(user1.Address.City)
	//匿名嵌套结构体访问属性简写
	fmt.Println(user1.City)
}
复制代码

当访问结构体成员时会先在结构体中查找该字段,找不到再去匿名结构体中查找。

10 嵌套结构体的字段名冲突

嵌套结构体内部可能存在相同的字段名。这个时候为了避免歧义需要指定具体的内嵌结构体的字段。

//Address 地址结构体
type Address struct {
	Province   string
	City       string
	CreateTime string
}

//Email 邮箱结构体
type Email struct {
	Account    string
	CreateTime string
}

//User 用户结构体
type User struct {
	Name   string
	Gender string
	Address
	Email
}

func main() {
	var user3 User
	user3.Name = "福小林"
	user3.Gender = "男"
	// user3.CreateTime = "2019" //ambiguous selector user3.CreateTime
	user3.Address.CreateTime = "2000" //指定Address结构体中的CreateTime
	user3.Email.CreateTime = "2000"   //指定Email结构体中的CreateTime
}
复制代码

11 结构体与JSON序列化

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。JSON键值对是用来保存JS对象的一种方式,键/值对组合中的键名写在前面并用双引号""包裹,使用冒号:分隔,然后紧接着值;多个键值之间使用英文,分隔。

package main
 
import (
	"encoding/json"
	"fmt"
)
 
//属性必须大写,不然json解析不出来
type Person struct {
	Name string
	Age int
	Sex string
	Hobby []string
}
 
var wek,alice Person
func init() {
	//初始化两个结构体
	wek = Person{"wek",18,"男",[]string{"alice","play","code"}}
	alice = Person{"alice",17,"女",[]string{"wek","study","teacher"}}
}
 
func main() {
	//序列化wek结构体
	wekBytes, e := json.Marshal(wek)
	if e!=nil{
		fmt.Println("结构体序列化失败!")
	}
	//打印wek序列化结构体
	fmt.Println("序列化结果为:",wekBytes)
	fmt.Println("序列化结果转化为string后为:",string(wekBytes))
	//序列化alice结构体
	aliceBytes, e := json.Marshal(alice)
	if e!=nil{
		fmt.Println("结构体序列化失败!")
	}
	//打印alice序列化结果
	fmt.Println("序列化结果为:",aliceBytes)
	fmt.Println("序列化结果转化为string后为:",string(aliceBytes))
}
复制代码

输出结果

序列化结果为: [123 34 78 97 109 101 34 58 34 119 101 107 34 44 34 65 103 101 34 58 49 56 44 34 83 101 120 34 58 34 231 148 183 34 44 34 72 111 98 98 121 34 58 91 34 97 108 105 99 101 34 44 34 112 108 97 121 34 44 34 99 111 100 101 34 93 125]
序列化结果转化为string后为: {"Name":"wek","Age":18,"Sex":"男","Hobby":["alice","play","code"]}
序列化结果为: [123 34 78 97 109 101 34 58 34 97 108 105 99 101 34 44 34 65 103 101 34 58 49 55 44 34 83 101 120 34 58 34 229 165 179 34 44 34 72 111 98 98 121 34 58 91 34 119 101 107 34 44 34 115 116 117 100 121 34 44 34 116 101 97 99 104 101 114 34 93 125]
序列化结果转化为string后为: {"Name":"alice","Age":17,"Sex":"女","Hobby":["wek","study","teacher"]}
复制代码

12 结构体标签(Tag)

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

::: danger 总结

  • golang是非面向对象语言,也可以说go语言中的结构体类似java中的类,但是很明显缺少继承多态等等OO的特性
  • 指针变量通过.访问结构体成员,如果是C或者C++一定要通过*访问,这是Go对它的一个优化

:::