Go语言基础之结构体

Go语言基础之结构体

前言

Hey,大家好呀,我是星期八,终于迎来了Go中最大一个知识点,结构体

在Go,是没有类和对象这个概念的。

是通过结构体的各种操作,模拟出来的像Java,Python之类的面向对象的。

回忆map

之前我们讲过,Go语言的map是键值对的方式存储数据的,就像这样的。

//方式一
var student = map[string]string{
    "Name": "张三""Age":  "18"}
//方式二
var student2 = make(map[string]string10)
student2["Name"] = "张三"
student2["Age"] = "18"

但是这样,似乎有一个弊端,我不知道我有几个key,并且value类型是固定的。

理论来说,key Age 对应的value应该是int类型,还有一些其他微妙的问题,通过map都是不太好解决的。

因为解决这些问题,所以,又引出了结构体这个类型。

前戏补充

在开始结构体之前呢,先看两个奇怪的知识点。

自定义类型

代码

type 自定义类型名 类型名
例:
type NewInt int

完整代码

package main

import "fmt"

type NewInt int

func main() {
	var n1 NewInt = 1
	fmt.Println(n1)//结果为1
}

如果要是理解的话,可以理解为NewInt包含了int的功能。

这里可以把NewInt当作int来使用。

注:NewInt是一个新的类型,它包含int,并不等于int

类型别名

代码

type 类型别名 = 类型名
例:
type Nint = int

完整代码

package main

import "fmt"

type Nint = int

func main() {
	var n1 Nint = 1
	fmt.Println(n1)//1
}

自定义类型和类型别名区别

可能猛一看,感觉自定义类型和类型别名似乎一样,但是其实是不太一样的。

代码

package main

import "fmt"

type Nint1 int   //自定义类型
type Nint2 = int //类型别名

func main() {
	var n1 Nint1 = 1
	var n2 Nint2 = 1
	fmt.Printf("n1类型:%T,n2类型:%T", n1, n2)
}

执行结果。

在这里插入图片描述

**结论:**自定义类型真的是自定义类型,类型都变了,类型别名只是类型名变了,但是本质没变。

结构体

Go语言的结构体,相当JavaPython等语言中的类,已经不再是简简单单的结构体那么简单了。

结构体属于基本数据类型

内存图大概如下

在这里插入图片描述

结构体定义

定义结构体需要用到关键字typestruct

语法

type 结构体名 struct {
	字段1 字段类型1
    字段2 字段类型2
    ...
}

示例,通过结构体描述一个学生。

type Student struct {
	Name   string
	Age    int
	Height int
	Weight int
	phone string
}

**注:**如果字段类型是相同的,可以写在同一行。

type Student struct {
	Name                string
	Age, Height, Weight int
	phone               string
}

结构体初始化

方式一,赋值时初始化
func main() {
	var s1 = Student{
		Name:   "张三",
		Age:    18,
		Height: 180,
		Weight: 120,
		phone:  "6666666"}
	fmt.Println(s1)
}
方式二,先声明,后赋值
func main() {
	var s1 Student
	s1.Name = "张三"
	s1.Age = 18
	s1.Height = 180
	s1.Weight = 120
	s1.phone = "66666"
}

两个执行结果。

在这里插入图片描述

匿名结构体

有时候我们的函数可能会要求传入一个结构体,但是你又不想定义,就想临时用一下,赶紧传参得了。

这时候可以考虑匿名结构体。

方式一,先声明,后赋值
func main() {
	var car struct {
		Name   string;
		CarNum string
	}
	car.Name = "QQ"
	car.CarNum = "京6666"
	fmt.Println(car) //{QQ 京6666}
}
方式二,声明+初始化
func main() {
	var car = struct {
		Name   string;
		CarNum string
	}{
		Name:   "QQ",
		CarNum: "京6666"}

	fmt.Println(car) //{QQ 京6666}
}

两个执行结果。

在这里插入图片描述

通过&方式初始化结构体

通过&的方式初始化,性能会提高一点,因为返回的是第一个的指针

但是操作过程跟上述一样,Go已经封装好了。

代码

func main() {
	//方式一,等于一个空&结构体在赋值
	var s1 = &Student{}
	s1.Name = "张三"
	//...
	//方式二,直接赋值
	var s2 = &Student{
		Name:   "",
		Age:    0,
		Height: 0,
		Weight: 0,
		phone:  ""}
	//方式三不可以
	//var s3 &Student//error
}

使用&的方式基本跟原来一样,但是方式三不行。

&初始化结构体函数注意事项

如果使用&的方式,那函数参数也要变一下的。

package main

import "fmt"

type Student struct {
	Name                string
	Age, Height, Weight int
	phone               string
}

func sayStudent1(s Student) {
	fmt.Println(s)
}
func sayStudent2(s *Student) {
	//如果穿的是结构体地址,那么接收就需要用*
	fmt.Println(s)
}
func main() {
	var s1 = Student{
		Name:   "111",
		Age:    0,
		Height: 0,
		Weight: 0,
		phone:  "1111"}
	var s2 = &Student{
		Name:   "2222",
		Age:    0,
		Height: 0,
		Weight: 0,
		phone:  "2222"}
	sayStudent1(s1)
	sayStudent2(s2)
}

执行结果。

在这里插入图片描述

关于结构体默认值

代码

func main() {
	var s1 = Student{}
	fmt.Println(s1)
}

执行结果。

在这里插入图片描述

在操作结构体时,即使没有赋值,也会有默认值,所以不用担心会报错。

int默认值时0,string默认值是"",等。

引言

在Go中,我们是没有类这个概念的,但是我们有结构体呀。

Go中的结构体,就相当于其他语言的类,基本能实现和其他语言一摸一样的操作。

构造函数

构造函数,跟其他语言一样了,官方理解就是在类实例化时执行的方法,通常用于赋值操作。

但是在Go中,可能不是太一样,需要独立用到一个函数完成。

结构体

type Student struct {
	Name  string
	Age   int
	phone string
}

构造函数

func NewStudent(name string, age int, phone string) *Student {
	return &Student{Name: name, Age: age, phone: phone}
}
//函数尽量采用固定格式 New结构体名

赋值操作

func main() {
	var s1 = NewStudent("张三"18"1111")
	fmt.Println(s1)
}

执行结果

在这里插入图片描述

为什么构造函数返回的时结构体指针

通常来说两个原因,第一个原因时传地址性能更高,第二个原因是因为规范,后面的函数绑定结构体也是,更多的是一个规范。

不太用纠结说指针怎么怎么看不懂,对于结构体来说,是不是指针,其实用法都一样。

函数绑定结构体

如果你有其他语言的基础,你可能对于类和对象比较熟悉,传统做法中,是将方法写入类中的。

但是在Go中,采用绑定的方式添加方法。

语法

func (一般用this 要绑定的结构体) 函数名([参数1,参数2...]) [(返回值1,返回值2...)]{
	代码
}
//一般用this,也可以用其他的,this就像形参一样,随便换,用self,用p,用s,都一样的

示例:给Student结构体绑定方法。

func (this Student) say() {
	fmt.Printf("我是%v,我今年%v岁了,我的手机号是%v\n", this.Name, this.Age, this.phone)
}

main代码

func main() {
	//调用构造方法
	var s1 = NewStudent("张三"18"1111")
	//调用Student绑定的say方法
	s1.say()
}

执行结果

在这里插入图片描述

有没有感觉有点Java和Python的感觉了,上述可是通过结构体的方式调用方法的,这里就和C区分开了。

在Go中,基本就是通过这些操作,模拟出来面向对象的,相比之下,我更习惯Go的方式,更加灵活。

函数绑定结构体(指针方式)

如果说区别,只是将要修改的 要绑定的结构体 前面加一个*

代码

func (this *Student) say() {
	fmt.Printf("我是%v,我今年%v岁了,我的手机号是%v\n", this.Name, this.Age, this.phone)
}

执行结果和上述一摸一样。

函数绑定结构体(指针方式和普通方式区别)

通常来说,一般使用指针的方式居多。

嗯…不是居多,是基本都是。

区别

代码一

func (this Student) say1() {
	fmt.Printf("我是%v,我今年%v岁了,我的手机号是%v\n", this.Name, this.Age, this.phone)
	this.Name = "666"//这里修改了Name为其他值
}

第3行修改了Name

func main() {
	//调用构造方法
	var s1 = NewStudent("张三"18"1111")
	//调用Student绑定的say方法
	s1.say1()
	//打印s1.Name
	fmt.Println(s1.Name)
}

第7行又打印了s1.Name

执行结果

在这里插入图片描述

???结果没修改,what。

代码二

func (this *Student) say1() {
	fmt.Printf("我是%v,我今年%v岁了,我的手机号是%v\n", this.Name, this.Age, this.phone)
	this.Name = "666"//这里修改了Name为其他值
}

第一行修改为*

func main() {
	//调用构造方法
	var s1 = NewStudent("张三"18"1111")
	//调用Student绑定的say方法
	s1.say1()
	//打印s1.Name
	fmt.Println(s1.Name)
}

执行结果

在这里插入图片描述

这次可以看到,结果变了,在其他函数修改了Name,影响了整个s1的Name。

结论

  • 在使用函数绑定结构体时,也尽可能的将结构体参数整成*类型的。

  • 一是因为规范,二是因为面向对象本该如此,修改对象的属性,理论来说就应该影响整个对象值。

匿名字段结构体

如果以后再遇到匿名这个,就把他当作没有名字的意思。

匿名加字段结构体代码

package main

import "fmt"

type Student struct {
	string
	int
	//string //error:duplicate field string
	//int    //error:duplicate field string
}

func main() {
	var s1 = Student{
		"666"0}
	fmt.Println(s1)
}

8行和第9行代码,如果去掉注释会报错。

这就说明了个问题,如果是匿名字段结构体,匿名字段类型是不能重复的,如上述代码所示。

结构体嵌套

结构体嵌套,就如名字一样,一个结构体,嵌套了另外一个结构体。

假设

一个学生的信息,假设有姓名年龄性别,这三个字段。

这个学生必定要归属一个班级的,假设这个班级的信息有年级几班班主任姓名

创建结构体

根据上述叙述,我们知道一定是有两个结构体的,至少一个是学生,一个是班级。

班级结构体
type Classes struct {
	Grade       int    //年级
	Class       int    //班级
	TeacherName string //班主任姓名
}
学生结构体
type Student struct {
	Name   string  //姓名
	Age    int     //年龄
	Gender string  //性别
	class  Classes //所属班级
}

可以看到第5行代码,结构体字段类型直接是一个结构体,这就是结构体嵌套、

当一个结构体不能完整描述一个对象时,或者说本来就是独立的对象有关联时,就需要结构体嵌套、

嵌套结构体赋值
方式一,直接赋值嵌套结构体
func main() {
	var s1 = Student{
		Name:   "张三",
		Age:    18,
		Gender: "男",
		class: Classes{
			Grade:       2020,
			Class:       1,
			TeacherName: "张三的老师"}}
	fmt.Println(s1)
}
方式二,分开赋值
func main() {
	var c1 = Classes{
		Grade:       2020,
		Class:       1,
		TeacherName: "张三的老师"}
	var s2 = Student{
		Name:   "张三",
		Age:    18,
		Gender: "男",
		class:  c1,
	}
	fmt.Println(s2)
}
两次执行结果

在这里插入图片描述

其实方式一方式二的本质是一样的,只不过是方式二嵌套的结构体单独赋值了而已。

匿名嵌套字段

上述我们的Student结构体是这样写的。

type Student struct {
	Name   string  //姓名
	Age    int     //年龄
	Gender string  //性别
	class  Classes //所属班级
}

但是其实第5行代码的字段是可以省略的,就像这样。

type Student struct {
	Name   string  //姓名
	Age    int     //年龄
	Gender string  //性别
	Classes //所属班级
}

但是在赋值时,就要注意了,因为Student结构体已经没有字段名了,所以就不能使用上述的方式赋值了

需要这种。

func main() {
	var s1 = Student{
		Name:   "张三",
		Age:    18,
		Gender: "男",
		Classes: Classes{
			Grade:       2020,
			Class:       1,
			TeacherName: "张三的老师"}}
}

没错,第5行的字段名是Classes结构体名。

执行结果还是一样的。

在这里插入图片描述

补充

上述是直接通过定义变量时就直接赋值了。

其实不管是结构体,还是嵌套结构体,都还有一种方法,就是通过.的方式赋值。

代码如下

结构体嵌套

默认的结构体嵌套,结构体还是有字段名的。

type Student struct {
	Name    string //姓名
	Age     int    //年龄
	Gender  string //性别
	class Classes        //所属班级
}

所以赋值代码如下。

func main() {
	var s1 Student
	s1.Name = "张三"
	s1.Age = 18
	s1.Gender = "男"
	s1.class.Grade = 2020
	s1.class.Class = 1
	s1.class.TeacherName = "张三的老师"
	fmt.Println(s1)
}

第6行代码开始,通过s1找到class这个字段,再根据class找到class具体对应的值进行赋值。

匿名嵌套字段

匿名嵌套字段是没有字段名的,是有一个字段类型

type Student struct {
	Name    string //姓名
	Age     int    //年龄
	Gender  string //性别
	Classes        //所属班级
}

所以赋值跟上述也不太一样,是这样的。

func main() {
	var s1 Student
	s1.Name = "张三"
	s1.Age = 18
	s1.Gender = "男"
	s1.Classes.Grade = 2020
	s1.Classes.Class = 1
	s1.Classes.TeacherName = "张三的老师"
	fmt.Println(s1)
}

通过s1直接找到Classes这个结构体,再根据这个结构体找到里面具体的值,进行赋值。

其实跟定义变量时赋值相似。

但是终究执行结果,还是一样的,只是赋值形式不同。

在这里插入图片描述

结论

根据嵌套结构体匿名嵌套结构体再赋值时可以发现。

如果嵌套结构体有字段名,通过字段名找具体的字段,进行赋值。

如果是嵌套结构体匿名字段,通过嵌套结构体的名字,找具体字段,进行赋值。

嵌套结构体字段冲突

这个冲突的问题,其实还是比较少见的,这个问题通常情况下,只会出现在匿名嵌套场景中。

还是上述的结构体,但是赋值可以是这样操作的。

func main() {
	var s1 Student
	s1.Name = "张三"
	s1.Age = 18
	s1.Gender = "男"
	s1.Classes.Grade = 2020
	s1.Classes.Class = 1
	s1.Classes.TeacherName = "张三的老师"
	//######### 分割 ##########
	s1.Grade = 2020	  //省去了Classes
	s1.Class = 1	  //省去了Classes
	s1.TeacherName = "张三的老师"	//省去了Classes
	fmt.Println(s1)
}

第10行,直接通过s1.Grade赋值,其实是省去了一个Classes,但是这种操作,只有在匿名嵌套结构体中可以使用。

但是如果我将结构体改成这样子。

//班级
type Classes struct {
	Grade       int    //年级
	Class       int    //班级
	TeacherName string //班主任姓名
}

//课程
type Course struct {
	CourseName  string //课程名字
	TeacherName string //任课老师姓名
}

//学生
type Student struct {
	Name    string //姓名
	Age     int    //年龄
	Gender  string //性别
	Classes        //所属班级
	Course         //任课老师
}

Student结构体有两个匿名嵌套结构体,一个是Classes,一个是Course

但是有一个字段,是冲突的,就是TeacherName,如果还是通过懒的方式赋值,会发生什么呢?

func main() {
	var s1 Student
	s1.Name = "张三"
	s1.Age = 18
	s1.Gender = "男"
	s1.Grade = 2020
	s1.Class = 1
	s1.TeacherName = "张三的老师"
	fmt.Println(s1)
}

第8行,直接找TeacherName字段,这时候就会出问题了。

在这里插入图片描述

意思很简单,就是不知道是ClassesTeacherName还是CourseTeacherName

这时候,就必须要指定了。

s1.Classes.TeacherName = "张三的班主任"
s1.Course.TeacherName = "张三的任课老师"

结构体继承

说起继承,学过Java,Python的肯定都不陌生,但是Go中,可没有这个东西呐。

那咋办呢???,还是得用结构体来实现。

假装我们都是男孩,喜欢车,那我们就拿车来举例子吧。

车结构体

//车
type Car struct {
	Brand  string //车品牌
	CarNum string //车牌号
	Tyre   int    //轮胎个数
}

//给车绑定一个方法,说明车的基本信息
func (this *Car) carInfo() {
	fmt.Printf("品牌:%s,车牌号:%s,轮胎个数:%d\n", this.Brand, this.CarNum, this.Tyre)
}

宝马车

//宝马车
type BMWCar struct {
	//*Car和Car基本没有区别,一个存的是整个结构体,一个存的是结构体地址,用法大同小异
	*Car //这就表示继承了Car这个结构体
}

比亚迪车

//比亚迪车
type BYDCar struct {
	*Car
}

可能看到这,你会有种熟悉得感觉,这不就是上节课所将的结构体嵌套吗???

这跟继承有毛关系?

其实在Go中,结构体既可以用来存储数据,也可以用来模仿对象的各种操作。

main代码

func main() {
	//一个宝马对象
	var bmw1 = BMWCar{&Car{
		Brand:  "宝马x8",
		CarNum: "京666",
		Tyre:   4}}
	//一个比亚迪对象
	var byd1 = BYDCar{&Car{
		Brand:  "比亚迪L3",
		CarNum: "京111",
		Tyre:   4}}
	//因为 BMWCar 和 BYDCar 都继承了Car,所以都有carInfo这个方法
	bmw1.carInfo()
	byd1.carInfo()
}

执行结果

在这里插入图片描述

这就是一个最简单的,面向对象,跟其他语言一样,继承会将所有的属性和方法都继承过来。

序列化

到此为止呢,结构体基本可以告一段落了,基本算是入门了,当然,并没有结束,但是我想大家都累了,换个方向继续玩。

这个东西叫做序列化,什么意思呢,就是像咱们的切片了,map了,结构体了等,这些都是Go的类型。

如果要和其他语言交流,人家可没有这些玩意唉,那怎么办呢???

众多大佬就形成了一个规范,json数据格式,json数据必须是字符串类型

最外面是'号,键/值对组合中的键名写在前面并用双引号""包裹。

就像这样。

'{"Gender":"男","Name":"张三"}'	//'说明这个是字符串,一般打印时不显示

序列化我们用到的是json模块的Marshal方法。

切片序列化

单独的切片序列化用的很少,但是仍然还是要知道。

示例代码

package main

import (
	"encoding/json"
	"fmt"
)

type Student struct {
	Gender string
	Name   string
}

func main() {
	var StudentList = []string{"张三""李四"}
	fmt.Printf("StudentList类型:%T\n", StudentList) //[]string,这是列表类型
	serializeByte, err := json.Marshal(StudentList)
	if err != nil {
		fmt.Println("序列化失败")
		return
	}
	var serializeStr = string(serializeByte)
	fmt.Printf("serializeStr类型:%T\n", serializeStr) //string,这是字符串类型
	fmt.Printf("serializeStr值:%v\n", serializeStr) //["张三","李四"]
}

第16行代码将切片序列化,但是返回的是[]byte类型,第21行代码将[]byte类型转成字符串。

执行结果

在这里插入图片描述

map序列化

字典序列化,就比较有味道了,序列化的是一个标准的json数据格式。

示例代码

package main

import (
	"encoding/json"
	"fmt"
)

type Student struct {
	Gender string
	Name   string
}

func main() {
	var StudentInfo = map[string]string{
		"Name":"张三""Age":"18""Gender":"男"}
	fmt.Printf("StudentInfo类型:%T\n",StudentInfo)
	serializeByte, err := json.Marshal(StudentInfo)
	if err != nil {
		fmt.Println("序列化失败")
	}
	var serializeStr = string(serializeByte)
	fmt.Printf("serializeStr类型:%T\n", serializeStr) //string,这是字符串类型
	fmt.Printf("serializeStr值:%v\n", serializeStr) //{"Age":"18","Gender":"男","Name":"张三"}
}

执行结果

在这里插入图片描述

这个就有点像标准的json格式了。

结构体序列化

结构体代码
type Student struct {
	Name   string
	Gender string
	Age    int
}
main
func main() {
	var s1 = Student{
		Name:   "张三",
		Gender: "男",
		Age:    18}
	fmt.Printf("StudentInfo类型:%T\n", s1)
	serializeByte, err := json.Marshal(s1)
	if err != nil {
		fmt.Println("序列化失败")
	}
	var serializeStr = string(serializeByte)
	fmt.Printf("serializeStr类型:%T\n", serializeStr) //string,这是字符串类型
	fmt.Printf("serializeStr值:%v\n", serializeStr)
}

执行结果

在这里插入图片描述

切片套结构体

一般情况下,这种方式数据格式是用的比较多的。

当然, 还可以切片嵌套map,方法和此方法一样,不做例子了。

示例代码

package main

import (
	"encoding/json"
	"fmt"
)

type Student struct {
	Name   string
	Gender string
	Age    int
}

func main() {
	var s1 = Student{
		Name:   "张三",
		Gender: "男",
		Age:    18}
	var s2 = Student{
		Name:   "李四",
		Gender: "女",
		Age:    16}
	//一个存放 Student 的列表
	var studentList = []Student{s1, s2}
	fmt.Printf("StudentInfo类型:%T\n", studentList)
	serializeByte, err := json.Marshal(studentList) //main.Student
	if err != nil {
		fmt.Println("序列化失败")
	}
	var serializeStr = string(serializeByte)
	fmt.Printf("serializeStr类型:%T\n", serializeStr) //string,这是字符串类型
	fmt.Printf("serializeStr值:%v\n", serializeStr)  
}

执行结果

在这里插入图片描述

结构体标签(Tag)

Tag可以理解为结构体的说明,由一对反引号包裹起来。

但是一般情况下,Tag在序列化是用的比较多。

结构体代码

type Student struct {
	Name   string `json:"name"`
	Gender string `json:"gender"`
	Age    int    `json:"age"`
}

每个字段后面跟的,就是Tag,一定不要把格式搞错啦。

main代码

func main() {
	var s1 = Student{
		Name:   "张三",
		Gender: "男",
		Age:    18}
	fmt.Printf("StudentInfo类型:%T\n", s1)
	serializeByte, err := json.Marshal(s1) //main.Student
	if err != nil {
		fmt.Println("序列化失败")
	}
	var serializeStr = string(serializeByte)
	fmt.Printf("serializeStr类型:%T\n", serializeStr) //string,这是字符串类型
	fmt.Printf("serializeStr值:%v\n", serializeStr)  
}

执行结果

在这里插入图片描述

可以发现key成小写的了,这就说明一个问题。

在序列化时,如果结构体json这个Tag,序列化时就会以jsonTag为准,如果没有jsonTag,则以结构体字段为准

总结

一定要在下面多多练习,如果在操作过程中有任何问题,记得下面留言,我们看到会第一时间解决问题。

我是码农星期八,如果觉得还不错,记得动手点赞一下哈。

感谢你的观看。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值