导图社区 Golang学习笔记
该思维导图涵盖了运算符、标识符&关键字、变量&常量、数据类型、流程控制、函数等多个关键分支,每个分支下又细致入微地罗列出相关知识点,结构清晰、内容全面,是Golang学习者夯实基础、系统梳理知识的得力工具。内容全面,是Golang学习者夯实基础、系统梳理知识的得力工具。对于Golang初学者和进阶学习者而言,此模板堪称学习路上的宝藏指南。运算符分支涵盖了逻辑运算符、位运算符等多种类型及其详细说明,帮助学习者清晰掌握运算规则;标识符&关键字分支明确了各类关键要素的定义与作用,为代码编写奠定基础;变量&常量和数据类型分支则深入剖析了相关概念、声明方式及不同数据类型的特点与应用场景,让学习者对Golang的数据体系有全面认识;流程控制和函数分支详细讲解了控制流程的语句和函数的使用方法,助力学习者编写出逻辑严谨的程序。在自学提升、课程复习、项目开发前的知识梳理等适用场景中,学习者可直接套用此模板,结合自身学习进度进行个性化修改与补充。
编辑于2026-04-03 23:21:53该思维导图涵盖了运算符、标识符&关键字、变量&常量、数据类型、流程控制、函数等多个关键分支,每个分支下又细致入微地罗列出相关知识点,结构清晰、内容全面,是Golang学习者夯实基础、系统梳理知识的得力工具。内容全面,是Golang学习者夯实基础、系统梳理知识的得力工具。对于Golang初学者和进阶学习者而言,此模板堪称学习路上的宝藏指南。运算符分支涵盖了逻辑运算符、位运算符等多种类型及其详细说明,帮助学习者清晰掌握运算规则;标识符&关键字分支明确了各类关键要素的定义与作用,为代码编写奠定基础;变量&常量和数据类型分支则深入剖析了相关概念、声明方式及不同数据类型的特点与应用场景,让学习者对Golang的数据体系有全面认识;流程控制和函数分支详细讲解了控制流程的语句和函数的使用方法,助力学习者编写出逻辑严谨的程序。在自学提升、课程复习、项目开发前的知识梳理等适用场景中,学习者可直接套用此模板,结合自身学习进度进行个性化修改与补充。
python基础基本知识点分享!列出来学习python所要掌握的基础知识点,涵盖基本概念、数据类型、运算符、控制流程、函数、面向对象、文件、异常处理八个部分,想要学习python的朋友可以根据这个脉络进行学习!
编程学习必备!一张图带你从小白开始学python!Python是一种跨平台的计算机程序设计语言。 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。最初被设计用于编写自动化脚本(shell),随着版本的不断更新和语言新功能的添加,越多被用于独立的、大型项目的开发。还不快收藏学起来!
社区模板帮助中心,点此进入>>
该思维导图涵盖了运算符、标识符&关键字、变量&常量、数据类型、流程控制、函数等多个关键分支,每个分支下又细致入微地罗列出相关知识点,结构清晰、内容全面,是Golang学习者夯实基础、系统梳理知识的得力工具。内容全面,是Golang学习者夯实基础、系统梳理知识的得力工具。对于Golang初学者和进阶学习者而言,此模板堪称学习路上的宝藏指南。运算符分支涵盖了逻辑运算符、位运算符等多种类型及其详细说明,帮助学习者清晰掌握运算规则;标识符&关键字分支明确了各类关键要素的定义与作用,为代码编写奠定基础;变量&常量和数据类型分支则深入剖析了相关概念、声明方式及不同数据类型的特点与应用场景,让学习者对Golang的数据体系有全面认识;流程控制和函数分支详细讲解了控制流程的语句和函数的使用方法,助力学习者编写出逻辑严谨的程序。在自学提升、课程复习、项目开发前的知识梳理等适用场景中,学习者可直接套用此模板,结合自身学习进度进行个性化修改与补充。
python基础基本知识点分享!列出来学习python所要掌握的基础知识点,涵盖基本概念、数据类型、运算符、控制流程、函数、面向对象、文件、异常处理八个部分,想要学习python的朋友可以根据这个脉络进行学习!
编程学习必备!一张图带你从小白开始学python!Python是一种跨平台的计算机程序设计语言。 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。最初被设计用于编写自动化脚本(shell),随着版本的不断更新和语言新功能的添加,越多被用于独立的、大型项目的开发。还不快收藏学起来!
Golang
标识符&关键字
标识符
定义
标识符(Identifier) 是Go语言中用来命名变量、常量、函数、接口、类型、包、结构体字段等程序实体的名称
命名规则
由数字、字母、下划线组成,但是不能以数字开头
区分大小写
不能是Go语言中的关键字
命名风格
包名: 全小写,简短,无下划线
变量/函数: 驼峰式(camelCase)
常量: 驼峰式或全大写
类型/结构体: 大驼峰(PascalCase)
接口: 通常以 er 结尾
私有变量: 小驼峰
私有常量: 小驼峰
作用域
宇宙作用域
所有 Go 程序共享,包含预定义标识符
包级作用域
在整个包内可见,如:包级变量、常量、类型、函数
文件级作用域
仅在一个文件内可见,如:导入的包名
函数级作用域
在整个函数内可见,如:函数参数、命名返回值
块级作用域
在代码块 { } 内可见,如:局部变量、if/for/switch 中声明的变量
预定义标识符
基本类型(20个)
布尔(1个)
bool
数值类型(17个)
byte(uint8 别名)、rune(int32 别名) int、int8、int16、int32、int64 uint、uint8、uint16、uint32、uint64、uintptr float32、float64 complex64、complex128
字符串类型(1个)
string
错误类型(1个)
error
内置函数(18个)
常量(3个)
true、false、iota
零值(1个)
nil:指针、切片、映射、通道、函数、接口的零值
关键字
包管理类
package
包声明,放在行首,如: package main
import
声明类
var
声明变量
const
声明常量
func
声明函数或方法
type
声明类型(结构体、接口、别名等)
流程控制类
条件分支
if
else
多路选择
switch
case
default
fallthrough
循环
for
range
跳转
break
continue
goto
return
通道选择
select
延迟执行
defer
类型定义
struct
interface
map
chan
并发编程
go
变量&常量
变量
变量声明
语法格式
var 变量名 类型,声明后会自动赋予该类型的零值
声明同时初始化
var name string = "Alice" var age int = 25
类型推断
声明时可省略类型,由编译器根据初始值自动推导 var name = "Alice" // 推断为 string var age = 25 // 推断为 int
多变量声明与批量声明
多变量同时声明
var x, y int = 1, 2 var a, b = "hello", true c, d := 3.14, "world"
批量声明
var ( name string age int salary float64 )
短变量声明
在函数内部,可以使用 := 进行短变量声明,自动推断类型,不能用于全局变量。 func main() { name := "Alice" // 等价于 var name = "Alice" age := 25 } 短变量批量声明 x, y := 10, "Go"
零值机制
var i int // 0 var f float64 // 0.0 var b bool // false var s string // ""(空字符串) var p *int // nil(指针) var arr [3]int // [0 0 0] var slice []int // nil(切片) var m map[string]int // nil(映射) var ch chan int // nil(通道) var fn func() // nil(函数) var iface interface{} // nil(接口)
匿名变量
概念
匿名变量使用下划线 _ 表示,它是一个特殊的占位符,用于接收不需要的值。匿名变量不会占用内存,也不可以被引用。
主要用途
1.接收函数的多返回值中不需要的部分
// 只获取错误信息,忽略返回值 _, err := os.Open("file.txt") // 只获取第一个返回值,忽略第二个 name, _ := getUserInfo() // 忽略所有返回值 _, _ = os.Open("file.txt")
2. 在循环中忽略索引或值
// 只需要值,忽略索引 for _, value := range slice { fmt.Println(value) } // 只需要索引,忽略值 for index, _ := range slice { fmt.Println(index) } // 也可以只写一个变量,但用匿名变量更清晰 for index := range slice { // 这样写也可以,但意图不如 _ 明确 fmt.Println(index) }
3. 导入包时仅执行 init 函数
import _ "github.com/go-sql-driver/mysql" // 只执行包的 init 函数,不使用包内其他内容
4. 接口类型检查
var _ io.Reader = (*MyReader)(nil) // 确保 MyReader 实现了 io.Reader 接口
作用域
全局变量
包级可见,首字母大写可导出(public),小写包内私有(private)
局部变量
函数级
块级
常量
常量声明
语法格式
const 常量名 类型 = 常量值,类型字段可省略
批量声明
const ( StatusOK = 200 StatusNotFound = 404 )
iota枚举器
const ( Sunday = iota // 0 Monday // 1 Tuesday // 2 Wednesday // 3 )
const ( _ = iota // 0,跳过 KB = 1 << (10 * iota) // 1 << 10 = 1024 MB // 1 << 20 GB // 1 << 30 )
作用域
全局常量
在函数外部声明,在整个包内可见,首字母大小写控制导出性。
局部常量
函数级
函数或代码块内可见
块级
仅在该代码块内可见
数据类型
基本数据类型
布尔型
bool
true
false
数值型
整型
int
int8
int16
int32
int64
uint
uint8
uint16
uint32
uint64
uintptr
浮点型
float32
float64
类型推断默认为float64
复数
complex64
float32 实部 + float32 虚部
complex128
float64 实部 + float64 虚部(默认)
字符型
rune
uint8 的别名,表示 ASCII 字符
byte
int32 的别名,表示 Unicode 码点(UTF-8 字符)
字符串型
string
概念
Go 中的字符串是只读的字节切片,底层是一个结构体
特点
字符串是不可变的(immutable) 使用 UTF-8 编码 本质是字节序列,但通常表示文本
创建
// 双引号创建(普通字符串) s1 := "Hello, Go" // 反引号创建(原始字符串,不转义) s2 := `Hello\nWorld` // 输出 "Hello\nWorld",\n 不会转义 // 多行字符串(反引号) s3 := `第一行 第二行 第三行` // 拼接创建 s4 := "Hello" + " " + "World" // 从字节切片创建 bytes := []byte{72, 101, 108, 108, 111} s5 := string(bytes) // "Hello" // 从 rune 切片创建 runes := []rune{'世', '界'} s6 := string(runes) // "世界"
基本操作
获取长度
s := "Hello, 世界" // len() 返回字节数,不是字符数 byteLen := len(s) // 13(H e l l o , 空格 世 界 = 5+1+1+3+3?实际是13) // 获取字符数(rune 个数) charLen := utf8.RuneCountInString(s) // 9 charLen2 := len([]rune(s)) // 9(性能稍差)
索引访问
s := "Hello" // 索引返回 byte(不是字符) b := s[0] // byte 类型,值为 72('H') // 直接索引只能处理 ASCII s2 := "世界" // ch := s2[0] // 这会得到第一个字节(不是 '世'),不推荐
切片操作
s := "Hello, World" // 切片返回新字符串(共享底层数据,不复制) sub1 := s[0:5] // "Hello" sub2 := s[7:] // "World" sub3 := s[:5] // "Hello" // 注意:切片按字节索引,处理中文要小心 s2 := "Hello, 世界" // sub := s2[7:10] // 可能得到乱码,因为 "世" 占3个字节
字符串拼接
// 方式1:+ 运算符(简单拼接) s1 := "Hello" + " " + "World" // 方式2:fmt.Sprintf(格式化拼接) name := "Alice" age := 25 s2 := fmt.Sprintf("%s is %d years old", name, age) // 方式3:strings.Join(切片拼接) parts := []string{"Hello", "World"} s3 := strings.Join(parts, " ") // 方式4:strings.Builder(高效拼接,推荐循环中) var builder strings.Builder builder.WriteString("Hello") builder.WriteString(" ") builder.WriteString("World") s4 := builder.String() // 性能对比:strings.Builder > strings.Join > + > fmt.Sprintf
字符串遍历
按字节遍历(不推荐处理中文)
s := "Hello, 世界" for i := 0; i < len(s); i++ { fmt.Printf("%c ", s[i]) // 中文会输出乱码 }
按字符遍历(推荐)
s := "Hello, 世界" // 方式1:range 遍历(自动按 rune 遍历) for index, ch := range s { fmt.Printf("%d: %c\n", index, ch) } // 方式2:转换为 []rune runes := []rune(s) for i, ch := range runes { fmt.Printf("%d: %c\n", i, ch) }
字符串与字节/字符的转换
s := "Hello, 世界" // 字符串 -> 字节切片 bytes := []byte(s) // 每个字节单独存储 // 字符串 -> rune 切片 runes := []rune(s) // 每个字符单独存储 // 字节切片 -> 字符串 s1 := string(bytes) // rune 切片 -> 字符串 s2 := string(runes)
字符串常用操作(strings包)
import "strings" s := "Hello, World" // 包含判断 strings.Contains(s, "World") // true strings.ContainsAny(s, "abc") // true(是否包含任意字符) strings.HasPrefix(s, "Hello") // true strings.HasSuffix(s, "World") // true // 索引查找 strings.Index(s, "World") // 7 strings.IndexAny(s, "aeiou") // 1(第一个元音位置) strings.LastIndex(s, "o") // 8 // 大小写转换 strings.ToLower(s) // "hello, world" strings.ToUpper(s) // "HELLO, WORLD" // 替换 strings.Replace(s, "World", "Go", -1) // "Hello, Go"(-1 表示全部替换) strings.ReplaceAll(s, "l", "L") // "HeLLo, WorLd" // 分割与拼接 parts := strings.Split(s, ", ") // ["Hello", "World"] joined := strings.Join(parts, "-") // "Hello-World" // 去除空白 strings.TrimSpace(" Hello ") // "Hello" strings.Trim(s, "Hel") // "o, World" strings.TrimPrefix(s, "Hello") // ", World" // 重复 strings.Repeat("Go", 3) // "GoGoGo" // 计数 strings.Count(s, "l") // 3 // 比较(区分大小写) strings.Compare("a", "b") // -1 strings.EqualFold("GO", "go") // true(忽略大小写)
字符串与数值转换(strconv包)
import "strconv" // 整数 -> 字符串 s1 := strconv.Itoa(123) // "123" s2 := strconv.FormatInt(123, 10) // "123"(指定进制) // 字符串 -> 整数 i1, err := strconv.Atoi("123") // 123 i2, err := strconv.ParseInt("123", 10, 64) // 123 // 浮点数 -> 字符串 s3 := strconv.FormatFloat(3.14, 'f', 2, 64) // "3.14" // 字符串 -> 浮点数 f, err := strconv.ParseFloat("3.14", 64) // 3.14 // 布尔值 -> 字符串 s4 := strconv.FormatBool(true) // "true" // 字符串 -> 布尔值 b, err := strconv.ParseBool("true") // true
复合数据类型
数组 array
基本概念
什么是数组
数组是长度固定的同类型元素序列。
数组特点
声明与初始化
声明语法
var 数组名 [长度]类型
初始化方式
方式1:声明后逐个赋值 var arr [3]int arr[0] = 10 arr[1] = 20 arr[2] = 30
方式2:声明时初始化 arr := [3]int{10, 20, 30}
方式3:自动推断长度(使用 ...) arr := [...]int{10, 20, 30} // 编译器自动计算长度为3 fmt.Println(len(arr)) // 3
方式4:指定索引初始化 arr := [5]int{0: 10, 2: 30, 4: 50} // [10, 0, 30, 0, 50]
方式5:零值初始化 var arr [3]int // [0, 0, 0] var arr2 [3]string // ["", "", ""]
数组的零值
数组的零值是所有元素都是该类型的零值。 var arr1 [3]int // [0, 0, 0] var arr2 [3]string // ["", "", ""] var arr3 [3]bool // [false, false, false] var arr4 [3]float64 // [0, 0, 0] var arr5 [3]*int // [nil, nil, nil]
基本操作
访问元素
使用索引访问,索引从 0 开始,最大为 长度-1。 arr := [3]int{10, 20, 30} fmt.Println(arr[0]) // 10 fmt.Println(arr[1]) // 20 fmt.Println(arr[2]) // 30 // fmt.Println(arr[3]) // 编译错误:索引越界
修改元素
arr := [3]int{10, 20, 30} arr[1] = 100 fmt.Println(arr) // [10, 100, 30]
获取长度
使用 len() 函数获取数组长度。 arr := [5]int{1, 2, 3, 4, 5} fmt.Println(len(arr)) // 5
遍历数组
方式1:普通for循环 arr := [5]int{10, 20, 30, 40, 50} for i := 0; i < len(arr); i++ { fmt.Printf("arr[%d] = %d\n", i, arr[i]) }
方式2:for - range循环 arr := [5]int{10, 20, 30, 40, 50} for index, value := range arr { fmt.Printf("索引: %d, 值: %d\n", index, value) } // 只需要值 for _, value := range arr { fmt.Println(value) } // 只需要索引 for index := range arr { fmt.Println(index) }
多维数组
二维数组声明与初始化
// 声明 var matrix [2][3]int // 2行3列的二维数组 // 初始化 matrix := [2][3]int{ {1, 2, 3}, {4, 5, 6}, } // 部分初始化 matrix2 := [2][3]int{ {1, 2}, {4, 5, 6}, } // 结果:[[1, 2, 0], [4, 5, 6]]
自动推断行数
matrix := [...][3]int{ {1, 2, 3}, {4, 5, 6}, {7, 8, 9}, } // 注意:只能自动推断行数,列数必须明确
访问多维数组
matrix := [2][3]int{ {1, 2, 3}, {4, 5, 6}, } fmt.Println(matrix[0][1]) // 2(第0行第1列) fmt.Println(matrix[1][2]) // 6(第1行第2列) matrix[1][1] = 100 fmt.Println(matrix) // [[1, 2, 3], [4, 100, 6]]
遍历多维数组
matrix := [2][3]int{ {1, 2, 3}, {4, 5, 6}, } // 双层循环 for i := 0; i < len(matrix); i++ { for j := 0; j < len(matrix[i]); j++ { fmt.Printf("matrix[%d][%d] = %d\n", i, j, matrix[i][j]) } } // for range 嵌套 for i, row := range matrix { for j, value := range row { fmt.Printf("matrix[%d][%d] = %d\n", i, j, value) } }
数组的特性
值类型
数组赋值或传参时会复制整个数组,修改副本不会影响原数组。 arr1 := [3]int{1, 2, 3} arr2 := arr1 // 复制整个数组 arr2[0] = 100 fmt.Println(arr1[0]) // 1(原数组不变) fmt.Println(arr2[0]) // 100(副本改变) // 函数传参也是复制 func modify(arr [3]int) { arr[0] = 100 // 修改的是副本 } arr := [3]int{1, 2, 3} modify(arr) fmt.Println(arr[0]) // 1(原数组不变)
数组的比较
比较前提: 长度相同 & 元素类型相同 arr1 := [3]int{1, 2, 3} arr2 := [3]int{1, 2, 3} arr3 := [3]int{1, 2, 4} fmt.Println(arr1 == arr2) // true fmt.Println(arr1 == arr3) // false fmt.Println(arr1 != arr3) // true // 长度不同的数组不能比较 // arr4 := [4]int{1, 2, 3, 4} // fmt.Println(arr1 == arr4) // 编译错误:类型不匹配
数组指针
可以使用指针操作数组,避免复制。 arr := [3]int{1, 2, 3} ptr := &arr // 指向数组的指针 ptr[0] = 100 // 可以直接通过指针修改 fmt.Println(arr) // [100, 2, 3] // 函数中通过指针修改原数组 func modifyByPtr(arr *[3]int) { arr[0] = 100 } modifyByPtr(&arr) fmt.Println(arr[0]) // 100
数组使用陷阱
陷阱1:索引越界
arr := [3]int{1, 2, 3} // fmt.Println(arr[3]) // panic: index out of range
陷阱2:数组值类型导致意外复制
arr := [3]int{1, 2, 3} modify(arr) // 期望修改原数组,但实际是副本 fmt.Println(arr) // 还是 [1, 2, 3] // 正确做法:使用指针或返回新数组 func modifyWithPtr(arr *[3]int) { arr[0] = 100 } modifyWithPtr(&arr)
陷阱3:大数组传参性能问题
// 不推荐:大数组传参会复制整个数组 func process(arr [1024]int) { ... } // 推荐:使用数组指针 func process(arr *[1024]int) { ... } // 或使用切片 func process(slice []int) { ... }
切片 slice
基本概念
什么是切片
切片是长度可变的序列,是对底层数组的引用(视图) var s []int // 声明一个整数切片
切片的结构
切片在底层是一个结构体,包含三个字段: type slice struct { array unsafe.Pointer // 指向底层数组的指针 len int // 长度(元素个数) cap int // 容量(底层数组能容纳的元素个数) }
切片的特点
长度 vs 容量
长度:切片当前包含的元素个数 容量:从切片第一个元素到底层数组末尾的元素个数 arr := [5]int{1, 2, 3, 4, 5} s := arr[1:4] // [2, 3, 4] fmt.Println(len(s)) // 3 fmt.Println(cap(s)) // 4(从索引1到数组末尾)
切片的声明与初始化
声明语法
var 切片名 []类型 // 示例 var s1 []int // nil 切片 var s2 []string // nil 切片 var s3 []bool // nil 切片
初始化方式
方式1:字面量初始化 s := []int{1, 2, 3, 4, 5} fmt.Println(s) // [1, 2, 3, 4, 5]
方式2:make函数创建 // make([]类型, 长度, 容量) s1 := make([]int, 5) // 长度5,容量5,元素都是零值 s2 := make([]int, 5, 10) // 长度5,容量10 fmt.Println(s1) // [0, 0, 0, 0, 0] fmt.Println(len(s1)) // 5 fmt.Println(cap(s1)) // 5
方式3:基于数组创建 arr := [5]int{1, 2, 3, 4, 5} s := arr[1:4] // [2, 3, 4]
方式4:基于切片创建 s1 := []int{1, 2, 3, 4, 5} s2 := s1[1:4] // [2, 3, 4]
nil切片 vs 空切片
// nil 切片 var s1 []int fmt.Println(s1 == nil) // true fmt.Println(len(s1)) // 0 fmt.Println(cap(s1)) // 0 // 空切片(不是 nil) s2 := []int{} s3 := make([]int, 0) fmt.Println(s2 == nil) // false fmt.Println(len(s2)) // 0 fmt.Println(cap(s2)) // 0
切片的基本操作
访问和修改元素
s := []int{10, 20, 30, 40, 50} fmt.Println(s[0]) // 10 fmt.Println(s[2]) // 30 s[1] = 100 fmt.Println(s) // [10, 100, 30, 40, 50]
append追加元素
s := []int{1, 2, 3} // 追加单个元素 s = append(s, 4) // [1, 2, 3, 4] // 追加多个元素 s = append(s, 5, 6) // [1, 2, 3, 4, 5, 6] // 追加另一个切片(需要 ...) s2 := []int{7, 8, 9} s = append(s, s2...) // [1, 2, 3, 4, 5, 6, 7, 8, 9] 重要:append 不会修改原切片,而是返回一个新的切片。 s := []int{1, 2, 3} s2 := append(s, 4) // s 不变,s2 是新的切片 fmt.Println(s) // [1, 2, 3] fmt.Println(s2) // [1, 2, 3, 4]
子切片
s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} s1 := s[2:5] // [3, 4, 5](索引2到4) s2 := s[:5] // [1, 2, 3, 4, 5](从开头到索引4) s3 := s[5:] // [6, 7, 8, 9, 10](从索引5到末尾) s4 := s[:] // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10](全部) // 限制容量的切片操作 s5 := s[2:5:7] // 长度3,容量5(从索引2到索引7) fmt.Println(len(s5)) // 3 fmt.Println(cap(s5)) // 5
子切片的共享特性
重新切片后的新切片与原切片共享底层数组。 s1 := []int{1, 2, 3, 4, 5} s2 := s1[1:4] // [2, 3, 4] s2[0] = 100 fmt.Println(s1) // [1, 100, 3, 4, 5](原切片也被修改)
删除元素
s := []int{1, 2, 3, 4, 5} // 删除索引2的元素(3) s = append(s[:2], s[3:]...) // [1, 2, 4, 5] // 删除第一个元素 s = s[1:] // [2, 4, 5] // 删除最后一个元素 s = s[:len(s)-1] // [2, 4]
插入元素
s := []int{1, 2, 4, 5} // 在索引2的位置插入3 s = append(s[:2], append([]int{3}, s[2:]...)...) // [1, 2, 3, 4, 5] // 在开头插入 s = append([]int{0}, s...) // [0, 1, 2, 3, 4, 5] // 在末尾插入 s = append(s, 6) // [0, 1, 2, 3, 4, 5, 6]
切片的扩容机制
容量不足时扩容
当 append 时容量不足,Go 会自动分配新的底层数组并复制数据。 s := make([]int, 0, 2) // 长度0,容量2 fmt.Printf("len=%d, cap=%d\n", len(s), cap(s)) // len=0, cap=2 s = append(s, 1, 2) // 容量足够,不扩容 fmt.Printf("len=%d, cap=%d\n", len(s), cap(s)) // len=2, cap=2 s = append(s, 3) // 容量不足,触发扩容 fmt.Printf("len=%d, cap=%d\n", len(s), cap(s)) // len=3, cap=4
扩容规则
Go 1.18+ 的扩容规则: 1. 如果原容量 < 256,新容量 = 原容量 × 2 2. 如果原容量 ≥ 256,新容量 = 原容量 × 1.25(渐进增长) s := []int{1} fmt.Printf("len=%d, cap=%d\n", len(s), cap(s)) // len=1, cap=1 s = append(s, 2) fmt.Printf("len=%d, cap=%d\n", len(s), cap(s)) // len=2, cap=2 s = append(s, 3) fmt.Printf("len=%d, cap=%d\n", len(s), cap(s)) // len=3, cap=4 s = append(s, 4, 5, 6) fmt.Printf("len=%d, cap=%d\n", len(s), cap(s)) // len=6, cap=8
扩容时的内存分配
扩容会分配新数组,原数组如果没有被引用就会被垃圾回收。 s1 := []int{1, 2, 3, 4, 5} s2 := s1[1:4] // s2 共享 s1 的底层数组 s2 = append(s2, 100) // 容量足够(s2 容量是4),不扩容,会影响 s1 fmt.Println(s1) // [1, 2, 3, 4, 100] s2 = append(s2, 200, 300) // 容量不足,触发扩容,不再共享 s2[0] = 999 fmt.Println(s1) // [1, 2, 3, 4, 100](s1 不受影响)
切片的拷贝
copy函数
copy(dst, src) 将 src 的元素复制到 dst,返回复制的元素个数。 // 基本用法 src := []int{1, 2, 3, 4, 5} dst := make([]int, 3) n := copy(dst, src) fmt.Println(dst) // [1, 2, 3] fmt.Println(n) // 3
复制不同长度的切片 src := []int{1, 2, 3, 4, 5} // dst 比 src 短 dst1 := make([]int, 3) copy(dst1, src) // 复制前3个元素 fmt.Println(dst1) // [1, 2, 3] // dst 比 src 长 dst2 := make([]int, 8) copy(dst2, src) // 复制全部5个元素 fmt.Println(dst2) // [1, 2, 3, 4, 5, 0, 0, 0]
切片部分复制 src := []int{1, 2, 3, 4, 5} dst := make([]int, 3) // 复制 src[1:4] 到 dst copy(dst, src[1:4]) // [2, 3, 4] // 复制 src 到 dst[1:4] dst2 := make([]int, 5) copy(dst2[1:4], src) // [0, 1, 2, 3, 0]
赋值与复制的区别
src := []int{1, 2, 3} // 赋值:共享底层数组 s1 := src s1[0] = 100 fmt.Println(src) // [100, 2, 3](原切片被修改) // copy:独立底层数组 s2 := make([]int, len(src)) copy(s2, src) s2[0] = 200 fmt.Println(src) // [100, 2, 3](原切片不变) fmt.Println(s2) // [200, 2, 3]
切片的遍历
for循环
s := []int{10, 20, 30, 40, 50} for i := 0; i < len(s); i++ { fmt.Printf("s[%d] = %d\n", i, s[i]) }
for - range 循环
s := []int{10, 20, 30, 40, 50} // 同时获取索引和值 for index, value := range s { fmt.Printf("索引: %d, 值: %d\n", index, value) } // 只需要值 for _, value := range s { fmt.Println(value) } // 只需要索引 for index := range s { fmt.Println(index) }
遍历时修改元素
s := []int{1, 2, 3, 4, 5} // 通过索引修改(有效) for i := range s { s[i] = s[i] * 2 } fmt.Println(s) // [2, 4, 6, 8, 10] // 通过 value 修改(无效,value 是副本) for _, value := range s { value = 100 // 不会影响原切片 }
切片使用陷阱
陷阱1:append 后原切片不变
s := []int{1, 2, 3} s2 := append(s, 4) // s 不变,需要重新赋值 fmt.Println(s) // [1, 2, 3] fmt.Println(s2) // [1, 2, 3, 4] // 正确做法:重新赋值给原变量 s = append(s, 4)
陷阱2:切片共享底层数组导致意外修改
s1 := []int{1, 2, 3, 4, 5} s2 := s1[1:4] // [2, 3, 4] s2[0] = 100 fmt.Println(s1) // [1, 100, 3, 4, 5](s1 也被修改) 解决方案:使用 copy 创建独立副本 s1 := []int{1, 2, 3, 4, 5} s2 := make([]int, 3) copy(s2, s1[1:4]) s2[0] = 100 fmt.Println(s1) // [1, 2, 3, 4, 5](不变)
陷阱3:循环中使用 append 导致无限循环
// 错误:切片长度在循环中增加,导致无限循环 s := []int{1, 2, 3} for i := 0; i < len(s); i++ { s = append(s, s[i]) // 长度一直在增加 } // 正确:使用固定长度 s := []int{1, 2, 3} length := len(s) for i := 0; i < length; i++ { s = append(s, s[i]) }
陷阱4:for range 遍历时 value 是副本
type Person struct { Name string Age int } people := []Person{{"Alice", 20}, {"Bob", 25}} // 错误:修改 value 无效 for _, p := range people { p.Age++ // 不会影响原切片 } // 正确:通过索引修改 for i := range people { people[i].Age++ } // 或使用指针切片 peoplePtr := []*Person{{"Alice", 20}, {"Bob", 25}} for _, p := range peoplePtr { p.Age++ // 有效 }
陷阱5:内存泄漏(切片引用大数组的一小部分)
func getData() []byte { data := make([]byte, 1024*1024*100) // 100MB 大数组 return data[:10] // 只返回前10个字节 } // 问题:底层100MB数组不会被回收,因为切片还引用着它 // 解决方案:复制需要的数据 func getData() []byte { data := make([]byte, 1024*1024*100) result := make([]byte, 10) copy(result, data[:10]) return result // 只保留10字节,大数组可被回收 }
结构体 struct
概念
结构体是字段的集合,用于自定义数据类型。
示例
// 定义结构体 type Person struct { Name string Age int City string } // 创建实例 var p1 Person // 零值:{"", 0, ""} p2 := Person{"Alice", 25, "Beijing"} // 按字段顺序赋值 p3 := Person{ // 指定字段名 Name: "Bob", Age: 30, City: "Shanghai", } p4 := &Person{Name: "Charlie"} // 指针类型 // 访问字段 p2.Name = "Alice Updated" fmt.Println(p2.Age) // 匿名字段(嵌入) type Employee struct { Person // 匿名字段,继承 Person 的字段 Salary float64 } e := Employee{ Person: Person{Name: "David", Age: 28}, Salary: 10000, } fmt.Println(e.Name) // 直接访问嵌入字段
指针
概念
指针存储变量的内存地址。
示例
var x int = 10 var ptr *int = &x // ptr 指向 x 的地址 // 通过指针访问值 fmt.Println(*ptr) // 10 // 修改指针指向的值 *ptr = 20 fmt.Println(x) // 20 // nil 指针 var nilPtr *int // nil // new 函数创建指针 p := new(int) // p 是 *int 类型,*p 为 0
映射 map
概念
map 是键值对的无序集合。
示例
// 创建 map var m1 map[string]int // nil map,不能直接赋值 m2 := map[string]int{ // 字面量创建 "age": 18, "score": 100, } m3 := make(map[string]int) // 空 map,可赋值 m4 := make(map[string]int, 10) // 预分配容量 // 基本操作 m3["name"] = "Alice" // 添加/修改 value := m3["name"] // 获取值 delete(m3, "name") // 删除键 // 安全取值(判断键是否存在) value, ok := m3["name"] if ok { fmt.Println("存在:", value) } else { fmt.Println("不存在") } // 遍历 map for key, value := range m2 { fmt.Printf("%s: %d\n", key, value) }
通道
概念
通道用于 goroutine 之间的通信。
示例
// 创建通道 ch1 := make(chan int) // 无缓冲通道 ch2 := make(chan string, 10) // 有缓冲通道 // 发送和接收 ch1 <- 42 // 发送 value := <-ch1 // 接收 // 关闭通道 close(ch1) // 遍历通道 for value := range ch2 { fmt.Println(value) }
函数
概念
函数也是引用类型,可以作为值传递。
示例
// 函数类型定义 type MathFunc func(int, int) int // 函数作为值 add := func(a, b int) int { return a + b } result := add(1, 2) // 3 // 函数作为参数 func execute(fn func(int, int) int, a, b int) int { return fn(a, b) }
接口
概念
接口定义了一组方法签名。
示例
// 定义接口 type Reader interface { Read(p []byte) (n int, err error) } // 空接口可以表示任意类型 var any interface{} any = 42 any = "hello" any = Person{} // 类型断言 value, ok := any.(string) if ok { fmt.Println("是字符串:", value) }
值类型 vs 引用类型
类型转换
概念
Go属于强类型语言, 不支持隐式类型转换,必须显式转换。
转换语法
目标类型(值)
var a int = 10 var b int64 = int64(a) // int → int64 var f float64 = 3.14 var i int = int(f) // float64 → int(截断小数) var i32 int32 = 100 var i64 int64 = int64(i32) var u uint = uint(i) // int → uint
数值类型之间的转换
整数之间的转换
整数与浮点数之间的转换
整数与复数之间的转换
字符串与字符/字节的转换
string ↔ []byte
string ↔ []rune
字符串与数值的转换(strconv包)
import "strconv" // 整数 → 字符串 s1 := strconv.Itoa(123) // "123" s2 := strconv.FormatInt(123, 10) // "123"(支持不同进制) // 字符串 → 整数 i1, err := strconv.Atoi("123") // 123 i2, err := strconv.ParseInt("123", 10, 64) // 123 // 浮点数 → 字符串 s3 := strconv.FormatFloat(3.14159, 'f', 2, 64) // "3.14" // 字符串 → 浮点数 f1, err := strconv.ParseFloat("3.14159", 64) // 3.14159 // 布尔值 → 字符串 s4 := strconv.FormatBool(true) // "true" // 字符串 → 布尔值 b1, err := strconv.ParseBool("true") // true
指针之间的转换(unsafe包)
import "unsafe" var i int = 42 var ptr *int = &i // *int → uintptr addr := uintptr(unsafe.Pointer(ptr)) // uintptr → *int(危险,需要确保地址有效) ptr2 := (*int)(unsafe.Pointer(addr))
接口类型转换
具体类型 → 接口(隐式转换)
Go 中具体类型赋值给接口时是自动的,不需要显式转换。 type Writer interface { Write(p []byte) (n int, err error) } type File struct { name string } func (f File) Write(p []byte) (n int, err error) { fmt.Println("Writing to", f.name) return len(p), nil } // 具体类型自动转换为接口 var w Writer = File{name: "test.txt"} // 隐式转换,不需要显式
接口 → 具体类型(类型断言)
从接口中提取具体类型需要类型断言。 var w Writer = File{name: "test.txt"} // 类型断言:获取具体类型 f, ok := w.(File) if ok { fmt.Println(f.name) // "test.txt" } // 不安全的断言(断言失败会 panic) f = w.(File) // 确定是 File 类型时使用
接口与接口的转换
小接口 → 大接口(隐式) 如果接口 A 的方法集是接口 B 的子集,则 A 可以自动转换为 B。 type Reader interface { Read(p []byte) (n int, err error) } type ReadWriter interface { Read(p []byte) (n int, err error) Write(p []byte) (n int, err error) } type MyFile struct{} func (MyFile) Read(p []byte) (n int, err error) { return 0, nil } func (MyFile) Write(p []byte) (n int, err error) { return 0, nil } var rw ReadWriter = MyFile{} // 具体类型 var r Reader = rw // 大接口 → 小接口,自动转换 // 小接口 → 大接口,需要类型断言 var r2 Reader = MyFile{} var rw2 ReadWriter = r2.(ReadWriter) // 类型断言
大接口 → 小接口(自动) var rw ReadWriter = MyFile{} var r Reader = rw // 自动转换(大→小)
空接口的转换
空接口 interface{} 可以接收任何类型,是最常用的接口转换场景。 var any interface{} // 任何类型都可以赋值给空接口 any = 42 any = "hello" any = File{name: "test"} // 从空接口中提取具体类型 // 方式1:类型断言 value, ok := any.(string) if ok { fmt.Println("字符串:", value) } // 方式2:类型 switch switch v := any.(type) { case int: fmt.Println("整数:", v) case string: fmt.Println("字符串:", v) case File: fmt.Println("File:", v.name) default: fmt.Println("未知类型") }
类型别名与自定义类型
类型别名
type MyInt = int type MyMap = map[string]int type MyFunc = func(int) int
自定义类型
type MyInt int type Person struct { Name string Age int } type Handler func(http.ResponseWriter, *http.Request)
两者对比
// 示例:对比两种形式 type CustomInt int // 自定义类型 type AliasInt = int // 类型别名 var a int = 10 var c CustomInt = 20 var a2 AliasInt = 30 // 自定义类型:需要显式转换 a = int(c) // 正确 // a = c // 错误 // 类型别名:可以直接赋值 a = a2 // 正确 a2 = a // 正确 // 方法 func (CustomInt) Method() { fmt.Println("custom") } // func (AliasInt) Method() { } // 错误:不能给别名添加方法 c.Method() // 正确 // a2.Method() // 错误:别名没有方法
使用场景
枚举类型(自定义类型 + iota)
type Status int const ( StatusPending Status = iota StatusRunning StatusSuccess StatusFailed ) func (s Status) String() string { return [...]string{"Pending", "Running", "Success", "Failed"}[s] } var status Status = StatusRunning fmt.Println(status) // Running fmt.Println(status == 1) // true(底层还是 int)
类型安全的ID(自定义类型)
type UserID string type ProductID string func GetUser(id UserID) { ... } func GetProduct(id ProductID) { ... } var uid UserID = "user-123" var pid ProductID = "product-456" GetUser(uid) // 正确 // GetUser(pid) // 编译错误:类型不匹配
函数类型别名(提高可读性)
// 复杂函数类型 type Middleware = func(http.Handler) http.Handler // 使用别名 func WithLogging(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println("Request:", r.URL) next.ServeHTTP(w, r) }) } var m Middleware = WithLogging
运算符
算术运算符
关系运算符 (比较运算符)
可比较的类型: 基本类型:bool、数值、字符串 指针:比较地址是否相同 通道:比较是否为同一个通道 结构体:字段全部可比较时,结构体可比较 数组:元素可比较时,数组可比较 接口:比较动态类型和动态值
逻辑运算符
位运算符
赋值运算符
其他运算符
运算符优先级
流程控制
条件判断
if
// 基本形式 if condition { // 条件为 true 时执行 }
if - else
// if - else if condition { // 条件为 true 时执行 } else { // 条件为 false 时执行 }
if - else if - else
// if - else if - else if condition1 { // condition1 为 true } else if condition2 { // condition1 为 false,condition2 为 true } else { // 全部为 false }
带初始化语句
// 可以在 if 条件前执行一个简单语句 if err := doSomething(); err != nil { fmt.Println("错误:", err) } // 变量作用域仅限于 if-else 块内 if num := 10; num > 0 { fmt.Println("正数:", num) } // fmt.Println(num) // 编译错误:num 在此不可见 // 多变量初始化 if a, b := 10, 20; a < b { fmt.Println("a 小于 b") }
分支控制
switch
表达式switch
switch expression { case value1: // expression == value1 时执行 case value2: // expression == value2 时执行 default: // 所有case都不匹配时执行 }
func main() { day := 3 // 基本switch switch day { case 1: fmt.Println("周一") case 2: fmt.Println("周二") case 3: fmt.Println("周三") case 4: fmt.Println("周四") case 5: fmt.Println("周五") case 6: fmt.Println("周六") case 7: fmt.Println("周日") default: fmt.Println("无效的日期") } }
func main() { // 多个值匹配同一个case switch day { case 1, 2, 3, 4, 5: fmt.Println("工作日") case 6, 7: fmt.Println("周末") default: fmt.Println("无效的日期") } }
无表达式switch
// 等价于 switch true,所以case后面表达式返回的一定是布尔值 switch { case condition1: // 条件1为真 case condition2: // 条件2为真 case condition3 && condition4: // 复合条件 default: // 所有条件都不满足 }
score := 85 switch { case score >= 90: fmt.Println("优秀") case score >= 80: fmt.Println("良好") case score >= 60: fmt.Println("及格") default: fmt.Println("不及格") }
type switch(类型判断)
switch v := x.(type) { case int: fmt.Printf("整数: %d\n", v) case string: fmt.Printf("字符串: %s\n", v) case float64: fmt.Printf("浮点数: %f\n", v) case bool: fmt.Printf("布尔值: %t\n", v) default: fmt.Printf("未知类型: %T\n", v) }
var x interface{} = 7 switch v := x.(type) { case int: fmt.Printf("整数: %d\n", v) case string: fmt.Printf("字符串: %s\n", v) default: fmt.Printf("未知类型: %T\n", v) }
fallthrough
// fallthrough(穿透到下一个case) switch num := 1; num { case 1: fmt.Println("1") fallthrough case 2: fmt.Println("2") // 当num=1时也会执行 case 3: fmt.Println("3") }
break
// 可以在case中使用break提前跳出switch switch num { case 1: fmt.Println("开始处理") if condition { break // 跳出switch,不会执行后续的fmt.Println } fmt.Println("处理完成") }
循环控制
for
// 1. 完整形式(类似C的for) for 初始化语句; 条件表达式; 后置语句 { // 循环体 }
// 示例:输出 0 到 4 for i := 0; i < 5; i++ { fmt.Println(i) }
// 2. 只有条件(类似while) for condition { // 循环体 }
// 示例:输出 0 到 4 i := 0 for i < 5 { fmt.Println(i) i++ }
// 3. 无限循环 for { // 循环体 }
// 示例:带退出条件 count := 0 for { fmt.Println("循环中...") count++ if count >= 3 { break } }
for - range
// 4. range子句(遍历数组、切片、映射、字符串、通道) for index, value := range collection { // 循环体 }
// 遍历切片 slice := []int{10, 20, 30} for index, value := range slice { fmt.Printf("索引: %d, 值: %d\n", index, value) } // 只需要索引 for index := range slice { fmt.Println("索引:", index) } // 只需要值(使用匿名变量) for _, value := range slice { fmt.Println("值:", value) } // 遍历字符串(按 rune,不是字节) s := "Hello, 世界" for index, ch := range s { fmt.Printf("%d: %c\n", index, ch) } // 遍历 map m := map[string]int{"a": 1, "b": 2} for key, value := range m { fmt.Printf("%s: %d\n", key, value) } // 遍历通道 ch := make(chan int) go func() { ch <- 1 ch <- 2 ch <- 3 close(ch) }() for value := range ch { fmt.Println(value) }
跳转控制
continue
跳过当前迭代,继续下一次循环。
// 跳过偶数 for i := 0; i < 10; i++ { if i%2 == 0 { continue // 跳过本次循环 } fmt.Println(i) // 输出 1,3,5,7,9 } // 跳过外层循环 outer: for i := 0; i < 3; i++ { for j := 0; j < 3; j++ { if i == 1 && j == 1 { continue outer // 跳过外层循环的当前迭代 } fmt.Printf("(%d,%d) ", i, j) } } // 输出:(0,0) (0,1) (0,2) (1,0) (2,0) (2,1) (2,2)
break
退出当前循环(switch、select、for)。
// 退出循环 for i := 0; i < 10; i++ { if i == 5 { break // i=5 时退出循环 } fmt.Println(i) // 输出 0,1,2,3,4 } // 跳出多层循环(使用标签) outer: for i := 0; i < 3; i++ { for j := 0; j < 3; j++ { if i == 1 && j == 1 { break outer // 跳出 outer 标签标记的循环 } fmt.Printf("(%d,%d) ", i, j) } } // 输出:(0,0) (0,1) (0,2) (1,0)
goto
跳转到同一函数内的标签。
// 基本用法 func main() { fmt.Println("开始") goto End fmt.Println("这行不会执行") End: fmt.Println("结束") } // 输出: // 开始 // 结束 // 实际用途:统一错误处理 func process() { err := step1() if err != nil { goto Cleanup } err = step2() if err != nil { goto Cleanup } err = step3() if err != nil { goto Cleanup } return Cleanup: fmt.Println("清理资源:", err) }
扩展点
defer: 延迟执行,详见【函数】章节
select: 通道选择,详见【并发编程】章节
函数
数组
切片
map
指针
主题