目录
一 闭包
1.使用闭包创建抽象行为
闭包: 可以捕获其所在环境的匿名函数
闭包:
- 是匿名函数
- 保存为变量、作为参数
- 可在一个地方创建闭包,然后在另一个上下文中调用闭包来完成运算
- 可从定义的作用域捕获值
例: 生成自定义运动计划的程序
算法的逻辑不是重点,重点是算法中的计算过程需要几秒钟时间
目标: 不让用户发生不必要的等待
- 仅在必要时调用该算法
- 只调用一次
use std::{time::Duration, thread};
fn main() {
println!("Hello, world!");
}
//模拟复杂的算法
fn sim_ex_ca(intensity: u32) -> u32 {
println!("ca slowly ....");
thread::sleep(Duration::from_secs(2)); // 睡了2sec
intensity
}
fn generate_workout(intensity: u32, random_number: u32) {
if intensity < 25 {
println!(
"today, do {} pushups",
sim_ex_ca(intensity)
);
println!(
"next, do {} pushups",
sim_ex_ca(intensity)
);
} else {
if random_number == 3 {
println!("take a break today!");
} else {
println!("today, run for {} klims",
sim_ex_ca(intensity)
);
}
}
}
其中在 generate_workout 函数中,多次调用 模拟算法,比较费时 , 进行该改善:
fn generate_workout(intensity: u32, random_number: u32) {
let expensive_result = sim_ex_ca(intensity);
if intensity < 25 {
println!("today, do {} pushups",expensive_result);
println!("next, do {} pushups",expensive_result);
} else {
if random_number == 3 {
println!("take a break today!");
} else {
println!("today, run for {} klims",expensive_result);
}
}
}
此时虽然对只对 模拟算法 只调用了一次,相比第一次的代码的已经有了较好的改善,但是本次代码中出现的问题是:对该模拟算法 总是会调用一次
再次进行改善:
在其中使用了闭包
但是还没有达到上面实现的目标
fn generate_workout(intensity: u32, random_number: u32) {
//定义的是闭包
let expensive_closure = |num| {
println!(" slowing");
thread::sleep(Duration::from_secs(2));
num
};
if intensity < 25 {
println!("today, do {} pushups",expensive_closure(intensity));
println!("next, do {} pushups",expensive_closure(intensity));
} else {
if random_number == 3 {
println!("take a break today!");
} else {
println!("today, run for {} klims",expensive_closure(intensity));
}
}
}
2. 闭包类型推断和标注
闭包不要求标注参数和返回值的类型
函数是暴露给用户,要显示接口的一部分,所以会指定类型
闭包通常很短小,只在狭小的上下文中工作,编译器通常能推断出类型
比较函数和闭包的定义语法
fn add_one_v1 (x: u32) -> u32 {x + 1}
let add_one_v2 = |x : u32| -> u32 { x + 1}; //闭包
let add_one_v3 = |x| {x + 1};
let add_one_v4 = |x| x+1;
闭包的定义最终只会为参数/返回值推断出唯一具体的类型
fn main() {
println!("Hello, world!");
let example_closure = |x| x;
let s = example_closure(String::from("hello"));
let n = example_closure(5);
}
3. 使用泛型参数和Fn Trait来存储闭包
继续解决 运动计划 点击跳转遗留的问题
给出一个解决方案:
创建一个struct ,它持有闭包以及调用结果
- 只会在需要结果时才执行该闭包
- 可缓存结果
这个模式通常叫做记忆化 或 延迟计算
如何让struct持有闭包
struct的定义需要知道所有字段的类型
- 需要指明闭包的类型
每个闭包实例都有自己唯一的匿名类型,即使两个闭包签名完全一样
所以需要使用: **泛型和Trait Bound**
Fn Trait
Fn traits 由标准库提供
所有的闭包都至少实现以下trait 之一:
- Fn
- FnMut
- FnOnce
use std::{time::Duration, thread};
struct Cacher<T>
where T: Fn(u32) -> u32
{
calculation: T,
value: Option<u32>
}
impl<T> Cacher<T>
where
T: Fn(u32) -> u32,
{
fn new(calculation: T) -> Cacher<T> {
Cacher {
calculation,
value: None,
}
}
fn value(&mut self, arg: u32) -> u32 {
match self.value {
Some(v) => v,
None => {
let v = (self.calculation)(arg);
self.value = Some(v);
v
}
}
}
}
fn main() {
let value = 10;
let number = 7;
generate_workout(value, number);
}
//模拟复杂的算法
fn generate_workout(intensity: u32, random_number: u32) {
//定义的是一个匿名函数
let mut expensive_closure =Cacher::new( |num| {
println!(" slowing");
thread::sleep(Duration::from_secs(2));
num
});
if intensity < 25 {
println!("today, do {} pushups",expensive_closure.value(intensity));
println!("next, do {} pushups",expensive_closure.value(intensity));
} else {
if random_number == 3 {
println!("take a break today!");
} else {
println!("today, run for {} klims",expensive_closure.value(intensity));
}
}
}
使用缓存器(Cacher)实现的限制
1. Cacher实例假定针对不同的参数arg,value方法总会得到相同的值
解决方法:
可以使用Hashmap替代单个值
key:arg参数
value: 执行闭包的结果
2. 只能接受一个u32类型的参数和u32类型的返回值
4.使用闭包捕获环境
闭包可以访问定义它的作用域内的变量,而普通函数则不能
闭包从所在环境捕获值的方式
与函数获得参数的三种方式一样:
1. 取得所有权: FnOnce
2. 可变借用: FnMut
3. 不可变借用: Fn
- 所有的闭包都实现了FnOnce
- 没有移动捕获变量的实现了FnMut
- 无需可变访问捕获变量的闭包实现了
二 迭代器
迭代器模式: 对一系列项执行某些任务
迭代器负责:
-遍历每个项
-确定序列(遍历)何时完成
Rust的迭代器:
--惰性的: 除非调用消费迭代器的方法,否则迭代器本身没有任何效果
1.Iterator trait 和next方法
所有迭代器都实现了iterator trait
Iterator trait 定义于标准库, 定义大致如下:
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// type Item 和self::Item 定义了与此该trait关联的类型
}
Iterator trait仅要求实现了一个方法: next
next:
每次返回迭代器的一项
返回结果包裹在Some里
迭代结束,返回None
可以直接在迭代器上调用next方法
#[cfg(test)]
mod tests {
#[test]
fn iterator_demos() {
let v1 = vec![1, 2, 3];
let mut v1_iter = v1.iter();
assert_eq!(v1_iter.next(), Some(&1));
}
}
几个迭代方法:
'iter’方法: 在不可变引用上创建迭代器
into_iter方法: 创建的迭代器会获得所有权
iter_mut方法:迭代可变的引用
2.消耗迭代器的方法
在标准库中 iterator trait有一些带默认实现的方法
其中有一些方法会调用next方法, 实现
调用next的方法叫做“消耗型迭代器”
例:sum方法:
取得迭代器的所有权
通过反复调用next,遍历所有元素
产生其他迭代器的方法
定义在Iterator trait上的另外一些方法叫做:迭代器适配器----把迭代器转换为不同种类的迭代器
可以通过链式调用使用多个迭代器适配器来执行复杂的操作,这种调用可读性较高
例 map方法
—接收一个闭包,闭包作用于每个元素
—产生一个 新的迭代器
#[cfg(test)]
mod tests {
#[test]
fn iterator_sum() {
let v1 = vec![1, 2, 3];
// _ 的意思是让编译器推断出类型
// collect()方法的作用是: 一个消耗型适配器,把结果收集到一个集合类型中
let v2: Vec<_> = v1.iter().map(|x| x+1).collect();
assert_eq!(v2, vec![2, 3, 4]);
}
}
3.使用闭包捕获环境
filter方法:
-接受一个闭包
-这个闭包在遍历迭代器的每个元素时,返回bool类型
-返回true:当前元素会包含在filter产生的迭代器中
#[derive(PartialEq, Debug)]
struct Shoe {
size: u32,
style: String,
}
fn shoe_in_my_size(shoe: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
shoe.into_iter().filter(|x| x.size == shoe_size).collect()
}
#[test]
fn filter_by_size() {
let shoes = vec![
Shoe {
size: 10,
style: String::from("sneaker"),
},
Shoe {
size: 11,
style: String::from("sneaker"),
},
Shoe {
size: 10,
style: String::from("sneaker"),
}
];
let in_my_size = shoe_in_my_size(shoes, 10);
assert_eq!(
in_my_size,
vec![
Shoe {
size: 10,
style: String::from("sneaker"),
},
Shoe {
size: 10,
style: String::from("sneaker"),
},
]
)
}
4. 使用Iterator trait 来创建自定义迭代器
实现next方法
struct Counter {
count: u32,
}
impl Counter {
fn new() -> Counter {
Counter { count: 0 }
}
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.count < 5 {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
#[test]
fn calling_next_directly() {
let mut counter = Counter::new();
assert_eq!(counter.next(), Some(1));
}
#[test]
fn using_other_iterator() {
//zip 拉链 将俩个迭代器合在一起, 形成一个元组
let sum: u32 = Counter::new()
.zip(Counter::new().skip(1))
.map(|(a, b)| a * b)
.filter(|x| x % 3 == 0)
.sum();
assert_eq!(18,sum);
}