1. 认识 Go

1.1. 什么是 GoLang

Go 语言是 Google 支持的一种静态、编译型编程语言。
全名 Go 语言(Go Language),简称 GoLang。

吉祥物 Gopher (囊鼠)

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

输出关键变量:

变量名含义
GOROOTGo 的安装目录,即可执行文件所在的目录
GOPATH工作目录并不是项目所有目录,编译后的二进制文件存放地,import 包的搜索路径,主要包含binpkgsrc
GO111MODULE是否启用 Go Module 管理项目,需要有go.modgo.sum 文件
GOPROXY下载依赖时的代理

2.4. 开发工具


3. Go Modules 包管理 / Go Mod 项目管理

3.1. 了解大型项目结构

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)

转义字符含义备注
\"双引号
\aalert sound警报声(不常用)
\bbackspace退格(不常用)
\fform feed换页(不常用)
\nnew line换行
\rcarriage return回车替换
\ttab制表符
\vvertical 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 中 byteuint8 的别名

6.1. 整数型

名称长度范围默认值
int88 bit-128 ~ 1270
uint88 bit0 ~ 2550
int1616 bit-32768 ~ 327670
uint1616 bit0 ~ 655350
int3232 bit-2147483648 ~ 21474836470
uint3232 bit0 ~ 42949672950
int6464 bit-9223372036854775808 ~ 92233720368547758070
uint6464 bit0 ~ 184467440737095516150
int32/64 bit与平台相关0
uint32/64 bit与平台相关0
  • decimal:十进制,无需前缀
  • binary:二进制,前缀 0b / 0B
  • octal:八进制,前缀 0o / 0O
  • hexadecimal:十六进制,前缀 0x / 0X

6.2. 浮点型 (floating point)

名称长度符号 + 指数 + 尾数默认值
float3232 bit1 + 8 + 23 bits0
float6464 bit1 + 11 + 52 bits0

6.3. 字符型

名称长度别名编码
byte8 bituint8ASCII
rune32 bitint32UTF-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. 布尔型

名称长度默认值
bool1 bitfalse

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. 通用

字符含义备注
%%%转义
%vvalue
%TType数据类型
// FmtVerbs fmt格式字符
func FmtVerbs() {
    fmt.Println("\n// 通用")
    fmt.Printf("%%\n")
}

输出:

// 通用
%

8.2. 整数

字符含义备注
%ddecimal十进制
%bbinary二进制(没有前缀)
%ooctal八进制(没有前缀)
%xhexadecimal十六进制 a-f(没有前缀)
%Xhexadecimal十六进制 A-F(没有前缀)
%UUnicodeU+四位16进制 int32
%ccharacterUnicode 码值对应的字符
%qquoted带单引号的字符
// 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
%x0x 十六进制科学记数法
%X0X 十六进制科学记数法
// 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. 布尔

字符含义
%ttrue 或 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. 运算优先级

  1. 括号 ()
  2. 点操作 .
  3. 地址运算符 & *
  4. 其他…

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. labelgoto

// 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 函数
  • 执行顺序(按依赖关系):

    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. 最后一组也要加,
// 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. 声明

  1. 引用数组的一段
  2. 引用切片的一段*(将指向同一个底层数组)
  3. 分配内存空间make([]Type, len, cap)
  4. 长度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. 追加元素

  1. append([]Type, …Type)
  2. 可以追加单个元素,也可以追加其他切片
// 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

  1. 可相互转换
  2. 格式字符通用
  3. 可以直接用 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. 删除

  1. 通过delete(map, key)删除单个元素
  2. 删除所有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. 结构体字段

  1. 结构体可以没有字段
  2. 结构体字段通过“.”访问
// 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. 方法

  1. 与特定类型绑定的函数
  2. 类型的定义和方法需要在同一个包内
  3. func (接收参数名 类型)方法名(形参列表)返回值列表{}
  4. 接收参数可以使用指针
// 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. 声明与存取

  1. channel 官方翻译为信道,是一种带有类型的管道
  2. 引用类型,使用前需要 make(Type, (缓冲区容量))
  3. 不带缓冲区的管道必须结合结合协程使用
  4. 可以查看长度 len(channel) 或容量 cap(channel)
  5. 存入: channel <- value
  6. 取出: value, (ok) <- channel
  7. 丢弃: <- channel
  8. 先进先出,自动阻塞
  9. 数据需要保持流动,否则会阻死报错
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

  1. 适用于无法确认合适关闭信道的情况,通常结合for循环使用
  2. 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标签
        }
    }
}
Last modification:April 23, 2025
喜欢我的文章吗? 别忘了点赞或赞赏,让我知道创作的路上有你陪伴。