这里写自定义目录标题
- 写在前面的话
- go语言的第几天忘了,反正其实3月初就开始学了,但是中间又断了,现在重新捡起来,慢慢学吧
- 正文开始
-
- 基础数据类型
-
- 1.基本类型
-
- 1.1布尔类型
- [1.2整数类型 - 有符号整数](#1.2整数类型 - 有符号整数)
- [1.3 整数类型 - 无符号整数](#1.3 整数类型 - 无符号整数)
- [1.4 浮点类型](#1.4 浮点类型)
- [1.5 复数类型(不常用)](#1.5 复数类型(不常用))
- [1.6 字符串类型](#1.6 字符串类型)
- [1.7 指针类型](#1.7 指针类型)
- [1.8 基本类型总结](#1.8 基本类型总结)
- 2.复合类型
-
- [2.1 数组(Array)](#2.1 数组(Array))
- [2.2 切片(Slice)](#2.2 切片(Slice))
- [2.3 映射(Map)](#2.3 映射(Map))
- 3.指针
- 控制流
- 函数和结构体
-
- 函数定义
- 闭包
- 闭包高级用法-柯里化
- 结构体和方法
- [值接收者 vs 指针接收者](#值接收者 vs 指针接收者)
写在前面的话
还记得上次写个人博客已经是6年前了,那时候,是希望能养成写博客的习惯,但是最后依然放弃了,当时学习的是python,现在又想再一次的记录一下学习情况,不是写给别人看的,仅记录自己学习情况,也算是一种自我督促吧。。。。
go语言的第几天忘了,反正其实3月初就开始学了,但是中间又断了,现在重新捡起来,慢慢学吧
正文开始
因为之前学习过python语法,所以相对来说对脑海里有个概念,遇到相似的内容,可以用python的知识去套,从而更快的了解
基础数据类型
1.基本类型
1.1布尔类型
go
// 布尔类型
var isTrue bool = true
fmt.Printf("bool: %t\n", isTrue)
1.2整数类型 - 有符号整数
go
// 整数类型 - 有符号
var age int = 33
fmt.Printf("int: %d\n", age)
// 不同长度的整数
var count int8 = 127 // 8位整数,范围: -128 到 127
var number int16 = 30000 // 16位整数,范围: -32768 到 32767
var data int32 = 2000000000 // 32位整数,范围: -2^31 到 2^31-1
var bigNumber int64 = 9223372036854775807 // 64位整数,范围: -2^63 到 2^63-1
fmt.Printf("int8: %d, int16: %d, int32: %d, int64: %d\n", count, number, data, bigNumber)
1.3 整数类型 - 无符号整数
go
// 无符号整数
var u8 uint8 = 255 // 0 到 255
var u16 uint16 = 65535 // 0 到 65535
var u32 uint32 = 4294967295 // 0 到 2^32-1
var u64 uint64 = 18446744073709551615 // 0 to 2^64-1
fmt.Printf("uint8: %d, uint16: %d, uint32: %d, uint64: %d\n", u8, u16, u32, u64)
// 类型别名
var b byte = 65 // byte 是 uint8 的别名
var r rune = '中' // rune 是 int32 的别名
fmt.Printf("byte: %d (%c), rune: %d (%c)\n", b, b, r, r)
// 显示类型占用的内存大小
fmt.Printf("\n类型大小:\n")
fmt.Printf("int8 size: %d bytes\n", unsafe.Sizeof(count))
fmt.Printf("int16 size: %d bytes\n", unsafe.Sizeof(number))
fmt.Printf("int32 size: %d bytes\n", unsafe.Sizeof(data))
fmt.Printf("int64 size: %d bytes\n", unsafe.Sizeof(bigNumber))
fmt.Printf("bool size: %d bytes\n", unsafe.Sizeof(isTrue))
对比总结:
| 类型 | 位数 | 符号位 | 值域范围 | 总数量 |
|---|---|---|---|---|
| int8 | 8 | 有 | -128 到 127 | 256 |
| uint8 (byte) | 8 | 无 | 0 到 255 | 256 |
1.4 浮点类型
go
// 浮点型
var price float32 = 99.99
var pi float64 = 3.14159265359
fmt.Printf("\nfloat32: %.2f, float64: %.10f\n", price, pi)
fmt.Printf("float32 size: %d bytes, float64 size: %d bytes\n", unsafe.Sizeof(price), unsafe.Sizeof(pi))
重要说明:Go语言没有 double 类型
如果你来自C/C++/Java等语言,可能会寻找 double 类型,但在Go中:
- ❌ 没有 double 类型
- ✅ 使用 float64 替代 double
- ✅ float32 对应 C/C++ 的 float
- ✅ float64 对应 C/C++ 的 double
1.5 复数类型(不常用)
什么是复数? 编程中基本很少用,了解即可 复数是数学中的概念,由一个实部 和一个虚部组成,形式为:a + bi
- 实部(Real Part):a,是实数部分(真实存在的数字)
- 虚部(Imaginary Part):b,是虚数部分(带有 i 的系数)
- i:虚数单位,满足 i² = -1
生活中的类比: 想象你在一个坐标系中:
- 实部:X轴上的位置(左右移动)
- 虚部:Y轴上的位置(上下移动)
- 复数:表示平面上的一个点
Go语言中的复数类型:
go
var c1 complex64 = 1 + 2i // complex64, 实部和虚部都是float32
var c2 complex128 = 3.14 + 6.28i // complex128, 实部和虚部都是float64
// 使用complex函数创建
c3 := complex(1.0, 2.0) // 实部1.0,虚部2.0
// 获取实部和虚部
realPart := real(c1) // 获取实部:1
imagPart := imag(c1) // 获取虚部:2
复数的基本概念详解:
1. 实部(Real Part)
- 实部是复数的"实数"部分
- 表示实际存在的数值
- 可以用 real() 函数获取
2. 虚部(Imaginary Part)
- 虚部是复数的"虚数"部分
- 是虚数单位 i 的系数
- 可以用 imag() 函数获取
- 注意:imag() 返回的是系数,不包含 i
示例说明:
go
c := 3 + 4i // 这是一个复数
fmt.Println(real(c)) // 输出:3(实部)
fmt.Println(imag(c)) // 输出:4(虚部,注意是4,不是4i)
// 完整形式:3 + 4i
// 实部 = 3
// 虚部 = 4(系数)
复数运算示例:
go
// 创建复数
c1 := 3 + 4i // 实部3,虚部4
c2 := 1 + 2i // 实部1,虚部2
// 加法:(3+4i) + (1+2i) = 4 + 6i
sum := c1 + c2
fmt.Println(sum) // 输出:(4+6i)
fmt.Println(real(sum)) // 输出:4
fmt.Println(imag(sum)) // 输出:6
// 乘法:(3+4i) × (1+2i) = 3 + 6i + 4i + 8i²
// = 3 + 10i - 8 = -5 + 10i(因为i² = -1)
product := c1 * c2
fmt.Println(product) // 输出:(-5+10i)
// 求模(绝对值)
// |3+4i| = √(3² + 4²) = √(9 + 16) = 5
// Go中没有内置函数,需要自己计算
modulus := math.Sqrt(real(c1)*real(c1) + imag(c1)*imag(c1))
fmt.Println(modulus) // 输出:5
实际应用场景:
信号处理 :音频、图像处理
**工程计算:**电路分析、机械振动
**科学计算:**量子力学、电磁学
**图形学:**旋转、缩放等变换
注意事项:
在日常编程中,复数使用频率不高
主要用在科学计算和特定工程领域
如果不涉及这些领域,可以先了解,不需要深入掌握
1.6 字符串类型
go
// 字符串
var name string = "Golang"
var greeting = "Hello World" // 类型推断
fmt.Printf("string: %s\n", name)
fmt.Printf("类型推断: %s\n", greeting)
// 字符串操作
str := "Hello 世界"
fmt.Printf("字符串长度: %d (字节数)\n", len(str)) // 注意:len返回字节数,不是字符数
firstByte := str[0]
fmt.Printf("第一个字节: %d ('%c')\n", firstByte, firstByte)
// 原始字符串字面量
raw := `这是
一个多行
的字符串`
fmt.Println("\n原始字符串:")
fmt.Println(raw)
// 字符(使用rune)
var char rune = '中'
fmt.Printf("rune: %c\n", char)
1.7 指针类型
go
var p *int // 指向int类型的指针
var x int = 10
p = &x // 获取x的地址
// 零值
var ptr *int // nil(指针的零值是nil)
1.8 基本类型总结
| 类型 | 说明 | 值域 | 零值 |
|---|---|---|---|
| bool | 布尔类型 | true, false | false |
| int8 | 8位有符号整数 | -128 到 127 | 0 |
| int16 | 16位有符号整数 | -32,768 到 32,767 | 0 |
| int32 (rune) | 32位有符号整数 | -2^31 到 2^31-1 | 0 |
| int64 | 64位有符号整数 | -2^63 到 2^63-1 | 0 |
| int | 平台相关有符号整数 | -2^31 到 2^31-1 或 -2^63 到 2^63-1 | 0 |
| uint8 (byte) | 8位无符号整数 | 0 到 255 | 0 |
| uint16 | 16位无符号整数 | 0 到 65,535 | 0 |
| uint32 | 32位无符号整数 | 0 到 4,294,967,295 | 0 |
| uint64 | 64位无符号整数 | 0 到 2^64-1 | 0 |
| uint | 平台相关无符号整数 | 0 到 2^32-1 或 2^64-1 | 0 |
| float32 | 32位浮点数 | IEEE 754 单精度 | 0.0 |
| float64 | 64位浮点数 | IEEE 754 双精度 | 0.0 |
| complex64 | 64位复数 | float32 + float32*i | 0+0i |
| complex128 | 128位复数 | float64 + float64*i | 0+0i |
| string | 字符串 | UTF-8字节序列 | "" (空字符串) |
| *T | 指针 | 指向类型T的地址 | nil |
类型转换:
Go语言不允许隐式类型转换,必须显式转换。这与其他语言(如Java)不同。
Go语言(必须显式转换):
go
// ❌ Go不允许:隐式类型转换
var x int = 10
// var y float64 = x // 编译错误!不能隐式转换
// ✅ Go要求:显式类型转换
var x int = 10
var y float64 = float64(x) // 必须显式转换:int转float64
var a float64 = 3.14
var b int = int(a) // float64转int(会截断小数部分)
var str string = "123"
var num, _ = strconv.Atoi(str) // string转int需要使用strconv包
Java语言(允许隐式转换)示例:
在Java中,某些类型转换是隐式的,编译器会自动进行转换:
java
// ✅ Java允许:int自动隐式转换为double
int x = 10;
double y = x; // 自动转换:int → double(无需显式转换)
System.out.println(y); // 输出:10.0
// ✅ Java允许:short隐式转换为int
short s = 100;
int i = s; // 自动转换:short → int
// ✅ Java允许:float隐式转换为double
float f = 3.14f;
double d = f; // 自动转换:float → double
// ✅ Java允许:char隐式转换为int
char c = 'A';
int code = c; // 自动转换:char → int,输出:65
2.复合类型
2.1 数组(Array)
数组的核心特征:
- 固定长度:一旦声明,长度不能改变
- 值类型:数组作为值传递时,会复制整个数组的内容
- 同构性:数组中的元素必须是相同类型
- 索引访问:使用从0开始的整数索引访问元素
代码示例:
go
// 声明数组
var arr [5]int // 声明长度为5的整数数组
arr := [5]int{1, 2, 3, 4, 5} // 初始化的数组
arr2 := [...]int{1, 2, 3} // 自动推断长度
// 数组特点
// - 长度固定
// - 值类型(复制会复制整个数组)
// - 索引从0开始
2.2 切片(Slice)
类似于pyhton的...split,但是有点区别
代码示例:
go
// 声明切片
var slice []int // 空切片
slice := []int{1, 2, 3, 4, 5} // 初始化切片
slice2 := make([]int, 5) // 使用make创建切片
// 切片操作
slice = append(slice, 6) // 追加元素
slice = slice[1:3] // 切片截取
length := len(slice) // 获取长度
capacity := cap(slice) // 获取容量
// 切片的本质
// - 对底层数组的引用
// - 包含长度和容量信息
// - 自动扩容机制
切片的自动扩容机制详解:
切片的容量(capacity)是指底层数组可以容纳的元素个数。当使用append向切片添加元素时,如果容量不足,Go会自动扩容。
扩容触发条件:
go
slice := make([]int, 0, 3) // 长度0,容量3
fmt.Printf("初始: len=%d, cap=%d\n", len(slice), cap(slice))
slice = append(slice, 1) // len=1, cap=3(未触发扩容)
slice = append(slice, 2) // len=2, cap=3(未触发扩容)
slice = append(slice, 3) // len=3, cap=3(未触发扩容)
slice = append(slice, 4) // len=4, cap=6(触发扩容!)
fmt.Printf("扩容后: len=%d, cap=%d\n", len(slice), cap(slice))
扩容的内部机制:
分配新数组:当容量不足时,Go会分配一个新的更大的底层数组
复制数据:将旧数组的所有元素复制到新数组
更新切片:切片指向新的底层数组,长度增加,容量扩大
旧数组回收:旧的底层数组由垃圾回收器回收
性能优化建议:
1、预估容量:如果知道大概需要多少元素,使用make([]T, 0, capacity)预设容量
go
// ❌ 性能差:频繁扩容
var s []int
for i := 0; i < 1000; i++ {
s = append(s, i) // 可能触发多次扩容
}
// ✅ 性能好:预设容量
s := make([]int, 0, 1000)
for i := 0; i < 1000; i++ {
s = append(s, i) // 只扩容一次(或0次)
}
2、一次性追加多个元素:
go
s = append(s, 1, 2, 3, 4, 5) // 一次性追加,可能只扩容一次
2.3 映射(Map)
什么是映射(Map)?
映射(Map)是Go语言中的键值对(key-value)集合,类似于其他语言中的字典(dictionary)或哈希表(hash table)。它提供了一种快速查找数据的方式,通过唯一的键来访问对应的值。
映射的核心特征:
- 键值对存储:每个元素都是键(key)和值(value)的配对
- 引用类型:映射是引用类型,传递时不会复制所有数据
- 无序性:Go语言的映射是无序的,遍历顺序不固定
- 快速查找:通过键快速访问值,时间复杂度接近O(1)
- 键唯一性:同一个映射中,每个键只能出现一次
代码示例
go
// 声明映射
var m map[string]int // 使用前需要初始化
m := make(map[string]int) // 使用make初始化
m2 := map[string]int{ // 直接初始化
"apple": 5,
"banana": 3,
}
// 映射操作
m["apple"] = 10 // 添加或修改
value := m["apple"] // 读取
delete(m, "banana") // 删除
value, ok := m["orange"] // 检查key是否存在
// 遍历映射
for key, value := range m {
fmt.Printf("%s: %d\n", key, value)
}
3.指针
指针emmmmm,基础了解就这样,就是获取地址,解地址,
看一下应该就懂了,就不多写了
以后用到复杂的指针知识再慢慢学。
go
package main
import "fmt"
func zhizhen() {
fmt.Println("====指针====")
//赋值
x := 10
fmt.Println("x:", x)
//&获取指针地址
p := &x
//打印出来的是地址值
fmt.Println("p:", p)
//加上*,可以获取指针指向的值
fmt.Println("*p:", *p)
//使用*获取指针指向的值,并进行更改
*p = 20
fmt.Println("修改后的值:", x)
}
func main() {
zhizhen()
}
控制流
if/else、switch、for循环
if/else,这个很好理解
Switch,也好理解,一看就会的
for,这个for循环,python语法就是for i,j in range() 一个道理,光是遍历值,你不知道这个值在哪儿,所以加个index,很简单
go
package main
import "fmt"
func kongzhiliu() {
fmt.Println("=======控制流======")
age := 210
//就是if/else语句
if age >= 100 {
fmt.Println("恭喜你,已经百岁")
} else {
fmt.Println("请继续加油")
}
//直接在控制流里面初始化语句--num
if num := 10; num > 5 {
fmt.Println("num大于5")
}
//来个多条件的,用slice语句
scores := []int{80, 90, 100, 60, 50, 70}
for index, score := range scores {
fmt.Printf("索引%d :%d\n", index, score)
if score >= 80 {
fmt.Printf("80还行\n")
} else if score >= 90 {
fmt.Printf("90不错\n")
} else if score < 70 {
fmt.Printf("请重新考试\n")
} else {
fmt.Printf("你没了\n")
}
}
fmt.Println("=======switch语句======")
for index, day := range scores {
fmt.Printf("索引%d: %d\n", index, day)
switch day {
case 80:
fmt.Println("大锤80")
case 90:
fmt.Println("90分厉害")
case 100:
fmt.Println("满分100!")
case 70:
fmt.Println("70太差了")
case 60, 50:
fmt.Println("60以下都不及格")
default:
fmt.Printf("分数 %d: 未知\n", day)
}
}
}
func main() {
kongzhiliu()
}
defer语句
defer 是Go语言中用于延迟执行函数调用的关键字。理解 defer 的执行时机非常重要。
defer的延迟执行时机
核心概念:defer延迟到函数返回之前执行
但更准确地说,defer 语句会在以下三种情况下执行:
- 函数正常返回前:执行完 return 语句之后,在函数真正返回之前
- 发生panic时:即使函数因为 panic 崩溃,defer 也会执行
- 函数执行到最后一行时:函数自然结束时(没有明确的 return)
执行顺序
go
函数开始
↓
执行普通语句
↓
遇到 defer(注册到延迟执行栈,但不执行)
↓
继续执行其他语句
↓
函数准备返回(return/panic/结束)
↓
【关键时机】开始执行 defer 语句(按 LIFO 顺序)
↓
函数真正返回
详细示例说明
- defer 在 return 之后执行
go
func example() int {
defer fmt.Println("张三") // 会在 return 之后执行
fmt.Println("李四")
return 666 // 先执行 return
}
func main() {
result := example()
fmt.Println("返回值:", result)
}
// 输出:
// 李四
// 张三
// 返回值: 666
- defer 在 panic 之后也会执行
go
func panicExample() {
defer fmt.Println("最后执行,扫尾工作") // 即使 panic 也会执行
fmt.Println("开始执行任务")
panic("发生错误") // 触发 panic
fmt.Println("这行不会执行")
}
// 输出:
// 开始执行任务
// 最后执行,扫尾工作 ← panic 后也会执行
// panic: 发生错误
- defer 执行顺序:LIFO(后进先出,Last In First Out)
go
func multipleDefer() {
fmt.Println("函数开始")
defer fmt.Println("1号defer") // 第1个注册,最后执行
defer fmt.Println("2号defer") // 第2个注册,倒数第2执行
defer fmt.Println("3号defer") // 第3个注册,第1个执行
fmt.Println("函数结束")
}
// 输出:
// 函数开始
// 函数结束
// 3号defer ← 最后注册的,最先执行
// 2号defer
// 1号defer ← 最先注册的,最后执行
- defer 捕获变量值的时机(立即计算)
defer 语句中的参数值会在 defer 语句执行时立即计算并捕获,而不是在 defer 真正执行时计算。
go
func deferValue() {
i := 0
defer fmt.Println("defer1:", i) // i的值是0(立即捕获)
i++
defer fmt.Println("defer2:", i) // i的值是1(立即捕获)
i++
fmt.Println("函数内:", i) // 输出:函数内: 2
return
}
// 输出:
// 函数内: 2
// defer2: 1 ← defer2 最后注册,最先执行(但值是1)
// defer1: 0 ← defer1 先注册,最后执行(值是0)
如果需要捕获最终值,使用闭包:
go
func deferClosure() {
i := 0
defer func() {
fmt.Println("defer闭包:", i) // 捕获变量本身,而不是值
}()
i++
i++
fmt.Println("函数内:", i) // 输出:函数内: 2
return
}
// 输出:
// 函数内: 2
// defer闭包: 2 ← 闭包捕获的是变量的引用,所以是最终值
defer的常见用途
- 释放资源(最常见)
比如在进行文件或者数据库访问的时候,访问第二步就使用defer进行关闭,这样即便发生错误,也会释放资源 - 解锁互斥锁
对某些需要加锁的操作,在上锁的后面直接使用defer解锁,同上即便发生错误,也会释放锁 - 恢复panic(配合recover使用)
通常情况下,发生panic报错后(比如类型断言失败、数组越界、空指针解引用、除0,或者手动触发panic等错误),程序就会终止运行,此时如果通过recover函数捕获报错,可以让程序继续执行,后面会详细降解 recover 函数 - 性能测量
类似于...python中的计时,比如任务开始,打个时间戳,任务结束,再打个时间戳,然后计算任务的执行时长之类的,但是这种情况一旦中间程序报错,就无法获取到结束时间,用panic就可以规避。
python
import time
def slow_operation():
start = time.time()
print("假装此处是一个任务在执行") # 这行会执行
# 出错了!
raise Exception("出错了!")
end = time.time() # ❌ 这行不会执行
print(f"耗时:{end - start:.2f}秒") # ❌ 不会打印
slow_operation()
# 输出:
# Exception: 出错了!
# 结束时间不会打印 ❌
defer总结
- defer 不是"异步"执行,而是在函数返回流程中的特定时机执行
- defer 语句注册到延迟执行栈时,就会计算参数值(立即计算值)
- 如果需要捕获变量的最终值,使用闭包而不是直接传递参数
- defer 保证了资源清理代码一定会执行,即使发生 panic
panic和recover
panic有点像java中的RuntimeException,python中的raise Exception(),
panic和recover的组合使用,就好比
java中的try-catch
python中的try-except
手动触发panic
go
// panic触发panic
func riskyOperation() {
panic("something went wrong")
}
// panic会导致程序崩溃
// 输出:
// panic: something went wrong
// goroutine 1 [running]:
// main.riskyOperation()
// ...
recover捕获panic
go
// recover捕获panic
func safeOperation() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("捕获panic: %v\n", r)
}
}()
panic("发生错误")
}
// recover只能在defer中使用
func main() {
safeOperation() // 输出:捕获panic: 发生错误
fmt.Println("程序继续执行")
}
函数和结构体
函数定义
过于基础,懒得写了...记个通用格式吧:
go
func 函数名 [泛型] (参数 名称 类型,...) (返回值 名称 类型,...) {
// 函数体
return 返回值
}
核心要点:
✅ func 开头
✅ 函数名必须有大写字母才能导出(对外可见)
✅ 参数必须有类型
✅ 返回值必须有类型
✅ 多个参数/返回值用逗号分隔
✅ 同类型可以合并简写
通用的函数大概就这样,然后有一些变体之类的,但万变不离其宗,大概就这样的,了解基础的就够了,能看懂,后续再精进吧
闭包
什么是闭包?
闭包(Closure) 是一个函数以及与其相关的引用环境组合而成的实体。更简单地说,闭包就是一个函数,它可以访问并"记住"其外部作用域中的变量,即使外部函数已经执行完毕。
闭包的核心特征:
- 函数嵌套:闭包是定义在函数内部的函数
- 捕获外部变量:内部函数可以访问外部函数的局部变量
- 变量持久化:即使外部函数执行完毕,内部函数仍然可以访问这些变量
- 每次调用独立:每次调用外部函数都会创建一个新的闭包实例
闭包的工作原理
没有闭包的普通函数:
go
func normalFunction() {
count := 0
count++
fmt.Println(count) // 每次调用都是1
}
normalFunction() // 输出:1
normalFunction() // 输出:1
// 每次调用时,count 都会重新创建并初始化为0
使用闭包:
go
// 外部函数
func makeCounter() func() int {
count := 0 // 这个变量被内部函数"捕获"了
// 内部函数(闭包)
return func() int {
count++ // 访问并修改外部变量
return count
}
}
// 使用闭包
counter := makeCounter() // 创建闭包实例
fmt.Println(counter()) // 1 - count从0变成1
fmt.Println(counter()) // 2 - count继续增加
fmt.Println(counter()) // 3 - count被"记住"了
// 创建另一个独立的闭包实例
counter2 := makeCounter() // 新的闭包,新的count
fmt.Println(counter2()) // 1 - 独立的计数器
fmt.Println(counter()) // 4 - 第一个计数器继续
个人的一句话总结:闭包,就是一个能记住创建函数时的环境(外部变量) 的一个函数
闭包 = 函数 + 函数可以访问的外部变量
特点 1:函数可以访问外部变量
特点 2:外部变量在函数调用之间保持状态
特点 3:即使外部函数返回,内部函数仍能访问外部变量
这个不特殊,java和python都有这个概念,可能叫法不太一样罢了;
闭包高级用法-柯里化
柯里化在go语言中,挺重要的
什么是柯里化? 柯里化是一种将接受多个参数的函数转换为一系列接受单个参数的函数的技术。
换句话说:柯里化 = 一种编程技术(多参数转单参数链),柯里化 通常使用 嵌套函数 来实现
简单的柯里化
go
// ==================== 示例1: 简单的柯里化 ====================
// 普通函数:add(a, b int) int
// 柯里化后:curryAdd(a int) func(int) int
// 普通加法函数
func add(a, b int) int {
return a + b
}
// 柯里化版本:先传入第一个参数,返回一个函数
func curryAdd(x int) func(int) int {
// 闭包捕获了 x
return func(y int) int {
return x + y
}
}
// 普通函数调用
fmt.Printf("普通函数: add(5, 3) = %d\n", add(5, 3))
// 柯里化调用
addFive := curryAdd(5) // 先传入第一个参数,返回一个函数 f(x) = 5 + y
fmt.Printf("柯里化: addFive(3) = %d\n", addFive(3)) // 5 + 3
fmt.Printf("柯里化: addFive(10) = %d\n", addFive(10)) // 5 + 10
resultt := curryAdd(3)(5) // 3+y 3+5 8
fmt.Println(resultt)
// 可以重复使用同一个柯里化函数
fmt.Printf("柯里化: addFive(100) = %d\n", addFive(100)) // 5 + 100
fmt.Println()
在python中,类似柯里化的代码(看起来比):
python
# ✅ 柯里化(使用了嵌套函数)
def curry_add(x):
return lambda y: x + y
result = curry_add(5)(3) # 8
# ✅ 柯里化(用 lambda,没有显式嵌套)
curry_add = lambda x: lambda y: x + y
result = curry_add(5)(3) # 8
多参数的柯里化
go
// ==================== 示例2: 多参数柯里化 ====================
// 普通函数:multiply(a, b, c int) int
// 柯里化后:curryMultiply(a int) func(int) func(int) int
func multiply(a, b, c int) int {
return a * b * c
}
// 多参数柯里化:逐层返回函数
func curryMultiply(a int) func(int) func(int) int {
return func(b int) func(int) int {
return func(c int) int {
return a * b * c
}
}
}
fmt.Println("--- 示例2: 多参数柯里化 ---")
fmt.Printf("普通函数: multiply(2, 3, 4) = %d\n", multiply(2, 3, 4))
// 柯里化调用:逐层传入参数
multiplyBy2 := curryMultiply(2) // 2 * b * c
multiplyBy2And3 := multiplyBy2(3) // 2 * 3 * c
result := multiplyBy2And3(4) // 2 * 3 * 4
fmt.Printf("柯里化: curryMultiply(2)(3)(4) = %d\n", result)
// 也可以链式调用
result2 := curryMultiply(2)(3)(4)
fmt.Printf("链式调用: curryMultiply(2)(3)(4) = %d\n", result2)
fmt.Println()
柯里化之配置化函数
go
// ==================== 示例3: 实际应用 - 配置化函数 ====================
// 创建一个带有前缀的日志函数
func createLogger(prefix string) func(string) {
return func(message string) {
fmt.Printf("[%s] %s\n", prefix, message)
}
}
// 创建一个带有乘法器的计算函数
func createMultiplier(factor int) func(int) int {
return func(value int) int {
return value * factor
}
}
fmt.Println("--- 示例3: 实际应用 - 配置化函数 ---")
// 创建不同前缀的日志函数
infoLog := createLogger("INFO")
warnLog := createLogger("WARN")
errorLog := createLogger("ERROR")
infoLog("应用程序启动")
warnLog("内存使用率较高")
errorLog("数据库连接失败")
fmt.Println()
// 创建不同的乘法器
double := createMultiplier(2) // 2 * x
triple := createMultiplier(3) // 3 * x
fmt.Printf("double(5) = %d\n", double(5)) // 2 * 5
fmt.Printf("triple(5) = %d\n", triple(5)) // 3 * 5
fmt.Println()
柯里化之字符串处理函数
go
// ==================== 示例4: 字符串处理柯里化 ====================
// 创建一个字符串连接函数(固定前缀)
func stringWithPrefix(prefix string) func(string) string {
return func(s string) string {
return prefix + s
}
}
// 创建一个字符串重复函数
func repeatString(n int) func(string) string {
return func(s string) string {
return strings.Repeat(s, n)
}
}
fmt.Println("--- 示例4: 字符串处理柯里化 ---")
withPrefix := stringWithPrefix("Hello, ") // hello + ''
fmt.Println(withPrefix("World"))
fmt.Println(withPrefix("Golang"))
repeat3Times := repeatString(3)
fmt.Printf("repeat3Times(\"Go\") = %s\n", repeat3Times("Go"))
fmt.Println()
柯里化之条件判断
go
// ==================== 示例5: 条件判断柯里化 ====================
// 创建一个比较函数
func greaterThan(threshold int) func(int) bool {
return func(value int) bool {
return value > threshold
}
}
// 创建一个范围检查函数
func inRange(min, max int) func(int) bool {
return func(value int) bool {
return value >= min && value <= max
}
}
fmt.Println("--- 示例5: 条件判断柯里化 ---")
isGreaterThan10 := greaterThan(10)
fmt.Printf("isGreaterThan10(5) = %v\n", isGreaterThan10(5))
fmt.Printf("isGreaterThan10(15) = %v\n", isGreaterThan10(15))
isInValidRange := inRange(1, 100)
fmt.Printf("isInValidRange(50) = %v\n", isInValidRange(50))
fmt.Printf("isInValidRange(150) = %v\n", isInValidRange(150))
fmt.Println()
柯里化之部分应用
go
// ==================== 示例6: 部分应用(Partial Application) ====================
// 注意:部分应用和柯里化略有不同
// - 柯里化:每次只接受一个参数
// - 部分应用:一次可以传入多个参数
// 部分应用示例
func partialAdd(a, b int) func(int) int {
sum := a + b // 先计算前两个参数的和
return func(c int) int {
return sum + c
}
}
fmt.Println("--- 示例6: 部分应用 vs 柯里化 ---")
partialAdd10 := partialAdd(5, 5) // 先计算 5+5=10
fmt.Printf("partialAdd(5,5)(3) = %d\n", partialAdd10(3))
fmt.Println()
柯里化小结
柯里化使用场景很多,可以问AI有哪些常用场景
go
fmt.Println("========== 柯里化的优势 ==========")
fmt.Println("1. 函数复用:可以创建预配置的函数")
fmt.Println("2. 代码组织:将复杂函数拆分为多个步骤")
fmt.Println("3. 灵活性:可以按需组合不同的函数")
fmt.Println("4. 可读性:代码更清晰,意图更明确")
结构体和方法
标准格式如下
go
// ✅ 结构体:有字段(数据)+ 方法(实现)
type Person struct {
Name string // ← 字段(存储数据)
Age int // ← 字段
}
// 方法(提供具体实现)
func (p Person) SayHello() {
fmt.Printf("你好,我是%s\n", p.Name)
}
结构体和方法
go
// 定义结构体
type Person struct {
Name string
Age int
}
// 初始化结构体
p1 := Person{
Name: "Alice",
Age: 25,
}
// 简短初始化
p2 := Person{"Bob", 30}
// 部分初始化
p3 := Person{Name: "Charlie"}
// 定义方法(值接收者)
func (p Person) GetInfo() string {
return fmt.Sprintf("%s is %d years old", p.Name, p.Age)
}
// 定义方法(指针接收者,可以修改结构体)
func (p *Person) IncrementAge() {
p.Age++
}
// 使用
person := Person{Name: "Alice", Age: 25}
fmt.Println(person.GetInfo())
person.IncrementAge()
fmt.Println(person.Age)
// 嵌入结构体(类似继承)
type Employee struct {
Person // 匿名字段
ID string
}
employee := Employee{
Person: Person{Name: "David", Age: 28},
ID: "E001",
}
fmt.Println(employee.Name) // 可以直接访问嵌入的字段
值接收者 vs 指针接收者
这是结构体方法中非常重要的概念,需要深入理解。
go
// 值接收者:接收结构体的副本
func (p Person) GetInfo() string {
return fmt.Sprintf("%s is %d years old", p.Name, p.Age)
}
// ❌ 值接收者无法修改原始结构体
func (p Person) IncrementAgeWrong() {
p.Age++ // 只修改了副本,不会影响原始结构体
}
// 指针接收者:接收结构体的指针
func (p *Person) IncrementAge() {
p.Age++ // 可以修改原始结构体
}
func (p *Person) ChangeName(name string) {
p.Name = name // 可以修改原始结构体
}
// 使用对比:
person := Person{Name: "Alice", Age: 25}
// 值接收者
fmt.Println(person.GetInfo()) // 正常工作,只读取数据
// 值接收者尝试修改(不会成功)
person.IncrementAgeWrong()
fmt.Println(person.Age) // 还是 25,没有改变
// 指针接收者修改(会成功)
person.IncrementAge()
fmt.Println(person.Age) // 变成 26,修改成功
person.ChangeName("Alice Smith")
fmt.Println(person.Name) // "Alice Smith",修改成功
以上是go语言的基础部分...学了大概好多天...也不知道几天