1. 认识 Go
1.1. 什么是 GoLang
Go 语言是 Google 支持的一种静态、编译型编程语言。
全名 Go 语言(Go Language),简称 GoLang。
1.2. Go 的核心特点
特点 | 描述 |
---|---|
⚡ 编译速度快 | 编译极快,几乎像脚本语言一样便捷 |
🧵 原生并发支持 | 通过 goroutine 和 channel 实现高效并发处理 |
✂️ 简洁语法 | 语法简洁,无冗余,容易学习 |
🧰 内置工具链 | 自带格式化、测试、文档等工具(如go fmt , go test ) |
🚀 性能接近 C/C++ | 编译为机器码,执行效率高 |
📦 标准库强大 | 网络、加密、HTTP、JSON、文件操作等开箱即用 |
🔒 静态类型语言 | 编译期检查类型错误,提升代码安全性 |
📦 易于部署 | 编译后生成单一二进制文件,部署方便 |
1.3. 主要设计者
- Robert Griesemer:瑞士计算机科学家
- Rob Pike:加拿大程序员
- Ken Thompson:美国计算机科学家
1.4. 官网
2. GoLang 开发环境搭建
2.1. 安装 SDK
SDK(软件开发工具包,Software Development Kit)
下载地址:https://go.dev/dl/
2.2. 安装验证
这里我用的是Windows Terminal:https://apps.microsoft.com/detail/9n0dx20hk701?hl=zh-CN&gl=CN
go version
输出:
go version go1.24.2 windows/amd64
2.3. 环境变量
go env
输出关键变量:
变量名 | 含义 |
---|---|
GOROOT | Go 的安装目录,即可执行文件所在的目录 |
GOPATH | 工作目录并不是项目所有目录,编译后的二进制文件存放地,import 包的搜索路径,主要包含bin 、pkg 、src |
GO111MODULE | 是否启用 Go Module 管理项目,需要有go.mod 和 go.sum 文件 |
GOPROXY | 下载依赖时的代理 |
2.4. 开发工具
JetBrains Toolbox App:轻松管理您的 IDE
下载地址:https://www.jetbrains.com/zh-cn/toolbox-app/- 安装 GoLand IDE
- 热重载 Go 应用的工具
air:https://github.com/air-verse/air/blob/master/README-zh_cn.md - Visual Studio Code https://code.visualstudio.com/
3. Go Modules 包管理 / Go Mod 项目管理
3.1. 了解大型项目结构
- 官方推荐项目布局:https://github.com/golang-standards/project-layout/blob/master/README_zh.md
- 多个包放入
cmd
文件夹中
3.2. 初始化项目
go mod init <项目名称>
go mod tidy // 整理依赖,生成 go.mod 和 go.sum
3.3. 编译二进制文件
go build <绝对路径>
3.4. 运行
go run <绝对路径>
# 或者
go run .
4. 注释与转义字符
4.1. 注释
字符 | 备注 |
---|---|
// | 单行 |
/*…*/ | 多行 |
// 单行注释
// 单行注释
// 单行注释
/*
多行注释
多行注释
多行注释
*/
4.2. 转义字符 (Escaped characters)
转义字符 | 含义 | 备注 |
---|---|---|
\" | 双引号 | |
\a | alert sound | 警报声(不常用) |
\b | backspace | 退格(不常用) |
\f | form feed | 换页(不常用) |
\n | new line | 换行 |
\r | carriage return | 回车替换 |
\t | tab | 制表符 |
\v | vertical tab | 垂直制表符(不常用) |
// EscapedCharacters 转义字符
func EscapedCharacters() {
fmt.Println("\n// 双引号")
fmt.Println("图灵祖师说:\"我不知道我是梦见自己是机器的图灵,还是梦见自己是图灵的机器\"")
fmt.Println("\n// 反斜线")
fmt.Println("\\\\电子邮件说\\项目已取消\\清理文档的时候\\我哭了")
fmt.Println("\n// 警报声")
fmt.Println("\a既使一个程序只有三行长,也总有一天需要去维护它\a")
fmt.Println("\n// 退格")
fmt.Println("三天不编程,,\b生活变得毫无意义") // 替换为空格
fmt.Println("\n// 换页")
fmt.Println("一个程序员正在写软件\f他的手指在键盘上飞舞\f程序编译时没有一条错误信息\f运行起来就如同一阵微风")
fmt.Println("\n// 回车")
fmt.Println("我的感官很悠闲,我的精神自由地按照它自己的直觉前进\r我的程序如同是自己在写自己") // 替换
fmt.Println("\n// 制表符")
fmt.Println("——\t两字\t两字\n\r三个字\t三个字\t三个字")
fmt.Println("\n// 纵向制表符")
fmt.Println("确实,有时候我会遇到难题。\v当我发现难题的时候,我会慢下来,安静地观察。\v然后我改变一行代码,困难就烟消云散")
}
输出:
// 双引号
图灵祖师说:"我不知道我是梦见自己是机器的图灵,还是梦见自己是图灵的机器"
// 反斜线
\\电子邮件说\项目已取消\清理文档的时候\我哭了
// 警报声
既使一个程序只有三行长,也总有一天需要去维护它
// 退格
三天不编程, 生活变得毫无意义
// 换页
一个程序员正在写软件
他的手指在键盘上飞舞
程序编译时没有一条错误信息
运行起来就如同一阵微风
// 回车
我的程序如同是自己在写自己由地按照它自己的直觉前进
// 制表符
—— 两字 两字
三个字 三个字 三个字
// 纵向制表符
确实,有时候我会遇到难题。
当我发现难题的时候,我会慢下来,安静地观察。
然后我改变一行代码,困难就烟消云散
5. 变量与常量
// VariableAndConstants 变量与常量
func VariableAndConstants() {
fmt.Println("\n// 变量")
// 单独定义
var v1 int
v1 = 1
var v2 int = 2
var v3 = 3
v4 := 4
// 批量定义
var (
v5 = 5
v6 int = 6
v7 int
)
fmt.Printf("v1=%v,v2=%v,v3=%v,v4=%v,v5=%v,v6=%v,v7=%v\n", v1, v2, v3, v4, v5, v6, v7)
fmt.Printf("v1=%T,v2=%T,v3=%T,v4=%T,v5=%T,v6=%T,v7=%T\n", v1, v2, v3, v4, v5, v6, v7)
fmt.Println("\n// 常量")
const (
c1 = 8
c2 = iota // 当前行数,从0开始
c3 = iota
c4 // 默认值为上一行的值
c5 = 12
c6
)
fmt.Printf("c1=%v,c2=%v,c3=%v,c4=%v,c5=%v,c6=%v\n", c1, c2, c3, c4, c5, c6)
fmt.Printf("c1=%T,c2=%T,c3=%T,c4=%T,c5=%T,c6=%T\n", c1, c2, c3, c4, c5, c6)
}
输出:
// 变量
v1=1,v2=2,v3=3,v4=4,v5=5,v6=6,v7=0
v1=int,v2=int,v3=int,v4=int,v5=int,v6=int,v7=int
// 常量
c1=8,c2=1,c3=2,c4=3,c5=12,c6=12
c1=int,c2=int,c3=int,c4=int,c5=int,c6=int
5.1. 全局定义
var GlobalVariable = 1 // 跨包全局变量
var globalVariable = 2 // 全局变量
const GlobalConstant = 1 // 跨包全局常量
const globalConstant = 2 // 跨包全局常量
// 这里不支持 v4 := 4 写法
6. 基本数据类型
Golang 中所有的值类型变量、常量都会在声明时被分配内存空间并被赋予默认值。
- bit: 比特位,信息的最小单位
- byte: 字节,1 byte = 8 bit,在 Go 中
byte
是uint8
的别名
6.1. 整数型
名称 | 长度 | 范围 | 默认值 |
---|---|---|---|
int8 | 8 bit | -128 ~ 127 | 0 |
uint8 | 8 bit | 0 ~ 255 | 0 |
int16 | 16 bit | -32768 ~ 32767 | 0 |
uint16 | 16 bit | 0 ~ 65535 | 0 |
int32 | 32 bit | -2147483648 ~ 2147483647 | 0 |
uint32 | 32 bit | 0 ~ 4294967295 | 0 |
int64 | 64 bit | -9223372036854775808 ~ 9223372036854775807 | 0 |
uint64 | 64 bit | 0 ~ 18446744073709551615 | 0 |
int | 32/64 bit | 与平台相关 | 0 |
uint | 32/64 bit | 与平台相关 | 0 |
- decimal:十进制,无需前缀
- binary:二进制,前缀
0b
/0B
- octal:八进制,前缀
0o
/0O
- hexadecimal:十六进制,前缀
0x
/0X
6.2. 浮点型 (floating point)
名称 | 长度 | 符号 + 指数 + 尾数 | 默认值 |
---|---|---|---|
float32 | 32 bit | 1 + 8 + 23 bits | 0 |
float64 | 64 bit | 1 + 11 + 52 bits | 0 |
6.3. 字符型
名称 | 长度 | 别名 | 编码 |
---|---|---|---|
byte | 8 bit | uint8 | ASCII |
rune | 32 bit | int32 | UTF-8(ASCII 的超集) |
// BasicDataType 基本数据类型
func BasicDataType() {
fmt.Println("\n// 字符型")
var (
c11 byte
c12 = '0'
c13 rune = 23454
)
fmt.Printf("c1的码值=%v,对应字符=%c,type为%T\n", c11, c11, c11)
fmt.Printf("c1的码值=%v,对应字符=%c,type为%T\n", c12, c12, c12)
fmt.Printf("c1的码值=%v,对应字符=%c,type为%T\n", c13, c13, c13)
}
输出:
// 字符型
c1的码值=0,对应字符=,type为uint8
c1的码值=48,对应字符=0,type为int32
c1的码值=23454,对应字符=实,type为int32
6.4. 布尔型
名称 | 长度 | 默认值 |
---|---|---|
bool | 1 bit | false |
6.5. 字符串
名称 | 长度 | 内部表示 | 默认值 |
---|---|---|---|
string | 使用len() 查看长度 | 底层为[]byte ,1 + 8 + 23 bits | "" |
6.6. 任意精度 (超过 64 bit)
使用 math/big
包,支持任意精度的整数、有理数、浮点数。
文档:https://pkg.go.dev/math/big
6.7. 数值类型转换
// 目标类型(被转换的数据)
n1 := int8(n2)
需要注意精度,溢出可能导致数据丢失
7. 指针
7.1. Go 中的指针
- 取址符:
&
(获取变量地址) - 取值符:
*
(访问地址指向的值) - 指针类型:
*T
表示指向 T 类型的指针
7.2. 值拷贝(值类型)
函数调用时会拷贝值,互不干扰:
// Pointer 指针
func Pointer() {
var src = 2025
increase(src)
fmt.Printf("调用后 src=%v, 内存地址=%v\n", src, &src)
}
// 自增方法
func increase(n int) {
n++
fmt.Printf("自增后 n=%v, 内存地址=%v\n", n, &n)
}
输出:
// 自增后 n=2026, 内存地址=0xc00000a110
调用后 src=2025, 内存地址=0xc00000a0f8
7.3. 值传递(引用类型)
传递指针可在函数内修改原值:
// Pointer 指针
func Pointer() {
var src = 2025
increase(&src)
fmt.Printf("调用后 src=%v, 内存地址=%v\n", src, &src)
}
// 自增方法
func increase(n *int) {
*n++
fmt.Printf("自增后 n=%v, 内存地址=%v\n", n, &n)
}
输出:
// 自增后 n=0xc00000a0f8, 内存地址=0xc000072060
调用后 src=2026, 内存地址=0xc00000a0f8
8. fmt
格式化字符 (fmt verbs)
8.1. 通用
字符 | 含义 | 备注 |
---|---|---|
%% | % | 转义 |
%v | value | 值 |
%T | Type | 数据类型 |
// FmtVerbs fmt格式字符
func FmtVerbs() {
fmt.Println("\n// 通用")
fmt.Printf("%%\n")
}
输出:
// 通用
%
8.2. 整数
字符 | 含义 | 备注 |
---|---|---|
%d | decimal | 十进制 |
%b | binary | 二进制(没有前缀) |
%o | octal | 八进制(没有前缀) |
%x | hexadecimal | 十六进制 a-f(没有前缀) |
%X | hexadecimal | 十六进制 A-F(没有前缀) |
%U | Unicode | U+四位16进制 int32 |
%c | character | Unicode 码值对应的字符 |
%q | quoted | 带单引号的字符 |
// FmtVerbs fmt格式字符
func FmtVerbs() {
fmt.Println("\n// 整数")
i := 123
fmt.Printf("i=%U\n", i)
fmt.Printf("i=%c\n", i)
fmt.Printf("i=%q\n", i)
}
输出:
// 整数
i=U+007B
i={
i='{'
8.3. 浮点数
字符 | 含义 |
---|---|
%f或%F | 小数格式 |
%.2f | 保留 2 位小数 (%.f 保留 0 位) |
%5f | 最小宽度为 5 的小数 |
%5.2f | 最小宽度为 5 的 2 位小数 |
%b | 指数为 2 的幂的无小数科学记数法 |
%e | 小写 e 科学计数法 |
%E | 大写 E 科学计数法 |
%g | 自动对宽度较大的数采用%e |
%G | 自动对宽度较大的数采用%E |
%x | 0x 十六进制科学记数法 |
%X | 0X 十六进制科学记数法 |
// FmtVerbs fmt格式字符
func FmtVerbs() {
fmt.Println("\n// 浮点数")
f := 123.456
fmt.Printf("f=%f\n", f)
fmt.Printf("f=%.2f\n", f)
fmt.Printf("f=%10.2f\n", f)
fmt.Printf("f=%b\n", f)
fmt.Printf("f=%E\n", f)
fmt.Printf("f=%X\n", f)
}
输出:
// 浮点数
f=123.456000
f=123.46
f= 123.46
f=8687443681197687p-46
f=1.234560E+02
f=0X1.EDD2F1A9FBE77P+06
8.4. 布尔
字符 | 含义 |
---|---|
%t | true 或 false 的单词 |
// FmtVerbs fmt格式字符
func FmtVerbs() {
fmt.Println("\n// 布尔")
fmt.Printf("b=%t\n", true)
fmt.Printf("b=%t\n", false)
}
输出:
// 布尔
b=true
b=false
8.5. 字符串或byte切片([]byte)
字符 | 含义 |
---|---|
%s | 按字符串输出 |
%q | 带双引号的字符串输出 |
%x | 每个 byte 按两位小写十六进制码值输出 |
%X | 每个 byte 按两位大写十六进制码值输出 |
// FmtVerbs fmt格式字符
func FmtVerbs() {
fmt.Println("\n// 字符串或byte切片")
s := "hello world"
fmt.Printf("s=%q\n", s)
fmt.Printf("s=%x\n", s)
}
输出:
// 字符串或byte切片
s="hello world"
s=68656c6c6f20776f726c64
8.6. 指针
字符 | 含义 |
---|---|
%p | 以 0x 开头的十六进制地址 |
// FmtVerbs fmt格式字符
func FmtVerbs() {
fmt.Println("\n// 指针")
p := "hello world"
fmt.Printf("s=%p\n", &p)
}
输出:
// 指针
s=0xc00009c080
9. 运算符
9.1. 算数运算符
运算符 | 含义 | 备注 |
---|---|---|
+ | 加 | 也可用于字符串拼接 |
- | 减 | |
* | 乘 | |
/ | 除 | |
% | 取余 | |
++ | 自增 | 不能作为表达式的一部分 |
-- | 自减 | 不能作为表达式的一部分 |
// Operator 运算符
func Operator() {
fmt.Println("\n// 算数运算符")
i := 123
i++ // i=i+1
fmt.Printf("i=%d\n", i)
fmt.Printf("8%%3=%d\n", 8%3)
}
输出:
// 算数运算符
i=124
8%3=2
9.2. 位运算符
运算符 | 含义 | 备注 | |
---|---|---|---|
>> | 右移 | 二进制向右移动 | |
<< | 左移 | 二进制向左移动 | |
& | 按位与 | 两位都为 1 则为 1 | |
` | ` | 按位或 | 任一位为 1 则为 1 |
^ | 按位异或 | 两位不相同则为 1 |
// Operator 运算符
func Operator() {
fmt.Println("\n// 位运算符")
var b uint8 = 0b00111100
fmt.Printf("b>>2=%b\n", b>>2)
fmt.Printf("b<<2=%b\n", b<<2)
var b1 uint8 = 0b00111100
var b2 uint8 = 0b11001111
fmt.Printf("b1&b2=%b\n", b1&b2) // 按位与
fmt.Printf("b1|b2=%b\n", b1|b2) // 按位或
fmt.Printf("b1^b2=%b\n", b1^b2) // 按位异或
}
输出:
// 位运算符
b>>2=1111
b<<2=11110000
b1&b2=1100
b1|b2=11111111
b1^b2=11110011
9.3. 赋值运算符
=
, +=
, -=
, *=
, /=
, %=
, >>=
, <<=
, &=
, |=
, ^=
9.4. 关系运算符
>
, >=
, <
, <=
, ==
, !=
9.5. 逻辑运算符
&&
, ||
, !
9.6. 地址运算符
&
, *
9.7. 运算优先级
- 括号
()
- 点操作
.
- 地址运算符
&
*
- 其他…
10. 流程控制
10.1. if...else
- 条件无需括号
- 注意自动插入分号规则
10.2. switch...case
- 可以替代多层
if...else if...else
case
尾部会自动加入break
,如需穿透使用fallthrough
default
可省略
10.3. for
循环
// For 循环
func For() {
fmt.Println("\n// 无限循环")
i := 1
for {
fmt.Print(i, "\t")
i++
if i == 11 {
fmt.Println()
break
}
}
fmt.Println("\n// 条件循环")
i = 1
for i < 11 {
fmt.Print(i, "\t")
i++
}
fmt.Println()
fmt.Println("\n// 标准循环")
for i := 1; i < 11; i++ {
fmt.Print(i, "\t")
}
fmt.Println()
}
输出:
// 无限循环
1 2 3 4 5 6 7 8 9 10
// 条件循环
1 2 3 4 5 6 7 8 9 10
// 标准循环
1 2 3 4 5 6 7 8 9 10
10.4. label
与 goto
// LabelAndGoto label与goto
func LabelAndGoto() {
fmt.Println("\n// 标签跳转")
outside:
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
fmt.Print("+ ")
if i == 9 && j == 4 {
break outside
}
}
fmt.Println()
}
fmt.Println()
}
输出:
// 标签跳转
+ + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + +
+ + + + +
11. 函数
func 函数名(参数1 类型, ...) (返回值类型...) {
return 返回值...
}
// 实例
func getSum(a int, b int) int {
return a + b
}
函数名称首字母大写可以被外部调用,同时注释需带函数名称前缀
同一个命名空间首字母小写私有,大写可以被外部调用,变量同理
11.1. 参数
- 实参:调用时传递的实际值
- 形参:定义函数时使用的参数
getSum(1, 2) // 1, 2 是实参
func getSum(a, b int) // a, b 是形参,相同类型可合并写法
不确定参数个数可使用可变参数:
func sum(nums ...int) int { /* ... */ }
11.2. 返回值
- 支持 0~多个返回值
- 可命名返回值,将它们视作函数顶部定义的变量
func getRes(a, b int) (sum int, difference int) {
sum = a + b
difference = a - b
return
}
rec1, rec2 := getRes(1, 2)
11.3. 函数是一种数据类型
函数名本质是指向其代码的指针常量。
getRes := func(a, b int) (int, int) {
return a + b, a - b
}
rec1, rec2 := getRes(1, 2)
fmt.Printf("rec1=%d, rec2=%d, getRes=%v, type=%T\n", rec1, rec2, getRes, getRes)
11.4. 匿名函数
rec1, rec2 := func(a, b int) (int, int) {
return a + b, a - b
}(1, 2)
fmt.Printf("rec1=%d, rec2=%d\n", rec1, rec2)
12. defer
/ init
函数 / 包初始化
12.1. defer
延迟执行
- 延迟调用的函数会被压入栈,
return
后按照 先进后出 顺序执行 - 延迟调用时其参数会立即求值
// Defer 延迟执行
func Defer() int {
fmt.Println("\n// 延迟执行")
f := deferUtil()
defer f(1)
defer f(2)
defer f(3)
return f(4)
}
// 延迟工具
func deferUtil() func(int) int {
i := 0
return func(x int) int {
i++
fmt.Printf("第%v次调用,i++后=%v\n", x, i)
return i
}
}
输出:
// Defer 延迟执行
func Defer() int {
fmt.Println("\n// 延迟执行")
f := deferUtil()
defer f(1)
defer f(2)
defer f(3)
return f(4)
}
// 延迟工具
func deferUtil() func(int) int {
i := 0
return func(x int) int {
i++
fmt.Printf("第%v次调用,i++后=%v\n", x, i)
return i
}
}
12.2. defer
+ recover
配合匿名函数进行错误捕捉与延迟处理。
// DeferRecover 捕获异常,不包含内部
func DeferRecover() {
fmt.Println("\n// 捕获异常")
defer func() {
if err := recover(); err != nil {
fmt.Println("========= err: ", err)
}
}()
fmt.Println("这里有输出")
testError(0)
fmt.Println("这里无输出")
}
// 测试报错
func testError(n int) {
fmt.Println(3 / n)
}
输出:
// 捕获异常
这里有输出
========= err: runtime error: integer divide by zero
12.3. init
函数
- 每个包可有多个
init
函数 执行顺序(按依赖关系):
- 被依赖包的全局变量
- 被依赖包的
init
函数 - ...
main
包的全局变量main
包的init
函数main
函数
13. 包 (Package)
- Go 程序由包构成,入口为
main
包 - 使用
import
引入包 - 可为包起别名
14. 数组
- 长度固定、元素类型相同的一组数据
- 数组是值类型,声明时有默认值
- 索引从 0 开始
14.1. 声明
- 左侧声明时长度不能为空,留空即为切片类型
- 数组长度是类型的一部分,无法改变
- 右侧可用
[...]
自动推导长度 - 最后一组也要加
,
// Array 数组
func Array() {
// 自动推导为 [3]int
var a = [...]int{
1,
456,
789,
}
a[0] = 123
fmt.Println("\n// for 遍历")
for i := 0; i < len(a); i++ {
fmt.Printf("a[%v]=%v\n", i, a[i])
}
}
输出:
// for 遍历
a[0]=123
a[1]=456
a[2]=789
14.2. for
+ range
遍历
// Array 数组
func Array() {
// 自动推导为 [3]int
var a = [...]int{
1,
456,
789,
}
a[0] = 123
fmt.Println("\n// for...range 遍历")
for i, v := range a {
fmt.Printf("a[%v]=%v\n", i, v)
}
}
暂时不用的参数可以用占位符"_"代替
14.3. 多维数组
// Array 数组
func Array() {
fmt.Println("\n// 多维数组")
var twoDimensionalArray [3][4]int = [3][4]int{
{1, 2, 3, 4},
{2, 3, 4, 5},
{3, 4, 5, 6},
}
for i, v := range twoDimensionalArray {
for i2, v2 := range v {
fmt.Printf("a[%v][%v]=%v\t", i, i2, v2)
}
fmt.Println()
}
}
输出:
// 多维数组
a[0][0]=1 a[0][1]=2 a[0][2]=3 a[0][3]=4
a[1][0]=2 a[1][1]=3 a[1][2]=4 a[1][3]=5
a[2][0]=3 a[2][1]=4 a[2][2]=5 a[2][3]=6
15. 切片(Slice)
- 切片是对数组的引用
- 切片本身并不存储任何数据,它只是描述了底层数组中的一段
- 索引从 0 开始
- 切片是引用类型,默认值为nil
- 遍历方式同数组
15.1. 声明
- 引用数组的一段
- 引用切片的一段*(将指向同一个底层数组)
- 分配内存空间make([]Type, len, cap)
- 长度len(s)和容量cap(s)
// Slice 切片
func Slice() {
fmt.Println("\n// 声明")
array := []int{1, 2, 3, 4, 5}
var s1 []int = array[0:len(array)] // [开始引用的index:结束引用的index+1],等效于[:]
s1[0] = 0
fmt.Println("array:", array)
s2 := s1[1:]
s2[0] = 0
fmt.Println("array:", array)
var s3 []int // 声明切片类型
fmt.Println("s3是否为空", s3 == nil) // 默认值为mil
s3 = make([]int, 3, 5) // 分配内存空间make([]Type, len, cap),cap留空默认与len相同
fmt.Printf("len(s3)=%v,cap(s3)%v\n", len(s3), cap(s3))
s4 := []int{1, 2, 3} // 由系统自动创建底层数组
fmt.Printf("len(s4)=%v,cap(s4)%v\n", len(s4), cap(s4))
}
输出:
// 声明
array: [0 2 3 4 5]
array: [0 0 3 4 5]
s3是否为空 true
len(s3)=3,cap(s3)5
len(s4)=3,cap(s4)3
15.2. 追加元素
append([]Type, …Type)
- 可以追加单个元素,也可以追加其他切片
// Slice 切片
func Slice() {
fmt.Println("\n// 追加元素")
s10 := []int{1, 2, 3}
fmt.Println("s10:", s10)
s11 := append(s10, 4, 5, 6)
fmt.Println("s11:", s11)
s12 := append(s10, s11...)
fmt.Println("s12", s12)
}
输出:
// 追加元素
s10: [1 2 3]
s11: [1 2 3 4 5 6]
s12 [1 2 3 1 2 3 4 5 6]
15.3. 复制数组
copy([]Type, []Type)
// Slice 切片
func Slice() {
fmt.Println("\n// 复制数组")
s20 := []int{1, 2, 3, 4, 5, 6}
s21 := []int{7, 8, 9}
copy(s20, s21)
fmt.Println("s20:", s20)
}
输出:
// 复制数组
s20: [7 8 9 4 5 6]
15.4. string
与 []byte
- 可相互转换
- 格式字符通用
- 可以直接用
for
+range
遍历字符串
// Slice 切片
func Slice() {
fmt.Println("\n// string与[]byte")
str := "hello 世界"
fmt.Printf("[]byte(str)=%v\n[]byte(str)=%s\n", []byte(str), []byte(str))
for i, v := range str {
fmt.Printf("str[%v]=\t%v=\t%c\n", i, v, v)
}
}
输出:
// string与[]byte
[]byte(str)=[104 101 108 108 111 32 228 184 150 231 149 140]
[]byte(str)=hello 世界
str[0]= 104= h
str[1]= 101= e
str[2]= 108= l
str[3]= 108= l
str[4]= 111= o
str[5]= 32=
str[6]= 19990= 世
str[9]= 30028= 界
15.5. 形参与切片
打印命令行菜单并返回index+1的工具函数
// Slice 切片
func Slice() {
fmt.Println("\n// 形参与切片")
key := selectByKey("注册", "登录", "退出")
fmt.Println("key:", key)
}
// 选择来自Key
func selectByKey(text ...string) (key int) {
for i, v := range text {
fmt.Printf("%v:%v\n", i+1, v)
}
fmt.Println("请选择:(数字)")
fmt.Scanln(&key)
return
}
输出:
// 形参与切片
1:注册
2:登录
3:退出
请选择:(数字)
2
key: 2
16. MAP(映射)
本质为无序键值对(key-value)
map是引用类型,默认值为nil
容量会自动增长
16.1. 声明
数据类型:map[key类型]value类型
通过 make
分配,可指定初始容量
// Map - MAP数据类型
func Map() {
fmt.Println("\n// 声明前")
var m1 map[string]string
fmt.Printf("m1=%v,m1==nil?%v\n", m1, m1 == nil)
fmt.Println("\n// make后")
m1 = make(map[string]string)
fmt.Printf("m1=%v,m1==nil?%v\n", m1, m1 == nil)
fmt.Println("\n// 定义值")
m1 = map[string]string{
"a": "b",
"c": "d",
}
fmt.Printf("m1=%v\n", m1)
fmt.Println("\n// 简写")
m2 := map[string]string{
"a": "b",
"c": "d",
}
fmt.Printf("m2=%v\n", m2)
// 混合类型
// info := map[string]any{
// "name": "张三",
// "age": 18,
// "nickname": "小张三",
// "desc": "这是一个描述",
// }
//
// fmt.Println(info)
}
输出:
// 声明前
m1=map[],m1==nil?true
// make后
m1=map[],m1==nil?false
// 定义值
m1=map[a:b c:d]
// 简写
m2=map[a:b c:d]
16.2. 修改/增加
通过 map[key]
// Map - MAP数据类型
func Map() {
fmt.Println("\n// 简写")
m2 := map[string]string{
"a": "b",
"c": "d",
}
fmt.Printf("m2=%v\n", m2)
fmt.Println("\n// 修改")
m2["a"] = "bb"
fmt.Printf("m2=%v\n", m2)
fmt.Println("\n// 新增")
m2["e"] = "f"
fmt.Printf("m2=%v\n", m2)
}
输出:
// 简写
m2=map[a:b c:d]
// 修改
m2=map[a:bb c:d]
// 新增
m2=map[a:bb c:d e:f]
16.3. 查找
通过双赋值检测某个key是否存在
// Map - MAP数据类型
func Map() {
fmt.Println("\n// 查找")
m3 := map[string]string{
"a": "b",
}
v, ok := m3["a"]
if ok {
fmt.Printf("key存在,value=%v\n", v)
} else {
fmt.Println("key不存在")
}
}
输出:
// 查找
key存在,value=b
16.4. 删除
- 通过delete(map, key)删除单个元素
- 删除所有key-value
// Map - MAP数据类型
func Map() {
fmt.Println("\n// 删除")
m4 := map[string]string{
"a": "b",
"c": "d",
"e": "f",
}
delete(m4, "c") // 删除指定key
fmt.Printf("m4=%v\n", m4)
m4 = make(map[string]string) // 删除全部key
fmt.Printf("m4=%v\n", m4)
}
输出:
// 删除
m4=map[a:b e:f]
m4=map[]
16.5. for
+ range
遍历
// Map - MAP数据类型
func Map() {
fmt.Println("\n// 遍历")
m5 := map[string]string{
"a": "b",
"c": "d",
"e": "f",
}
for k, v := range m5 {
fmt.Printf("m5[%v]=%v\n", k, v)
}
}
输出:
// 遍历
m5[e]=f
m5[a]=b
m5[c]=d
17. 自定义数据类型&类型别名
17.1. 类型定义
定义方法:type 自定义数据类型 底层数据类型
属于不同类型,混用需要类型转换
// TypeDefinition 类型定义
func TypeDefinition() {
fmt.Println("\n// 类型定义")
type dType string // 自定义类型
var dt dType = "hello"
fmt.Printf("dt=%v,type=%T\n", dt, dt)
var s1 string = "hello 世界" // 基本类型
dt = dType(s1) // 基本类型 转换 自定义类型
fmt.Printf("dt=%v,type=%T\n", dt, dt)
}
输出:
// 类型定义
dt=hello,type=note.dType
dt=hello 世界,type=note.dType
17.2. 类型别名
定义方法:type 自定义数据类型 = 底层数据类型
属于相同类型,混用无需类型转换
// TypeAlias 类型别名
func TypeAlias() {
fmt.Println("\n// 类型别名")
type aType = string
var at aType = "hello"
fmt.Printf("at=%v,type=%T\n", at, at)
}
输出:
// 类型别名
at=hello,type=string
18. 结构体
- 由一组字段构成的一种自定义数据类型
- type 结构体名 struct {字段名 字段类型}
18.1. 结构体字段
- 结构体可以没有字段
- 结构体字段通过“.”访问
// Struct 结构体
func Struct() {
type user struct {
id uint32
name string
}
fmt.Println("\n// 赋值")
var u1 user = user{
name: "张三",
}
u1.id = 1000
fmt.Printf("u1=%v\n", u1)
}
输出:
// 赋值
u1={1000 张三}
18.2. 结构体指针
注意 .
优先级高于 &
/ *
使用时可以简写(隐式间接引用)
可以使用 &
前缀快速声明结构体指针
// Struct 结构体
func Struct() {
type user struct {
id uint32
name string
}
fmt.Println("\n// 指针赋值")
var u2 *user = &user{
name: "李四",
}
(*u2).id = 1000 // 简写 u2.id = 1000
fmt.Printf("u2=%v\n", u2)
}
18.3. 继承
type 结构体名 struct {(*)被继承的结构体名}
// Struct 结构体
func Struct() {
type user1 struct {
id uint32
name string
}
type user2 struct {
user1
password string
}
}
18.4. 结构体标签
type 结构体名 struct {字段名 字段类型 \`标签:"字段别名"\`}
// Struct 结构体
func Struct() {
type Account struct {
Name string `json:"name"`
password string
}
}
19. 方法
- 与特定类型绑定的函数
- 类型的定义和方法需要在同一个包内
- func (接收参数名 类型)方法名(形参列表)返回值列表{}
- 接收参数可以使用指针
// User 结构体定义
type User struct {
id uint32
name string
}
// 值传递
func (u User) printName() {
fmt.Println("u.name=", u.name)
}
// 指针传递
func (u *User) setId(id uint32) {
(*u).id = id
}
// Method 方法
func Method() {
fmt.Println("\n// 方法")
u := User{
name: "hello",
}
u.printName()
u.setId(1000)
fmt.Println("u=", u)
}
输出:
// 方法
u.name= hello
u= {1000 hello}
20. 接口
- 特殊的数据类型
- 方法定义的集合
- 方法名(形参类型)返回值类型
- 提高代码的复用率
接口本身不能绑定方法,保存的是:值+原始类型
// 定义 文本 结构体
type textMes struct {
Text string
Type uint8
}
func (tm *textMes) setText() { tm.Text = "hello" }
func (tm *textMes) setType() { tm.Type = 1 }
// 定义 图片 结构体
type imgMes struct {
Img string
Type uint8
}
func (im *imgMes) setImg() { im.Img = "清明上河图" }
func (im *imgMes) setType() { im.Type = 2 }
// 接口
type mes interface {
setType()
}
func sendMes(m mes) {
// 结构体相同方法,直接使用接口中的函数
m.setType()
// 结构体不同方法,调用switch...case
switch mPtr := m.(type) {
case *textMes:
mPtr.setText()
case *imgMes:
mPtr.setImg()
}
fmt.Println("m=", m)
}
// Interface 接口
func Interface() {
fmt.Println("\n// 接口")
tm := textMes{}
sendMes(&tm)
im := imgMes{}
sendMes(&im)
}
输出:
// 接口
m= &{hello 1}
m= &{清明上河图 2}
20.1. 空接口
interface{}
空接口可保存任何类型
// Interface 接口
func Interface() {
fmt.Println("\n// 类型断言")
n1 := 1
n1interface := interface{}(n1) // 空接口
n2, ok := n1interface.(int)
if ok {
fmt.Println("n2=", n2)
} else {
fmt.Println("类型断言失败")
}
}
输出:
// 类型断言
n2= 1
20.2. nil问题
nil 值:有类型没有值,接口本身并不是 nil,可以处理
nil 接口:即没有保存值,也没有保存类型,使用时会报错
21. 协程(Goroutine)
- Goroutine 是 Go 运行时管理的轻量级线程
- 主线程结束时,协程会被中断,需要有效的阻塞机制
- 多个线程同时对同一个内存空间进行写操作会导致数据竞争,sync包可以解决此问题,互斥锁Mutex,但在 Go 中不常用,因为go中有更高效的信道channel来解决这个问题
var (
c int64 // 计数
lock sync.Mutex // 互斥锁
)
// 是否偶数
func ifEven(n int) {
if n%2 == 0 {
// 多线程操作同一变量会产生数据竞争,开启互斥锁
lock.Lock()
c++
lock.Unlock()
}
}
// Goroutine 线程
func Goroutine() {
fmt.Println("\n// 线程")
for i := 2; i <= 100000; i++ {
go ifEven(i)
}
// 命令行阻塞,防止线程提前退出
var key string
fmt.Scanln(&key)
fmt.Println("共找到:", c, "个")
}
输出:
// 线程
共找到: 50000 个
22. 信道(channel)
22.1. 声明与存取
channel
官方翻译为信道,是一种带有类型的管道- 引用类型,使用前需要
make(Type, (缓冲区容量))
- 不带缓冲区的管道必须结合结合协程使用
- 可以查看长度
len(channel)
或容量cap(channel)
- 存入:
channel <- value
- 取出:
value, (ok) <- channel
- 丢弃:
<- channel
- 先进先出,自动阻塞
- 数据需要保持流动,否则会阻死报错
func pushNum(c chan int) {
for i := 0; i <= 100; i++ {
c <- i
}
close(c) // 关闭信道,表示没有需要放入的值了
}
// Channel 信道
func Channel() {
fmt.Println("\n// 信道")
var c1 chan int = make(chan int) // 简写 c1 := make(chan int)
go pushNum(c1)
// 不断从信道接收值,直到它被关闭(缺乏close关闭机制会报错)
for {
v, ok := <-c1
if ok {
fmt.Printf("v=%v\t", v)
} else {
break
}
}
// for...range 简写
// for v := range c1 {
// fmt.Printf("v=%v\t", v)
// }
}
输出:
// 信道
v=0 v=1 v=2 v=3 v=4 v=5 v=6 v=7 v=8 v=9 v=10 v=11 v=12 v=13 v=14 v=15 v=16 v=17 v=18 v=19 v=20 v=21 v=22 v=23 v=24 v=25 v=26 v=27 v=28 v=29 v=30 v=31 v=32 v=33 v=34 v=35 v=36 v=37 v=38 v=39 v=40 v=41 v=42 v=43 v=44 v=45 v=46 v=47 v=48 v=49 v=50 v=51 v=52 v=53 v=54 v=55 v=56 v=57 v=58 v=59 v=60 v=61 v=62 v=63 v=64 v=65 v=66 v=67 v=68 v=69 v=70 v=71 v=72 v=73 v=74 v=75 v=76 v=77 v=78 v=79 v=80 v=81 v=82 v=83 v=84 v=85 v=86 v=87 v=88 v=89 v=90 v=91 v=92 v=93 v=94 v=95 v=96 v=97 v=98 v=99 v=100
22.2. select
...case
- 适用于无法确认合适关闭信道的情况,通常结合for循环使用
- select…case 会阻塞到某个分支可以继续执行时执行该分支,当没有可执行的分支时则执行 default 分支
// 是否偶数 管道方式
func ifEvenChan(n int, c chan int) {
if n%2 == 0 {
c <- n
}
}
// Channel 信道
func Channel() {
fmt.Println("\n// select...case")
var c2 chan int = make(chan int)
for i := 2; i <= 100000; i++ {
go ifEvenChan(i, c2)
}
var c int64
print: // 创建一个标签
for {
select {
case v := <-c2:
c++
fmt.Printf("v=%v\t", v)
default:
fmt.Println("\n共找到:", c, "个")
break print // 跳出for回到print标签
}
}
}