【2025 Rust学习 --- 13 闭包:Rust的Lambda】

news2025/1/12 11:54:28

Rust的Lambda — 闭包

对整型向量进行排序很容易:

integers.sort(); 遗憾的是,当我们想对一些数据进行排序时,它们几乎从来都不是整型向量。例 如,对某种记录型数据来说,内置的 sort 方法一般不适用:

struct City {
 name: String,
 population: i64,
 country: String,
 ...
}
fn sort_cities(cities: &mut Vec<City>) {
 cities.sort(); // 出错:你到底想怎么排序?
}

报错说 City 没有实现 std::cmp::Ord。我们需要指定排序顺序,如下所示:

/// 按照人口数量对城市进行排序的辅助函数
fn city_population_descending(city: &City) -> i64 {
 -city.population
}
fn sort_cities(cities: &mut Vec<City>) {
 cities.sort_by_key(city_population_descending); // 正确
}

辅助函数 city_population_descending 会接受 City 型记录并提取其键,该键是我们对数据进行排序时要依据的字段。

sort_by_key 方法会将这个取键函数作为参数

如果将辅助函数写成闭包(匿名函数表达式)则会更简洁:

fn sort_cities(cities: &mut Vec<City>) {
 cities.sort_by_key(|city| -city.population);
}

它会接受一个参数 city 并 返回 -city.population。Rust 会从闭包的使用方式中推断出其参数类型和返 回类型。下面是标准库中接受闭包的其他例子。

这并不罕见:

  • 像 map 和 filter 这样的 Iterator 方法,可用于处理序列数据。
  • thread::spawn 这样的线程 API,会启动一个新的系统线程。并发就是要将工作转移给其他线程,而闭包能方便地表示这些工作单元。
  • 一些需要根据条件计算默认值的方法,比如 HashMap 条目的or_insert_with方法。此方法用于获取或创建 HashMap 中的条目,当默认值的计算成本很高时就要使用闭包。默认值会作为闭包传入,只有当不得不创建新条目时才会调用此闭包。

捕获变量

闭包可以使用属于其所在函数的数据:(参数)

// 根据任何其他的统计标准排序
fn sort_by_statistic(cities: &mut Vec<City>, stat: Statistic) {
 cities.sort_by_key(|city| -city.get_statistic(stat));
}

闭包“捕获”了 stat

// 启动重新排列城市所在表行的动画
function startSortingAnimation(cities, stat) {
 // 用来对表格进行排序的辅助函数
 // 注意此函数引用了stat
 function keyfn(city) {
 	return city.get_statistic(stat);
 }
 if (pendingSort)
 	pendingSort.cancel();
 // 现在开始动画,把keyfn传给它
 // 排序算法稍后会调用keyfn
 pendingSort = new SortingAnimation(cities, keyfn);
}

闭包 keyfn 存储在了新的 SortingAnimation 对象中。当 startSortingAnimation 返回后就会调用它。通常,当一个函数返回时,它 的所有变量和参数都会超出作用域并被丢弃。但是在这里,JavaScript 引擎必须 以某种方式保留 stat,因为闭包会使用它。大多数 JavaScript 引擎的实现方式 是在堆中分配 stat 并等垃圾回收器稍后回收。

借用值的闭包

fn sort_by_statistic(cities: &mut Vec<City>, stat: Statistic) {
 cities.sort_by_key(|city| -city.get_statistic(stat));
}

当 Rust 创建闭包时,会自动借入对 stat 的引用。这很合理, 因为闭包引用了 stat,所以闭包必须包含对 stat 的引用。

闭包同样遵循借用和生命周期的规则。特别是,由于闭包中包含对 stat 的引用,因此 Rust 不会让它的生命周期超出 stat。因为闭包只会在排序期间使用,所以这个例子是适用的。 简而言之,Rust 会使用生命周期而非垃圾回收来确保安全。Rust 的方式更快, 因为即使是最快的垃圾回收器在分配内存时也会比把 stat 保存在栈上慢,本例 中 Rust 就把 stat 保存在栈上。

“窃取”值的闭包

use std::thread;
fn start_sorting_thread(mut cities: Vec<City>, stat: Statistic)
 -> thread::JoinHandle<Vec<City>>
{
 let key_fn = |city: &City| -> i64 { -city.get_statistic(stat) }; // ->可以省略
 thread::spawn(|| {
     cities.sort_by_key(key_fn);
     cities
 })
}

thread::spawn 会接受一个闭包并 在新的系统线程中调用它。请注意 || 是闭包的空参数列表。

新线程会和调用者并行运行。当闭包返回时,新线程退出。(闭包的返回值会作 为 JoinHandle 值发送回调用线程。

但是编译不会通过:

因为 cities 也被不安全地共享了。简单来说, thread::spawn 创建的新线程无法保证在 cities 和 stat 被销毁之前在函数末尾完成其工作。

解决:要求 Rust 将 cities 和 stat移动到使用它们的闭包中,而不是借入对它们的引用。

fn start_sorting_thread(mut cities: Vec<City>, stat: Statistic)
 -> thread::JoinHandle<Vec<City>>
{
 let key_fn = move |city: &City| -> i64 { -city.get_statistic(stat) };
 thread::spawn(move || {
     cities.sort_by_key(key_fn);
     cities
 })
}

move 关键字会告诉 Rust,闭包并不是要借入它用到的变量,而是要“窃取”它们。

Rust 为闭包提供了两种从封闭作用域中获取数据的方法:移动和借用。

如果闭包要移动可复制类型的值 (如 i32),那么就会复制该值。因此,如果 Statistic 恰好是可复制类型【Clone】,那么即使在创建了要使用 stat 的 move 闭包之后,我们仍可以继续使用 stat。 不可复制类型的值(如 Vec)则确实会被移动。

在创建此闭包后,Rust 就不允许再通过 cities 访问它了。 实际上,在闭包将 cities 移动之后,此代码就不需要再使用它了。但 是,即使我们确实需要在此之后使用 cities,解决方法也很简单:可以要 求 Rust 克隆 cities 并将副本存储在另一个变量中。闭包将只会“窃取”其 中一个副本,即它所引用的那个副本。 通过遵循 Rust 的严格规则,我们收获线程安全。正是因为向量是被移动的,而不是跨线程共享的,所以我们知道旧线程肯定不会在新线程正在 修改向量的时候释放它。

函数与闭包的类型

可以将函数存储在变量【函数指针】中,也可 以使用所有常用的 Rust 语法来计算函数值:

let my_key_fn: fn(&City) -> i64 =
 if user.prefs.by_population {
 	city_population_descending
 } else {
 	city_monster_attack_risk_descending
 };
cities.sort_by_key(my_key_fn);

结构体也可以有函数类型的字段。像 Vec 这样的泛型类型可以存储大量的函 数,只要它们共享同一个 fn 类型即可。而且函数值占用的空间很小,因为 fn 值就是函数机器码的内存地址,就像 C++ 中的函数指针一样。

/// 给定一份城市列表和一个测试函数,返回有多少个城市通过了测试
fn count_cities(cities: &Vec<City>, func: fn(&City) -> bool) -> usize
{
 let mut count = 0;
 for city in cities {
     if func(city) {
        count += 1;
     }
 }
 count
}
/// 测试函数的示例。注意,此函数的类型是`fn(&City) -> bool`,
/// 与`count_cities` 的 `func`参数相同
fn has_monster_attacks(city: &City) -> bool {
 city.monster_attack_risk > 0.0
}
// 有多少个城市存在被怪兽袭击的风险?
let n = count_cities(&my_cities, has_monster_attacks);

但是闭包与函数不是同一种类型

let limit = preferences.acceptable_monster_risk();
let n = count_cities(&my_cities, |city| city.monster_attack_risk > limit); // 错误:类型不匹配

第二个参数会导致类型错误。为了支持闭包,必须更改这个函数的类型签名:

where F: Fn(&City) -> bool 注意Fn是大写 表示接受函数和闭包

fn count_selected_cities<F>(cities: &Vec<City>, test_fn: F) -> usize
 where F: Fn(&City) -> bool
{
 let mut count = 0;
 for city in cities {
     if test_fn(city) {
    	 count += 1;
     }
 }
 count
}

新版本是泛型函数。只要 F 实现了特定的特型 Fn(&City) -> bool,该函数 就能接受任意 F 型的 test_fn。以单个 &City 为参数并返回 bool 值的所有 函数和大多数闭包会自动实现这个特型:

fn(&City) -> bool // fn类型(只接受函数)

Fn(&City) -> bool // Fn特型(既接受函数也接受闭包)

-> 和返回类型是可选的,如果省略,则返回类 型为 ()。

每个闭包都有自己的类型,因为闭包可以包含数据:从封闭作 用域中借用或“窃取”的值。这既可以是任意数量的变量,也可以是任意类型的组 合。所以每个闭包都有一个由编译器创建的特殊类型,大到足以容纳这些数据。 任何两个闭包的类型都不相同。但是每个闭包都会实现 Fn 特型

每个闭包都有自己的类型,所以使用闭包的代码通常都应该是泛型的

闭包性能

Rust 中闭包的设计目标是要快:比函数指针还要快,快到甚至可以在对性能敏感的热点代码中使用它们。

如果你熟悉 C++ 的 lambda 表达式,就会发现 Rust 闭包也一样快速而紧凑,但更安全在大多数语言中,闭包会在堆中分配内存、进行动态派发以及进行垃圾回收。因此,创建、调用和收集每一个闭包都会花费一点点额外的 CPU 时间。

更糟的是,闭包往往难以内联,而内联是编译器用来消除函数调用开销并实施大量其他优化的关键技术。总而言之,闭包在这些语言中确实慢到值得手动将它们从节奏紧凑的内层循环中去掉。

Rust 闭包则没有这些性能缺陷,闭包没有垃圾回收。与 Rust 中的其他所有类型 一样,除非你将闭包放在 Box、Vec 或其他容器中,否则它们不会被分配到堆 上。由于每个闭包都有不同的类型,因此 Rust 编译器只要知道你正在调用的闭包的类型,就可以内联该闭包的代码。

在这里插入图片描述

  • 闭包 (a) 使用了上述两个变量。显然,我们正在寻找既有炸玉米饼(taco)又 有龙卷风(tornado)的城市。在内存中,这个闭包看起来像一个小型结构体,其中包含对其所用变量的引用。 请注意,这个闭包并不包含指向其代码的指针。只要 Rust 知道闭包的类型,就知道在调用此闭包时该运行哪些代码。
  • 闭包 (b) 与闭包 (a) 完全相同,只不过它是一个 move 闭包因此会包含值而非引用。
  • 闭包 © 不会使用其环境中的任何变量。该结构体是空的,所以这个闭包根本不会占用任何内存。 通常,编译器会内联所有对闭包的调用,然后连图中所示的小结构体也优化掉。

闭包与安全

杀死闭包

let my_str = "hello".to_string();
let f = || drop(my_str);

调用 f 时,my_str 会被丢弃。

调用f两次会发生什么呢?类似C++ 编程中会触发未定义行为的经典错误:双重释放

f(); // 正确
f(); // 错误:使用了已移动的值

Rust 知道这个闭包不能调用两次。

FnOnce

Rust实现了一个不那么强大的特型 FnOnce,即只 能调用一次的闭包特型。【cpp -> std::call_once】

第一次调用 FnOnce 闭包时,闭包本身也会被消耗掉。这是因为 Fn 和 FnOnce 这两个特型是这样定义的:

// 无参数的`Fn`特型和`FnOnce`特型的伪代码
trait Fn() -> R {
 fn call(&self) -> R;
}
trait FnOnce() -> R {
 fn call_once(self) -> R;
}

正如算术表达式 a + b 是方法调用 Add::add(a, b) 的简写形式一样,Rust 也会将 closure() 视为前面示例中的两个特型方法【call call_once】之一的简写形式:

  • 对于 Fn 闭包,closure() 会扩展为 closure.call()。此方法会通过引用获取 self,因此闭包不会被移动。
  • 但是如果闭包只能安全地调用一次,那么 closure() 就会扩展为 closure.call_once()。该方法会按值获取 self, 因此这个闭包就会被消耗掉。

FnMut

FnMut 是一个 trait,用于描述可以捕获其环境变量的可变引用的闭包。这意味着这样的闭包可以在其内部修改捕获到的变量的值。

FnMut 与 Fn、FnOnce 的区别

Rust 中有三个主要的闭包 trait:Fn、FnMut 和 FnOnce。它们之间的区别在于对捕获的环境变量的引用类型和使用方式:

  • Fn: 只能捕获环境变量的不可变引用,不能修改捕获到的变量。
  • FnMut: 可以捕获环境变量的可变引用,可以在闭包内部修改捕获到的变量。
  • FnOnce: 可以捕获环境变量的所有权,只能调用一次,调用后闭包就失效了。

在这里插入图片描述

Fn() 是 FnMut() 的子特型,而 FnMut() 是 FnOnce() 的子特型。 这使得 Fn 成了最严格且最强大的类别。FnMut 和 FnOnce 是更宽泛的类别, 其中包括某些具有使用限制的闭包。

为什么需要 FnMut?
  • 修改状态: 当我们需要在闭包内部修改外部变量的值时,就需要使用 FnMut。
  • 灵活的函数式编程: FnMut 提供了更多的灵活性,允许我们在函数式编程中进行一些有状态的操作。
FnMut 的应用场景
  • 迭代器: 许多迭代器适配器(比如 mapfilter)会产生新的迭代器,这些新的迭代器可能需要修改内部状态。
  • 高阶函数: 将闭包作为参数传递给函数时,如果需要闭包修改外部状态,就需要使用 FnMut。
  • 异步编程: 在异步编程中,闭包经常需要捕获外部状态,并异步地修改它们。

包含可变数据或可变引用的闭包

Rust 认为不可变值可以安全地跨线程共享,但是包含可变数据的不可变闭包不能安全共享——从多个线程调用这样的闭包可能会导致各种竞态条件,因为多个线程会试图同时读取和写入同一份数据。 Rust 还有另一类名为 FnMut 的闭包,也就是可写入的闭包。FnMut 闭包会通过可变引用来调用,其定义如下所示:

// `Fn`特型、`FnMut`特型和`FnOnce`特型的伪代码
trait Fn() -> R {
 fn call(&self) -> R;
}
trait FnMut() -> R {
 fn call_mut(&mut self) -> R;
}
trait FnOnce() -> R {
 fn call_once(self) -> R;
}

任何需要对值进行可变访问但不会丢弃任何值的闭包都是 FnMut 闭包

fn call_twice<F>(closure: F) where F: Fn() {
 closure();
 closure();
}
let mut i = 0;
let incr = || {
 i += 1; // incr借入了对i的一个可变引用 
 println!("Ding! i is now: {}", i);
};
call_twice(incr);

按照 call_twice 的调用方式,它会要求传入一个 Fn。由于 incr 是 FnMut 而非 Fn,因此上述代码无法通过编译。

对闭包的 Copy 与 Clone

就像能自动找出哪些闭包只能调用一次一样,Rust 也能找出哪些闭包可以实现 Copy 和 Clone,哪些则不可以实现。

闭包是表示包含它们捕获的变量的值(对于 move 闭包)或 对值的引用(对于非 move 闭包)的结构体。闭包的 Copy 规则和 Clone 规则 与常规结构体的规则是一样的。一个不修改变量的非 move 闭包只持有共享引用,这些引用既能 Clone 也能 Copy,所以闭包也能 Clone 和 Copy:

let y = 10;
let add1 = |x| x + y;
let add2 = add1; // 此闭包能`Copy`,所以……
assert_eq!(add1(add2(22)), 42); // ……可以调用它两次

一个会修改值的非 move 闭包在其内部表示中也可以有可变引用。可变引 用既不能 Clone,也不能 Copy,使用它们的闭包同样如此:

let mut x = 0;
let mut add_to_x = |n| { x += n; x };
let copy_of_add_to_x = add_to_x; // 这会进行移动而非复制
assert_eq!(add_to_x(copy_of_add_to_x(1)), 2); // 错误:使用了已移动出去的值

如果 move 闭包捕获的所有内容都能 Copy,那 它就能 Copy。如果 move 闭包捕获的所有内容都能 Clone

let mut greeting = String::from("Hello, ");
let greet = move |name| {
 greeting.push_str(name); println!("{}", greeting);
};
greet.clone()("Alfred");
greet.clone()("Bruce");

回调

App::new()
 .route("/", web::get().to(get_index))
 .route("/gcd", web::post().to(post_gcd))

get_index 和 post_gcd 是我们在程序其他地方 使用 fn 关键字声明的函数名称,也可以在这里传入闭包:

App::new()
 .route("/", web::get().to(|| {
 HttpResponse::Ok()
 .content_type("text/html")
 .body("<title>GCD Calculator</title>...")
 }))
 .route("/gcd", web::post().to(|form: web::Form<GcdParameters>| {
 HttpResponse::Ok()
 .content_type("text/html")
 .body(format!("The GCD of {} and {} is {}.",
 form.n, form.m, gcd(form.n, form.m)))
 }))

HTTP 请求和响应:

struct Request {
 method: String,
 url: String,
 headers: HashMap<String, String>,
 body: Vec<u8>
}
struct Response {
 code: u32,
 headers: HashMap<String, String>,
 body: Vec<u8>
}

现在路由器所做的只是存储一个将 URL 映射到回调的表,以便按需调用正确的 回调。(为简单起见,只允许用户创建与单个 URL 精确匹配的路由。)

struct BasicRouter<C> where C: Fn(&Request) -> Response {
 routes: HashMap<String, C>
}
impl<C> BasicRouter<C> where C: Fn(&Request) -> Response {
 /// 创建一个空路由器
     fn new() -> BasicRouter<C> {
     	BasicRouter { routes: HashMap::new() }
     }
     /// 给路由器添加一个路由
     fn add_route(&mut self, url: &str, callback: C) {
     	self.routes.insert(url.to_string(), callback);
     }
}

只给路由器添加一个路由,那么它是可以正常工作的:

let mut router = BasicRouter::new(); router.add_route("/", |_| get_form_response()); 这段代码可以编译和运行。

如果再添加一个路由: router.add_route("/gcd", |req| get_gcd_response(req));就会得到一些错误

在这里插入图片描述

所犯的错误在于如何定义 BasicRouter 类型:

struct BasicRouter<C> where C: Fn(&Request) -> Response {
 routes: HashMap<String, C>
}

这里声明的每个 BasicRouter 都带有一个回调类型 C,并且 HashMap 中的所 有回调都是此类型的。

解决方案:因为要支持多种类型,所以需要使 用 Box 和特型对象:

type BoxedCallback = Box<dyn Fn(&Request) -> Response>;
struct BasicRouter {
 routes: HashMap<String, BoxedCallback>
}

每个 Box 可以包含不同类型的闭包,因此单个 HashMap 可以包含各种回调。请 注意,类型参数 C 消失了。对此方法进行一些调整。

impl BasicRouter {
 // 创建一个空路由器
 fn new() -> BasicRouter {
 	BasicRouter { routes: HashMap::new() }
 }
 // 给路由器添加一个路由
 fn add_route<C>(&mut self, url: &str, callback: C)
 where C: Fn(&Request) -> Response + 'static {
 	self.routes.insert(url.to_string(), Box::new(callback));
 }
}

注意 add_route 的类型签名中 C 的两个限界:特定的 Fn 特型和 'static 生命周期。Rust 要求我们添加这个 'static 限界。如果没有 它,那么对 Box::new(callback) 的调用就会出错,因为如果闭包包含对即将超出作用域的变量的已借用引用,那么存储闭包就是不安全的。

处理传入请求:

impl BasicRouter {
 fn handle_request(&self, request: &Request) -> Response {
     match self.routes.get(&request.url) {
         None => not_found_response(),
         Some(callback) => callback(request)
     }
 }
}

我们还可以写出此路由器的更省空间的版本:它并不 存储特型对象,而是使用函数指针或 fn 类型。

fn add_ten(x: u32) -> u32 {
 x + 10
}
let fn_ptr: fn(u32) -> u32 = add_ten;
let eleven = fn_ptr(1); // 11

不从其环境中捕获任何内容的闭包与函数指针是一样的,因为它们不需 要保存有关捕获变量的任何额外信息。

持有函数指针的路由表如下所示:

struct FnPointerRouter {
 routes: HashMap<String, fn(&Request) -> Response>
}

HashMap 只会为每个 String 键存储一个 usize 值,更关键的是, 没有 Box。除了 HashMap 自身,根本不存在动态分配

闭包具有独特的类型,因为每个闭包会捕获不同的变量,所以和别的语法元素一样,它们各自具有不同的大小。但是,如果闭包没有捕捉到任何东西,那就没有什么要存储的了。通过在接受回调的函数中使用 fn 指针,可 以限制调用者仅使用这些非捕获型闭包,以牺牲调用者的灵活性为代价,在接受回调的代码中换取一定的性能和灵活性。

高效使用闭包

在具有垃圾回收的语言中,你可以在闭包中使用局部变量,而无须考虑生命周期或所有权的问题。但如果没有垃圾回收,那么情况就不同了。一些在 Java、C# 和 JavaScript 中常见的设计模式如果不进行改变将无法在 Rust 中正常工作。
在这里插入图片描述

以模型-视图-控制器设计模式(简称 MVC)为例。对于用户界面的每个元素,MVC 框架都会创建 3 个对象:表示该 UI 元素状态的模型、 负责其外观的视图和处理用户交互的控制器。

多年来,MVC 模式已经出现了无数变体,但总体思路仍是 3 个对象以某种方式分担了 UI 的职责。 这就是问题所在。通常,每个对象都会直接或通过回调对其他对象中的一个或两个进行引用,每当 3 个对象中的一个对象发生变化时,它会通知其他两个对象,因此所有内容都会及时更新。哪个对象“拥有”其他对象之类的问题永远不会出现。

在这里插入图片描述

如果不进行更改,就无法在 Rust 中实现此模式。所有权必须明晰,循环引用也必须消除。模型和控制器不能相互直接引用。 Rust 的“激进赌注”是基于“必然存在好的替代设计”这个假设的。有时你可以通过 让每个闭包接受它需要的引用作为参数,来解决闭包所有权和生命周期的问题。 有时你可以为系统中的每个事物分配一个编号,并传递这些编号而不是传递引用。或者你可以实现 MVC 的众多变体之一,其中的对象并非都相互引用。或者 你可以将工具包建模为具有单向数据流的非 MVC 系统,比如 Facebook 的 Flux 架构

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2275482.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

鸿蒙面试 2025-01-09

鸿蒙分布式理念&#xff1f;&#xff08;个人认为理解就好&#xff09; 鸿蒙操作系统的分布式理念主要体现在其独特的“流转”能力和相关的分布式操作上。在鸿蒙系统中&#xff0c;“流转”是指涉多端的分布式操作&#xff0c;它打破了设备之间的界限&#xff0c;实现了多设备…

一个基于Spring Boot的智慧养老平台

以下是一个基于Spring Boot的智慧养老平台的案例代码。这个平台包括老人信息管理、健康监测、紧急呼叫、服务预约等功能。代码结构清晰&#xff0c;适合初学者学习和参考。 1. 项目结构 src/main/java/com/example/smartelderlycare├── controller│ ├── ElderlyCon…

Taro+react 开发第一节创建 带有redux状态管理的项目

Taro 项目基于 node&#xff0c;请确保已具备较新的 node 环境&#xff08;>16.20.0&#xff09;&#xff0c;推荐使用 node 版本管理工具 nvm 来管理 node&#xff0c;这样不仅可以很方便地切换 node 版本&#xff0c;而且全局安装时候也不用加 sudo 了。 1.安装 npm inf…

云商城--基础数据处理和分布式文件存储

第2章 基础数据处理和分布式文件存储 1.分布式文件存储系统Ceph学习 ​ 1).掌握Ceph架构 ​ 2).掌握Ceph组件 ​ 3).搭建Ceph集群(了解) 2.Ceph使用 ​ 1).基于Ceph实现文件上传 ​ 2).基于Ceph实现文件下载 3.SKU、SPU管理 ​ 1).掌握SKU和SPU关系 ​ 2).理解商品发…

Vue.js:现代前端开发的灵活框架

大家好&#xff01;我是 [数擎 AI]&#xff0c;一位热爱探索新技术的前端开发者&#xff0c;在这里分享前端和 Web3D、AI 技术的干货与实战经验。如果你对技术有热情&#xff0c;欢迎关注我的文章&#xff0c;我们一起成长、进步&#xff01; 开发领域&#xff1a;前端开发 | A…

初学者关于对机器学习的理解

一、机器学习&#xff1a; 1、概念&#xff1a;是指从有限的观测数据中学习(或“猜 测”)出具有一般性的规律&#xff0c;并利用这些规律对未知数据进行预测的方法.机器学 习是人工智能的一个重要分支&#xff0c;并逐渐成为推动人工智能发展的关键因素。 2、使用机器学习模型…

小程序textarea组件键盘弹起会遮挡住输入框

<textarea value"{{remark}}" input"handleInputRemark" ></textarea> 如下会有遮挡&#xff1a; 一行代码搞定 cursor-spacing160 修改后代码 <textarea value"{{remark}}" input"handleInputRemark" cursor-spacin…

树的模拟实现

一.链式前向星 所谓链式前向星&#xff0c;就是用链表的方式实现树。其中的链表是用数组模拟实现的链表。 首先我们需要创建一个足够大的数组h&#xff0c;作为所有结点的哨兵位。创建两个足够大的数组e和ne&#xff0c;一个作为数据域&#xff0c;一个作为指针域。创建一个变…

通过氧化最小化工艺提高SiC MOSFET迁移率的深入分析

标题 Insight Into Mobility Improvement by the Oxidation-Minimizing Process in SiC MOSFETs&#xff08;TED2024&#xff09; 文章的研究内容 文章的研究内容主要围绕氧化最小化工艺&#xff08;oxidation-minimizing process&#xff09;对碳化硅&#xff08;SiC&…

相机和激光雷达的外参标定 - 无标定板版本

1. 实现的效果 通过本软件实现求解相机和LiDAR的外参&#xff0c;即2个传感器之间的三维平移[x, y, z]和三维旋转[roll, pitch, yaw]。完成标定后&#xff0c;可将点云投影到图像&#xff0c;效果图如下&#xff1a; 本软件的优势&#xff1a;&#xff08;1&#xff09;无需特…

git问题

拉取项目代码后&#xff0c;出现 1、找回未commit的代码 2、记录不全&#xff0c;只是显示部分代码记录

Spring bean的生命周期和扩展

接AnnotationConfigApplicationContext流程看实例化的beanPostProcessor-CSDN博客&#xff0c;以具体实例看bean生命周期的一些执行阶段 bean生命周期流程 生命周期扩展处理说明实例化:createBeanInstance 构造方法&#xff0c; 如Autowired的构造方法注入依赖bean 如UserSer…

来自通义万相的创意加速器:AI 绘画创作

来自通义万相的创意加速器&#xff1a;AI 绘画创作 通义万相动手搭建“通义万相”部署方案资源准备对象存储OSS&#xff08;手动部署&#xff09;DashScope 模型服务灵积云服务器ECS&#xff08;手动部署&#xff09;一键部署ROS Web文生图艺术与设计创作广告与营销物料生成教育…

STM32F4分别驱动SN65HVD230和TJA1050进行CAN通信

目录 一、CAN、SN65HVD230DR二、TJA10501、TJA1050 特性2、TJA1050 引脚说明 三、硬件设计1、接线说明2、TJA1050 模块3、SN65HVD230 模块 四、程序设计1、CAN_Init&#xff1a;CAN 外设初始化函数2、CAN_Send_Msg、CAN_Receive_Msg 五、功能展示1、接线图2、CAN 数据收发测试 …

Elasticsearch:在 HNSW 中提前终止以实现更快的近似 KNN 搜索

作者&#xff1a;来自 Elastic Tommaso Teofili 了解如何使用智能提前终止策略让 HNSW 加快 KNN 搜索速度。 在高维空间中高效地找到最近邻的挑战是向量搜索中最重要的挑战之一&#xff0c;特别是当数据集规模增长时。正如我们之前的博客文章中所讨论的&#xff0c;当数据集规模…

时空笔记:CBEngine(微观交通模拟引擎)

CBEngine 是一个微观交通模拟引擎&#xff0c;可以支持城市规模的道路网络交通模拟。CBEngine 能够快速模拟拥有数千个交叉路口和数十万辆车辆的道路网络交通。 以下内容基本翻译自CBEngine — CBLab 1.0.0 documentation 1 模拟演示 1.0 模拟演示结构 config.cfg 定义了 roa…

Notepad++上NppFTP插件的安装和使用教程

一、NppFTP插件下载 图示是已经安装好了插件。 在搜索框里面搜NppFTP&#xff0c;一般情况下&#xff0c;自带的下载地址容易下载失败。这里准备了一个下载连接&#xff1a;Release v0.29.10 ashkulz/NppFTP GitHub 这里我下载的是x86版本 下载好后在nodepad的插件里面选择打…

基于华为ENSP的OSPF不规则区域划分深入浅出(5)

本篇技术博文摘要 &#x1f31f; OSPF不规则区域划分及其问题解决方案涉及多个技术手段&#xff0c;包括隧道、虚链路和路由重发布等。合理的网络设计和配置对于避免网络中出现的环路问题至关重要。通过多进程双向重发布等方式&#xff0c;能够有效地优化路由协议的互通性和网络…

微信小程序——创建滑动颜色条

在微信小程序中&#xff0c;你可以使用 slider 组件来创建一个颜色滑动条。以下是一个简单的示例&#xff0c;展示了如何实现一个颜色滑动条&#xff0c;该滑动条会根据滑动位置改变背景颜色。 步骤一&#xff1a;创建小程序项目 首先&#xff0c;使用微信开发者工具创建一个新…

【再谈设计模式】模板方法模式 - 算法骨架的构建者

一、引言 在软件工程、软件开发过程中&#xff0c;我们经常会遇到一些算法或者业务逻辑具有固定的流程步骤&#xff0c;但其中个别步骤的实现可能会因具体情况而有所不同的情况。模板方法设计模式&#xff08;Template Method Design Pattern&#xff09;就为解决这类问题提供了…