当前位置: 首页 > news >正文

【Rust自学】15.6. RefCell与内部可变性:“摆脱”安全性限制

题外话,这篇文章一共4050字,是截止到目前为止最长的文章,如果你能坚持读完并理解,那真的很强!
请添加图片描述
喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
请添加图片描述

15.6.1. 什么是内部可变性

内部可变性(interior mutability)是Rust的设计模式之一,它允许程序员在只持有不可变引用的前提下对数据进行修改。

通常而言,这样的行为会被借用规则(详见 4.4. 引用与借用)所禁止,但是为了能够改变数据,内部可变性模式在代码的数据结构里使用了unsafe代码来绕过Rust正常的可变性和借用规则。

不安全代码向编译器表明我们正在手动检查规则,而不是依赖编译器为我们检查规则。不安全代码相关的概念将在以后的文章中涉及。

15.6.2. RefCell<T>

Rc<T>不同,RefCell<T>类型代表了其持有数据的唯一所有权

为了了解RefCell<T>Box<T>的区别,我们得回顾一下借用规则(详见 4.4. 引用与借用):

  • 在任何给定时间,你可以拥有(但不能同时拥有)一个可变引用或任意数量的不可变引用。
  • 引用总是保持有效。

PS:给定时间可以理解为给定的作用域内

RefCell<T>Box<T>的区别如下:

类型检查阶段规则违背后果
Box<T>编译阶段检查借用规则编译时报错
RefCell<T>运行时检查借用规则触发 panic

借用规则在不同阶段进行检查有不同的特点:

  • 编译阶段:

    • 尽早暴露问题
    • 没有任何运行时的开销
    • 是大多数场景的最佳选择
    • 是Rust的默认行为
  • 运行时:

    • 问题暴露延后,甚至到生产环境
    • 因借用计数产生些许性能损失
    • 实现某些特定的内存安全场景(比如在不可变环境中修改自身数据)

该在什么时候使用`RefCell

Rust编译器在编译阶段会检查所有的代码,其中大部分代码它都能够分析明白,如果没有问题就通过编译,如果有问题就报错。

Rust编译器是非常保守的,某些代码并不能在编译阶段就能分析明白,针对这类无法在编译阶段完成分析的代码Rust会直接拒绝掉,哪怕这些代码本质上没有任何问题。

Rust这么保守是为了保证程序的安全性。虽然拒绝掉某些本身没有问题的代码会对开发者造成不便,但是至少不会产生任何灾难性的后果。

针对这些编译器无法分析的代码,如果开发者能够保证这段代码满足借用规则,那么就可以使用RefCell<T>

RefCell<T>类似,Rc<T>只适用于单线程场景。

15.6.3. 如何在Box<T>Rc<T>RefCell<T>中进行选择

根据下表列出的三者的特性就可以进行选择:

特性Box<T>Rc<T>RefCell<T>
同一数据的所有者一个多个一个
可变性、借用检查可变、不可变借用(编译时检查)不可变借用(编译时检查)可变、不可变借用(运行时检查)

额外说一句,由于RefCell<T>在运行时才会被检查,所以即使RefCell<T>本身是不可变的,但我们仍然可以修改里面储存的值

15.6.4. 内部可变形:可变的借用一个不可变的值

这个小标题有一点绕,意思是对一个没有声明为mut的类型使用&mut引用。看个例子就明白了:

fn main() {let x = 5;let y = &mut x;
}

借用规则的一个推论是,当你有一个不可变的值时,你就不能可变地借用它。所以这么写会报错:

error[E0596]: cannot borrow `x` as mutable, as it is not declared as mutable--> src/main.rs:3:13|
3 |     let y = &mut x;|             ^^^^^^ cannot borrow as mutable|
help: consider changing this to be mutable|
2 |     let mut x = 5;|         +++For more information about this error, try `rustc --explain E0596`.
error: could not compile `borrowing` (bin "borrowing") due to 1 previous error

然而在某些特定情况下,我们会需要这样一个值——它对外部保持不可变,但它同时能在方法内部修改自身的值,除了这个值本身的方法,其余的代码都不能修改这个值,这叫做内部可变性RefCell<T>就是为了这种情况而存在的。

但是RefCell<T>并没有完全地绕开借用规则,编译阶段的检查虽然能够通过,但是在运行阶段如果违反了借用规则就会造成程序恐慌。

下面看一个例子(lib.rs):

功能:用于跟踪某个值与最大值的接近程度,并在该值达到特定级别时发出警告

pub trait Messenger {fn send(&self, msg: &str);
}pub struct LimitTracker<'a, T: Messenger> {messenger: &'a T,value: usize,max: usize,
}impl<'a, T> LimitTracker<'a, T>
whereT: Messenger,
{pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {LimitTracker {messenger,value: 0,max,}}pub fn set_value(&mut self, value: usize) {self.value = value;let percentage_of_max = self.value as f64 / self.max as f64;if percentage_of_max >= 1.0 {self.messenger.send("Error: You are over your quota!");} else if percentage_of_max >= 0.9 {self.messenger.send("Urgent warning: You've used up over 90% of your quota!");} else if percentage_of_max >= 0.75 {self.messenger.send("Warning: You've used up over 75% of your quota!");}}
}

这个例子的逻辑并不重要,看一下它的写法:

  • 程序开头定义了Messenger trait,里面有send方法的签名:接收不可变引用&self和一个字符串切片类型&str的形参msg作为参数。

  • 下面定义了一个结构体叫LimitTracker,它是一个泛型类型,生命周期为'a,泛型参数为T,要求T的生命周期为'a并实现Messenger这个在程序开头定义的trait。LimitTracker里面有三个字段:

    • messenger:类型为&str字符串切片类型,生命周期为'a
    • value:类型为usize
    • max:类型为usize
  • 往下看,通过impl块在LimitTracker上写了关联函数new,其参数是类型为泛型引用&T的形参messenger和类型为usize的形参max,返回值是LimitTracker类型。这个函数用于创建LimitTracker实例,这个实例:

    • messenger字段是形参menssenger的值
    • value字段值为0
    • max字段值为形参max的值
  • LimitTracker还有一个方法叫做set_value,其第一个参数是self的可变引用&mut self,第二个参数是value,类型为usize
    方法内部的代码逻辑很简单。把selfvalue字段值和参数value的值相除(还要转换成f64避免丢失精度)得到一个百分比,存储在percentage_of_max内。根据percentage_of_max的大小使用Messenger trait下的send方法发送不同的警告。

使用测试替代(test double)进行测试

这里有一个问题,如果我们要对这个set_value方法进行测试,就需要这个方法得输出些什么东西以供断言。但是set_value方法实际上并没有返回任何的值,所以说它不会提供任何的结果来进行断言。

我们要测试的是当某一个实现了Messenger trait的值和一个max值来创建LimitTracker实例时,传入不同的value就能够触发Messenger发送不同的消息。

为了解决这个问题,这里要介绍test double,它的中文叫测试替代,是一个通用的变成概念,代表了测试工作中被用作其他类型的替代品。test double中有一个特定的类型,叫模拟对象Mock Object),它会承担记录测试过程中的工作。我们就可以利用这些记录来断言这个测试工作运行是否正确。

Rust里没有类似的概念,在标准库里也没有模拟对象(Mock Object),但是我们可以自定义一个结构体来实现和Mock Object相同的功能。

接着上文的代码来写:

#[cfg(test)]
mod tests {use super::*;struct MockMessenger {sent_messages: Vec<String>,}impl MockMessenger {fn new() -> MockMessenger {MockMessenger {sent_messages: vec![],}}}impl Messenger for MockMessenger {fn send(&self, message: &str) {self.sent_messages.push(String::from(message));}}#[test]fn it_sends_an_over_75_percent_warning_message() {let mock_messenger = MockMessenger::new();let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);limit_tracker.set_value(80);assert_eq!(mock_messenger.sent_messages.len(), 1);}
}
  • 在测试模块的最开头先声明了MockMessenger结构体,里面有1个字段sent_message,表示发送的消息,其类型是Vec<String>

  • MockMessenger在下文又通过impl块创建了了new函数用于创建MockMessenger的实例,实例的sent_messages字段的值是一个空的Vector

  • 后面又为MockMessenger结构体实现了整个代码一开头的Messenger trait。实现了这个trait之后MockMessenger就可以用来创建LimitTracker(因为LimitTracker要求泛型类型实现Messenger trait)。
    使用send方法时这个消息会存储在MockMessenger下字段sent_message这个Vector里。

  • 最后是it_sends_an_over_75_percent_warning_message这个测试函数,它测试的是超过75%的这部分。
    首先创建了MockMessenger的实例叫mock_messenger,然后创建了一个LimitTracker的实例叫limit_tracker,接着在LimitTracker的实例上(就是limit_tracker)调用。
    最后通过mock_messengersent_message这个Vector里元素的数量来断言。

此时的代码逻辑有问题,但是运行会报错:

error[E0596]: cannot borrow `self.sent_messages` as mutable, as it is behind a `&` reference--> src/lib.rs:58:13|
58 |             self.sent_messages.push(String::from(message));|             ^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable|
help: consider changing this to be a mutable reference in the `impl` method and the `trait` definition|
2  ~     fn send(&mut self, msg: &str);
3  | }
...
56 |     impl Messenger for MockMessenger {
57 ~         fn send(&mut self, message: &str) {|For more information about this error, try `rustc --explain E0596`.
error: could not compile `limit-tracker` (lib test) due to 1 previous error

错误在为MockMessenger实现Messenger trait时定义send方法的过程:

impl Messenger for MockMessenger {fn send(&self, message: &str) {self.sent_messages.push(String::from(message));}
}

无法修改MockMessenger来跟踪消息,因为send方法的函数签名参数是对self不可变引用。我们也无法使用&mut self来代替,因为这样send的签名将与Messenger trait定义中的签名&self不匹配。

针对这种需要内部可变性的情况,就可以使用RefCell<T>,只需要把MockMessengersent_messages字段用RefCell<T>再包装一下即可:

struct MockMessenger {sent_messages: RefCell<Vec<String>>,
}

由于RefCell<T>不在预导入模块中,所以在使用它之前得先把它引入当前作用域

use std::cell::RefCell;

这样改了之后使用了sent_messages字段的代码都需要使用RefCell<T>再包装一下:

impl MockMessenger {fn new() -> MockMessenger {MockMessenger {sent_messages: RefCell::new(vec![]),}}
}

RefCell到底是怎么用的呢?其实就是用RefCell创建的数据,可以用borrow_mut方法来修改,对实参调用borrow_mut方法即可获得一个可变引用,所以为MockMessenger实现Messenger trait时定义send方法就可以使用borrow_mut

impl Messenger for MockMessenger {fn send(&self, message: &str) {self.sent_messages.borrow_mut().push(String::from(message));}
}

这样即使send的参数是不可变引用,在函数体里也可以通过borrow_mut来修改其值。

最后把测试函数的断言部分改一下:

fn it_sends_an_over_75_percent_warning_message() {let mock_messenger = MockMessenger::new();let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);limit_tracker.set_value(80);assert_eq!(mock_messenger.sent_messages..borrow().len(), 1);
}

mock_messenger使用borrow函数即可获取对该变量的不可变引用用于断言。

这时候运行就没有问题了,整体代码如下:

pub trait Messenger {fn send(&self, msg: &str);
}pub struct LimitTracker<'a, T: Messenger> {messenger: &'a T,value: usize,max: usize,
}impl<'a, T> LimitTracker<'a, T>
whereT: Messenger,
{pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {LimitTracker {messenger,value: 0,max,}}pub fn set_value(&mut self, value: usize) {self.value = value;let percentage_of_max = self.value as f64 / self.max as f64;if percentage_of_max >= 1.0 {self.messenger.send("Error: You are over your quota!");} else if percentage_of_max >= 0.9 {self.messenger.send("Urgent warning: You've used up over 90% of your quota!");} else if percentage_of_max >= 0.75 {self.messenger.send("Warning: You've used up over 75% of your quota!");}}
}#[cfg(test)]
mod tests {use super::*;use std::cell::RefCell;struct MockMessenger {sent_messages: RefCell<Vec<String>>,}impl MockMessenger {fn new() -> MockMessenger {MockMessenger {sent_messages: RefCell::new(vec![]),}}}impl Messenger for MockMessenger {fn send(&self, message: &str) {self.sent_messages.borrow_mut().push(String::from(message));}}#[test]fn it_sends_an_over_75_percent_warning_message() {let mock_messenger = MockMessenger::new();let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);limit_tracker.set_value(80);assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);}
}

15.6.5. 使用RefCell<T>在运行时记录借用信息

实际上,上文所使用的borrow_mutborrow方法相当于提供给用户的两个安全接口:

  • borrow:返回智能指针Ref<T>,它实现了Deref trait
  • borrow_mut:返回智能指针RefMut<T>,实现了Deref trait

RefCell<T>会记录当前存在多少活跃的Ref<T>RefMut<T>

  • 每次调用borrow:不可变借用计数加1。
    任何一个Ref<T>的值离开作用域被释放:不可变借用计数减1
  • 每次调用borrow_mut:可变借用计数加1
    任何一个RefMut<T>的值离开作用域被释放:可变借用计数减1

与编译时借用规则(详见 4.4. 引用与借用)一样, RefCell<T>允许我们在任何时间点拥有许多不可变借用或一个可变借用。

如果我们尝试违反这些规则, RefCell<T>的实现将在运行时出现恐慌(因为RefCell<T>在运行时才会进行借用规则检查)。出现恐慌 already borrowed: BorrowMutError 就是RefCell<T>在运行时处理违反借用规则的方式。

15.6.6. 将Rc<T>RefCell<T>结合使用的例子

Rc<T>允许某些数据被多个所有者持有,但它只提供对该数据的不可变访问。如果您有一个包含RefCell<T>Rc<T> ,你可以获得一个可以拥有多个所有者并且可变的值。

下面看一个将Rc<T>RefCell<T>结合使用来实现多重数据所有权的可变数据:

#[derive(Debug)]
enum List {Cons(Rc<RefCell<i32>>, Rc<List>),Nil,
}use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;fn main() {let value = Rc::new(RefCell::new(5));let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a));let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));*value.borrow_mut() += 10;println!("a after = {a:?}");println!("b after = {b:?}");println!("c after = {c:?}");
}

还记得上一篇文章的Cons列表示例吗?其中我们使用了Rc<T>允许多个列表共享另一个列表的所有权。因为Rc<T>仅保存不可变值,一旦创建了列表中的任何值,我们就无法更改它们。通过这篇文章的内容,让我们添加RefCell<T>以获得更改列表中的值的能力:

  • 首先在生命枚举类型List时把Cons关联的i32类型用RefCell<>包裹,由于Rust编译器无法确定RefCell<T>大小,得用Rc<>包裹在外,其余保持不变
  • 记得引入RcRefCell到当前作用域
  • 下面通过Rc::new()RefCell::new()来创建实例,a通过Rc::clone()来共享value的值,bc通过Rc::clone()来共享a的值(前提是aRc<>包裹)。
  • 最后通过RefCell<T>上的borrow_mut获得value的可变引用,其类型时&i32,然后通过解引用符号*变为i32来进行加10的操作。

输出:

a after = Cons(RefCell { value: 15 }, Nil)
b after = Cons(RefCell { value: 3 }, Cons(RefCell { value: 15 }, Nil))
c after = Cons(RefCell { value: 4 }, Cons(RefCell { value: 15 }, Nil))

跟预期一样,没有问题。

15.6.7. 其他可以实现内部可变性的类型

  • Cell<T>:通过复制来访问数据
  • Mutex<T>:用于实现跨线程情况下的内部可变性模5

相关文章:

【Rust自学】15.6. RefCell与内部可变性:“摆脱”安全性限制

题外话&#xff0c;这篇文章一共4050字&#xff0c;是截止到目前为止最长的文章&#xff0c;如果你能坚持读完并理解&#xff0c;那真的很强&#xff01; 喜欢的话别忘了点赞、收藏加关注哦&#xff08;加关注即可阅读全文&#xff09;&#xff0c;对接下来的教程有兴趣的可以…...

14.模型,纹理,着色器

模型、纹理和着色器是计算机图形学中的三个核心概念&#xff0c;用通俗易懂的方式来解释&#xff1a; 1. 模型&#xff1a;3D物体的骨架 通俗解释&#xff1a; 模型就像3D物体的骨架&#xff0c;定义了物体的形状和结构。 比如&#xff0c;一个房子的模型包括墙、屋顶、窗户等…...

【C语言分支与循环结构详解】

目录 ---------------------------------------begin--------------------------------------- 一、分支结构 1. if语句 2. switch语句 二、循环结构 1. for循环 2. while循环 3. do-while循环 三、嵌套结构 结语 -----------------------------------------end----…...

新项目上传gitlab

Git global setup git config --global user.name “FUFANGYU” git config --global user.email “fyfucnic.cn” Create a new repository git clone gitgit.dev.arp.cn:casDs/sawrd.git cd sawrd touch README.md git add README.md git commit -m “add README” git push…...

qt-QtQuick笔记之常见项目类简要介绍

qt-QtQuick笔记之常见项目类简要介绍 code review! 文章目录 qt-QtQuick笔记之常见项目类简要介绍1.QQuickItem2.QQuickRectangle3.QQuickImage4.QQuickText5.QQuickBorderImage6.QQuickTextInput7.QQuickButton8.QQuickSwitch9.QQuickListView10.QQuickGridView11.QQuickPopu…...

Continuous Batching 连续批处理

原始论文题目: Continuous Batching — ORCA: a distributed serving system for Transformer-based generative models 关键词: Continuous Batching, iteration-level scheduling, selective batching 1.迭代级调度(iteration-level scheduling) Orca系统又由几个关键…...

海外问卷调查渠道查如何设置:最佳实践+示例

随着经济全球化和一体化进程的加速&#xff0c;企业间的竞争日益加剧&#xff0c;为了获得更大的市场份额&#xff0c;对企业和品牌而言&#xff0c;了解受众群体的的需求、偏好和痛点才是走向成功的关键。而海外问卷调查才是获得受众群体痛点的关键&#xff0c;制作海外问卷调…...

把本地搭建的hexo博客部署到自己的服务器上

配置远程服务器的git 安装git 安装依赖工具包 yum install -y curl-devel expat-devel gettext-devel openssl-devel zlib-devel安装编译工具 yum install -y gcc perl-ExtUtils-MakeMaker package下载git&#xff0c;也可以去官网下载了传到服务器上 wget https://www.ke…...

初阶数据结构:链表(二)

目录 一、前言 二、带头双向循环链表 1.带头双向循环链表的结构 &#xff08;1)什么是带头&#xff1f; (2)什么是双向呢&#xff1f; &#xff08;3&#xff09;那什么是循环呢&#xff1f; 2.带头双向循环链表的实现 &#xff08;1&#xff09;节点结构 &#xff08;2…...

postgresql根据主键ID字段分批删除表数据

生产环境针对大表的处理相对比较麻烦。 方案1、直接truncate&#xff0c;可能会遇到系统卡主的情况&#xff0c;因为truncate的过程中会对表进行加锁&#xff0c;会导致数据不能正常的写入 方案2、创建一个同结构的表结构&#xff0c;rename旧表&#xff0c;不停业务rename表担…...

10.business english-global market

eco-friendly case study: 案例学习 At the workshop工作坊, they agreed to emphasize eco-friendliness,adapt messageing, and boost digital marketing to stand out globally. Our study shows that more people want eco-friendly products in different places.Looks …...

C 语言实现计算一年中指定日期是第几天 题】

引言 在编程的世界里&#xff0c;处理日期和时间相关的问题是非常常见的。比如在日历应用、任务管理系统、数据分析等场景中&#xff0c;经常需要计算某个日期在一年中是第几天。本文将详细介绍如何使用 C 语言来实现这一功能&#xff0c;通过分析代码的结构、逻辑以及可能存在…...

深入理解三高架构:高可用性、高性能、高扩展性的最佳实践

引言 在现代互联网环境下&#xff0c;随着用户规模和业务需求的快速增长&#xff0c;系统架构的设计变得尤为重要。为了确保系统能够在高负载和复杂场景下稳定运行&#xff0c;"三高架构"&#xff08;高可用性、高性能、高扩展性&#xff09;成为技术架构设计中的核…...

【反悔堆】力扣1642. 可以到达的最远建筑

给你一个整数数组 heights &#xff0c;表示建筑物的高度。另有一些砖块 bricks 和梯子 ladders 。 你从建筑物 0 开始旅程&#xff0c;不断向后面的建筑物移动&#xff0c;期间可能会用到砖块或梯子。 当从建筑物 i 移动到建筑物 i1&#xff08;下标 从 0 开始 &#xff09;…...

关于使用Mybatis-plus的TableNameHandler动态表名处理器实现分表业务的详细介绍

引言 随着互联网应用的快速发展&#xff0c;数据量呈爆炸式增长。传统的单表设计在面对海量数据时显得力不从心&#xff0c;容易出现性能瓶颈、查询效率低下等问题。为了提高数据库的扩展性和响应速度&#xff0c;分表&#xff08;Sharding&#xff09;成为了一种常见的解决方案…...

docker 安装 redis 详解

在平常的开发工作中&#xff0c;我们经常会用到 redis&#xff0c;那么 docker 下应该如何安装 redis 呢&#xff1f;简单来说&#xff1a;第一步&#xff1a;拉取redis镜像&#xff1b;第二步&#xff1a;设置 redis.conf 配置文件&#xff1b;第三步&#xff1a;编写 docker-…...

56. 合并区间

【题目】&#xff1a;56. 合并区间 class Solution { public:vector<vector<int>> merge(vector<vector<int>>& intervals) {// 按照左端点排序sort(intervals.begin(), intervals.end(), [&](vector<int> lhs, vector<int> rhs)…...

BOM对象location与数组操作结合——查询串提取案例

BOM对象location与数组操作结合——查询串提取案例 前置知识 1. Location 对象 Location 对象是 JavaScript 提供的内置对象之一&#xff0c;它表示当前窗口或框架的 URL&#xff0c;并允许你通过它操作或获取 URL 的信息。可以通过 window.location 访问。 主要属性&#…...

Jetson Orin Nano Super之 onnxruntime 编译安装

Jetson Orin Nano Super之 onnxruntime 编译安装 1. 源由2. 步骤步骤一&#xff1a;安装3.26 cmake步骤二&#xff1a;下载代码步骤三&#xff1a;编译代码步骤四&#xff1a;找到安装包步骤五&#xff1a;安装whl包 3. 注意4. 参考资料 1. 源由 Build onnxruntime 1.19.2 fai…...

开发环境搭建-3:配置 nodejs 开发环境 (fnm+ node + pnpm)

在 WSL 环境中配置&#xff1a;WSL2 (2.3.26.0) Oracle Linux 8.7 官方镜像 node 官网&#xff1a;https://nodejs.org/zh-cn/download 点击【下载】&#xff0c;选择想要的 node 版本、操作系统、node 版本管理器、npm包管理器 根据下面代码提示依次执行对应代码即可 基本概…...

[SWPUCTF 2022 新生赛]js_sign

题目 查看页面源代码 <!DOCTYPE html> <html> <head><meta charset"utf-8"><style>body {background-color: rgb(255, 255, 255);}</style> </head> <body><input id"flag" /><button>Check…...

农业信息化的基本框架

农业信息化的主要研究内容 基于作物模型的相关研究 作物生长模拟模型以及模型评价、模型的应用作物模型应用&#xff0c;包括&#xff1a;作物生态系统过程、生产管理措施、区域作物产量评估与气候变化对产量影响预测、基于作物模型的决策支持系统 数据挖掘、知识工程及应用、管…...

OpenAI的真正对手?DeepSeek-R1如何用强化学习重构LLM能力边界——DeepSeek-R1论文精读

2025年1月20日&#xff0c;DeepSeek-R1 发布&#xff0c;并同步开源模型权重。截至目前&#xff0c;DeepSeek 发布的 iOS 应用甚至超越了 ChatGPT 的官方应用&#xff0c;直接登顶 AppStore。 DeepSeek-R1 一经发布&#xff0c;各种资讯已经铺天盖地&#xff0c;那就让我们一起…...

Vue 3 中的父子组件传值:详细示例与解析

在 Vue 3 中&#xff0c;父子组件之间的数据传递是一个常见的需求。父组件可以通过 props 将数据传递给子组件&#xff0c;而子组件可以通过 defineProps 接收这些数据。本文将详细介绍父子组件传值的使用方法&#xff0c;并通过优化后的代码示例演示如何实现。 1. 父子组件传值…...

回顾2024,展望2025

项目 LMD performance phase2 今年修修补补&#xff0c;设计和做了很多item&#xff0c;有时候自己都数不清做了什么大大小小的item&#xff0c;但是for LMD performance phase2的go-live确实是最大也是最难的了&#xff0c;无论什么系统&#xff0c;只要用的人多了&#xff…...

【Python实现机器遗忘算法】复现2021年顶会 AAAI算法Amnesiac Unlearning

【Python实现机器遗忘算法】复现2021年顶会 AAAI算法Amnesiac Unlearning 1 算法原理 论文&#xff1a;Graves, L., Nagisetty, V., & Ganesh, V. (2021). Amnesiac machine learning. In Proceedings of the AAAI Conference on Artificial Intelligence, volume 35, 115…...

Vue 3 30天精进之旅:Day 03 - Vue实例

引言 在前两天的学习中&#xff0c;我们成功搭建了Vue.js的开发环境&#xff0c;并创建了我们的第一个Vue项目。今天&#xff0c;我们将深入了解Vue的核心概念之一——Vue实例。通过学习Vue实例&#xff0c;你将理解Vue的基础架构&#xff0c;掌握数据绑定、模板语法和指令的使…...

【ArcGIS微课1000例】0141:提取多波段影像中的单个波段

文章目录 一、波段提取函数二、加载单波段导出问题描述:如下图所示,img格式的时序NDVI数据有24个波段。现在需要提取某一个波段,该怎样操作? 一、波段提取函数 首先加载多波段数据。点击【窗口】→【影像分析】。 选择需要处理的多波段影像,点击下方的【添加函数】。 在多…...

【第九天】零基础入门刷题Python-算法篇-数据结构与算法的介绍-六种常见的图论算法(持续更新)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、Python数据结构与算法的详细介绍1.Python中的常用的图论算法2. 图论算法3.详细的图论算法1&#xff09;深度优先搜索&#xff08;DFS&#xff09;2&#xf…...

落地 轮廓匹配

个人理解为将一幅不规则的图形&#xff0c;通过最轮廓发现&#xff0c;最大轮廓匹配来确定图像的位置&#xff0c;再通过pt将不规则的图像放在规定的矩形里面&#xff0c;在通过透视变换将不规则的图形放进规则的图像中。 1. findHomography 函数 • Mat h findHomography(s…...