Go学习笔记
Go语言基础
类型别名和自定义类型
自定义类型
在Go语言中有一些基本的数据类型, 如
string
、整型
、浮点型
、布尔
等数据类型, Go语言中可以使用type
关键字来定义自定义类型。自定义类型是定义了一个全新的类型。我们可以基于内置的基本类型定义, 也可以通过
struct
定义。类型定义和类型别名的区别: 自定义类型,定义以后输出的类型会输出为 新定义的类型, 而类型别名,在定义以后输出的类型仍然是原类型。
|
|
- 输出:
|
|
类型别名
- 类型别名规定:
TypeAlias
只是Type
的别名, 本质上TypeAlias
与Type
是同一个类型。 - 类型别名定义:
type TypeAlias = Type
。 rune
和byte
就是类型别名, 它们其实是type byte = uint8
,type rune = int32
|
|
- 输出:
|
|
结构体
Go语言中的基础数据类型可以表示一些事物的基本属性, 但是当我们想表达一个事物的全部或部分属性时, 这时候再用单一的基本数据类型明显就无法满足需求了, Go语言提供了一种自定义数据类型, 可以封装多个基本数据类型, 这种数据类型叫
结构体
, 英文名称struct
。 也就是我们可以通过struct
来定义自己的类型了。Go语言中通过
struct
来实现面向对象。
结构体的定义
- 使用
type
和struct
关键字来定义结构体。
|
|
- 说明:
- 类型名: 标识自定义结构体的名称, 在同一个包内不能重复。
- 字段名: 表示结构体字段名。结构体中的字段名必须唯一。
- 字段类型: 表示结构体字段的具体类型。
|
|
如上定义了一个
person
的自定义类型, 它有name
、city
、age
三个字段, 分别表示姓名、城市和年龄。这样我们使用这个person结构体就能够很方便的在程序中表示和存储人信息了。语言内置的基础数据类型是用来描述一个值的, 而结构体是用来描述一组值的。比如一个人有名字、年龄和居住城市等, 本质上是一种聚合型的数据类型。
结构体实例化
只有当结构体实例化时, 才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。
结构体本身也是一种类型, 我们可以像声明内置类型一样使用
var
关键字声明结构体类型。 如:var 结构体实例 结构体类型
|
|
- 输出:
|
|
匿名结构体
- 在定义一些临时数据结构等场景下可以使用匿名结构体。
|
|
- 输出:
|
|
指针类型的结构体
- 通过使用
new
关键字对结构体进行实例化, 得到的是结构体的内存地址。
|
|
- 输出:
|
|
取结构体的地址实例化
- 使用
&
对结构体进行取地址操作相当于对该结构体类型进行了一次new
实例化操作。
|
|
- 输出:
|
|
结构体初始化
没有初始化的结构体, 其成员变量都是对应其类型的零值。
- 使用键值对初始化 - 使用键值对对结构体进行初始化时, 键对应结构体的字段, 值对应该字段的初始值。也可以对结构体指针进行键值对初始化。当某些字段没有初始值的时候, 该字段可以不写。此时, 没有指定初始值的字段的值就是该字段类型的零值。
- 使用值的列表初始化 - 初始化结构体的时候可以简写, 也就是初始化的时候不写键, 直接写值。但是需要注意的是 (1. 必须初始化结构体的所有字段。2. 初始值的填充顺序必须与字段在结构体中的声明顺序一致。3. 方式不能和键值初始化方式混用。)
键值对初始化
|
|
- 输出:
|
|
- 值的列表初始化
|
|
- 输出:
|
|
结构体内存布局
- 结构体占用一块连续的内存。
|
|
- 输出:
|
|
结构体的匿名字段
- 结构体允许其成员字段在声明时没有字段名而只有类型, 这种没有名字的字段就称为匿名字段。
- 匿名字段不能有重复的类型.
|
|
- 输出:
|
|
嵌套结构体
- 一个结构体中可以嵌套包含另一个结构体或结构体指针。
|
|
- 输出:
|
|
嵌套匿名结构体
- 如上例子修改一下嵌套的结构体只写类型名
|
|
- 输出:
|
|
- 匿名结构体字段冲突
|
|
- 输出:
|
|
构造函数
- Go语言的结构体是没有构造函数, 我们可以自己实现。构造函数就是 构造一个结构体实例的函数。
- 如下实现了一个person的构造函数。 因为struct是值类型, 如果结构体比较复杂的话, 值拷贝性能开销会比较大, 所以该构造函数返回的是结构体指针类型。
|
|
- 输出:
|
|
结构体练习题
|
|
- 输出:
|
|
方法和接收者
Go语言中的方法
(Method)
是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)
, 接收者的概念就类似于其他语言中的this
或者self
。方法与函数的区别是, 函数不属于任何类型, 方法属于特定的类型。
方法的定义格式:
- 接收者变量: 接收者中的参数变量名在命名时, 官方建议使用接收者类型名的第一个小写字母, 而不是self、this之类的命名。例如,
Person
类型的接收者变量应该命名为p
,Connector
类型的接收者变量应该命名为c
等。 - 接收者类型: 接收者类型和参数类似, 可以是指针类型和非指针类型。
- 方法名、参数列表、返回参数: 具体格式与函数定义相同。
- 接收者变量: 接收者中的参数变量名在命名时, 官方建议使用接收者类型名的第一个小写字母, 而不是self、this之类的命名。例如,
|
|
|
|
- 输出:
|
|
指针类型的接收者
指针类型的接收者由一个结构体的指针组成, 由于指针的特性, 调用方法时修改接收者指针的任意成员变量, 在方法结束后, 修改都是有效的。这种方式就十分接近于其他语言中面向对象中的
this
或者self
。例如 我们为Person添加一个SetAge方法, 来修改实例变量的年龄。
|
|
- 输出:
|
|
值接收者
当方法作用于值类型接收者时, Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值, 但修改操作只是针对副本, 无法修改接收者变量本身。
函数与方法传参的时候都是复制了一份, 所以修改只是修改了复制的那一份, 没有修改到本身。
|
|
- 输出:
|
|
- 什么时候应该使用指针类型接收者
- 需要修改接收者中的值
- 接收者是拷贝代价比较大的大对象
- 保证一致性, 如果有某个方法使用了指针接收者, 那么其他的方法也应该使用指针接收者。
任意类型添加方法
- 在Go语言中, 接收者的类型可以是任何类型, 不仅仅是结构体, 任何类型都可以拥有方法。
- 举个例子: 我们基于内置的
int
类型使用type
关键字可以定义新的自定义类型, 然后可以为我们的自定义类型添加方法。 - 注意事项: 非本地类型不能定义方法, 也就是说我们不能给别的包的类型定义方法。
|
|
- 输出:
|
|
结构体"继承”
- Go语言中是没有继承这个概念的, 但是使用结构体也可以实现其他编程语言中面向对象的继承。
|
|
- 输出:
|
|
结构体字段的可见性
- 结构体中字段大写开头表示可公开访问, 小写表示私有(仅在定义当前结构体的包中可访问)。
|
|
结构体JSON序列化
- JSON
(JavaScript Object Notation)
是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。 - JSON键值对是用来保存
JS
对象的一种方式,键/值
对组合中的键名写在前面并用双引号""
包裹,使用冒号:
分隔, 然后紧接着值, 多个键值之间使用英文,
分隔。
|
|
- 输出:
|
|
结构体JSON 反序列化
- JSON格式的字符串 反序列化成 结构体
|
|
- 输出:
|
|
结构体标签 (Tag)
Tag
是结构体的元信息, 可以在运行的时候通过反射的机制读取出来。Tag
在结构体字段的后方定义, 由一对 `` 反引号包裹起来。结构体标签由一个或多个键值对组成。键与值使用冒号
:
分隔, 值用双引号""
括起来。键值对之间使用一个空格
分隔。注意事项: 为结构体编写
Tag
时, 必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差, 一旦格式写错, 编译和运行时都不会提示任何错误, 通过反射也无法正确取值。例如不要在key
和value
之间添加空格。
|
|
- 输出:
|
|
练习题
- 使用"面向对象"的思维方式编写一个学生信息管理系统。
- 学生有id、姓名、年龄、分数等信息
- 程序提供展示学生列表、添加学生、编辑学生信息、删除学生等功能
|
|
|
|