面试题答案
一键面试定义差异
- 闭包:闭包是一个可以捕获其环境的匿名函数。语法上,闭包使用
||
表示参数列表,{}
包含代码块,例如let closure = |x| x + 1;
。闭包的参数类型和返回类型通常可以省略,Rust 能根据上下文推断。 - 函数指针:函数指针指向一个命名函数,通过函数名直接获取。例如
fn add(a: i32, b: i32) -> i32 { a + b }
,let func_ptr: fn(i32, i32) -> i32 = add;
,函数指针必须明确声明参数和返回类型。
捕获环境变量
- 闭包:可以捕获其定义所在环境中的变量。有三种捕获方式:按值捕获(
move
语义),按可变引用捕获(&mut
),按不可变引用捕获(&
)。例如:
let x = 5;
let closure = move || println!("x is: {}", x);
这里闭包 closure
按值捕获了 x
。
- 函数指针:不能捕获环境变量,函数指针只能操作传递给它的参数。
生命周期
- 闭包:闭包捕获的变量的生命周期与闭包本身相关。如果闭包按值捕获变量,变量的所有权被转移到闭包中,闭包结束时变量被释放。如果按引用捕获,闭包的生命周期受限于被捕获引用的生命周期。
- 函数指针:函数指针本身没有生命周期,因为它们不捕获环境变量。函数参数和返回值遵循常规的 Rust 生命周期规则。
调用方式
- 闭包:可以像普通函数一样调用,例如
let result = closure(5);
。由于闭包可能捕获环境变量,调用时可能涉及到从环境中获取数据。 - 函数指针:同样像普通函数一样调用,
let result = func_ptr(3, 4);
,调用函数指针只涉及传递参数和执行函数体。
适用场景
- 适合闭包的场景:
- 迭代器操作:在
Iterator
的各种方法(如map
、filter
)中,闭包使用非常方便。例如:
- 迭代器操作:在
let numbers = vec![1, 2, 3, 4];
let squared = numbers.iter().map(|x| x * x).collect::<Vec<_>>();
- **需要捕获环境变量**:当逻辑需要依赖于外部环境中的变量时,闭包是很好的选择。如在一个函数内部定义一个根据外部变量动态生成逻辑的闭包。
- 适合函数指针的场景:
- 作为参数传递给需要明确类型的函数:有些函数要求参数类型明确,函数指针能提供清晰的类型信息。例如
std::thread::spawn
函数,如果传递的任务是一个简单的命名函数,使用函数指针更合适。
- 作为参数传递给需要明确类型的函数:有些函数要求参数类型明确,函数指针能提供清晰的类型信息。例如
fn task() {
println!("This is a task");
}
let handle = std::thread::spawn(task);
- **调用 C 语言风格的回调函数**:与 C 语言交互时,函数指针符合 C 语言的函数指针概念,使用函数指针更方便。