第一次提交
This commit is contained in:
178
src/programming/backend/go/Go基础语法/01Hello World.md
Normal file
178
src/programming/backend/go/Go基础语法/01Hello World.md
Normal file
@@ -0,0 +1,178 @@
|
||||
---
|
||||
icon: mdi:language-go
|
||||
date: 2025-05-22
|
||||
title: Go 基础语法 - Hello World
|
||||
category:
|
||||
- Go
|
||||
- 后端
|
||||
- 编程
|
||||
- 教程
|
||||
- 入门
|
||||
- 开发环境
|
||||
- Linux
|
||||
- GoLand
|
||||
- VSCode
|
||||
- 命令行
|
||||
- Go Module
|
||||
- Go fmt
|
||||
- Go build
|
||||
- Go run
|
||||
- GOPATH
|
||||
- GOROOT
|
||||
- Linux Mint
|
||||
- Go 1.22.2
|
||||
- Hello World
|
||||
---
|
||||
|
||||
好的!根据你提供的环境(Linux Mint XFCE、Go 1.22.2 已安装、项目目录为 `/home/liumangmang/GolandProjects`),我将为你梳理 **完整但简洁** 的步骤,包括验证安装、配置 Go module、编写 Hello World、以及运行 `go fmt` 和 `go build`。
|
||||
|
||||
<!-- more -->
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 1:验证 Go 安装
|
||||
|
||||
打开终端,运行:
|
||||
|
||||
```bash
|
||||
go version
|
||||
```
|
||||
|
||||
预期输出:
|
||||
```
|
||||
go version go1.22.2 linux/amd64
|
||||
```
|
||||
|
||||
同时检查 GOPATH 和 GOROOT(Go 1.11+ 默认支持 module,GOPATH 不再强制):
|
||||
|
||||
```bash
|
||||
go env GOROOT GOPATH
|
||||
```
|
||||
|
||||
通常输出类似:
|
||||
```
|
||||
/usr/local/go
|
||||
/home/liumangmang/go
|
||||
```
|
||||
|
||||
> 📌 提示:Go 1.22 默认启用 module 模式,无需设置 `GO111MODULE=on`。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 2:创建项目并初始化 Go Module
|
||||
|
||||
进入你的项目目录:
|
||||
|
||||
```bash
|
||||
cd /home/liumangmang/GolandProjects
|
||||
mkdir hello-go && cd hello-go
|
||||
```
|
||||
|
||||
初始化一个 Go module(模块名建议用类似域名的格式,比如 `example.com/hello-go`,本地开发可随意):
|
||||
|
||||
```bash
|
||||
go mod init hello-go
|
||||
```
|
||||
|
||||
这会生成 `go.mod` 文件,内容类似:
|
||||
|
||||
```go
|
||||
module hello-go
|
||||
|
||||
go 1.22
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 3:编写 Hello World
|
||||
|
||||
创建主程序文件:
|
||||
|
||||
```bash
|
||||
nano main.go
|
||||
```
|
||||
|
||||
输入以下代码:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello, Go from Linux Mint!")
|
||||
}
|
||||
```
|
||||
|
||||
保存并退出(`Ctrl+O` → 回车 → `Ctrl+X`)。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 4:格式化代码(go fmt)
|
||||
|
||||
运行格式化命令:
|
||||
|
||||
```bash
|
||||
go fmt
|
||||
```
|
||||
|
||||
> 如果代码已规范,可能无输出。`go fmt` 会自动修正缩进、空格等风格问题。
|
||||
|
||||
你可以故意写成不规范格式测试一下,例如:
|
||||
|
||||
```go
|
||||
func main(){fmt.
|
||||
Println("Hello")}
|
||||
```
|
||||
|
||||
再运行 `go fmt`,它会自动修复。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 5:构建和运行程序
|
||||
|
||||
### 构建二进制文件:
|
||||
|
||||
```bash
|
||||
go build
|
||||
```
|
||||
|
||||
成功后会在当前目录生成一个可执行文件(在 Linux 下默认名为 `hello-go`,即 module 名):
|
||||
|
||||
```bash
|
||||
./hello-go
|
||||
```
|
||||
|
||||
输出:
|
||||
```
|
||||
Hello, Go from Linux Mint!
|
||||
```
|
||||
|
||||
### 或者直接运行(不生成二进制):
|
||||
|
||||
```bash
|
||||
go run .
|
||||
```
|
||||
|
||||
同样输出:
|
||||
```
|
||||
Hello, Go from Linux Mint!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 额外建议(GoLand 集成)
|
||||
|
||||
- 在 GoLand 中打开 `/home/liumangmang/GolandProjects/hello-go` 目录。
|
||||
- GoLand 会自动识别 `go.mod` 并启用 module 支持。
|
||||
- 你可以通过 IDE 的绿色 ▶️ 按钮直接运行 `main.go`。
|
||||
- `go fmt` 在 GoLand 中默认在保存时自动触发(可在 Settings → Tools → File Watchers 中配置)。
|
||||
|
||||
---
|
||||
|
||||
✅ 到此,你已完成:
|
||||
- Go 环境验证
|
||||
- Module 初始化
|
||||
- Hello World 编写
|
||||
- 代码格式化(`go fmt`)
|
||||
- 编译与运行(`go build` / `go run`)
|
||||
208
src/programming/backend/go/Go基础语法/02变量与类型.md
Normal file
208
src/programming/backend/go/Go基础语法/02变量与类型.md
Normal file
@@ -0,0 +1,208 @@
|
||||
---
|
||||
icon: mdi:variable
|
||||
date: 2025-05-22
|
||||
title: Go 基础语法 - 变量与类型
|
||||
category:
|
||||
- Go
|
||||
- 后端
|
||||
- 编程
|
||||
- 教程
|
||||
- 入门
|
||||
- 变量
|
||||
- 类型
|
||||
- 指针
|
||||
- new
|
||||
- make
|
||||
- 简短声明
|
||||
- 基本类型
|
||||
- var
|
||||
---
|
||||
|
||||
当然可以!以下是基于你当前环境(Linux Mint XFCE、Go 1.22.2、项目目录 `/home/liumangmang/GolandProjects`)整理的 **Go 语言基础语法实践步骤**,涵盖:
|
||||
|
||||
- 基本类型
|
||||
- 变量声明
|
||||
- `:=` 简短声明
|
||||
- 指针
|
||||
- `new` vs `make`
|
||||
|
||||
我们将通过一个可运行的示例程序来演示这些概念。
|
||||
|
||||
<!-- more -->
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 1:创建练习项目
|
||||
|
||||
打开终端:
|
||||
|
||||
```bash
|
||||
cd /home/liumangmang/GolandProjects
|
||||
mkdir go-basics && cd go-basics
|
||||
go mod init go-basics
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 2:编写示例代码(main.go)
|
||||
|
||||
使用你喜欢的编辑器(或 GoLand)创建 `main.go`:
|
||||
|
||||
```bash
|
||||
nano main.go
|
||||
```
|
||||
|
||||
粘贴以下完整示例代码(包含注释说明):
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
// ========== 1. 基本类型 ==========
|
||||
var a int = 42
|
||||
var b float64 = 3.14
|
||||
var c bool = true
|
||||
var d string = "Hello, Go!"
|
||||
|
||||
fmt.Println("基本类型:")
|
||||
fmt.Printf("int: %d\n", a)
|
||||
fmt.Printf("float64: %.2f\n", b)
|
||||
fmt.Printf("bool: %t\n", c)
|
||||
fmt.Printf("string: %s\n", d)
|
||||
|
||||
// ========== 2. 变量声明方式 ==========
|
||||
// 方式1:var 声明(可带或不带初始值)
|
||||
var x int
|
||||
x = 100
|
||||
var y = 200 // 类型自动推导
|
||||
|
||||
// 方式2:批量声明
|
||||
var (
|
||||
name = "Liu"
|
||||
age = 30
|
||||
height = 175.5
|
||||
isAdmin = true
|
||||
)
|
||||
|
||||
fmt.Println("\n变量声明:")
|
||||
fmt.Println("x =", x, ", y =", y)
|
||||
fmt.Println("name:", name, "age:", age, "height:", height, "isAdmin:", isAdmin)
|
||||
|
||||
// ========== 3. := 简短声明(仅在函数内部可用)==========
|
||||
z := 999 // 自动推导为 int
|
||||
message := "Use := !" // 自动推导为 string
|
||||
fmt.Println("\n简短声明 (:=):")
|
||||
fmt.Println("z =", z, ", message =", message)
|
||||
|
||||
// 注意::= 必须至少声明一个新变量
|
||||
// 下面这行会报错:no new variables on left side of :=
|
||||
// z := 888 // ❌ 错误!
|
||||
|
||||
// 但可以这样混合新旧变量:
|
||||
w, z := "new", 888 // w 是新的,z 是重新赋值(shadowing)
|
||||
fmt.Println("w =", w, ", z =", z)
|
||||
|
||||
// ========== 4. 指针 ==========
|
||||
p := &a // p 是指向 a 的指针(*int 类型)
|
||||
fmt.Println("\n指针:")
|
||||
fmt.Printf("a 的值: %d\n", a)
|
||||
fmt.Printf("a 的地址: %p\n", &a)
|
||||
fmt.Printf("p 的值(即 a 的地址): %p\n", p)
|
||||
fmt.Printf("*p(解引用): %d\n", *p)
|
||||
|
||||
*p = 1000 // 通过指针修改 a 的值
|
||||
fmt.Println("修改后 a =", a)
|
||||
|
||||
// ========== 5. new vs make ==========
|
||||
// new(T) → 分配零值内存,返回 *T
|
||||
ptrInt := new(int) // *int,值为 0
|
||||
*ptrInt = 42
|
||||
fmt.Println("\nnew(int):", *ptrInt)
|
||||
|
||||
// make 仅用于 slice, map, chan —— 返回初始化后的 T(不是指针!)
|
||||
s := make([]int, 3) // 长度为3的 slice,元素为 [0 0 0]
|
||||
m := make(map[string]int) // 空 map
|
||||
ch := make(chan int, 2) // 缓冲通道
|
||||
|
||||
s[0] = 10
|
||||
m["count"] = 1
|
||||
ch <- 100
|
||||
|
||||
fmt.Println("make(slice):", s)
|
||||
fmt.Println("make(map):", m)
|
||||
fmt.Println("make(chan) 示例(略去接收)")
|
||||
|
||||
// ⚠️ 不能对 slice/map/chan 使用 new!
|
||||
// bad := new([]int) // 这是一个 *[]int,但底层数组未初始化,不能直接用!
|
||||
}
|
||||
```
|
||||
|
||||
保存并退出(`Ctrl+O` → Enter → `Ctrl+X`)。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 3:格式化 & 运行
|
||||
|
||||
```bash
|
||||
# 格式化代码(虽然我们写得很规范,但习惯要好)
|
||||
go fmt
|
||||
|
||||
# 运行程序
|
||||
go run .
|
||||
```
|
||||
|
||||
你应该看到类似输出:
|
||||
|
||||
```
|
||||
基本类型:
|
||||
int: 42
|
||||
float64: 3.14
|
||||
bool: true
|
||||
string: Hello, Go!
|
||||
|
||||
变量声明:
|
||||
x = 100 , y = 200
|
||||
name: Liu age: 30 height: 175.5 isAdmin: true
|
||||
|
||||
简短声明 (:=):
|
||||
z = 999 , message = Use := !
|
||||
w = new , z = 888
|
||||
|
||||
指针:
|
||||
a 的值: 42
|
||||
a 的地址: 0xc0000b4008
|
||||
p 的值(即 a 的地址): 0xc0000b4008
|
||||
*p(解引用): 42
|
||||
修改后 a = 1000
|
||||
|
||||
new(int): 42
|
||||
make(slice): [10 0 0]
|
||||
make(map): map[count:1]
|
||||
make(chan) 示例(略去接收)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 关键知识点总结(对比 Java)
|
||||
|
||||
| 概念 | Go 行为 | Java 对比 |
|
||||
|------|--------|----------|
|
||||
| **变量声明** | `var x int = 1` 或 `x := 1` | `int x = 1;`(必须指定类型或用 var + 初始化) |
|
||||
| **:=** | 函数内快速声明+初始化,至少一个新变量 | Java 无直接等价(Java 10+ 有 `var`,但非声明新作用域) |
|
||||
| **指针** | 显式支持 `*T` 和 `&x` | Java 无显式指针(对象引用类似,但不可运算) |
|
||||
| **new** | 分配零值内存,返回指针 `*T` | 类似 `new Object()`,但 Go 的 `new` 只用于值类型 |
|
||||
| **make** | 专用于 slice/map/chan 的初始化(返回 T,非指针) | Java 中 `new ArrayList<>()` 等构造器承担类似职责 |
|
||||
|
||||
> 💡 提醒:Go 中 **slice / map / chan 是引用类型**,传递时是“引用语义”,但它们本身不是指针。`make` 是它们的正确初始化方式。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 下一步建议
|
||||
|
||||
- 在 GoLand 中打开这个项目,尝试修改变量、加断点调试指针。
|
||||
- 尝试注释掉 `*p = 1000`,观察 `a` 是否变化。
|
||||
- 尝试 `bad := new([]int); bad[0] = 1`,看是否会 panic(会!因为底层未分配)。
|
||||
|
||||
如果你希望我继续讲解 **结构体、方法、接口、goroutine** 等内容,随时告诉我!祝你 Go 学习顺利 🚀
|
||||
266
src/programming/backend/go/Go基础语法/03slice和map.md
Normal file
266
src/programming/backend/go/Go基础语法/03slice和map.md
Normal file
@@ -0,0 +1,266 @@
|
||||
---
|
||||
icon: mdi:table-arrow-down
|
||||
date: 2025-05-22
|
||||
title: Go 基础语法 - Slice 和 Map
|
||||
category:
|
||||
- Go
|
||||
- 后端
|
||||
- 编程
|
||||
- 教程
|
||||
- 数据结构
|
||||
- slice
|
||||
- map
|
||||
- 扩容机制
|
||||
- 深拷贝
|
||||
- 浅拷贝
|
||||
- 并发安全
|
||||
- sync.Map
|
||||
---
|
||||
|
||||
当然可以!以下是基于你当前环境(Linux Mint XFCE、Go 1.22.2、项目目录 `/home/liumangmang/GolandProjects`)整理的 **Go 语言 slice 与 map 深入实践步骤**,涵盖:
|
||||
|
||||
- **slice 的扩容机制**
|
||||
- **slice 的深拷贝 vs 浅拷贝**
|
||||
- **map 的基本使用**
|
||||
- **map 的并发写风险与安全方案**
|
||||
|
||||
我们将通过一个可运行的示例程序,在你的环境中一步步验证这些行为。
|
||||
|
||||
<!-- more -->
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 1:创建新项目
|
||||
|
||||
```bash
|
||||
cd /home/liumangmang/GolandProjects
|
||||
mkdir go-slice-map && cd go-slice-map
|
||||
go mod init go-slice-map
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 2:编写演示代码(main.go)
|
||||
|
||||
创建 `main.go`:
|
||||
|
||||
```bash
|
||||
nano main.go
|
||||
```
|
||||
|
||||
粘贴以下完整代码(含详细注释):
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("=== 1. Slice 扩容机制 ===")
|
||||
demoSliceGrowth()
|
||||
|
||||
fmt.Println("\n=== 2. Slice 深拷贝 vs 浅拷贝 ===")
|
||||
demoSliceCopy()
|
||||
|
||||
fmt.Println("\n=== 3. Map 基本使用 ===")
|
||||
demoMapUsage()
|
||||
|
||||
fmt.Println("\n=== 4. Map 并发写风险(会 panic!)===")
|
||||
// ⚠️ 下面这行默认注释掉,避免程序崩溃
|
||||
// demoUnsafeConcurrentMap()
|
||||
|
||||
fmt.Println("\n=== 5. 安全的并发 map(sync.Map)===")
|
||||
demoSafeConcurrentMap()
|
||||
}
|
||||
|
||||
// 1. 演示 slice 扩容机制
|
||||
func demoSliceGrowth() {
|
||||
var s []int
|
||||
fmt.Printf("初始: len=%d, cap=%d, addr=%p\n", len(s), cap(s), s)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
s = append(s, i)
|
||||
fmt.Printf("append %d: len=%d, cap=%d, addr=%p\n", i, len(s), cap(s), s)
|
||||
}
|
||||
// 观察:当容量不足时,底层数组会重新分配,地址改变
|
||||
}
|
||||
|
||||
// 2. 深拷贝 vs 浅拷贝
|
||||
func demoSliceCopy() {
|
||||
original := []int{1, 2, 3, 4, 5}
|
||||
shallow := original // 浅拷贝:共享底层数组
|
||||
deep := make([]int, len(original))
|
||||
copy(deep, original) // 深拷贝:独立底层数组
|
||||
|
||||
fmt.Println("修改前:")
|
||||
fmt.Println("original:", original)
|
||||
fmt.Println("shallow :", shallow)
|
||||
fmt.Println("deep :", deep)
|
||||
|
||||
// 修改原 slice
|
||||
original[0] = 999
|
||||
|
||||
fmt.Println("\n修改 original[0] = 999 后:")
|
||||
fmt.Println("original:", original) // [999, 2, 3, 4, 5]
|
||||
fmt.Println("shallow :", shallow) // [999, 2, 3, 4, 5] ← 被影响!
|
||||
fmt.Println("deep :", deep) // [1, 2, 3, 4, 5] ← 不受影响
|
||||
}
|
||||
|
||||
// 3. Map 基本使用
|
||||
func demoMapUsage() {
|
||||
m := make(map[string]int)
|
||||
m["apple"] = 5
|
||||
m["banana"] = 3
|
||||
|
||||
fmt.Println("map 内容:", m)
|
||||
fmt.Println("apple 数量:", m["apple"])
|
||||
|
||||
// 检查 key 是否存在
|
||||
if count, ok := m["orange"]; ok {
|
||||
fmt.Println("orange 存在,数量:", count)
|
||||
} else {
|
||||
fmt.Println("orange 不存在")
|
||||
}
|
||||
|
||||
// 删除 key
|
||||
delete(m, "apple")
|
||||
fmt.Println("删除 apple 后:", m)
|
||||
}
|
||||
|
||||
// 4. 不安全的并发 map 写入(会导致 panic)
|
||||
func demoUnsafeConcurrentMap() {
|
||||
m := make(map[int]int)
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func(key int) {
|
||||
defer wg.Done()
|
||||
m[key] = key * 10 // 并发写 → fatal error: concurrent map writes
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
fmt.Println("unsafe map done (should not reach here)")
|
||||
}
|
||||
|
||||
// 5. 使用 sync.Map 实现安全并发
|
||||
func demoSafeConcurrentMap() {
|
||||
var sm sync.Map
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func(key int) {
|
||||
defer wg.Done()
|
||||
sm.Store(key, key*10)
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// 读取部分值验证
|
||||
sm.Range(func(key, value any) bool {
|
||||
if key.(int) < 5 { // 只打印前几个
|
||||
fmt.Printf("key=%v, value=%v\n", key, value)
|
||||
}
|
||||
return true // 继续遍历
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
保存退出(`Ctrl+O` → Enter → `Ctrl+X`)。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 3:格式化并运行
|
||||
|
||||
```bash
|
||||
go fmt
|
||||
go run .
|
||||
```
|
||||
|
||||
### 预期输出(节选):
|
||||
|
||||
```
|
||||
=== 1. Slice 扩容机制 ===
|
||||
初始: len=0, cap=0, addr=0x0
|
||||
append 0: len=1, cap=1, addr=0xc0000180c0
|
||||
append 1: len=2, cap=2, addr=0xc0000180e0
|
||||
append 2: len=3, cap=4, addr=0xc00001c0a0 ← 容量翻倍,地址变了!
|
||||
...
|
||||
|
||||
=== 2. Slice 深拷贝 vs 浅拷贝 ===
|
||||
修改前:
|
||||
original: [1 2 3 4 5]
|
||||
shallow : [1 2 3 4 5]
|
||||
deep : [1 2 3 4 5]
|
||||
|
||||
修改 original[0] = 999 后:
|
||||
original: [999 2 3 4 5]
|
||||
shallow : [999 2 3 4 5]
|
||||
deep : [1 2 3 4 5]
|
||||
|
||||
=== 3. Map 基本使用 ===
|
||||
map 内容: map[apple:5 banana:3]
|
||||
apple 数量: 5
|
||||
orange 不存在
|
||||
删除 apple 后: map[banana:3]
|
||||
|
||||
=== 4. Map 并发写风险(会 panic!)===
|
||||
|
||||
=== 5. 安全的并发 map(sync.Map)===
|
||||
key=0, value=0
|
||||
key=1, value=10
|
||||
key=2, value=20
|
||||
key=3, value=30
|
||||
key=4, value=40
|
||||
```
|
||||
|
||||
> 🔒 注意:`demoUnsafeConcurrentMap()` 默认被注释。如果你想**亲眼看到 panic**,可以取消注释它再运行——程序会崩溃并输出:
|
||||
>
|
||||
> ```
|
||||
> fatal error: concurrent map writes
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## 📚 关键知识点总结
|
||||
|
||||
### 🔸 Slice 扩容机制(Go 1.22)
|
||||
- 当 `cap < 1024`:扩容为 **2 倍**
|
||||
- 当 `cap >= 1024`:扩容为 **1.25 倍**(实际策略可能微调,但趋势是渐进增长)
|
||||
- 扩容时会**分配新底层数组**,旧 slice 和新 slice **不再共享数据**
|
||||
|
||||
### 🔸 深拷贝 vs 浅拷贝
|
||||
| 方式 | 是否共享底层数组 | 是否安全独立修改 |
|
||||
|------|------------------|------------------|
|
||||
| `s2 := s1` | ✅ 是 | ❌ 否 |
|
||||
| `copy(s2, s1)` | ❌ 否 | ✅ 是 |
|
||||
|
||||
> 💡 提醒:`copy` 要求目标 slice 已分配内存(用 `make`)!
|
||||
|
||||
### 🔸 Map 并发安全
|
||||
- **普通 map 不是并发安全的**:多个 goroutine 同时写会 panic
|
||||
- **解决方案**:
|
||||
- 使用 `sync.Mutex` 或 `sync.RWMutex` 保护 map
|
||||
- 或直接使用 `sync.Map`(适用于读多写少场景)
|
||||
|
||||
> ⚠️ `sync.Map` 性能不一定比加锁的 map 高,仅在特定场景推荐使用。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 在 GoLand 中调试建议
|
||||
|
||||
1. 打开项目 `/home/liumangmang/GolandProjects/go-slice-map`
|
||||
2. 在 `demoSliceGrowth()` 中设置断点,观察每次 `append` 后地址变化
|
||||
3. 尝试取消注释 `demoUnsafeConcurrentMap()`,运行看 panic(有助于理解并发风险)
|
||||
|
||||
---
|
||||
|
||||
如果你希望继续学习 **channel、goroutine、context、错误处理** 或 **Go 与 Java 对比(如 slice vs ArrayList)**,欢迎随时告诉我!祝你 Go 编程愉快 🚀
|
||||
187
src/programming/backend/go/Go基础语法/04Struct、方法与接收者类型详解.md
Normal file
187
src/programming/backend/go/Go基础语法/04Struct、方法与接收者类型详解.md
Normal file
@@ -0,0 +1,187 @@
|
||||
---
|
||||
icon: mdi:shape-outline
|
||||
date: 2025-05-22
|
||||
title: Go 基础语法 - Struct、方法与接收者类型详解
|
||||
category:
|
||||
- Go
|
||||
- 后端
|
||||
- 编程
|
||||
- 教程
|
||||
- 面向对象
|
||||
- Struct
|
||||
- 方法
|
||||
- 接收者
|
||||
- 值接收者
|
||||
- 指针接收者
|
||||
- 面向对象编程
|
||||
---
|
||||
|
||||
当然可以!以下是专为你定制的实践指南,基于你的环境:
|
||||
|
||||
- **系统**:Linux Mint XFCE
|
||||
- **Go 版本**:go1.22.2 linux/amd64
|
||||
- **项目目录**:`/home/liumangmang/GolandProjects`
|
||||
|
||||
<!-- more -->
|
||||
|
||||
## 📌 标题:
|
||||
# Go 面向对象基石:Struct、方法与接收者类型详解(值 vs 指针)
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 1:创建练习项目
|
||||
|
||||
打开终端,执行:
|
||||
|
||||
```bash
|
||||
cd /home/liumangmang/GolandProjects
|
||||
mkdir go-struct-methods && cd go-struct-methods
|
||||
go mod init go-struct-methods
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 2:编写演示代码(main.go)
|
||||
|
||||
创建并编辑 `main.go`:
|
||||
|
||||
```bash
|
||||
nano main.go
|
||||
```
|
||||
|
||||
粘贴以下完整示例代码(含详细注释和对比):
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
// 定义一个 Person 结构体
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
// ========== 值接收者方法 ==========
|
||||
func (p Person) SayHello() {
|
||||
fmt.Printf("Hello, I'm %s (值接收者)\n", p.Name)
|
||||
}
|
||||
|
||||
// 值接收者无法修改原始结构体字段
|
||||
func (p Person) SetName(name string) {
|
||||
p.Name = name // 修改的是副本!
|
||||
fmt.Printf("在值接收者中设置 Name = %s\n", p.Name)
|
||||
}
|
||||
|
||||
// ========== 指针接收者方法 ==========
|
||||
func (p *Person) SayHelloPtr() {
|
||||
fmt.Printf("Hello, I'm %s (指针接收者)\n", p.Name)
|
||||
}
|
||||
|
||||
// 指针接收者可以真正修改原始结构体
|
||||
func (p *Person) SetNamePtr(name string) {
|
||||
p.Name = name // 修改的是原始对象!
|
||||
fmt.Printf("在指针接收者中设置 Name = %s\n", p.Name)
|
||||
}
|
||||
|
||||
// ========== 对比调用行为 ==========
|
||||
func main() {
|
||||
fmt.Println("=== 1. 使用值类型变量调用方法 ===")
|
||||
p1 := Person{Name: "Alice", Age: 30}
|
||||
p1.SayHello() // OK
|
||||
p1.SetName("Alicia") // ❌ 不会改变 p1.Name
|
||||
fmt.Printf("调用值接收者 SetName 后,p1.Name = %s\n", p1.Name)
|
||||
|
||||
p1.SayHelloPtr() // ✅ Go 自动取地址 (&p1)
|
||||
p1.SetNamePtr("Anna") // ✅ 真正修改了 p1
|
||||
fmt.Printf("调用指针接收者 SetNamePtr 后,p1.Name = %s\n", p1.Name)
|
||||
|
||||
fmt.Println("\n=== 2. 使用指针类型变量调用方法 ===")
|
||||
p2 := &Person{Name: "Bob", Age: 25}
|
||||
p2.SayHello() // ✅ Go 自动解引用 (*p2)
|
||||
p2.SayHelloPtr() // ✅ 直接调用
|
||||
|
||||
p2.SetName("Bobby") // ❌ 副本修改,无效
|
||||
fmt.Printf("调用值接收者 SetName 后,p2.Name = %s\n", p2.Name)
|
||||
|
||||
p2.SetNamePtr("Robert") // ✅ 真正修改
|
||||
fmt.Printf("调用指针接收者 SetNamePtr 后,p2.Name = %s\n", p2.Name)
|
||||
|
||||
fmt.Println("\n=== 3. 关键规则总结 ===")
|
||||
fmt.Println("- 值接收者:操作副本,不能修改原结构体")
|
||||
fmt.Println("- 指针接收者:操作原始数据,可修改")
|
||||
fmt.Println("- Go 会自动处理 & 和 * 转换(只要变量可寻址)")
|
||||
fmt.Println("- 如果方法需要修改字段,请使用指针接收者!")
|
||||
}
|
||||
```
|
||||
|
||||
保存并退出(`Ctrl+O` → Enter → `Ctrl+X`)。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 3:格式化并运行
|
||||
|
||||
```bash
|
||||
go fmt
|
||||
go run .
|
||||
```
|
||||
|
||||
### 预期输出:
|
||||
|
||||
```
|
||||
=== 1. 使用值类型变量调用方法 ===
|
||||
Hello, I'm Alice (值接收者)
|
||||
在值接收者中设置 Name = Alicia
|
||||
调用值接收者 SetName 后,p1.Name = Alice
|
||||
Hello, I'm Alice (指针接收者)
|
||||
在指针接收者中设置 Name = Anna
|
||||
调用指针接收者 SetNamePtr 后,p1.Name = Anna
|
||||
|
||||
=== 2. 使用指针类型变量调用方法 ===
|
||||
Hello, I'm Bob (值接收者)
|
||||
Hello, I'm Bob (指针接收者)
|
||||
在值接收者中设置 Name = Bobby
|
||||
调用值接收者 SetName 后,p2.Name = Bob
|
||||
在指针接收者中设置 Name = Robert
|
||||
调用指针接收者 SetNamePtr 后,p2.Name = Robert
|
||||
|
||||
=== 3. 关键规则总结 ===
|
||||
- 值接收者:操作副本,不能修改原结构体
|
||||
- 指针接收者:操作原始数据,可修改
|
||||
- Go 会自动处理 & 和 * 转换(只要变量可寻址)
|
||||
- 如果方法需要修改字段,请使用指针接收者!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 核心知识点解析(对比 Java)
|
||||
|
||||
| 概念 | Go 行为 | Java 类比 |
|
||||
|------|--------|----------|
|
||||
| **Struct** | 值类型,默认按值传递 | 类似 `class`,但 Go 的 struct 是值语义(除非用指针) |
|
||||
| **方法** | 绑定到类型(struct 或其他) | 类似 Java 的实例方法 |
|
||||
| **值接收者** | 方法内操作的是副本 | 类似 Java 中传入不可变对象(但 Java 对象默认是引用) |
|
||||
| **指针接收者** | 方法内可修改原始对象 | 更接近 Java 实例方法的行为(因为 Java 对象总是引用) |
|
||||
|
||||
> 💡 **重要提示**:
|
||||
> - 在 Go 中,**所有参数和接收者都是“按值传递”**。
|
||||
> - 指针接收者之所以能修改原数据,是因为“值”是一个地址,通过该地址可以访问原始内存。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 在 GoLand 中进一步探索
|
||||
|
||||
1. 打开项目:`/home/liumangmang/GolandProjects/go-struct-methods`
|
||||
2. 将鼠标悬停在 `p1.SetName` 和 `p1.SetNamePtr` 上,观察 IDE 提示的接收者类型
|
||||
3. 尝试将 `SetName` 改为指针接收者,看输出变化
|
||||
4. 使用调试器(Debug)查看 `p1` 内存地址是否在方法调用中被共享
|
||||
|
||||
---
|
||||
|
||||
## 🧭 下一步建议
|
||||
|
||||
- 学习 **接口(interface)** 如何与 struct + 方法配合实现多态
|
||||
- 探索 **嵌入(embedding)** 替代继承
|
||||
- 对比 Java 的 getter/setter 与 Go 的字段直接访问(Go 无 private/public 关键字,靠首字母大小写控制可见性)
|
||||
|
||||
如果你希望我继续讲解 **接口、组合、错误处理** 或 **Go 风格的面向对象设计**,请随时告诉我!祝你编码愉快 🚀
|
||||
255
src/programming/backend/go/Go基础语法/05接口.md
Normal file
255
src/programming/backend/go/Go基础语法/05接口.md
Normal file
@@ -0,0 +1,255 @@
|
||||
---
|
||||
icon: mdi:link-variant
|
||||
date: 2025-05-22
|
||||
title: Go 基础语法 - 接口(鸭子类型)、空接口与类型断言实战
|
||||
category:
|
||||
- Go
|
||||
- 后端
|
||||
- 编程
|
||||
- 教程
|
||||
- 接口
|
||||
- 多态
|
||||
- 鸭子类型
|
||||
- 空接口
|
||||
- 类型断言
|
||||
- any
|
||||
- interface{}
|
||||
---
|
||||
|
||||
当然可以!以下是专为你量身定制的实践指南,完全基于你的开发环境:
|
||||
|
||||
- **操作系统**:Linux Mint XFCE
|
||||
- **Go 版本**:go1.22.2 linux/amd64
|
||||
- **项目目录**:`/home/liumangmang/GolandProjects`
|
||||
|
||||
<!-- more -->
|
||||
|
||||
## 📌 标题:
|
||||
# Go 的多态之道:接口(鸭子类型)、空接口与类型断言实战
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 1:创建新项目
|
||||
|
||||
打开终端,执行以下命令:
|
||||
|
||||
```bash
|
||||
cd /home/liumangmang/GolandProjects
|
||||
mkdir go-interfaces && cd go-interfaces
|
||||
go mod init go-interfaces
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 2:编写演示代码(main.go)
|
||||
|
||||
创建并编辑 `main.go`:
|
||||
|
||||
```bash
|
||||
nano main.go
|
||||
```
|
||||
|
||||
粘贴以下完整示例代码(含详细注释,覆盖鸭子类型、空接口、类型断言):
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ========== 1. 定义接口(鸭子类型)==========
|
||||
type Speaker interface {
|
||||
Speak() string
|
||||
}
|
||||
|
||||
type Dog struct{}
|
||||
func (d Dog) Speak() string {
|
||||
return "Woof!"
|
||||
}
|
||||
|
||||
type Cat struct{}
|
||||
func (c Cat) Speak() string {
|
||||
return "Meow~"
|
||||
}
|
||||
|
||||
type Robot struct{}
|
||||
func (r Robot) Speak() string {
|
||||
return "Beep boop."
|
||||
}
|
||||
|
||||
// ========== 2. 空接口(interface{})==========
|
||||
// 在 Go 1.18+ 中,推荐使用 any(它是 interface{} 的别名)
|
||||
func printAnything(v any) {
|
||||
fmt.Printf("接收到: %v (类型: %T)\n", v, v)
|
||||
}
|
||||
|
||||
// ========== 3. 类型断言 ==========
|
||||
func describeSpeaker(s Speaker) {
|
||||
fmt.Println("它说:", s.Speak())
|
||||
|
||||
// 类型断言:判断具体类型
|
||||
if d, ok := s.(Dog); ok {
|
||||
fmt.Println("这是一只狗!", d)
|
||||
} else if c, ok := s.(Cat); ok {
|
||||
fmt.Println("这是一只猫!", c)
|
||||
}
|
||||
}
|
||||
|
||||
// 使用 switch 进行类型断言(更优雅)
|
||||
func identify(v any) {
|
||||
switch x := v.(type) {
|
||||
case string:
|
||||
fmt.Printf("字符串: %s\n", x)
|
||||
case int:
|
||||
fmt.Printf("整数: %d\n", x)
|
||||
case Speaker:
|
||||
fmt.Printf("会说话的东西: %s\n", x.Speak())
|
||||
default:
|
||||
fmt.Printf("未知类型: %T\n", x)
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 主函数 ==========
|
||||
func main() {
|
||||
fmt.Println("=== 1. 鸭子类型:只要会 Speak(),就是 Speaker ===")
|
||||
animals := []Speaker{Dog{}, Cat{}, Robot{}}
|
||||
for _, a := range animals {
|
||||
fmt.Println(a.Speak())
|
||||
}
|
||||
|
||||
fmt.Println("\n=== 2. 空接口(any)可接收任意类型 ===")
|
||||
printAnything(42)
|
||||
printAnything("Hello")
|
||||
printAnything(Dog{})
|
||||
|
||||
fmt.Println("\n=== 3. 类型断言:从接口还原具体类型 ===")
|
||||
describeSpeaker(Dog{})
|
||||
describeSpeaker(Cat{})
|
||||
|
||||
fmt.Println("\n=== 4. 类型 switch:安全高效的类型判断 ===")
|
||||
identify("Gopher")
|
||||
identify(100)
|
||||
identify(Robot{})
|
||||
identify(true) // 未知类型
|
||||
|
||||
fmt.Println("\n=== 5. 安全 vs 不安全的类型断言 ===")
|
||||
var i any = "hello"
|
||||
s := i.(string) // 不安全:如果类型不对,会 panic
|
||||
fmt.Println("不安全断言结果:", s)
|
||||
|
||||
// 安全方式
|
||||
if val, ok := i.(int); ok {
|
||||
fmt.Println("是整数:", val)
|
||||
} else {
|
||||
fmt.Println("不是整数!")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
保存并退出(`Ctrl+O` → Enter → `Ctrl+X`)。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 3:格式化并运行
|
||||
|
||||
```bash
|
||||
go fmt
|
||||
go run .
|
||||
```
|
||||
|
||||
### 预期输出:
|
||||
|
||||
```
|
||||
=== 1. 鸭子类型:只要会 Speak(),就是 Speaker ===
|
||||
Woof!
|
||||
Meow~
|
||||
Beep boop.
|
||||
|
||||
=== 2. 空接口(any)可接收任意类型 ===
|
||||
接收到: 42 (类型: int)
|
||||
接收到: Hello (类型: string)
|
||||
接收到: {} (类型: main.Dog)
|
||||
|
||||
=== 3. 类型断言:从接口还原具体类型 ===
|
||||
它说: Woof!
|
||||
这是一只狗! {}
|
||||
它说: Meow~
|
||||
这是一只猫! {}
|
||||
|
||||
=== 4. 类型 switch:安全高效的类型判断 ===
|
||||
字符串: Gopher
|
||||
整数: 100
|
||||
会说话的东西: Beep boop.
|
||||
未知类型: bool
|
||||
|
||||
=== 5. 安全 vs 不安全的类型断言 ===
|
||||
不安全断言结果: hello
|
||||
不是整数!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 核心概念解析
|
||||
|
||||
### 🦆 1. 鸭子类型(Duck Typing)
|
||||
> “如果它走起来像鸭子,叫起来也像鸭子,那它就是鸭子。”
|
||||
|
||||
- Go 的接口是 **隐式实现** 的:只要结构体有 `Speak()` 方法,就自动满足 `Speaker` 接口。
|
||||
- **无需显式声明** `implements`(对比 Java)。
|
||||
|
||||
### 🕳️ 2. 空接口 `interface{}`(或 `any`)
|
||||
- 可以存储任意类型的值(类似 Java 的 `Object`,但更灵活)。
|
||||
- 常用于:
|
||||
- 通用函数参数(如 `fmt.Println`)
|
||||
- JSON 解析(`map[string]any`)
|
||||
- **代价**:失去类型安全,需配合类型断言使用。
|
||||
|
||||
### 🔍 3. 类型断言
|
||||
语法:
|
||||
```go
|
||||
value, ok := interfaceVar.(ConcreteType)
|
||||
```
|
||||
- **安全方式**:用 `, ok` 检查是否成功,避免 panic。
|
||||
- **不安全方式**:直接 `v := i.(T)`,类型不符时程序崩溃。
|
||||
|
||||
### 🔄 4. 类型 switch
|
||||
```go
|
||||
switch v := x.(type) {
|
||||
case int:
|
||||
// v 是 int
|
||||
case string:
|
||||
// v 是 string
|
||||
}
|
||||
```
|
||||
- 更简洁、高效地处理多种类型。
|
||||
|
||||
---
|
||||
|
||||
## 💡 对比 Java
|
||||
|
||||
| Go | Java |
|
||||
|----|------|
|
||||
| `interface{}` / `any` | `Object` |
|
||||
| 隐式实现接口 | 显式 `implements` |
|
||||
| 类型断言 | `instanceof` + 强转 |
|
||||
| 鸭子类型 | 接口必须显式实现 |
|
||||
|
||||
> Go 的接口设计更轻量、灵活,强调“行为”而非“继承关系”。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 在 GoLand 中探索建议
|
||||
|
||||
1. 打开项目:`/home/liumangmang/GolandProjects/go-interfaces`
|
||||
2. 将光标放在 `Speaker` 上,按 `Ctrl+H`(或右键 → Find Usages),查看所有实现者
|
||||
3. 尝试删除 `Robot` 的 `Speak()` 方法,观察编译错误(或无错误?因为没被用到!)
|
||||
4. 在 `identify` 函数中添加新类型(如 `[]int`),看类型 switch 如何处理
|
||||
|
||||
---
|
||||
|
||||
## 🧭 下一步学习方向
|
||||
|
||||
- 接口组合(`io.Reader`, `io.Writer`)
|
||||
- 错误处理与 `error` 接口
|
||||
- 如何设计小而美的接口(Go 哲学:“Accept interfaces, return structs”)
|
||||
|
||||
如果你希望我继续讲解 **错误处理、panic/recover、泛型入门** 或 **Go 标准库常用接口**,欢迎随时告诉我!祝你 Go 之旅越来越顺 🚀
|
||||
253
src/programming/backend/go/Go基础语法/06错误机制.md
Normal file
253
src/programming/backend/go/Go基础语法/06错误机制.md
Normal file
@@ -0,0 +1,253 @@
|
||||
---
|
||||
icon: mdi:alert-circle-outline
|
||||
date: 2025-05-22
|
||||
title: Go 基础语法 - 错误处理全解
|
||||
category:
|
||||
- Go
|
||||
- 后端
|
||||
- 编程
|
||||
- 教程
|
||||
- 错误处理
|
||||
- error
|
||||
- defer
|
||||
- panic
|
||||
- recover
|
||||
- fmt.Errorf
|
||||
- errors.Is
|
||||
- errors.As
|
||||
---
|
||||
|
||||
当然可以!以下是专为你定制的实践指南,完全基于你的开发环境:
|
||||
|
||||
- **操作系统**:Linux Mint XFCE
|
||||
- **Go 版本**:go1.22.2 linux/amd64
|
||||
- **项目目录**:`/home/liumangmang/GolandProjects`
|
||||
|
||||
<!-- more -->
|
||||
|
||||
## 📌 标题:
|
||||
# Go 错误处理全解:error、defer、panic/recover 与错误封装实战
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 1:创建新项目
|
||||
|
||||
打开终端,执行:
|
||||
|
||||
```bash
|
||||
cd /home/liumangmang/GolandProjects
|
||||
mkdir go-error-handling && cd go-error-handling
|
||||
go mod init go-error-handling
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 2:编写演示代码(main.go)
|
||||
|
||||
创建并编辑 `main.go`:
|
||||
|
||||
```bash
|
||||
nano main.go
|
||||
```
|
||||
|
||||
粘贴以下完整示例代码(涵盖 error 返回、defer 清理、panic 恢复、fmt.Errorf 封装):
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ========== 1. 自定义错误 + fmt.Errorf 封装 ==========
|
||||
func divide(a, b float64) (float64, error) {
|
||||
if b == 0 {
|
||||
// 使用 fmt.Errorf 包装原始错误,添加上下文
|
||||
return 0, fmt.Errorf("divide by zero: cannot divide %.2f by %.2f", a, b)
|
||||
}
|
||||
return a / b, nil
|
||||
}
|
||||
|
||||
// 更复杂的错误链(Go 1.13+ 支持 %w)
|
||||
var ErrNegativeInput = errors.New("input must be non-negative")
|
||||
|
||||
func sqrt(x float64) (float64, error) {
|
||||
if x < 0 {
|
||||
// 使用 %w 包装错误,支持 errors.Is 和 errors.As
|
||||
return 0, fmt.Errorf("invalid input for sqrt: %w", ErrNegativeInput)
|
||||
}
|
||||
return x * x, nil // 注意:这里故意写成平方,方便测试
|
||||
}
|
||||
|
||||
// ========== 2. defer 的典型用途 ==========
|
||||
func readFile(filename string) error {
|
||||
fmt.Println("尝试打开文件:", filename)
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("无法打开文件 %s: %w", filename, err)
|
||||
}
|
||||
// defer 确保文件在函数退出前关闭(即使 panic)
|
||||
defer func() {
|
||||
fmt.Println("defer: 关闭文件")
|
||||
file.Close()
|
||||
}()
|
||||
|
||||
// 模拟读取
|
||||
fmt.Println("文件已打开,正在读取...")
|
||||
return nil
|
||||
}
|
||||
|
||||
// ========== 3. panic 与 recover ==========
|
||||
func riskyFunction(n int) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf("recover 捕获到 panic: %v\n", r)
|
||||
}
|
||||
}()
|
||||
if n < 0 {
|
||||
panic("n 不能为负数!")
|
||||
}
|
||||
fmt.Println("riskyFunction 正常执行,n =", n)
|
||||
}
|
||||
|
||||
// recover 只在 defer 中有效!
|
||||
func noRecover() {
|
||||
// ❌ 这样写无法捕获 panic
|
||||
recover()
|
||||
panic("不会被捕获")
|
||||
}
|
||||
|
||||
// ========== 4. 综合示例:安全计算 ==========
|
||||
func safeCompute(a, b float64) {
|
||||
defer fmt.Println("safeCompute 结束\n---")
|
||||
|
||||
fmt.Printf("计算 %.2f / %.2f\n", a, b)
|
||||
result, err := divide(a, b)
|
||||
if err != nil {
|
||||
fmt.Println("错误:", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("结果: %.2f\n", result)
|
||||
|
||||
fmt.Println("尝试对结果开方(实际是平方)")
|
||||
sq, err := sqrt(result)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrNegativeInput) {
|
||||
fmt.Println("检测到特定错误:输入为负")
|
||||
}
|
||||
fmt.Println("sqrt 错误:", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("平方结果: %.2f\n", sq)
|
||||
}
|
||||
|
||||
// ========== 主函数 ==========
|
||||
func main() {
|
||||
fmt.Println("=== 1. 基本 error 处理 ===")
|
||||
safeCompute(10, 2)
|
||||
safeCompute(10, 0) // 触发除零错误
|
||||
|
||||
fmt.Println("\n=== 2. defer 文件操作 ===")
|
||||
readFile("/nonexistent/file.txt") // 文件不存在,触发错误
|
||||
readFile("/etc/hostname") // 存在的文件(Linux 系统通常有)
|
||||
|
||||
fmt.Println("\n=== 3. panic 与 recover ===")
|
||||
riskyFunction(5)
|
||||
riskyFunction(-1) // 触发 panic,但被 recover 捕获
|
||||
|
||||
fmt.Println("\n=== 4. 错误封装与识别 ===")
|
||||
_, err := sqrt(-4)
|
||||
if err != nil {
|
||||
fmt.Println("原始错误信息:", err)
|
||||
// 使用 errors.Is 判断是否包含特定错误
|
||||
if errors.Is(err, ErrNegativeInput) {
|
||||
fmt.Println("✅ 成功识别自定义错误 ErrNegativeInput")
|
||||
}
|
||||
}
|
||||
|
||||
// ⚠️ 取消注释下面这行会 crash(recover 不在 defer 中无效)
|
||||
// noRecover()
|
||||
}
|
||||
```
|
||||
|
||||
保存并退出(`Ctrl+O` → Enter → `Ctrl+X`)。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 3:格式化并运行
|
||||
|
||||
```bash
|
||||
go fmt
|
||||
go run .
|
||||
```
|
||||
|
||||
### 预期输出(节选):
|
||||
|
||||
```
|
||||
=== 1. 基本 error 处理 ===
|
||||
计算 10.00 / 2.00
|
||||
结果: 5.00
|
||||
尝试对结果开方(实际是平方)
|
||||
平方结果: 25.00
|
||||
safeCompute 结束
|
||||
---
|
||||
计算 10.00 / 0.00
|
||||
错误: divide by zero: cannot divide 10.00 by 0.00
|
||||
safeCompute 结束
|
||||
---
|
||||
|
||||
=== 2. defer 文件操作 ===
|
||||
尝试打开文件: /nonexistent/file.txt
|
||||
defer: 关闭文件
|
||||
尝试打开文件: /etc/hostname
|
||||
文件已打开,正在读取...
|
||||
defer: 关闭文件
|
||||
|
||||
=== 3. panic 与 recover ===
|
||||
riskyFunction 正常执行,n = 5
|
||||
recover 捕获到 panic: n 不能为负数!
|
||||
safeCompute 结束
|
||||
---
|
||||
|
||||
=== 4. 错误封装与识别 ===
|
||||
原始错误信息: invalid input for sqrt: input must be non-negative
|
||||
✅ 成功识别自定义错误 ErrNegativeInput
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 核心知识点总结
|
||||
|
||||
| 机制 | 说明 | 最佳实践 |
|
||||
|------|------|--------|
|
||||
| **`error`** | Go 的错误是值,不是异常 | 函数最后一个返回值通常是 `error`,必须显式检查 |
|
||||
| **`fmt.Errorf`** | 创建带格式的错误;用 `%w` 包装(Go 1.13+) | 用于添加上下文,支持 `errors.Is`/`errors.As` |
|
||||
| **`defer`** | 延迟执行,常用于资源清理(文件、锁、连接) | 确保 cleanup 逻辑总被执行,即使 panic |
|
||||
| **`panic`** | 立即停止当前 goroutine,开始栈展开 | 仅用于不可恢复的严重错误(如程序状态损坏) |
|
||||
| **`recover`** | 在 defer 中调用,可捕获 panic | 用于守护关键服务不崩溃(如 HTTP 服务器中间件) |
|
||||
|
||||
> 💡 **重要原则**:
|
||||
> - **不要滥用 panic**:Go 推崇“显式错误处理”,而非异常。
|
||||
> - **永远检查 error**:忽略 error 是 Go 新手常见错误。
|
||||
> - **用 `%w` 而不是 `%v` 包装错误**:这样才能用 `errors.Is` 判断。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 在 GoLand 中调试建议
|
||||
|
||||
1. 打开项目:`/home/liumangmang/GolandProjects/go-error-handling`
|
||||
2. 在 `divide` 函数中设置断点,观察错误如何逐层返回
|
||||
3. 尝试取消注释 `noRecover()`,运行看程序崩溃(理解 recover 必须在 defer 中)
|
||||
4. 使用 **Evaluate Expression** 功能测试 `errors.Is(err, ErrNegativeInput)`
|
||||
|
||||
---
|
||||
|
||||
## 🧭 下一步学习方向
|
||||
|
||||
- 自定义 error 类型(实现 `Error() string` 方法)
|
||||
- 使用 `errors.Unwrap()` 手动解包错误链
|
||||
- 在 Web 服务中统一错误处理中间件
|
||||
|
||||
如果你希望我继续讲解 **泛型入门、context 使用、并发模式(worker pool)** 或 **Go 单元测试(testing 包)**,欢迎随时告诉我!祝你写出健壮又优雅的 Go 代码 🚀
|
||||
275
src/programming/backend/go/Go基础语法/07从零实现 Mini 日志库.md
Normal file
275
src/programming/backend/go/Go基础语法/07从零实现 Mini 日志库.md
Normal file
@@ -0,0 +1,275 @@
|
||||
---
|
||||
icon: mdi:file-document-edit-outline
|
||||
date: 2025-05-22
|
||||
title: Go 基础语法 - 从零实现 Mini 日志库
|
||||
category:
|
||||
- Go
|
||||
- 后端
|
||||
- 编程
|
||||
- 教程
|
||||
- 日志库
|
||||
- 实践项目
|
||||
- 标准库
|
||||
---
|
||||
|
||||
<!-- more -->
|
||||
|
||||
当然可以!以下是专为你定制的实战项目,完全基于你的开发环境:
|
||||
|
||||
- **操作系统**:Linux Mint XFCE
|
||||
- **Go 版本**:go1.22.2 linux/amd64
|
||||
- **项目目录**:`/home/liumangmang/GolandProjects`
|
||||
|
||||
---
|
||||
|
||||
## 📌 标题:
|
||||
# 从零实现 Mini 日志库:Go 结构体、接口与 fmt 的完美结合
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 1:创建项目结构
|
||||
|
||||
打开终端,执行以下命令:
|
||||
|
||||
```bash
|
||||
cd /home/liumangmang/GolandProjects
|
||||
mkdir go-mini-logger && cd go-mini-logger
|
||||
go mod init go-mini-logger
|
||||
```
|
||||
|
||||
我们将实现一个支持多级别(Info/Warn/Error)、可输出到控制台或文件、并可通过接口扩展的轻量日志库。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 2:定义日志接口(logger.go)
|
||||
|
||||
创建 `logger.go`:
|
||||
|
||||
```bash
|
||||
nano logger.go
|
||||
```
|
||||
|
||||
粘贴以下代码:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LogLevel 日志级别
|
||||
type LogLevel int
|
||||
|
||||
const (
|
||||
LevelInfo LogLevel = iota
|
||||
LevelWarn
|
||||
LevelError
|
||||
)
|
||||
|
||||
func (l LogLevel) String() string {
|
||||
switch l {
|
||||
case LevelInfo:
|
||||
return "INFO"
|
||||
case LevelWarn:
|
||||
return "WARN"
|
||||
case LevelError:
|
||||
return "ERROR"
|
||||
default:
|
||||
return "UNKNOWN"
|
||||
}
|
||||
}
|
||||
|
||||
// Logger 接口:定义日志行为
|
||||
type Logger interface {
|
||||
Log(level LogLevel, format string, args ...any)
|
||||
Info(format string, args ...any)
|
||||
Warn(format string, args ...any)
|
||||
Error(format string, args ...any)
|
||||
SetMinLevel(level LogLevel)
|
||||
}
|
||||
|
||||
// defaultLogger 结构体:默认实现
|
||||
type defaultLogger struct {
|
||||
minLevel LogLevel
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
// NewLogger 创建新日志实例,默认输出到 os.Stdout
|
||||
func NewLogger() Logger {
|
||||
return &defaultLogger{
|
||||
minLevel: LevelInfo,
|
||||
writer: os.Stdout,
|
||||
}
|
||||
}
|
||||
|
||||
// NewFileLogger 创建写入文件的日志实例
|
||||
func NewFileLogger(filename string) (Logger, error) {
|
||||
file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("无法创建日志文件 %s: %w", filename, err)
|
||||
}
|
||||
return &defaultLogger{
|
||||
minLevel: LevelInfo,
|
||||
writer: file,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetMinLevel 设置最低日志级别
|
||||
func (l *defaultLogger) SetMinLevel(level LogLevel) {
|
||||
l.minLevel = level
|
||||
}
|
||||
|
||||
// Log 核心日志方法
|
||||
func (l *defaultLogger) Log(level LogLevel, format string, args ...any) {
|
||||
if level < l.minLevel {
|
||||
return // 低于最小级别,丢弃
|
||||
}
|
||||
|
||||
// 构建日志消息:[时间] [级别] 消息
|
||||
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
||||
message := fmt.Sprintf(format, args...)
|
||||
line := fmt.Sprintf("[%s] [%s] %s\n", timestamp, level.String(), message)
|
||||
|
||||
// 写入目标(控制台或文件)
|
||||
fmt.Fprint(l.writer, line)
|
||||
}
|
||||
|
||||
// 快捷方法
|
||||
func (l *defaultLogger) Info(format string, args ...any) { l.Log(LevelInfo, format, args...) }
|
||||
func (l *defaultLogger) Warn(format string, args ...any) { l.Log(LevelWarn, format, args...) }
|
||||
func (l *defaultLogger) Error(format string, args ...any) { l.Log(LevelError, format, args...) }
|
||||
```
|
||||
|
||||
保存退出(`Ctrl+O` → Enter → `Ctrl+X`)。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 3:编写使用示例(main.go)
|
||||
|
||||
创建 `main.go`:
|
||||
|
||||
```bash
|
||||
nano main.go
|
||||
```
|
||||
|
||||
粘贴以下代码:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("=== 1. 控制台日志 ===")
|
||||
consoleLogger := NewLogger()
|
||||
consoleLogger.Info("程序启动,PID: %d", os.Getpid())
|
||||
consoleLogger.Warn("磁盘使用率超过 80%")
|
||||
consoleLogger.Error("数据库连接失败: timeout")
|
||||
|
||||
fmt.Println("\n=== 2. 设置最低级别为 WARN ===")
|
||||
consoleLogger.SetMinLevel(LevelWarn)
|
||||
consoleLogger.Info("这条不会显示") // 被过滤
|
||||
consoleLogger.Warn("这条会显示") // 显示
|
||||
consoleLogger.Error("错误也会显示") // 显示
|
||||
|
||||
fmt.Println("\n=== 3. 文件日志(写入 app.log)===")
|
||||
fileLogger, err := NewFileLogger("app.log")
|
||||
if err != nil {
|
||||
consoleLogger.Error("无法创建文件日志: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fileLogger.Info("应用开始处理请求")
|
||||
fileLogger.Warn("用户 %s 尝试越权操作", "liumangmang")
|
||||
fileLogger.Error("写入数据库时发生唯一键冲突")
|
||||
|
||||
fmt.Println("日志已写入 app.log,请查看内容:")
|
||||
os.ReadFile("app.log") // 不打印,仅确保写入
|
||||
fmt.Println("✅ 查看 app.log 可验证文件日志内容")
|
||||
}
|
||||
```
|
||||
|
||||
保存退出。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 4:格式化并运行
|
||||
|
||||
```bash
|
||||
go fmt
|
||||
go run .
|
||||
```
|
||||
|
||||
### 预期终端输出:
|
||||
|
||||
```
|
||||
=== 1. 控制台日志 ===
|
||||
[2025-11-25 21:30:05] [INFO] 程序启动,PID: 12345
|
||||
[2025-11-25 21:30:05] [WARN] 磁盘使用率超过 80%
|
||||
[2025-11-25 21:30:05] [ERROR] 数据库连接失败: timeout
|
||||
|
||||
=== 2. 设置最低级别为 WARN ===
|
||||
[2025-11-25 21:30:05] [WARN] 这条会显示
|
||||
[2025-11-25 21:30:05] [ERROR] 错误也会显示
|
||||
|
||||
=== 3. 文件日志(写入 app.log)===
|
||||
日志已写入 app.log,请查看内容:
|
||||
✅ 查看 app.log 可验证文件日志内容
|
||||
```
|
||||
|
||||
同时,当前目录会生成 `app.log` 文件。你可以查看它:
|
||||
|
||||
```bash
|
||||
cat app.log
|
||||
```
|
||||
|
||||
内容类似:
|
||||
|
||||
```
|
||||
[2025-11-25 21:30:05] [INFO] 应用开始处理请求
|
||||
[2025-11-25 21:30:05] [WARN] 用户 liumangmang 尝试越权操作
|
||||
[2025-11-25 21:30:05] [ERROR] 写入数据库时发生唯一键冲突
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 设计亮点解析
|
||||
|
||||
| Go 特性 | 在本项目中的体现 |
|
||||
|--------|----------------|
|
||||
| **结构体** | `defaultLogger` 封装 writer 和 minLevel |
|
||||
| **接口** | `Logger` 接口解耦调用方与实现,便于未来扩展(如网络日志、JSON 格式等) |
|
||||
| **fmt 包** | 使用 `fmt.Sprintf` 格式化消息,`fmt.Fprint` 写入任意 `io.Writer` |
|
||||
| **io.Writer** | 支持无缝切换输出目标(控制台、文件、网络流) |
|
||||
| **可扩展性** | 只需实现 `Logger` 接口,即可插入新的日志后端 |
|
||||
|
||||
> 💡 **为什么用接口?**
|
||||
> 未来你可以轻松添加 `JSONLogger`、`SyslogLogger` 等,而**无需修改业务代码**——只需传入不同的 `Logger` 实现。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 在 GoLand 中进一步探索
|
||||
|
||||
1. 打开项目:`/home/liumangmang/GolandProjects/go-mini-logger`
|
||||
2. 右键点击 `Logger` 接口 → **Go to → Implementations**,查看所有实现(目前只有 `defaultLogger`)
|
||||
3. 尝试新增一个 `SilentLogger`(不输出任何内容),验证接口的灵活性
|
||||
4. 在 `main.go` 中将 `consoleLogger` 替换为 `fileLogger`,观察行为变化
|
||||
|
||||
---
|
||||
|
||||
## 🧭 扩展建议(课后练习)
|
||||
|
||||
- 添加 `WithField(key, value string)` 方法支持结构化日志
|
||||
- 支持日志轮转(log rotation)
|
||||
- 增加 `Debug` 级别,并通过 build tag 控制是否编译
|
||||
|
||||
---
|
||||
|
||||
如果你希望我继续带你实现 **带上下文(context)的日志、JSON 格式输出、或集成 zap/logrus 对比分析**,欢迎随时告诉我!祝你编码愉快,日志清晰 🚀
|
||||
@@ -0,0 +1,235 @@
|
||||
title: Go 并发入门:Goroutine 基础与 GPM 调度模型实战解析
|
||||
icon: go
|
||||
date: 2025-12-11
|
||||
category:
|
||||
- Go
|
||||
- 并发编程
|
||||
tag:
|
||||
- Goroutine
|
||||
- GPM模型
|
||||
- 调度器
|
||||
star: true
|
||||
---
|
||||
|
||||
当然可以!以下是专为你定制的学习实践指南,完全基于你的开发环境:
|
||||
|
||||
- **操作系统**:Linux Mint XFCE
|
||||
- **Go 版本**:go1.22.2 linux/amd64
|
||||
- **项目目录**:`/home/liumangmang/GolandProjects`
|
||||
|
||||
<!-- more -->
|
||||
|
||||
---
|
||||
|
||||
## 📌 标题:
|
||||
# Go 并发入门:Goroutine 基础与 GPM 调度模型实战解析
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 1:创建练习项目
|
||||
|
||||
打开终端,执行:
|
||||
|
||||
```bash
|
||||
cd /home/liumangmang/GolandProjects
|
||||
mkdir go-goroutine-gpm && cd go-goroutine-gpm
|
||||
go mod init go-goroutine-gpm
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 2:编写 Goroutine 基础示例(main.go)
|
||||
|
||||
创建 `main.go`:
|
||||
|
||||
```bash
|
||||
nano main.go
|
||||
```
|
||||
|
||||
粘贴以下代码,涵盖 goroutine 启动、并发行为、以及 GPM 模型的观察方法:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func say(s string, id int) {
|
||||
for i := 0; i < 3; i++ {
|
||||
fmt.Printf("[%d] %s: step %d\n", id, s, i)
|
||||
time.Sleep(100 * time.Millisecond) // 模拟工作
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("=== 1. 单线程顺序执行 ===")
|
||||
say("Hello", 1)
|
||||
say("World", 2)
|
||||
|
||||
fmt.Println("\n=== 2. 使用 Goroutine 并发执行 ===")
|
||||
go say("Goroutine-A", 1)
|
||||
go say("Goroutine-B", 2)
|
||||
|
||||
// 主 goroutine 等待 1 秒,否则程序会提前退出
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
fmt.Println("\n=== 3. 使用 sync.WaitGroup 安全等待 ===")
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 1; i <= 3; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
fmt.Printf("Worker %d started\n", id)
|
||||
time.Sleep(time.Duration(id*200) * time.Millisecond)
|
||||
fmt.Printf("Worker %d finished\n", id)
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait() // 阻塞直到所有 goroutine 完成
|
||||
fmt.Println("All workers done!")
|
||||
|
||||
fmt.Println("\n=== 4. 查看当前 GOMAXPROCS 和 CPU 核心数 ===")
|
||||
fmt.Printf("CPU 核心数: %d\n", runtime.NumCPU())
|
||||
fmt.Printf("GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0)) // 0 表示不修改,仅查询
|
||||
|
||||
fmt.Println("\n=== 5. 手动设置 GOMAXPROCS(通常不需要)===")
|
||||
old := runtime.GOMAXPROCS(2)
|
||||
fmt.Printf("旧 GOMAXPROCS: %d, 新设为: 2\n", old)
|
||||
runtime.GOMAXPROCS(old) // 恢复原值
|
||||
|
||||
fmt.Println("\n=== 6. 观察 Goroutine 数量变化 ===")
|
||||
fmt.Printf("启动前 Goroutine 数: %d\n", runtime.NumGoroutine())
|
||||
|
||||
var wg2 sync.WaitGroup
|
||||
for i := 0; i < 5; i++ {
|
||||
wg2.Add(1)
|
||||
go func(n int) {
|
||||
defer wg2.Done()
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}(i)
|
||||
}
|
||||
|
||||
time.Sleep(50 * time.Millisecond) // 让 goroutine 启动
|
||||
fmt.Printf("启动后 Goroutine 数: %d\n", runtime.NumGoroutine())
|
||||
|
||||
wg2.Wait()
|
||||
fmt.Printf("全部完成后 Goroutine 数: %d\n", runtime.NumGoroutine())
|
||||
}
|
||||
```
|
||||
|
||||
保存并退出(`Ctrl+O` → Enter → `Ctrl+X`)。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 3:格式化并运行
|
||||
|
||||
```bash
|
||||
go fmt
|
||||
go run .
|
||||
```
|
||||
|
||||
### 预期输出(顺序可能略有不同):
|
||||
|
||||
```
|
||||
=== 1. 单线程顺序执行 ===
|
||||
[1] Hello: step 0
|
||||
[1] Hello: step 1
|
||||
[1] Hello: step 2
|
||||
[2] World: step 0
|
||||
[2] World: step 1
|
||||
[2] World: step 2
|
||||
|
||||
=== 2. 使用 Goroutine 并发执行 ===
|
||||
[Goroutine-A] step 0
|
||||
[Goroutine-B] step 0
|
||||
[Goroutine-A] step 1
|
||||
[Goroutine-B] step 1
|
||||
[Goroutine-A] step 2
|
||||
[Goroutine-B] step 2
|
||||
|
||||
=== 3. 使用 sync.WaitGroup 安全等待 ===
|
||||
Worker 1 started
|
||||
Worker 2 started
|
||||
Worker 3 started
|
||||
Worker 1 finished
|
||||
Worker 2 finished
|
||||
Worker 3 finished
|
||||
All workers done!
|
||||
|
||||
=== 4. 查看当前 GOMAXPROCS 和 CPU 核心数 ===
|
||||
CPU 核心数: 8
|
||||
GOMAXPROCS: 8
|
||||
|
||||
=== 5. 手动设置 GOMAXPROCS(通常不需要)===
|
||||
旧 GOMAXPROCS: 8, 新设为: 2
|
||||
|
||||
=== 6. 观察 Goroutine 数量变化 ===
|
||||
启动前 Goroutine 数: 1
|
||||
启动后 Goroutine 数: 6
|
||||
全部完成后 Goroutine 数: 1
|
||||
```
|
||||
|
||||
> 💡 注意:Goroutine 输出顺序是**不确定的**,这正是并发的体现!
|
||||
|
||||
---
|
||||
|
||||
## 🔍 核心概念解析:GPM 调度模型
|
||||
|
||||
Go 的调度器采用 **GPM 模型**,三者含义如下:
|
||||
|
||||
| 组件 | 全称 | 作用 |
|
||||
|------|------|------|
|
||||
| **G** | Goroutine | 用户级轻量协程,由 Go 运行时管理 |
|
||||
| **P** | Processor | 逻辑处理器,持有 G 的本地队列,数量 = `GOMAXPROCS` |
|
||||
| **M** | Machine | 操作系统线程,真正执行代码的实体 |
|
||||
|
||||
### 调度流程简述:
|
||||
1. 每个 **P** 绑定一个 **M**(OS 线程)
|
||||
2. **G** 被分配到某个 **P** 的本地队列
|
||||
3. **M** 执行 **P** 上的 **G**
|
||||
4. 若 **G** 阻塞(如 I/O),**M** 会与 **P** 解绑,**P** 可被其他 **M** 接管,保证 CPU 不空闲
|
||||
|
||||
> ✅ **优势**:
|
||||
> - Goroutine 创建成本极低(初始栈仅 2KB)
|
||||
> - 即使百万级 Goroutine,也能高效调度
|
||||
> - 阻塞不会阻塞 OS 线程(网络 I/O 由 netpoller 处理)
|
||||
|
||||
---
|
||||
|
||||
## 🧪 实验建议(在 GoLand 中)
|
||||
|
||||
1. **查看 Goroutine 堆栈**:
|
||||
在调试模式下,点击 **Goroutines** 面板,实时查看所有 goroutine 状态。
|
||||
|
||||
2. **修改 GOMAXPROCS**:
|
||||
尝试 `runtime.GOMAXPROCS(1)`,观察并发是否变成“伪并发”(仍能切换,但只用 1 个 CPU)。
|
||||
|
||||
3. **制造阻塞**:
|
||||
在 goroutine 中加入 `time.Sleep` 或 `http.Get`,观察调度器如何复用 M。
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 常见误区提醒
|
||||
|
||||
| 误区 | 正确理解 |
|
||||
|------|--------|
|
||||
| “Goroutine = OS 线程” | ❌ Goroutine 是用户态协程,由 Go 调度器在少量 OS 线程上复用 |
|
||||
| “必须设置 GOMAXPROCS” | ❌ Go 1.5+ 默认等于 CPU 核心数,通常无需手动设置 |
|
||||
| “Goroutine 会自动等待” | ❌ 主 goroutine 退出,程序立即终止!必须用 `WaitGroup`、channel 或 `time.Sleep` 等待 |
|
||||
|
||||
---
|
||||
|
||||
## 📚 延伸学习方向
|
||||
|
||||
- 使用 `go tool trace` 可视化调度行为
|
||||
- 学习 **channel** 实现 goroutine 通信(避免共享内存)
|
||||
- 理解 **context** 如何优雅取消 goroutine
|
||||
|
||||
---
|
||||
|
||||
如果你希望我接下来带你实现 **channel 通信、select 多路复用、或 worker pool 模式**,欢迎随时告诉我!祝你轻松掌握 Go 并发编程 🚀
|
||||
643
src/programming/backend/go/go基础入门.md
Normal file
643
src/programming/backend/go/go基础入门.md
Normal file
@@ -0,0 +1,643 @@
|
||||
---
|
||||
icon: mdi:language-go
|
||||
date: 2025-05-22
|
||||
category:
|
||||
- 后端开发
|
||||
- Go语言
|
||||
tag:
|
||||
- Go
|
||||
- 编程语言
|
||||
- 入门教程
|
||||
star: true
|
||||
---
|
||||
|
||||
# Go 语言基础入门
|
||||
|
||||
Go(又称 Golang)是由 Google 开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。它于 2009 年首次发布,以其简洁、高效和强大的并发处理能力而闻名。
|
||||
|
||||
## 目录
|
||||
1. [Go 语言简介](#go-语言简介)
|
||||
2. [环境搭建](#环境搭建)
|
||||
3. [第一个 Go 程序](#第一个-go-程序)
|
||||
4. [基本语法](#基本语法)
|
||||
5. [数据类型](#数据类型)
|
||||
6. [变量和常量](#变量和常量)
|
||||
7. [运算符](#运算符)
|
||||
8. [控制结构](#控制结构)
|
||||
9. [函数](#函数)
|
||||
10. [数组和切片](#数组和切片)
|
||||
11. [映射(Map)](#映射map)
|
||||
12. [结构体](#结构体)
|
||||
13. [接口](#接口)
|
||||
14. [并发编程](#并发编程)
|
||||
15. [错误处理](#错误处理)
|
||||
16. [包管理](#包管理)
|
||||
|
||||
## Go 语言简介
|
||||
|
||||
Go 语言的设计目标是:
|
||||
- 简单易学:语法简洁,去除了许多其他语言的复杂特性
|
||||
- 高效执行:编译型语言,运行速度快
|
||||
- 内置并发支持:通过 goroutine 和 channel 实现轻量级并发
|
||||
- 强大的标准库:提供了丰富的内置库
|
||||
- 跨平台支持:支持多种操作系统和架构
|
||||
|
||||
## 环境搭建
|
||||
|
||||
### 下载安装
|
||||
|
||||
1. 访问 [Go 官方网站](https://golang.org/dl/) 下载适合你操作系统的安装包
|
||||
2. Windows 用户下载 `.msi` 文件,macOS 用户下载 `.pkg` 文件,Linux 用户下载 `.tar.gz` 文件
|
||||
|
||||
### Windows 安装步骤
|
||||
1. 双击下载的 `.msi` 文件开始安装
|
||||
2. 默认会安装到 `C:\Go\` 目录下
|
||||
3. 安装完成后会自动配置环境变量
|
||||
|
||||
### Linux 安装步骤
|
||||
```bash
|
||||
# 下载 Go 安装包
|
||||
wget https://golang.org/dl/go1.19.linux-amd64.tar.gz
|
||||
|
||||
# 解压到 /usr/local 目录
|
||||
sudo tar -C /usr/local -xzf go1.19.linux-amd64.tar.gz
|
||||
|
||||
# 配置环境变量
|
||||
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
### 验证安装
|
||||
打开终端或命令提示符,输入以下命令验证是否安装成功:
|
||||
```bash
|
||||
go version
|
||||
```
|
||||
如果显示版本信息,则说明安装成功。
|
||||
|
||||
### 设置工作目录
|
||||
Go 项目推荐使用模块化管理,在任意目录下创建你的项目文件夹:
|
||||
```bash
|
||||
mkdir myproject
|
||||
cd myproject
|
||||
go mod init myproject
|
||||
```
|
||||
|
||||
## 第一个 Go 程序
|
||||
|
||||
创建一个名为 `main.go` 的文件:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello, World!")
|
||||
}
|
||||
```
|
||||
|
||||
运行程序:
|
||||
```bash
|
||||
go run main.go
|
||||
```
|
||||
|
||||
或者编译后运行:
|
||||
```bash
|
||||
go build main.go
|
||||
./main
|
||||
```
|
||||
|
||||
代码解释:
|
||||
- `package main`:声明这是一个可执行程序的主包
|
||||
- `import "fmt"`:导入格式化输入输出包
|
||||
- `func main()`:程序入口函数
|
||||
- `fmt.Println()`:打印文本并换行
|
||||
|
||||
## 基本语法
|
||||
|
||||
### 注释
|
||||
```go
|
||||
// 单行注释
|
||||
|
||||
/*
|
||||
多行注释
|
||||
可以跨越多行
|
||||
*/
|
||||
```
|
||||
|
||||
### 标识符命名规则
|
||||
- 只能包含字母、数字和下划线
|
||||
- 不能以数字开头
|
||||
- 区分大小写
|
||||
- 不能是关键字
|
||||
|
||||
### 关键字
|
||||
Go 语言有 25 个关键字:
|
||||
```
|
||||
break default func interface select
|
||||
case defer go map struct
|
||||
chan else goto package switch
|
||||
const fallthrough if range type
|
||||
continue for import return var
|
||||
```
|
||||
|
||||
## 数据类型
|
||||
|
||||
Go 语言的数据类型分为四大类:
|
||||
|
||||
### 基本类型
|
||||
1. **布尔型**:bool(true/false)
|
||||
2. **数字类型**:
|
||||
- 整数型:int8, int16, int32, int64, uint8, uint16, uint32, uint64, int, uint, uintptr
|
||||
- 浮点型:float32, float64
|
||||
- 复数型:complex64, complex128
|
||||
3. **字符串类型**:string
|
||||
4. **派生类型**:
|
||||
- 指针类型(Pointer)
|
||||
- 数组类型
|
||||
- 结构化类型(struct)
|
||||
- Channel 类型
|
||||
- 函数类型
|
||||
- 切片类型
|
||||
- 接口类型(interface)
|
||||
- Map 类型
|
||||
|
||||
### 类型示例
|
||||
```go
|
||||
var a bool = true // 布尔型
|
||||
var b int = 10 // 整型
|
||||
var c float32 = 3.14 // 浮点型
|
||||
var d string = "Hello" // 字符串
|
||||
var e complex64 = 3+4i // 复数型
|
||||
```
|
||||
|
||||
## 变量和常量
|
||||
|
||||
### 变量声明
|
||||
|
||||
四种声明变量的方式:
|
||||
|
||||
```go
|
||||
// 1. 指定变量类型,声明后若不赋值则使用默认值
|
||||
var v1 int
|
||||
|
||||
// 2. 根据值自行判定变量类型
|
||||
var v2 = 10
|
||||
|
||||
// 3. 省略 var,注意 := 左侧的变量必须是未声明过的
|
||||
v3 := 10
|
||||
|
||||
// 4. 多变量声明
|
||||
var v4, v5 int = 1, 2
|
||||
var v6, v7 = 3, "hello"
|
||||
v8, v9 := 5, true
|
||||
```
|
||||
|
||||
### 变量作用域
|
||||
- **局部变量**:在函数内声明,只在函数内有效
|
||||
- **全局变量**:在函数外声明,在整个包内有效
|
||||
|
||||
### 常量声明
|
||||
```go
|
||||
const PI = 3.14159
|
||||
const NAME = "Golang"
|
||||
|
||||
// 常量组
|
||||
const (
|
||||
Monday = 1
|
||||
Tuesday = 2
|
||||
Wednesday = 3
|
||||
)
|
||||
```
|
||||
|
||||
### iota
|
||||
iota 是一个特殊的常量,用于生成一组相似的常量值:
|
||||
```go
|
||||
const (
|
||||
a = iota // 0
|
||||
b // 1
|
||||
c // 2
|
||||
d // 3
|
||||
)
|
||||
```
|
||||
|
||||
## 运算符
|
||||
|
||||
### 算术运算符
|
||||
| 运算符 | 描述 |
|
||||
|--------|------|
|
||||
| + | 相加 |
|
||||
| - | 相减 |
|
||||
| * | 相乘 |
|
||||
| / | 相除 |
|
||||
| % | 求余 |
|
||||
| ++ | 自增 |
|
||||
| -- | 自减 |
|
||||
|
||||
### 关系运算符
|
||||
| 运算符 | 描述 |
|
||||
|--------|------|
|
||||
| == | 相等 |
|
||||
| != | 不相等 |
|
||||
| > | 大于 |
|
||||
| < | 小于 |
|
||||
| >= | 大于等于 |
|
||||
| <= | 小于等于 |
|
||||
|
||||
### 逻辑运算符
|
||||
| 运算符 | 描述 |
|
||||
|--------|------|
|
||||
| && | 逻辑与 |
|
||||
| \|\| | 逻辑或 |
|
||||
| ! | 逻辑非 |
|
||||
|
||||
### 位运算符
|
||||
| 运算符 | 描述 |
|
||||
|--------|------|
|
||||
| & | 按位与 |
|
||||
| \| | 按位或 |
|
||||
| ^ | 按位异或 |
|
||||
| << | 左移 |
|
||||
| >> | 右移 |
|
||||
|
||||
## 控制结构
|
||||
|
||||
### 条件语句 if
|
||||
|
||||
```go
|
||||
if condition {
|
||||
// code
|
||||
}
|
||||
|
||||
// 带初始化语句
|
||||
if x := 10; x > 5 {
|
||||
fmt.Println("x 大于 5")
|
||||
} else {
|
||||
fmt.Println("x 小于等于 5")
|
||||
}
|
||||
```
|
||||
|
||||
### 循环语句 for
|
||||
|
||||
Go 只有一种循环结构:for 循环
|
||||
|
||||
```go
|
||||
// 基本 for 循环
|
||||
for i := 0; i < 10; i++ {
|
||||
fmt.Println(i)
|
||||
}
|
||||
|
||||
// while 形式的循环
|
||||
i := 0
|
||||
for i < 10 {
|
||||
fmt.Println(i)
|
||||
i++
|
||||
}
|
||||
|
||||
// 无限循环
|
||||
for {
|
||||
// 死循环
|
||||
}
|
||||
```
|
||||
|
||||
### 选择语句 switch
|
||||
|
||||
```go
|
||||
switch day {
|
||||
case 1:
|
||||
fmt.Println("Monday")
|
||||
case 2:
|
||||
fmt.Println("Tuesday")
|
||||
default:
|
||||
fmt.Println("Unknown day")
|
||||
}
|
||||
|
||||
// 不带条件的 switch
|
||||
switch {
|
||||
case score >= 90:
|
||||
fmt.Println("优秀")
|
||||
case score >= 80:
|
||||
fmt.Println("良好")
|
||||
default:
|
||||
fmt.Println("一般")
|
||||
}
|
||||
```
|
||||
|
||||
## 函数
|
||||
|
||||
### 函数定义
|
||||
```go
|
||||
func functionName(parameterName type) returnType {
|
||||
// 函数体
|
||||
return returnValue
|
||||
}
|
||||
```
|
||||
|
||||
### 示例
|
||||
```go
|
||||
// 简单函数
|
||||
func add(a int, b int) int {
|
||||
return a + b
|
||||
}
|
||||
|
||||
// 多返回值函数
|
||||
func swap(x, y string) (string, string) {
|
||||
return y, x
|
||||
}
|
||||
|
||||
// 可变参数函数
|
||||
func sum(nums ...int) int {
|
||||
total := 0
|
||||
for _, num := range nums {
|
||||
total += num
|
||||
}
|
||||
return total
|
||||
}
|
||||
```
|
||||
|
||||
### 函数调用
|
||||
```go
|
||||
result := add(3, 5)
|
||||
a, b := swap("hello", "world")
|
||||
total := sum(1, 2, 3, 4, 5)
|
||||
```
|
||||
|
||||
### defer 语句
|
||||
defer 用于延迟执行函数,通常用于资源清理:
|
||||
```go
|
||||
func readFile() {
|
||||
file, err := os.Open("file.txt")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer file.Close() // 函数结束前关闭文件
|
||||
|
||||
// 处理文件...
|
||||
}
|
||||
```
|
||||
|
||||
## 数组和切片
|
||||
|
||||
### 数组
|
||||
数组是固定长度的序列:
|
||||
```go
|
||||
// 声明数组
|
||||
var arr1 [5]int // 长度为 5 的整型数组
|
||||
arr2 := [5]int{1, 2, 3, 4, 5} // 初始化数组
|
||||
arr3 := [...]int{1, 2, 3} // 根据元素个数确定长度
|
||||
|
||||
// 访问数组元素
|
||||
fmt.Println(arr2[0]) // 输出 1
|
||||
arr2[1] = 10 // 修改元素
|
||||
```
|
||||
|
||||
### 切片
|
||||
切片是对数组的抽象,长度可变:
|
||||
```go
|
||||
// 创建切片
|
||||
slice1 := []int{1, 2, 3} // 直接创建切片
|
||||
slice2 := make([]int, 5) // 创建长度为 5 的切片
|
||||
slice3 := make([]int, 3, 5) // 长度为 3,容量为 5
|
||||
|
||||
// 从数组创建切片
|
||||
arr := [5]int{1, 2, 3, 4, 5}
|
||||
slice4 := arr[1:4] // 包含索引 1 到 3 的元素
|
||||
|
||||
// 切片操作
|
||||
slice5 := append(slice1, 4, 5) // 添加元素
|
||||
len(slice1) // 获取长度
|
||||
cap(slice1) // 获取容量
|
||||
```
|
||||
|
||||
## 映射(Map)
|
||||
|
||||
Map 是一种无序的键值对集合:
|
||||
|
||||
```go
|
||||
// 创建 map
|
||||
var m1 map[string]int // 声明但未初始化
|
||||
m2 := make(map[string]int) // 使用 make 初始化
|
||||
m3 := map[string]int{ // 直接初始化
|
||||
"apple": 5,
|
||||
"banana": 3,
|
||||
}
|
||||
|
||||
// 操作 map
|
||||
m2["orange"] = 10 // 添加键值对
|
||||
value := m2["orange"] // 获取值
|
||||
delete(m2, "orange") // 删除键值对
|
||||
|
||||
// 检查键是否存在
|
||||
if val, ok := m2["apple"]; ok {
|
||||
fmt.Println("apple:", val)
|
||||
} else {
|
||||
fmt.Println("apple not found")
|
||||
}
|
||||
```
|
||||
|
||||
## 结构体
|
||||
|
||||
结构体是一种用户自定义的数据类型,允许我们组合不同类型的字段:
|
||||
|
||||
```go
|
||||
// 定义结构体
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
Email string
|
||||
}
|
||||
|
||||
// 创建结构体实例
|
||||
person1 := Person{Name: "张三", Age: 25, Email: "zhangsan@example.com"}
|
||||
person2 := Person{"李四", 30, "lisi@example.com"}
|
||||
person3 := Person{} // 所有字段使用零值
|
||||
|
||||
// 访问结构体字段
|
||||
fmt.Println(person1.Name)
|
||||
person1.Age = 26
|
||||
|
||||
// 结构体指针
|
||||
person4 := &Person{Name: "王五", Age: 28}
|
||||
fmt.Println(person4.Name) // 自动解引用
|
||||
```
|
||||
|
||||
### 方法
|
||||
方法是带有接收者的函数:
|
||||
|
||||
```go
|
||||
// 为结构体定义方法
|
||||
func (p Person) SayHello() {
|
||||
fmt.Printf("Hello, I'm %s\n", p.Name)
|
||||
}
|
||||
|
||||
func (p *Person) SetAge(age int) {
|
||||
p.Age = age
|
||||
}
|
||||
|
||||
// 调用方法
|
||||
person1.SayHello()
|
||||
person1.SetAge(27)
|
||||
```
|
||||
|
||||
## 接口
|
||||
|
||||
接口是一组方法签名的集合:
|
||||
|
||||
```go
|
||||
// 定义接口
|
||||
type Shape interface {
|
||||
Area() float64
|
||||
Perimeter() float64
|
||||
}
|
||||
|
||||
// 实现接口
|
||||
type Rectangle struct {
|
||||
Width, Height float64
|
||||
}
|
||||
|
||||
func (r Rectangle) Area() float64 {
|
||||
return r.Width * r.Height
|
||||
}
|
||||
|
||||
func (r Rectangle) Perimeter() float64 {
|
||||
return 2 * (r.Width + r.Height)
|
||||
}
|
||||
|
||||
// 使用接口
|
||||
var s Shape = Rectangle{Width: 10, Height: 5}
|
||||
fmt.Println("Area:", s.Area())
|
||||
fmt.Println("Perimeter:", s.Perimeter())
|
||||
```
|
||||
|
||||
## 并发编程
|
||||
|
||||
Go 语言内置了强大的并发支持,主要包括 goroutine 和 channel。
|
||||
|
||||
### Goroutine
|
||||
Goroutine 是轻量级线程:
|
||||
|
||||
```go
|
||||
// 启动 goroutine
|
||||
go func() {
|
||||
fmt.Println("Hello from goroutine")
|
||||
}()
|
||||
|
||||
// 启动多个 goroutine
|
||||
for i := 0; i < 5; i++ {
|
||||
go func(n int) {
|
||||
fmt.Printf("Goroutine %d\n", n)
|
||||
}(i)
|
||||
}
|
||||
```
|
||||
|
||||
### Channel
|
||||
Channel 用于 goroutine 之间通信:
|
||||
|
||||
```go
|
||||
// 创建 channel
|
||||
ch := make(chan int)
|
||||
|
||||
// 发送数据到 channel
|
||||
go func() {
|
||||
ch <- 42
|
||||
}()
|
||||
|
||||
// 从 channel 接收数据
|
||||
value := <-ch
|
||||
fmt.Println(value)
|
||||
|
||||
// 带缓冲的 channel
|
||||
bufferedCh := make(chan int, 3)
|
||||
bufferedCh <- 1
|
||||
bufferedCh <- 2
|
||||
bufferedCh <- 3
|
||||
```
|
||||
|
||||
### Select
|
||||
Select 用于在多个 channel 操作中进行选择:
|
||||
|
||||
```go
|
||||
select {
|
||||
case msg1 := <-ch1:
|
||||
fmt.Println("Received", msg1)
|
||||
case msg2 := <-ch2:
|
||||
fmt.Println("Received", msg2)
|
||||
case <-time.After(time.Second):
|
||||
fmt.Println("Timeout")
|
||||
}
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
Go 语言通过返回错误值来进行错误处理:
|
||||
|
||||
```go
|
||||
// 错误处理示例
|
||||
func divide(a, b float64) (float64, error) {
|
||||
if b == 0 {
|
||||
return 0, errors.New("division by zero")
|
||||
}
|
||||
return a / b, nil
|
||||
}
|
||||
|
||||
// 使用错误
|
||||
result, err := divide(10, 0)
|
||||
if err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
} else {
|
||||
fmt.Println("Result:", result)
|
||||
}
|
||||
```
|
||||
|
||||
### 自定义错误
|
||||
```go
|
||||
type MyError struct {
|
||||
Msg string
|
||||
Code int
|
||||
}
|
||||
|
||||
func (e *MyError) Error() string {
|
||||
return fmt.Sprintf("Error %d: %s", e.Code, e.Msg)
|
||||
}
|
||||
```
|
||||
|
||||
## 包管理
|
||||
|
||||
### 创建模块
|
||||
```bash
|
||||
go mod init myproject
|
||||
```
|
||||
|
||||
### 添加依赖
|
||||
```bash
|
||||
go get github.com/gin-gonic/gin
|
||||
```
|
||||
|
||||
### 查看依赖
|
||||
```bash
|
||||
go mod tidy
|
||||
go list -m all
|
||||
```
|
||||
|
||||
### go.mod 文件示例
|
||||
```
|
||||
module myproject
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.8.1
|
||||
)
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
Go 语言以其简洁的语法、强大的并发支持和高效的执行性能成为现代软件开发的重要选择。通过本文档的学习,你应该掌握了 Go 语言的基本概念和语法,可以开始编写简单的 Go 程序了。
|
||||
|
||||
建议接下来的学习路径:
|
||||
1. 练习更多 Go 语言编程题目
|
||||
2. 学习标准库的使用
|
||||
3. 了解 Go 语言的最佳实践
|
||||
4. 尝试构建实际项目
|
||||
|
||||
Happy coding with Go!
|
||||
Reference in New Issue
Block a user