go+vue
- 技术选择
- 搭建Go语言开发环境
- Go语言基础之变量和常量
- 基本数据类型
- 运算符
- Go语言基础之流程控制
- array 数组
- slice 切片 :又称动态数组, 对底层数组的封装
- map 映射
- 函数
- 指针
- 结构体
- 包 packet
- 接口 : 是一个类型, 一个抽象的类型
- reflect 反射 (暂时跳过 ----------------------------------------- -----------------------------------------)
- 结构体 反射 (暂时跳过 ----------------------------------------- -----------------------------------------)
- 并发编程
- 并发同步 与 锁 (暂时跳过 ----------------------------------------- -----------------------------------------)
- 网络 编程 (暂时跳过 ----------------------------------------- -----------------------------------------)
- 单元测试 (暂时跳过 ----------------------------------------- -----------------------------------------)
技术选择
入坑理由
需要搭建前后端,Java 0 基础 ,环境容易出现问题;GO上手快,问题少
看到有人推【七米】,所以。。。
推荐:【七米】
代码
https://github.com/Q1mi/go_tutorial
博客
https://liwenzhou.com/
https://www.liwenzhou.com/posts/Go/golang-menu/
https://www.liwenzhou.com/posts/Go/install/
搭建Go语言开发环境
下载 并 安装
下载地址
Go官网下载地址:https://golang.org/dl/
Go官方镜像站(推荐):https://golang.google.cn/dl/
检查是否安装好?
GOPROXY 非常重要(帮你下载国外、GitHub代码)
复制、粘贴到cmd命令行、回车。
之后什么都不用做,cmd命令行 也没有输出。
go env -w GOPROXY=https://goproxy.cn,direct
VScode
插件
chinese
第一个【简体】,安装完后,【右下角】【重启】
go
新建项目
错误提示,直接点【install】
非常重要!!! 如果此时VS Code右下角弹出提示让你安装插件,务必点 install all 进行安装。
这一步需要先执行完上面提到的go env -w GOPROXY=https://goproxy.cn,direct命令配置好GOPROXY。
go mod init
使用go module模式新建项目时,我们需要通过go mod init 项目名命令对项目进行初始化,该命令会在项目根目录下生成go.mod文件。例如,我们使用hello作为我们第一个Go项目的名称,执行如下命令。
go mod init hello
go mod init github.com/Q1mi/hello
后面跟, 别人 导入 你的 【包】 的名字。
require 表示引用其他人的库
编写程序 hello world
编译 go build
go build
go build -o Out_Name.exe
生成可执行文件 hello.exe
执行 .\xxx.exe
只有一个go文件时,临时编译并执行,不生产exe文件
go run main.go
windows —— powershell
.\xxx.exe
windows —— cmd
xxx.exe
linux、mac
./xxx.exe
看清楚【终端】是 powershell 还是 cmd
【终端】【选择默认配置文件】
终端 之间 切换
跨平台编译
cmd 中 设置环境变量
不能在 windows 执行, 只能在 Linux 上执行
go mod tidy 帮你分析 你的依赖 是否 都在 .mod 中 require
cmd中执行
go mod tidy
执行 go mod tidy , 下载第三方包
先 import , 并在代码中调用
再执行,go mod tidy
并 帮你 引入 第三方包
需要先 保存一下 Ctrl + S , 执行 go mod tidy 才有效
下载第三方库
go get github.com/q1mi/hello
Go语言基础之变量和常量
https://www.liwenzhou.com/posts/Go/var-and-const/
变量(同C++)
注意事项:
函数外的每个语句都必须以关键字开始(var、const、func等)
:=不能使用在函数外。
_多用于占位,表示忽略值。
常量 iota (只能在常量的表达式中使用)
const (
n1 = iota //0
n2 //1
n3 //2
n4 //3
)
const (
n1 = iota //0
n2 //1
_
n4 //3
)
const (
n1 = iota //0
n2 = 100 //100
n3 = iota //2
n4 //3
)
const n5 = iota //0
iota在const关键字出现时将被重置为0。
const (
_ = iota
KB = 1 << (10 * iota)
MB = 1 << (10 * iota)
GB = 1 << (10 * iota)
TB = 1 << (10 * iota)
PB = 1 << (10 * iota)
)
定义数量级
(这里的<<表示左移操作,
1<<10表示将1的二进制表示向左移10位,也就是由1变成了10000000000,也就是十进制的1024。
同理2<<2表示将2的二进制表示向左移2位,也就是由10变成了1000,也就是十进制的8。)
const (
a, b = iota + 1, iota + 2 //1,2, iota=0
c, d //2,3, iota=1
e, f //3,4
)
多个iota定义在一行
0+1, 0+2 = 1, 2 //, iota=0
1+1, 1+2 = 2, 3 //, iota=1
iota在const关键字出现时将被重置为0。
const中每新增【一行】常量声明将使iota计数一次
(iota可理解为const语句块中的行索引)。
使用iota能简化定义,在定义枚举时很有用。
基本数据类型
整数
注意: 在使用int和 uint类型时,不能假定它是32位或64位的整型,而是考虑int和uint可能在【不同平台上的差异】。
- 注意事项 获取对象的长度的内建【len() 】函数返回的长度可以根据不同平台的字节长度进行变化。
- 实际使用中,【切片或 map 】 的【元素数量】等都可以用int来表示。
- 在涉及到【二进制传输、读写文件】的结构描述时,为了保持文件的结构不会受到不同编译目标平台字节长度的影响,【不要使用】int和 uint。
进制
浮点数
布尔 bool
注意:
布尔类型变量的默认值为false。
Go 语言中不允许将整型强制转换为布尔型.
布尔型无法参与数值运算,也无法与其他类型进行转换。
字符串 string
Go语言中的字符串以【原生数据】类型出现,使用字符串就像使用其他原生数据类型(int、bool、float32、float64 等)一样。 Go 语言里的字符串的内部实现使用【UTF-8】编码。 字符串的值为双引号(")中的内容,可以在Go语言的源码中直接添加非ASCII码字符,例如:
转义字符
多行字符串
Go语言中要定义一个多行字符串时,就必须使用反引号字符:
字符串的常用操作
方法 介绍
len(str) 求长度
+或fmt.Sprintf 拼接字符串
strings.Split 分割
strings.contains 判断是否包含
strings.HasPrefix,strings.HasSuffix 前缀/后缀判断
strings.Index(),strings.LastIndex() 子串出现的位置
strings.Join(a[]string, sep string) join操作
byte和rune类型
rune
英
/ruːn/
美
/ruːn/
n.
如尼字母(属于北欧古文字体系);神秘的记号;有魔力的符号
for range
运算符
算数
自增 自减
关系
逻辑
位
赋值运算符
Go语言基础之流程控制
if else
for
break
continue
for range
Go语言中可以使用for range遍历数组、切片、字符串、map 及通道(channel)。 通过for range遍历的返回值有以下规律:
数组、切片、字符串返回索引和值。
map返回键和值。
通道(channel)只返回通道内的值。
switch case
C++ 不可以:
【关系运算】
array 数组
推导、索引
长度为8,数据元素为string
遍历 [i]
for index, vallue := range cityArray {
二维数组
推导:只能推一位
数组是值类型:(不同于C++)
数组是值类型,赋值和传参会复制整个数组。因此改变副本的值,不会改变本身的值。
slice 切片 :又称动态数组, 对底层数组的封装
初始化: []中什么都不写,就是 定义 切片 的语法
func main() {
// 声明切片类型
var a []string //声明一个字符串【切片】,【未 初始化】
var b = []int{} //声明一个整型【切片】并【初始化】
var c = []bool{false, true} //声明一个布尔【切片】并【初始化】
var d = []bool{false, true} //声明一个布尔【切片】并【初始化】
fmt.Println(a) //[]
fmt.Println(b) //[]
fmt.Println(c) //[false true]
fmt.Println(a == nil) //true 未 初始化[]T
fmt.Println(b == nil) //false 初始化[]T{}
fmt.Println(c == nil) //false 初始化
// fmt.Println(c == d) //切片是引用类型,不支持直接比较,只能和nil比较
}
数组 初始化 切片
切片 初始化 切片
make(类型, 初始元素个数, 容量 | 默认-同初始元素个数)
len cap
内存图
切片定义切片,容量等于从【起点处】到【终点处】
操作
零值: nil
数值类型:数组
int 零值: 0
字符串 零值: 空串
引用类型: 切片、 map
切片、 map 的零值: nil
比较 : 通过 len(切片)0 来判断是不是空,不能通过nil
切片之间是不能比较的,我们不能使用==操作符来判断两个切片是否含有全部相等元素。 切片唯一合法的比较操作是和nil比较。 一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0。但是我们不能说一个长度和容量都是0的切片一定是nil,例如下面的示例:
赋值拷贝(浅拷贝)
遍历: 索引、for range ; for i,vk := range xxlist ;
添加元素
错误方式:没有初始化,就 [i]=xxx
NewSlice = append(xxx) 必须返回,因为可能是扩容之后的 【新切片】 %v d p
append 可以一次增加多个元素
copy
由于切片是引用类型,所以a和b其实都指向了同一块内存地址。修改b的同时a的值也会发生变化。
Go语言内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中,copy()函数的使用格式如下:
copy(destSlice, srcSlice []T)
其中:
srcSlice: 数据来源切片
destSlice: 目标切片
删除
练习:空串
练习:sort.Ints ( 切片 )
[ … ] int 是 数组
a[ : ] 转换成切片,对应的仍然是底层数组
map 映射
初始化
map[string] int ; make ( map[string] int , 容量 可选 )
Println( a )
Printf(" %#v ", a)
初始化 赋值
错误方式:没有初始化, 就 c[100] = 200
操作
查询
没有查到:value 返回 零值: 0、空串、nil
查到后:value 返回 key对应的值value
遍历 for range ; for k,v := range xxmap ; map 是无序hash
删除 delete(scoreMap, “key name”)
按序 遍历
无序
顺序: sort.Strings( key_Slice ) 切片排序+遍历map[有序切片]
复杂:slice+map
slice里map:两层初始化工作
map value 是 slice:两层初始化工作
统计单词个数
函数
返回
参数
可变参数: 切片
可变参数 在 固定参数 后
没有默认参数
参数 类型简写
返回值 多个
参数类型简写
defer 延迟执行 : 处理: 资源的释放、文件关闭、解锁、记录时间
作用域
全局变量
局部变量 : 覆盖 全局变量, 从内 往外 找
for 语句块 局部变量i
一等公民 : 三可做 : 变量、参数、返回值
函数 作为 变量
函数 作为 参数
匿名函数
定义变量
直接调用
闭包: 返回的函数 是否 包含 外层变量的 引用
闭包指的是一个【函数和与其相关的引用环境】组合而成的实体。
简单来说,【闭包 = 函数 + 引用环境】。
简单来说,【闭包 = 函数 + 外层变量 引用】。
简单来说,【闭包 = 返回 匿名函数 + 匿名函数 完成对 外层变量的引用】。
首先我们来看一个例子:
函数作为返回值
最基本的 闭包: 返回 内部 匿名 函数 anon ,anon 完成对 外层 变量 引用(参数)
升级
传入资源
闭包进阶示例2:
外层变量 的 引用: suffix = ".jpg" 、 ".txt"
// 没有后缀、增加后缀
func makeSuffixFunc(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}
func main() {
jpgFunc := makeSuffixFunc(".jpg")
txtFunc := makeSuffixFunc(".txt")
fmt.Println(jpgFunc("test")) //test.jpg
fmt.Println(txtFunc("test")) //test.txt
}
闭包进阶示例3:
func calc(base int) (func(int) int, func(int) int) {
add := func(i int) int {
base += i
return base
}
sub := func(i int) int {
base -= i
return base
}
return add, sub
}
func main() {
f1, f2 := calc(10)
fmt.Println(f1(1), f2(2)) //11 9
fmt.Println(f1(3), f2(4)) //12 8
fmt.Println(f1(5), f2(6)) //13 7
}
内置函数
内置函数 介绍
close 主要用来关闭channel
len 用来求长度,比如string、array、slice、map、channel
new 用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针
make 用来分配内存,主要用来分配引用类型,比如chan、map、slice
append 用来追加元素到数组、slice中
panic和recover 用来做错误处理
panic
panic/recover
Go语言中目前(Go1.12)是没有异常机制,但是使用panic/recover模式来处理错误。 panic可以在任何地方引发,但recover只有在defer调用的函数中有效。 首先来看一个例子:
defer recover
指针
Go 语言 函数 都是 值拷贝/浅拷贝
指针=取地址&, %v=%p
取值=*指针
函数 传 指针
未初始化, 引发 panic
new : 整型、浮点型、bool、字符串、数组
make : slice、map、channel
new与make的区别
二者都是用来做内存分配的。
make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针。
func new(Type) *Type
func make(t Type, size ...IntegerType) Type
结构体
自定义 类型 %T
别名
自定义类型是定义了一个全新的类型。我们可以基于内置的基本类型定义,也可以通过struct定义。例如:
//将MyInt定义为int类型
type MyInt int
通过type关键字的定义,MyInt就是一种新的类型,它具有int的特性。
类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型。就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。
type TypeAlias = Type
我们之前见过的rune和byte就是类型别名,他们的定义如下:
type byte = uint8
type rune = int32
结构体
使用type和struct关键字来定义结构体,具体代码格式如下:
type 类型名 struct {
字段名 字段类型
字段名 字段类型
…
}
其中:
类型名:标识自定义结构体的名称,在同一个包内不能重复。
字段名:表示结构体字段名。结构体中的字段名必须唯一。
字段类型:表示结构体字段的具体类型。
结构体 实例化
匿名结构体
结构体 指针 new
可以直接写 p. 不用 ( *p ).
结构体初始化
取结构体的地址实例化 默认 初始化
取结构体的地址实例化 初始化
键值对 初始化 , 最后的“ ,”必须要写
列表 初始化 , 最后的“ ,”必须要写
构造函数
struct 是 值类型
方法
标识符 首字母大写
变量 标识符 首字母 大写,
表示 对外部 可见;
在别的包中 可见。
构造函数
定义方法
值类型 的 接收者
方法: 接受者
构造函数: 任意调用
值类型 的 接收者: p Person
Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。接收者的概念就类似于其他语言中的this或者 self。
方法的定义格式如下:
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
函数体
}
其中,
接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名称首字母的小写,而不是self、this之类的命名。例如,Person类型的接收者变量应该命名为 p,Connector类型的接收者变量应该命名为c等。
接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。
方法名、参数列表、返回参数:具体格式与函数定义相同。
指针类型 的 接收者
指针类型 的 接收者: p *Person
针类型的接收者
指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其他语言中面向对象中的this或者self。 例如我们为Person添加一个SetAge方法,来修改实例变量的年龄。
// SetAge 设置p的年龄
// 使用指针接收者
func (p *Person) SetAge(newAge int8) {
p.age = newAge
}
对比
什么时候应该使用指针类型接收者
需要【修改】接收者中的值
接收者是【拷贝代价】比较大的大对象
保证一致性,如果有某个【方法使用】了指针接收者,
那么其他的方法也应该使用指针接收者。
任意类型添加方法
不可以,不是自己 的 包
可以,起别名(继承)
任意类型添加方法
在Go语言中,接收者的类型可以是任何类型,
不仅仅是结构体,任何类型都可以拥有方法。
举个例子,
我们基于内置的int类型使用type关键字可以定义新的自定义类型,
然后为我们的自定义类型添加方法。
//MyInt 将int定义为自定义MyInt类型
type MyInt int
//SayHello 为MyInt添加一个SayHello的方法
func (m MyInt) SayHello() {
fmt.Println("Hello, 我是一个int。")
}
func main() {
var m1 MyInt
m1.SayHello() //Hello, 我是一个int。
m1 = 100
fmt.Printf("%#v %T\n", m1, m1) //100 main.MyInt
}
注意事项: 非本地类型不能定义方法,
也就是说我们不能给别的包的类型定义方法。
只能在我【自己】的代码包 中 添加 方法,
不能给 go 语言 内置包 中的 基本数据类型 添加方法。
比如,不能给 : int string array map 添加方法,
但是,可以起别名,然后添加方法。
结构体 嵌套
匿名字段
按照 类型 访问
不能重复 类型
嵌套 结构体
嵌套 结构体
匿名 嵌套 结构体
嵌套 结构体 字段冲突
指定 结构体 名字
结构体 继承 (用 嵌套 模拟, 更准确的说 应该叫 组合)
结构体 字段 可见性
结构体中字段大写开头表示可公开访问,
小写表示私有(仅在定义当前结构体的包中可访问)。
JSON
https://www.json.com
序列化、 反序列化
如果 首字母小写, JSON 访问不了, 字段缺失
序列化,缺失
反序列化,字段为 零值: 空串 、 0
结构体标签(Tag)
结构体标签(Tag)
Tag是结构体的【元信息】,可以在运行的时候通过【反射】的机制读取出来。
Tag在结构体字段的【后方】定义,由一对【反引号】包裹起来,具体的格式如下:
`key1:"value1" key2:"value2"`
结构体tag由一个或多个键值对组成。
键与值使用【冒号】分隔,【值】用【双引号】括起来。
同一个结构体字段可以
设置【多个】键值对tag,
不同的键值对之间使用【空格】分隔。
注意事项:
为结构体编写Tag时,必须严格遵守键值对的规则。
结构体标签的解析代码的容错能力很差,一旦格式写错,
编译和运行时都不会提示任何错误,通过反射也无法正确取值。
例如不要在key和value之间添加空格。
例如我们为Student结构体的每个字段定义json序列化时使用的Tag:
//Student 学生
type Student struct {
ID int `json:"id"` //通过指定tag实现json序列化该字段时的key
Gender string //json序列化是默认使用字段名作为key
name string //私有不能被json包访问
}
func main() {
s1 := Student{
ID: 1,
Gender: "男",
name: "沙河娜扎",
}
data, err := json.Marshal(s1)
if err != nil {
fmt.Println("json marshal failed!")
return
}
fmt.Printf("json str:%s\n", data) //json str:{"id":1,"Gender":"男"}
}
序列化 结果
JSON 中的
title 变成 小写
student_list 小写
Tag在结构体字段的【后方】定义,由一对【反引号】包裹起来,具体的格式如下
json、db、xml
注意: 结构体内 slice、 map 使用 make(类型, len(变量名))
因为slice和map这两种数据类型都包含了指向底层数据的指针,
因此我们在需要复制它们时要特别注意。
我们来看下面的例子:
错误:
type Person struct {
name string
age int8
dreams []string
}
func (p *Person) SetDreams(dreams []string) {
p.dreams = dreams
}
func main() {
p1 := Person{name: "小王子", age: 18}
data := []string{"吃饭", "睡觉", "打豆豆"}
p1.SetDreams(data)
// 你真的想要修改 p1.dreams 吗?
data[1] = "不睡觉"
fmt.Println(p1.dreams) // ?
}
正确的做法是在方法中使用传入的slice的拷贝进行结构体赋值。
func (p *Person) SetDreams(dreams []string) {
p.dreams = make([]string, len(dreams))
copy(p.dreams, dreams)
}
学生信息管理系统
main.go
package main
import (
"fmt"
"os"
)
// 学员信息管理系统
// 需求:
// 1. 添加学员信息
// 2. 编辑学员信息
// 3. 展示所有学员信息
func showMenu() {
fmt.Println("欢迎来到学员信息管理系统")
fmt.Println("1. 添加学员")
fmt.Println("2. 编辑学员信息")
fmt.Println("3. 展示所有学员信息")
fmt.Println("4. 退出系统")
}
// 获取用户输入的学员信息
func getInput() *student {
var (
id int
name string
class string
)
fmt.Println("请按要求输入学员信息")
fmt.Print("请输入学员的学号:")
fmt.Scanf("%d\n", &id)
fmt.Print("请输入学员的姓名:")
fmt.Scanf("%s\n", &name)
fmt.Print("请输入学员的班级:")
fmt.Scanf("%s\n", &class)
// 就能拿到用户输入的学员的所有信息
stu := newStudent(id, name, class) // 调用student的构造函数造一个学生
return stu
}
func main() {
sm := newStudentMgr()
for {
// 1. 打印系统菜单
showMenu()
// 2. 等待用户选择要执行的选项
var input int
fmt.Print("请输入你要操作的序号:")
fmt.Scanf("%d\n", &input)
fmt.Println("用户输入的是:", input)
// 3. 执行用户选择的动作
switch input {
case 1:
// 添加学员
stu := getInput()
sm.addStudent(stu)
case 2:
// 编辑学员
stu := getInput()
sm.modifyStudent(stu)
case 3:
// 展示所有学员
sm.showStudent()
case 4:
// 退出
os.Exit(0)
}
}
}
student.go
package main
import "fmt"
type student struct {
id int // 学号是唯一的
name string
class string
}
// newStudent 是student类型的构造函数
func newStudent(id int, name, class string) *student {
return &student{
id: id,
name: name,
class: class,
}
}
// 学员管理的类型
type studentMgr struct {
allStudents []*student
}
// newStudentMgr 是studentMgr的构造函数
func newStudentMgr() *studentMgr {
return &studentMgr{
allStudents: make([]*student, 0, 100),
}
}
// 1. 添加学生
func (s *studentMgr) addStudent(newStu *student) {
s.allStudents = append(s.allStudents, newStu)
}
// 2. 编辑学生
func (s *studentMgr) modifyStudent(newStu *student) {
for i, v := range s.allStudents {
if newStu.id == v.id { // 当学号相同时,就表示找到了要修改的学生
s.allStudents[i] = newStu // 根据切片的索引直接把新学生赋值进来
return
}
}
// 如果走到这里说明输入的学生没有找到
fmt.Printf("输入的学生信息有误,系统中没有学号是:%d的学生\n", newStu.id)
}
// 3. 展示学生
func (s *studentMgr) showStudent() {
for _, v := range s.allStudents {
fmt.Printf("学号:%d 姓名:%s 班级:%s\n", v.id, v.name, v.class)
}
}
包 packet
包名 定义
- 一个文件夹 == 一个包;
- 一个文件夹下,
- 所有.go文件的 第一行 packet 都相同
- .go文件的名字无所谓,xx.go
- 包名 可以 和 文件夹名 不一样
- 包名 不能 包含 “-” 中横线
- 包名 为 main 的 包 是程序 入口
- 不包含 main 包, 的源代码 , 不会得到 可执行文件
包的声明 === 文件夹 的名字
默认: 包的声明 === 文件夹 的名字
导入 包 $GOPATH/src 后面写起
打包生成 的二进制文件, 不需要在引入 packet, 引入的 源代码 已经被打包进去。
给包 起别名
匿名导入包 , 只调用 init() 函数
func init() {} 优先于 main 函数 , 多用于 初始化: 日志、加载配置文件
全局声明 --> init() --> main()
init() 执行顺序
同一个包 , 多个文件 , 可以相互调用
接口 : 是一个类型, 一个抽象的类型
接口类型
接口是一种由程序员来定义的类型,一个接口类型就是一组方法的集合,它规定了需要实现的所有方法。
相较于使用结构体类型,当我们使用接口类型说明相比于它是什么更关心它能做什么。
接口的定义
每个接口类型由任意个方法签名组成,接口的定义格式如下:
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
其中:
接口类型名:Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有关闭操作的接口叫closer等。接口名最好要能突出该接口的类型含义。
方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。
举个例子,定义一个包含Write方法的Writer接口。
type Writer interface{
Write([]byte) error
}
当你看到一个Writer接口类型的值时,你不知道它是什么,唯一知道的就是可以通过调用它的Write方法来做一些事情。
struct 必须实现 接口 interface 的函数
不实现 interface 的 函数, 就报错
多态 : 类似于C++虚函数+父指针
指针 给 接口
使用 【值接受者】 实现接口
使用 【指针接受者】 实现接口
值p1 := person{} 就 不能存到 m接口 里面
值p2 := &person{} 可以 存到 m接口 里面
一个 struct 类型, 可以 实现 多个接口 (例子如下) ; 多个类型 也可实现 一个接口 (例子如上)
接口 的嵌套
空接口 : 作为 : 函数参数 ; map 扩展 值value
所有类型 都满足
可以接受 任意数据
不需要 提前 定义, 直接使用 interface{}
Println ( a … interface{} )
变长的函数参数 func f1(parms …int){
变长的函数参数
package main
import (
"fmt"
)
func f1(parms ...int){
for i,v := range parms {
fmt.Printf("%v %v\n",i,v)
}
}
func main() {
f1(0,1,2)
}
等价于下面代码
package main
import (
"fmt"
)
func f1(parms []int){
for i,v := range parms {
fmt.Printf("f2:%v %v\n",i,v)
}
}
func main() {
b := []int{0,1,2}
f1(b)
打散Slice , arr1 = append(arr1,arr2…)
打散Slice
package main
import (
"fmt"
)
func main() {
var arr1 []int
arr2 := []int{1,2,3}
arr1 = append(arr1,0)
arr1 = append(arr1,arr2...) //arr2... 将切片arr2打散成 ==> arr1 = append(arr1,1,2,3)
fmt.Printf("%v\n",arr1)
var arr3 []byte
arr3 = append(arr3,[]byte("hello")...)
fmt.Printf("%s\n",arr3)
}
底层实现
类型 断言 x.( string )
Switch 猜猜猜 (为什么,不先打印出来,先判断是true、“”,然后再猜?)
reflect 反射 (暂时跳过 ----------------------------------------- -----------------------------------------)
结构体 反射 (暂时跳过 ----------------------------------------- -----------------------------------------)
并发编程
goroutine 用户态 线程 、 轻量级 、 灵活 、 资源少 、 go 语言 调度 、 包装好的 线程池 启动一个个任务
channel 通信
goroutine
go关键字
Go语言中使用 goroutine 非常简单,只需要在函数或方法调用前加上go关键字就可以创建一个 goroutine ,从而让该函数或方法在新创建的 goroutine 中执行。
go f() // 创建一个新的 goroutine 运行函数f
匿名函数也支持使用go关键字创建 goroutine 去执行。
go func(){
// ...
}()
一个 goroutine 必定对应一个函数/方法,可以创建多个 goroutine 去执行相同的函数/方法。
等待
time.Sleep(time.Second)
wg.Wait()
package main
import (
"fmt"
"sync"
)
// 声明全局等待组变量
var wg sync.WaitGroup
func hello() {
fmt.Println("hello")
wg.Done() // 告知当前goroutine完成
}
func main() {
wg.Add(1) // 登记1个goroutine
go hello()
fmt.Println("你好")
wg.Wait() // 阻塞等待登记的goroutine完成
}
匿名函数
还是一个闭包、 包含外部函数变量 i
main 函数 i 都走到10000, 闭包 才想起 自己要做什么事情
传参
单核(一个先执行完) 、 多核(混在一起)
channel
make (chan int, 10)
无缓冲区 ,阻塞 死锁 ,同步通道: 当面交付 数据, 快递员交到你的手上
带缓冲区 , 异步通道: 驿站
长度、 容量、 len cap
ch1 ch2 同步三个
两种 从通道 取值 方式
单向通道
chan<-
<-chan
报错:
chan 异常 总结
关闭 后 , 读完数据, 返回 value, ok = ( int 0 , false ) = ( string ""空串 , false )