Initial commit

This commit is contained in:
Docker7530
2026-03-01 01:43:46 +08:00
commit c6125c117b
3840 changed files with 415340 additions and 0 deletions
@@ -0,0 +1,248 @@
# 环境准备
## 下载安装
下载地址: https://go.dev
双击“下一步”安装,注意安装位置尽可能不放 C 盘,可以建立自己的环境目录。
安装完成后在 cmd 控制台输入 `go version` 查看 Go 版本,检测是否安装成功。
环境变量在新版本后不需要过多额外设置,但需注意用户变量中默认有 GOPATH 的设置,如果不希望放在默认用户目录下,需进行修改。
`GOPATH` 即为存储 Go 语言项目的路径,包含 src(已不强制要求)、pkg、bin 三个目录。
打开 cmd 控制台,输入 `go env` 查看 Go 的环境变量配置。
依次在 cmd 控制台设置代理和 `GO111MODULE`
```shell
go env -w GOPROXY=https://goproxy.cn,direct
go env -w GO111MODULE=auto
```
## 关于 GOPATH
在 Go 语言开发中,`GOPATH` 是一个环境变量,用于定义 Go 代码的工作区。传统的 Go 项目结构基于 `GOPATH` 下的三个经典目录,分别是:
1. src 存放 Go 源代码文件。按照惯例,源代码文件组织成以包为单位的目录,例如:`github.com/user/project`
2. pkg 存放编译后的包文件(以 `.a` 结尾)。这些文件通常用于加速编译。
3. bin 存放编译生成的可执行文件。执行 `go install` 时,生成的二进制文件会放在 bin 目录中,方便直接运行。
### 示例目录结构
```
GOPATH
├── src // 源代码目录,存放你的 Go 项目代码
├── bin // 可执行文件目录,存放编译后的可执行文件
└── pkg // 已编译的包目录
├── linux_amd64 // 针对特定平台和架构编译的包文件
│ ├── foo.a // 编译后的 foo 包文件
│ └── bar.a // 编译后的 bar 包文件
└── mod // 在 Go Modules 模式下使用,存放模块缓存
```
### 为什么现在不再注重 GOPATH?
自从 Go 1.11 引入了 **Go Modules** 后,`GOPATH` 不再是项目管理的核心部分,以下是原因:
1. 模块化依赖管理
Go Modules 使用 `go.mod` 文件来管理依赖和版本,解决了 `GOPATH` 下多个项目共享依赖库时的冲突问题,避免了“依赖地狱”。
2. 无需固定目录
使用 Go Modules 后,项目可以放在任意目录下,而不再需要存放在 `GOPATH` 的 src 目录中,增加了开发的灵活性。
3. 版本控制
Go Modules 原生支持版本控制和依赖管理,确保项目中使用的是正确版本的依赖,而 `GOPATH` 无法提供这种能力。
4. 更简单的构建
通过 Go Modules,开发者可以直接运行 `go build``go run`,无需考虑是否在 `GOPATH` 内,提升了开发体验。
# 开发工具
采用 VsCode,如果写过 Java 可以使用 GoLand,会更顺手,一样的 UI。
1. 安装 VsCode 软件,在插件市场安装 GO 插件。
2. VsCode 软件的`settings.json` 文件中新增以下配置, 设置 `go tools` 的全局安装目录。
- `"go.toolsGopath": "D:\\MyGo\\go-tools"`
3. 安装扩展:快捷键 `ctrl+shift+p`,输入 `Go install`,选择 `Install/Update Tools`,勾选所有插件(插件显示可能会反应慢一些,稍等。)进行安装。
4. 等待安装完成。
# Hello World
创建文件:`hello.go`
```go
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
```
## 1. package(声明包)
定义:Go 源文件必须以 `package` 开头,声明其所属的包。
1. 包名与文件夹一一对应,但可以不同。
2. 一个程序必须有且仅有一个 `main` 包,作为入口包。
3.`main` 包的程序无法生成可执行文件。
## 2. import(导入包)
格式: `import "包名"` 或用括号引入多个包:
```go
import (
"name1"
"name2"
)
```
> 注意:
> 只能导入已使用的包,未使用的包会导致编译错误。
> `fmt` 是 Go 标准库,用于格式化输入输出。
## 3. main 函数
Go 程序的入口函数,必须声明在 `main` 包中,且只能有一个。
其他包中不能定义 `main` 函数。
```go
func 函数名 (参数列表) (返回值列表) {
函数体
}
```
- 参数列表:`变量名 类型` 组合,如 `a int, b string`
- 返回值列表:支持多个返回值,用 `return` 返回。
- 函数体左大括号 `{` 必须与函数声明在同一行。
## 4. 打印 Hello World
`Println``fmt` 包的函数,用于格式化输出,自动换行。
点号 `.` 表示调用 `fmt` 包的函数。
结尾无需分号 `;`Go 编译器会自动处理。
# 编译和运行
Go 是编译型静态语言,在运行程序之前需将代码编译为二进制可执行文件。Go 提供两种主要命令用于编译和运行:`go build``go run`
| 命令 | 功能 | 生成文件 | 适用场景 |
| ---------- | ------------------ | ------------- | ----------- |
| `go build` | 编译 Go 程序为可执行文件 | 生成 `.exe` 文件 | 用于生产环境或打包分发 |
| `go run` | 编译后立即运行程序,不生成可执行文件 | 不生成文件,仅临时编译运行 | 用于调试、快速测试 |
## go build 命令
```go
go build fileName
```
`fileName`:Go 源文件名,可以是一个或多个,多个文件名用空格分隔;也可省略,省略时默认为当前目录。
参数不为空:
- 如果 `fileName` 属于 `main` 包,生成与第一个 `fileName` 同名的可执行文件(如 `abc.go` 生成 `abc.exe`)。
- 如果 `fileName` 不属于 `main` 包,只进行语法检查,不生成可执行文件。
参数为空:
- 如果当前目录包含 `main` 包,生成与目录同名的 `.exe` 文件(如 `hello` 目录下生成 `hello.exe`)。
- 如果目录中无 `main` 包,仅进行语法检查。
```go
go build .\hello.go
```
Windows 系统:当前目录使用 `.\` 表示。
类 Unix 系统:当前目录使用 `./` 表示。
# 常用 Print 方式
## 1. Print
将内容**直接**输出到控制台。
不接受任何格式化,占位符无效。
等价于对每一个操作数应用 `%v`
示例:
```go
fmt.Print("Hello, World!")
```
输出:`Hello, World!`
## 2. Println
输出内容后自动**换行**。
同样不支持格式化,占位符无效。
示例:
```go
fmt.Println("Hello,", "World!")
fmt.Println("Hello,", "World!")
```
输出:
```
Hello, World!
Hello, World!
```
## 3. Printf
支持**格式化**输出,可按照指定的格式输出数据。
需要提供格式化占位符。
语法:
```go
fmt.Printf(format, values...)
```
示例:
```go
var a, b, c = 1, 2, 3
fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c)
```
输出:
```
a = 1, b = 2, c = 3
```
> `\n` 为占位符。
## 常见占位符
| 占位符 | 描述 |
| ------------- | ------------------------------------- |
| `%v` | 以默认格式打印变量的值(适用于大多数类型) |
| `%T` | 打印变量的类型(类型名) |
| `%s` | 输出字符串 |
| `%t` | 输出布尔值(`true``false` |
| `%d` | 打印整数的十进制表示 |
| `%b` | 打印整数的二进制表示 |
| `%o` / `%#o` | 八进制,不带零 / 带零(0o,零加小写字母 o) |
| `%x` / `%#x` | 小写十六进制 / 带 `0x` 前缀的小写十六进制 |
| `%X` / `%#X` | 大写十六进制 / 带 `0X` 前缀的大写十六进制 |
| `%f` / `%.3f` | 浮点数,默认精度 6 位小数 / 保留 3 位小数,位数可控 |
| `%e` / `%.3e` | 科学计数法,默认精度 6 位小数 / 保留 3 位小数 |
| `%U` / `%#U` | Unicode 字符 / 带字符的 Unicode,用 `rune` 声明 |
| `%p` | 打印指针的地址,带 `0x` 前缀,变量前加 `&` 取指针 |
| `%q` | 带双引号的字符串,字符串内的引号用转义符 |
| `%c` | 打印一个 Unicode 字符(根据整数值打印对应的字符) |
@@ -0,0 +1,919 @@
# 注释
Go 语言的注释主要分成两类,分别是**单行**注释和**多行**注释。
```go
package main
import "fmt"
/*
多行注释。
*/
func main() {
// 单行注释。
fmt.Printf("单行注释。")
/*
多行注释
*/
fmt.Println("多行注释。")
}
```
> 发现一个现象,写在函数上不会被格式化,在代码中会被退格格式化。
# 变量
## 声明
```go
package main
import "fmt"
func main() {
// 1️⃣ 变量声明(包含默认值)
var name string = "田卓"
var age int = 27
var isGood bool = true
var myAge int // 默认值 0
fmt.Println("默认值示例:")
fmt.Println("name:", name)
fmt.Println("age:", age)
fmt.Println("isGood:", isGood)
fmt.Println("myAge (未赋值,默认值):", myAge)
fmt.Println()
// 2️⃣ 批量声明变量(默认值)
var (
name1 string
age1 int
)
fmt.Println("批量声明:")
fmt.Println("name1:", name1) // 默认 ""
age1 = 20
fmt.Println("age1:", age1) // 赋值后 20
fmt.Println()
// 3️⃣ 变量自动类型推导(语法糖 :=)
name2 := "田秉衡" // Go 自动推导为 string
age2 := 2 // 自动推导为 int
height := 8 // 自动推导为 int
weight := 28 // 自动推导为 int
fmt.Println("类型推导:")
fmt.Println("name2:", name2)
fmt.Println("age2:", age2)
fmt.Println("height:", height)
fmt.Println("weight:", weight)
fmt.Println()
// 4️⃣ 变量交换
nameA := "田卓"
nameB := "田秉衡"
fmt.Println("交换前:", nameA, nameB)
nameA, nameB = nameB, nameA
fmt.Println("交换后:", nameA, nameB)
fmt.Println()
// 5️⃣ 多返回值赋值(使用 `_` 忽略不需要的值)
result, _ := test() // 只取第一个返回值,忽略第二个
fmt.Println("多返回值赋值(忽略不需要的值):", result)
}
// 定义一个返回两个整数的函数
func test() (int, int) {
return 200, 200
}
```
## 作用域
```go
package main
import "fmt"
// 1️⃣ 全局变量(作用域:整个 package)
var globalVar string = "全局变量"
func main() {
// 2️⃣ 局部变量(作用域:main 函数内)
var localVar string = "局部变量"
fmt.Println("🌍 全局变量:", globalVar)
fmt.Println("🔹 局部变量:", localVar)
// 3️⃣ 代码块作用域
{
blockVar := "代码块变量"
fmt.Println("📦 代码块变量:", blockVar)
}
// ❌ 代码块变量超出作用域,下面的代码会报错
// fmt.Println(blockVar) // ❌ 编译错误:未定义 blockVar
// 4️⃣ 变量遮蔽(局部变量遮蔽全局变量)
globalVar := "局部遮蔽全局"
fmt.Println("🚧 遮蔽后的 globalVar:", globalVar)
// 5️⃣ 变量交换
a, b := "变量A", "变量B"
fmt.Println("🔄 交换前:", a, b)
a, b = b, a
fmt.Println("🔄 交换后:", a, b)
// 6️⃣ 函数参数作用域
printVar("函数参数")
// 7️⃣ 访问真正的全局变量(使用 package 级别变量)
fmt.Println("🌍 真实的全局变量仍然是:", getGlobalVar())
}
// 6️⃣ 函数参数作用域
func printVar(param string) {
fmt.Println("🎯 函数参数作用域:", param)
}
// 7️⃣ 访问全局变量的辅助函数
func getGlobalVar() string {
return globalVar
}
```
# 常量
```go
package main
import (
"fmt"
"reflect"
"unsafe"
)
func main() {
// 1️⃣ 基本常量声明
const URL1 string = "www.baidu.com"
fmt.Println("🌍 基本常量:", URL1)
// 2️⃣ 多常量声明
const URL2, URL3 string = "www.baidu1.com", "www.baidu2.com"
fmt.Println("🌍 多常量:", URL2, URL3)
// 3️⃣ 常量枚举(iota
const (
A = iota // 0
B // 1
C // 2
)
fmt.Println("🔢 常量 iota:", A, B, C)
// 4️⃣ `iota` 用法(位移计算)
const (
Flag1 = 1 << iota // 1 (2^0)
Flag2 // 2 (2^1)
Flag3 // 4 (2^2)
)
fmt.Println("🛠️ iota 位移:", Flag1, Flag2, Flag3)
// 5️⃣ 变量可修改,但常量不能修改
var myVar int = 100
fmt.Println("🔄 变量修改前:", myVar)
// 6️⃣ 变量地址修改(⚠️ 变量可以被 unsafe 修改,但常量不能)
myVarPtr := unsafe.Pointer(&myVar)
rv := reflect.NewAt(reflect.TypeOf(myVar), myVarPtr).Elem()
// 修改值
rv.SetInt(200)
fmt.Println("🔄 变量修改后:", myVar)
// 7️⃣ 常量的内存地址(不会被修改)
// ❌ 下面代码如果取消注释会报错:常量不能被修改
// const myConst int = 300
// fmt.Printf("🚫 常量内存地址: %p (常量不会被修改)\n", &myConst)
// rvConst := reflect.NewAt(reflect.TypeOf(myConst), unsafe.Pointer(&myConst)).Elem()
// rvConst.SetInt(500)
// fmt.Println("🚫 常量修改后:", myConst)
}
```
## iota 特殊常量
```go
package main
import "fmt"
func main() {
// 1️⃣ `iota` 基本递增
const (
a = iota // 0
b // 1 (继承 `a = iota`iota 递增)
c // 2
d = 0 // ❌ 这里手动赋值 d = 0,iota 仍然递增(跳过)
e // 0 (继承 d = 0,而不是 iota)
f = iota // 5 (iota 继续递增,从 `c = 2` 到 `iota = 5`)
g // 6
h = iota // 7 (手动写 `iota`,但值仍然递增)
)
fmt.Println("🔢 基本 iota:", a, b, c, d, e, f, g, h) // 0 1 2 0 0 5 6 7
// 2️⃣ `iota` 遇到手动赋值的影响
const (
i = iota // 0
j = 0 // ❌ 手动赋值 j = 0,但 iota 仍然递增
k = iota // 2 (iota 继续递增,从 `i = 0` 变为 `k = 2`)
)
fmt.Println("🎯 iota 断开影响:", i, j, k) // 0 0 2
}
```
# 数据类型
## 布尔类型 (bool)
```go
package main
import "fmt"
func main() {
// ✅ 布尔值只能是 true 或 false
var b1 bool = true
var b2 bool = false
// 🔹 %t 格式化布尔值
fmt.Printf("b1: %t, b2: %t\n", b1, b2)
// ✅ Go 规定:false = 0, true = 1
var num1, num2 int = 1, 2
if num1 < num2 {
fmt.Println("✅ 1 小于 2")
}
// ⚠️ 布尔类型默认值是 `false`
var b3 bool
fmt.Println("🔹 bool默认值:", b3) // 输出 false
}
```
- 布尔类型 `bool` 只能是 `true``false`
- 默认值是 `false`
- `if` 语句可以直接使用布尔表达式
## 整数类型 (int, uint)
```go
package main
import "fmt"
func main() {
// ✅ 无符号整数类型(uint
var u8 uint8 = 255
fmt.Printf("uint8: %v\n", u8)
// ✅ 有符号整数类型(int
var i8 int8 = -128
fmt.Printf("int8: %v\n", i8)
}
```
| 类型 | 描述 | 取值范围 |
| -------- | ---------- | ---------------------------------------------- |
| `uint8` | 无符号 8 位整型 | `0` - `255` |
| `uint16` | 无符号 16 位整型 | `0` - `65535` |
| `uint32` | 无符号 32 位整型 | `0` - `4294967295` |
| `uint64` | 无符号 64 位整型 | `0` - `18446744073709551615` |
| `int8` | 有符号 8 位整型 | `-128` - `127` |
| `int16` | 有符号 16 位整型 | `-32768` - `32767` |
| `int32` | 有符号 32 位整型 | `-2147483648` - `2147483647` |
| `int64` | 有符号 64 位整型 | `-9223372036854775808` - `9223372036854775807` |
- **`int` 类型** 在 `32-bit``64-bit` 平台上的大小可能不同
- **`uint` 是无符号整数,不能表示负数**
- **默认 `int``int64`64-bit 机器)**
## 浮点类型 (float32, float64)
```go
package main
import "fmt"
func main() {
// ✅ 浮点类型(默认 `float64`)
var f1 float64 = 3.141592653
var f2 float32 = 2.71
fmt.Printf("float64: %T, %.3f\n", f1, f1) // 保留3位小数
fmt.Printf("float32: %T, %.2f\n", f2, f2) // 保留2位小数
// ⚠️ 计算机存储浮点数时存在精度丢失
var num1 float32 = -123.0000901
var num2 float64 = -123.0000901
fmt.Println("float32:", num1)
fmt.Println("float64:", num2)
}
```
- `float64` **精度比 `float32` 高**
- **浮点数计算可能会有精度损失**
- **格式化输出 `%.2f` 控制小数位数**
## 特殊整数类型 (byte, rune, uintptr)
```go
package main
import (
"fmt"
"unsafe"
)
func main() {
// ✅ `byte` 是 `uint8` 的别名(常用于存储字符)
var b byte = 255
fmt.Printf("byte: %T, %v\n", b, b)
// ✅ `rune` 是 `int32` 的别名(用于存储 Unicode 字符)
var r rune = '魑'
fmt.Printf("rune: %T, %v, %c\n", r, r, r)
// ✅ `uintptr` 存储指针地址
var x int = 42
ptr := unsafe.Pointer(&x)
// 强制转换
uintPtr := uintptr(ptr)
fmt.Printf("Pointer: %v, Uintptr: %v\n", ptr, uintPtr)
}
```
- `byte` **等价于 `uint8`**,用于 **存储 ASCII 字符**
- `rune` **等价于 `int32`**,用于 **存储 Unicode 字符**
- `uintptr` **用于存储指针地址**
## 字符串类型 (string)
```go
package main
import "fmt"
func main() {
// ✅ 字符串类型
var str string = "Hello, World!"
fmt.Printf("string: %T, %s\n", str, str)
// ✅ 单引号是字符(rune),双引号是字符串(string)
v1 := 'A' // `rune`int32
v2 := "A" // `string`
fmt.Printf("rune: %T, %d\n", v1, v1) // 'A' 的 Unicode 编码值
fmt.Printf("string: %T, %s\n", v2, v2)
// ✅ Unicode 字符
v3 := '魑'
fmt.Printf("rune: %T, %d, %c\n", v3, v3, v3)
}
```
- **单引号 `'A'``rune`int32**
- **双引号 `"A"``string`**
- **字符本质上是 `int32`Unicode 码点)**
## 类型转换
```go
package main
import "fmt"
func main() {
// ✅ 浮点数转整数(丢失小数部分)
a := 5.9
b := int(a)
fmt.Printf("浮点转整型: %v\n", b) // 输出 5
// ✅ 整数转浮点数
var c int = 10
var d float64 = float64(c)
fmt.Printf("整数转浮点: %v\n", d) // 输出 10.000000
// ✅ `byte` 和 `rune` 互转
var ch rune = 'A'
fmt.Printf("字符 'A' 转 int: %v\n", int(ch)) // 65
// ✅ `string` 转 `byte`
str := "Go"
byteArr := []byte(str)
fmt.Printf("字符串转 byte: %v\n", byteArr) // [71 111]
}
```
- **转换 `float``int` 会丢失小数部分**
- **整型可以转换为 `float64`**
- **字符 (`rune`) 本质是 `int32`,可以直接转整数**
- **`string` 可以转换为 `byte[]`**
# 运算符全
## 算术运算符
| 运算符 | 描述 | 示例(a = 7, b = 3 | 结果 |
| ---- | ------------- | ---------------- | ------------------- |
| `+` | 加法 | `a + b` | `7 + 3 = 10` |
| `-` | 减法 | `a - b` | `7 - 3 = 4` |
| `*` | 乘法 | `a * b` | `7 * 3 = 21` |
| `/` | 除法 | `a / b` | `7 / 3 = 2`(整数除法取整) |
| `%` | 取模(求余数) | `a % b` | `7 % 3 = 1` |
| `++` | 自增(不支持 `++a` | `a++` | `a = a + 1` |
| `--` | 自减(不支持 `--a` | `a--` | `a = a - 1` |
```go
package main
import "fmt"
func main() {
a, b := 7, 3
fmt.Println("加法:", a+b)
fmt.Println("减法:", a-b)
fmt.Println("乘法:", a*b)
fmt.Println("除法:", a/b) // 结果是 2,整数除法取整
fmt.Println("取模:", a%b) // 余数 1
// 自增 & 自减
a++
fmt.Println("自增:", a) // 8
a--
fmt.Println("自减:", a) // 7
}
```
## 关系运算符
| 运算符 | 描述 | 示例(a = 21, b = 10 | 结果 |
| ---- | ---- | ------------------ | ------- |
| `>` | 大于 | `a > b` | `true` |
| `<` | 小于 | `a < b` | `false` |
| `>=` | 大于等于 | `a >= b` | `true` |
| `<=` | 小于等于 | `a <= b` | `false` |
| `==` | 等于 | `a == b` | `false` |
| `!=` | 不等于 | `a != b` | `true` |
```go
package main
import "fmt"
func main() {
a, b := 21, 10
fmt.Println(a > b) // true
fmt.Println(a >= b) // true
fmt.Println(a < b) // false
fmt.Println(a <= b) // false
fmt.Println(a == b) // false
fmt.Println(a != b) // true
}
```
## 逻辑运算符
```go
package main
import "fmt"
func main() {
a, b := true, false
fmt.Println("与 &&:", a && b) // false
fmt.Println("或 ||:", a || b) // true
fmt.Println("非 !:", !a) // false
}
```
## 位运算符
| 运算符 | 描述 | 示例(a = 60, b = 13) | 结果(二进制) | 结果(十进制) |
| ---- | ---- | ------------------ | ------------------------ | ---------------- |
| `&` | 按位与 | `a & b` | `0011 1100 & 0000 1101` | `0000 1100 = 12` |
| ` | ` | 按位或 | `a | b` |
| `^` | 按位异或 | `a ^ b` | `0011 1100 ^ 0000 1101` | `0011 0001 = 49` |
| `&^` | 按位清空 | `a &^ b` | `0011 1100 &^ 0000 1101` | `0011 0000 = 48` |
| `<<` | 左移 | `a << 2` | `1111 0000` | `240` |
| `>>` | 右移 | `a >> 2` | `0000 1111` | `15` |
```go
package main
import "fmt"
func main() {
a, b := 60, 13
fmt.Println("按位与 &:", a&b) // 12
fmt.Println("按位或 |:", a|b) // 61
fmt.Println("按位异或 ^:", a^b) // 49
fmt.Println("位清空 &^:", a&^b) // 48
fmt.Println("左移 <<:", a<<2) // 240
fmt.Println("右移 >>:", a>>2) // 15
}
```
## 赋值运算符
| 运算符 | 描述 | 示例 |
| ---- | ----- | ---------------------- |
| `=` | 赋值 | `a = 10` |
| `+=` | 加后赋值 | `a += 5``a = a + 5` |
| `-=` | 减后赋值 | `a -= 5``a = a - 5` |
| `*=` | 乘后赋值 | `a *= 5``a = a * 5` |
| `/=` | 除后赋值 | `a /= 5``a = a / 5` |
| `%=` | 取模后赋值 | `a %= 5``a = a % 5` |
```go
package main
import "fmt"
func main() {
a := 10
a += 5
fmt.Println("a += 5:", a) // 15
a *= 2
fmt.Println("a *= 2:", a) // 30
}
```
## 指针运算符
| 运算符 | 描述 | 示例 |
| --- | ------------- | ------------ |
| `&` | 取地址 | `ptr = &a` |
| `*` | 解引用(获取指针指向的值) | `val = *ptr` |
```go
package main
import "fmt"
func main() {
a := 10
ptr := &a // 获取变量 a 的地址
fmt.Println("a 的地址:", ptr)
fmt.Println("指针解引用:", *ptr) // 10
}
```
## 运算符优先级
**高****低**
1. `*` `/` `%` `<<` `>>` `&` `&^`
2. `+` `-` `|` `^`
3. `==` `!=` `<` `<=` `>` `>=`
4. `&&`
5. `||`
# 流程控制
## 顺序结构
**默认执行顺序**:代码从上到下依次执行,不需要特殊控制。
```go
package main
import "fmt"
func main() {
fmt.Println("步骤 1")
fmt.Println("步骤 2")
fmt.Println("步骤 3")
}
```
**结果**
```
步骤 1
步骤 2
步骤 3
```
## 选择结构(if 语句)
### 1)基本 if 语句
```go
package main
import "fmt"
func main() {
num := 15
if num > 10 {
fmt.Println("num > 10")
}
fmt.Println("main 结束")
}
```
### 2if-else 语句
```go
package main
import "fmt"
func main() {
score := 90
if score == 100 {
fmt.Println("满分")
} else {
fmt.Println("不是满分")
}
}
```
### 3if-else if-else 多条件判断
```go
package main
import "fmt"
func main() {
var score int
fmt.Println("请输入成绩:")
fmt.Scan(&score)
if score >= 90 && score <= 100 {
fmt.Println("A")
} else if score >= 80 {
fmt.Println("B")
} else if score >= 70 {
fmt.Println("C")
} else if score >= 60 {
fmt.Println("D")
} else if score < 0 || score > 100 {
fmt.Println("输入不合法")
} else {
fmt.Println("不及格")
}
}
```
## 选择结构(switch 语句)
### 1)基本 switch
```go
package main
import "fmt"
func main() {
score := 100
switch score {
case 100, 95, 91:
fmt.Println("A")
case 90:
fmt.Println("B")
case 80, 70, 60:
fmt.Println("C")
default:
fmt.Println("其他")
}
}
```
- `case` **支持多个匹配值**(如 `100, 95, 91`
- `default` **可选**
### 2switch 省略条件
```go
package main
import "fmt"
func main() {
switch {
case false:
fmt.Println("false")
case true:
fmt.Println("true")
default:
fmt.Println("其他")
}
}
```
- `switch` **默认匹配 `true`**,可以用作 `if-else if-else` 结构的替代。
### 3fallthrough 关键字
`fallthrough` 用于强制执行下一个 `case` 语句,即使条件不匹配。
```go
package main
import "fmt"
func main() {
a := false
switch a {
case false:
fmt.Println("1")
fallthrough
case true:
fmt.Println("2") // 强制执行
}
}
```
- **`fallthrough` 忽略 `case` 的匹配条件**
- **避免滥用**,否则可能产生意外行为
### 4)终止 fallthrough
```go
package main
import "fmt"
func main() {
a := false
switch a {
case false:
fmt.Println("1")
fallthrough
case true:
fmt.Println("2")
if !a {
break // 终止 `fallthrough`
}
fallthrough
default:
fmt.Println("3")
}
}
```
## 循环结构(for 语句)
### 1)基本 for 语句
```go
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
fmt.Println(i)
}
}
```
### 2)变形 for 语句
省略初始值 & 结束条件
```go
package main
import "fmt"
func main() {
i := 0
for i <= 5 {
fmt.Println(i)
i++
}
}
```
无限循环
```go
package main
import "fmt"
func main() {
for {
fmt.Println("死循环")
}
}
```
## 终止循环
### 1break 语句
用于**终止**整个循环。
```go
package main
import "fmt"
func main() {
for i := 1; i <= 10; i++ {
if i == 5 {
break
}
fmt.Println(i)
}
}
```
`i == 5` 时,`break` 终止循环,输出 `1 2 3 4`
### 2continue 语句
用于**跳过当前循环**,继续执行下一个循环。
```go
package main
import "fmt"
func main() {
for i := 1; i <= 10; i++ {
if i == 5 {
continue // 跳过 5
}
fmt.Println(i)
}
}
```
输出 `1 2 3 4 6 7 8 9 10``5` 被跳过)
## goto 语句
用于**无条件跳转**,但容易造成代码混乱,不建议使用。
`goto` 只能跳转到**同一函数**内的标签。
```go
package main
import "fmt"
func main() {
fmt.Println("开始")
goto END // 跳转到 END
fmt.Println("不会执行") // 这行代码被跳过
END:
fmt.Println("结束")
}
```
**输出**
```
开始
结束
```
@@ -0,0 +1,293 @@
# strconv
bool - 字符串 ,“true” = 转换 true
数字 - 字符串, “3.99” / 转换为数字才可以进行计算
```go
package main
import (
"fmt"
"strconv"
)
// 项目:前端(网页、小程序、app) 后端代码-接收前端的请求
/*
func http(request) response{
string:= request.getUrlParm
// 数据库
// 计算
// 字符串的转换
}
*/
// string convert = strconv
func main() {
// bool
s1 := "true"
// 转化 - 字符串转bool(解析:parse
// func ParseBool(str string) (bool, error)
b1, err := strconv.ParseBool(s1)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%T,%t\n", b1, b1) // bool,true
// bool转字符串(格式化 format
s2 := strconv.FormatBool(b1)
fmt.Printf("%T,%s\n", s2, s2) // string,true
// 整数->字符串 Format 字符串->整数
s3 := "100000"
// 整数: 数字、进制、大小
// 参数:1、str 2、 进制(10) 3、大小
i1, _ := strconv.ParseInt(s3, 10, 64)
fmt.Printf("%T,%d\n", i1, i1) // int64,100
s4 := strconv.FormatInt(i1, 10)
fmt.Printf("%T,%s\n", s4, s4) // string,100000
// 10进制转换字符串,简便方法 atoi itoa
atoi, _ := strconv.Atoi("-20")
fmt.Printf("%T,%d\n", atoi, atoi) // int,-20
itoa := strconv.Itoa(30)
fmt.Printf("%T,%s\n", itoa, itoa) // string,30
}
```
# time 时间
一切的代码,都是为了解决现实生活中可能出现的业务,时间是很重要的。
time 包
- 获取当前时间
- 格式化时间
```go
package main
import (
"fmt"
"time"
)
// time
func main() {
}
// 获取当前时间 now
func time1() {
// 返回值为Time结构体 : 常量:日月年时分秒 周日-周六 方法:获取常量,计算。
now := time.Now()
year := now.Year()
month := now.Month()
day := now.Day()
hour := now.Hour()
minute := now.Minute()
second := now.Second()
// 2023-2-23 20:40:31
// Printf : 整数补位--02如果不足两位,左侧用0补齐输出
fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second)
}
func time2() {
//time1()
// 打印时间
now := time.Now()
// 时间格式化 2023-02-23 20:43:49
// 格式化模板: yyyy-MM-dd HH:mm:ss
// Go语言诞生的时间作为格式化模板:2006年1月2号下午3点4分
// Go语言格式化时间的代码:2006-01-02 15:04:05 (记忆方式:2006 12 345
// 固定的:"2006-01-02 15:04:05"
fmt.Println(now.Format("2006-01-02 15:04:05")) // 24小时制
fmt.Println(now.Format("2006-01-02 03:04:05 PM")) // 12小时制
fmt.Println(now.Format("2006/01/02 15:04")) // 2023/02/23 20:52
fmt.Println(now.Format("15:04 2006/01/02")) // 20:52 2023/02/23
fmt.Println(now.Format("2006/01/02")) // 2023/02/23
}
// 将字符串格式化为Time对象 (获取到网页传递的时间字符串,需要转化为Time才能在代码中使用)
func time3() {
// 其他地方的时区格式:https://www.zeitverschiebung.net/cn/all-time-zones.html
// 获取时间的时区 // "Asia/Shanghai" 必须要大写 手动构建 ,如果不对,会报未知的时间错误
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
fmt.Println(err)
return
}
// 将字符串解析为时间 Time
timeStr := "2023-02-23 20:53:08"
// layout 格式 时间字符串 时区位置 , 需要和前端传递的格式进行匹配
// func ParseInLocation(layout, value string, loc *Location)
timeObj, _ := time.ParseInLocation("2006-01-02 15:04:05", timeStr, loc)
fmt.Println(timeObj)
}
// 时间戳:更多时候和随机数结合
func time4() {
// 格林威治时间自1970年1月1日(00:00:00 GMT)至当前时间的总秒数
// 时间戳 Unix 1970.1.1 00:00:00 - 当下的一个毫秒数,Unix 时间戳,不会重复的。
now := time.Now()
timestamp1 := now.Unix() // 时间戳
timestamp2 := now.UnixNano() // 纳秒的时间数
fmt.Println(timestamp1)
fmt.Println(timestamp2)
//
// 通过 Unix 转换time对象
timeObj := time.Unix(timestamp1, 0) // 返回time对象
year := timeObj.Year()
month := timeObj.Month()
day := timeObj.Day()
hour := timeObj.Hour()
minute := timeObj.Minute()
second := timeObj.Second()
// 2023-2-23 20:40:31
// Printf : 整数补位--02如果不足两位,左侧用0补齐输出
fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second)
}
```
# 随机数
```go
package main
import (
"fmt"
"math/rand"
"time"
)
// random随机数- math/rand
func main() {
// 获取一个随机数
num1 := rand.Int()
fmt.Println("num1:", num1) // 5577006791947779410
// 随机需要一个随机数的种子,如果种子一样,那么结果一致
// n范围(0-n
num2 := rand.Intn(100)
fmt.Println("num2:", num2) // 7
// 需要一个随时都在发生变化的量 时间戳
timestamp := time.Now().Unix()
// 设置随机数种子, 使用时间戳
// 种子只需要设置一次即可。
rand.Seed(timestamp) // 每次执行都不同
for i := 0; i < 5; i++ {
// Intn [0,n)
// 20-29
num1 := rand.Intn(200) // 抽奖程序
// 必中的逻辑
num2 := rand.Intn(5) // 抽奖程序
if i == num2 {
fmt.Println(10)
continue
}
fmt.Println(num1)
}
}
```
# 定时器-时间操作
> 时间间隔常量 Duration
time.Duration是time包定义的一个类型,它代表两个时间点之间经过的时间
以纳秒为单位,可表示的最长时间段大约290年。
time包中定义的时间间隔类型的常量如下:
```go
const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
)
```
time.Duration表示1纳秒,time.Second表示1秒。
> 定时器
```go
// 定时器: 每隔xxx s执行一次, 其余的关于定时器,放到chan讲
ticker := time.Tick(time.Second) // 每一秒都会触发。
for i := range ticker {
fmt.Println(i)
}
```
> 时间判断
```go
package main
import (
"fmt"
"time"
)
func main() {
// 加 减 比较(在xxx之前 在xxx之后 相等)
now := time.Now()
later := now.Add(time.Hour)
fmt.Println(later)
// 两个时间的差值
subTime := later.Sub(now)
fmt.Println(subTime) // 1h0m0s
// 比较时间, init() 校验时间 当地时间和网络时间是否一致
fmt.Println(now.Equal(later)) // fasle
fmt.Println(now.Before(later)) // true
fmt.Println(now.After(later)) // fasle
}
// 定时器 - 本质是一个通道chan
func d1() {
// 定时器: 每隔xxx s执行一次, 其余的关于定时器,放到chan讲
ticker := time.Tick(time.Second) // 每一秒都会触发。
for i := range ticker {
fmt.Println(i)
}
}
```
小案例:
```go
package main
import (
"syscall"
"time"
"unsafe"
)
func main() {
ticker := time.Tick(time.Second) // 每一秒都会触发。
for i := range ticker {
msgBox(i.Format("2006-01-02 15:04:05"))
}
}
func msgBox(timeStr string) {
user32 := syscall.NewLazyDLL("user32.dll")
messageBox := user32.NewProc("MessageBoxW")
hwnd := 0 // 0表示将弹窗放在桌面的中心位置
title := "Hello"
text := timeStr
flags := 0x00000000 | 0x00000040 // 0x00000000表示弹出消息框并且默认按钮为OK,0x00000040表示消息框的图标为信息图标
messageBox.Call(uintptr(hwnd), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text))), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title))), uintptr(flags))
}
```
@@ -0,0 +1,699 @@
# 计算机 I/O 与文件系统基础
## 数据的本质
计算机中的所有数据本质上都是二进制流,即由 0 和 1 组成的序列。不同类型的文件(如图片、视频、文本等)只是这些二进制数据按照特定格式编码和解码的结果。例如:
- 图片文件(JPG):二进制数据按照 JPEG 格式编码,显示器将其解析为可视化的图像
- 视频文件(MP4):二进制数据按照 MP4 编码规范存储,包含视频帧、音频等信息
- 文本文件:二进制数据通过字符编码(如 UTF-8)转换为可读的文字
## I/O 的含义
I/O(输入/输出)是计算机与外部世界交换数据的基础:
- Input(输入):从外部获取数据到计算机系统
- Output(输出):将数据从计算机系统传输到外部
## 文件系统中的文件类型
主要分为两大类:
1. 文本文件:存储人类可读的字符序列
2. 二进制文件:存储原始的二进制数据(如图片、视频、可执行文件等)
## 文件操作的基本功能
现代编程语言通常通过封装底层操作系统的文件 API 来提供文件操作能力。核心功能包括:
1. 获取文件信息(FileInfo
- 文件大小
- 创建时间
- 修改时间
- 访问权限等
2. 文件读取(Read
- 读取文本内容
- 读取二进制数据
- 支持随机访问和顺序读取
3. 文件写入(Write
- 写入文本内容
- 写入二进制数据
- 支持追加和覆盖模式
这种组织方式更系统地展示了这些概念之间的关系,并提供了更详细的解释。你觉得这样的组织方式如何?是否还需要在某些方面做进一步的调整?
```go
package main
import (
"fmt"
"os"
)
// file
// fileInfo
/*
type FileInfo interface {
Name() string // base name of the file
Size() int64 // length in bytes for regular files; system-dependent for others
Mode() FileMode // file mode bits 权限
ModTime() time.Time // modification time
IsDir() bool // abbreviation for Mode().IsDir()
// 获取更加详细的文件信息, *syscall.Stat_t 反射来获取
Sys() any // underlying data source (can return nil)
*/
func main() {
// 获取某个文件的状态
fileinfo, err := os.Stat("D:\\Environment\\GoWorks\\src\\xuego\\lesson11\\test")
if err != nil {
return
}
fmt.Println(fileinfo.Name()) // demo01.go
fmt.Println(fileinfo.IsDir()) // false
fmt.Println(fileinfo.ModTime()) // 2023-02-23 20:25:43.1772351 +0800 CST
fmt.Println(fileinfo.Size()) // 1186 字节数
fmt.Println(fileinfo.Mode()) // -rw-rw-rw-
}
```
## 创建文件、目录
通过代码创建文件
路径:
- 相对路径
- 相对当前目录的路径
- ./ 当前目录
- ../ 上一级目录
- 绝对路径
- 从盘符开始的路径
> 创建目录
mkdir / 权限
mkdirAll 创建层级目录
remove 删除目录
removeAll 强制删除目录
```go
package main
import (
"fmt"
"os"
)
// 创建目录
// 项目开源框架,一运行,就会自动生成脚手架目录
func main() {
// 打开一个文件夹(1、存在我就打开 2、不存在,创建这个文件夹)
// func Mkdir(name string, perm FileMode) error
// ModePerm : 0777
err := os.Mkdir("D:\\MyGo\\src\\github.com\\Docker7530\\testproject\\file1", os.ModePerm)
if err != nil {
// 存在就无法创建了 Cannot create a file when that file already exists.
fmt.Println(err)
}
fmt.Println("文件夹创建完毕")
// 创建层级文件夹
err2 := os.MkdirAll("D:\\MyGo\\src\\github.com\\Docker7530\\testproject\\file2\\aa\\bb\\cc\\dd", os.ModePerm)
if err2 != nil {
fmt.Println(err2)
}
fmt.Println("层级文件夹创建完毕")
// 删除 remove
// func Remove(name string) error
// 通过remove方法只能删除单个空文件夹:
// remove D:\Environment\GoWorks\src\xuego\lesson11\file2: The directory is not empty.
err3 := os.Remove("D:\\MyGo\\src\\github.com\\Docker7530\\testproject\\file2")
if err3 != nil {
fmt.Println(err3)
//return
}
fmt.Println("file delete success!!")
// 如果存在多层文件,removeAll,相对来说比较危险,删除这个目录下的所有东西, 强制删除
err4 := os.RemoveAll("D:\\MyGo\\src\\github.com\\Docker7530\\testproject\\file2")
if err4 != nil {
fmt.Println(err4)
return
}
fmt.Println("err4 delete success!!")
}
```
> 创建文件
os.create(),若存在就是的打开-就是返回的这个file对象. 如果不存在,创建在打开
```go
package main
import (
"fmt"
"os"
)
func main() {
// 创建文件 Create
// func Create(name string) (*File, error) {
// 返回的file对象就是我们的文件
file1, err := os.Create("a.go") // 相对路径
if err != nil {
fmt.Println(err)
}
fmt.Println(file1)
// 删除
os.Remove("D:\\Environment\\GoWorks\\src\\xuego\\lesson11\\a.go")
}
```
## IO 读
1、与文件建立连接
```go
package main
import (
"fmt"
"os"
)
// IO读
func main() {
// 找到这个文件的对象 create 创建、 打开Open
// func Open(name string) (*File, error)
file1, err := os.Open("D:\\Environment\\GoWorks\\src\\xuego\\lesson11\\狂神的Go世界.txt")
if err != nil {
fmt.Println(err)
}
fmt.Println(file1)
// file 类-- 指定的对象
// 打开文件的时候,选定权限: 可读可写的方式打开
// OpenFile(文件名,打开方式:可读、可写...,FileMode , 权限)
file2, err2 := os.OpenFile("D:\\Environment\\GoWorks\\src\\xuego\\lesson11\\狂神的Go世界.txt",
os.O_RDONLY|os.O_WRONLY, os.ModePerm)
if err2 != nil {
fmt.Println(err2)
}
fmt.Println(file2)
// 可以操作这个对象了
}
```
2、读取 file.Read([]byte) ,将file中的数据读取到 []byte 中, n,err n读取到的行数,err 错误,EOF错误,就代表文件读取完毕了
一直调用read,就代表光标往后移动….
```go
package main
import (
"fmt"
"os"
)
// 读取文件数据
func main() {
// 我们习惯于在建立连接时候通过defer来关闭连接,保证程序不会出任何问题,或者忘记关闭
// 建立连接
file, _ := os.Open("狂神的Go世界.txt")
// 关闭连接
defer file.Close()
// 读代码 ,Go 的错误机制,让我们专心可以写业务代码。
// 1、创建一个容器 (二进制文本文件--0100101010 => 读取流到一个容器 => 读取容器的数据)
bs := make([]byte, 2, 1024) // 缓冲区,可以接受我们读取的数据
// 2、读取到缓冲区中。 // 汉字一个汉字占 3个位置
n, err := file.Read(bs)
fmt.Println(n)
fmt.Println(err)
fmt.Println(string(bs)) // 读取到的字符串 ab
// 光标不停的向下去指向,读取出来的内容就存到我们的容器中。
file.Read(bs)
fmt.Println(string(bs)) // 读取到的字符串 cd
file.Read(bs)
fmt.Println(string(bs)) // 读取到的字符串 e
n, err = file.Read(bs)
fmt.Println(n)
fmt.Println(err) // EOF ,读取到了文件末尾。就会返回EOF。
fmt.Println(string(bs)) // 读取到的字符串
n, err = file.Read(bs)
fmt.Println(n)
fmt.Println(err)
fmt.Println(string(bs)) // 读取到的字符串
}
```
## IO 写(权限)
建立连接 (设置权限:可读可写,扩充这个文件的append os.OpenFile
关闭连接
写入 file.write
- file.Write
- file.WriteString
```go
package main
import (
"fmt"
"os"
)
func main() {
fileName := "狂神的Go世界.txt"
// 权限:如果我们要向一个文件中追加内容, O_APPEND, 如果没有,就是从头开始写
file, _ := os.OpenFile(fileName, os.O_WRONLY|os.O_RDONLY|os.O_APPEND, os.ModePerm)
defer file.Close()
// 操作
bs := []byte{65, 66, 67, 68, 69} // A B C D E
n, err := file.Write(bs)
if err != nil {
fmt.Println(err)
}
fmt.Println(n)
// string类型的写入
n, err = file.WriteString("hhahahahah哈哈哈哈哈哈哈")
if err != nil {
fmt.Println(err)
}
fmt.Println(n)
}
```
## 文件复制
```go
package utils
import (
"fmt"
"io"
"os"
)
// Copy 方法需要参数为:source 源文件 ,destination 目标文件
func Copy(source, destination string, bufferSize int) {
// 读取文件
sourceFile, err := os.Open(source)
if err != nil {
fmt.Println("Open错误:", err)
}
// 输出文件 O_WRONLY , O_CREATE 如果不不存在,则会创建
destinationFile, err := os.OpenFile(destination, os.O_WRONLY|os.O_CREATE, os.ModePerm)
if err != nil {
fmt.Println("OpenFile错误:", err)
}
// 关闭
defer sourceFile.Close()
defer destinationFile.Close()
// 专注业务代码,拷贝
buf := make([]byte, bufferSize)
// 读取
for {
n, err := sourceFile.Read(buf)
if n == 0 || err == io.EOF {
fmt.Println("读取完毕源文件,复制完毕")
break
} else if err != nil {
fmt.Println("读取错误:", err)
return // 错误之后,必须要return终止函数执行。
}
// 将缓冲区的东西写出到目标文件
_, err = destinationFile.Write(buf[:n])
if err != nil {
fmt.Println("写出错误:", err)
}
}
}
```
调用
```go
package main
import "xuego/lesson11/utils"
func main() {
source := "C:\\Users\\遇见狂神说\\Desktop\\xq.png"
dest := "D:\\Environment\\GoWorks\\src\\xuego\\lesson11\\xq.png"
utils.Copy(source, dest, 1024)
}
```
> 系统给我们提供了copy方法
```go
// 调用系统的方法
func Copy2(source, destination string) {
// 读取文件
sourceFile, err := os.Open(source)
if err != nil {
fmt.Println("Open错误:", err)
}
// 输出文件 O_WRONLY , O_CREATE 如果不不存在,则会创建
destinationFile, err := os.OpenFile(destination, os.O_WRONLY|os.O_CREATE, os.ModePerm)
if err != nil {
fmt.Println("OpenFile错误:", err)
}
// 关闭
defer sourceFile.Close()
defer destinationFile.Close()
// 具体的实现
written, err := io.Copy(destinationFile, sourceFile)
fmt.Println("文件的字节大小:", written)
}
```
## Seeker接口
设置光标的位置,读写文件
```go
package main
import (
"fmt"
"io"
"os"
)
func main() {
// 1. 创建一个测试文件
file, err := os.OpenFile("狂神的Go世界1.txt", os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
fmt.Println("打开文件错误:", err)
return
}
defer file.Close()
// 2. 写入初始内容
file.WriteString("Hello World!")
// 3. 演示不同的 Seek 操作
demonstrateSeek(file)
}
func demonstrateSeek(file *os.File) {
// 重置光标到文件开始
file.Seek(0, io.SeekStart)
// 从文件开始读取 5 个字节
buf := make([]byte, 5)
n, err := file.Read(buf)
if err != nil {
fmt.Println("读取错误:", err)
return
}
fmt.Printf("1. 读取前5个字符: %s\n", string(buf[:n]))
// 从当前位置向后移动 1 个字节
file.Seek(1, io.SeekCurrent)
buf = make([]byte, 5)
n, _ = file.Read(buf)
fmt.Printf("2. 跳过1个字符后读取: %s\n", string(buf[:n]))
// 从文件末尾向前移动 5 个字节
file.Seek(-5, io.SeekEnd)
buf = make([]byte, 5)
n, _ = file.Read(buf)
fmt.Printf("3. 从末尾往前5个字符读取: %s\n", string(buf[:n]))
// 在文件末尾追加内容
file.Seek(0, io.SeekEnd)
file.WriteString("\nAppended Text")
// 读取整个文件内容
file.Seek(0, io.SeekStart)
content, _ := io.ReadAll(file)
fmt.Printf("\n4. 最终文件内容:\n%s\n", string(content))
}
```
## 断点续传
思考几个问题:
1、如果你要传的文件很大,70G,是否有方法可以缩短耗时?
- 将文件拆分
- 同时多线程进行下载
2、如果在文件传递过程中,程序被迫中断(断电、断网、内存满了..),下次重启之后,文件是否还需要重头再传?
- 希望能够继续上传或者下载
3、传递文件的时候,支持暂停和恢复上传?假设这个两个操作分布在重启前后?
- 支持!
file、read、write、seek
思路:
1、需要记住上一次传递了多少数据、temp.txt => 记录
2、如果被暂停或者中断了,我们就可以读取这个temp.txt的记录,恢复上传
3、删除temp.txt
![image-20230226203841579](../../../attachment/images-paste/image-20230226203841579.png)
**所有人必须要要全部理解的一段代码**
```go
package main
import (
"fmt"
"io"
"os"
"strconv"
)
// 断点续传
func main() {
// 传输源文件地址
srcFile := "C:\\Users\\遇见狂神说\\Desktop\\client\\gp.png"
// 传输的目标位置
destFile := "D:\\Environment\\GoWorks\\src\\xuego\\lesson11\\server\\gp-upload.png"
// 临时记录文件
tempFile := "D:\\Environment\\GoWorks\\src\\xuego\\lesson11\\temp.txt"
// 创建对应的file对象,连接起来
file1, _ := os.Open(srcFile)
file2, _ := os.OpenFile(destFile, os.O_CREATE|os.O_RDWR, os.ModePerm)
file3, _ := os.OpenFile(tempFile, os.O_CREATE|os.O_RDWR, os.ModePerm)
defer file1.Close()
defer file2.Close()
fmt.Println("file1/2/3 文件连接建立完毕")
// 1、读取temp.txt
file3.Seek(0, io.SeekStart)
buf := make([]byte, 1024, 1024)
n, _ := file3.Read(buf)
// 2、转换成string - 数字。
countStr := string(buf[:n])
count, _ := strconv.ParseInt(countStr, 10, 64)
fmt.Println("temp.txt中记录的值为:", count) // 5120
// 3、设置读写的偏移量
file1.Seek(count, io.SeekStart)
file2.Seek(count, io.SeekStart)
fmt.Println("file1/2 光标已经移动到了目标位置")
// 4、开始读写(复制、上传)
bufData := make([]byte, 1024, 1024)
// 5、需要记录读取了多少个字节
total := int(count)
for {
// 读取数据
readNum, err := file1.Read(bufData)
if err == io.EOF { // file1 读取完毕了
fmt.Println("文件传输完毕了")
file3.Close()
os.Remove(tempFile)
break
}
// 向目标文件中写入数据
writeNum, err := file2.Write(bufData[:readNum])
// 将写入数据放到 total中, 在这里total 就是传输的进度
total = total + writeNum
// temp.txt 存放临时记录数据
file3.Seek(0, io.SeekStart) // 将光标重置到开头
file3.WriteString(strconv.Itoa(total))
}
}
```
## 遍历文件夹
**下去一定要自己实现**
```go
package main
import (
"fmt"
"log"
"os"
)
// cd /d 文件夹路径
// tree /F , 查看当前文件夹下的所有文件
// 遍历文件夹
// 1、读取当前文件夹下的所有文件
// 2、如果是文件夹,进入文件夹,继续读取里面的所有文件
// 3、设置一些结构化代码
func main() {
dir := "D:\\Environment\\GoWorks\\src\\xuego"
tree(dir, 0)
}
// 日常调试测试常用fmt输出 、 工作中or项目中更多是log日志输出
func tree(dir string, level int) {
// 编写层级
tabString := "|--"
for i := 0; i < level; i++ {
tabString = "| " + tabString
}
// 获取目录 ReadDir, 返回目录信息[]DirEntry,多个文件信息
fileInfos, err := os.ReadDir(dir)
if err != nil {
log.Println(err)
}
// 遍历出来所有文件之后,获取里面的单个文件
for _, file := range fileInfos {
// 文件夹中文件的全路径展示
filename := dir + "\\" + file.Name()
fmt.Println(tabString + file.Name())
// 如果是文件夹,再次遍历
if file.IsDir() {
tree(filename, level+1)
}
}
}
```
## bufio
Go语言自带的IO操作包。bufio,使用这个包可以大幅提升文件的读写效率。
buf 缓冲区.
io操作效率本身是还可以的,频繁访问本地磁盘文件(效率低)
所以说 bufio ,提供了一个缓冲区,读和写都先在缓冲区中,最后再一次性读取或者写入到文件里,降低访问本地磁盘的次数。
![image-20230226214440096](../../../attachment/images-paste/image-20230226214440096.png)
bufio写入
```go
package main
import (
"bufio"
"fmt"
"log"
"os"
)
// bufio 的应用
func main() {
file, err := os.Open("D:\\Environment\\GoWorks\\src\\xuego\\lesson11\\demo01.go")
if err != nil {
log.Println(err)
}
defer file.Close()
// 读取文件
// 创建一个bufio包下的 reader对象。
//bufioReader := bufio.NewReader(file)
//buf := make([]byte, 1024)
//n, err := bufioReader.Read(buf)
//fmt.Println("读取到了多少个字节:", n)
// 读取键盘的输入
// 键盘的输入,实际上是流 os.Stdin
inputReader := bufio.NewReader(os.Stdin)
// delim 到哪里结束读取
readString, _ := inputReader.ReadString('\n')
fmt.Println("读取键盘输入的信息:", readString)
}
```
bufio写出
```go
package main
import (
"bufio"
"fmt"
"os"
)
// 写入
func main() {
file, _ := os.OpenFile("D:\\Environment\\GoWorks\\src\\xuego\\lesson11\\a.txt",
os.O_RDWR|os.O_CREATE,
os.ModePerm)
defer file.Close()
// bufio
fileWrite := bufio.NewWriter(file)
writeNum, _ := fileWrite.WriteString("kuangshen")
fmt.Println("writeNum:", writeNum)
// 发现并没有写出到文件,是留在了缓冲区,所以我们需要调用 flush 刷新缓冲区
// 手动刷新进文件
fileWrite.Flush()
}
```
@@ -0,0 +1,410 @@
# 1. 反射基础概念
- **定义**: 在程序运行时动态获取和操作变量信息的机制
- **主要用途**:
- 获取变量类型和值
- 访问和修改结构体字段
- 动态调用方法
- 实现框架底层逻辑
# 2. 使用场景
- **适用场景**:
- 开发框架和脚手架
- 实现通用性工具
- 处理未知类型的数据
- **使用建议**:
- 一般开发中谨慎使用
- 反射操作性能开销较大
- 代码复杂度会增加
# 3. 核心概念
## Type 和 Kind 的区别
```go
// Type: 具体类型
var user User = User{name: "Tom", age: 20}
// Kind: 底层类型分类
var num int = 18
```
- **Type(类型)**:
- 具体的数据类型
- 例如: `User`, `Student`, `int64`
- **Kind(种类)**:
- 类型的底层分类
- 例如: `struct`, `int`, `string`
# 4. reflect 包核心功能
## 主要 API
```go
// 获取类型信息
typeInfo := reflect.TypeOf(变量)
// 获取值信息
valueInfo := reflect.ValueOf(变量)
```
## 常用操作
```go
// 类型判断
if typeInfo.Kind() == reflect.Struct {
// 处理结构体
}
// 获取/修改值
if valueInfo.Kind() == reflect.Int {
value := valueInfo.Int()
valueInfo.Set(reflect.ValueOf(20))
}
```
# 5. 实际应用示例
## 结构体反射
```go
type User struct {
Name string
Age int
}
func inspectStruct(v interface{}) {
t := reflect.TypeOf(v)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("字段名: %s, 类型: %s\n",
field.Name, field.Type)
}
}
```
## 方法反射
```go
func (u User) SayHello(name string) {
fmt.Printf("Hello, %s\n", name)
}
// 通过反射调用方法
func callMethod(v interface{}) {
val := reflect.ValueOf(v)
method := val.MethodByName("SayHello")
args := []reflect.Value{reflect.ValueOf("Tom")}
method.Call(args)
}
```
# 6. 性能考虑
- 反射操作比直接操作慢 10-100 倍
- 建议在以下场景使用:
- 框架开发
- 配置解析
- 序列化/反序列化
- 避免在性能敏感代码中使用
# 7. 最佳实践
1. 优先使用类型断言代替反射
2. 缓存反射结果避免重复操作
3. 谨慎使用反射修改值
4. 做好错误处理
5. 编写清晰的文档说明
```go
package main
import (
"fmt"
"reflect"
)
// 反射
/*
Type : reflect.TypeOf(a) , 获取变量的类型
Value reflect.ValueOf(a) 获取变量的值
*/
func main() {
// 正常编程定义变量
var a int = 3
fmt.Println("type", reflect.TypeOf(a))
fmt.Println("value", reflect.ValueOf(a))
// 根据反射的值,来获取对象对应的类型和数值
// 如果我们不知道这个对象的信息,我们可以通过这个对象拿到代码中的一切。
// Value
v := reflect.ValueOf(a) // string int User
// Kind : 获取这个值的种类, 在反射中,所有数据类型判断都是使用种类。
if v.Kind() == reflect.Float64 {
fmt.Println(v.Float())
}
if v.Kind() == reflect.Int {
fmt.Println(v.Int())
}
fmt.Println(v.Kind() == reflect.Float64)
//fmt.Println(v.Type())
}
```
> 静态类型 & 动态类型
在反射过程中,编译的时候就知道变量类型的就是静态类型、如果在运行时候才知道类型的就是动态类型
静态类型:变量在声明时候给他赋予类型的
```go
var name string // string是静态类型的
var age int // int 是静态类型的
```
动态类型:在运行的时候可能发生变化,主要考虑赋值问题
```go
var A interface{} // interface{} 静态类型
A = 10 // interface{} 静态类型 动态类型 int
A = "xuexiangban" // interface{} 静态类型 动态类型 string
```
**为什么要用反射:**
1、我们需要编写一个函数,但是不知道函数传递给我的参数时什么?没约定好,传入的类型太多,这些类型不能统一表示,反射。
2、我们在某些使用,需要根据条件来判断具体使用哪个函数处理问题,根据用户的输入来决定,这时候就需要对函数的参数进行反射,在运行期间来动态处理。
**为什么不建议使用反射:**
1、和反射相关的代码,不方便阅读,开发中,代码可读性(指标)很重要
2、Go语言是静态类型的语言,编译器可以找出开发时候的错误,如果代码中有大量反射代码,随时可能存在安全问题,panic,项目就终止。
3、反射的性能很低,相对于正常的开发,至少慢2-3个数量级。项目关键位置低耗时,一定是不能使用反射的。更多时候使用约定。
# 反射获取变量信息
**实现拿到一个对象,还原它的本身结构信息**
reflect.TypeOf(v) : 获取变量的类型 Type
- Type.kind , 找到种类
- Type.NumField(),找到里面字段的数量
- Type.Filed(i)
- Type.NumMethod(),找到里面方法的数量
- Type.Method(i)
reflect.ValueOf(v) : 获取变量的值 Value
- Value.Field(i).Interface()
```go
package main
import (
"fmt"
"reflect"
)
type User struct {
Name string
Age int
Sex string
}
func (user User) Say(msg string) {
fmt.Println("User 说:", msg)
}
// toString : 打印结构体信息
func (user User) PrintInfo() {
fmt.Printf("姓名:%s,年龄:%d,性别:%s\n", user.Name, user.Age, user.Sex)
}
func main() {
user := User{"kuangshen", 18, "男"}
reflectGetInfo(user)
}
// 通过反射,获取变量的信息
func reflectGetInfo(v interface{}) {
// 1、获取参数的类型Type , 可能是用户自己定义的,但是Kind一定是内部类型struct
getType := reflect.TypeOf(v)
fmt.Println(getType.Name()) // 类型信息 User
fmt.Println(getType.Kind()) // 找到上级的种类Kind struct
// 2、获取值
getValue := reflect.ValueOf(v)
fmt.Println("获取到value", getValue)
// 获取字段,通过Type扒出字段
// Type.NumField() 获取这个类型中有几个字段 3
// field(index) 得到字段的值
for i := 0; i < getType.NumField(); i++ {
field := getType.Field(i) // 类型
value := getValue.Field(i).Interface() // value
// 打印
fmt.Printf("字段名:%s,字段类型:%s,字段值:%v\n", field.Name, field.Type, value)
}
// 获取这个结构体的方法 , 获取方法的数量
for i := 0; i < getType.NumMethod(); i++ {
method := getType.Method(i)
fmt.Printf("方法的名字:%s,方法类型:%s", method.Name, method.Type)
}
}
/*
type User struct{
Name string
Age int
Sex string
}
func (main.User) PrintInfo(){}
func (main.User) Say(string)
*/
```
# 反射修改变量的值
vaule.CanSet
vaule.SetXXX
```go
package main
import (
"fmt"
"reflect"
)
// 反射设置变量的值
func main() {
var num float64 = 3.14
update(num)
fmt.Println(num)
}
func update(v any) {
// 通过反射修改值,需要操作对象的指针,拿到地址,然后拿到指针对象
pointer := reflect.ValueOf(&v)
newValue := pointer.Elem()
fmt.Println("类型:", newValue.Type())
fmt.Println("判断该类型是否可以修改:", newValue.CanSet())
if newValue.Kind() == reflect.Float64 {
// 通过反射对象给变量赋值
newValue.SetFloat(2.21)
}
if newValue.Kind() == reflect.Int {
// 通过反射对象给变量赋值
newValue.SetFloat(2)
}
}
```
修改结构体变量:通过属性名,来实现修改
```go
func main() {
user := User{"kuangshen", 18, "男"}
value := reflect.ValueOf(&user)
if value.Kind() == reflect.Ptr {
// 获取指针对象
newValue := value.Elem()
if newValue.CanSet() {
// 如果是结构体:1、需要找到对象的结构体字段名
newValue.FieldByName("Name").SetString("狂神")
newValue.FieldByName("Age").SetInt(26)
}
}
fmt.Println(user)
//reflectGetInfo(user)
}
```
# 反射调用方法
> value.MethodByName("PrintInfo").Call(nil)
>
> // 参数构建
>
> args := make([]reflect.Value, 1)
> args[0] = reflect.ValueOf("这反射来调用的")
通过反射调用user方法
```go
user := User{"kuangshen", 18, "男"}
// 通过方法名,找到这个方法, 然后调用 Call() 方法来执行
// 反射调用方法
value := reflect.ValueOf(user)
fmt.Printf("kind:%s,type:%s\n", value.Kind(), value.Type())
value.MethodByName("PrintInfo").Call(nil) // 无参方法调用
// 有参方法调用
args := make([]reflect.Value, 1)
args[0] = reflect.ValueOf("这反射来调用的")
value.MethodByName("Say").Call(args) // 无参方法调用
```
# 反射调用函数
```go
package main
import (
"fmt"
"reflect"
)
// 反射调用函数 func
func main() {
// 通过函数名来进行反射
// Kind func
value1 := reflect.ValueOf(fun1)
fmt.Println(value1.Kind(), value1.Type())
value1.Call(nil)
value2 := reflect.ValueOf(fun2)
fmt.Println(value2.Kind(), value2.Type())
args1 := make([]reflect.Value, 2)
args1[0] = reflect.ValueOf(1)
args1[1] = reflect.ValueOf("hahahhaha")
value2.Call(args1)
vuale3 := reflect.ValueOf(fun3)
fmt.Println(vuale3.Kind(), vuale3.Type())
args2 := make([]reflect.Value, 2)
args2[0] = reflect.ValueOf(1)
args2[1] = reflect.ValueOf("hahahhaha")
resultValue := vuale3.Call(args2)
fmt.Println("返回值:", resultValue)
}
func fun1() {
fmt.Println("fun1:无参")
}
func fun2(i int, s string) {
fmt.Println("fun2:有参 i=", i, " s=", s)
}
func fun3(i int, s string) string {
fmt.Println("fun3:有参有返回值 i=", i, " s=", s)
return s
}
```
@@ -0,0 +1,341 @@
# 泛型(1.18
## 泛型的概念理解
Go语言他是在进化的,不断的迭代,优化功能!
1.0 - 1.19 (1.20)/ 和最开始Go语言比起来发生了很多变化。
1.7 Context
1.11 modules
1.13 error嵌套
1.18 泛型(类型参数)
https://segmentfault.com/a/1190000041634906
泛型的出现,很受期待,但是很少用!场景不多。
> 打印一个切片(传递不同的参数string 、int)
```go
package main
import (
"fmt"
)
func main() {
is := []int{1, 2}
//strs := []string{"kuangshen", "xuexiangban"}
printSlice(is)
//printSlice(strs)
}
// 如何实现一个方法可以打印上面不同类型的切片呢? 反射
func printSlice(s interface{}) {
// 断言 x.(T), 如果x实现了T,那么就将 x转换为T类型
for _, v := range s.([]string) {
fmt.Println(v)
}
}
```
这种情况,传递的参数进来,需要自己做N种判断来进行适配。
> 泛型
思考:不限定参数的类型,让调用的人自己去定义类型。
```go
// 参数的类型是不确定的,让用户自己指定。
// 泛型也是使用 [],和数组很像!
//
func printSlice[T any](s []T) {
for _, v := range s {
fmt.Println(v)
}
}
```
printSlice , [T any] 泛型的约束!由于我们不确定这个函数的参数类型。我们希望用户给我们传递这个值。
```
print[T string](name T)
print[T string|int](name T)
func Min[T int|int8|int16|int32|int64](a,b T){
}
```
泛型的作用:
1、减少重复性的代码,提高安全性
- **针对不同的类型,写了相同逻辑的代码,我们就可以通过泛型来简化代码!**
2、在1.18版本之前 反射 来实现。 泛型并不能完全取代反射!
泛型:多的多个类型(类型可以是不确定的)
```
var 变量名 数据类型 = 值
var 变量名 T = 值 // 泛型的逻辑
```
## 泛型类型
```go
package main
import "fmt"
// s1 是我们自己定义的类型 ,本质 []int
type s1 []int
type s2 []float64
type s3 []float32
// 我们定义的结构都是一样的,只是它的类型不同,就需要重新定义这么多的类型。
// 思考:是否有一种机制,只定义一个类型就可以代表上面的所有类型?
// 泛型:类型 参数化了! 参数:人为传递的
/*
1、T 说白了就是一个占位符,类型的形式参数,T是不确定的,需要在使用的时候进行传递。
2、由于T类型是不确定的,我们需要加一些约束 int|float64|float32 。告诉编译器我这个T,只接受
int、float64、float32 类型
3、我们这里定义的类型是什么?Slice[T]
*/
// 这种类型的定义方式,带了类型形参,和普通定义类型就完全不同的。
// 普通的定义类型,这个类型只能代表本身一个,泛型类型,我们可以实现,参数类型传递。
// 我们可以在使用的时候来定义类型。
// 语法糖:简化开发
type Slice[T int | float64 | float32] []T
func main() {
var a Slice[int] = []int{1, 2, 3}
fmt.Printf("%T\n", a) // Slice[int]
var b Slice[float64] = []float64{1.0, 2.0, 3.0}
fmt.Printf("%T\n", b) // Slice[float64]
// 不能够赋值(string 不在T的约束当中,不能实例化的)
//var c Slice[string] = []string{"kuangshen","xxx"}
// T是占位符,在使用的时候,必须要实例化为具体的类型。
//var d Slice[T] = []int{1,2,3}
}
func test(name interface{}) {
fmt.Println(name)
}
```
泛型的类型使用
```go
package main
import "fmt"
// 泛型可以用在所有有类型的地方
type MyStruct[T int | string] struct {
Id T
Name string
}
type IprintData[T int|float64|string] interface {
Print(data T)
}
// 通道
type MyChan[T int|string|float64] chan T
func main() {
//T 泛型的参数类型的属性可以远不止一个,所有东西都可以泛型化。
// map(int)string
// map[KEY]VALUE 类型形参(参数是不确定) KEY 、VALUE
// KEY int | string VALUE float32 | float64 约束
// 类型的名字 MyMap[KEY,VALUE], 通过这一个类型,来代表多个类型!--> 泛型
type MyMap[KEY int | string | float64, VALUE float32 | float64 | int] map[KEY]VALUE
// map [string]float64
var score MyMap[string, float64] = map[string]float64{
"go": 9.9,
"java": 8.0,
}
fmt.Println(score)
}
```
> 特殊的泛型
```go
package main
func main() {
// 特殊的泛型类型,泛型的参数时多样的,但是实际类型定义就是int
type AAA[T int|string] int
var a AAA[int] = 123
var b AAA[string] = 123
//var c AAA[string] = "hello" // 底层类型是int
}
```
这里虽然使用了泛型。但是底层类型就是int,所以无论传什么都可以的,但是赋值,只能是int、
这个例子没什么意义。
## 泛型函数
单纯的泛型没啥意义。和函数结合使用, 可以使用调用者(调用者的类型可以自定义,就可以实现泛型。)
```go
package main
import (
"fmt"
)
type MySlice[T int | float32 | int64] []T
func main() {
var s MySlice[int] = []int{1, 2, 3, 4}
fmt.Println(s.sum())
var s1 MySlice[float32] = []float32{1.0, 2.0, 3.0, 4.4}
fmt.Println(s1.sum())
}
// 调用者,类型是不确定的,用户传什么,她就实例化什么。 类型参数化了 , 泛型
// 没有泛型之前, 反射: reflect.ValueOf().Kind() , 也需要很多if,本质是逻辑相同的,只是类型不同!
func (s MySlice[T]) sum() T {
var sum T
for _, v := range s {
sum += v
}
return sum
}
```
泛型可以增加代码的灵活性,降低了可读性!
```go
package main
import (
"fmt"
)
func main() {
var a int = 1
var b int = 2
fmt.Println(Add[int](a, b))
var c float32 = 1.1
var d float32 = 2.2
fmt.Println(Add[float32](c, d))
// 每次都去写T的类型是很麻烦的,支持自动推导!
// Go的泛型语法糖:自动推导 (本质,就是编译器帮我们加上去了,在实际运行,这里T还是加上去的)
fmt.Println(Add(a, b)) // T : int
fmt.Println(Add(c, d)) // T : float32
}
// 真正的Add实现,传递不同的参数都是可以适配的! Add[T] T在调用的时候需要实例化
// 这种带了类型形参的函数就叫做泛型函数,极大的提高代码的灵活心,降低阅读性!
func Add[T int | float32 | float64](a T, b T) T {
return a + b
}
```
1、泛型类型 (自定义类型结合泛型使用)
2、泛型作为接收者 (实现函数的灵活变化)
3、泛型函数(参数是泛型)
计算机底层的运行,绕不过的,你只能简化代码开发,不能简化实际运行操作!
## 自定义泛型
由于约束有时候很多,我们可以定义一些自己的泛型约束(本质是一个接口)
```go
package main
// 泛型的约束提取定义
type MyInt interface {
int|float32|int8|int32 // 作用域泛型的,而不是一个接口方法
}
// 自定义泛型
func main() {
var a int = 10
var b int = 20
GetMaxNum(a,b)
}
func GetMaxNum[T MyInt](a,b T) T {
if a>b {
return a
}
return b
}
```
内置泛型类型:
any (就是一个泛型,表示了go所有的内置类型。interface{}
comparable :表示所有可以比较的类型
新符号 ~,和类型一起出现的,表示支持该类型的衍生类型!
```go
package main
import "fmt"
// int8 衍生类型
type int8A int8
type int8B = int8
// ~ 表示可以匹配该类型的衍生类型
type NewInt interface {
~int8
}
// ~
func main() {
var a int8A = 10
var b int8A = 10
fmt.Println(GetMax(a, b))
}
func GetMax[T NewInt](a, b T) T {
if a > b {
return a
}
return b
}
```
总结泛型:
1、类型参数 T
2、类型集合 T,K map slice
3、类型推断 (简化开发)
@@ -0,0 +1,318 @@
# Web 基础架构
## 1. Web 类型
- **静态 Web**: 所有用户看到的内容完全相同
- **动态 Web**: 根据用户身份、权限等展示不同内容
## 2. Web 应用架构
- **B/S (Browser/Server)**: 基于浏览器的 Web 应用
- **C/S (Client/Server)**: 需要安装的客户端程序
- 优势: 跨平台支持 (Windows/Mac/Linux)
## 3. 网络协议
- **HTTP**:
- 标准端口: 80
- 用途: 传输超文本(文本、图片、视频等)
- **HTTPS**:
- 标准端口: 443
- 特点: 加密传输,更安全
## 4. 通信模型
**请求-响应模式**:
- 请求 (Request):
- 来源: 客户端(浏览器)
- 内容: URL 地址(如 https://www.baidu.com)
- 方式: GET、POST、PUT、DELETE 等
- 响应 (Response):
- 来源: 服务端
- 功能: 处理请求并返回数据
- 主要操作: CRUD(增删改查),占比约 80%
## 5. 工作流程
1. 客户端通过 TCP/IP 连接服务器
2. 发送 HTTP(S) 请求:
- 普通请求: www.example.com/login?username=xxx&pwd=xxx
- 文件上传: upload/video?file=xxx
3. 服务器处理请求
4. 返回响应结果
5. 客户端展示结果
6. 断开连接
## 开发调试
- 使用浏览器开发者工具 (F12 或右键检查)
- 分析请求/响应数据
- 优化代码性能和健壮性
# helloworld
go 语言中所有和网络相关的都在 net 包下,http 也在 net 包下。
包含了客户端和服务端的代码实现。
未来我们学习的所有框架,都是基于这些底层代码的。
请求(request- 响应(response
```go
package main
import (
"fmt"
"net/http"
)
// 一个简单的服务端代码实现
// http程序启动之后是不会停止的,一直监听请求
func main() {
// 写一些请求来接收浏览器的信息
// HandleFunc http请求的处理函数
// func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
// localhost:8080/hello url -> 代码处理
http.HandleFunc("/hello", hello)
// 有一个地址给浏览器访问,什么都没有。404
// func ListenAndServe(addr string, handler Handler)
// localhost 本机(127.0.0.1 端口 8080
// nil默认处理器,空的 404
// 开启监听程序的代码是放在main方法的最后一行的。
http.ListenAndServe("localhost:8080", nil)
}
// 请求和响应
func hello(resp http.ResponseWriter, req *http.Request) {
// 查看一些请求信息 (/login:用户名和密码来匹配登录 /user/id 接收用户的id然后查询用户信息 )
fmt.Println(req.URL)
fmt.Println(req.Method)
fmt.Println(req.RemoteAddr)
//...
// 一般会响应一些信息给客户端 (文字、网页) resp.Write
// 响应一段文字[]byte("hello,web")
// 响应一段html代码 []byte("html代码") 网页
resp.Write([]byte("<h1 style=\"color: red;\">hello,web</h1>"))
}
```
代码来写客户端
```go
package main
import (
"fmt"
"io"
"net/http"
)
// 手写客户端访问
func main() {
/*
http.Get()
*/
// 请求方式 请求的url 接收响应结果
resp, _ := http.Get("http://localhost:8080/hello")
// 通过defer关闭连接 resp.Body 响应的主体
defer resp.Body.Close()
fmt.Println(resp.Body)
fmt.Println(resp.Status) // 200 OK
fmt.Println(resp.Header) // 响应头
// 接收具体的响应内容
buf := make([]byte, 1024)
for {
n, err := resp.Body.Read(buf)
if err != nil && err != io.EOF {
fmt.Println("读取出现了错误")
return
} else {
fmt.Println("读取完毕")
res := string(buf[:n])
fmt.Println("服务器响应的数据为:", res)
break
}
}
}
```
# 带参数的请求
客户端编写
- url的参数拼接 ?拼接 & 连接
```go
package main
import (
"fmt"
"io"
"net/http"
"net/url"
)
func main() {
// 复杂请求
urlStr := "http://127.0.0.1:8080/login" // ?
// 参数如何拼接到url上,参数封装为数据url.Values{}
data := url.Values{}
data.Set("username", "admin") // ?
data.Set("password", "123456") // ?
// 将url字符串转化为url对象,并给携带数据
urlNew, _ := url.ParseRequestURI(urlStr)
urlNew.RawQuery = data.Encode()
// http://127.0.0.1:8080/login?password=123456&username=kuangshen
// ? get的传参,多个参数之间使用 & 连接
fmt.Println(urlNew)
// 发请求,参数是一个地址
resp, _ := http.Get(urlNew.String())
defer resp.Body.Close()
// 读取resp信息,返回buf
buf, _ := io.ReadAll(resp.Body)
fmt.Println(string(buf))
}
```
后台处理代码
```go
func login(resp http.ResponseWriter, req *http.Request) {
// 模拟数据库中存在一个数据
mysqlUserData := "admin"
mysqlPwdData := "123456"
fmt.Println("接收到了login请求")
// 拿到请求中的参数
urlData := req.URL.Query()
username := urlData.Get("username")
password := urlData.Get("password")
// 登录逻辑, 将客户端发送的数据和系统数据比对实现登录业务
if username == mysqlUserData {
if password == mysqlPwdData {
resp.Write([]byte("登录成功"))
} else {
resp.Write([]byte("密码错误"))
}
} else {
resp.Write([]byte("登录失败"))
}
}
```
# 表单参数获取
html表单
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<!--表单-->
<!--http://localhost:8080/register?username=kuangshen&password=123456-->
<form action="http://localhost:8080/register" method="post">
<h2>注册</h2>
<p>用户名:<input type="text" name="username"></p>
<p>密码:<input type="password" name="password"></p>
<input type="submit" value="注册">
</form>
</body>
</html>
```
后端处理代码
```go
func register(resp http.ResponseWriter, req *http.Request) {
fmt.Println("接收到了注册请求")
// 处理表单的请求, 前端提交表单-后盾解析表单
req.ParseForm() // 解析表单
// 获取表单参数 post
username := req.PostForm.Get("username")
password := req.PostForm.Get("password")
fmt.Println(username, password)
// 很多的判断
// 短信
// 验证码
resp.Write([]byte("注册成功"))
}
```
# 要给前端响应数据 (了解即可)
- 我们给前端数据
- 页面模板要渲染数据!有很多语法,看看就好。类似于Java中的JSP 、或者PHP里面的代码
- 代码+页面融合,很乱,已经被这个时代抛弃了
- 前后端分离(我们后面都用这种方式)
1、通过代码跳转到页面 template
```go
// 通过请求,进入页面(路由) temp.Execute(resp, data)
// 通过URl进入某个页面
func findAll(resp http.ResponseWriter, req *http.Request) {
// 接收到前端的信息 /findAll, 查询全部用户
userMap := make(map[int]User)
userMap[1] = User{"KUANGSHEN", 1}
userMap[2] = User{"xiaoming", 2}
// 返回给前端页面并渲染上去
temp, _ := template.ParseFiles("./userlist.html")
data := make(map[string](map[int]User))
data["data"] = userMap
temp.Execute(resp, data)
}
```
2、前端的一些简答语法(了解即可,和php、jsp、C#差不多
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>查询用户</title>
</head>
<body>
<!--获取后端的数据 {{.data}}
遍历
{{range $k,$v := .data}}
{{end}}
-->
{{range $k,$v := .data}}
{{$k}}
{{if eq $k 1}}
{{.Name}}
{{$v}}
{{end}}
{{end}}
</body>
</html>
```
+116
View File
@@ -0,0 +1,116 @@
---
title: 修复Go时区设置
created: 2025-08-02
source: Cherry Studio
tags: []
---
`CGO`(通常写作 **cgo**)是 Go 语言提供的一个工具和机制,**允许 Go 程序调用 C 语言的函数、使用 C 的数据类型、链接 C 的库**。它是 Go 与 C 之间互操作的桥梁。
---
### 🧱 为什么需要 CGO
Go 是一门现代系统编程语言,但它不能(也不打算)完全替代 C 在底层系统中的地位。许多操作系统 API、硬件驱动、已有库(如 OpenSSL、FFmpeg、SQLite)都是用 C 写的。
所以 Go 提供了 **cgo**,让开发者可以在 Go 代码中直接调用这些 C 接口。
---
### 🔧 如何使用 CGO?简单例子
```go
package main
/*
#include <stdio.h>
*/
import "C" // 这是关键:必须导入 "C" 这个伪包
func main() {
C.puts(C.CString("Hello from C!"))
}
```
解释:
- `/* … */` 中的 C 代码会被 cgo 编译处理。
- `import "C"` 不是导入真实包,而是 **启用 cgo 模式**,并提供访问 C 的符号(函数、变量等)。
- `C.puts`:调用 C 标准库的 `puts` 函数。
- `C.CString("…")`:将 Go 字符串转为 C 风格的 `char*`(需要手动释放,在复杂场景下要注意内存管理)。
---
### 🧩 CGO 能做什么?
| 功能 | 示例 |
|------|------|
| ✅ 调用 C 函数 | `C.printf(…)`, `C.open()`, `C.sqrt()` |
| ✅ 使用 C 类型 | `C.int`, `C.struct_stat`, `C.size_t` |
| ✅ 定义 C 代码 | 在 Go 文件的注释中写 C 函数或头文件包含 |
| ✅ 链接外部 C 库 | 通过 `#cgo LDFLAGS: -lxxx` 链接 libcurl、libusb 等 |
| ✅ 传递数据 | Go 和 C 之间传递整数、字符串、结构体指针等(需注意内存模型) |
---
### ⚠️ 使用 CGO 的代价
虽然强大,但 cgo 有缺点:
| 缺点 | 说明 |
|------|------|
| 🐢 性能开销 | 跨 Go/C 边界有额外开销(栈切换、参数转换) |
| 🔗 破坏静态编译 | 使用 cgo 后,编译出的程序依赖系统的 C 库(如 glibc),可能无法跨平台或静态链接 |
| 🧨 安全风险 | C 没有内存安全,容易引入缓冲区溢出、空指针等 bug,可能导致整个 Go 程序崩溃 |
| 📦 构建复杂 | 需要 C 编译器(gcc/clang)环境,交叉编译更麻烦 |
> 因此,Go 社区常说:“**cgo should be avoided unless absolutely necessary.**”
---
### 💡 实际应用场景
1. **操作系统底层调用**
- 如你看到的例子:读取本地时区(调用 `localtime_r`
- 获取网络接口信息、设置 socket 选项等
2. **移动端开发(Android/iOS**
- Android:调用 JNI 或 Bionic C 库
- iOS:调用系统框架(通过 C 封装)
3. **集成已有 C/C++ 库**
- 比如图像处理用 OpenCV(通过 cgo 封装)
- 数据库如 SQLite`github.com/mattn/go-sqlite3` 就用了 cgo
4. **高性能计算或硬件交互**
- GPU、加密芯片、嵌入式设备驱动等
---
### ✅ 小结:什么是 CGO
> **CGO(cgo)是 Go 语言的一个功能,允许你在 Go 程序中调用 C 代码,实现与 C 的互操作。**
- ✅ 用途:访问系统底层 API、复用 C 库
- ❌ 缺点:影响性能、可移植性和安全性
- 📌 原则:能不用就不用;必须用时,尽量封装成干净接口
---
### 📚 官方文档参考
- [https://golang.org/cmd/cgo](https://golang.org/cmd/cgo)
- 关键命令:`go tool cgo file.go`(内部使用)
---
✅ 所以你在代码里看到的:
```go
/*
#include <time.h>
*/
import "C"
```
这就是在启用了 **cgo**,接着调用 C 的 `time()``localtime_r()` 函数 —— 这正是利用 C 的运行时能力来修复 Go 自身在某些平台上时区检测不准确的问题。
+1
View File
@@ -0,0 +1 @@
`GOMAXPROCS` 是 Go 语言中的一个运行时参数,用于控制 Go 程序可以**并行执行的最多操作系统线程(OS threads)数量**,即可以同时运行的 **逻辑处理器(logical processors** 的数量。
@@ -0,0 +1,165 @@
---
title: Go命令行参数初始化
created: 2025-08-02
source: Cherry Studio
tags: []
---
**Go 语言(Golang**`init` 函数,它使用了 Go 的标准库 `flag` 来处理命令行参数。
---
### ✅ 一句话总结这个语法是干什么的:
> 这段代码是在 **程序启动前** 自动运行的一段“初始化设置”,用来 **读取用户通过命令行输入的各种选项**(比如配置文件路径、是否显示版本等),并把它们保存到变量里,方便后面程序使用。
---
## 🧩 一步一步通俗解释
### 1. `func init()` 是什么?
```go
func init() {
// ...
}
```
- `init()` 是 Go 语言中一个**特殊函数**,不需要你手动调用。
- 它会在 **main函数执行之前自动运行**
- 通常用来做**初始化工作**,比如:设置参数、加载配置、注册组件等。
- 你可以有多个 `init` 函数(在不同文件里),Go 会按顺序执行它们。
🎯 你可以把它想象成:
> “程序启动前的闹钟”,它先醒,帮你把房间灯打开、水烧上,等 `main()` 起床时一切就绪了。
---
### 2. `flag.StringVar(…)` 是干嘛的?
这是 Go 的 `flag` 包提供的功能,用来 **定义命令行参数**
比如你在终端输入这样的命令:
```bash
clash -d /my/config/path -f config.yaml -v
```
那么这段代码就是告诉程序:
> “用户可能通过 `-d` 指定目录,通过 `-f` 指定配置文件,通过 `-v` 查看版本……我先把这些选项提前注册好。”
我们来看一个典型的例子:
```go
flag.StringVar(&homeDir, "d", os.Getenv("CLASH_HOME_DIR"), "set configuration directory")
```
拆开解释:
| 部分 | 说明 |
|------|------|
| `flag.StringVar` | 表示我要定义一个**字符串类型**的命令行参数 |
| `&homeDir` | 把用户输入的值存到变量 `homeDir` 里(`&` 是取地址) |
| `"d"` | 命令行短选项名:`-d /path/to/dir` |
| `os.Getenv(…)` | 默认值:如果用户没写 `-d`,就去环境变量里找 `CLASH_HOME_DIR` |
| `"set configuration directory"` | 帮助信息:别人运行 `-h` 时看到的提示 |
📌 举个生活类比:
> 就像你开空调前,允许用户设置几个选项:
> - 温度(`-t 26`
> - 模式(`-m cool`
> - 是否静音(`-s true`
>
> `init()` 函数就是在说:“我先准备好这些按钮,等用户按哪个我就知道怎么反应。”
---
### 3. 各个参数都是什么意思?(简化版解释)
| 参数 | 例子 | 作用 |
|------|------|------|
| `-d` | `-d ~/.clash` | 设置配置文件所在的“主目录” |
| `-f` | `-f config.yaml` | 指定具体的配置文件 |
| `-config` | `-config base64….` | 直接传一个加密过的配置字符串(不用文件) |
| `-ext-ui` | `-ext-ui ./web` | 换一个网页界面目录(比如自己做的UI) |
| `-ext-ctl` | `-ext-ctl 127.0.0.1:9090` | 设置API服务监听地址(给前端控制用) |
| `-ext-ctl-unix` | 用于 Linux/macOS 的高级通信方式(Unix套接字) |
| `-ext-ctl-pipe` | Windows 上的进程通信方式(命名管道) |
| `-secret` | `-secret mypassword123` | 给API加密码保护,防止别人乱访问 |
| `-m` | `-m` | 开启“地理数据模式”(可能用于精准分流) |
| `-v` | `-v` | 显示版本号,然后退出(比如你想看 `clash -v` 输出 v1.2.3 |
| `-t` | `-t` | 只测试配置文件有没有写错,不真正启动 |
---
### 4. `flag.Parse()` 是干嘛的?
```go
flag.Parse()
```
> 这是**真正开始解析命令行输入**的语句。
比如你输入了:
```bash
clash -d /mydir -f my.yaml -v
```
那么 `flag.Parse()` 就会去分析:
- `-d` 后面是 `/mydir` → 存到 `homeDir`
- `-f` 后面是 `my.yaml` → 存到 `configFile`
- `-v` 出现了 → `version` 变成 `true`
✅ 它就像是:“收卷子+批改作业”的动作,前面都是“出题”,它是“阅卷”。
---
## 🧠 总结:通俗理解整段代码
你可以把整个 `init()` 函数看作:
> “程序还没正式开始,但我已经把遥控器准备好了。
> 用户想换台(改配置)、调音量(设API)、看说明书(查版本)……
> 我都提前把按钮做个登记,等他们按下去时,我才知道怎么反应。”
---
## 🎯 举个现实例子
假设你运行这个命令:
```bash
clash -d ~/.myclash -f myconfig.yaml -secret hello123 -v
```
会发生什么?
1. `init()` 函数自动运行(不用你调)
2. 它用 `flag` 注册了各种选项(-d, -f, -secret, -v 等)
3. `flag.Parse()` 解析你输入的内容:
- `homeDir = "~/.myclash"`
- `configFile = "myconfig.yaml"`
- `secret = "hello123"`
- `version = true`
4. 然后程序继续执行 `main()`,main 函数就可以读取这些变量来做后续操作:
- 如果 `-v` 是 true → 打印版本后退出
- 否则 → 加载配置,启动代理服务……
---
## 💡 小知识补充
- `os.Getenv("XXX")`:先看有没有环境变量,有就当默认值,没有就是空。
- 比如你设置了 `export CLASH_CONFIG_FILE=my.yaml`,那不加 `-f` 也会自动读这个。
- Base64 配置字符串:有些场景不能传文件,就用 `-config XXXXX` 传一大串编码过的文本,也能还原出配置。
---
✅ 最后一句话总结:
> 这段 Go 代码就是 **为命令行程序准备“开关和旋钮”**,让用户能灵活控制程序行为,而 `init()` 就是“开机前自检+准备面板”的过程。