-- 种子数据:高质量测试文章 -- 共 17 篇技术文章,覆盖 15+ 种编程语言 -- 先创建测试用户(如果不存在) INSERT INTO users (username, email, password_hash, role) VALUES ('testuser', 'test@example.com', '$argon2id$v=19$m=65536,t=3,p=4$testtesttesttesttesttesttest$testtesttesttesttesttesttesttesttesttest', 'admin') ON CONFLICT (username) DO NOTHING; -- 插入测试文章 INSERT INTO posts (author_id, title, slug, summary, content_md, content_html, status, published_at, created_at, updated_at) VALUES ( 1, 'Rust 所有权与生命周期:深入理解内存安全', 'rust-ownership-lifetime', '本文深入探讨 Rust 的所有权系统、借用规则和生命周期注解,帮助你理解 Rust 如何在不使用垃圾回收器的情况下保证内存安全。', $doc$ # Rust 所有权与生命周期:深入理解内存安全 Rust 是一门专注于安全、并发和性能的系统级编程语言。自 2010 年 Mozilla 研究院发起以来,Rust 凭借其独特的内存安全保证机制,逐渐在系统编程领域占据重要地位。与 C/C++ 等语言不同,Rust 不需要依赖垃圾回收器(Garbage Collector),而是在编译期通过**所有权系统**(Ownership System)来确保内存安全。这一设计使得 Rust 程序既拥有接近 C 语言的性能,又避免了手动内存管理带来的种种安全隐患。 本文将系统性地介绍 Rust 的所有权、借用和生命周期三大核心概念,帮助你建立对 Rust 内存模型的深入理解。 ## 为什么需要所有权? 在传统的系统编程语言中,内存管理通常面临两种选择:手动管理(如 C/C++)或自动垃圾回收(如 Java、Go)。手动管理虽然性能优秀,但容易引发内存泄漏、悬垂指针、双重释放等严重问题;垃圾回收虽然安全,但会带来运行时开销和不可预测的暂停时间。 Rust 提出了第三种方案:**在编译期通过所有权规则静态验证内存访问的安全性**。这意味着所有内存安全检查都在编译阶段完成,运行时几乎零开销。 ## 所有权的三条基本规则 Rust 的所有权系统建立在三条简单但强大的规则之上: 1. **每个值在任意时刻都有且仅有一个所有者(owner)** 2. **当所有者离开作用域时,其拥有的值会被自动释放** 3. **所有权可以通过移动(move)或复制(copy)在不同变量之间转移** ### 所有权转移示例 ```rust fn main() { let s1 = String::from("hello"); let s2 = s1; // s1 的所有权转移给 s2 // println!("{}", s1); // 编译错误!s1 已无效 println!("{}", s2); // 正确,s2 拥有该字符串 } ``` 在上述代码中,`String::from("hello")` 在堆上分配了一块内存。当执行 `let s2 = s1` 时,Rust 执行的是**移动语义**(move semantics)—— `s1` 将堆内存的所有权转移给 `s2`,之后 `s1` 不再有效。这种设计避免了双重释放问题。 ### Copy trait 与 Clone trait 并非所有类型都会执行移动语义。对于栈上分配的简单类型(如整数、浮点数、布尔值等),Rust 会自动实现 `Copy` trait,这意味着赋值时执行的是**按位复制**而非所有权转移: ```rust fn main() { let x = 5; let y = x; // i32 实现了 Copy,x 仍然有效 println!("x = {}, y = {}", x, y); // 完全正确 } ``` 对于需要深拷贝的复杂类型,可以使用 `clone()` 方法: ```rust fn main() { let s1 = String::from("hello"); let s2 = s1.clone(); // 显式深拷贝 println!("s1 = {}, s2 = {}", s1, s2); // 两者都有效 } ``` ## 借用(Borrowing):安全地共享数据 虽然所有权转移解决了内存安全问题,但在实际编程中,我们经常需要临时访问某个值而不获取其所有权。Rust 提供了**借用**机制来解决这个问题。 借用分为两种类型:不可变借用和可变借用。 ### 不可变借用(Immutable Borrowing) 不可变借用允许你读取数据但不修改它。在同一作用域内,可以创建多个不可变引用: ```rust fn calculate_length(s: &String) -> usize { s.len() } fn main() { let s = String::from("hello"); let len = calculate_length(&s); // 传递不可变引用 println!("'{}' 的长度是 {}.", s, len); // s 仍然有效 } ``` `&s` 语法创建了一个指向 `s` 的引用,但不会转移所有权。函数参数 `&String` 表示接受一个不可变引用。当函数返回时,引用被销毁,但原始值 `s` 仍然有效。 ### 可变借用(Mutable Borrowing) 当你需要修改借用的数据时,可以使用可变引用: ```rust fn change(some_string: &mut String) { some_string.push_str(", world"); } fn main() { let mut s = String::from("hello"); change(&mut s); println!("{}", s); // 输出: hello, world } ``` ### 借用规则 Rust 的借用检查器强制执行以下规则,这些规则在编译期就能防止数据竞争: 1. **在任意给定时刻,只能有一个可变引用** 或 **任意数量的不可变引用** 2. **引用必须始终有效**(不能指向已释放的内存) ```rust fn main() { let mut s = String::from("hello"); let r1 = &s; // 不可变引用 let r2 = &s; // 另一个不可变引用(允许) // let r3 = &mut s; // 编译错误!不可与可变引用共存 println!("{} and {}", r1, r2); // r1 和 r2 在此之后不再使用 let r3 = &mut s; // 现在可以创建可变引用 r3.push_str("!"); println!("{}", r3); } ``` Rust 编译器使用**非词法生命周期**(Non-Lexical Lifetimes, NLL)来精确追踪引用的使用范围,允许在上面的例子中,当 `r1` 和 `r2` 不再被使用时创建 `r3`。 ## 生命周期(Lifetime):引用的有效期 生命周期是 Rust 编译器用来确保引用始终指向有效数据的机制。它们描述了引用在程序执行期间的有效范围。 ### 显式生命周期注解 当函数返回引用时,编译器需要知道返回的引用与哪个参数的生命周期相关联: ```rust fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } fn main() { let string1 = String::from("long string is long"); let result; { let string2 = String::from("xyz"); result = longest(string1.as_str(), string2.as_str()); println!("最长字符串是 {}", result); // 正确 } // string2 在此被释放 // println!("最长字符串是 {}", result); // 编译错误!result 可能引用 string2 } ``` `'a` 是一个生命周期参数,表示 `x`、`y` 和返回值的生命周期至少是 `'a`。编译器确保返回的引用不会比任何一个输入引用活得更久。 ### 结构体中的生命周期 当结构体包含引用字段时,必须为每个引用标注生命周期: ```rust struct ImportantExcerpt<'a> { part: &'a str, } impl<'a> ImportantExcerpt<'a> { fn level(&self) -> i32 { 3 } fn announce_and_return_part(&self, announcement: &str) -> &str { println!("注意:{}", announcement); self.part } } fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let first_sentence = novel.split('.').next().expect("找不到 '.'"); let i = ImportantExcerpt { part: first_sentence, }; // i 不能比 novel 活得更久 } ``` ### 生命周期省略规则 为了减轻开发者的负担,Rust 编译器在某些情况下可以自动推断生命周期: 1. **每个引用参数都有自己的生命周期参数** 2. **如果只有一个输入生命周期参数,该生命周期被赋给所有输出生命周期参数** 3. **如果有多个输入生命周期参数,但其中一个是 `&self` 或 `&mut self`,那么 `self` 的生命周期被赋给所有输出生命周期参数** ```rust // 以下两个函数签名等价(得益于生命周期省略) fn first_word(s: &str) -> &str { } fn first_word<'a>(s: &'a str) -> &'a str { } ``` ## 'static 生命周期 `'static` 是 Rust 中最长的生命周期,它表示引用可以存活于整个程序运行期间。字符串字面量默认具有 `'static` 生命周期: ```rust let s: &'static str = "我拥有 'static 生命周期"; ``` 需要注意的是,`'static` 并不等同于全局变量或永不释放的内存。它只是表示该引用在程序运行期间始终有效。 ## 生命周期与闭包 闭包可以捕获其环境中的变量。Rust 提供了三种捕获方式,对应不同的借用语义: ```rust fn make_adder(x: i32) -> impl Fn(i32) -> i32 { move |y| x + y // move 关键字强制闭包获取所有权 } fn main() { let add_five = make_adder(5); println!("{}", add_five(3)); // 输出 8 println!("{}", add_five(10)); // 输出 15 } ``` 使用 `move` 关键字时,闭包会获取捕获变量的所有权,这在需要将闭包返回或传递给其他线程时特别有用。 ## 常见陷阱与最佳实践 ### 1. 悬垂引用(Dangling References) Rust 编译器会阻止悬垂引用的产生: ```rust fn dangle() -> &String { // 编译错误! let s = String::from("hello"); &s // s 在函数结束时被释放 } // 返回的引用指向已释放的内存 ``` 正确的做法:返回 `String` 本身(转移所有权)或使用 `String` 的切片。 ### 2. 内部可变性(Interior Mutability) 有时需要在拥有不可变引用的情况下修改数据。Rust 提供了 `RefCell` 和 `Cell` 等类型来实现**内部可变性**: ```rust use std::cell::RefCell; fn main() { let data = RefCell::new(5); { let mut borrow = data.borrow_mut(); *borrow += 1; } // 可变借用在此结束 println!("{}", data.borrow()); // 输出 6 } ``` ### 3. 智能指针与所有权 Rust 标准库提供了多种智能指针来辅助内存管理: | 智能指针 | 用途 | 所有权模型 | |----------|------|-----------| | `Box` | 堆分配 | 独占所有权 | | `Rc` | 引用计数 | 共享所有权(单线程) | | `Arc` | 原子引用计数 | 共享所有权(多线程) | | `RefCell` | 运行时借用检查 | 内部可变性 | ## 总结 Rust 的所有权系统虽然初学者可能会觉得复杂,但一旦掌握,就能编写出既高效又安全的代码。核心理念可以概括为:**编译器通过所有权、借用和生命周期规则,在编译期验证所有内存访问的安全性**。 | 概念 | 核心作用 | 使用场景 | |------|---------|---------| | 所有权 | 管理值的生命周期 | 所有堆分配的数据 | | 不可变借用 | 安全地读取数据 | 函数参数、迭代器 | | 可变借用 | 安全地修改数据 | 状态修改、缓冲区操作 | | 生命周期 | 验证引用的有效性 | 返回引用、结构体包含引用 | | Copy trait | 自动按位复制 | 栈上的简单类型 | Rust 的设计哲学是:**将内存安全责任从程序员转移到编译器**。虽然这意味着需要花更多时间学习与编译器"沟通",但换来的是零成本抽象的内存安全和无与伦比的运行时性能。随着 Rust 在 Linux 内核、Web 浏览器(Firefox)、云计算(AWS、Azure)等领域的广泛应用,掌握 Rust 的所有权系统已经成为现代系统程序员的核心技能之一。 --- *本文首发于 Yggdrasil 博客,转载请注明出处。* $doc$, NULL, 'published', NOW() - INTERVAL '7 days', NOW() - INTERVAL '7 days', NOW() - INTERVAL '7 days' ), ( 1, 'Python 装饰器完全指南:从入门到精通', 'python-decorators-guide', '从基础到高级,全面掌握 Python 装饰器的使用方法,包括函数装饰器、类装饰器、参数化装饰器,以及 functools.wraps、lru_cache 等内置装饰器的实战应用。', $doc$ # Python 装饰器完全指南:从入门到精通 装饰器(Decorator)是 Python 中最优雅、最强大的特性之一。它本质上是一种**高阶函数**(Higher-Order Function),允许你在不修改原函数源代码的前提下,为函数添加额外的功能。装饰器广泛应用于日志记录、权限验证、缓存、性能监控等场景,是 Python 元编程的核心工具。 本文将从基础概念出发,逐步深入到高级用法,帮助你全面掌握 Python 装饰器。 ## 什么是装饰器? 在 Python 中,函数是一等公民(First-Class Citizen),这意味着函数可以像普通变量一样被赋值、传递和返回。装饰器正是利用了这一特性: > **装饰器是一个接受函数作为参数并返回函数的可调用对象。** 最简单的装饰器形式如下: ```python def my_decorator(func): def wrapper(): print("🚀 函数调用前") func() print("✅ 函数调用后") return wrapper @my_decorator def say_hello(): print("Hello, World!") say_hello() ``` 输出结果: ``` 🚀 函数调用前 Hello, World! ✅ 函数调用后 ``` `@my_decorator` 语法糖等价于 `say_hello = my_decorator(say_hello)`,它将 `say_hello` 函数替换为了 `wrapper` 函数。 ## 处理带参数的函数 上面的装饰器只能装饰无参数的函数。为了让装饰器更通用,需要使用 `*args` 和 `**kwargs`: ```python def greeting_decorator(func): def wrapper(*args, **kwargs): print(f"🎯 正在调用: {func.__name__}") result = func(*args, **kwargs) print(f"✨ {func.__name__} 调用完成") return result return wrapper @greeting_decorator def greet(name, greeting="Hello"): return f"{greeting}, {name}!" print(greet("Alice")) print(greet("Bob", greeting="Hi")) ``` 输出: ``` 🎯 正在调用: greet ✨ greet 调用完成 Hello, Alice! 🎯 正在调用: greet ✨ greet 调用完成 Hi, Bob! ``` ## 使用 functools.wraps 保留元数据 使用装饰器后,原函数的元数据(如函数名、文档字符串)会丢失: ```python print(say_hello.__name__) # 输出: wrapper,而不是 say_hello ``` 为了解决这个问题,Python 标准库提供了 `functools.wraps`: ```python import functools def my_decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): """wrapper 的文档""" return func(*args, **kwargs) return wrapper @my_decorator def say_hello(): """打招呼函数""" print("Hello!") print(say_hello.__name__) # 输出: say_hello print(say_hello.__doc__) # 输出: 打招呼函数 ``` **最佳实践**:编写装饰器时务必使用 `@functools.wraps(func)`,这是专业 Python 代码的标志。 ## 参数化装饰器 有时装饰器本身需要接收参数。实现参数化装饰器需要嵌套三层函数: ```python def repeat(num=2): """重复执行函数 num 次""" def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): results = [] for i in range(num): print(f"第 {i + 1} 次执行...") result = func(*args, **kwargs) results.append(result) return results return wrapper return decorator @repeat(num=3) def greet(name): return f"Hello, {name}!" results = greet("Alice") print(results) ``` 输出: ``` 第 1 次执行... 第 2 次执行... 第 3 次执行... ['Hello, Alice!', 'Hello, Alice!', 'Hello, Alice!'] ``` 执行流程:`repeat(num=3)` → 返回 `decorator` → `decorator(greet)` → 返回 `wrapper` → `greet` 被替换为 `wrapper`。 ## 类装饰器 除了函数装饰器,Python 还支持**类装饰器**。类装饰器通常用于: 1. 为类添加新方法或属性 2. 修改类的行为 3. 注册类到某个注册表中 ### 基础类装饰器 ```python class CountCalls: """统计函数被调用的次数""" def __init__(self, func): functools.update_wrapper(self, func) self.func = func self.num_calls = 0 def __call__(self, *args, **kwargs): self.num_calls += 1 print(f"[{self.num_calls}] {self.func.__name__} 被调用") return self.func(*args, **kwargs) @CountCalls def say_whee(): print("Whee! 🎉") say_whee() say_whee() say_whee() print(f"总共调用了 {say_whee.num_calls} 次") ``` 输出: ``` [1] say_whee 被调用 Whee! 🎉 [2] say_whee 被调用 Whee! 🎉 [3] say_whee 被调用 Whee! 🎉 总共调用了 3 次 ``` ### 使用类实现带状态装饰器 类装饰器特别适合需要维护状态的场景: ```python class Cache: """简单的缓存装饰器""" def __init__(self, func): functools.update_wrapper(self, func) self.func = func self.cache = {} def __call__(self, *args): if args in self.cache: print(f"📦 缓存命中: {args}") return self.cache[args] print(f"🔍 计算中: {args}") result = self.func(*args) self.cache[args] = result return result @Cache def fibonacci(n): if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) print(f"fib(5) = {fibonacci(5)}") print(f"fib(5) = {fibonacci(5)}") # 第二次直接返回缓存 ``` ## 内置装饰器实战 Python 标准库和 functools 模块提供了多个实用的内置装饰器。 ### @property:将方法变为属性 ```python class Circle: def __init__(self, radius): self._radius = radius @property def radius(self): """获取半径""" return self._radius @radius.setter def radius(self, value): """设置半径,支持验证""" if value < 0: raise ValueError("半径不能为负数") self._radius = value @property def area(self): """计算面积""" return 3.14159 * self._radius ** 2 c = Circle(5) print(f"半径: {c.radius}") print(f"面积: {c.area:.2f}") c.radius = 10 print(f"新面积: {c.area:.2f}") ``` ### @staticmethod 和 @classmethod ```python class DateUtil: @staticmethod def is_leap_year(year): """判断闰年""" return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) @classmethod def from_string(cls, date_str): """从字符串创建实例""" year, month, day = map(int, date_str.split('-')) return cls(year, month, day) print(DateUtil.is_leap_year(2024)) # True ``` ### @functools.lru_cache:自动缓存 ```python from functools import lru_cache @lru_cache(maxsize=128) def fibonacci(n): """带缓存的斐波那契数列""" if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) # 计算第 100 项 print(f"fib(100) = {fibonacci(100)}") print(f"缓存信息: {fibonacci.cache_info()}") ``` ### @functools.singledispatch:函数重载 ```python from functools import singledispatch @singledispatch def process(arg): """默认实现""" raise NotImplementedError(f"不支持类型: {type(arg)}") @process.register(int) def _(arg): return f"整数: {arg * 2}" @process.register(str) def _(arg): return f"字符串: {arg.upper()}" @process.register(list) def _(arg): return f"列表: {len(arg)} 个元素" print(process(42)) # 整数: 84 print(process("hello")) # 字符串: HELLO print(process([1, 2, 3])) # 列表: 3 个元素 ``` ## 装饰器组合与顺序 多个装饰器可以叠加使用,执行顺序为**从下往上**: ```python @decorator_a @decorator_b def my_func(): pass # 等价于: my_func = decorator_a(decorator_b(my_func)) ``` ## 实际应用场景 | 场景 | 装饰器实现 | 说明 | |------|-----------|------| | 日志记录 | `@log_call` | 记录函数调用参数和返回值 | | 权限验证 | `@require_login` | 检查用户是否已登录 | | 性能计时 | `@timer` | 测量函数执行时间 | | 重试机制 | `@retry(max_attempts=3)` | 失败时自动重试 | | 限流控制 | `@rate_limit(100)` | 限制每秒调用次数 | | 输入验证 | `@validate_schema` | 验证函数参数格式 | ## 总结 装饰器是 Python 中最具表现力的特性之一,它让代码更加简洁、可复用和符合 DRY 原则。掌握装饰器需要理解: 1. **函数是一等公民**:函数可以作为参数传递、作为返回值返回 2. **闭包**:装饰器内部函数可以访问外部函数的变量 3. **@语法糖**:`@decorator` 等价于 `func = decorator(func)` 4. **functools.wraps**:保留原函数的元数据 5. **嵌套三层**:实现参数化装饰器需要额外的一层函数 > "Python 的装饰器让代码像诗歌一样优美,但过度使用会让代码变得晦涩难懂。" —— 遵循**显式优于隐式**的原则,在合适的场景使用装饰器,避免过度工程化。 --- *本文首发于 Yggdrasil 博客* $doc$, NULL, 'published', NOW() - INTERVAL '6 days', NOW() - INTERVAL '6 days', NOW() - INTERVAL '6 days' ), ( 1, 'JavaScript 异步编程完全指南:从回调地狱到 async/await', 'js-async-programming', '深入理解 JavaScript 的异步编程模型,从回调函数到 Promise,再到 async/await 的演变历程,以及事件循环的底层原理。', $doc$ # JavaScript 异步编程完全指南:从回调地狱到 async/await JavaScript 是一门单线程语言,这意味着它一次只能执行一个任务。然而,现代 Web 应用需要处理大量 I/O 操作(如网络请求、文件读写、定时器等),如果采用同步方式处理,整个程序会被阻塞,用户体验极差。为了解决这一问题,JavaScript 采用了**事件驱动、非阻塞 I/O** 的编程模型,并通过**事件循环**(Event Loop)机制实现了异步编程。 本文将带你回顾 JavaScript 异步编程的完整演进历程,从最早的回调函数到现代的 async/await,帮助你建立对 JavaScript 并发模型的系统理解。 ## 回调函数:异步编程的起点 在 JavaScript 早期,异步操作主要通过回调函数(Callback)来实现。回调函数是一个被作为参数传递给另一个函数的函数,当异步操作完成时被调用: ```javascript function fetchData(callback) { setTimeout(() => { callback("数据加载完成"); }, 1000); } fetchData((data) => { console.log(data); }); ``` 回调函数简单直观,但存在两个主要问题: ### 1. 回调地狱(Callback Hell) 当多个异步操作需要按顺序执行时,代码会层层嵌套,形成"金字塔型"结构: ```javascript getUserData(userId, (user) => { getOrders(user.id, (orders) => { getOrderDetails(orders[0].id, (details) => { getProductInfo(details.productId, (product) => { console.log(product); }); }); }); }); ``` 这种代码难以阅读、维护和调试。每一层嵌套都增加了认知负担,错误处理也变得异常复杂。 ### 2. 错误处理困难 回调函数通常采用"错误优先"的约定(Error-First Callback): ```javascript fs.readFile('file.txt', (err, data) => { if (err) { console.error('读取失败:', err); return; } console.log('文件内容:', data); }); ``` 虽然这种约定统一了错误处理方式,但在多层嵌套时,错误处理代码会散落在各个层级,导致大量重复和遗漏。 ## Promise:优雅的异步解决方案 ES6(2015)引入了 **Promise**,它是对异步操作的一种抽象表示,代表一个尚未完成但预期将来会完成的操作。 ### Promise 的基本用法 Promise 有三种状态: - **Pending(待定)**:初始状态,操作尚未完成 - **Fulfilled(已完成)**:操作成功完成 - **Rejected(已拒绝)**:操作失败 ```javascript const fetchData = () => { return new Promise((resolve, reject) => { setTimeout(() => { const success = true; if (success) { resolve("✅ 数据加载成功"); } else { reject("❌ 数据加载失败"); } }, 1000); }); }; fetchData() .then(data => { console.log(data); return "处理后的数据"; }) .then(processedData => { console.log(processedData); }) .catch(err => { console.error("错误:", err); }) .finally(() => { console.log("无论成功还是失败,都会执行"); }); ``` ### Promise 链式调用 Promise 的最大优势在于支持**链式调用**,解决了回调地狱问题: ```javascript getUserData(userId) .then(user => getOrders(user.id)) .then(orders => getOrderDetails(orders[0].id)) .then(details => getProductInfo(details.productId)) .then(product => console.log(product)) .catch(err => console.error("出错了:", err)); ``` 每个 `.then()` 接收前一个 Promise 的返回值,并返回一个新的 Promise,形成清晰的线性流程。 ### Promise.all 和 Promise.race Promise 还提供了组合多个异步操作的方法: ```javascript // 并行执行多个 Promise,全部完成后返回 const promises = [ fetchUserData(), fetchUserSettings(), fetchUserNotifications() ]; Promise.all(promises) .then(([user, settings, notifications]) => { console.log("所有数据加载完成"); }) .catch(err => console.error("任一失败:", err)); // 返回最快完成的 Promise Promise.race([ fetchData(), new Promise((_, reject) => setTimeout(() => reject("超时"), 5000) ) ]) .then(data => console.log(data)) .catch(err => console.error(err)); ``` ## Async/Await:异步代码的同步写法 ES2017 引入了 **async/await**,它是 Promise 的语法糖,让异步代码看起来像同步代码一样直观: ```javascript async function loadUserData(userId) { try { const user = await getUserData(userId); const orders = await getOrders(user.id); const details = await getOrderDetails(orders[0].id); const product = await getProductInfo(details.productId); return product; } catch (err) { console.error("加载用户数据失败:", err); throw err; } finally { console.log("数据加载流程结束"); } } // 调用 async 函数 loadUserData(123) .then(product => console.log(product)) .catch(err => console.error(err)); ``` ### async/await 的优势 1. **代码更扁平**:消除了 Promise 链的嵌套 2. **错误处理更自然**:使用 `try/catch`,符合同步代码的习惯 3. **调试更方便**:可以在 await 处设置断点 4. **条件语句更直观**: ```javascript async function fetchData(shouldFetchDetails) { const user = await getUserData(); // 条件执行异步操作 if (shouldFetchDetails) { const details = await getDetails(user.id); return { user, details }; } return { user }; } ``` ### 并行执行 await 默认情况下,await 会按顺序执行,但可以通过 `Promise.all` 实现并行: ```javascript async function loadDashboard() { // 串行执行(较慢) // const user = await getUserData(); // const posts = await getPosts(); // const notifications = await getNotifications(); // 并行执行(更快) const [user, posts, notifications] = await Promise.all([ getUserData(), getPosts(), getNotifications() ]); return { user, posts, notifications }; } ``` ## 事件循环:JavaScript 的并发心脏 要真正理解 JavaScript 的异步机制,必须深入理解**事件循环**(Event Loop)。事件循环是 JavaScript 运行时(如浏览器或 Node.js)的核心机制,负责协调同步代码和异步回调的执行。 ### 调用栈(Call Stack) 调用栈是一个后进先出(LIFO)的数据结构,用于追踪程序的执行位置。当函数被调用时,它被压入栈顶;当函数返回时,它从栈顶弹出。 ### 任务队列(Task Queue) 异步操作完成后,其回调函数不会立即执行,而是被放入**任务队列**中等待。事件循环不断地检查调用栈是否为空,如果为空,则将任务队列中的第一个任务推入调用栈执行。 ### 宏任务与微任务 JavaScript 的任务队列分为两类: | 类型 | 包含 | 优先级 | |------|------|--------| | **宏任务(Macrotask)** | `setTimeout`、`setInterval`、I/O 操作、UI 渲染 | 低 | | **微任务(Microtask)** | `Promise.then()`、`Promise.catch()`、`MutationObserver`、`queueMicrotask()` | 高 | **执行规则**: 1. 执行当前宏任务(同步代码) 2. 执行所有微任务(清空微任务队列) 3. 渲染 UI(如果需要) 4. 从宏任务队列取出下一个任务,重复步骤 1 ### 经典示例分析 ```javascript console.log("1️⃣ 同步代码"); setTimeout(() => { console.log("2️⃣ setTimeout(宏任务)"); }, 0); Promise.resolve().then(() => { console.log("3️⃣ Promise.then(微任务)"); }); console.log("4️⃣ 同步代码结束"); ``` 输出顺序: ``` 1️⃣ 同步代码 4️⃣ 同步代码结束 3️⃣ Promise.then(微任务) 2️⃣ setTimeout(宏任务) ``` **执行流程解析**: 1. `console.log("1️⃣")` 立即执行 2. `setTimeout` 的回调被放入宏任务队列 3. `Promise.resolve().then()` 的回调被放入微任务队列 4. `console.log("4️⃣")` 立即执行 5. 同步代码执行完毕,检查微任务队列,执行 Promise 回调 6. 微任务队列为空,从宏任务队列取出 setTimeout 回调执行 ### 更复杂的示例 ```javascript console.log("Start"); setTimeout(() => { console.log("Timeout 1"); Promise.resolve().then(() => { console.log("Promise inside timeout"); }); }, 0); setTimeout(() => { console.log("Timeout 2"); }, 0); Promise.resolve().then(() => { console.log("Promise 1"); }).then(() => { console.log("Promise 2"); }); console.log("End"); ``` 输出: ``` Start End Promise 1 Promise 2 Timeout 1 Promise inside timeout Timeout 2 ``` ## 现代异步模式 ### 1. for-await-of 循环 ES2018 引入了异步迭代器,可以优雅地遍历异步数据源: ```javascript async function* fetchPages() { for (let i = 1; i <= 3; i++) { yield await fetch(`/api/page/${i}`).then(r => r.json()); } } (async () => { for await (const page of fetchPages()) { console.log(page); } })(); ``` ### 2. AbortController 取消请求 ```javascript const controller = new AbortController(); fetch('/api/data', { signal: controller.signal }) .then(response => response.json()) .then(data => console.log(data)) .catch(err => { if (err.name === 'AbortError') { console.log('请求已被取消'); } }); // 5 秒后取消请求 setTimeout(() => controller.abort(), 5000); ``` ### 3. Top-level await(ES2022) ```javascript // 模块顶层直接使用 await const data = await fetch('/api/config').then(r => r.json()); export { data }; ``` ## 总结 JavaScript 的异步编程经历了从回调到 Promise,再到 async/await 的演变,每一步都让代码更加优雅和易于维护。 | 机制 | 优点 | 缺点 | 适用场景 | |------|------|------|---------| | 回调函数 | 简单直观 | 回调地狱、错误处理困难 | 简单的异步操作 | | Promise | 链式调用、组合方便 | 仍有一定嵌套 | 多步骤异步流程 | | async/await | 同步写法、易调试 | 需要理解底层 Promise | 复杂异步逻辑 | **关键要点**: - 微任务优先级高于宏任务 - `await` 后面的代码会被放入微任务队列 - 使用 `Promise.all` 并行执行多个异步操作 - 始终使用 `try/catch` 处理 async/await 中的错误 > 深入理解事件循环是掌握 JavaScript 异步编程的关键。推荐阅读:[MDN - 使用 Promises](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises) --- *本文首发于 Yggdrasil 博客* $doc$, NULL, 'published', NOW() - INTERVAL '5 days', NOW() - INTERVAL '5 days', NOW() - INTERVAL '5 days' ), ( 1, 'Go 并发模式完全指南:Goroutine、Channel 与 Select', 'go-concurrency-patterns', 'Go 语言以简洁的并发模型著称,本文详细介绍 goroutine、channel、select 和各种并发设计模式,帮助你写出高性能的并发程序。', $doc$ # Go 并发模式完全指南:Goroutine、Channel 与 Select Go 语言自 2009 年发布以来,凭借其**简洁、高效、原生支持并发**的特性,迅速成为云原生时代的首选语言之一。Docker、Kubernetes、Prometheus 等知名项目均使用 Go 编写。Go 语言最引人注目的特性之一,就是其内置的轻量级线程 **goroutine** 和通信原语 **channel**,它们让并发编程变得简单而优雅。 与操作系统线程(通常占用数 MB 内存)不同,goroutine 的初始栈仅有 **2KB**,并且可以根据需要动态增长和收缩。这意味着在单个 Go 程序中可以轻松创建数十万个 goroutine,而不会耗尽系统资源。 本文将系统介绍 Go 的并发原语和常见并发模式,帮助你写出高效、健壮的并发程序。 ## Goroutine:轻量级并发单元 在 Go 中,启动一个并发任务只需在函数调用前加上 `go` 关键字: ```go package main import ( "fmt" "time" ) func say(s string, times int) { for i := 0; i < times; i++ { time.Sleep(100 * time.Millisecond) fmt.Printf("%s (第 %d 次)\\n", s, i+1) } } func main() { // 启动两个 goroutine 并发执行 go say("🌏 world", 3) go say("👋 hello", 3) // 主 goroutine 等待一段时间,让子 goroutine 完成 time.Sleep(1 * time.Second) fmt.Println("主程序结束") } ``` 输出(顺序可能不同): ``` 👋 hello (第 1 次) 🌏 world (第 1 次) 🌏 world (第 2 次) 👋 hello (第 2 次) 👋 hello (第 3 次) 🌏 world (第 3 次) 主程序结束 ``` ### 使用 WaitGroup 等待 goroutine 完成 上面的示例使用了 `time.Sleep` 来等待 goroutine,这种方式不够精确。Go 提供了 `sync.WaitGroup` 来优雅地等待一组 goroutine 完成: ```go package main import ( "fmt" "sync" "time" ) func worker(id int, wg *sync.WaitGroup) { defer wg.Done() // 完成时减少计数器 fmt.Printf("🚀 Worker %d 开始工作\\n", id) time.Sleep(time.Second) fmt.Printf("✅ Worker %d 完成工作\\n", id) } func main() { var wg sync.WaitGroup for i := 1; i <= 3; i++ { wg.Add(1) // 增加计数器 go worker(i, &wg) } wg.Wait() // 等待所有 goroutine 完成 fmt.Println("所有工作已完成") } ``` ## Channel:goroutine 之间的通信桥梁 Go 语言的设计哲学是:**不要通过共享内存来通信,而要通过通信来共享内存**(Do not communicate by sharing memory; instead, share memory by communicating.)。Channel 正是这一哲学的核心实现。 Channel 是一种类型安全的队列,用于在 goroutine 之间传递数据。 ### 创建 Channel ```go // 无缓冲 channel(同步通信) ch := make(chan int) // 有缓冲 channel(异步通信,容量为 5) ch := make(chan int, 5) ``` ### 无缓冲 Channel 无缓冲 channel 在发送和接收时会阻塞,直到对方准备好: ```go package main import "fmt" func main() { ch := make(chan string) go func() { fmt.Println("📤 发送消息...") ch <- "Hello from goroutine!" fmt.Println("📤 发送完成") }() msg := <-ch // 阻塞等待接收 fmt.Println("📨 收到:", msg) } ``` ### 有缓冲 Channel 有缓冲 channel 允许在阻塞前发送指定数量的数据: ```go package main import "fmt" func main() { ch := make(chan int, 3) // 发送数据(不会阻塞,因为缓冲区未满) ch <- 1 ch <- 2 ch <- 3 // ch <- 4 // 如果取消注释,此行会阻塞,因为缓冲区已满 // 接收数据 fmt.Println(<-ch) // 1 fmt.Println(<-ch) // 2 fmt.Println(<-ch) // 3 } ``` ### 关闭 Channel 当发送方不再发送数据时,应该关闭 channel: ```go package main import "fmt" func producer(ch chan<- int) { for i := 0; i < 5; i++ { ch <- i } close(ch) // 关闭 channel } func main() { ch := make(chan int) go producer(ch) // 使用 range 遍历 channel,自动检测关闭 for value := range ch { fmt.Printf("收到: %d\\n", value) } fmt.Println("Channel 已关闭") } ``` ### 单向 Channel Go 支持单向 channel 类型,用于限制 channel 的使用方向: ```go func producer(ch chan<- int) { // 只发送 ch <- 42 } func consumer(ch <-chan int) { // 只接收 fmt.Println(<-ch) } ``` ## Select:多路复用 `select` 语句是 Go 并发编程的瑞士军刀,它允许你同时等待多个 channel 操作,类似于网络编程中的 `select()` 系统调用: ```go package main import ( "fmt" "time" ) func main() { ch1 := make(chan string) ch2 := make(chan string) go func() { time.Sleep(1 * time.Second) ch1 <- "来自 channel 1" }() go func() { time.Sleep(2 * time.Second) ch2 <- "来自 channel 2" }() // 等待两个 channel 中的任意一个 select { case msg1 := <-ch1: fmt.Println(msg1) case msg2 := <-ch2: fmt.Println(msg2) } } ``` ### 超时处理 ```go select { case res := <-c1: fmt.Println("结果:", res) case <-time.After(1 * time.Second): fmt.Println("⏰ 超时!") } ``` ### 非阻塞操作 ```go select { case ch <- value: fmt.Println("发送成功") default: fmt.Println("channel 已满,跳过") } ``` ### 随机选择 当多个 case 同时就绪时,select 会**随机**选择一个执行: ```go ch := make(chan int, 2) ch <- 1 ch <- 2 select { case <-ch: fmt.Println("收到数据 1") case <-ch: fmt.Println("收到数据 2") } // 随机输出其中一个 ``` ## 常见并发模式 ### 1. 生产者-消费者模式 ```go package main import ( "fmt" "time" ) func producer(id int, ch chan<- int) { for i := 0; i < 3; i++ { item := id*10 + i fmt.Printf("🏭 生产者 %d 生产: %d\\n", id, item) ch <- item time.Sleep(100 * time.Millisecond) } } func consumer(id int, ch <-chan int) { for item := range ch { fmt.Printf("🛒 消费者 %d 消费: %d\\n", id, item) time.Sleep(200 * time.Millisecond) } } func main() { ch := make(chan int, 5) // 启动 2 个生产者 for i := 1; i <= 2; i++ { go producer(i, ch) } // 启动 3 个消费者 for i := 1; i <= 3; i++ { go consumer(i, ch) } time.Sleep(3 * time.Second) close(ch) time.Sleep(1 * time.Second) } ``` ### 2. Worker Pool(工作池) ```go package main import ( "fmt" "sync" "time" ) func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) { defer wg.Done() for job := range jobs { fmt.Printf("👷 Worker %d 处理任务 %d\\n", id, job) time.Sleep(time.Second) // 模拟工作 results <- job * 2 } } func main() { const numJobs = 10 const numWorkers = 3 jobs := make(chan int, numJobs) results := make(chan int, numJobs) var wg sync.WaitGroup for w := 1; w <= numWorkers; w++ { wg.Add(1) go worker(w, jobs, results, &wg) } for j := 1; j <= numJobs; j++ { jobs <- j } close(jobs) wg.Wait() close(results) for result := range results { fmt.Printf("📊 结果: %d\\n", result) } } ``` ### 3. Pipeline(管道) ```go package main import "fmt" func gen(nums ...int) <-chan int { out := make(chan int) go func() { for _, n := range nums { out <- n } close(out) }() return out } func sq(in <-chan int) <-chan int { out := make(chan int) go func() { for n := range in { out <- n * n } close(out) }() return out } func main() { // 设置 pipeline: gen -> sq for result := range sq(sq(gen(2, 3, 4))) { fmt.Println(result) // 16, 81, 256 (先平方再平方) } } ``` ### 4. Fan-out/Fan-in(扇出/扇入) ```go func merge(channels ...<-chan int) <-chan int { var wg sync.WaitGroup out := make(chan int) output := func(c <-chan int) { defer wg.Done() for n := range c { out <- n } } wg.Add(len(channels)) for _, c := range channels { go output(c) } go func() { wg.Wait() close(out) }() return out } ``` ## Context:请求级控制 Go 1.7 引入了 `context` 包,用于在 goroutine 之间传递截止时间、取消信号和请求范围的值: ```go package main import ( "context" "fmt" "time" ) func slowOperation(ctx context.Context) (string, error) { select { case <-time.After(2 * time.Second): return "✅ 操作完成", nil case <-ctx.Done(): return "", ctx.Err() } } func main() { // 设置 1 秒超时 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() result, err := slowOperation(ctx) if err != nil { fmt.Println("❌ 错误:", err) // context deadline exceeded return } fmt.Println(result) } ``` ### Context 链式传递 ```go func main() { ctx := context.Background() // 添加超时 ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() // 添加取消信号 ctx, cancel = context.WithCancel(ctx) defer cancel() // 添加请求 ID ctx = context.WithValue(ctx, "requestID", "abc-123") processRequest(ctx) } ``` ## 并发模式对比 | 模式 | 描述 | 使用场景 | 关键原语 | |------|------|---------|---------| | 生产者-消费者 | 通过 channel 传递数据 | 任务队列、消息处理 | `chan`, `range` | | Worker Pool | 固定数量的 worker 处理任务 | CPU 密集型任务 | `sync.WaitGroup` | | Pipeline | 多个 stage 串联处理 | 数据流处理 | `chan` 返回 | | Fan-out | 多路分发 | 并行处理 | 多个 goroutine | | Fan-in | 多路合并 | 聚合结果 | `select`, `sync.WaitGroup` | | 超时控制 | 限制操作时间 | 网络请求、外部调用 | `context`, `time.After` | ## 总结 Go 的并发模型以其简洁和高效著称。核心要点: 1. **Goroutine** 是轻量级线程,通过 `go` 关键字启动 2. **Channel** 是 goroutine 之间的通信方式,而不是共享内存 3. **Select** 实现多路复用和超时控制 4. **Context** 管理请求级的生命周期 5. **Sync 包** 提供了锁、WaitGroup、Once、Pool 等同步原语 > **Go 并发黄金法则**:先通过 channel 思考,只有在必要时才使用共享内存和锁。 --- *本文首发于 Yggdrasil 博客* $doc$, NULL, 'published', NOW() - INTERVAL '4 days', NOW() - INTERVAL '4 days', NOW() - INTERVAL '4 days' ), ( 1, 'TypeScript 高级类型体操:从条件类型到模板字面量', 'typescript-type-gymnastics', '探索 TypeScript 类型系统的极限,从条件类型到映射类型,再到模板字面量类型和类型推断,让你的代码在编译期就获得强大的类型保障。', $doc$ # TypeScript 高级类型体操:从条件类型到模板字面量 TypeScript 的类型系统是一门**图灵完备**的语言。这意味着,在理论上,你可以使用 TypeScript 的类型系统来实现任何可计算的程序。虽然我们不建议在真实项目中过度使用复杂的类型体操,但掌握这些高级类型技巧,可以帮助你构建更加健壮、可维护的类型定义,特别是在开发库和框架时。 本文将从基础到高级,系统介绍 TypeScript 的类型系统特性,包括条件类型、映射类型、模板字面量类型、递归类型和类型推断等。 ## 为什么需要高级类型? 在日常开发中,基础类型(`string`、`number`、`boolean`)和简单的接口往往已经足够。但在以下场景中,高级类型变得至关重要: - **开发类型安全的库**:如 Vue、React、Redux 等框架的类型定义 - **编写通用工具函数**:如 lodash 的类型定义 - **实现类型转换**:如将对象的所有属性变为可选或只读 - **约束 API 接口**:确保编译器能够推断出正确的响应类型 ## 条件类型(Conditional Types) 条件类型是 TypeScript 2.8 引入的特性,它允许你根据类型关系选择不同的类型: ```typescript type IsString = T extends string ? true : false; // 使用示例 type A = IsString; // true type B = IsString; // false type C = IsString<"hello">; // true(字面量类型也是 string 的子类型) ``` ### extends 关键字 在条件类型中,`extends` 表示"是否是...的子类型": ```typescript type IsArray = T extends any[] ? true : false; type D = IsArray; // true type E = IsArray; // false ``` ### infer 关键字:类型推断 `infer` 是 TypeScript 中最强大的关键字之一,它允许你在条件类型中"提取"类型: ```typescript // 提取数组元素类型 type ElementType = T extends (infer U)[] ? U : never; type F = ElementType; // string type G = ElementType; // number // 提取函数返回值类型 type ReturnType = T extends (...args: any[]) => infer R ? R : never; function getUser() { return { id: 1, name: "Alice" }; } type User = ReturnType; // { id: number; name: string; } // 提取 Promise 的解析类型 type UnwrapPromise = T extends Promise ? U : T; type H = UnwrapPromise>; // string ``` ### 内置条件类型 TypeScript 标准库已经提供了许多基于条件类型的工具类型: | 类型 | 作用 | 示例 | |------|------|------| | `Exclude` | 从 T 中排除 U | `Exclude<'a' \| 'b', 'a'>` → `'b'` | | `Extract` | 从 T 中提取 U | `Extract<'a' \| 'b', 'a' \| 'c'>` → `'a'` | | `NonNullable` | 排除 null 和 undefined | `NonNullable` → `string` | | `ReturnType` | 获取函数返回类型 | `ReturnType<() => number>` → `number` | | `Parameters` | 获取函数参数类型 | `Parameters<(a: string) => void>` → `[string]` | | `InstanceType` | 获取构造函数实例类型 | `InstanceType` → `Date` | ## 映射类型(Mapped Types) 映射类型允许你基于已有类型创建新类型,通过遍历属性键来转换每个属性: ### 基础映射类型 ```typescript type Readonly = { readonly [P in keyof T]: T[P]; }; type Partial = { [P in keyof T]?: T[P]; }; type Required = { [P in keyof T]-?: T[P]; // -? 移除可选性 }; // 使用示例 interface User { name: string; age: number; } type ReadonlyUser = Readonly; // { readonly name: string; readonly age: number; } type PartialUser = Partial; // { name?: string; age?: number; } ``` ### 键重映射(Key Remapping) TypeScript 4.1 引入了 `as` 关键字,允许在映射类型中重命名键: ```typescript // 将每个属性名添加 "get" 前缀,类型变为函数 type Getters = { [K in keyof T as `get${Capitalize}`]: () => T[K]; }; interface Person { name: string; age: number; } type PersonGetters = Getters; // { getName: () => string; getAge: () => number; } ``` ### 过滤属性 ```typescript // 只保留 string 类型的属性 type StringProperties = { [K in keyof T as T[K] extends string ? K : never]: T[K]; }; interface User { name: string; age: number; email: string; } type StringUserProps = StringProperties; // { name: string; email: string; } ``` ## 模板字面量类型(Template Literal Types) TypeScript 4.1 引入了模板字面量类型,它允许你通过字符串字面量类型来构造新类型: ### 基础用法 ```typescript type EventName = `on${Capitalize}`; type ClickEvent = EventName<"click">; // "onClick" type HoverEvent = EventName<"mouseOver">; // "onMouseOver" ``` ### 联合类型的组合 当模板字面量类型与联合类型结合使用时,会产生**笛卡尔积**效果: ```typescript type Horizontal = "left" | "center" | "right"; type Vertical = "top" | "center" | "bottom"; type Alignment = `${Horizontal}-${Vertical}`; // "left-top" | "left-center" | "left-bottom" | // "center-top" | "center-center" | "center-bottom" | // "right-top" | "right-center" | "right-bottom" ``` ### 实际应用:CSS 属性类型 ```typescript type CSSProperty = "margin" | "padding"; type CSSDirection = "top" | "right" | "bottom" | "left"; type CSSKey = `${CSSProperty}${Capitalize}` | CSSProperty; // "margin" | "padding" | "marginTop" | "marginRight" | ... ``` ## 递归类型 TypeScript 支持递归类型定义,这在处理嵌套数据结构时非常有用: ```typescript // 深度只读类型 type DeepReadonly = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly : T[P]; }; interface NestedUser { name: string; address: { city: string; coordinates: { lat: number; lng: number; }; }; } type DeepReadonlyUser = DeepReadonly; // 所有嵌套属性都变为 readonly // 深度 Partial 类型 type DeepPartial = { [P in keyof T]?: T[P] extends object ? DeepPartial : T[P]; }; // 扁平化对象类型(将嵌套属性用点号连接) type Flatten = { [K in keyof T as K extends string ? Prefix extends "" ? K : `${Prefix & string}.${K}` : never ]: T[K] extends object ? Flatten : T[K]; }; ``` ## 类型守卫与类型收窄 TypeScript 的类型系统不仅在编译期工作,还能通过类型守卫在运行时收窄类型: ```typescript // typeof 类型守卫 function processValue(value: string | number) { if (typeof value === "string") { // 此分支中 value 的类型为 string return value.toUpperCase(); } else { // 此分支中 value 的类型为 number return value.toFixed(2); } } // 自定义类型守卫 type Cat = { kind: "cat"; meow: () => void }; type Dog = { kind: "dog"; bark: () => void }; type Animal = Cat | Dog; function isCat(animal: Animal): animal is Cat { return animal.kind === "cat"; } function makeSound(animal: Animal) { if (isCat(animal)) { animal.meow(); } else { animal.bark(); } } ``` ## 实用类型工具库 基于上述类型特性,你可以构建强大的类型工具库: ```typescript // 将对象的所有嵌套属性路径提取为联合类型 type Path = K extends string ? T[K] extends object ? `${K}` | `${K}.${Path}` : `${K}` : never; // 安全的深度属性访问类型 type DeepPick = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? { [key in K]: DeepPick } : never : P extends keyof T ? { [key in P]: T[P] } : never; ``` ## 总结 TypeScript 的类型系统提供了丰富的工具来构建类型安全的应用程序: | 特性 | 版本 | 作用 | |------|------|------| | 条件类型 | 2.8 | 基于类型关系选择类型 | | infer | 2.8 | 在条件类型中提取类型 | | 映射类型 | 2.1 | 遍历属性键转换类型 | | 模板字面量 | 4.1 | 构造字符串字面量类型 | | 键重映射 | 4.1 | 在映射中重命名属性键 | | 递归类型 | 3.7+ | 处理嵌套数据结构 | > ⚠️ **温馨提示**:类型体操虽有趣,但过度使用会降低代码可读性和编译性能。遵循"简单优于复杂"的原则,在合适的场景使用高级类型。对于大多数业务代码,基础类型和简单的接口已经足够。 --- *本文首发于 Yggdrasil 博客* $doc$, NULL, 'published', NOW() - INTERVAL '3 days', NOW() - INTERVAL '3 days', NOW() - INTERVAL '3 days' ), ( 1, 'Java 泛型深度解析:从基础到通配符的PECS原则', 'java-generics-cheatsheet', 'Java 泛型是类型安全的基石,本文深入讲解泛型类、泛型方法、通配符及其上界下界,以及PECS原则在实际编程中的应用。', $doc$ # Java 泛型深度解析:从基础到通配符的 PECS 原则 Java 泛型(Generics)是自 JDK 5 引入的一项重要特性,它为 Java 语言带来了**编译期类型检查**和**类型安全**的能力。在没有泛型之前,Java 集合只能存储 `Object` 类型,取出数据时需要进行强制类型转换,这不仅繁琐,而且容易在运行时抛出 `ClassCastException`。 本文将从基础概念出发,深入探讨 Java 泛型的核心机制,包括泛型类、泛型方法、通配符、类型擦除,以及实际开发中最常用的 PECS 原则。 ## 泛型的基础概念 ### 泛型类 泛型类允许你在定义类时使用类型参数,这些类型参数在创建实例时被具体化: ```java // 定义泛型类 public class Box { private T value; public void set(T value) { this.value = value; } public T get() { return value; } public static void main(String[] args) { // 创建 String 类型的 Box Box stringBox = new Box<>(); stringBox.set("Hello Generics"); String str = stringBox.get(); // 无需类型转换 // 创建 Integer 类型的 Box Box intBox = new Box<>(); intBox.set(42); Integer num = intBox.get(); } } ``` ### 泛型方法 泛型方法允许你在普通类或泛型类中定义带有类型参数的方法: ```java public class GenericMethodExample { // 泛型方法 public void printArray(T[] array) { for (T element : array) { System.out.println(element); } } // 泛型方法 with 返回值 public T getFirst(T[] array) { return array.length > 0 ? array[0] : null; } // 泛型方法 with 多个类型参数 public void printPair(K key, V value) { System.out.println("Key: " + key + ", Value: " + value); } public static void main(String[] args) { GenericMethodExample example = new GenericMethodExample(); String[] names = {"Alice", "Bob", "Charlie"}; example.printArray(names); Integer first = example.getFirst(new Integer[]{1, 2, 3}); example.printPair("ID", 1001); } } ``` ### 类型参数的约束 你可以使用 `extends` 关键字对类型参数进行约束: ```java // T 必须是 Number 的子类 public class NumberBox { private T value; public double getDoubleValue() { return value.doubleValue(); } } // 使用 NumberBox intBox = new NumberBox<>(); // ✅ NumberBox doubleBox = new NumberBox<>(); // ✅ // NumberBox stringBox = new NumberBox<>(); // ❌ 编译错误 ``` ## 通配符(Wildcards) 泛型中的通配符 `?` 表示**未知类型**,它提供了更灵活的类型兼容性。 ### 无界通配符 `?` ```java public void printList(List list) { for (Object obj : list) { System.out.println(obj); } } // 可以接受任何类型的 List printList(new ArrayList()); printList(new ArrayList()); ``` ### 上界通配符 `? extends T` `? extends T` 表示**T 的子类型**,适用于读取数据的场景: ```java // 可以接受 Number 及其子类型的 List public double sumNumbers(List numbers) { double sum = 0; for (Number num : numbers) { sum += num.doubleValue(); } return sum; } // 使用 List ints = Arrays.asList(1, 2, 3); List doubles = Arrays.asList(1.1, 2.2, 3.3); sumNumbers(ints); // ✅ Integer 是 Number 的子类 sumNumbers(doubles); // ✅ Double 是 Number 的子类 ``` ### 下界通配符 `? super T` `? super T` 表示**T 的父类型**,适用于写入数据的场景: ```java // 可以接受 Integer 及其父类型的 List public void addIntegers(List list) { list.add(1); list.add(2); list.add(3); } // 使用 List numbers = new ArrayList<>(); List objects = new ArrayList<>(); addIntegers(numbers); // ✅ Number 是 Integer 的父类 addIntegers(objects); // ✅ Object 是 Integer 的父类 // addIntegers(new ArrayList()); // ❌ 编译错误 ``` ## PECS 原则 PECS 是 Java 泛型中最重要也最实用的原则,它是 **Producer-Extends, Consumer-Super** 的缩写: | 原则 | 含义 | 使用场景 | |------|------|---------| | **Producer-Extends** | 如果数据从集合中产出(读取),使用 `? extends T` | 方法参数用于读取 | | **Consumer-Super** | 如果数据被消费(写入),使用 `? super T` | 方法参数用于写入 | ### 实际案例:Collections.copy Java 标准库中的 `Collections.copy` 方法完美诠释了 PECS 原则: ```java public static void copy(List dest, List src) { for (int i = 0; i < src.size(); i++) { dest.set(i, src.get(i)); } } ``` 分析: - `src`(源列表)是**生产者**,从中读取数据,所以用 `? extends T` - `dest`(目标列表)是**消费者**,向其中写入数据,所以用 `? super T` ### 实战示例 ```java public class PECSExample { // Producer: 从列表中读取数据 public static double sumOfList(List list) { double sum = 0.0; for (Number n : list) { sum += n.doubleValue(); } return sum; } // Consumer: 向列表中写入数据 public static void addNumbers(List list) { for (int i = 1; i <= 5; i++) { list.add(i); } } public static void main(String[] args) { // Producer 示例 List ints = Arrays.asList(1, 2, 3); List doubles = Arrays.asList(1.1, 2.2, 3.3); System.out.println(sumOfList(ints)); // 6.0 System.out.println(sumOfList(doubles)); // 6.6 // Consumer 示例 List numbers = new ArrayList<>(); addNumbers(numbers); System.out.println(numbers); // [1, 2, 3, 4, 5] List objects = new ArrayList<>(); addNumbers(objects); System.out.println(objects); // [1, 2, 3, 4, 5] } } ``` ## 类型擦除(Type Erasure) Java 泛型采用了**类型擦除**机制来实现向后兼容。在编译期,所有泛型信息都会被擦除,替换为它们的上界(通常是 `Object`): ```java // 编译前 List strings = new ArrayList<>(); String s = strings.get(0); // 编译后(类型擦除) List strings = new ArrayList(); String s = (String) strings.get(0); // 自动插入类型转换 ``` ### 类型擦除的影响 1. **不能使用基本类型**:`List` 是错误的,必须使用 `List` 2. **运行时类型检查受限**:`instanceof List` 是非法的 3. **不能创建泛型数组**:`new T[10]` 是非法的 4. **可以通过反射绕过泛型检查**: ```java List strings = new ArrayList<>(); // strings.add(42); // 编译错误 // 但可以通过反射绕过 Method m = strings.getClass().getMethod("add", Object.class); m.invoke(strings, 42); // 运行时成功添加 Integer! ``` ## 泛型与继承 泛型类型之间**不协变**(not covariant): ```java List objects = new ArrayList(); // ❌ 编译错误 ``` 虽然 `String` 是 `Object` 的子类,但 `List` 并不是 `List` 的子类。这是为了防止以下运行时错误: ```java // 假设允许协变 List strings = new ArrayList<>(); List objects = strings; // 假设可以 objects.add(42); // 向字符串列表中添加整数! String s = strings.get(0); // ClassCastException! ``` ## 总结 Java 泛型是类型安全的基石,掌握它需要理解以下核心概念: | 概念 | 说明 | 示例 | |------|------|------| | 泛型类 | 类级别的类型参数 | `class Box` | | 泛型方法 | 方法级别的类型参数 | ` T method(T arg)` | | `? extends T` | 上界通配符,用于读取 | `List` | | `? super T` | 下界通配符,用于写入 | `List` | | PECS | Producer-Extends, Consumer-Super | `copy(dest, src)` | | 类型擦除 | 编译期擦除泛型信息 | `List` → `List` | > **最佳实践**:始终遵循 PECS 原则设计 API,使用通配符增加 API 的灵活性,但不要过度使用复杂的泛型嵌套,保持代码的可读性。 --- *本文首发于 Yggdrasil 博客* $doc$, NULL, 'published', NOW() - INTERVAL '2 days', NOW() - INTERVAL '2 days', NOW() - INTERVAL '2 days' ), ( 1, 'C++ RAII 与智能指针:现代 C++ 内存管理完全指南', 'cpp-raii', '深入理解 C++ 的 RAII 原则、智能指针(unique_ptr、shared_ptr、weak_ptr)以及三/五/零法则,写出内存安全的现代 C++ 代码。', $doc$ # C++ RAII 与智能指针:现代 C++ 内存管理完全指南 C++ 是一门赋予程序员极大自由度的语言,这种自由既带来了高性能,也带来了内存管理的挑战。手动管理内存容易引发内存泄漏、悬垂指针、双重释放等问题。为了解决这些问题,C++ 社区发展出了 **RAII**(Resource Acquisition Is Initialization,资源获取即初始化)原则,以及一套智能指针工具。掌握这些现代 C++ 特性,是编写健壮 C++ 代码的关键。 本文将系统介绍 RAII 原则、智能指针的使用、三/五/零法则,以及实际开发中的最佳实践。 ## 什么是 RAII? RAII 是 C++ 的核心编程范式,由 Bjarne Stroustrup 提出。其核心思想是:**将资源的生命周期与对象的生命周期绑定,在对象构造时获取资源,在对象析构时释放资源**。 由于 C++ 的栈对象在离开作用域时会自动调用析构函数,这一机制天然地保证了资源的正确释放,即使在发生异常的情况下也是如此。 ### 基础示例:文件句柄 ```cpp #include #include class FileHandle { FILE* file; public: // 构造函数:获取资源 explicit FileHandle(const char* filename, const char* mode = "r") : file(std::fopen(filename, mode)) { if (!file) { throw std::runtime_error("无法打开文件"); } } // 析构函数:释放资源 ~FileHandle() { if (file) { std::fclose(file); } } // 禁用拷贝(资源不可复制) FileHandle(const FileHandle&) = delete; FileHandle& operator=(const FileHandle&) = delete; // 允许移动(C++11) FileHandle(FileHandle&& other) noexcept : file(other.file) { other.file = nullptr; } FileHandle& operator=(FileHandle&& other) noexcept { if (this != &other) { if (file) std::fclose(file); file = other.file; other.file = nullptr; } return *this; } FILE* get() const { return file; } }; // 使用 void processFile(const char* filename) { FileHandle fh(filename); // 打开文件 // 处理文件... // 即使发生异常,fh 的析构函数也会确保文件被关闭 } ``` ## 三/五/零法则 ### 三法则(Rule of Three) 在 C++98/03 时代,如果一个类需要自定义以下三个函数中的任意一个,通常需要定义全部三个: 1. **析构函数**(Destructor) 2. **拷贝构造函数**(Copy Constructor) 3. **拷贝赋值运算符**(Copy Assignment Operator) 这是因为这三个函数都与资源管理密切相关。如果你需要自定义其中一个,说明你的类管理着某种资源,因此三个都需要自定义。 ### 五法则(Rule of Five) C++11 引入了移动语义,五法则在三法则基础上增加了: 4. **移动构造函数**(Move Constructor) 5. **移动赋值运算符**(Move Assignment Operator) ```cpp class Resource { int* data; size_t size; public: // 构造函数 explicit Resource(size_t n) : data(new int[n]), size(n) {} // 1. 析构函数 ~Resource() { delete[] data; } // 2. 拷贝构造函数 Resource(const Resource& other) : data(new int[other.size]), size(other.size) { std::copy(other.data, other.data + size, data); } // 3. 拷贝赋值运算符 Resource& operator=(const Resource& other) { if (this != &other) { Resource temp(other); // 拷贝并交换惯用法 std::swap(data, temp.data); std::swap(size, temp.size); } return *this; } // 4. 移动构造函数 Resource(Resource&& other) noexcept : data(other.data), size(other.size) { other.data = nullptr; other.size = 0; } // 5. 移动赋值运算符 Resource& operator=(Resource&& other) noexcept { if (this != &other) { delete[] data; data = other.data; size = other.size; other.data = nullptr; other.size = 0; } return *this; } }; ``` ### 零法则(Rule of Zero) 现代 C++ 的最佳实践是**零法则**: > **如果一个类不需要自定义析构函数、拷贝/移动构造函数或赋值运算符,那就不要自定义。** 通过使用智能指针和标准库容器,你可以让编译器自动生成这些函数: ```cpp // ✅ 遵循零法则 class ModernResource { std::unique_ptr data; size_t size; public: explicit ModernResource(size_t n) : data(std::make_unique(n)), size(n) {} // 编译器自动生成的析构函数、拷贝/移动函数都能正确工作 // 因为 unique_ptr 已经正确管理了内存 }; ``` ## 智能指针 C++11 引入了三种智能指针,分别适用于不同的所有权模型: ### std::unique_ptr:独占所有权 `unique_ptr` 表示对对象的**独占所有权**,同一时间只能有一个 `unique_ptr` 指向给定对象。当 `unique_ptr` 被销毁时,它所指向的对象也会被自动删除。 ```cpp #include #include class Widget { public: Widget() { std::cout << "Widget 构造\\n"; } ~Widget() { std::cout << "Widget 析构\\n"; } void doSomething() { std::cout << "Widget 工作中\\n"; } }; void uniquePtrDemo() { // 创建 unique_ptr std::unique_ptr ptr1 = std::make_unique(); ptr1->doSomething(); // 转移所有权 std::unique_ptr ptr2 = std::move(ptr1); // ptr1 现在为空 // ptr2->doSomething(); // ✅ // 自动释放 } // Widget 在此处被销毁 // 工厂函数返回 unique_ptr std::unique_ptr createWidget() { return std::make_unique(); } ``` **最佳实践**: - 默认使用 `std::make_unique` 创建(C++14) - 用于表示独占所有权 - 作为函数参数传递时,使用 `std::move` 转移所有权 ### std::shared_ptr:共享所有权 `shared_ptr` 通过**引用计数**实现共享所有权。多个 `shared_ptr` 可以同时指向同一个对象,当最后一个 `shared_ptr` 被销毁时,对象才被删除。 ```cpp #include #include void sharedPtrDemo() { // 创建 shared_ptr std::shared_ptr ptr1 = std::make_shared(); { std::shared_ptr ptr2 = ptr1; // 引用计数 +1 std::cout << "引用计数: " << ptr1.use_count() << "\\n"; // 2 } // ptr2 销毁,引用计数 -1 std::cout << "引用计数: " << ptr1.use_count() << "\\n"; // 1 } // Widget 在此处被销毁 ``` ### std::weak_ptr:弱引用 `weak_ptr` 是一种**不控制对象生命周期**的智能指针。它指向一个由 `shared_ptr` 管理的对象,但不会增加引用计数。它主要用于解决 **循环引用** 问题: ```cpp #include #include class B; // 前向声明 class A { public: std::shared_ptr b_ptr; ~A() { std::cout << "A 析构\\n"; } }; class B { public: // 使用 weak_ptr 避免循环引用 std::weak_ptr a_ptr; ~B() { std::cout << "B 析构\\n"; } }; void weakPtrDemo() { { std::shared_ptr a = std::make_shared(); std::shared_ptr b = std::make_shared(); a->b_ptr = b; b->a_ptr = a; // weak_ptr,不增加引用计数 // 检查对象是否还存在 if (auto shared = b->a_ptr.lock()) { std::cout << "A 仍然存在\\n"; } } // A 和 B 都被正确析构 } ``` ## 智能指针对比 | 特性 | `unique_ptr` | `shared_ptr` | `weak_ptr` | |------|-------------|-------------|-----------| | 所有权 | 独占 | 共享 | 无(弱引用) | | 引用计数 | 无 | 有 | 无 | | 可拷贝 | ❌ | ✅ | ✅ | | 可移动 | ✅ | ✅ | ✅ | | 内存开销 | 最小 | 引用计数 + 控制块 | 最小 | | 适用场景 | 独占资源 | 共享资源 | 打破循环引用 | ## RAII 在标准库中的应用 C++ 标准库大量使用了 RAII 原则: ```cpp // 容器 std::vector vec = {1, 2, 3}; // 自动管理内存 // 锁 std::mutex mtx; { std::lock_guard lock(mtx); // 自动加锁 // 临界区... } // 自动解锁 // 文件流 { std::ofstream file("data.txt"); file << "Hello, RAII!"; } // 自动关闭文件 // 线程 { std::thread t([]() { std::cout << "后台任务\\n"; }); t.join(); // 或者使用 RAII 包装器 } ``` ## 总结 现代 C++ 的内存管理已经不再是噩梦。通过遵循以下原则,你可以写出既高效又安全的代码: 1. **优先使用智能指针**:`unique_ptr` > `shared_ptr` > 原始指针 2. **遵循零法则**:使用标准库工具管理资源 3. **使用 `std::make_unique` 和 `std::make_shared`**:异常安全且高效 4. **理解所有权语义**:明确谁拥有资源,谁只是借用 5. **注意循环引用**:使用 `weak_ptr` 打破循环 > **C++ 之父的名言**:"C++ 中,资源管理即对象管理。" —— Bjarne Stroustrup --- *本文首发于 Yggdrasil 博客* $doc$, NULL, 'published', NOW() - INTERVAL '1 day', NOW() - INTERVAL '1 day', NOW() - INTERVAL '1 day' ), ( 1, 'Haskell 纯函数、惰性求值与 Monad:函数式编程的精髓', 'haskell-pure-functions', '深入理解 Haskell 的纯函数、惰性求值、高阶函数和 Monad,探索函数式编程的核心思想。', $doc$ # Haskell 纯函数、惰性求值与 Monad:函数式编程的精髓 Haskell 是一门**纯函数式编程语言**,以严格的数学基础和优雅的设计著称。与命令式语言不同,Haskell 强调函数的数学本质:**函数只是从输入到输出的映射**,没有副作用、没有状态变化、没有隐式的执行顺序。这种纯粹性带来了前所未有的代码可预测性和可组合性。 本文将深入探讨 Haskell 的三大核心特性:纯函数、惰性求值和 Monad,帮助你理解函数式编程的精髓。 ## 纯函数(Pure Functions) 纯函数是函数式编程的基石。一个函数是纯函数,当且仅当满足两个条件: 1. **引用透明**(Referential Transparency):对于相同的输入,永远返回相同的输出 2. **无副作用**(No Side Effects):不修改外部状态,不执行 I/O 操作 ### 纯函数示例 ```haskell -- 纯函数:给定输入,总有确定的输出 factorial :: Integer -> Integer factorial 0 = 1 factorial n = n * factorial (n - 1) -- 另一个纯函数 square :: Num a => a -> a square x = x * x -- 纯函数可以安全地被替换为其结果(引用透明) -- square 5 总是可以被替换为 25 ``` ### 纯函数的优势 | 特性 | 说明 | |------|------| | 可测试性 | 无需模拟外部状态,输入确定即可测试 | | 可缓存性 | 结果可以被记忆化(memoization) | | 可并行性 | 无共享状态,天然适合并行计算 | | 可组合性 | 函数可以像乐高积木一样组合 | ## 高阶函数(Higher-Order Functions) Haskell 中的函数是一等公民,可以作为参数传递,也可以作为返回值: ```haskell -- map:对列表每个元素应用函数 map :: (a -> b) -> [a] -> [b] doubleAll = map (* 2) -- doubleAll [1, 2, 3] => [2, 4, 6] -- filter:根据条件过滤列表 filter :: (a -> Bool) -> [a] -> [a] evens = filter even -- evens [1..10] => [2, 4, 6, 8, 10] -- foldl / foldr:列表归约 sumList = foldl (+) 0 -- sumList [1, 2, 3, 4] => 10 -- 函数组合 (.) :: (b -> c) -> (a -> b) -> a -> c f . g = \\x -> f (g x) -- 使用函数组合 process = sum . map square . filter even -- process [1..10] = sum (map square (filter even [1..10])) ``` ### 自定义高阶函数 ```haskell -- 将函数应用两次 applyTwice :: (a -> a) -> a -> a applyTwice f x = f (f x) -- 使用 applyTwice (+ 3) 10 -- 16 applyTwice reverse [1, 2, 3] -- [1, 2, 3] -- 柯里化(Currying) add :: Int -> Int -> Int add x y = x + y -- add 5 是一个接收 Int 返回 Int 的函数 addFive = add 5 addFive 3 -- 8 ``` ## 惰性求值(Lazy Evaluation) Haskell 默认采用**惰性求值**(Lazy Evaluation),也称为**按需调用**(Call by Need)。这意味着表达式在真正需要其值时才会被计算。 ### 无限列表 惰性求值最惊人的特性之一,就是可以定义**无限数据结构**: ```haskell -- 无限自然数列表 nats :: [Integer] nats = [0..] -- 无限偶数列表 evens :: [Integer] evens = [0, 2..] -- 无限斐波那契数列 fibs :: [Integer] fibs = 0 : 1 : zipWith (+) fibs (tail fibs) -- 使用 take 只取有限部分 main = do print $ take 10 nats -- [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] print $ take 10 evens -- [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] print $ take 15 fibs -- [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377] ``` ### 列表推导式 Haskell 的列表推导式(List Comprehension)是处理集合的强大工具: ```haskell -- 基本列表推导式 squares = [x^2 | x <- [1..10]] -- [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] -- 带过滤条件 evenSquares = [x^2 | x <- [1..20], even x] -- [4, 16, 36, 64, 100, 144, 196, 256, 324, 400] -- 多生成器 pairs = [(x, y) | x <- [1..3], y <- ['a', 'b']] -- [(1,'a'), (1,'b'), (2,'a'), (2,'b'), (3,'a'), (3,'b')] -- 使用 let 绑定 result = [let y = x * 2 in (x, y) | x <- [1..5]] -- [(1,2), (2,4), (3,6), (4,8), (5,10)] ``` ### 惰性求值的实际应用 ```haskell -- 只计算需要的部分 firstEvenSquareOver100 = head [x^2 | x <- [1..], even x, x^2 > 100] -- 结果为 144(12^2),不需要计算 [1..] 的所有元素 -- 短路求值 and' :: [Bool] -> Bool and' = foldr (&&) True -- and' (False : undefined) => False -- 不需要计算 undefined,因为第一个 False 已经决定了结果 ``` ## 类型系统与类型类 Haskell 拥有强大的静态类型系统和类型推断能力: ```haskell -- 代数数据类型(Algebraic Data Types) data Shape = Circle Float | Rectangle Float Float | Triangle Float Float Float deriving (Show, Eq) area :: Shape -> Float area (Circle r) = pi * r * r area (Rectangle w h) = w * h area (Triangle a b c) = let s = (a + b + c) / 2 in sqrt (s * (s - a) * (s - b) * (s - c)) -- 类型类(Type Classes) class Describable a where describe :: a -> String instance Describable Shape where describe (Circle r) = "半径为 " ++ show r ++ " 的圆" describe (Rectangle w h) = show w ++ " x " ++ show h ++ " 的矩形" ``` ## Monad:处理副作用的优雅方式 纯函数不能执行 I/O 操作,但程序总要与外界交互。Haskell 使用 **Monad** 来在纯函数式框架内处理副作用。 ### Maybe Monad:处理可能失败的计算 ```haskell -- 安全除法 safeDiv :: Int -> Int -> Maybe Int safeDiv _ 0 = Nothing safeDiv a b = Just (a `div` b) -- 使用 do 语法串联 Maybe 计算 calculate :: Int -> Int -> Int -> Maybe Int calculate x y z = do a <- safeDiv x y -- 如果失败,整个计算返回 Nothing b <- safeDiv a z return (b + 1) -- 示例 calculate 10 2 3 -- Just 2 calculate 10 0 3 -- Nothing calculate 10 2 0 -- Nothing ``` ### IO Monad:处理输入输出 ```haskell -- IO 是一个 Monad,将副作用操作封装起来 greet :: IO () greet = do putStrLn "请输入你的名字:" name <- getLine putStrLn $ "你好, " ++ name ++ "!" -- 使用 >>=(bind)操作符 main :: IO () main = putStrLn "Hello" >>= \_ -> putStrLn "World" ``` ### 常用 Monad | Monad | 用途 | 示例 | |-------|------|------| | `Maybe` | 可能失败的计算 | `safeDiv`、`lookup` | | `Either` | 带错误信息的计算 | `parseNumber` | | `IO` | 输入输出操作 | `readFile`、`putStrLn` | | `List` | 非确定性计算 | 多个结果 | | `State` | 状态传递 | 计数器、随机数生成 | | `Reader` | 环境读取 | 配置读取 | | `Writer` | 日志记录 | 追踪计算过程 | ## 总结 Haskell 的函数式编程范式带来了全新的编程思维方式: | 概念 | 核心思想 | 优势 | |------|---------|------| | 纯函数 | 无副作用、引用透明 | 可预测、可测试、可并行 | | 惰性求值 | 按需计算 | 无限数据结构、短路求值 | | 高阶函数 | 函数作为一等公民 | 强大的组合能力 | | 类型系统 | 强类型、类型推断 | 编译期捕获错误 | | Monad | 在纯函数框架内处理副作用 | 优雅的副作用管理 | > "学习 Haskell 不是为了在工作中使用它,而是为了以全新的方式思考编程。" —— 匿名 Haskell 可能不是最适合所有场景的语言,但它所倡导的函数式编程思想——不可变性、纯函数、组合——已经深刻影响了现代编程语言的设计。从 JavaScript 的 `map`/`filter`/`reduce`,到 Java 的 Stream API,再到 Rust 的迭代器,函数式编程的思想无处不在。 --- *本文首发于 Yggdrasil 博客* $doc$, NULL, 'published', NOW() - INTERVAL '12 hours', NOW() - INTERVAL '12 hours', NOW() - INTERVAL '12 hours' ), ( 1, 'Ruby 元编程:打开动态语言的黑箱', 'ruby-metaprogramming', 'Ruby 被誉为"程序员的最好朋友",其强大的元编程能力让代码更加灵活和富有表现力。本文深入探讨 define_method、method_missing、类_eval、模块混入等核心元编程技术。', $doc$ # Ruby 元编程:打开动态语言的黑箱 Ruby 是一门充满魅力的动态语言,Matz(松本行弘)在设计 Ruby 时的理念是:**让编程变得快乐**。这种快乐很大程度上来自于 Ruby 强大的**元编程**(Metaprogramming)能力——即编写能够编写代码的代码。 元编程并非 Ruby 独有,但 Ruby 的元编程能力在众多语言中出类拔萃。Rails 框架的成功很大程度上归功于其巧妙运用元编程实现的"约定优于配置"哲学。 本文将深入探讨 Ruby 元编程的核心技术,帮助你理解这门动态语言的本质。 ## 什么是元编程? 元编程是指在运行时创建、修改或分析代码的技术。在 Ruby 中,几乎所有事物都是对象,包括类本身。这意味着你可以像操作普通对象一样操作类——添加方法、修改方法、甚至在运行时创建全新的类。 ```ruby # 类也是对象! puts String.class # => Class puts Class.class # => Class puts Object.class # => Class ``` ## 动态方法定义 ### define_method `define_method` 允许在运行时动态定义方法: ```ruby class Robot ACTIONS = [:walk, :run, :jump, :fly] ACTIONS.each do |action| define_method(action) do |*args| speed = args.first || "normal" "🤖 Robot is #{action}ing at #{speed} speed!" end end end robot = Robot.new puts robot.walk # => 🤖 Robot is walking at normal speed! puts robot.run("fast") # => 🤖 Robot is running at fast speed! puts robot.fly("super") # => 🤖 Robot is flying at super speed! ``` 这种技术在框架开发中极为常见。例如,ActiveRecord 的 `find_by_*` 方法就是动态生成的。 ### method_missing:拦截不存在的方法 `method_missing` 是 Ruby 元编程中最强大也最危险的工具。当调用一个不存在的方法时,Ruby 会将调用转发给 `method_missing`: ```ruby class DynamicFinder def method_missing(name, *args, &block) if name.to_s.start_with?("find_by_") attribute = name.to_s.sub("find_by_", "") "Looking for record where #{attribute} = #{args.first}" else super # 如果不是我们处理的格式,交给父类处理 end end def respond_to_missing?(name, include_private = false) name.to_s.start_with?("find_by_") || super end end finder = DynamicFinder.new puts finder.find_by_name("Alice") # => Looking for record where name = Alice puts finder.find_by_email("a@b.c") # => Looking for record where email = a@b.c ``` ### const_missing:动态加载常量 ```ruby module AutoLoader def self.const_missing(name) file = name.to_s.downcase require_relative "./#{file}" const_get(name) rescue LoadError super end end ``` ## 打开类(Open Classes) Ruby 允许随时重新打开已存在的类并添加或修改方法,这被称为**猴子补丁**(Monkey Patching): ```ruby class String def shout upcase + "!!!" end def reverse_words split.reverse.join(" ") end def to_slug downcase.strip.gsub(/\s+/, '-').gsub(/[^\w-]/, '') end end puts "hello world".shout # => HELLO WORLD!!! puts "hello world".reverse_words # => world hello puts "Hello World!".to_slug # => hello-world ``` ### 使用 Refinement 安全地扩展 为了避免猴子补丁污染全局命名空间,Ruby 2.0 引入了 **Refinement**: ```ruby module StringExtensions refine String do def shout upcase + "!!!" end end end class MyApp using StringExtensions def self.greet(name) name.shout end end puts MyApp.greet("hello") # => HELLO!!! puts "hello".shout rescue puts "No method error" # => No method error ``` ## 类_eval 和 instance_eval Ruby 提供了多种在特定上下文中执行代码的方式: ### class_eval(或 module_eval) 在类的上下文中执行代码,可以访问类的私有方法: ```ruby class Person def initialize(name) @name = name end end # 为 Person 类动态添加方法 Person.class_eval do attr_reader :name def greet "Hello, I'm #{@name}!" end def self.species "Homo sapiens" end end person = Person.new("Alice") puts person.name # => Alice puts person.greet # => Hello, I'm Alice! puts Person.species # => Homo sapiens ``` ### instance_eval 在对象的上下文中执行代码: ```ruby class Dog def initialize(name) @name = name end end dog = Dog.new("Buddy") dog.instance_eval do def bark "#{@name} says: Woof!" end end puts dog.bark # => Buddy says: Woof! # 注意:bark 方法只存在于这个实例上,其他 Dog 实例没有 ``` ### instance_exec(带参数) ```ruby class Calculator def initialize(value) @value = value end end calc = Calculator.new(10) result = calc.instance_exec(5) do |n| @value + n end puts result # => 15 ``` ## 模块与混入(Mixins) Ruby 不支持多重继承,但通过模块混入实现了更灵活的功能复用: ```ruby module Loggable def log(message) puts "[#{Time.now}] #{self.class}: #{message}" end def self.included(base) puts "#{base} included #{self}" end end module Validatable def validate! raise "Invalid!" unless valid? end end class User include Loggable # 实例方法 extend Validatable # 类方法 def valid? true end end user = User.new user.log("Created") # => [2024-...] User: Created ``` ### prepend:方法前置 Ruby 2.0 引入了 `prepend`,可以将模块的方法插入到类的方法链前面: ```ruby module Logging def save puts "Before save..." super puts "After save..." end end class Article prepend Logging def save puts "Saving article..." end end article = Article.new article.save # => Before save... # => Saving article... # => After save... ``` ## 元编程在 Rails 中的应用 Rails 是 Ruby 元编程能力的最佳展示: ```ruby # ActiveRecord 动态属性访问 user = User.find(1) user.name # 动态生成 getter user.name = "Alice" # 动态生成 setter user.save # 动态生成 SQL # 关联宏 class User < ApplicationRecord has_many :posts has_many :comments belongs_to :company end # 这背后使用元编程动态创建了 posts、comments、company 等方法 ``` ## 元编程最佳实践 | 技术 | 适用场景 | 注意事项 | |------|---------|---------| | `define_method` | 批量生成相似方法 | 比 `class_eval` 更清洁 | | `method_missing` | 实现动态 API | 始终实现 `respond_to_missing?` | | `class_eval` | 动态修改类 | 会改变类的全局行为 | | `instance_eval` | DSL 设计 | 注意 `self` 的变化 | | Refinement | 安全扩展内置类 | 只在 `using` 的作用域内生效 | ## 总结 Ruby 的元编程能力让这门语言充满了表达力和灵活性。通过动态方法定义、方法拦截、类修改和模块混入,你可以写出极其简洁和富有表现力的代码。 但元编程也是一把双刃剑: - **优点**:代码更 DRY、更表达力、框架更强大 - **缺点**:调试困难、IDE 支持有限、可读性降低 > "Ruby 让编程变得有趣,但元编程让 Ruby 变得强大。" —— 改编自 Matz 在使用元编程时,请始终牢记:**清晰的代码胜过聪明的代码**。元编程应该用来消除重复、提高抽象层次,而不是用来炫耀技巧。 --- *本文首发于 Yggdrasil 博客* $doc$, NULL, 'draft', NULL, NOW(), NOW() ), ( 1, 'Zig 显式内存管理与编译期编程:系统级语言的新选择', 'zig-memory-management', 'Zig 是一门注重可读性、健壮性和最优性的系统级编程语言。没有隐式内存分配、没有隐藏的控制流,让每一行代码都清晰可控。', $doc$ # Zig 显式内存管理与编译期编程:系统级语言的新选择 Zig 是一门相对年轻的系统级编程语言,由 Andrew Kelley 于 2016 年开始开发。它的设计哲学可以概括为:**显式优于隐式**、**可读性优先于奇技淫巧**、**健壮性优先于方便性**。 与 C 语言相比,Zig 提供了更好的安全性、更强大的元编程能力和更友好的构建系统。与 Rust 相比,Zig 更加简单直接,没有复杂的所有权系统,而是通过显式的内存分配和错误处理来保证安全。 本文将深入探讨 Zig 的核心特性,包括显式内存管理、错误处理、编译期编程和 C 互操作性。 ## 显式内存分配 Zig 最显著的特点之一是**没有隐式内存分配**。在 Zig 中,所有可能分配内存的操作都必须显式地接收一个分配器参数。 ### 基础内存分配 ```zig const std = @import("std"); pub fn main() !void { // 创建通用分配器 var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); // 动态分配内存 const memory = try allocator.alloc(u8, 100); defer allocator.free(memory); // 确保内存被释放 // 使用内存... @memcpy(memory, "Hello, Zig!"); std.debug.print("{s}\n", .{memory}); } ``` ### 不同的分配器策略 Zig 标准库提供了多种分配器,适用于不同场景: | 分配器 | 用途 | 特点 | |--------|------|------| | `GeneralPurposeAllocator` | 通用场景 | 安全、支持内存泄漏检测 | | `PageAllocator` | 大块内存 | 直接向 OS 申请 | | `FixedBufferAllocator` | 嵌入式/无堆环境 | 使用预分配缓冲区 | | `ArenaAllocator` | 批量分配/释放 | 一次性释放所有内存 | | `c_allocator` | C 互操作 | 使用 malloc/free | ```zig // 使用 Arena 分配器 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); // 分配多个对象 const str1 = try allocator.dupe(u8, "Hello"); const str2 = try allocator.dupe(u8, "World"); // 所有内存会在 arena.deinit() 时一次性释放 ``` ### 错误处理与内存安全 Zig 的 `try` 关键字确保了错误被传播,而 `defer` 确保了资源被释放,即使在错误路径上: ```zig fn processData(allocator: std.mem.Allocator, input: []const u8) !void { const buffer = try allocator.alloc(u8, input.len * 2); defer allocator.free(buffer); // 无论成功与否都会执行 if (input.len == 0) { return error.EmptyInput; // buffer 仍会被释放 } // 处理数据... @memcpy(buffer[0..input.len], input); } ``` ## 错误处理 Zig 的错误处理机制结合了错误联合类型(Error Union Types)和 `try/catch` 风格: ### 错误联合类型 ```zig const FileOpenError = error{ AccessDenied, OutOfMemory, FileNotFound, NotAFile, }; fn openFile(path: []const u8) FileOpenError!std.fs.File { return std.fs.cwd().openFile(path, .{}); } ``` `FileOpenError!std.fs.File` 表示这个函数要么返回一个错误(`FileOpenError`),要么返回一个文件(`std.fs.File`)。 ### try 与 catch ```zig pub fn main() !void { // 使用 try:如果出错,立即返回错误 const file = try openFile("data.txt"); defer file.close(); // 使用 catch:处理特定错误 const alt_file = openFile("backup.txt") catch |err| { std.debug.print("打开备份失败: {}\n", .{err}); return; }; defer alt_file.close(); // 使用 catch 提供默认值 const content = readFile("config.txt") catch "default_config"; } ``` ### errdefer `errdefer` 只在函数返回错误时执行,非常适合错误路径上的清理: ```zig fn createUser(allocator: std.mem.Allocator, name: []const u8) !User { const user_name = try allocator.dupe(u8, name); errdefer allocator.free(user_name); // 只有出错时才释放 const user_email = try allocator.dupe(u8, "default@example.com"); errdefer allocator.free(user_email); return User{ .name = user_name, .email = user_email, }; } ``` ## 编译期编程(Comptime) Zig 的 `comptime` 关键字允许在编译期执行代码,这是 Zig 最强大的特性之一: ### 编译期计算 ```zig fn fibonacci(comptime n: u32) u32 { if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2); } const fib_10 = comptime fibonacci(10); // 编译期计算! ``` ### 类型作为参数 ```zig fn Vector(comptime T: type, comptime n: comptime_int) type { return struct { data: [n]T, pub fn add(self: @This(), other: @This()) @This() { var result: @This() = undefined; for (&result.data, self.data, other.data) |*r, a, b| { r.* = a + b; } return result; } }; } const Vec3f = Vector(f32, 3); var v1 = Vec3f{ .data = .{ 1, 2, 3 } }; var v2 = Vec3f{ .data = .{ 4, 5, 6 } }; const v3 = v1.add(v2); ``` ### 编译期反射 ```zig fn printStructInfo(comptime T: type) void { const info = @typeInfo(T); std.debug.print("Type: {s}\n", .{@typeName(T)}); if (info == .Struct) { std.debug.print("Fields:\n"); inline for (info.Struct.fields) |field| { std.debug.print(" {s}: {}\n", .{ field.name, field.type }); } } } ``` ## C 互操作性 Zig 与 C 的互操作性非常出色,可以直接导入 C 头文件并调用 C 函数: ```zig const c = @cImport({ @cInclude("stdio.h"); @cInclude("stdlib.h"); }); pub fn main() void { c.printf("Hello from C!\n"); const ptr = c.malloc(100); defer c.free(ptr); // Zig 可以直接处理 C 指针 const zig_slice = @as([*]u8, @ptrCast(ptr))[0..100]; } ``` ## Zig 的哲学 | 原则 | 说明 | 示例 | |------|------|------| | **显式优于隐式** | 没有隐式分配、没有隐式转换 | 所有分配器必须显式传递 | | **无隐藏控制流** | 没有运算符重载、没有隐式析构 | 资源释放用 `defer` | | **无隐藏分配** | 内存分配一目了然 | `ArrayList` 需要传入分配器 | | **可读性优先** | 代码应该像散文一样易读 | 简洁的语法,无冗余符号 | | **与 C 互操作** | 无缝集成现有 C 代码库 | `@cImport` 直接导入头文件 | ## 总结 Zig 是一门为系统编程而生的现代语言,它吸收了 C 的简洁和 Rust 的安全理念,同时保持了自己的独特风格: 1. **显式内存管理**:没有垃圾回收,没有隐式分配 2. **强大的错误处理**:错误联合类型 + try/catch 3. **编译期编程**:`comptime` 提供元编程能力 4. **出色的 C 互操作**:无缝集成现有生态系统 5. **简单直接**:没有复杂的所有权系统,只有清晰的规则 > **Zig 的设计理念**:"专注于调试你的应用程序而不是调试你的编程语言知识。" 对于需要高性能、低级别控制、但又不想处理 C 语言种种陷阱的项目,Zig 是一个极具吸引力的选择。 --- *本文首发于 Yggdrasil 博客* $doc$, NULL, 'published', NOW() - INTERVAL '6 hours', NOW() - INTERVAL '6 hours', NOW() - INTERVAL '6 hours' ), ( 1, 'Markdown 语法完全测试:渲染引擎兼容性验证', 'markdown-syntax-test', '一篇专门用于测试博客系统对 Markdown 各种语法元素支持情况的文章,包含文本格式、列表、代码块、表格、数学公式、脚注等完整测试。', $doc$ # Markdown 语法完全测试:渲染引擎兼容性验证 这是一篇专门用于测试 Markdown 渲染效果的文章。无论你使用的是 CommonMark、GitHub Flavored Markdown (GFM)、还是其他扩展方言,本文都涵盖了最全面的语法测试用例。 Markdown 的设计目标是**易读易写**,其语法灵感来源于纯文本电子邮件的格式。本文不仅展示各种语法元素,还说明了它们的使用场景和最佳实践。 ## 文本格式 Markdown 支持多种内联文本格式: 这是**粗体**(使用 `**` 或 `__` 包裹),这是*斜体*(使用 `*` 或 `_` 包裹),这是***粗斜体***(同时使用两者),这是~~删除线~~(使用 `~~` 包裹),这是`行内代码`(使用反引号包裹)。 ### 强调嵌套 你可以嵌套不同的格式:这是**粗体中的_斜体_**,这是*斜体中的**粗体***。 ### 转义字符 如果需要显示 Markdown 语法字符本身,可以使用反斜杠转义:\*这不是斜体\*,\`这不是代码\`。 ## 标题层级 Markdown 支持六级标题,使用 `#` 的数量表示层级: ### 三级标题 三级标题常用于文章的主要小节。 #### 四级标题 四级标题用于更细分的子节。 ##### 五级标题 五级标题用于细节描述。 ###### 六级标题 六级标题是最深层级,一般用于列表项内的标题。 ## 列表 ### 无序列表 无序列表使用 `-`、`+` 或 `*` 作为标记符: - 第一项:这是列表的第一项 - 第二项:包含子列表 - 嵌套项 1:使用两个空格缩进 - 嵌套项 2:可以继续嵌套 - 更深嵌套:三层缩进 - 混合使用不同标记符也可以 - 第三项:回到第一层 ### 有序列表 有序列表使用数字加句点: 1. 第一步:准备工作 2. 第二步:执行操作 1. 子步骤 A:检查环境 2. 子步骤 B:执行命令 3. 第三步:验证结果 ### 任务列表 GitHub Flavored Markdown 支持任务列表: - [x] 已完成任务:设置开发环境 - [x] 已完成任务:编写核心代码 - [ ] 未完成任务:编写单元测试 - [ ] 未完成任务:部署到生产环境 - [x] 已完成任务:编写文档 ## 引用块 引用块用于引用他人的话或强调重要内容: > 这是一段普通的引用块。引用块可以包含多行内容, > 每一行都以 `>` 开头。 > > > 这是嵌套引用块。在引用中再引用,形成层级结构。 > > 这在回复邮件或讨论中非常常见。 > > 回到第一层引用,继续你的论述。 ### 引用块中的其他元素 > **注意**:引用块中可以包含其他 Markdown 元素。 > > - 比如列表项 > - 比如 `行内代码` > - 比如 [链接](https://example.com) > > ```python > # 甚至可以包含代码块 > print("在引用块中执行代码") > ``` ## 代码块 代码块是技术文章的核心。Markdown 支持多种方式展示代码。 ### Python ```python def hello_world(name: str = "World") -> str: """返回问候语。""" return f"Hello, {name}!" class Greeter: def __init__(self, greeting: str = "Hello"): self.greeting = greeting def greet(self, name: str) -> str: return f"{self.greeting}, {name}!" if __name__ == "__main__": greeter = Greeter("Hi") print(greeter.greet("Alice")) ``` ### Rust ```rust fn main() { let name = "world"; println!("Hello, {}!", name); let numbers = vec![1, 2, 3, 4, 5]; let sum: i32 = numbers.iter().sum(); println!("Sum: {}", sum); } ``` ### JavaScript ```javascript async function fetchUserData(userId) { try { const response = await fetch(`/api/users/${userId}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.error("Failed to fetch user:", error); throw error; } } ``` ### Go ```go package main import ( "fmt" "time" ) func worker(id int, jobs <-chan int, results chan<- int) { for j := range jobs { fmt.Printf("Worker %d processing job %d\n", id, j) time.Sleep(time.Second) results <- j * 2 } } func main() { jobs := make(chan int, 100) results := make(chan int, 100) for w := 1; w <= 3; w++ { go worker(w, jobs, results) } for j := 1; j <= 9; j++ { jobs <- j } close(jobs) for a := 1; a <= 9; a++ { <-results } } ``` ### JSON ```json { "project": "Yggdrasil", "version": "1.0.0", "description": "A modern blog system built with Rust and Dioxus", "dependencies": { "frontend": "Dioxus 0.7", "backend": "tokio-postgres", "database": "PostgreSQL 15" }, "features": [ "Markdown support", "Server-side rendering", "Real-time preview" ] } ``` ### SQL ```sql -- 获取已发布的文章列表 SELECT p.id, p.title, p.slug, p.summary, p.published_at, u.username as author FROM posts p JOIN users u ON p.author_id = u.id WHERE p.status = 'published' AND p.deleted_at IS NULL ORDER BY p.published_at DESC LIMIT 20; ``` ### Bash ```bash #!/bin/bash # 脚本:备份数据库 echo "开始备份..." BACKUP_DIR="./backups" TIMESTAMP=$(date +%Y%m%d_%H%M%S) FILENAME="backup_${TIMESTAMP}.sql" mkdir -p "$BACKUP_DIR" pg_dump "$DATABASE_URL" > "$BACKUP_DIR/$FILENAME" if [ $? -eq 0 ]; then echo "✅ 备份成功: $FILENAME" ls -lh "$BACKUP_DIR/$FILENAME" else echo "❌ 备份失败" exit 1 fi ``` ### 行内代码高亮 你也可以在段落中使用行内代码,比如 `git status`、`:wq`、`<Route>`、`<Suspense>`。 ## 表格 Markdown 表格使用管道符 `|` 和连字符 `-` 构建: ### 基础表格 | 语言 | 类型 | 内存管理 | 并发模型 | 适用场景 | |------|------|----------|----------|---------| | Rust | 系统级 | 所有权系统 | Fearless Concurrency | 高性能系统、嵌入式 | | Go | 系统级 | 垃圾回收 | Goroutine + Channel | 云原生、微服务 | | Python | 高级动态 | 垃圾回收 (GIL) | 多进程/异步IO | 数据科学、Web开发 | | JavaScript | 高级动态 | 垃圾回收 | 事件循环 + Promise | Web前端、Node.js | | C++ | 系统级 | 手动/RAII | 线程 + 锁 | 游戏引擎、高频交易 | | Zig | 系统级 | 显式分配 | 线程 | 系统工具、嵌入式 | ### 对齐方式 | 左对齐 | 居中对齐 | 右对齐 | |:-------|:-------:|-------:| | 内容 1 | 内容 2 | 内容 3 | | A | B | 100 | | 长文本示例 | 居中显示 | 999.99 | ## 水平线 水平线用于分隔文章的不同部分: --- *** ___ ## 链接 Markdown 支持多种链接格式: ### 行内链接 [GitHub](https://github.com) - 世界上最流行的代码托管平台。 [相对链接](/) - 链接到网站首页。 [带标题的链接](https://example.com "示例网站") - 鼠标悬停显示标题。 ### 引用式链接 [Google][google-link] 和 [Bing][bing-link] 是两大搜索引擎。 [google-link]: https://google.com "Google 搜索" [bing-link]: https://bing.com "Bing 搜索" ## 图片 ![Markdown Logo](https://markdown-here.com/img/icon256.png) ## HTML 内嵌 某些场景下,你可能需要直接使用 HTML:

提示: 这是使用 HTML 创建的自定义样式块。当 Markdown 的表达能力不足时,可以直接嵌入 HTML。但请注意,这会降低内容的可移植性。

## 特殊字符 Markdown 和 HTML 实体: - 版权符号:© 2024 Yggdrasil - 注册商标:Markdown® - 商标符号:GitHub™ - 长破折号:这是—一个长破折号 - 短破折号:这是–一个短破折号 - 省略号:等等… ## 脚注 脚注是学术写作中常用的功能[^1]。你可以在同一条注释中引用多个脚注[^2][^3]。 [^1]: 这是第一个脚注的内容。脚注可以包含多行文字和格式。 [^2]: 脚注通常用于提供补充说明或引用来源。 [^3]: Markdown 的脚注语法在不同的渲染引擎中支持程度不同。 ## 总结 本文测试了 Markdown 的完整语法集: | 语法元素 | 支持情况 | 说明 | |---------|---------|------| | 文本格式 | ✅ | 粗体、斜体、删除线、行内代码 | | 标题 | ✅ | 六级标题支持 | | 列表 | ✅ | 无序、有序、嵌套、任务列表 | | 引用块 | ✅ | 支持嵌套引用 | | 代码块 | ✅ | 语法高亮 | | 表格 | ✅ | 对齐方式 | | 水平线 | ✅ | 多种分隔符 | | 链接 | ✅ | 行内和引用式 | | 图片 | ✅ | 外部图片 | | HTML | ✅ | 内嵌 HTML 块 | | 脚注 | ⚠️ | 依赖渲染引擎支持 | 如果你的渲染引擎正确显示了以上内容,说明它对 Markdown 的支持非常完善! --- *本文用于测试 Markdown 渲染引擎的兼容性。* $doc$, NULL, 'published', NOW() - INTERVAL '3 hours', NOW() - INTERVAL '3 hours', NOW() - INTERVAL '3 hours' ), ( 1, 'Rust 错误处理完全指南:Result、Option 与 thiserror', 'rust-error-handling', 'Rust 使用 Result 和 Option 类型来处理可能失败的操作和可能缺失的值,取代了传统的异常机制。本文深入讲解 Rust 的错误处理哲学和最佳实践。', $doc$ # Rust 错误处理完全指南:Result、Option 与 thiserror Rust 没有异常机制(Exception)。对于习惯了 Java、Python 或 JavaScript 的开发者来说,这可能需要一些适应。但 Rust 的类型驱动错误处理——通过 `Result` 和 `Option` 类型——实际上是一种更健壮、更明确的设计。 本文将深入探讨 Rust 的错误处理哲学,从基础的 `Option` 和 `Result` 到自定义错误类型,再到 `?` 操作符和第三方库 `thiserror`。 ## 为什么 Rust 没有异常? 传统的异常机制存在几个问题: 1. **不可见的控制流**:异常可以穿透任意层级的调用栈,使得代码路径难以追踪 2. **类型安全缺失**:编译器无法检查你是否处理了所有可能的异常 3. **性能开销**:异常处理通常需要运行时维护额外的栈信息 Rust 的解决方案是**将错误作为值返回**。如果一个函数可能失败,它的返回类型就明确地表示这一点。编译器会强迫你处理这个错误,否则代码无法通过编译。 ## Option 类型:处理可能缺失的值 `Option` 是 Rust 中表示"可能有值,也可能没有"的类型: ```rust enum Option { Some(T), // 有值 None, // 无值 } ``` ### 基础使用 ```rust fn find_char(s: &str, c: char) -> Option { s.find(c) // 如果找到返回 Some(index),否则返回 None } fn main() { // 使用 match 处理 Option match find_char("hello", 'e') { Some(index) => println!("✅ 找到字符在位置 {}", index), None => println!("❌ 未找到字符"), } // 使用 if let 简化(只关心 Some 的情况) if let Some(index) = find_char("hello", 'l') { println!("🎯 在位置 {}", index); } // while let:循环处理 let mut text = "hello"; while let Some(c) = text.chars().next() { println!("字符: {}", c); text = &text[1..]; } } ``` ### Option 的组合子 `Option` 提供了丰富的组合子方法来链式处理: ```rust fn main() { let maybe_number: Option = Some(5); // map:对 Some 中的值进行转换 let doubled = maybe_number.map(|n| n * 2); // Some(10) // and_then:链式调用返回 Option 的函数 let result = maybe_number .and_then(|n| if n > 3 { Some(n) } else { None }) .map(|n| n * 2); // unwrap_or:提供默认值 let value = maybe_number.unwrap_or(0); // 5 let empty = None::.unwrap_or(0); // 0 // unwrap_or_else:懒加载默认值 let lazy = None::.unwrap_or_else(|| expensive_computation()); // ok_or:将 Option 转换为 Result let result: Result = maybe_number.ok_or("值为空"); } ``` ## Result 类型:处理可能失败的操作 `Result` 是 Rust 中表示操作可能失败的核心类型: ```rust enum Result { Ok(T), // 成功 Err(E), // 失败,携带错误信息 } ``` ### 基础使用 ```rust use std::fs::File; use std::io::{self, Read}; fn read_username_from_file() -> Result { let mut file = File::open("hello.txt")?; let mut username = String::new(); file.read_to_string(&mut username)?; Ok(username) } ``` ### ? 操作符:错误传播 `?` 操作符是 Rust 错误处理中最常用的语法糖。它在 `Result` 为 `Ok` 时解包值,在 `Err` 时提前返回错误: ```rust fn read_config() -> Result { let content = std::fs::read_to_string("config.json")?; // 如果失败,直接返回 Err let config: Config = serde_json::from_str(&content)?; // 如果失败,直接返回 Err Ok(config) } ``` `?` 操作符的强大之处在于它可以自动进行错误类型的转换。只要当前函数的返回错误类型实现了 `From`,就可以使用 `?`。 ### Result 的组合子 与 `Option` 类似,`Result` 也有丰富的组合子: ```rust fn main() { let result: Result = Ok(42); // map:对 Ok 中的值进行转换 let doubled = result.map(|n| n * 2); // Ok(84) // map_err:对 Err 中的值进行转换 let with_prefix = Err("error").map_err(|e| format!("ERROR: {}", e)); // and_then:链式调用 let chained = Ok(5) .and_then(|n| if n > 0 { Ok(n * 2) } else { Err("负数") }); // unwrap_or / unwrap_or_else let value = result.unwrap_or(0); // 42 // expect:unwrap 的变体,可自定义 panic 消息 let file = std::fs::read_to_string("config.txt") .expect("config.txt 必须存在"); } ``` ## 自定义错误类型 在实际项目中,你可能需要定义自己的错误类型。Rust 提供了多种方式: ### 枚举错误类型 ```rust #[derive(Debug)] pub enum AppError { Io(std::io::Error), Parse(std::num::ParseIntError), InvalidInput(String), NotFound { resource: String, id: i64 }, } impl std::fmt::Display for AppError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { AppError::Io(e) => write!(f, "IO 错误: {}", e), AppError::Parse(e) => write!(f, "解析错误: {}", e), AppError::InvalidInput(msg) => write!(f, "无效输入: {}", msg), AppError::NotFound { resource, id } => write!(f, "{} 不存在: id={}", resource, id), } } } impl std::error::Error for AppError {} // 实现 From trait 以支持 ? 操作符 impl From for AppError { fn from(err: std::io::Error) -> Self { AppError::Io(err) } } impl From for AppError { fn from(err: std::num::ParseIntError) -> Self { AppError::Parse(err) } } ``` ### 使用 thiserror 简化 手动实现错误类型很繁琐。[`thiserror`](https://docs.rs/thiserror) 是一个宏库,可以大大简化这个过程: ```rust use thiserror::Error; #[derive(Error, Debug)] pub enum AppError { #[error("IO 错误: {0}")] Io(#[from] std::io::Error), #[error("解析错误: {0}")] Parse(#[from] std::num::ParseIntError), #[error("无效输入: {0}")] InvalidInput(String), #[error("{resource} 不存在: id={id}")] NotFound { resource: String, id: i64 }, #[error("数据库错误")] Database { #[from] source: sqlx::Error, }, } // 使用 fn load_user(id: i64) -> Result { let content = std::fs::read_to_string("users.json")?; // 自动转换 io::Error let users: Vec = serde_json::from_str(&content)?; users.into_iter() .find(|u| u.id == id) .ok_or_else(|| AppError::NotFound { resource: "User".to_string(), id, }) } ``` ### anyhow:快速原型开发 如果你不需要精细的错误分类,可以使用 [`anyhow`](https://docs.rs/anyhow) 库: ```rust use anyhow::{Context, Result}; fn main() -> Result<()> { let config = std::fs::read_to_string("config.toml") .with_context(|| "无法读取配置文件")?; let settings: Settings = toml::from_str(&config) .context("配置文件格式错误")?; println!("{:?}", settings); Ok(()) } ``` ## Option 与 Result 的转换 在实际代码中,经常需要在 `Option` 和 `Result` 之间转换: ```rust // Option -> Result let opt: Option = Some(5); let res: Result = opt.ok_or("值为空"); // Ok(5) let res2 = opt.ok_or_else(|| format!("{} 为空", "value")); // Ok(5) // Result -> Option let res: Result = Ok(5); let opt = res.ok(); // Some(5) let err_opt = res.err(); // None // 在迭代中收集 Result let numbers = vec!["1", "2", "3", "not_a_number"]; let parsed: Result, _> = numbers.iter() .map(|s| s.parse::()) .collect(); // 如果有任何 Err,返回第一个 Err ``` ## 错误处理最佳实践 | 场景 | 推荐方式 | 示例 | |------|---------|------| | 快速原型 | `anyhow::Result` | `fn main() -> anyhow::Result<()>` | | 库开发 | `thiserror` | 定义精细的错误枚举 | | 错误传播 | `?` 操作符 | `let x = may_fail()?;` | | 提供默认值 | `unwrap_or` | `let x = opt.unwrap_or(0);` | | 不可恢复错误 | `expect` | `let x = vec[0].expect("不能为空")` | | 可选值链 | `and_then` + `map` | `opt.and_then(f).map(g)` | ## 总结 Rust 的错误处理机制强迫开发者显式处理所有错误路径,这看似繁琐,实际上带来了巨大的好处: | 类型 | 表示 | 使用场景 | |------|------|---------| | `Option` | `Some(T)` / `None` | 可能缺失的值 | | `Result` | `Ok(T)` / `Err(E)` | 可能失败的操作 | | `?` 操作符 | 自动传播错误 | 函数内部错误处理 | | `thiserror` | 宏生成的错误类型 | 库开发 | | `anyhow` | 泛型错误类型 | 应用程序开发 | Rust 的类型驱动错误处理是一种**以编译期检查换取运行时安全**的设计哲学。虽然需要写更多的错误处理代码,但换来的是程序在运行时几乎不会因为未处理的错误而崩溃。 > "在 Rust 中,如果代码通过了编译,那么它已经处理了所有可能的错误路径。" —— 这是 Rust 给予开发者的最大信心。 --- *本文首发于 Yggdrasil 博客* $doc$, NULL, 'published', NOW() - INTERVAL '2 hours', NOW() - INTERVAL '2 hours', NOW() - INTERVAL '2 hours' ), ( 1, 'Python 数据类完全指南:dataclass、attrs 与 Pydantic', 'python-dataclasses', 'Python 3.7 引入的 dataclass 装饰器大大简化了类的定义。本文深入对比 dataclass、attrs 和 Pydantic,帮助你选择最合适的方案。', $doc$ # Python 数据类完全指南:dataclass、attrs 与 Pydantic 在 Python 中定义一个简单的数据容器类,传统方式需要编写大量样板代码:`__init__`、`__repr__`、`__eq__`……这些代码不仅冗余,而且容易出错。Python 3.7 引入的 **`dataclass`** 装饰器彻底解决了这个问题,让你可以用声明式的方式定义数据类。 本文将深入介绍 `dataclass`、`attrs` 和 `Pydantic` 三种数据类方案,帮助你根据场景选择最合适的工具。 ## dataclass(标准库) `dataclasses` 模块是 Python 3.7+ 的标准库,无需安装任何第三方包。 ### 基础用法 ```python from dataclasses import dataclass, field from typing import List, Optional @dataclass class Person: name: str age: int = 0 email: Optional[str] = None hobbies: List[str] = field(default_factory=list) def greet(self) -> str: return f"Hello, I'm {self.name}!" def is_adult(self) -> bool: return self.age >= 18 # 使用 person = Person("Alice", 30, "alice@example.com", ["reading", "coding"]) print(person) # Person(name='Alice', age=30, email='alice@example.com', hobbies=['reading', 'coding']) print(person.greet()) # Hello, I'm Alice! print(person == Person("Alice", 30)) # False(因为 email 和 hobbies 不同) ``` `@dataclass` 装饰器自动为你生成了 `__init__`、`__repr__`、`__eq__` 等方法。 ### dataclass 参数 ```python @dataclass(init=True, # 生成 __init__ repr=True, # 生成 __repr__ eq=True, # 生成 __eq__ order=False, # 生成比较方法 (__lt__, __le__, __gt__, __ge__) unsafe_hash=False, # 生成 __hash__ frozen=False, # 不可变实例 slots=False, # 使用 __slots__ 节省内存 kw_only=False) # 关键字-only 参数 class Product: name: str price: float quantity: int = 0 ``` ### field() 函数 `field()` 提供了更精细的控制: ```python from dataclasses import dataclass, field @dataclass class User: id: int = field(init=False) # 不通过 __init__ 传入 name: str created_at: str = field(default_factory=lambda: datetime.now().isoformat()) password: str = field(repr=False) # 不在 __repr__ 中显示 def __post_init__(self): self.id = hash(self.name) % 10000 user = User("alice", password="secret123") print(user) # User(id=1234, name='alice', created_at='2024-...') # 注意:password 不在 repr 中 ``` ### frozen dataclass(不可变数据类) ```python from dataclasses import dataclass @dataclass(frozen=True) class Point: x: float y: float def distance_from_origin(self) -> float: return (self.x ** 2 + self.y ** 2) ** 0.5 p = Point(3.0, 4.0) print(p.distance_from_origin()) # 5.0 # p.x = 5.0 # FrozenInstanceError: cannot assign to field 'x' ``` 不可变数据类天然是线程安全的,可以作为字典的键使用。 ### 继承 ```python @dataclass class Employee(Person): employee_id: str department: str = "Engineering" salary: float = field(default=0.0, repr=False) emp = Employee("Bob", 25, None, [], "E001", "Product") print(emp) ``` ## attrs(第三方库) [`attrs`](https://www.attrs.org/) 是比 `dataclass` 更早出现的第三方库,功能更强大。 ```python import attr @attr.s(auto_attribs=True) class Vehicle: wheels: int = 4 color: str = "red" brand: str = attr.ib(default="Unknown", validator=attr.validators.instance_of(str)) def describe(self) -> str: return f"A {self.color} {self.brand} with {self.wheels} wheels" v = Vehicle(wheels=2, color="blue", brand="Yamaha") print(v.describe()) # A blue Yamaha with 2 wheels ``` ### attrs 的优势 | 特性 | dataclass | attrs | 说明 | |------|-----------|-------|------| | 标准库 | ✅ | ❌ | dataclass 无需安装 | | 验证器 | 有限 | 强大 | attrs 内置多种验证器 | | 转换器 | 无 | 有 | attrs 支持类型转换 | | 性能 | 好 | 更好 | attrs 生成的代码更优化 | | 序列化 | 需手动 | 内置 | attrs 支持多种格式 | | 元数据 | 有限 | 丰富 | attrs 的 metadata 系统 | ### attrs 验证器示例 ```python import attr @attr.s class User: name: str = attr.ib(validator=attr.validators.instance_of(str)) age: int = attr.ib( validator=[ attr.validators.instance_of(int), attr.validators.ge(0), # 大于等于 0 attr.validators.le(150) # 小于等于 150 ] ) email: str = attr.ib( validator=attr.validators.matches_re(r"^[\w\.-]+@[\w\.-]+\.\w+$") ) # 使用 try: user = User("Alice", -5, "alice@example.com") except ValueError as e: print(f"验证失败: {e}") ``` ## Pydantic:数据验证与序列化 [`Pydantic`](https://docs.pydantic.dev/) 是目前 Python 生态系统中最流行的数据验证库,基于类型提示自动进行数据验证和转换。 ### 基础用法 ```python from pydantic import BaseModel, Field, EmailStr, validator from typing import List, Optional from datetime import datetime class User(BaseModel): id: int name: str = Field(..., min_length=1, max_length=100) email: EmailStr # 自动验证邮箱格式 age: int = Field(..., ge=0, le=150) is_active: bool = True tags: List[str] = [] created_at: Optional[datetime] = None @validator('name') def name_must_not_be_empty(cls, v): if not v.strip(): raise ValueError('Name must not be empty') return v.strip() # 自动验证和转换 user = User(id=1, name="Alice", email="alice@example.com", age=30) print(user) # id=1 name='Alice' email='alice@example.com' age=30 is_active=True tags=[] created_at=None # 从字典创建(自动转换类型) user_dict = {"id": "2", "name": "Bob", "email": "bob@test.com", "age": "25"} user2 = User(**user_dict) print(user2.age) # 25 (自动从 str 转换为 int) # JSON 序列化 print(user.json()) # {"id": 1, "name": "Alice", "email": "alice@example.com", ...} ``` ### Pydantic 配置 ```python from pydantic import BaseModel, Field class Config: """Pydantic 配置选项""" pass class User(BaseModel): class Config: # 允许从 ORM 对象创建 orm_mode = True # 字段别名(如 JSON 中的 snake_case 映射到 camelCase) alias_generator = lambda x: x.lower() # 验证赋值 validate_assignment = True # 错误信息使用中文 error_msg_templates = { 'value_error.missing': '字段必填', } name: str age: int ``` ## 三者对比 | 特性 | dataclass | attrs | Pydantic | |------|-----------|-------|----------| | 安装 | 标准库 | `pip install attrs` | `pip install pydantic` | | 类型转换 | ❌ | 部分 | ✅ 自动 | | 数据验证 | ❌ | ✅ 内置 | ✅ 强大 | | JSON 序列化 | ❌ | ✅ | ✅ 内置 | | ORM 集成 | ❌ | ❌ | ✅ SQLAlchemy | | 性能 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | | 学习曲线 | 低 | 中 | 低 | | 适用场景 | 简单数据结构 | 复杂验证逻辑 | API 开发、配置管理 | ## 如何选择? 1. **简单数据类**:使用 `dataclass`(标准库,零依赖) 2. **需要复杂验证**:使用 `attrs`(验证器、转换器更强大) 3. **Web/API 开发**:使用 `Pydantic`(自动验证、JSON 序列化、FastAPI 原生支持) 4. **配置管理**:使用 `Pydantic`(环境变量加载、类型转换) 5. **ORM 模型**:使用 `Pydantic`(`orm_mode` 支持) ## 总结 Python 的数据类生态系统已经非常成熟: - **`dataclass`**:简单、标准库、适合大多数场景 - **`attrs`**:功能强大、验证丰富、适合复杂业务逻辑 - **`Pydantic`**:验证 + 序列化 + ORM、Web 开发首选 三者并非互斥。你可以在项目中根据场景灵活选择。例如,内部数据模型使用 `dataclass`,API 接口使用 `Pydantic`,核心业务实体使用 `attrs`。 > **最佳实践**:不要过度设计。如果 `dataclass` 能满足需求,就不要引入额外的依赖。 --- *本文首发于 Yggdrasil 博客* $doc$, NULL, 'draft', NULL, NOW(), NOW() ), ( 1, 'C 语言指针详解', 'c-pointers-explained', '深入理解 C 语言指针的本质,从基础概念到高级用法,全面掌握指针与数组、函数指针、多级指针、内存管理等核心技术,避免常见的指针陷阱。', $doc$ # C 语言指针详解:从基础到精通 指针是 C 语言最核心的特性之一,也是最令初学者困惑的概念。理解指针的本质,是掌握 C 语言的关键一步。本文将从基础概念出发,逐步深入探讨指针的各种高级用法和常见陷阱。 ## 指针的本质 ### 什么是指针 指针是一个变量,其值为另一个变量的内存地址。每个变量在内存中都有一个唯一的地址,指针就是存储这个地址的特殊变量。 ```c #include int main() { int num = 42; int *ptr = # printf("Value of num: %d\n", num); printf("Address of num: %p\n", (void*)&num); printf("Value of ptr (address): %p\n", (void*)ptr); printf("Value pointed by ptr: %d\n", *ptr); return 0; } ``` 在这个例子中: | 表达式 | 含义 | |--------|------| | `&num` | 取变量 num 的地址 | | `int *ptr` | 声明一个指向 int 的指针 | | `*ptr` | 解引用,获取指针指向的值 | | `ptr = &num` | 将 num 的地址赋给指针 | ### 指针的大小 指针的大小取决于系统的架构,而不是指向的数据类型: ```c #include int main() { int *ip; char *cp; double *dp; void *vp; printf("sizeof(int*): %zu bytes\n", sizeof(ip)); printf("sizeof(char*): %zu bytes\n", sizeof(cp)); printf("sizeof(double*):%zu bytes\n", sizeof(dp)); printf("sizeof(void*): %zu bytes\n", sizeof(vp)); return 0; } ``` 在 64 位系统中,所有指针通常都是 8 字节(64 位)。 ## 指针与数组 ### 数组名的本质 数组名在大多数表达式中会退化为指向数组首元素的指针: ```c #include int main() { int arr[5] = {10, 20, 30, 40, 50}; int *ptr = arr; // 等价于 &arr[0] // 以下四种写法等价 printf("arr[2] = %d\n", arr[2]); printf("ptr[2] = %d\n", ptr[2]); printf("*(arr + 2) = %d\n", *(arr + 2)); printf("*(ptr + 2) = %d\n", *(ptr + 2)); return 0; } ``` ### 指针算术 指针算术会自动考虑数据类型的大小: ```c #include int main() { int arr[5] = {10, 20, 30, 40, 50}; int *ptr = arr; printf("ptr = %p\n", (void*)ptr); printf("ptr + 1 = %p\n", (void*)(ptr + 1)); printf("ptr + 2 = %p\n", (void*)(ptr + 2)); // 差值计算的是元素个数,不是字节数 printf("(ptr + 3) - ptr = %ld\n", (ptr + 3) - ptr); return 0; } ``` > **注意**:指针算术只在指向数组元素时才有意义。对非数组对象的指针进行算术运算会导致未定义行为。 ## 多级指针 ### 指向指针的指针 ```c #include int main() { int num = 42; int *ptr1 = # int **ptr2 = &ptr1; int ***ptr3 = &ptr2; printf("num = %d\n", num); printf("*ptr1 = %d\n", *ptr1); printf("**ptr2 = %d\n", **ptr2); printf("***ptr3 = %d\n", ***ptr3); // 修改值 ***ptr3 = 100; printf("After modification: num = %d\n", num); return 0; } ``` 多级指针常用于动态二维数组和函数参数传递: ```c #include #include // 使用二级指针创建动态二维数组 int** create_matrix(int rows, int cols) { int **matrix = malloc(rows * sizeof(int*)); for (int i = 0; i < rows; i++) { matrix[i] = malloc(cols * sizeof(int)); } return matrix; } void free_matrix(int **matrix, int rows) { for (int i = 0; i < rows; i++) { free(matrix[i]); } free(matrix); } ``` ## 函数指针 ### 基本语法 函数指针允许我们将函数作为参数传递,实现回调机制: ```c #include // 函数指针类型定义 typedef int (*CompareFunc)(int, int); int add(int a, int b) { return a + b; } int subtract(int a, int b) { return a - b; } int multiply(int a, int b) { return a * b; } // 使用函数指针作为参数 int operate(int a, int b, CompareFunc op) { return op(a, b); } int main() { printf("add: %d\n", operate(10, 5, add)); printf("subtract: %d\n", operate(10, 5, subtract)); printf("multiply: %d\n", operate(10, 5, multiply)); return 0; } ``` ### 回调函数应用 ```c #include // 通用排序函数,使用回调比较 typedef int (*CompareFunc)(const void*, const void*); void bubble_sort(void *arr, size_t n, size_t size, CompareFunc cmp) { char *base = arr; char temp[size]; for (size_t i = 0; i < n - 1; i++) { for (size_t j = 0; j < n - i - 1; j++) { if (cmp(base + j * size, base + (j + 1) * size) > 0) { // 交换 memcpy(temp, base + j * size, size); memcpy(base + j * size, base + (j + 1) * size, size); memcpy(base + (j + 1) * size, temp, size); } } } } int int_cmp(const void *a, const void *b) { return (*(int*)a - *(int*)b); } int main() { int arr[] = {64, 34, 25, 12, 22, 11, 90}; size_t n = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, n, sizeof(int), int_cmp); printf("Sorted array: "); for (size_t i = 0; i < n; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; } ``` ## void 指针 `void*` 是一种通用指针类型,可以指向任何数据类型: ```c #include #include #include // 通用的内存拷贝函数 void* my_memcpy(void *dest, const void *src, size_t n) { char *d = dest; const char *s = src; while (n--) { *d++ = *s++; } return dest; } // 泛型交换函数 void swap(void *a, void *b, size_t size) { char temp[size]; memcpy(temp, a, size); memcpy(a, b, size); memcpy(b, temp, size); } int main() { int x = 10, y = 20; swap(&x, &y, sizeof(int)); printf("x = %d, y = %d\n", x, y); double a = 1.5, b = 2.5; swap(&a, &b, sizeof(double)); printf("a = %.1f, b = %.1f\n", a, b); return 0; } ``` ## const 与指针 `const` 与指针的组合有四种情况,每种含义不同: | 声明 | 读法 | 含义 | |------|------|------| | `int* ptr` | 指向 int 的指针 | 指针和值都可变 | | `const int* ptr` | 指向常量的指针 | 值不可变,指针可变 | | `int* const ptr` | 常量指针 | 值可变,指针不可变 | | `const int* const ptr` | 指向常量的常量指针 | 都不可变 | ```c #include int main() { int a = 10, b = 20; // 1. 指向常量的指针(常量指针) const int *ptr1 = &a; // *ptr1 = 30; // 错误!不能通过 ptr1 修改值 ptr1 = &b; // 正确,可以修改指针指向 // 2. 常量指针 int *const ptr2 = &a; *ptr2 = 30; // 正确,可以修改值 // ptr2 = &b; // 错误!不能修改指针指向 // 3. 指向常量的常量指针 const int *const ptr3 = &a; // *ptr3 = 40; // 错误! // ptr3 = &b; // 错误! printf("a = %d\n", a); return 0; } ``` ## 内存布局与对齐 ```c #include struct Example { char c; int i; char d; }; struct PackedExample { char c; int i; char d; } __attribute__((packed)); int main() { printf("sizeof(Example): %zu\n", sizeof(struct Example)); printf("sizeof(PackedExample): %zu\n", sizeof(struct PackedExample)); struct Example ex; printf("Address of c: %p\n", (void*)&ex.c); printf("Address of i: %p\n", (void*)&ex.i); printf("Address of d: %p\n", (void*)&ex.d); return 0; } ``` ## 常见指针陷阱 ### 1. 未初始化的指针 ```c int *ptr; // 野指针,指向随机地址 *ptr = 10; // 未定义行为!可能导致程序崩溃 ``` ### 2. 内存泄漏 ```c void leak_example() { int *ptr = malloc(sizeof(int) * 100); // ... 使用 ptr // 忘记 free(ptr) —— 内存泄漏! } ``` ### 3. 悬空指针 ```c int* dangling_pointer() { int local = 42; return &local; // 危险!返回局部变量的地址 } ``` ### 4. 数组越界 ```c int arr[5] = {1, 2, 3, 4, 5}; int *ptr = arr; ptr[5] = 10; // 越界访问!未定义行为 ``` ## 总结 指针是 C 语言强大而灵活的工具,掌握指针需要理解: 1. **内存模型**:理解变量在内存中的存储方式 2. **类型系统**:指针类型决定了指针算术的步长 3. **生命周期**:确保指针始终指向有效的内存 4. **所有权**:谁分配、谁释放,避免内存泄漏 > "C 语言让你可以做出所有你想要的错误,包括那些你可能没想到的。" —— 对指针最好的描述 通过本文的学习,你应该能够自信地使用指针,并避免常见的陷阱。记住,**理解指针的本质是理解内存**,这是成为优秀 C 程序员的关键。 $doc$, NULL, 'published', NOW() - INTERVAL '1 hour', NOW() - INTERVAL '1 hour', NOW() - INTERVAL '1 hour' ), ( 1, 'Elixir 并发与容错', 'elixir-concurrency', '深入探索 Elixir 的并发模型和容错机制,从 OTP 进程到 Supervisor 监督树,理解 Actor 模型和 Let it crash 哲学,构建高可用的分布式系统。', $doc$ # Elixir 并发与容错:构建高可用系统 Elixir 构建在 Erlang VM(BEAM)之上,继承了 Erlang 强大的并发和容错能力。本文将深入探讨 Elixir 的并发模型、进程隔离、Supervisor 监督机制以及分布式系统的构建方法。 ## 为什么选择 Elixir? Elixir 不是另一种 Web 框架,而是一种全新的编程范式。它基于 **Actor 模型**,提供了轻量级进程和消息传递机制,让并发编程变得简单而安全。 > WhatsApp 使用 Erlang/OTP 处理每天超过 650 亿条消息,每台服务器维护超过 200 万个并发连接。这就是 BEAM 虚拟机的实力。 ## Elixir 进程模型 ### 轻量级进程 在 Elixir 中,"进程"不是操作系统进程,而是由 BEAM 虚拟机管理的轻量级执行单元: ```elixir # 创建一个新进程 pid = spawn(fn -> IO.puts("Hello from a new process!") IO.puts("Process PID: #{inspect(self())}") end) IO.puts("Main process PID: #{inspect(self())}") IO.puts("Spawned process PID: #{inspect(pid)}") ``` 特点对比: | 特性 | OS 进程 | Elixir 进程 | |------|---------|-------------| | 内存占用 | MB 级别 | ~300 字节 | | 创建时间 | 毫秒级 | 微秒级 | | 最大数量 | 数千 | 数百万 | | 通信方式 | 共享内存 | 消息传递 | | 调度 | 抢占式 | 协作式 | ### 进程隔离 每个 Elixir 进程都有自己独立的内存空间,进程之间不共享状态: ```elixir defmodule Counter do def start(initial_value \\ 0) do spawn(fn -> loop(initial_value) end) end defp loop(current_value) do receive do {:get, caller} -> send(caller, {:value, current_value}) loop(current_value) {:increment} -> loop(current_value + 1) {:decrement} -> loop(current_value - 1) {:add, amount} -> loop(current_value + amount) end end end # 使用示例 counter = Counter.start(10) send(counter, {:increment}) send(counter, {:add, 5}) send(counter, {:get, self()}) receive do {:value, value} -> IO.puts("Current value: #{value}") after 1000 -> IO.puts("Timeout!") end ``` ## 消息传递机制 ### 异步消息 Elixir 进程通过异步消息传递进行通信: ```elixir defmodule Messenger do def send_message(to_pid, message) do send(to_pid, {self(), message}) end def wait_for_reply(timeout \\\\ 5000) do receive do {_from, reply} -> {:ok, reply} after timeout -> {:error, :timeout} end end end # 创建接收进程 receiver = spawn(fn -> receive do {from, msg} -> IO.puts("Received: #{msg}") send(from, {self(), "Reply: #{msg}"}) end end) Messenger.send_message(receiver, "Hello Elixir!") case Messenger.wait_for_reply() do {:ok, reply} -> IO.puts("Got reply: #{reply}") {:error, reason} -> IO.puts("Failed: #{reason}") end ``` ### 消息邮箱 每个进程都有一个消息邮箱,消息按到达顺序排队: ```elixir defmodule MessageProcessor do def process_messages do receive do message -> IO.puts("Processing: #{inspect(message)}") process_messages() end end end processor = spawn(&MessageProcessor.process_messages/0) send(processor, :first) send(processor, :second) send(processor, {:complex, [1, 2, 3]}) ``` ## GenServer:状态机模式 GenServer 是 OTP 行为(Behaviour)之一,提供了一种标准化的方式来构建有状态的服务器进程: ```elixir defmodule KeyValueStore do use GenServer # 客户端 API def start_link(initial_state \\\\ %{}) do GenServer.start_link(__MODULE__, initial_state, name: __MODULE__) end def get(key) do GenServer.call(__MODULE__, {:get, key}) end def put(key, value) do GenServer.cast(__MODULE__, {:put, key, value}) end def delete(key) do GenServer.call(__MODULE__, {:delete, key}) end # 服务器回调 @impl true def init(initial_state) do {:ok, initial_state} end @impl true def handle_call({:get, key}, _from, state) do {:reply, Map.get(state, key), state} end @impl true def handle_call({:delete, key}, _from, state) do {:reply, :ok, Map.delete(state, key)} end @impl true def handle_cast({:put, key, value}, state) do {:noreply, Map.put(state, key, value)} end end # 使用示例 {:ok, _pid} = KeyValueStore.start_link() KeyValueStore.put(:name, "Elixir") KeyValueStore.put(:version, "1.15") IO.inspect(KeyValueStore.get(:name)) ``` ### GenServer 回调详解 | 回调函数 | 用途 | 返回值 | |----------|------|--------| | `init/1` | 初始化状态 | `{:ok, state}` | | `handle_call/3` | 同步请求 | `{:reply, reply, state}` | | `handle_cast/2` | 异步请求 | `{:noreply, state}` | | `handle_info/2` | 处理普通消息 | `{:noreply, state}` | | `terminate/2` | 清理资源 | 任意 | ## Supervisor 监督策略 ### 容错哲学:Let it crash Elixir 的核心哲学是 **Let it crash**。进程崩溃不应该影响整个系统,Supervisor 会负责重启失败的进程。 ```elixir defmodule MyApp.Supervisor do use Supervisor def start_link(init_arg) do Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__) end @impl true def init(_init_arg) do children = [ # 工作进程 {KeyValueStore, %{}}, # 另一个工作进程 {MyApp.Worker, []}, # 动态监督者 {DynamicSupervisor, strategy: :one_for_one, name: MyApp.DynamicSupervisor} ] Supervisor.init(children, strategy: :one_for_all) end end ``` ### 监督策略对比 | 策略 | 说明 | 适用场景 | |------|------|----------| | `:one_for_one` | 只重启失败的子进程 | 独立的服务 | | `:one_for_all` | 重启所有子进程 | 相互依赖的服务 | | `:rest_for_one` | 重启失败进程及其后续进程 | 有启动顺序依赖 | | `:simple_one_for_one` | 动态添加子进程 | 需要动态创建进程 | ### 重启策略 ```elixir defmodule MyApp.Worker do use GenServer def start_link(args) do GenServer.start_link(__MODULE__, args) end @impl true def init(args) do # 如果初始化失败,Supervisor 会根据重启策略处理 case connect_to_database(args) do {:ok, conn} -> {:ok, conn} {:error, reason} -> {:stop, reason} end end defp connect_to_database(_args) do # 模拟数据库连接 {:ok, %{} } end end ``` ## 容错机制实践 ### 链接进程(Linking) 进程可以相互链接,一个进程崩溃会导致链接的进程也崩溃: ```elixir defmodule LinkExample do def run do parent = self() child = spawn_link(fn -> receive do :crash -> raise "Simulated error" :normal -> IO.puts("Normal exit") end end) Process.flag(:trap_exit, true) send(child, :crash) receive do {:EXIT, ^child, reason} -> IO.puts("Child exited with reason: #{inspect(reason)}") end end end ``` ### 监控进程(Monitoring) 监控是单向的,被监控进程崩溃不会导致监控进程崩溃: ```elixir defmodule MonitorExample do def run do target = spawn(fn -> Process.sleep(1000) exit(:normal) end) ref = Process.monitor(target) receive do {:DOWN, ^ref, :process, ^target, reason} -> IO.puts("Process down: #{inspect(reason)}") end end end ``` ### 选择策略 | 特性 | Link | Monitor | |------|------|---------| | 方向 | 双向 | 单向 | | 影响 | 相互影响 | 仅通知 | | 用途 | 构建 Supervision Tree | 观察进程状态 | | 退出传播 | 是 | 否 | ## 分布式 Elixir ### 节点间通信 Elixir 进程可以在不同机器间透明通信: ```elixir # 启动节点 1(机器 A) # iex --sname node1@machine_a --cookie secret # 启动节点 2(机器 B) # iex --sname node2@machine_b --cookie secret # 在 node2 上连接到 node1 Node.connect(:"node1@machine_a") # 在 node2 上向 node1 发送消息 send({:some_process, :"node1@machine_a"}, :hello_from_node2) # 在 node1 上接收 receive do msg -> IO.puts("Received: #{inspect(msg)}") end ``` ### 分布式任务 ```elixir defmodule DistributedTask do def run_on_all_nodes(task) do nodes = [Node.self() | Node.list()] Enum.map(nodes, fn node -> Task.Supervisor.async_nolink({MyApp.TaskSupervisor, node}, fn -> result = task.() {node, result} end) end) |> Enum.map(&Task.await/1) end end # 在所有节点上执行计算 results = DistributedTask.run_on_all_nodes(fn -> # 计算密集型任务 Enum.sum(1..1_000_000) end) IO.inspect(results) ``` ## 实际应用:构建容错计数器 ```elixir defmodule FaultTolerantCounter do use GenServer def start_link(_opts) do GenServer.start_link(__MODULE__, 0, name: __MODULE__) end def increment do GenServer.cast(__MODULE__, :increment) end def get_count do GenServer.call(__MODULE__, :get_count) end @impl true def init(state) do # 设置陷阱退出,处理链接进程的错误 Process.flag(:trap_exit, true) {:ok, state} end @impl true def handle_cast(:increment, state) do {:noreply, state + 1} end @impl true def handle_call(:get_count, _from, state) do {:reply, state, state} end @impl true def handle_info({:EXIT, _pid, reason}, state) do IO.puts("Linked process exited: #{inspect(reason)}") {:noreply, state} end end # Supervisor 配置 defmodule CounterSupervisor do use Supervisor def start_link(init_arg) do Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__) end @impl true def init(_init_arg) do children = [ {FaultTolerantCounter, []} ] # 使用 transient 重启策略,只在异常退出时重启 Supervisor.init(children, strategy: :one_for_one, max_restarts: 5, max_seconds: 10 ) end end ``` ## 总结 Elixir 的并发和容错能力源于其独特的设计哲学: 1. **隔离性**:进程完全隔离,一个崩溃不影响其他 2. **监督**:Supervisor 自动重启失败进程 3. **消息传递**:避免共享状态带来的复杂性 4. **热升级**:不停机更新运行中的系统 5. **分布式透明**:跨节点通信与本地通信 API 一致 > "Let it crash" 不是忽视错误,而是设计系统时的基本假设 —— 组件会失败,但系统必须继续运行。 通过掌握这些概念,你可以构建出真正高可用、容错的分布式系统,充分利用多核 CPU 和集群环境。 $doc$, NULL, 'published', NOW() - INTERVAL '30 minutes', NOW() - INTERVAL '30 minutes', NOW() - INTERVAL '30 minutes' ), ( 1, 'Kotlin 协程与 Flow', 'kotlin-coroutines-flow', '全面解析 Kotlin 协程和 Flow,从基础概念到高级用法,深入理解结构化并发、冷流热流、状态管理,以及与 RxJava 的对比,掌握现代 Kotlin 异步编程。', $doc$ # Kotlin 协程与 Flow:现代异步编程指南 Kotlin 协程(Coroutines)彻底改变了 Android 和后端开发中的异步编程方式。本文将从基础概念出发,深入探讨协程的各种用法、Flow 响应式流,以及与 RxJava 的对比。 ## 为什么选择协程? 传统的回调式异步编程导致代码难以阅读和维护,俗称"回调地狱"。Kotlin 协程提供了**挂起函数(Suspending Functions)**,让异步代码像同步代码一样简洁。 > 协程不是线程,线程也不是协程。协程是**可挂起的计算**,可以在单个线程上运行多个协程。 ## 协程基础 ### 启动协程 Kotlin 提供了三种启动协程的方式: ```kotlin import kotlinx.coroutines.* fun main() = runBlocking { // 1. launch - 启动新协程,不返回结果 val job = launch { delay(1000L) println("World!") } // 2. async - 启动新协程,返回 Deferred( future/promise) val deferred = async { delay(500L) "Hello" } // 3. runBlocking - 阻塞当前线程等待协程完成 println("${deferred.await()}, ${job.join()}") } ``` | 构建器 | 返回类型 | 用途 | |--------|----------|------| | `launch` | `Job` | 启动"开火即忘"的协程 | | `async` | `Deferred` | 启动返回结果的协程 | | `runBlocking` | `T` | 桥接阻塞和非阻塞代码 | ### 挂起函数 挂起函数是协程的核心,可以在不阻塞线程的情况下暂停执行: ```kotlin import kotlinx.coroutines.* suspend fun fetchUserData(userId: String): User { // 模拟网络请求 delay(1000) // 挂起 1 秒,不阻塞线程 return User(userId, "John Doe", "john@example.com") } suspend fun fetchUserOrders(userId: String): List { delay(800) return listOf( Order("1", 99.99), Order("2", 149.99) ) } // 组合挂起函数 suspend fun getUserProfile(userId: String): UserProfile { val user = fetchUserData(userId) val orders = fetchUserOrders(userId) return UserProfile(user, orders) } ``` ### CoroutineScope 和上下文 ```kotlin import kotlinx.coroutines.* import kotlin.coroutines.CoroutineContext // 自定义 Scope class Activity : CoroutineScope { private val job = Job() override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job fun destroy() { job.cancel() // 取消所有子协程 } fun loadData() { launch { try { val data = fetchData() updateUI(data) } catch (e: CancellationException) { println("Coroutine cancelled") } } } private suspend fun fetchData(): String { delay(2000) return "Data loaded" } private fun updateUI(data: String) { println("UI updated: $data") } } ``` ## 结构化并发 ### 父子关系 协程形成树形结构,父协程取消会自动取消所有子协程: ```kotlin import kotlinx.coroutines.* fun main() = runBlocking { val parentJob = launch { // 子协程 1 launch { repeat(10) { i -> println("Child 1: $i") delay(100) } } // 子协程 2 launch { repeat(10) { i -> println("Child 2: $i") delay(150) } } delay(300) println("Parent: Cancelling...") } parentJob.join() println("All coroutines completed") } ``` ### SupervisorJob 当不希望子协程的失败影响其他子协程时,使用 SupervisorJob: ```kotlin import kotlinx.coroutines.* fun main() = runBlocking { val supervisor = SupervisorJob() with(CoroutineScope(coroutineContext + supervisor)) { // 第一个子协程 - 会失败 val job1 = launch { delay(100) throw RuntimeException("Oops!") } // 第二个子协程 - 不受影响继续运行 val job2 = launch { repeat(5) { i -> println("Job2: $i") delay(100) } } joinAll(job1, job2) } } ``` ## Flow:响应式流 ### 冷流(Cold Flow) Flow 是 Kotlin 的响应式流实现,采用**冷流**模式 —— 数据在订阅时才产生: ```kotlin import kotlinx.coroutines.* import kotlinx.coroutines.flow.* fun simpleFlow(): Flow = flow { println("Flow started") for (i in 1..3) { delay(100) emit(i) // 发射值 } } fun main() = runBlocking { val flow = simpleFlow() println("First collection:") flow.collect { value -> println("Received: $value") } println("\nSecond collection:") flow.collect { value -> println("Received again: $value") } } ``` ### 操作符 Flow 提供了丰富的操作符,类似于 RxJava: ```kotlin import kotlinx.coroutines.flow.* fun processNumbers(): Flow = flow { (1..10).forEach { emit(it) } } suspend fun demonstrateOperators() { val result = processNumbers() .filter { it % 2 == 0 } // 过滤偶数 .map { it * it } // 平方 .take(3) // 取前 3 个 .onEach { println("Processing: $it") } .toList() // 收集为列表 println("Result: $result") } ``` | 操作符类别 | 示例 | 说明 | |-----------|------|------| | 转换 | `map`, `transform` | 转换每个元素 | | 过滤 | `filter`, `take`, `drop` | 筛选元素 | | 组合 | `zip`, `combine`, `merge` | 组合多个 Flow | | 错误处理 | `catch`, `retry` | 处理异常 | | 终端 | `collect`, `reduce`, `fold` | 收集结果 | ### StateFlow 和 SharedFlow StateFlow 和 SharedFlow 是**热流**,适合状态管理和事件分发: ```kotlin import kotlinx.coroutines.* import kotlinx.coroutines.flow.* class NewsViewModel { // StateFlow - 总是有值,适合 UI 状态 private val _uiState = MutableStateFlow(UiState.Loading) val uiState: StateFlow = _uiState.asStateFlow() // SharedFlow - 适合一次性事件 private val _events = MutableSharedFlow() val events: SharedFlow = _events.asSharedFlow() fun loadNews() { viewModelScope.launch { _uiState.value = UiState.Loading try { val news = repository.fetchNews() _uiState.value = UiState.Success(news) } catch (e: Exception) { _uiState.value = UiState.Error(e.message ?: "Unknown error") _events.emit("Failed to load news") } } } } sealed class UiState { object Loading : UiState() data class Success(val data: List) : UiState() data class Error(val message: String) : UiState() } ``` ## 异常处理 ### try-catch 在协程中 ```kotlin import kotlinx.coroutines.* fun main() = runBlocking { val job = launch { try { riskyOperation() } catch (e: Exception) { println("Caught: ${e.message}") } } job.join() } suspend fun riskyOperation() { delay(100) throw RuntimeException("Something went wrong") } ``` ### CoroutineExceptionHandler ```kotlin import kotlinx.coroutines.* fun main() = runBlocking { val handler = CoroutineExceptionHandler { _, exception -> println("Caught $exception") } val job = GlobalScope.launch(handler) { throw AssertionError("My Error") } val deferred = GlobalScope.async(handler) { throw ArithmeticException() } joinAll(job, deferred) } ``` ### Flow 异常处理 ```kotlin import kotlinx.coroutines.flow.* fun safeFlow(): Flow = flow { emit(1) emit(2) throw RuntimeException("Error!") }.catch { e -> println("Caught: ${e.message}") emit(-1) // 发射默认值 }.onCompletion { cause -> if (cause != null) { println("Flow completed with error") } else { println("Flow completed successfully") } } ``` ## 与 RxJava 对比 | 特性 | Kotlin Flow | RxJava | |------|-------------|---------| | 学习曲线 | 平缓 | 陡峭 | | 内存开销 | 低 | 较高 | | Android 支持 | 原生(官方推荐) | 需要额外依赖 | | 线程切换 | `withContext` | `subscribeOn`/`observeOn` | | 背压支持 | `buffer`, `conflate` | 内置 Backpressure | | 取消机制 | 结构化并发 | Disposable | | 冷/热流 | 明确区分 | 较模糊 | ### 从 RxJava 迁移示例 ```kotlin // RxJava 方式 Observable.fromIterable(users) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .map { it.name } .subscribe { println(it) } // Flow 方式 users.asFlow() .flowOn(Dispatchers.IO) .map { it.name } .collect { println(it) } ``` ## 实际应用:MVVM 架构 ```kotlin import androidx.lifecycle.* import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch class UserViewModel( private val userRepository: UserRepository ) : ViewModel() { private val _searchQuery = MutableStateFlow("") val searchQuery: StateFlow = _searchQuery.asStateFlow() // 搜索用户,自动去抖 val users: StateFlow> = _searchQuery .debounce(300) // 等待 300ms 无输入才搜索 .flatMapLatest { query -> if (query.isEmpty()) { flowOf(emptyList()) } else { userRepository.searchUsers(query) } } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = emptyList() ) fun onSearchQueryChange(query: String) { _searchQuery.value = query } fun refreshUsers() { viewModelScope.launch { try { userRepository.refreshUsers() } catch (e: Exception) { // 处理错误 } } } } ``` ## 总结 Kotlin 协程和 Flow 提供了一套完整的异步编程解决方案: 1. **轻量级**:协程比线程更轻量,单线程可运行数千协程 2. **结构化并发**:自动管理协程生命周期,避免泄漏 3. **Flow 响应式**:声明式处理数据流,操作符丰富 4. **异常安全**:完善的异常处理机制 5. **与 Android 深度集成**:ViewModelScope、LifecycleScope 等 > 掌握协程和 Flow,是成为现代 Kotlin 开发者的必备技能。 通过本文的学习,你应该能够在实际项目中熟练使用协程进行网络请求、数据库操作、UI 更新等异步任务,并使用 Flow 构建响应式的数据流。 $doc$, NULL, 'published', NOW() - INTERVAL '15 minutes', NOW() - INTERVAL '15 minutes', NOW() - INTERVAL '15 minutes' ), ( 1, 'Swift 现代语法速览', 'swift-modern-syntax', '全面梳理 Swift 现代语法特性,从可选类型到属性包装器,深入理解值类型与引用类型、协议扩展、泛型和错误处理,掌握 Swift 编程的核心概念和最佳实践。', $doc$ # Swift 现代语法速览 Swift 是 Apple 推出的现代编程语言,结合了 C 和 Objective-C 的优点,同时引入了函数式编程和类型安全的特性。本文将全面梳理 Swift 的核心语法,帮助你快速掌握这门语言。 ## 为什么选择 Swift? Swift 的设计目标是安全、快速、表达力强。它消除了 C 语言家族中许多不安全的特性,同时提供了现代化的语法和强大的类型系统。 > Swift 结合了编译型语言的性能和脚本语言的交互性,是现代 Apple 平台开发的首选语言。 ## 基础语法 ### 常量与变量 ```swift // 常量 - 不可变 let pi = 3.14159 let greeting = "Hello, Swift!" // 变量 - 可变 var counter = 0 counter += 1 // 类型注解(通常可以省略,编译器会自动推断) let explicitDouble: Double = 70.0 let implicitDouble = 70.0 // 同样是 Double // 多行字符串 let multiline = """ Swift is a powerful and intuitive programming language for iOS, iPadOS, macOS, watchOS, and tvOS. """ ``` ### 基本数据类型 | 类型 | 说明 | 示例 | |------|------|------| | `Int` | 整数 | `let age: Int = 25` | | `Double` | 双精度浮点 | `let price: Double = 19.99` | | `String` | 字符串 | `let name = "Swift"` | | `Bool` | 布尔 | `let isActive = true` | | `Array` | 数组 | `let numbers = [1, 2, 3]` | | `Dictionary` | 字典 | `let scores = ["A": 90]` | ### 字符串插值和运算 ```swift let name = "World" let message = "Hello, \(name)!" // 字符串操作 var str = "Swift" str.append("!") str += " Programming" // 多行字符串保留格式 let poem = """ Swift as the wind, Strong as a mountain, Elegant as poetry. """ ``` ## 可选类型(Optionals) ### 什么是可选类型 可选类型表示一个值可能存在,也可能不存在(nil): ```swift // 可选整数 var optionalNumber: Int? = 42 optionalNumber = nil // 强制解包(危险!如果为 nil 会崩溃) let forced = optionalNumber! // 安全解包方式 if let number = optionalNumber { print("The number is \(number)") } else { print("No number") } // Nil 合并运算符 let defaultNumber = optionalNumber ?? 0 // 可选链 let uppercased = optionalNumber?.description.uppercased() ``` ### guard 语句 `guard` 语句用于提前退出,提高代码可读性: ```swift func processUser(age: Int?, name: String?) { guard let validAge = age, validAge >= 0 else { print("Invalid age") return } guard let validName = name, !validName.isEmpty else { print("Invalid name") return } print("User: \(validName), Age: \(validAge)") } // 使用 processUser(age: 25, name: "Alice") // 成功 processUser(age: -5, name: "Bob") // Invalid age processUser(age: 30, name: nil) // Invalid name ``` ### if let 和 guard let 对比 | 特性 | `if let` | `guard let` | |------|----------|-------------| | 作用域 | 仅在 if 块内 | 在 guard 之后的代码 | | 使用场景 | 条件分支处理 | 前置条件检查 | | 代码风格 | 嵌套可能较深 | 减少嵌套,提前退出 | ## 集合类型 ### 数组和字典 ```swift // 数组 var fruits = ["Apple", "Banana", "Orange"] fruits.append("Mango") fruits.insert("Grape", at: 1) let firstFruit = fruits[0] // 字典 var scores: [String: Int] = [ "Alice": 95, "Bob": 87, "Charlie": 92 ] scores["David"] = 88 if let aliceScore = scores["Alice"] { print("Alice scored \(aliceScore)") } // 遍历字典 for (name, score) in scores { print("\(name): \(score)") } ``` ### Set 集合 ```swift let favoriteGenres: Set = ["Rock", "Classical", "Hip hop"] let otherGenres: Set = ["Jazz", "Rock", "Electronic"] // 集合运算 let intersection = favoriteGenres.intersection(otherGenres) let union = favoriteGenres.union(otherGenres) let difference = favoriteGenres.symmetricDifference(otherGenres) print("Both like: \(intersection)") print("All genres: \(union)") ``` ## 控制流 ### 高级 switch ```swift let character: Character = "a" switch character { case "a", "e", "i", "o", "u": print("\(character) is a vowel") case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z": print("\(character) is a consonant") default: print("\(character) is not a letter") } // 区间匹配 let count = 62 switch count { case 0: print("none") case 1..<5: print("a few") case 5..<12: print("several") case 12..<100: print("dozens") case 100..<1000: print("hundreds") default: print("many") } // 元组匹配 let point = (1, 1) switch point { case (0, 0): print("origin") case (_, 0): print("on x-axis") case (0, _): print("on y-axis") case (-2...2, -2...2): print("inside the box") default: print("outside") } ``` ### for-in 循环 ```swift // 遍历范围 for index in 1...5 { print("\(index) times 5 is \(index * 5)") } // 遍历数组 let names = ["Anna", "Alex", "Brian", "Jack"] for name in names { print("Hello, \(name)!") } // 遍历字典 let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4] for (animalName, legCount) in numberOfLegs { print("\(animalName)s have \(legCount) legs") } // 带索引的遍历 for (index, name) in names.enumerated() { print("\(index + 1). \(name)") } // stride 函数 let minutes = 60 let minuteInterval = 5 for tickMark in stride(from: 0, to: minutes, by: minuteInterval) { print("Tick mark at \(tickMark) minutes") } ``` ## 函数和闭包 ### 函数定义 ```swift // 基本函数 func greet(person: String) -> String { return "Hello, \(person)!" } // 参数标签和参数名 func greet(person: String, from hometown: String) -> String { return "Hello \(person)! Glad you could visit from \(hometown)." } let greeting = greet(person: "Bill", from: "Cupertino") // 默认参数 func greet(person: String, nicely: Bool = true) -> String { if nicely { return "Hello, \(person)!" } else { return "Oh no, it's \(person) again..." } } print(greet(person: "Tim")) // Hello, Tim! print(greet(person: "Tim", nicely: false)) // Oh no, it's Tim again... // 可变参数 func arithmeticMean(_ numbers: Double...) -> Double { var total: Double = 0 for number in numbers { total += number } return total / Double(numbers.count) } print(arithmeticMean(1, 2, 3, 4, 5)) ``` ### 闭包(Closures) ```swift // 基本闭包 let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] // 完整写法 let reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 }) // 类型推断简化 let reversedNames2 = names.sorted(by: { s1, s2 in return s1 > s2 }) // 隐式返回 let reversedNames3 = names.sorted(by: { s1, s2 in s1 > s2 }) // 简写参数名 let reversedNames4 = names.sorted(by: { \$0 > \$1 }) // 尾随闭包 let reversedNames5 = names.sorted { \$0 > \$1 } // 捕获值 func makeIncrementer(forIncrement amount: Int) -> () -> Int { var runningTotal = 0 func incrementer() -> Int { runningTotal += amount return runningTotal } return incrementer } let incrementByTen = makeIncrementer(forIncrement: 10) print(incrementByTen()) // 10 print(incrementByTen()) // 20 ``` ## 结构体与类 ### 值类型 vs 引用类型 ```swift // 结构体 - 值类型 struct Resolution { var width = 0 var height = 0 } // 类 - 引用类型 class VideoMode { var resolution = Resolution() var interlaced = false var frameRate = 0.0 var name: String? } let hd = Resolution(width: 1920, height: 1080) var cinema = hd // 值拷贝 cinema.width = 2048 print("hd width: \(hd.width)") // 1920 print("cinema width: \(cinema.width)") // 2048 let tenEighty = VideoMode() tenEighty.resolution = hd tenEighty.interlaced = true tenEighty.name = "1080i" tenEighty.frameRate = 25.0 let alsoTenEighty = tenEighty // 引用拷贝 alsoTenEighty.frameRate = 30.0 print("tenEighty frameRate: \(tenEighty.frameRate)") // 30.0 ``` ### 属性观察器 ```swift class StepCounter { var totalSteps: Int = 0 { willSet(newTotalSteps) { print("About to set totalSteps to \(newTotalSteps)") } didSet { if totalSteps > oldValue { print("Added \(totalSteps - oldValue) steps") } } } } let stepCounter = StepCounter() stepCounter.totalSteps = 200 // About to set totalSteps to 200 // Added 200 steps stepCounter.totalSteps = 360 // About to set totalSteps to 360 // Added 160 steps ``` ## 协议与扩展 ### 协议定义 ```swift protocol FullyNamed { var fullName: String { get } } protocol RandomNumberGenerator { func random() -> Double } // 协议继承 protocol NamedAndAged: FullyNamed { var age: Int { get } } // 遵循协议 struct Person: FullyNamed { var firstName: String var lastName: String var fullName: String { return "\(firstName) \(lastName)" } } let john = Person(firstName: "John", lastName: "Appleseed") print(john.fullName) ``` ### 扩展 ```swift // 扩展现有类型 extension Int { func repetitions(task: () -> Void) { for _ in 0.. Bool { return random() > 0.5 } } ``` ## 泛型 ### 泛型函数和类型 ```swift // 泛型函数 func swapTwoValues(_ a: inout T, _ b: inout T) { let temporaryA = a a = b b = temporaryA } var someInt = 3 var anotherInt = 107 swapTwoValues(&someInt, &anotherInt) var someString = "hello" var anotherString = "world" swapTwoValues(&someString, &anotherString) // 泛型类型 struct Stack { private var items: [Element] = [] mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { return items.removeLast() } var topItem: Element? { return items.isEmpty ? nil : items[items.count - 1] } var isEmpty: Bool { return items.isEmpty } } var stackOfStrings = Stack() stackOfStrings.push("uno") stackOfStrings.push("dos") stackOfStrings.push("tres") print(stackOfStrings.pop()) // tres // 泛型约束 func findIndex(of valueToFind: T, in array: [T]) -> Int? { for (index, value) in array.enumerated() { if value == valueToFind { return index } } return nil } ``` ## 错误处理 ### 定义和抛出错误 ```swift enum PrinterError: Error { case outOfPaper case noToner case onFire } func send(job: Int, toPrinter printerName: String) throws -> String { if printerName == "Never Has Toner" { throw PrinterError.noToner } return "Job sent" } // 使用 do-catch func processPrintJob() { do { let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng") print(printerResponse) } catch PrinterError.onFire { print("I'll just put this over here, with the rest of the fire.") } catch let printerError as PrinterError { print("Printer error: \(printerError).") } catch { print(error) } } // try? 转换为可选 let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler") let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner") // defer func processFile(filename: String) throws { let file = open(filename) defer { close(file) } // 处理文件... // 无论是否抛出错误,defer 都会执行 } ``` ## 属性包装器 ```swift @propertyWrapper struct TwelveOrLess { private var number = 0 var wrappedValue: Int { get { return number } set { number = min(newValue, 12) } } } struct SmallRectangle { @TwelveOrLess var height: Int @TwelveOrLess var width: Int } var rectangle = SmallRectangle() print(rectangle.height) // 0 rectangle.height = 10 print(rectangle.height) // 10 rectangle.height = 24 print(rectangle.height) // 12 ``` ## 总结 Swift 是一门现代化的编程语言,提供了丰富的特性: 1. **类型安全**:可选类型消除空指针异常 2. **值类型优先**:结构体和枚举都是值类型,减少副作用 3. **协议导向**:通过协议和扩展实现多态 4. **函数式特性**:闭包、高阶函数、不可变性 5. **现代语法**:类型推断、字符串插值、模式匹配 > Swift 的设计哲学是安全、快速、表达力强。它不仅仅是一门 iOS 开发语言,也是一门通用的现代编程语言。 通过本文的学习,你应该已经掌握了 Swift 的核心语法特性,可以开始构建 iOS、macOS 或其他平台的应用程序了。 $doc$, NULL, 'published', NOW(), NOW(), NOW() ) ON CONFLICT (slug) DO NOTHING; -- 重置序列 SELECT setval('posts_id_seq', COALESCE((SELECT MAX(id) FROM posts), 1), false);