# 1. 认识 Go ## 1.1. 什么是 GoLang Go 语言是 Google 支持的一种静态、编译型编程语言。 全名 Go 语言(Go Language),简称 GoLang。 - Github:https://github.com/golang/go - 官网:https://go.dev/ 或 https://golang.google.cn/ - 自学视频教程:https://www.bilibili.com/video/BV1s341147US - 自学视频文档:https://www.bilibili.com/read/readlist/rl496566  ## 1.2. Go 的核心特点 | 特点 | 描述 | | ---------------- | ------------------------------------------------------ | | ⚡ 编译速度快 | 编译极快,几乎像脚本语言一样便捷 | | 🧵 原生并发支持 | 通过 goroutine 和 channel 实现高效并发处理 | | ✂️ 简洁语法 | 语法简洁,无冗余,容易学习 | | 🧰 内置工具链 | 自带格式化、测试、文档等工具(如 `go fmt`, `go test`) | | 🚀 性能接近 C/C++ | 编译为机器码,执行效率高 | | 📦 标准库强大 | 网络、加密、HTTP、JSON、文件操作等开箱即用 | | 🔒 静态类型语言 | 编译期检查类型错误,提升代码安全性 | | 📦 易于部署 | 编译后生成单一二进制文件,部署方便 | ## 1.3. 主要设计者 - **Robert Griesemer**:瑞士计算机科学家 - **Rob Pike**:加拿大程序员 - **Ken Thompson**:美国计算机科学家 --- # 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 go version ``` **输出:** ```go go version go1.24.2 windows/amd64 ``` ## 2.3. 环境变量 ```go 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 go mod init <项目名称> go mod tidy // 整理依赖,生成 go.mod 和 go.sum ``` ## 3.3. 编译二进制文件 ```go go build <绝对路径> ``` ## 3.4. 运行 ```go go run <绝对路径> # 或者 go run . ``` --- # 4. 注释与转义字符 ## 4.1. 注释 | 字符 | 备注 | | :-----: | ---- | | `//` | 单行 | | `/*…*/` | 多行 | ```go // 单行注释 // 单行注释 // 单行注释 /* 多行注释 多行注释 多行注释 */ ``` ## 4.2. 转义字符 (Escaped characters) | 转义字符 | 含义 | 备注 | | :------: | :-------------: | -------------------- | | `\"` | 双引号 | | | `\a` | alert sound | 警报声(不常用) | | `\b` | backspace | 退格(不常用) | | `\f` | form feed | 换页(不常用) | | `\n` | new line | 换行 | | `\r` | carriage return | 回车替换 | | `\t` | tab | 制表符 | | `\v` | vertical tab | 垂直制表符(不常用) | ```go // 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然后我改变一行代码,困难就烟消云散") } ``` **输出:** ```go // 双引号 图灵祖师说:"我不知道我是梦见自己是机器的图灵,还是梦见自己是图灵的机器" // 反斜线 \\电子邮件说\项目已取消\清理文档的时候\我哭了 // 警报声 既使一个程序只有三行长,也总有一天需要去维护它 // 退格 三天不编程, 生活变得毫无意义 // 换页 一个程序员正在写软件 他的手指在键盘上飞舞 程序编译时没有一条错误信息 运行起来就如同一阵微风 // 回车 我的程序如同是自己在写自己由地按照它自己的直觉前进 // 制表符 —— 两字 两字 三个字 三个字 三个字 // 纵向制表符 确实,有时候我会遇到难题。 当我发现难题的时候,我会慢下来,安静地观察。 然后我改变一行代码,困难就烟消云散 ``` --- # 5. 变量与常量 ```go // 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) } ``` **输出:** ```go // 变量 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. 全局定义 ```go 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 的超集) | ```go // 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) } ``` **输出:** ```go // 字符型 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. 数值类型转换 ```go // 目标类型(被转换的数据) n1 := int8(n2) ``` > 需要注意精度,溢出可能导致数据丢失 --- # 7. 指针 ## 7.1. Go 中的指针 - **取址符**:`&`(获取变量地址) - **取值符**:`*`(访问地址指向的值) - **指针类型**:`*T` 表示指向 T 类型的指针 ## 7.2. 值拷贝(值类型) 函数调用时会拷贝值,互不干扰: ```go // 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) } ``` **输出:** ```go // 自增后 n=2026, 内存地址=0xc00000a110 调用后 src=2025, 内存地址=0xc00000a0f8 ``` ## 7.3. 值传递(引用类型) 传递指针可在函数内修改原值: ```go // 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) } ``` **输出:** ```go // 自增后 n=0xc00000a0f8, 内存地址=0xc000072060 调用后 src=2026, 内存地址=0xc00000a0f8 ``` --- # 8. `fmt` 格式化字符 (fmt verbs) ## 8.1. 通用 | 字符 | 含义 | 备注 | | :---: | :---: | -------- | | `%%` | `%` | 转义 | | `%v` | value | 值 | | `%T` | Type | 数据类型 | ```go // FmtVerbs fmt格式字符 func FmtVerbs() { fmt.Println("\n// 通用") fmt.Printf("%%\n") } ``` **输出:** ```go // 通用 % ``` ## 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 | 带单引号的字符 | ```go // 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) } ``` **输出:** ```go // 整数 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 十六进制科学记数法 | ```go // 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) } ``` **输出:** ```go // 浮点数 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 的单词 | ```go // FmtVerbs fmt格式字符 func FmtVerbs() { fmt.Println("\n// 布尔") fmt.Printf("b=%t\n", true) fmt.Printf("b=%t\n", false) } ``` **输出:** ```go // 布尔 b=true b=false ``` ## 8.5. 字符串或byte切片([]byte) | 字符 | 含义 | | :---: | :----------------------------------: | | `%s` | 按字符串输出 | | `%q` | 带双引号的字符串输出 | | `%x` | 每个 byte 按两位小写十六进制码值输出 | | `%X` | 每个 byte 按两位大写十六进制码值输出 | ```go // FmtVerbs fmt格式字符 func FmtVerbs() { fmt.Println("\n// 字符串或byte切片") s := "hello world" fmt.Printf("s=%q\n", s) fmt.Printf("s=%x\n", s) } ``` **输出:** ```go // 字符串或byte切片 s="hello world" s=68656c6c6f20776f726c64 ``` ## 8.6. 指针 | 字符 | 含义 | | :---: | :----------------------: | | `%p` | 以 0x 开头的十六进制地址 | ```go // FmtVerbs fmt格式字符 func FmtVerbs() { fmt.Println("\n// 指针") p := "hello world" fmt.Printf("s=%p\n", &p) } ``` **输出:** ```go // 指针 s=0xc00009c080 ``` --- # 9. 运算符 ## 9.1. 算数运算符 | 运算符 | 含义 | 备注 | | :----: | :---: | ---------------------- | | `+` | 加 | 也可用于字符串拼接 | | `-` | 减 | | | `*` | 乘 | | | `/` | 除 | | | `%` | 取余 | | | `++` | 自增 | 不能作为表达式的一部分 | | `--` | 自减 | 不能作为表达式的一部分 | ```go // 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) } ``` **输出:** ```go // 算数运算符 i=124 8%3=2 ``` ## 9.2. 位运算符 | 运算符 | 含义 | 备注 | | :----: | :------: | ----------------- | | `>>` | 右移 | 二进制向右移动 | | `<<` | 左移 | 二进制向左移动 | | `&` | 按位与 | 两位都为 1 则为 1 | | `\|` | 按位或 | 任一位为 1 则为 1 | | `^` | 按位异或 | 两位不相同则为 1 | ```go // 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) // 按位异或 } ``` **输出:** ```go // 位运算符 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. 运算优先级 1. 括号 `()` 2. 点操作 `.` 3. 地址运算符 `&` `*` 4. 其他… --- # 10. 流程控制 ## 10.1. `if...else` - 条件无需括号 - 注意自动插入分号规则 ## 10.2. `switch...case` - 可以替代多层 `if...else if...else` - `case` 尾部会自动加入 `break`,如需穿透使用 `fallthrough` - `default` 可省略 ## 10.3. `for` 循环 ```go // 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() } ``` **输出:** ```go // 无限循环 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` ```go // 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() } ``` **输出:** ```go // 标签跳转 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ``` --- # 11. 函数 ```go func 函数名(参数1 类型, ...) (返回值类型...) { return 返回值... } // 实例 func getSum(a int, b int) int { return a + b } ``` > 函数名称首字母大写可以被外部调用,同时注释需带函数名称前缀 > 同一个命名空间首字母小写私有,大写可以被外部调用,变量同理 ## 11.1. 参数 - **实参**:调用时传递的实际值 - **形参**:定义函数时使用的参数 ```go getSum(1, 2) // 1, 2 是实参 func getSum(a, b int) // a, b 是形参,相同类型可合并写法 ``` 不确定参数个数可使用可变参数: ```go func sum(nums ...int) int { /* ... */ } ``` ## 11.2. 返回值 - 支持 0~多个返回值 - 可命名返回值,将它们视作函数顶部定义的变量 ```go func getRes(a, b int) (sum int, difference int) { sum = a + b difference = a - b return } rec1, rec2 := getRes(1, 2) ``` ## 11.3. 函数是一种数据类型 函数名本质是指向其代码的指针常量。 ```go 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. 匿名函数 ```go 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` 后按照 **先进后出** 顺序执行 - 延迟调用时其参数会立即求值 ```go // 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 } } ``` **输出:** ```go // 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` 配合匿名函数进行错误捕捉与延迟处理。 ```go // 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) } ``` **输出:** ```go // 捕获异常 这里有输出 ========= err: runtime error: integer divide by zero ``` ## 12.3. `init` 函数 - 每个包可有多个 `init` 函数 - 执行顺序(按依赖关系): 1. 被依赖包的全局变量 2. 被依赖包的 `init` 函数 3. ... 4. `main` 包的全局变量 5. `main` 包的 `init` 函数 6. `main` 函数 --- # 13. 包 (Package) 1. Go 程序由包构成,入口为 `main` 包 2. 使用 `import` 引入包 3. 可为包起别名 --- # 14. 数组 - 长度固定、元素类型相同的一组数据 - 数组是值类型,声明时有默认值 - 索引从 0 开始 ## 14.1. 声明 1. 左侧声明时长度不能为空,留空即为切片类型 2. 数组长度是类型的一部分,无法改变 3. 右侧可用 `[...]` 自动推导长度 4. 最后一组也要加`,` ```go // 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]) } } ``` **输出:** ```go // for 遍历 a[0]=123 a[1]=456 a[2]=789 ``` ## 14.2. `for` + `range` 遍历 ```go // 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. 多维数组 ```go // 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() } } ``` **输出:** ```go // 多维数组 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. 声明 1. 引用数组的一段 2. 引用切片的一段*(将指向同一个底层数组) 3. 分配内存空间make([]Type, len, cap) 4. 长度len(s)和容量cap(s) ```go // 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)) } ``` **输出:** ```go // 声明 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. 追加元素 1. `append([]Type, …Type)` 2. 可以追加单个元素,也可以追加其他切片 ```go // 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) } ``` **输出:** ```go // 追加元素 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) ```go // 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) } ``` **输出:** ```go // 复制数组 s20: [7 8 9 4 5 6] ``` ## 15.4. `string` 与 `[]byte` 1. 可相互转换 2. 格式字符通用 3. 可以直接用 `for` + `range` 遍历字符串 ```go // 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) } } ``` **输出:** ```go // 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的工具函数 ```go // 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 } ``` **输出:** ```go // 形参与切片 1:注册 2:登录 3:退出 请选择:(数字) 2 key: 2 ``` --- # 16. MAP(集合) 本质为无序键值对(key-value) map是引用类型,默认值为`nil` 容量会自动增长 ## 16.1. 声明 数据类型:`map[key类型]value类型` 通过 `make` 分配,可指定初始容量 ```go // 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) } ``` **输出:** ```go // 声明前 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]` ```go // 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) } ``` **输出:** ```go // 简写 m2=map[a:b c:d] // 修改 m2=map[a:bb c:d] // 新增 m2=map[a:bb c:d e:f] ``` ## 16.3. 查找 通过双赋值检测某个key是否存在 ```go // 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不存在") } } ``` **输出:** ```go // 查找 key存在,value=b ``` ## 16.4. 删除 1. 通过delete(map, key)删除单个元素 2. 删除所有key-value ```go // 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) } ``` **输出:** ```go // 删除 m4=map[a:b e:f] m4=map[] ``` ## 16.5. `for` + `range` 遍历 ```go // 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) } } ``` **输出:** ```go // 遍历 m5[e]=f m5[a]=b m5[c]=d ``` --- # 17. 自定义数据类型&类型别名 ## 17.1. 类型定义 定义方法:type 自定义数据类型 底层数据类型 属于不同类型,混用需要类型转换 ```go // 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) } ``` **输出:** ```go // 类型定义 dt=hello,type=note.dType dt=hello 世界,type=note.dType ``` ## 17.2. 类型别名 定义方法:type 自定义数据类型 = 底层数据类型 属于相同类型,混用无需类型转换 ```go // TypeAlias 类型别名 func TypeAlias() { fmt.Println("\n// 类型别名") type aType = string var at aType = "hello" fmt.Printf("at=%v,type=%T\n", at, at) } ``` **输出:** ```go // 类型别名 at=hello,type=string ``` --- # 18. 结构体 - 由一组字段构成的一种自定义数据类型 - type 结构体名 struct {字段名 字段类型} ## 18.1. 结构体字段 1. 结构体可以没有字段 2. 结构体字段通过“.”访问 ```go // 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) } ``` **输出:** ```go // 赋值 u1={1000 张三} ``` ## 18.2. 结构体指针 注意 `.` 优先级高于 `&` / `*` 使用时可以简写(隐式间接引用) 可以使用 `&` 前缀快速声明结构体指针 ```go // 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 {(*)被继承的结构体名} ```go // Struct 结构体 func Struct() { type user1 struct { id uint32 name string } type user2 struct { user1 password string } } ``` ## 18.4. 结构体标签 type 结构体名 struct {字段名 字段类型 \`标签:"字段别名"\`} ```go // Struct 结构体 func Struct() { type Account struct { Name string `json:"name"` password string } } ``` --- # 19. 方法 1. 与特定类型绑定的函数 2. 类型的定义和方法需要在同一个包内 3. func (接收参数名 类型)方法名(形参列表)返回值列表{} 4. 接收参数可以使用指针 ```go // 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) } ``` **输出:** ```go // 方法 u.name= hello u= {1000 hello} ``` --- # 20. 接口 - 特殊的数据类型 - 方法定义的集合 - 方法名(形参类型)返回值类型 - 提高代码的复用率 > 接口本身不能绑定方法,保存的是:值+原始类型 ```go // 定义 文本 结构体 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) } ``` **输出:** ```go // 接口 m= &{hello 1} m= &{清明上河图 2} ``` ## 20.1. 空接口 `interface{}` 空接口可保存任何类型 ```go // Interface 接口 func Interface() { fmt.Println("\n// 类型断言") n1 := 1 n1interface := interface{}(n1) // 空接口 n2, ok := n1interface.(int) if ok { fmt.Println("n2=", n2) } else { fmt.Println("类型断言失败") } } ``` **输出:** ```go // 类型断言 n2= 1 ``` ## 20.2. nil问题 nil 值:有类型没有值,接口本身并不是 nil,可以处理 nil 接口:即没有保存值,也没有保存类型,使用时会报错 --- # 21. 协程(Goroutine) - Goroutine 是 Go 运行时管理的轻量级线程 - 主线程结束时,协程会被中断,需要有效的阻塞机制 - 多个线程同时对同一个内存空间进行写操作会导致数据竞争,sync包可以解决此问题,互斥锁Mutex,但在 Go 中不常用,因为go中有更高效的信道channel来解决这个问题 ```go 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, "个") } ``` **输出:** ```go // 线程 共找到: 50000 个 ``` --- # 22. 信道(channel) ## 22.1. 声明与存取 1. `channel` 官方翻译为信道,是一种带有类型的管道 2. 引用类型,使用前需要 `make(Type, (缓冲区容量))` 3. 不带缓冲区的管道必须结合结合协程使用 4. 可以查看长度 `len(channel)` 或容量 `cap(channel)` 5. 存入: `channel <- value` 6. 取出: `value, (ok) <- channel` 7. 丢弃: `<- channel` 8. 先进先出,自动阻塞 9. 数据需要保持流动,否则会阻死报错 ```go 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) // } } ``` **输出:** ```go // 信道 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` 1. 适用于无法确认合适关闭信道的情况,通常结合for循环使用 2. select…case 会阻塞到某个分支可以继续执行时执行该分支,当没有可执行的分支时则执行 default 分支 ```go // 是否偶数 管道方式 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标签 } } } ``` Loading... # 1. 认识 Go ## 1.1. 什么是 GoLang Go 语言是 Google 支持的一种静态、编译型编程语言。 全名 Go 语言(Go Language),简称 GoLang。 - Github:https://github.com/golang/go - 官网:https://go.dev/ 或 https://golang.google.cn/ - 自学视频教程:https://www.bilibili.com/video/BV1s341147US - 自学视频文档:https://www.bilibili.com/read/readlist/rl496566  ## 1.2. Go 的核心特点 | 特点 | 描述 | | ---------------- | ------------------------------------------------------ | | ⚡ 编译速度快 | 编译极快,几乎像脚本语言一样便捷 | | 🧵 原生并发支持 | 通过 goroutine 和 channel 实现高效并发处理 | | ✂️ 简洁语法 | 语法简洁,无冗余,容易学习 | | 🧰 内置工具链 | 自带格式化、测试、文档等工具(如 `go fmt`, `go test`) | | 🚀 性能接近 C/C++ | 编译为机器码,执行效率高 | | 📦 标准库强大 | 网络、加密、HTTP、JSON、文件操作等开箱即用 | | 🔒 静态类型语言 | 编译期检查类型错误,提升代码安全性 | | 📦 易于部署 | 编译后生成单一二进制文件,部署方便 | ## 1.3. 主要设计者 - **Robert Griesemer**:瑞士计算机科学家 - **Rob Pike**:加拿大程序员 - **Ken Thompson**:美国计算机科学家 --- # 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 go version ``` **输出:** ```go go version go1.24.2 windows/amd64 ``` ## 2.3. 环境变量 ```go 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 go mod init <项目名称> go mod tidy // 整理依赖,生成 go.mod 和 go.sum ``` ## 3.3. 编译二进制文件 ```go go build <绝对路径> ``` ## 3.4. 运行 ```go go run <绝对路径> # 或者 go run . ``` --- # 4. 注释与转义字符 ## 4.1. 注释 | 字符 | 备注 | | :-----: | ---- | | `//` | 单行 | | `/*…*/` | 多行 | ```go // 单行注释 // 单行注释 // 单行注释 /* 多行注释 多行注释 多行注释 */ ``` ## 4.2. 转义字符 (Escaped characters) | 转义字符 | 含义 | 备注 | | :------: | :-------------: | -------------------- | | `\"` | 双引号 | | | `\a` | alert sound | 警报声(不常用) | | `\b` | backspace | 退格(不常用) | | `\f` | form feed | 换页(不常用) | | `\n` | new line | 换行 | | `\r` | carriage return | 回车替换 | | `\t` | tab | 制表符 | | `\v` | vertical tab | 垂直制表符(不常用) | ```go // 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然后我改变一行代码,困难就烟消云散") } ``` **输出:** ```go // 双引号 图灵祖师说:"我不知道我是梦见自己是机器的图灵,还是梦见自己是图灵的机器" // 反斜线 \\电子邮件说\项目已取消\清理文档的时候\我哭了 // 警报声 既使一个程序只有三行长,也总有一天需要去维护它 // 退格 三天不编程, 生活变得毫无意义 // 换页 一个程序员正在写软件 他的手指在键盘上飞舞 程序编译时没有一条错误信息 运行起来就如同一阵微风 // 回车 我的程序如同是自己在写自己由地按照它自己的直觉前进 // 制表符 —— 两字 两字 三个字 三个字 三个字 // 纵向制表符 确实,有时候我会遇到难题。 当我发现难题的时候,我会慢下来,安静地观察。 然后我改变一行代码,困难就烟消云散 ``` --- # 5. 变量与常量 ```go // 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) } ``` **输出:** ```go // 变量 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. 全局定义 ```go 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 的超集) | ```go // 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) } ``` **输出:** ```go // 字符型 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. 数值类型转换 ```go // 目标类型(被转换的数据) n1 := int8(n2) ``` > 需要注意精度,溢出可能导致数据丢失 --- # 7. 指针 ## 7.1. Go 中的指针 - **取址符**:`&`(获取变量地址) - **取值符**:`*`(访问地址指向的值) - **指针类型**:`*T` 表示指向 T 类型的指针 ## 7.2. 值拷贝(值类型) 函数调用时会拷贝值,互不干扰: ```go // 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) } ``` **输出:** ```go // 自增后 n=2026, 内存地址=0xc00000a110 调用后 src=2025, 内存地址=0xc00000a0f8 ``` ## 7.3. 值传递(引用类型) 传递指针可在函数内修改原值: ```go // 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) } ``` **输出:** ```go // 自增后 n=0xc00000a0f8, 内存地址=0xc000072060 调用后 src=2026, 内存地址=0xc00000a0f8 ``` --- # 8. `fmt` 格式化字符 (fmt verbs) ## 8.1. 通用 | 字符 | 含义 | 备注 | | :---: | :---: | -------- | | `%%` | `%` | 转义 | | `%v` | value | 值 | | `%T` | Type | 数据类型 | ```go // FmtVerbs fmt格式字符 func FmtVerbs() { fmt.Println("\n// 通用") fmt.Printf("%%\n") } ``` **输出:** ```go // 通用 % ``` ## 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 | 带单引号的字符 | ```go // 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) } ``` **输出:** ```go // 整数 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 十六进制科学记数法 | ```go // 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) } ``` **输出:** ```go // 浮点数 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 的单词 | ```go // FmtVerbs fmt格式字符 func FmtVerbs() { fmt.Println("\n// 布尔") fmt.Printf("b=%t\n", true) fmt.Printf("b=%t\n", false) } ``` **输出:** ```go // 布尔 b=true b=false ``` ## 8.5. 字符串或byte切片([]byte) | 字符 | 含义 | | :---: | :----------------------------------: | | `%s` | 按字符串输出 | | `%q` | 带双引号的字符串输出 | | `%x` | 每个 byte 按两位小写十六进制码值输出 | | `%X` | 每个 byte 按两位大写十六进制码值输出 | ```go // FmtVerbs fmt格式字符 func FmtVerbs() { fmt.Println("\n// 字符串或byte切片") s := "hello world" fmt.Printf("s=%q\n", s) fmt.Printf("s=%x\n", s) } ``` **输出:** ```go // 字符串或byte切片 s="hello world" s=68656c6c6f20776f726c64 ``` ## 8.6. 指针 | 字符 | 含义 | | :---: | :----------------------: | | `%p` | 以 0x 开头的十六进制地址 | ```go // FmtVerbs fmt格式字符 func FmtVerbs() { fmt.Println("\n// 指针") p := "hello world" fmt.Printf("s=%p\n", &p) } ``` **输出:** ```go // 指针 s=0xc00009c080 ``` --- # 9. 运算符 ## 9.1. 算数运算符 | 运算符 | 含义 | 备注 | | :----: | :---: | ---------------------- | | `+` | 加 | 也可用于字符串拼接 | | `-` | 减 | | | `*` | 乘 | | | `/` | 除 | | | `%` | 取余 | | | `++` | 自增 | 不能作为表达式的一部分 | | `--` | 自减 | 不能作为表达式的一部分 | ```go // 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) } ``` **输出:** ```go // 算数运算符 i=124 8%3=2 ``` ## 9.2. 位运算符 | 运算符 | 含义 | 备注 | | :----: | :------: | ----------------- | | `>>` | 右移 | 二进制向右移动 | | `<<` | 左移 | 二进制向左移动 | | `&` | 按位与 | 两位都为 1 则为 1 | | `\|` | 按位或 | 任一位为 1 则为 1 | | `^` | 按位异或 | 两位不相同则为 1 | ```go // 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) // 按位异或 } ``` **输出:** ```go // 位运算符 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. 运算优先级 1. 括号 `()` 2. 点操作 `.` 3. 地址运算符 `&` `*` 4. 其他… --- # 10. 流程控制 ## 10.1. `if...else` - 条件无需括号 - 注意自动插入分号规则 ## 10.2. `switch...case` - 可以替代多层 `if...else if...else` - `case` 尾部会自动加入 `break`,如需穿透使用 `fallthrough` - `default` 可省略 ## 10.3. `for` 循环 ```go // 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() } ``` **输出:** ```go // 无限循环 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` ```go // 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() } ``` **输出:** ```go // 标签跳转 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ``` --- # 11. 函数 ```go func 函数名(参数1 类型, ...) (返回值类型...) { return 返回值... } // 实例 func getSum(a int, b int) int { return a + b } ``` > 函数名称首字母大写可以被外部调用,同时注释需带函数名称前缀 > 同一个命名空间首字母小写私有,大写可以被外部调用,变量同理 ## 11.1. 参数 - **实参**:调用时传递的实际值 - **形参**:定义函数时使用的参数 ```go getSum(1, 2) // 1, 2 是实参 func getSum(a, b int) // a, b 是形参,相同类型可合并写法 ``` 不确定参数个数可使用可变参数: ```go func sum(nums ...int) int { /* ... */ } ``` ## 11.2. 返回值 - 支持 0~多个返回值 - 可命名返回值,将它们视作函数顶部定义的变量 ```go func getRes(a, b int) (sum int, difference int) { sum = a + b difference = a - b return } rec1, rec2 := getRes(1, 2) ``` ## 11.3. 函数是一种数据类型 函数名本质是指向其代码的指针常量。 ```go 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. 匿名函数 ```go 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` 后按照 **先进后出** 顺序执行 - 延迟调用时其参数会立即求值 ```go // 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 } } ``` **输出:** ```go // 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` 配合匿名函数进行错误捕捉与延迟处理。 ```go // 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) } ``` **输出:** ```go // 捕获异常 这里有输出 ========= err: runtime error: integer divide by zero ``` ## 12.3. `init` 函数 - 每个包可有多个 `init` 函数 - 执行顺序(按依赖关系): 1. 被依赖包的全局变量 2. 被依赖包的 `init` 函数 3. ... 4. `main` 包的全局变量 5. `main` 包的 `init` 函数 6. `main` 函数 --- # 13. 包 (Package) 1. Go 程序由包构成,入口为 `main` 包 2. 使用 `import` 引入包 3. 可为包起别名 --- # 14. 数组 - 长度固定、元素类型相同的一组数据 - 数组是值类型,声明时有默认值 - 索引从 0 开始 ## 14.1. 声明 1. 左侧声明时长度不能为空,留空即为切片类型 2. 数组长度是类型的一部分,无法改变 3. 右侧可用 `[...]` 自动推导长度 4. 最后一组也要加`,` ```go // 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]) } } ``` **输出:** ```go // for 遍历 a[0]=123 a[1]=456 a[2]=789 ``` ## 14.2. `for` + `range` 遍历 ```go // 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. 多维数组 ```go // 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() } } ``` **输出:** ```go // 多维数组 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. 声明 1. 引用数组的一段 2. 引用切片的一段*(将指向同一个底层数组) 3. 分配内存空间make([]Type, len, cap) 4. 长度len(s)和容量cap(s) ```go // 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)) } ``` **输出:** ```go // 声明 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. 追加元素 1. `append([]Type, …Type)` 2. 可以追加单个元素,也可以追加其他切片 ```go // 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) } ``` **输出:** ```go // 追加元素 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) ```go // 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) } ``` **输出:** ```go // 复制数组 s20: [7 8 9 4 5 6] ``` ## 15.4. `string` 与 `[]byte` 1. 可相互转换 2. 格式字符通用 3. 可以直接用 `for` + `range` 遍历字符串 ```go // 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) } } ``` **输出:** ```go // 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的工具函数 ```go // 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 } ``` **输出:** ```go // 形参与切片 1:注册 2:登录 3:退出 请选择:(数字) 2 key: 2 ``` --- # 16. MAP(集合) 本质为无序键值对(key-value) map是引用类型,默认值为`nil` 容量会自动增长 ## 16.1. 声明 数据类型:`map[key类型]value类型` 通过 `make` 分配,可指定初始容量 ```go // 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) } ``` **输出:** ```go // 声明前 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]` ```go // 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) } ``` **输出:** ```go // 简写 m2=map[a:b c:d] // 修改 m2=map[a:bb c:d] // 新增 m2=map[a:bb c:d e:f] ``` ## 16.3. 查找 通过双赋值检测某个key是否存在 ```go // 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不存在") } } ``` **输出:** ```go // 查找 key存在,value=b ``` ## 16.4. 删除 1. 通过delete(map, key)删除单个元素 2. 删除所有key-value ```go // 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) } ``` **输出:** ```go // 删除 m4=map[a:b e:f] m4=map[] ``` ## 16.5. `for` + `range` 遍历 ```go // 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) } } ``` **输出:** ```go // 遍历 m5[e]=f m5[a]=b m5[c]=d ``` --- # 17. 自定义数据类型&类型别名 ## 17.1. 类型定义 定义方法:type 自定义数据类型 底层数据类型 属于不同类型,混用需要类型转换 ```go // 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) } ``` **输出:** ```go // 类型定义 dt=hello,type=note.dType dt=hello 世界,type=note.dType ``` ## 17.2. 类型别名 定义方法:type 自定义数据类型 = 底层数据类型 属于相同类型,混用无需类型转换 ```go // TypeAlias 类型别名 func TypeAlias() { fmt.Println("\n// 类型别名") type aType = string var at aType = "hello" fmt.Printf("at=%v,type=%T\n", at, at) } ``` **输出:** ```go // 类型别名 at=hello,type=string ``` --- # 18. 结构体 - 由一组字段构成的一种自定义数据类型 - type 结构体名 struct {字段名 字段类型} ## 18.1. 结构体字段 1. 结构体可以没有字段 2. 结构体字段通过“.”访问 ```go // 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) } ``` **输出:** ```go // 赋值 u1={1000 张三} ``` ## 18.2. 结构体指针 注意 `.` 优先级高于 `&` / `*` 使用时可以简写(隐式间接引用) 可以使用 `&` 前缀快速声明结构体指针 ```go // 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 {(*)被继承的结构体名} ```go // Struct 结构体 func Struct() { type user1 struct { id uint32 name string } type user2 struct { user1 password string } } ``` ## 18.4. 结构体标签 type 结构体名 struct {字段名 字段类型 \`标签:"字段别名"\`} ```go // Struct 结构体 func Struct() { type Account struct { Name string `json:"name"` password string } } ``` --- # 19. 方法 1. 与特定类型绑定的函数 2. 类型的定义和方法需要在同一个包内 3. func (接收参数名 类型)方法名(形参列表)返回值列表{} 4. 接收参数可以使用指针 ```go // 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) } ``` **输出:** ```go // 方法 u.name= hello u= {1000 hello} ``` --- # 20. 接口 - 特殊的数据类型 - 方法定义的集合 - 方法名(形参类型)返回值类型 - 提高代码的复用率 > 接口本身不能绑定方法,保存的是:值+原始类型 ```go // 定义 文本 结构体 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) } ``` **输出:** ```go // 接口 m= &{hello 1} m= &{清明上河图 2} ``` ## 20.1. 空接口 `interface{}` 空接口可保存任何类型 ```go // Interface 接口 func Interface() { fmt.Println("\n// 类型断言") n1 := 1 n1interface := interface{}(n1) // 空接口 n2, ok := n1interface.(int) if ok { fmt.Println("n2=", n2) } else { fmt.Println("类型断言失败") } } ``` **输出:** ```go // 类型断言 n2= 1 ``` ## 20.2. nil问题 nil 值:有类型没有值,接口本身并不是 nil,可以处理 nil 接口:即没有保存值,也没有保存类型,使用时会报错 --- # 21. 协程(Goroutine) - Goroutine 是 Go 运行时管理的轻量级线程 - 主线程结束时,协程会被中断,需要有效的阻塞机制 - 多个线程同时对同一个内存空间进行写操作会导致数据竞争,sync包可以解决此问题,互斥锁Mutex,但在 Go 中不常用,因为go中有更高效的信道channel来解决这个问题 ```go 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, "个") } ``` **输出:** ```go // 线程 共找到: 50000 个 ``` --- # 22. 信道(channel) ## 22.1. 声明与存取 1. `channel` 官方翻译为信道,是一种带有类型的管道 2. 引用类型,使用前需要 `make(Type, (缓冲区容量))` 3. 不带缓冲区的管道必须结合结合协程使用 4. 可以查看长度 `len(channel)` 或容量 `cap(channel)` 5. 存入: `channel <- value` 6. 取出: `value, (ok) <- channel` 7. 丢弃: `<- channel` 8. 先进先出,自动阻塞 9. 数据需要保持流动,否则会阻死报错 ```go 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) // } } ``` **输出:** ```go // 信道 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` 1. 适用于无法确认合适关闭信道的情况,通常结合for循环使用 2. select…case 会阻塞到某个分支可以继续执行时执行该分支,当没有可执行的分支时则执行 default 分支 ```go // 是否偶数 管道方式 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标签 } } } ``` Last modification:April 26, 2025 © Allow specification reprint Support Appreciate the author AliPayWeChat Like 3 喜欢我的文章吗? 别忘了点赞或赞赏,让我知道创作的路上有你陪伴。