Initial commit
This commit is contained in:
@@ -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 结束")
|
||||
}
|
||||
```
|
||||
|
||||
### (2)if-else 语句
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
score := 90
|
||||
if score == 100 {
|
||||
fmt.Println("满分")
|
||||
} else {
|
||||
fmt.Println("不是满分")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### (3)if-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` **可选**
|
||||
|
||||
### (2)switch 省略条件
|
||||
|
||||
```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` 结构的替代。
|
||||
|
||||
### (3)fallthrough 关键字
|
||||
|
||||
`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("死循环")
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 终止循环
|
||||
|
||||
### (1)break 语句
|
||||
|
||||
用于**终止**整个循环。
|
||||
|
||||
```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`
|
||||
|
||||
### (2)continue 语句
|
||||
|
||||
用于**跳过当前循环**,继续执行下一个循环。
|
||||
|
||||
```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
|
||||
|
||||

|
||||
|
||||
**所有人必须要要全部理解的一段代码**
|
||||
|
||||
```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 ,提供了一个缓冲区,读和写都先在缓冲区中,最后再一次性读取或者写入到文件里,降低访问本地磁盘的次数。
|
||||
|
||||

|
||||
|
||||
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>
|
||||
```
|
||||
@@ -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 自身在某些平台上时区检测不准确的问题。
|
||||
@@ -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()` 就是“开机前自检+准备面板”的过程。
|
||||
Reference in New Issue
Block a user