【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
字段值为0max
字段值为形参max
的值
-
LimitTracker
还有一个方法叫做set_value
,其第一个参数是self
的可变引用&mut self
,第二个参数是value
,类型为usize
。
方法内部的代码逻辑很简单。把self
的value
字段值和参数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_messenger
下sent_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>
,只需要把MockMessenger
的sent_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_mut
和borrow
方法相当于提供给用户的两个安全接口:
borrow
:返回智能指针Ref<T>
,它实现了Deref
traitborrow_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<>
包裹在外,其余保持不变 - 记得引入
Rc
和RefCell
到当前作用域 - 下面通过
Rc::new()
和RefCell::new()
来创建实例,a
通过Rc::clone()
来共享value
的值,b
和c
通过Rc::clone()
来共享a
的值(前提是a
被Rc<>
包裹)。 - 最后通过
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与内部可变性:“摆脱”安全性限制
题外话,这篇文章一共4050字,是截止到目前为止最长的文章,如果你能坚持读完并理解,那真的很强! 喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以…...
护眼好帮手:Windows显示器调节工具
在长时间使用电脑的过程中,显示器的亮度和色温对眼睛的舒适度有着重要影响。传统的显示器调节方式不仅操作繁琐,而且在低亮度下容易导致色彩失真。因此,今天我想为大家介绍一款适用于Windows系统的护眼工具,它可以帮助你轻松调节显…...
使用 OpenResty 构建高效的动态图片水印代理服务20250127
使用 OpenResty 构建高效的动态图片水印代理服务 在当今数字化的时代,图片在各种业务场景中广泛应用。为了保护版权、统一品牌形象,动态图片水印功能显得尤为重要。然而,直接在后端服务中集成水印功能,往往会带来代码复杂度增加、…...
36、【OS】【Nuttx】OSTest分析(2):环境变量测试
背景 2025.1.29 蛇年快乐! 接之前wiki 35、【OS】【Nuttx】OSTest分析(1):stdio测试(五) 已经分析完了第一个测试项,输入输出端口测试,接下来分析下环境变量测试,也比较…...
C++并发编程指南04
文章目录 共享数据的问题3.1.1 条件竞争双链表的例子条件竞争示例恶性条件竞争的特点 3.1.2 避免恶性条件竞争1. 使用互斥量保护共享数据结构2. 无锁编程3. 软件事务内存(STM) 总结互斥量与共享数据保护3.2.1 互斥量使用互斥量保护共享数据示例代码&…...
Java实现LRU缓存策略实战
实现LRU模型选择LRU缓存回收算法集成Google Guava(LRU缓存策略)插件Google Guava(LRU策略)缓存示例总结LRU(Least Recently Used,最近最少使用)缓存是一种常见的缓存淘汰策略。它的基本思想是优先保留最近被访问过的数据,淘汰最久未被访问的数据。这种策略的目的是为了…...
三个不推荐使用的线程池
线程池的种类 其实看似这么多的线程池,都离不开ThreadPoolExecutor去创建,只不过他们是简化一些参数 newFixedThreadPool 里面全是核心线程 有资源耗尽的风险,任务队列最大长度为Integer.MAX_VALUE,可能会堆积大量的请求ÿ…...
Golang 并发机制-1:Golang并发特性概述
并发是现代软件开发中的一个基本概念,它使程序能够同时执行多个任务,从而提高效率和响应能力。在本文中,我们将探讨并发性在现代软件开发中的重要性,并深入研究Go处理并发任务的独特方法。 并发的重要性 增强性能 并发在提高软…...
Flink中的时间和窗口
在批处理统计中,我们可以等待一批数据都到齐后,统一处理。但是在实时处理统计中,我们是来一条就得处理一条,那么我们怎么统计最近一段时间内的数据呢?引入“窗口”。 所谓的“窗口”,一般就是划定的一段时…...
Alfresco Content Services dockerCompose自动化部署详尽操作
Alfresco Content Services docker社区部署文档 Alfresco Content Services简介 官方说明书 https://support.hyland.com/r/Alfresco/Alfresco-Content-Services-Community-Edition/23.4/Alfresco-Content-Services-Community-Edition/Using/Content/Folder-rules/Defining-…...
吴恩达深度学习——深层神经网络
来自https://www.bilibili.com/video/BV1FT4y1E74V,仅为本人学习所用。 符号约定 对于该深层网络,有四层,包含三个隐藏层和一个输出层。 隐藏层中,第一层有五个单元、第二层有五个单元,第三层有三个单元。标记 l l l…...
【算法设计与分析】实验1:字符串匹配问题的算法设计与求解
目录 一、实验目的 二、实验环境 三、实验内容 四、核心代码 五、记录与处理 六、思考与总结 七、完整报告和成果文件提取链接 一、实验目的 给定一个文本,在该文本中查找并定位任意给定字符串。 1、深刻理解并掌握蛮力法的设计思想; 2、提高应用…...
C语言二级题解:查找字母以及其他字符个数、数字字符串转双精度值、二维数组上下三角区域数据对调
目录 一、程序填空题 --- 查找字母以及其他字符个数 题目 分析 二、程序修改 --- 数字字符串转双精度值 题目 分析 小数位字符串转数字 三、程序设计 --- 二维数组上下三角区域数据对调 题目 分析 前言 本文来讲解: 查找字母以及其他字符个数、数字字符串…...
Git进阶之旅:Git 配置信息 Config
Git 配置级别: 仓库级别:local [ 优先级最高 ]用户级别:global [ 优先级次之 ]系统级别:system [ 优先级最低 ] 配置文件位置: git 仓库级别对应的配置文件是当前仓库下的 .git/configgit 用户级别对应的配置文件时用…...
Qwen2-VL:在任何分辨率下增强视觉语言模型对世界的感知 (大型视觉模型 核心技术 分享)
摘要 我们推出了Qwen2-VL系列,这是对之前Qwen-VL模型的高级升级,重新定义了视觉处理中的常规预设分辨率方法。Qwen2-VL引入了Naive Dynamic Resolution机制,使模型能够动态地将不同分辨率的图像转换为不同的视觉令牌数量。这种方法允许模型生成更高效和准确的视觉表示,紧密…...
【C语言】在Windows上为可执行文件.exe添加自定义图标
本文详细介绍了在 Windows 环境下,如何为使用 GCC 编译器编译的 C程序 添加自定义图标,从而生成带有图标的 .exe 可执行文件。通过本文的指导,读者可以了解到所需的条件以及具体的操作步骤,使生成的程序更具专业性和个性化。 目录 1. 准备条件2. 具体步骤步骤 1: 准备资源文…...
记录 | Docker的windows版安装
目录 前言一、1.1 打开“启用或关闭Windows功能”1.2 安装“WSL”方式1:命令行下载方式2:离线包下载 二、Docker Desktop更新时间 前言 参考文章:Windows Subsystem for Linux——解决WSL更新速度慢的方案 参考视频:一个视频解决D…...
FortiOS 存在身份验证绕过导致命令执行漏洞(CVE-2024-55591)
免责声明: 本文旨在提供有关特定漏洞的深入信息,帮助用户充分了解潜在的安全风险。发布此信息的目的在于提升网络安全意识和推动技术进步,未经授权访问系统、网络或应用程序,可能会导致法律责任或严重后果。因此,作者不对读者基于本文内容所采取的任何行为承担责任。读者在…...
系统思考—心智模式
“我们的大脑对连贯性的渴望远胜于对准确性的追求。”—诺贝尔经济学得主丹尼尔卡尼曼 在面对复杂的决策时,我们往往更倾向于寻找那些能够迅速串联起来的信息,而非深入挖掘每一个细节的真实性。这种倾向在日常生活中或许能帮助我们迅速作出决策…...
【信息系统项目管理师-选择真题】2008上半年综合知识答案和详解
更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 【第1题】【第2题】【第3题】【第4题】【第5题】【第6题】【第7~8题】【第9题】【第10题】【第11题】【第12题】【第13题】【第14题】【第15题】【第16~20题】【第21题】【第22题】【第23题】【第24题】【第25题…...
深入理解三高架构:高可用性、高性能、高扩展性的最佳实践
引言 在现代互联网环境下,随着用户规模和业务需求的快速增长,系统架构的设计变得尤为重要。为了确保系统能够在高负载和复杂场景下稳定运行,"三高架构"(高可用性、高性能、高扩展性)成为技术架构设计中的核…...
从 SAP 功能顾问到解决方案架构师:破茧成蝶之路
目录 行业瞭望:架构师崭露头角 现状剖析:功能顾问的局限与机遇 能力跃迁:转型的核心要素 (一)专业深度的掘进 (二)集成能力的拓展 (三)知识广度的延伸 ࿰…...
《从因果关系的角度学习失真不变表示以用于图像恢复》学习笔记
paper:2303.06859 GitHub:lixinustc/Causal-IR-DIL: Distortion invariant feature learning for image restoration from a causality perspective 2023 CVPR 目录 摘要 1、介绍 1.1 图像修复任务 1.2 失真不变表示学习 1.3 因果效应估计的挑战…...
STM32 PWM驱动直流电机
接线图: 代码配置: 根据驱动舵机的代码来写,与舵机不同的是,这次的引脚接到了PA2上,所以需要改一下引脚以及改为OC3通道。 另外还需在配置两个GPIO引脚,来控制电机的旋转方向,这里连接到了PA4与…...
【Hadoop】Hadoop 概述
Hadoop 概述 Hadoop 是什么Hadoop 发展历史Hadoop 三大发行版本Hadoop 优势(4 高)Hadoop 组成(面试重点)HDFS 架构概述YARN 架构概述MapReduce 架构概述HDFS、YARN、MapReduce 三者关系 大数据技术生态体系 Hadoop 是什么 Hadoop…...
【仪器分析】FACTs-幅度
** 当然,这回是一篇没有插图的文章,但是有足够多的描述可以用来想象。 我拿这个系列当作前传试试水 引言。正弦信号可能会发生怎样的变化? ** 近日学FACTs,险些成为传函丁真, 如果从仪器角度考察正弦信号的测量&…...
deepseek R1的确不错,特别是深度思考模式
deepseek R1的确不错,特别是深度思考模式,每次都能自我反省改进。比如我让 它写文案: 【赛博朋克版程序员新春密码——2025我们来破局】 亲爱的代码骑士们: 当CtrlS的肌肉记忆遇上抢票插件,当Spring Boot的…...
Unity敌人逻辑笔记
写ai逻辑基本上都需要状态机。因为懒得手搓状态机,所以选择直接用动画状态机当逻辑状态机用。 架构设计 因为敌人的根节点已经有一个animator控制动画,只能增加一个子节点AI,给它加一个animator指向逻辑“动画”状态机。还有一个脚本&#…...
C++,STL 简介:历史、组成、优势
文章目录 引言一、STL 的历史STL 的核心组成三、STL 的核心优势四、结语进一步学习资源: 引言 C 是一门强大且灵活的编程语言,但其真正的魅力之一在于其标准库——尤其是标准模板库(Standard Template Library, STL)。STL 提供了…...
【事务管理】
目录 一. 介绍与操作二. Spring事务管理三. 事务四大特性 \quad 一. 介绍与操作 \quad \quad 二. Spring事务管理 \quad 推荐加在经常进行增删改的方法上 \quad 三. 事务四大特性 \quad ctrlaltt...
ERP革新:打破数据壁垒,重塑市场竞争
标题:ERP革新:打破数据壁垒,重塑市场竞争 文章信息摘要: Operator和Computer Use等工具通过模拟用户交互和自动化数据提取,绕过了传统ERP系统的API限制,打破了其数据护城河。这种技术革新降低了企业切换软…...
小阿卡纳牌
小阿卡纳牌 风:热湿 火:热干 水:冷湿 土:冷干 火风:温度相同,但是湿度不同,二人可能会在短期内十分热情,但是等待热情消退之后,会趋于平淡。 湿度相同、温度不同&#x…...
android的gradle
资料: GitHub - ChenSWD/CopyGradleInAction: 备份《Gradle IN Action》书中的源码,添加了部分注释 //github上一个开源项目,外加pdf书 Gradle User Manual gradle官网 讲的挺好的博客 Gradle之重新认识Gradle(项目结构、命令行、tas…...
时间轮:XXL-JOB 高效、精准定时任务调度实现思路分析
大家好,我是此林。 定时任务是我们项目中经常会遇到的一个场景。那么如果让我们手动来实现一个定时任务框架,我们会怎么做呢? 1. 基础实现:简单的线程池时间轮询 最直接的方式是创建一个定时任务线程池,用户每提交一…...
【愚公系列】《循序渐进Vue.js 3.x前端开发实践》029-组件的数据注入
标题详情作者简介愚公搬代码头衔华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主&…...
「 机器人 」扑翼飞行器控制的当前挑战与后续潜在研究方向
前言 在扑翼飞行器设计与控制方面,虽然已经取得了显著的进步,但在飞行时间、环境适应性、能量利用效率及模型精度等方面依旧存在亟待解决的挑战。以下内容概括了这些挑战和可能的改进路径。 1. 当前挑战 1.1 飞行时间短 (1)主要原因 能源存储有限(电池容量小)、驱动系…...
ICSE‘25 LLM Assistance for Memory Safety
不知道从什么时候开始,各大技术社区,技术群聊流行着 “用Rust重写!” ,放一张图(笑死… 这不, 随着大模型技术的流行,大家都在探索如何让大模型自动完成仓库级别(全程序)的代码重构,代码变换(Refactor&…...
使用 Docker 运行 Oracle Database 23ai Free 容器镜像并配置密码与数据持久化
使用 Docker 运行 Oracle Database 23ai Free 容器镜像并配置密码与数据持久化 前言环境准备运行 Oracle Database 23ai Free 容器基本命令参数说明示例 注意事项高级配置参数说明 总结 前言 Oracle Database 23ai Free 是 Oracle 提供的免费版数据库,基于 Oracle …...
在Linux系统上安装.NET
测试系统:openKylin(开放麒麟) 1.确定系统和架构信息: 打开终端(Ctrl Alt T),输入cat /etc/os-release查看系统版本相关信息。 输入uname -m查看系统架构。确保你的系统和架构符合.NET 的要求,如果架构…...
C++ unordered_map和unordered_set的使用,哈希表的实现
文章目录 unordered_map,unorder_set和map ,set的差异哈希表的实现概念直接定址法哈希冲突哈希冲突举个例子 负载因子将关键字转为整数哈希函数除法散列法/除留余数法 哈希冲突的解决方法开放定址法线性探测二次探测 开放定址法代码实现 哈希表的代码 un…...
星火大模型接入及文本生成HTTP流式、非流式接口(JAVA)
文章目录 一、接入星火大模型二、基于JAVA实现HTTP非流式接口1.配置2.接口实现(1)分析接口请求(2)代码实现 3.功能测试(1)测试对话功能(2)测试记住上下文功能 三、基于JAVA实现HTTP流…...
数据结构——二叉树——堆(1)
今天,我们来写一篇关于数据结构的二叉树的知识。 在学习真正的二叉树之前,我们必不可少的先了解一下二叉树的相关概念。 一:树的概念 树是一种非线性的数据结构,它是由n(n>0)个有限结点组成一个具有层…...
【HarmonyOS之旅】基于ArkTS开发(三) -> 兼容JS的类Web开发(二)
目录 1 -> HML语法 1.1 -> 页面结构 1.2 -> 数据绑定 1.3 -> 普通事件绑定 1.4 -> 冒泡事件绑定5 1.5 -> 捕获事件绑定5 1.6 -> 列表渲染 1.7 -> 条件渲染 1.8 -> 逻辑控制块 1.9 -> 模板引用 2 -> CSS语法 2.1 -> 尺寸单位 …...
SOME/IP--协议英文原文讲解4
前言 SOME/IP协议越来越多的用于汽车电子行业中,关于协议详细完全的中文资料却没有,所以我将结合工作经验并对照英文原版协议做一系列的文章。基本分三大块: 1. SOME/IP协议讲解 2. SOME/IP-SD协议讲解 3. python/C举例调试讲解 4.1.3 End…...
【AI】【本地部署】OpenWebUI的升级并移植旧有用户信息
【背景】 OpenWebUI的版本升级频率很高,并会修改旧版本的Bug,不过对于已经在使用的系统,升级后现有用户信息都会丢失,于是研究如何在升级后将现有的用户信息移植到升级后版本。 【准备工作】 OpenWebUI的升级步骤在Docker中有现…...
论文笔记(六十三)Understanding Diffusion Models: A Unified Perspective(五)
Understanding Diffusion Models: A Unified Perspective(五) 文章概括基于得分的生成模型(Score-based Generative Models) 文章概括 引用: article{luo2022understanding,title{Understanding diffusion models: A…...
Tableau:为数据科学专家量身定制
Tableau是一款功能强大且广泛应用的数据可视化和商业智能(BI)工具,由斯坦福大学的三位研究者于2003年创立。它旨在通过直观的界面和强大的功能,帮助用户轻松地探索、分析和呈现数据,从而做出更明智的决策。 核心功能与…...
CAG技术:提升LLM响应速度与质量
标题:CAG技术:提升LLM响应速度与质量 文章信息摘要: CAG(Cache-Augmented Generation)通过预加载相关知识到LLM的扩展上下文中,显著减少了检索延迟和错误,从而提升了响应速度和质量。与传统的R…...
飞桨PaddleNLP套件中使用DeepSeek r1大模型
安装飞桨PaddleNLP 首先安装最新的PaddleNLP3.0版本: pip install paddlenlp3.0.0b3 依赖库比较多,可能需要较长时间安装。 安装好后,看看版本: import paddlenlp paddlenlp.__version__ 输出: 3.0.0b3.post2025…...
单片机基础模块学习——NE555芯片
一、NE555电路图 NE555也称555定时器,本文主要利用NE555产生方波发生电路。整个电路相当于频率可调的方波发生器。 通过调整电位器的阻值,方波的频率也随之改变。 RB3在开发板的位置如下图 测量方波信号的引脚为SIGHAL,由上面的电路图可知,NE555已经构成完整的方波发生电…...