yggdrasil/scripts/seed_posts.sql

6285 lines
163 KiB
Transact-SQL
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- 种子数据:高质量测试文章
-- 共 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++或自动垃圾回收 JavaGo手动管理虽然性能优秀但容易引发内存泄漏悬垂指针双重释放等严重问题垃圾回收虽然安全但会带来运行时开销和不可预测的暂停时间
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 实现了 Copyx 仍然有效
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<T>` 和 `Cell<T>` 等类型来实现**内部可变性**
```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<T>` | 堆分配 | 独占所有权 |
| `Rc<T>` | 引用计数 | 共享所有权(单线程) |
| `Arc<T>` | 原子引用计数 | 共享所有权(多线程) |
| `RefCell<T>` | 运行时借用检查 | 内部可变性 |
## 总结
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优雅的异步解决方案
ES62015引入了 **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 awaitES2022
```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("所有工作已完成")
}
```
## Channelgoroutine 之间的通信桥梁
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> = T extends string ? true : false;
// 使用示例
type A = IsString<string>; // true
type B = IsString<number>; // false
type C = IsString<"hello">; // true字面量类型也是 string 的子类型)
```
### extends 关键字
在条件类型中,`extends` 表示"是否是...的子类型"
```typescript
type IsArray<T> = T extends any[] ? true : false;
type D = IsArray<number[]>; // true
type E = IsArray<string>; // false
```
### infer 关键字:类型推断
`infer` 是 TypeScript 中最强大的关键字之一,它允许你在条件类型中"提取"类型:
```typescript
// 提取数组元素类型
type ElementType<T> = T extends (infer U)[] ? U : never;
type F = ElementType<string[]>; // string
type G = ElementType<number[]>; // number
// 提取函数返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getUser() {
return { id: 1, name: "Alice" };
}
type User = ReturnType<typeof getUser>; // { id: number; name: string; }
// 提取 Promise 的解析类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type H = UnwrapPromise<Promise<string>>; // string
```
### 内置条件类型
TypeScript 标准库已经提供了许多基于条件类型的工具类型:
| 类型 | 作用 | 示例 |
|------|------|------|
| `Exclude<T, U>` | 从 T 中排除 U | `Exclude<'a' \| 'b', 'a'>` → `'b'` |
| `Extract<T, U>` | 从 T 中提取 U | `Extract<'a' \| 'b', 'a' \| 'c'>` → `'a'` |
| `NonNullable<T>` | 排除 null 和 undefined | `NonNullable<string \| null>` → `string` |
| `ReturnType<T>` | 获取函数返回类型 | `ReturnType<() => number>` → `number` |
| `Parameters<T>` | 获取函数参数类型 | `Parameters<(a: string) => void>` → `[string]` |
| `InstanceType<T>` | 获取构造函数实例类型 | `InstanceType<typeof Date>` → `Date` |
## 映射类型Mapped Types
映射类型允许你基于已有类型创建新类型,通过遍历属性键来转换每个属性:
### 基础映射类型
```typescript
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Partial<T> = {
[P in keyof T]?: T[P];
};
type Required<T> = {
[P in keyof T]-?: T[P]; // -? 移除可选性
};
// 使用示例
interface User {
name: string;
age: number;
}
type ReadonlyUser = Readonly<User>;
// { readonly name: string; readonly age: number; }
type PartialUser = Partial<User>;
// { name?: string; age?: number; }
```
### 键重映射Key Remapping
TypeScript 4.1 引入了 `as` 关键字,允许在映射类型中重命名键:
```typescript
// 将每个属性名添加 "get" 前缀,类型变为函数
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number; }
```
### 过滤属性
```typescript
// 只保留 string 类型的属性
type StringProperties<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
interface User {
name: string;
age: number;
email: string;
}
type StringUserProps = StringProperties<User>;
// { name: string; email: string; }
```
## 模板字面量类型Template Literal Types
TypeScript 4.1 引入了模板字面量类型,它允许你通过字符串字面量类型来构造新类型:
### 基础用法
```typescript
type EventName<T extends string> = `on${Capitalize<T>}`;
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<CSSDirection>}` | CSSProperty;
// "margin" | "padding" | "marginTop" | "marginRight" | ...
```
## 递归类型
TypeScript 支持递归类型定义,这在处理嵌套数据结构时非常有用:
```typescript
// 深度只读类型
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? DeepReadonly<T[P]>
: T[P];
};
interface NestedUser {
name: string;
address: {
city: string;
coordinates: {
lat: number;
lng: number;
};
};
}
type DeepReadonlyUser = DeepReadonly<NestedUser>;
// 所有嵌套属性都变为 readonly
// 深度 Partial 类型
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object
? DeepPartial<T[P]>
: T[P];
};
// 扁平化对象类型(将嵌套属性用点号连接)
type Flatten<T, Prefix = ""> = {
[K in keyof T as K extends string
? Prefix extends ""
? K
: `${Prefix & string}.${K}`
: never
]: T[K] extends object ? Flatten<T[K], `${Prefix & string}.${K & string}`> : 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<T, K extends keyof T = keyof T> = K extends string
? T[K] extends object
? `${K}` | `${K}.${Path<T[K]>}`
: `${K}`
: never;
// 安全的深度属性访问类型
type DeepPick<T, P extends string> = P extends `${infer K}.${infer Rest}`
? K extends keyof T
? { [key in K]: DeepPick<T[K], Rest> }
: 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<T> {
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<String> stringBox = new Box<>();
stringBox.set("Hello Generics");
String str = stringBox.get(); // 无需类型转换
// 创建 Integer 类型的 Box
Box<Integer> intBox = new Box<>();
intBox.set(42);
Integer num = intBox.get();
}
}
```
### 泛型方法
泛型方法允许你在普通类或泛型类中定义带有类型参数的方法:
```java
public class GenericMethodExample {
// 泛型方法
public <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
// 泛型方法 with 返回值
public <T> T getFirst(T[] array) {
return array.length > 0 ? array[0] : null;
}
// 泛型方法 with 多个类型参数
public <K, V> 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<T extends Number> {
private T value;
public double getDoubleValue() {
return value.doubleValue();
}
}
// 使用
NumberBox<Integer> intBox = new NumberBox<>(); // ✅
NumberBox<Double> doubleBox = new NumberBox<>(); // ✅
// NumberBox<String> stringBox = new NumberBox<>(); // ❌ 编译错误
```
## 通配符Wildcards
泛型中的通配符 `?` 表示**未知类型**,它提供了更灵活的类型兼容性。
### 无界通配符 `?`
```java
public void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
// 可以接受任何类型的 List
printList(new ArrayList<String>());
printList(new ArrayList<Integer>());
```
### 上界通配符 `? extends T`
`? extends T` 表示**T 的子类型**,适用于读取数据的场景:
```java
// 可以接受 Number 及其子类型的 List
public double sumNumbers(List<? extends Number> numbers) {
double sum = 0;
for (Number num : numbers) {
sum += num.doubleValue();
}
return sum;
}
// 使用
List<Integer> ints = Arrays.asList(1, 2, 3);
List<Double> 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<? super Integer> list) {
list.add(1);
list.add(2);
list.add(3);
}
// 使用
List<Number> numbers = new ArrayList<>();
List<Object> objects = new ArrayList<>();
addIntegers(numbers); // ✅ Number 是 Integer 的父类
addIntegers(objects); // ✅ Object 是 Integer 的父类
// addIntegers(new ArrayList<String>()); // ❌ 编译错误
```
## PECS 原则
PECS 是 Java 泛型中最重要也最实用的原则,它是 **Producer-Extends, Consumer-Super** 的缩写:
| 原则 | 含义 | 使用场景 |
|------|------|---------|
| **Producer-Extends** | 如果数据从集合中产出(读取),使用 `? extends T` | 方法参数用于读取 |
| **Consumer-Super** | 如果数据被消费(写入),使用 `? super T` | 方法参数用于写入 |
### 实际案例Collections.copy
Java 标准库中的 `Collections.copy` 方法完美诠释了 PECS 原则:
```java
public static <T> void copy(List<? super T> dest, List<? extends T> 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<? extends Number> list) {
double sum = 0.0;
for (Number n : list) {
sum += n.doubleValue();
}
return sum;
}
// Consumer: 向列表中写入数据
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 5; i++) {
list.add(i);
}
}
public static void main(String[] args) {
// Producer 示例
List<Integer> ints = Arrays.asList(1, 2, 3);
List<Double> 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<Number> numbers = new ArrayList<>();
addNumbers(numbers);
System.out.println(numbers); // [1, 2, 3, 4, 5]
List<Object> objects = new ArrayList<>();
addNumbers(objects);
System.out.println(objects); // [1, 2, 3, 4, 5]
}
}
```
## 类型擦除Type Erasure
Java 泛型采用了**类型擦除**机制来实现向后兼容。在编译期,所有泛型信息都会被擦除,替换为它们的上界(通常是 `Object`
```java
// 编译前
List<String> strings = new ArrayList<>();
String s = strings.get(0);
// 编译后(类型擦除)
List strings = new ArrayList();
String s = (String) strings.get(0); // 自动插入类型转换
```
### 类型擦除的影响
1. **不能使用基本类型**`List<int>` 是错误的,必须使用 `List<Integer>`
2. **运行时类型检查受限**`instanceof List<String>` 是非法的
3. **不能创建泛型数组**`new T[10]` 是非法的
4. **可以通过反射绕过泛型检查**
```java
List<String> strings = new ArrayList<>();
// strings.add(42); // 编译错误
// 但可以通过反射绕过
Method m = strings.getClass().getMethod("add", Object.class);
m.invoke(strings, 42); // 运行时成功添加 Integer
```
## 泛型与继承
泛型类型之间**不协变**not covariant
```java
List<Object> objects = new ArrayList<String>(); // ❌ 编译错误
```
虽然 `String` 是 `Object` 的子类,但 `List<String>` 并不是 `List<Object>` 的子类。这是为了防止以下运行时错误:
```java
// 假设允许协变
List<String> strings = new ArrayList<>();
List<Object> objects = strings; // 假设可以
objects.add(42); // 向字符串列表中添加整数!
String s = strings.get(0); // ClassCastException
```
## 总结
Java 泛型是类型安全的基石,掌握它需要理解以下核心概念:
| 概念 | 说明 | 示例 |
|------|------|------|
| 泛型类 | 类级别的类型参数 | `class Box<T>` |
| 泛型方法 | 方法级别的类型参数 | `<T> T method(T arg)` |
| `? extends T` | 上界通配符,用于读取 | `List<? extends Number>` |
| `? super T` | 下界通配符,用于写入 | `List<? super Integer>` |
| PECS | Producer-Extends, Consumer-Super | `copy(dest, src)` |
| 类型擦除 | 编译期擦除泛型信息 | `List<String>` → `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 <cstdio>
#include <stdexcept>
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<int[]> data;
size_t size;
public:
explicit ModernResource(size_t n) : data(std::make_unique<int[]>(n)), size(n) {}
// 编译器自动生成的析构函数、拷贝/移动函数都能正确工作
// 因为 unique_ptr 已经正确管理了内存
};
```
## 智能指针
C++11 引入了三种智能指针,分别适用于不同的所有权模型:
### std::unique_ptr独占所有权
`unique_ptr` 表示对对象的**独占所有权**,同一时间只能有一个 `unique_ptr` 指向给定对象。当 `unique_ptr` 被销毁时,它所指向的对象也会被自动删除。
```cpp
#include <memory>
#include <iostream>
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<Widget> ptr1 = std::make_unique<Widget>();
ptr1->doSomething();
// 转移所有权
std::unique_ptr<Widget> ptr2 = std::move(ptr1);
// ptr1 现在为空
// ptr2->doSomething(); // ✅
// 自动释放
} // Widget 在此处被销毁
// 工厂函数返回 unique_ptr
std::unique_ptr<Widget> createWidget() {
return std::make_unique<Widget>();
}
```
**最佳实践**
- 默认使用 `std::make_unique` 创建C++14
- 用于表示独占所有权
- 作为函数参数传递时,使用 `std::move` 转移所有权
### std::shared_ptr共享所有权
`shared_ptr` 通过**引用计数**实现共享所有权。多个 `shared_ptr` 可以同时指向同一个对象,当最后一个 `shared_ptr` 被销毁时,对象才被删除。
```cpp
#include <memory>
#include <iostream>
void sharedPtrDemo() {
// 创建 shared_ptr
std::shared_ptr<Widget> ptr1 = std::make_shared<Widget>();
{
std::shared_ptr<Widget> 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 <memory>
#include <iostream>
class B; // 前向声明
class A {
public:
std::shared_ptr<B> b_ptr;
~A() { std::cout << "A 析构\\n"; }
};
class B {
public:
// 使用 weak_ptr 避免循环引用
std::weak_ptr<A> a_ptr;
~B() { std::cout << "B 析构\\n"; }
};
void weakPtrDemo() {
{
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
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<int> vec = {1, 2, 3}; // 自动管理内存
// 锁
std::mutex mtx;
{
std::lock_guard<std::mutex> 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]
-- 结果为 14412^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
# 这背后使用元编程动态创建了 postscommentscompany 等方法
```
## 元编程最佳实践
| 技术 | 适用场景 | 注意事项 |
|------|---------|---------|
| `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 渲染效果的文章无论你使用的是 CommonMarkGitHub 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``&lt;Route&gt;``&lt;Suspense&gt;`
## 表格
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
<div style="padding: 1em; background: #f8f9fa; border-radius: 8px; border-left: 4px solid #007bff;">
<p><strong>提示</strong> 这是使用 HTML 创建的自定义样式块 Markdown 的表达能力不足时可以直接嵌入 HTML但请注意这会降低内容的可移植性</p>
</div>
## 特殊字符
Markdown HTML 实体
- 版权符号&copy; 2024 Yggdrasil
- 注册商标Markdown&reg;
- 商标符号GitHub&trade;
- 长破折号这是&mdash;一个长破折号
- 短破折号这是&ndash;一个短破折号
- 省略号等等&hellip;
## 脚注
脚注是学术写作中常用的功能[^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 错误处理完全指南ResultOption thiserror
Rust 没有异常机制Exception对于习惯了 JavaPython JavaScript 的开发者来说这可能需要一些适应 Rust 的类型驱动错误处理通过 `Result` `Option` 类型实际上是一种更健壮更明确的设计
本文将深入探讨 Rust 的错误处理哲学从基础的 `Option` `Result` 到自定义错误类型再到 `?` 操作符和第三方库 `thiserror`
## 为什么 Rust 没有异常
传统的异常机制存在几个问题
1. **不可见的控制流**异常可以穿透任意层级的调用栈使得代码路径难以追踪
2. **类型安全缺失**编译器无法检查你是否处理了所有可能的异常
3. **性能开销**异常处理通常需要运行时维护额外的栈信息
Rust 的解决方案是**将错误作为值返回**如果一个函数可能失败它的返回类型就明确地表示这一点编译器会强迫你处理这个错误否则代码无法通过编译
## Option 类型处理可能缺失的值
`Option<T>` Rust 中表示"可能有值,也可能没有"的类型
```rust
enum Option<T> {
Some(T), // 有值
None, // 无值
}
```
### 基础使用
```rust
fn find_char(s: &str, c: char) -> Option<usize> {
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<i32> = 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::<i32>.unwrap_or(0); // 0
// unwrap_or_else懒加载默认值
let lazy = None::<i32>.unwrap_or_else(|| expensive_computation());
// ok_or Option 转换为 Result
let result: Result<i32, &str> = maybe_number.ok_or("值为空");
}
```
## Result 类型处理可能失败的操作
`Result<T, E>` Rust 中表示操作可能失败的核心类型
```rust
enum Result<T, E> {
Ok(T), // 成功
Err(E), // 失败携带错误信息
}
```
### 基础使用
```rust
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
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<Config, io::Error> {
let content = std::fs::read_to_string("config.json")?; // 如果失败直接返回 Err
let config: Config = serde_json::from_str(&content)?; // 如果失败直接返回 Err
Ok(config)
}
```
`?` 操作符的强大之处在于它可以自动进行错误类型的转换只要当前函数的返回错误类型实现了 `From<E>`就可以使用 `?`
### Result 的组合子
`Option` 类似`Result` 也有丰富的组合子
```rust
fn main() {
let result: Result<i32, &str> = 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
// expectunwrap 的变体可自定义 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<std::io::Error> for AppError {
fn from(err: std::io::Error) -> Self {
AppError::Io(err)
}
}
impl From<std::num::ParseIntError> 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<User, AppError> {
let content = std::fs::read_to_string("users.json")?; // 自动转换 io::Error
let users: Vec<User> = 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<i32> = Some(5);
let res: Result<i32, &str> = opt.ok_or("值为空"); // Ok(5)
let res2 = opt.ok_or_else(|| format!("{} 为空", "value")); // Ok(5)
// Result -> Option
let res: Result<i32, &str> = 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<Vec<i32>, _> = numbers.iter()
.map(|s| s.parse::<i32>())
.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<T>` | `Some(T)` / `None` | 可能缺失的值 |
| `Result<T, E>` | `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 数据类完全指南dataclassattrs 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 <stdio.h>
int main() {
int num = 42;
int *ptr = &num;
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 <stdio.h>
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 <stdio.h>
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 <stdio.h>
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 <stdio.h>
int main() {
int num = 42;
int *ptr1 = &num;
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 <stdio.h>
#include <stdlib.h>
// 使用二级指针创建动态二维数组
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 <stdio.h>
// 函数指针类型定义
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 <stdio.h>
// 通用排序函数,使用回调比较
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 <stdio.h>
#include <stdlib.h>
#include <string.h>
// 通用的内存拷贝函数
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 <stdio.h>
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 <stdio.h>
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 VMBEAM之上继承了 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<T>` | 启动返回结果的协程 |
| `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<Order> {
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<Int> = 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<Int> = 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>(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
// SharedFlow - 适合一次性事件
private val _events = MutableSharedFlow<String>()
val events: SharedFlow<String> = _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<News>) : 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<Int> = 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<String> = _searchQuery.asStateFlow()
// 搜索用户,自动去抖
val users: StateFlow<List<User>> = _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<String> = ["Rock", "Classical", "Hip hop"]
let otherGenres: Set<String> = ["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..<self {
task()
}
}
var squared: Int {
return self * self
}
}
3.repetitions {
print("Hello!")
}
print(5.squared) // 25
// 协议扩展提供默认实现
extension RandomNumberGenerator {
func randomBool() -> Bool {
return random() > 0.5
}
}
```
## 泛型
### 泛型函数和类型
```swift
// 泛型函数
func swapTwoValues<T>(_ 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<Element> {
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<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
print(stackOfStrings.pop()) // tres
// 泛型约束
func findIndex<T: Equatable>(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);