GolangNote
1. Go基础简介
Go(又称Golang)是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。
1.1 Go 特点
- 强类型静态语言
- 语法简单
- 垃圾回收
- 快速编译
- 简单的依赖管理
- 优秀的并发能力
1.2 Go 资源地址
- Go 官网:https://go.dev/
- 安装包下载地址: Go 安装包下载
- 国内镜像下载地址:study golang
- Go 模块包查找位置: Go package
1.3. Windows 安装
- 下载 go 安装包 msi 文件,按照提示进行安装
- 安装完成后重启所有终端或命令行
1.4. Linux 或 MacOS 安装和配置环境变量
- 下载go包
wget -c https://go.dev/dl/go1.19.5.linux-amd64.tar.gz - 删除之前的go安装包
rm -rf /usr/local/go - 将安装包解压到指定目录
tar -C /usr/local -xzf go1.19.5.linux-amd64.tar.gz - 配置环境变量
export PATH=$PATH:/usr/local/go/bin
1.5 卸载
- 删除 go 的安装目录,通常为
/usr/local/go - 删除 go 的环境变量,通常为
/etc/profile或者~/.bash_profile中的/usr/local/go/bin目录
1.6 配置
使用go命令配置:
go env -w GOPROXY=https://goproxy.cn,direct- 配置proxygo env -w GO111MODULE="on"- 配置使用go module,1.13版本后默认开启
使用环境变量配置
export GO111MODULE="on"- 配置使用go module,1.13版本后默认开启export GOPROXY=https://goproxy.io- 配置proxy
1.7 相关命令
go version- 查看go的版本go build- 项目目录下编译,在该项目目录下生成可执行文件go build 路径名- 在其他路径下执行go build,路径名从工程目录的scr开始往下写,在当前目录下生成可执行文件go build -o 自定义文件名- 自定义可执行文件文件名go run 源码文件名- 以脚本文件执行,不生成可执行文件go install- 类似分两步执行,第一步为执行go build,第二步为将可执行文件移动到GOPATH/bin文件夹下go env- 查看所有go的环境配置-w- 可以以NAME=VALUE的方式修改环境变量,例如:go env -w GO111MODULE=on-u- 将用-w设置的变量恢复为默认值,例如:go env -u GO111MODULE-json- 以json格式输出
go get- 下载安装第三方包-t- 让命令程序同时下载并安装指定的代码包中的测试源码文件中依赖的代码包。-u- 让命令利用网络来更新已有代码包及其依赖包。默认情况下,该命令只会从网络上下载本地不存在的代码包,而不会更新已有的代码包。
go mod- 使用go module管理三方包go clean- 命令会删除掉执行其它命令时产生的一些文件和目录
1.8 跨平台编译
命令格式
SET CGO_ENABLED=0 // 禁用CGO,使用cgo不支持跨平台,默认值为1
SET GOOS=linux // 目标平台是linux,windows写windows,mac写drawin
SET GOARCH=amd64 // 目标处理器架构是amd64
runtime.GOOS - 获取当前操作系统平台
1.9 注意事项
注释:
//- 行注释/* */- 块注释
大括号:Go语言规定,左括号必须与关键字在一行,不能放在其他位置。
for
{// 错误,无法通过编译
xxx
}
注意:{必须和关键字在同一行,如果有类似else的语句,else必须和上一个语句块的}在一行
1.10 go mod管理包
- 使用
go mod init 项目名称初始化项目,如go mod init github.com/stolenzc/hello_world vscode配置以下内容使用vendor依赖
"go.toolsEnvVars": { "GOFLAGS": "-mod=vendor" }使用
go mod tidy维护go.mod文件。- 使用
go build -mod=vendor使用vendor目录的依赖进行编译
1. 常量变量和标识符
标识符规则
数字、字母、下划线组成,数字不能开头
变量和函数命名规范遵循驼峰原则
可见性
- 声明在函数内部,是函数的本地值,仅在函数内部可用
- 声明在函数外部,对当前包可见,同一个包的所有
.go文件均可见 - 声明在函数外部且首字母是大写,对所有包可见,是全局值
关键字
25个关键字
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
37个保留字
Constants:
true false iota nil
Types:
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64
bool byte rune string error
Functions:
make len cap new append
copy close delete complex real
imag panic recover
声明
GO声明的四种方式
- var(声明变量)
- const(声明常量)
- type(声明类型)
- func(声明函数)
变量
变量声明
var name string // 默认声明
var (
name string
age int
) // 批量声明
var name string = "大锤" // 声明并初始化
var age = 18 // 自动识别补足类型
name := "大锤" // 短变量声明,不能用于全局变量
var name, age = "大锤", 18
_ - 匿名变量不需要声明,不分配内存,可以重复使用
注意
- 默认初始化值:int(0), float(0), string(""), boolean(false), 切片、函数、指针(nil)
- GO语言中局部变量声明后必须使用
- 匿名变量不会分配内存空间,可以重复声明
常量
常量声明用const,常量在声明时必须赋值:
声明
const pi = 3.14259265 //不需要类型声明,自动推导,必须赋值
const (
pi = 3.14
e = 2.7182
n
) // 多个常量声明,不写值默认等于上一行的值
iota - 常量计数器,const出现被置为零,每新增一行,iota值加1
// 遇到const就置零
const n5 = iota //0
const (
a, b = iota + 1, iota + 2 //1,2
c, d //2,3
e, f //3,4
)
类型
类型声明有两种方式
type byte = uint8 // 给uint8起别名 (此时两个类型可以互相赋值和直接使用,只是一个别名,不存在新的类型)
type Duration int64 // 定义一个新的类型 (此时两个类型互不相同,相应的变量无法直接替换使用)
数据类型
值类型和引用类型
值类型:
- int, int8, int16, int32, int64
- uint, uint8, uint16, uint32, uint64
- float32, float64
- complex64, complex128
- string, bool, array
引用类型:
- slice, map, chan
整型
| 类型 | 描述 |
|---|---|
| uint8 | 无符号 8位整型 (0 到 2^8-1) |
| uint16 | 无符号 16位整型 (0 到 2^16-1) |
| uint32 | 无符号 32位整型 (0 到 2^32-1) |
| uint64 | 无符号 64位整型 (0 到 2^64-1) |
| int8 | 有符号 8位整型 (-2^7 到 2^7-1) |
| int16 | 有符号 16位整型 (-2^15 到 2^15-1) |
| int32 | 有符号 32位整型 (-2^31 到 2^31-1) |
| int64 | 有符号 64位整型 (-2^63 到 2^63-1) |
| uint | 32位操作系统上就是uint32,64位操作系统上就是uint64 |
| int | 32位操作系统上就是int32,64位操作系统上就是int64 |
| uintptr | 无符号整型,用于存放一个指针 |
len(v Type) int- 返回数据类型占用的字节数0b开头表示二进制数,0o开头表示八进制数,0x开头表示十六进制数123_456允许用_分割数字,等同于123456
浮点型
| 类型 | 描述 |
|---|---|
| float32 | 最大范围3.4e38,可以使用math.MaxFloat32查看 |
| float64 | 最大范围1.8e308,可以说使用math.MaxFloat64查看 |
复数
| 类型 | 描述 |
|---|---|
| complex64 | complex64的实部和虚部为32位 |
| complex128 | complex128的实部和虚部为64位 |
real(c ComplexType) FloatType- 返回complex的实部imag(c ComplexType) FloatType- 返回complex的虚部
布尔
| 类型 | 描述 |
|---|---|
| bool | 有true和false两个值,不可参与其他类型运算,无法转换为其他类型 |
字符
| 类型 | 描述 |
|---|---|
| byte | uint8类型,代表了ASCII码的一个字符。 |
| rune | int32类型,代表一个 UTF-8字符。 |
字符串
注意:字符串不可变,修改字符串通常需要转换为字符的切片类型,[]byte或[]rune,然后进行修改。会重新进行内存分配。
| 类型 | 描述 |
|---|---|
| string | 需要用""包裹,多行字符串使用反引号`包裹,使用UTF-8编码 |
字符串转义
| 转义符 | 含义 |
|---|---|
\r |
回车符(返回行首) |
\n |
换行符(直接跳到下一行的同列位置) |
\t |
制表符 |
\' |
单引号 |
\" |
双引号 |
\\ |
反斜杠 |
字符串常用操作
| 方法 | 介绍 |
|---|---|
| len(str) | 求长度,获得的是字节长度 |
| +或fmt.Sprintf | 拼接字符串 |
- 字符串 str 的第 1 个字节:
str[0] - 第 i 个字节:
str[i-1] - 最后 1 个字节:
str[len(str)-1]
类型转换
类型(表达式) - 将表达式的结果转换为所指定的类型
格式化占位符号
| 符号 | 含义 |
|---|---|
%d %b %o |
表示 十/二/八 进制数 |
%x %X |
表示十六进制数,字母用 小/大 写 |
%f %.nf |
表示浮点数,n为保留小数位数 |
%U |
Unicode格式 |
%s %q |
输出字符串表示(string或[]byte),q为%s外加双引号围绕 |
%p |
指针,格式为0x开头的十六进制数 |
%t |
布尔值 |
%T |
相应值的类型 |
%v |
相应值的默认格式 |
%+v |
打印结构体会添加字段名 |
%#v |
相应值的go语法表示,类型+值 |
运算符
+、-、*、/、%、 - 加减乘除余
注意:++(自增)和--(自减)在Go语言中是单独的语句,并不是运算符
==、!=、>、>=、<、<= - 关系运算符
&&、||、! - 逻辑运算符
&、|、^、>>、<< - 位运算符(与、或、异或、右移、左移)
=、+=、-=、*=、\=、%=、<<=、>>=、&=、|=、^= - 赋值运算符(等、加等、减等、乘等、除等、余等、左移等、右移等、与等、或等、异或等)
指针
Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。
var intP *int- 定义一个指向整型的指针intP = &i1- 给指针变量赋值*intP- 获取i1的值
指针的一些特点:
- 指针的格式化标识符为
%p - 空指针值为nil
- go语言中无法进行指针运算
数组
注意:数组是定长的
声明:
//类型是[5]int,是一个数组类型,函数调用会进行拷贝操作,不会修改实参
var arr1 [5]int
//类型是*[5]int,是一个指针,函数调用会修改实参
var arr1 = new([5]int)
//创建并初始化
var arrAge = [5]int{18, 20, 15, 22, 16}
var arrLazy = [...]int{5, 6, 7, 8, 22} //实际为切片
var arrKeyValue = []string{3: "Chris", 4: "Ron"}//给指定下标赋值
for-range操作数组
//获取的分别是下标和值
for index, value := range arr{}
切片
切片是引用类型,长度可变
声明:
//不需要说明长度,未初始化之前默认为 nil,长度为 0
var identifier []type
//初始化
var slice1 []type = arr1[start:end]
var x = []int{2, 3, 5, 7, 11}
//用make创建一个切片,cap可不填。默认为len的长度
var slice1 []type = make([]type, len, cap)
var slice2 []int = new([100]int)[0:50]
注意:
- make(T)返回一个类型为T的初始值,适用于切片,map和channel
- new(T)返回一个类型为*T的内存地址,指向T,使用于数组和结构体
切片的方法:
len()- 计算切片的长度cap()- 切片的最长长度。等于切片的长度 + 数组除切片之外的长度copy(dst, src []T)- 将类型为 T 的切片从源地址 src 拷贝到目标地址 dst,覆盖 dst 的相关元素,并且返回拷贝的元素个数slice1 = append(slice1, silce2...)- 将切片2追加到切片1后a = append(a[:i], a[i+1:]...)- 删除位于索引 i 的元素a = append(a[:i], a[j:]...)- 切除切片 a 中从索引 i 至 j 位置的元素a = append(a, make([]T, j)...)- 为切片 a 扩展 j 个元素长度a = append(a[:i], append([]T{x}, a[i:]...)...)- 在索引 i 的位置插入元素 xa = append(a[:i], append(make([]T, j), a[i:]...)...)- 在索引 i 的位置插入长度为 j 的新切片a = append(a[:i], append(b, a[i:]...)...)- 在索引 i 的位置插入切片 b 的所有元素x, a = a[len(a)-1], a[:len(a)-1]- 取出位于切片 a 最末尾的元素 xa = append(a, x)- 将元素 x 追加到切片 a
Map
map是引用类型,用:=需要用make来分配内存, map是无序的
声明:
var map1 map[keytype]valuetype
var map1 map[string]int
//map可以不固定大小,也可以设定cap标明map的最大容量
var map2 := make(map[string]int, cap)
注意:
- 数组、切片和结构体不能作为 key,string、int、float、指针、接口类型可以
- map排序需要先把key取出来进行排序,在根据排序后的key来取value
map的方法:
len(map1)- 获取map的键值对数量val1, isPresent = map1[key1]- 判断map1是否存在key1这个键,isPresent为true表示存在,为false表示不存在delete(map1, key1)- 删除key1,不存在不会报错for key, value := range map1{}- for-range循环得到的是key和value,只取一个值得到的是key
流程控制
if
if condition1 {
// do something
} else if condition2 {
// do something else
} else {
// catch-all or default
}
- condition1 处可以包含多条语句
for
for 初始语句;条件表达式;结束语句{
循环体语句
}
- 初始语句和结束语句都可以省略,但是分号不能省略
- 当初始语句和结束语句同时省略时,可以省略分号
- 当for后没有条件时,为死循环
- for循环可以通过
break、goto、return、panic语句强制退出循环
for-range
for 值1, 值2 := range 值3{
循环体语句
}
for range遍历数组、切片、字符串、map 及通道(channel)- 数组、切片、字符串返回索引和值
- map返回键和值
- 通道(channel)只返回通道内的值
switch-case
func switchDemo1() {
finger := 3
switch finger {
case 1:
fmt.Println("大拇指")
case 2:
fmt.Println("食指")
case 3:
fmt.Println("中指")
case 4:
fmt.Println("无名指")
case 5:
fmt.Println("小拇指")
fallthrough
default:
fmt.Println("无效的输入!")
}
}
- case执行后不会继续向后执行
- 使用fallthrougt来继续向后执行,实现C语言默认的switch-case模式
- switch后面也可以不写任何值,默认为True进行case判断
标签与goto
标签:大写字母后面连接冒号。HERE:
goto加标签可以跳到标签处继续执行。goto HERE
Break
break语句可以结束for、switch和select的代码块。
break语句还可以在语句后面添加标签,表示退出某个标签对应的代码块,标签要求必须定义在对应的for、switch和 select的代码块上。
Continue
continue语句可以结束当前循环,开始下一次的循环迭代过程,仅限在for循环内使用。
在 continue语句后添加标签时,表示开始标签对应的循环。
十. 常用包介绍
1. bytes
bytes.Buffer - 一种长度可变的bytes,提供Read和Write方法
通过buffer串联字符串:
var buffer bytes.Buffer
buffer.WriteString(s)//s追加到buffer后面
s = buffer.String()//将buffer转换为字符串
2. sort
Ints(x []int)- 对int类型对切片排序IntsAreSorted(x []int) bool- 检查数组是否已经排序Float64s(x []float64)- 对float64元素排序Strings(x []string)- 排序字符串元素SearchInts(a []int, x int) int- 搜索n在a中的位置。使用的是二分法,先排序后搜索SearchFloat64s(a []float64, x float64) int- 搜索float64SearchStrings(a []string, x string) int- 搜索字符串
3. strings
HasPrefix(s, prefix string) bool- 判断s是否以prefix开头HasSuffix(s, suffix string) bool- 判断s是否以suffix结尾Contains(s, substr string) bool- 判断s是否包含substrIndex(s, substr string) int- 获取substr在s中第一次出现的索引,-1表示不存在LastIndex(s, substr string) int- 获取substr在s中第最后出现的索引,-1表示不存在IndexRune(s string, r rune) int- 查询非ASCII字符在s中的位置Replace(s, old, new string, n int) string- 将s中的old替换为new,n为替换次数,-1为全部替换Count(s, substr string) int- 计算substr在s中出现的非重叠次数Repeat(s string, count int) string- 返回s重复count次的字符串ToLower(s string) string- 将s中所有字符转为小写ToUpper(s string) string- 将s中所有字符转为大写Trim(s, cutset string) string- 剔除字符串开头和结尾的cutset字符TrimLeft(s, cutset string) string- 剔除字符串开头的cutset字符TrimRight(s, cutset string) string- 剔除字符串结尾的cutset字符TrimSpace(s string) string- 剔除字符串开头和结尾的空白符号Fields(s string) []string- 将s用连续的空格进行分割,返回字符串切片Split(s, sep string) []string- 将s用sep进行分割,返回字符串切片Join(elems []string, sep string) string- 将切片元素通过sep进行连接NewReader(str)- 生成一个Reader并读取字符串中的内容,返回一个指向该Reader的指针Read()- 实现io.Reader接口ReadByte()- 从字符串中读取下一个byteReadRune()- 从字符串中读取下一个rune
4. strconv
Itoa(i int) string返回数字 i 所表示的字符串类型的十进制数。FormatFloat(f float64, fmt byte, prec, bitSize int) string- 将浮点型转为字符串,fmt表示格式(b、e、f、g),prec表示精度,bitSize 32表示float32,64表示float64Atoi(s string) (i int, err error)- 将字符串转换为 int 型ParseFloat(s string, bitSize int) (f float64, err error)- 将字符串转换为 float64 型
5. time
Now() Time- 获取当前时间Sleep(d Duration)- 暂停当前的goroutine,负值或零值会导致立即返回func (t Time) Year() int- 返回t的年份func (t Time) Month() Month- 返回t的月份func (t Time) Day() int- 返回t的日期func (t Time) Hour() int- 返回t的小时,[0, 23]func (t Time) Minute() int- 返回t的分钟,[0, 59]func (t Time) Second() int- 返回t的秒,[0, 59]func (t Time) UTC() Time- 将时间转换为UTC时间func (t Time) Add(d Duration) Time- 返回t加上d后的时间func (t Time) Sub(d Duration) Time- 返回t减去d后的时间
6. unicode/utf8
RuneCount(p []byte) int- 返回p的rune数量RuneCountInString(s string) (n int)- 返回s中rune的数量
四. 函数
类型:普通函数,匿名函数,方法
1. 定义
func g() {
}
调用:pack1.Function(arg1, arg2, …, argn)
注意: go不支持函数重载
切片(slice)、字典(map)、接口(interface)、通道(channel)这样的引用类型都是默认使用引用传递。修改形参会影响实参的值
2. 命名返回值
func funcname(input int) (return1 int, return2 int){
//计算过程
return
}
注意:命名返回值return后不需要写返回内容,只有一个返回值时,命名返回值也需要括起来
3. 变长参数
func myfunc(a ...int){}
调用:
slice := []int{1, 2, 3, 4}
myfunc(slice...)
4. defer
将某个语句推迟到函数最后执行(return之后)。多个defer会以栈的形式执行(先进后出)
func f() {
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
}
// 4, 3, 2, 1, 0
5. 内置函数
| 名称 | 说明 |
|---|---|
| close | 用于管道通信 |
| len、cap | len 用于返回某个类型的长度或数量(字符串、数组、切片、map 和管道);cap 是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map) |
| new、make | new 和 make 均是用于分配内存:new 用于值类型和用户定义的类型,如自定义结构,make 用于内置引用类型(切片、map 和管道)。它们的用法就像是函数,但是将类型作为参数:new(type)、make(type)。new(T) 分配类型 T 的零值并返回其地址,也就是指向类型 T 的指针。它也可以被用于基本类型:v := new(int)。make(T) 返回类型 T 的初始化之后的值,因此它比 new 进行更多的工作new() 是一个函数,不要忘记它的括号 |
| copy、append | 用于复制和连接切片 |
| panic、recover | 两者均用于错误处理机制 |
| print、println | 底层打印函数,在部署环境中建议使用 fmt 包 |
| complex、real imag | 用于创建和操作复数 |
6. 匿名函数
func(x, y int) int { return x + y }
注意:匿名函数必须进行赋值或者被调用。否则会编译失败
7. 闭包
func main() {
var f = Adder()
fmt.Print(f(1), " - ")
fmt.Print(f(20), " - ")
fmt.Print(f(300))
//1 - 21 - 321
}
func Adder() func(int) int {
var x int
return func(delta int) int {
x += delta
return x
}
}
五. 包
1. 标准库
- unsafe: 包含了一些打破 Go 语言“类型安全”的命令,一般的程序中不会被使用,可用在 C/C++ 程序的调用中。
- syscall-os-os/exec
- os: 提供给我们一个平台无关性的操作系统功能接口,采用类Unix设计,隐藏了不同操作系统间差异,让不同的文件系统和操作系统对象表现一致。
- os/exec: 提供我们运行外部操作系统命令和程序的方式。
- syscall: 底层的外部包,提供了操作系统底层调用的基本接口。
- archive/tar 和 /zip-compress:压缩(解压缩)文件功能。
- fmt-io-bufio-path/filepath-flag
- fmt: 提供了格式化输入输出功能。
- io: 提供了基本输入输出功能,大多数是围绕系统功能的封装。
- bufio: 缓冲输入输出功能的封装。
- path/filepath: 用来操作在当前系统中的目标文件名路径。
- flag: 对命令行参数的操作。
- strings-strconv-unicode-regexp-bytes:
- strings: 提供对字符串的操作。
- strconv: 提供将字符串转换为基础类型的功能。
- unicode: 为 unicode 型的字符串提供特殊的功能。
- regexp: 正则表达式功能。
- bytes: 提供对字符型分片的操作。
- index/suffixarray: 子字符串快速查询。
- math-math/cmath-math/big-math/rand-sort:
- math: 基本的数学函数。
- math/cmath: 对复数的操作。
- math/rand: 伪随机数生成。需要设置随机种子才能实现不同的随机
- sort: 为数组排序和自定义集合。
- math/big: 大数的实现和计算。
- container-/list-ring-heap: 实现对集合的操作。
- list: 双链表。
- ring: 环形链表。
- time-log:
- time: 日期和时间的基本操作。
- log: 记录程序运行时产生的日志,我们将在后面的章节使用它。
- encoding/json-encoding/xml-text/template:
- encoding/json: 读取并解码和写入并编码 JSON 数据。
- encoding/xml:简单的 XML1.0 解析器,有关 JSON 和 XML 的实例请查阅第 12.9/10 章节。
- text/template:生成像 HTML 一样的数据与文本混合的数据驱动模板(参见第 15.7 节)。
- net-net/http-html:(参见第 15 章)
- net: 网络数据的基本操作。
- http: 提供了一个可扩展的 HTTP 服务器和基础客户端,解析 HTTP 请求和回复。
- html: HTML5 解析器。
- runtime: Go 程序运行时的交互操作,例如垃圾回收和协程创建。
- reflect: 实现通过程序运行时反射,让程序操作任意类型的变量。
2. regexp
正则表达式
ok, _ := regexp.Match(pat, []byte(searchIn)) - 简单模式匹配,ok返回true或flase
ok, _ := regexp.MatchString(pat, searchIn) - 简单匹配字符串
re, _ := regexp.Compile(pat) - 将正则表达式编译为一个regexp对象
str := re.ReplaceAllString(searchIn, "##.#") - 将匹配到的部分替换为"##.#"
str2 := re.ReplaceAllStringFunc(searchIn, f) - 参数为函数,匹配内容传入函数,替换为函数的返回内容
3. 锁和sync
import "sync"
type Info struct {
mu sync.Mutex
// ... other fields, e.g.: Str string
}
func Update(info *Info) {
//函数想要改变这个变量可以这样写
info.mu.Lock()
// critical section:
info.Str = // new value
// end critical section
info.mu.Unlock()
}
//通过 Mutex 来实现一个可以上锁的共享缓冲器
type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
}
4. 精密计算和big包
import (
"math"
"math/big"
)
im := big.NewInt(n)//构造大的整型数字
rm := big.NewRat(n, d)//构造大的有理数(n为分子,d为分母)
5. 自定义包和导入
导包格式
import "包的路径或 URL 地址"
//用点作为别名可以直接使用包内的项目不需要通过包名.项目名
import . "./pack1"
使用go install来安装外部包
6. 使用godoc
godoc -http=:6060 -goroot="."访问自动生成的doc网页
六. 结构和方法
1. 结构体
定义:
type identifier struct {
field1 type1 tag(可忽略,只有reflect可以使用tag内容)
field2 type2
...
}
声明结构体变量并赋值
type T struct {a, b int}
var s T
s.a = 5
s.b = 8
//new(Type)和&Type{}是等价的
ms := &struct1{10, 15.5, "Chris"}
//&不是必须的,没有&获取的时一个值类型,有&得到的是一个指针类型
intr := T{0, 3}
intr := T{b:5, a:1}
intr := T{b:5}
var ms struct1
ms = struct1{10, 15.5, "Chris"}
用new创建结构体返回新分配的内存的指针
var t *T = new(T)
var t *T
t = new(T)
t := new(T)
可以使用点给结构体字段赋值(选择器):structname.fieldname = value
结构体或指针都能使用选择器,但用new和&创建但对象本质还是指针。
type myStruct struct { i int }
var v myStruct // v是结构体类型变量
var p *myStruct // p是指向一个结构体类型变量的指针
v.i
p.i
结构体嵌套,如双向链表
type Node struct {
pr *Node
data float64
su *Node
}
查看结构体的内存占用:unsafe.Sizeof(T{})
注意:make只能用于slice、map、channels,不能用于结构体
匿名字段和内嵌结构体
结构体如果不设置字段名,那么默认使用结构体名称.变量类型来获取值,在一个结构体中对于每一种数据类型只能有一个匿名字段
结构体中包含结构体,其结构类似继承。
type A struct {
ax, ay int
}
type B struct {
A
bx, by float32
}
func main() {
b := B{A{1, 2}, 3.0, 4.0}
fmt.Println(b.ax, b.ay, b.bx, b.by)
fmt.Println(b.A)
}
变量名相同,类型相同会冲突。类型不同需要使用子结构体名来获取
type A struct {a int}
type B struct {a, b int}
type C struct {A; B}
var c C
//无法获取c.a
type D struct {B; b float32}
var d D
//可以获取d.b,因为类型不同,d.B.b获取整型b
2. 方法
定义:
func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... }
recv类似self或this,recv不能是接口类型,不能是指针类型,但是它可以是任何其他允许类型的指针。
方法和类型必须在同一个包里,所以不能在int、float等类型上定义方法。
如果结构体内的字段是私有的,外部包要访问该字段,需要写getter和setter方法,对于 setter 方法使用 Set 前缀,对于 getter 方法只使用成员名。
type Person struct {
firstName string
lastName string
}
func (p *Person) FirstName() string {
return p.firstName
}
func (p *Person) SetFirstName(newName string) {
p.firstName = newName
}
内存状态
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("%d Kb\n", m.Alloc / 1024)
//获取以kb为单位的内存总量
//释放内存
runtime.GC()
//对象在释放前执行的函数
runtime.SetFinalizer(obj, func(obj *typeObj))
七. 接口与反射
1. 接口
定义
接口定义了一组方法(方法集),但是这些方法不包含(实现)代码:它们没有被实现(它们是抽象的)。接口里也不能包含变量。只包含一个方法的接口的名字由方法名加 [e]r 后缀,不适用于er时以 able 结尾,或者以 I 开头
type Namer interface {
Method1(param_list) return_type
Method2(param_list) return_type
...
}
接口特性:
- 类型不需要显式声明它实现了某个接口:接口被隐式地实现。多个类型可以实现同一个接口。
- 实现某个接口的类型(除了实现接口方法外)可以有其他的方法。
- 一个类型可以实现多个接口。
- 接口类型可以包含一个实例的引用, 该实例的类型实现了此接口(接口是动态类型)。
- 即使接口在类型之后才定义,二者处于不同的包中,被单独编译:只要类型实现了接口中的方法,它就实现了此接口。
- 实现了接口类型的变量可以赋值给 接收者值
//接上段程序
type Person struct {
side float32
}
func (p *Person) Method1(param_list){
return return_value
}
func (p *Person) Method2(param_list){
return return_value
}
func main(){
p := new(Person)
var ai Namer
//实现了接口类型的变量可以赋值给接口接收者,此处相当于变量引用
ai = p
}
可以创建接口数组
//Shaper是接口,r、q是实现了该接口类型的变量。r、q类型可以不同
shapes := []Shaper{Shaper(r), Shaper(q)}
shapes := []Shaper{r, q}
接口的嵌套
一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。
接口类型的断言
//varI是一个接口变量,T是类型指针,v是varI转换到类型T的值。ok表示是否转换成功
if v, ok := varI.(T); ok {
// ...
}
也可以使用type-switch来检测
//type这儿是固定关键字,T1和T2是不同的类型,不允许使用fallthrough
switch t := varI.(type){
case *T1:
//do something
case *T2:
//do something
}
判断值是否实现了某个接口
//v表示值,Stringer表示接口名。
sv, ok := v.(Stringer);
显式声明实现了某个接口
type Fooer interface {
Foo()
ImplementsFooer()
}
type Bar struct{}
func (b Bar) ImplementsFooer() {}
func (b Bar) Foo() {}
空接口实现函数重载
//最后一个参数设置为空接口,允许传任何参数,即可实现函数重载
fmt.Printf(format string, a ...interface{}) (n int, errno error)
接口继承
一个类型包含(内嵌)另一个类型(实现了一个或多个接口)的指针时,这个类型就可以使用(另一个类型)所有的接口方法。
type Task struct {
Command string
*log.Logger
}
2. 反射
var x float64 = 3.4
reflect.TypeOf(x)//float64
reflect.ValueOf(x)//<float64 Value>
//进行拷贝赋值
v := reflect.ValueOf(x)
//Kind总是返回最底层类型
v.Kind() == reflect.Float64//true
v.Type()//float64
v.Float()//3.4
v.Interface()//3.4
//是否可以赋值,返回false
v.CanSet()
//设置其为可以赋值
v = v.Elem()
//赋值操作
v.SetFloat(3.1415)
//反射结构类型,遍历结构内的字段
value := reflect.ValueOf(secret)//secret为结构体
value.NumField()//用for循环进行遍历
包的使用
unsafe
unsafe.Pointer(x) - Pointer类型用于表示任意类型的指针,Pointer类型允许程序绕过类型系统读写任意内存
http
func NewServeMux() *ServeMux - 多路复用处理函数
func (mux *ServeMux) Handle(pattern string, handler Handler) - 注册给定匹配字符串的处理程序
gorm.io/gorm
go的数据库处理包
注意:需要_ "github.com/go-sql-driver/mysql" 和 "gorm.io/driver/mysql" 驱动支持
定义数据模型:
type Product struct {
gorm.Model
Code string
Price uint
}
protocol-buffers
grpc的格式定义方法
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3;
// required表示必填,optional可选项,repeated可重复项
// 使用repeated的效率不高,需要添加packed=true来提高效率
// repeated int32 samples = 4 [packed=true];
// 消息需要有唯一编码,reserved 编号或字段名表示保留该字段。
}
message SearchResponse {
// 定义消息的回复格式
}
五. Array数组
定义
var 数组变量名 [元素数量]元素类型
注意:数组的长度必须是常量,并且长度是数组类型的一部分。一旦定义,长度不能变。不同长度的数组也是不同的类型。
不赋值的变量默认为类型的默认值:int为0,bool为false,string为""
示例:
var intArray [3]int; // [0, 0, 0]
var numArray = [3]int{1, 2} // 用固定的值初始化变量
var numArray = [...]int{1, 2} // 自动推断数组长度
a := [...]int{1: 1, 3: 5} // 使用指定索引值的方式来初始化数组
遍历
用for循环
var a = [...]string{"北京", "上海", "深圳"}
for i := 0; i < len(a); i++ {
fmt.Println(a[i])
}
用for-range循环
var a = [...]string{"北京", "上海", "深圳"}
for index, value := range a {
fmt.Println(index, value)
}
注意:如果用for i := range a获取到的i是索引
方法
len(数组) - 获取数组的长度
注意
数组是值类型,赋值和传参会复制整个数组。因此改变副本的值,不会改变本身的值。
数组支持 “==“、”!=” 操作符,因为内存总是被初始化过的。
[n]*T表示指针数组,*[n]T表示数组指针 。
六. 切片
注意:切片是引用类型,内部结构包含地址、长度和容量,底层指向一个数组,使用前需要先开辟内存
定义
var 变量名 []类型
var a []string //声明一个字符串切片
var b = []int{} //声明一个整型切片并初始化
var c = []bool{false, true} //声明一个布尔切片并初始化
a[low : high : max] - 可以使用从数组切片的方式获得切片类型
方法
len(slice) - 获取切片的长度
cap(slice) - 获取切片底层的最大长度(注意:计算的是从切片开始位置到指向数组的最后的长度)
make([]类型, size, cap) - size表示切片中元素的数量,cap表示切片的容量,size和cap都可以不填。当切片的无法存放跟多的值会自动重新分配内存
len(s) == 0 - 判断切片是否为空
copy(slice1, slice2) - 将slice2的元素拷贝到slice1
append(slice1, slice2...) - 将slice2添加到slice1后面
append(slice1, int1, int2, int3) - 将后面的值添加到slice1后面
append(slice[:i], slice[i+1:]) - 将slice删除指定下标的方法
七. map
注意:map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用。
定义
map[KeyType]ValueType // 定义
make(map[KeyType]ValueType, [cap]) //分配空间,cap
userInfo := map[string]string{
"username": "user",
"password": "password",
} // 定义并赋值
scoreMap := make(map[string]int, 8)
scoreMap["张三"] = 90 // 创建键值或修改键的值
遍历
使用for-range
for key, value := range map1 {
// 执行语句
}
注意: 使用for key := range map1只能获取到map1的key
方法
value, ok := map1[key] - 判断key是否在map1中,如果在,ok返回true,不在返回false
delete(map1, key) - 删除map1中的key
八. 函数
定义
func 函数名(参数)(返回值){
函数体
}
参数和返回值
参数简写
func intSum(x, y int) int {
return x + y
}
可变参数
func funcName(x1 int, x2 int, x ...int) int {
// 函数体
}
注意:可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后加...来标识。
可变参数通常要作为函数的最后一个参数。
多参数返回:
func calc(x, y int) (int, int) {
sum := x + y
sub := x - y
return sum, sub
}
注意:多个返回值时参数后面的返回值必须用()将所有返回值包裹起来。
返回值命名:
func calc(x, y int) (sum, sub int) {
sum = x + y
sub = x - y
return
}
注意:返回值命名相当于函数的开头就定义了这两个变量,不需要再次进行定义
返回值补充:
func someFunc(x string) []int {
if x == "" {
return nil // 没必要返回[]int{}
}
...
}
变量作用域:
局部变量和全局变量冲突了。优先使用局部变量
函数类型与变量
定义一个函数类型
type calculation func(int, int) int
凡是满足该参数和返回值的函数都是该类型的函数,该类型的实例可以接受实现了该类型函数的赋值
func add(x, y int) int {
return x + y
}
var c calculation
c = add
高阶函数
高阶函数分为函数作为参数和函数作为返回值两部分。
函数作为参数
func add(x, y int) int {
return x + y
}
func calc(x, y int, op func(int, int) int) int {
return op(x, y)
}
func main() {
ret2 := calc(10, 20, add)
fmt.Println(ret2) //30
}
函数作为返回值
func do(s string) (func(int, int) int, error) {
switch s {
case "+":
return add, nil
case "-":
return sub, nil
default:
err := errors.New("无法识别的操作符")
return nil, err
}
}
匿名函数
没有函数名的函数称为匿名函数
func(参数)(返回值){
函数体
}
匿名函数执行需要复制给变量调用或者定义时执行
func main() {
// 将匿名函数保存到变量
add := func(x, y int) {
fmt.Println(x + y)
}
add(10, 20) // 通过变量调用匿名函数
//自执行函数:匿名函数定义完加()直接执行
func(x, y int) {
fmt.Println(x + y)
}(10, 20)
}
闭包
Gin框架
基础概念
源码地址:gin
优点:
- 速度快,类似python中的flask,小而快
- 中间件,支持中间件处理
- 路由,非常简单实现路由解析功能,支持路由组
- 内置渲染,支持JSON,XML和HTML的格式渲染
安装gin框架:
go get -u github.com/gin-gonic/gin
简单的gin程序
func main(){
engine := gin.Default()
engine.handle("GET", "/hello", func(context *gin.Context){
context.Writer.Write([]byte("hello, world"))
})
engine.run(":8090")
}
异常
os.Exit(1):
- 程序立即退出
- defer函数不会执行
log.Fatal():
- 打印日志
- 退出程序
- defer不会被执行
panic():
- 函数立刻停止
- defer函数执行
- 调用者收到panic
- 调用者执行defer函数
- 重复3、4步骤到最上层函数
- 执行panic行为
2. Go内置类型和函数
2.2 内置函数
append // 追加元素到数组、切片中,返回修改后的数组、切片
close // 主要用来关闭channel
delete // 从map中删除key对应的value
panic // 停止常规的goroutine (panic和recover:通常做错误处理)
recover // 允许程序定义goroutine的panic动作
real // 返回complex的实部
imag // 返回complex的虚部
make // 用来分配内存,返回Type本身(只能用于slice,map,channel)
new // 用来分配内存,主要用来分配值类型,如int,struct,返回指向type的指针
cap // 返回某个类型的最大容量(只能用于slice,map)
copy // 用于复制和连接slice,返回复制的数目
len // 求长度,如string,array,slice,map,channel,返回长度
print/println // 底层打印函数,部署环境中建议用fmt包
2.3 内置接口
error接口
type error interface{ // 只要实现了Error()函数,返回值为string的都实现了err接口
Error() string
}
3. init 和 main函数
3.1 init函数
init函数用于包(package)的初始化,主要有以下特征
- init函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量
- 每个包可以拥有多个init函数
- 包的每个源文件可以拥有多个init函数
- 同一个go文件的init调用顺序是从上到下的
- 同一个包中不同文件是按照文件名字符串比较从小到大顺序调用各文件中的init函数
- 不同包的init函数按照包导入的依赖关系决定该初始化函数的执行顺序
- init函数不能被其他函数调用,而是在main函数执行之前自动调用
3.2 main函数
main函数是Go语言程序的默认入口函数(主函数)
func main(){
// 函数体
}
3.3 init和main的异同
相同点:
- 两个函数在定义时不能有任何的参数和返回值,且Go程序自动调用
不同点:
- init可以应用于任意包中,且可以重复定义多个
- main函数只能用于main包中,且只能定义一个
4. 命令
- go env - 用于打印Go语言的环境信息。
- go run - 命令可以编译并运行命令源码文件。
- go get - 可以根据要求和实际情况从互联网上下载或更新指定的代码包及其依赖包,并对它们进行编译和安装。
- go build - 命令用于编译我们指定的源码文件或代码包以及它们的
- go install - 用于编译并安装指定的代码包及它们的依赖包。
- go clean - 命令会删除掉执行其它命令时产生的一些文件和目录。
- go doc - 命令可以打印附于Go语言程序实体上的文档。我们可以通过把程序实体的标识符作为该命令的参数来达到查看其文档的目的。
- go test - 命令用于对Go语言编写的程序进行测试。
- go list - 命令的作用是列出指定的代码包的信息。
- go fix - 会把指定代码包的所有Go语言源码文件中的旧版本代码修正为新版本的代码。
- go vet - 是一个用于检查Go语言源码中静态错误的简单工具。
- go tool pprof - 命令来交互式的访问概要文件的内容。
5. 运算符
算术运算符:
+、-、*、/、% - 加减乘除余
注意:++(自增)和--(自减)在Go语言中是单独的语句,并不是运算符
关系运算符:
==、!=、>、>=、<、<= - 相等、不等、大于、大于等、小于、小于等(返回bool值)
逻辑运算符:
&&、||、! - 与、或、非(返回bool值)
位运算符:
&、|、^、>>、<< - 与、或、异或、右移(除以2的n次方)、左移(乘2的n次方)
赋值运算符:
=、+=、-=、*=、\=、%=、<<=、>>=、&=、|=、^= - 等、加等、减等、乘等、除等、余等、左移等、右移等、与等、或等、异或等
6. 下划线
_是特殊标识符,用来忽略结果。不需要提前定义,可以接受任何类型,不占用内存空间
在import中,使用_命名包表示只调用该包的init函数
在代码中,_表示忽略变量或者占位使用
9. 数组
9.1 定义
var 数组变量名 [元素数量]元素类型
示例:
var intArray [3]int; // [0, 0, 0]
var numArray = [3]int{1, 2} // 用固定的值初始化变量
var numArray = [...]int{1, 2} // 自动推断数组长度
a := [...]int{1: 1, 3: 5} // 使用指定索引值的方式来初始化数组
9.2 遍历
用for循环
var a = [...]string{"北京", "上海", "深圳"}
for i := 0; i < len(a); i++ {
fmt.Println(a[i])
}
用for-range循环
var a = [...]string{"北京", "上海", "深圳"}
for index, value := range a {
fmt.Println(index, value)
}
9.3 方法
len(数组)- 获取数组的长度cap(数组)- 获取数组的长度(元素的数量)
9.4 注意
- 如果用
for i := range a获取到的i是索引 - 数组的长度必须是常量,并且长度是数组类型的一部分。一旦定义,长度不能变。不同长度的数组也是不同的类型。
- 不赋值的变量默认为类型的默认值:
int为0,bool为false,string为"" - 数组是值类型,赋值和传参会复制整个数组。因此改变副本的值,不会改变本身的值。
- 数组支持 “==“、”!=” 操作符,因为内存总是被初始化过的。
[n]*T表示指针数组,*[n]T表示数组指针 。
10. 切片
10.1 定义
var 变量名 []类型
var a []string //声明一个字符串切片
var b = []int{} //声明一个整型切片并初始化
var c = []bool{false, true} //声明一个布尔切片并初始化
10.2 方法
a[low : high : max] - 可以使用从数组切片的方式获得切片类型
len(slice) - 获取切片的长度
cap(slice) - 获取切片底层的最大长度(注意:计算的是从切片开始位置到指向数组的最后的长度)
make([]类型, len, cap) - len表示切片中元素的数量,cap表示切片的容量,len和cap都可以不填。当切片的无法存放跟多的值会自动重新分配内存
len(s) == 0 - 判断切片是否为空
copy(slice1, slice2) - 将slice2的元素拷贝到slice1
append(slice1, slice2...) - 将slice2添加到slice1后面
append(slice1, int1, int2, int3) - 将后面的值添加到slice1后面
append(slice[:i], slice[i+1:]) - 将slice删除指定下标的方法
10.3 注意
切片是引用类型,内部结构包含地址、长度和容量,底层指向一个数组,使用前需要先开辟内存
string底层就是一个byte的数组,也可以进行切片操作
s := []byte(str) // 中文字符使用[]rune(str)