【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!");}}
}
这个例子的逻辑并不重要,看一下它的写法:
-
程序开头定义了
Messengertrait,里面有send方法的签名:接收不可变引用&self和一个字符串切片类型&str的形参msg作为参数。 -
下面定义了一个结构体叫
LimitTracker,它是一个泛型类型,生命周期为'a,泛型参数为T,要求T的生命周期为'a并实现Messenger这个在程序开头定义的trait。LimitTracker里面有三个字段:messenger:类型为&str字符串切片类型,生命周期为'avalue:类型为usizemax:类型为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的大小使用Messengertrait下的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结构体实现了整个代码一开头的Messengertrait。实现了这个trait之后MockMessenger就可以用来创建LimitTracker(因为LimitTracker要求泛型类型实现Messengertrait)。
使用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>,它实现了Dereftraitborrow_mut:返回智能指针RefMut<T>,实现了Dereftrait
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字,是截止到目前为止最长的文章,如果你能坚持读完并理解,那真的很强! 喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以…...
Luzmo 专为SaaS公司设计的嵌入式数据分析平台
Luzmo 是一款嵌入式数据分析平台,专为 SaaS 公司设计,旨在通过直观的可视化和快速开发流程简化数据驱动决策。以下是关于 Luzmo 的详细介绍: 1. 背景与定位 Luzmo 前身为 Cumul.io ,专注于为 SaaS 公司提供嵌入式分析解决方案。…...
数组at()方法:负索引的救赎与JavaScript标准化之路
数组at()方法:负索引的救赎与JavaScript标准化之路 从一次代码评审说起 在某次团队代码评审中,小白注意到有同事写下了这样的代码: const lastItem arr[arr.length - 1];这让我回想起自己早期开发时被负索引问题困扰的经历。今天…...
HTML<label>标签
例子 三个带标签的单选按钮: <form action"/action_page.php"> <input type"radio" id"html" name"fav_language" value"HTML"> <label for"html">HTML</label><br&…...
约瑟夫问题(信息学奥赛一本通-2037)
【题目描述】 N个人围成一圈,从第一个人开始报数,数到M的人出圈;再由下一个人开始报数,数到M 的人出圈;…输出依次出圈的人的编号。 【输入】 输入N和M。 【输出】 输出一行,依次出圈的人的编号。 【输入样…...
二分查找题目:寻找两个正序数组的中位数
文章目录 题目标题和出处难度题目描述要求示例数据范围 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 题目 标题和出处 标题:寻找两个正序数组的中位数 出处:4. 寻找两个正序数组的中位数 难度 8 级 题目描述 要求 给定两个大…...
【技术洞察】2024科技绘卷:浪潮、突破、未来
涌动与突破 2024年,科技的浪潮汹涌澎湃,人工智能、量子计算、脑机接口等前沿技术如同璀璨星辰,方便了大家的日常生活,也照亮了人类未来的道路。这一年,科技的突破与创新不断刷新着人们对未来的想象。那么回顾2024年的科…...
【Linux】gdb——Linux调试器
gdb使用背景 程序的发布方式有两种,debug模式和release模式 Linux gcc/g出来的二进制程序,默认是release模式 要使用gdb调试,必须在源代码生成二进制程序的时候, 加上 -g 选项 gdb使用方法 首先进入gdb gdb test_glist显示代码 断点 b 行…...
fpga系列 HDL:XILINX Vivado Vitis 高层次综合(HLS) 实现 EBAZ板LED控制(上)
目录 创建工程创建源文件并编写C代码C仿真综合仿真导出RTL CG导出RTL错误处理: 创建工程 创建源文件并编写C代码 创建源文件(Souces下的hlsv.h和hlsv.cpp,Test Bench下的test_hlsv1.cpp): hlsv1.h #ifndef HLSV1 #define HLSV1 #include &l…...
卡特兰数学习
1,概念 卡特兰数(英语:Catalan number),又称卡塔兰数,明安图数。是组合数学中一种常出现于各种计数问题中的数列。它在不同的计数问题中频繁出现。 2,公式 卡特兰数的递推公式为:f(…...
度小满Java开发面试题及参考答案 (上)
String 是基本类型吗?String、StringBuffer、StringBuilder 的区别是什么?拼接字符串有哪些做法? String 不是基本类型,它是 Java 中的一个类,属于引用类型。 下面来看看 String、StringBuffer、StringBuilder 的区别: 类型可变性线程安全性性能适用场景String不可变线程…...
Python-基于PyQt5,json和playsound的通用闹钟
前言:刚刚结束2024年秋季学期的学习,接下来我们继续来学习PyQt5。由于之前我们已经学习了PyQt5以及PyUIC,Pyrcc和QtDesigner的安装,配置。所以接下来我们一起深入PyQt5,学习如何利用PyQt5进行实际开发-基于PyQt5,json和…...
关于数字地DGND和模拟地AGND隔离
文章目录 前言一、1、为什么要进行数字地和模拟地隔离二、隔离元件1.①0Ω电阻:2.②磁珠:3.电容:4.④电感: 三、隔离方法①单点接地②数字地与模拟地分开布线,最后再PCB板上一点接到电源。③电源隔离④、其他隔离方法 …...
小识Java死锁是否会造成CPU100%?
死锁或者大量的死锁不一定会直接导致CPU占用率达到100%。以下是详细分析: 一、死锁对CPU的影响 资源占用:死锁是指两个或多个线程(或进程)在相互等待对方释放资源,导致所有涉及的线程都无法继续执行。在死锁状态下&a…...
DeepSeek R1学习
0.回顾: https://blog.csdn.net/Together_CZ/article/details/144431432?ops_request_misc%257B%2522request%255Fid%2522%253A%25226574a586f0850d0329fbb720e5b8d5a9%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id…...
激光线扫相机无2D图像的标定方案
方案一:基于运动控制平台的标定 适用场景:若激光线扫相机安装在可控运动平台(如机械臂、平移台、旋转台)上,且平台的运动精度已知(例如通过编码器或高精度步进电机控制)。 步骤: 标…...
12 款开源OCR发 PDF 识别框架
2024 年 12 款开源文档解析框架的选型对比评测:PDF解析、OCR识别功能解读、应用场景分析及优缺点比较 这是该系列的第二篇文章,聚焦于智能文档处理(特别是 PDF 解析)。无论是在模型预训练的数据收集阶段,还是基于 RAG…...
【反悔堆】【hard】力扣871. 最低加油次数
汽车从起点出发驶向目的地,该目的地位于出发位置东面 target 英里处。 沿途有加油站,用数组 stations 表示。其中 stations[i] [positioni, fueli] 表示第 i 个加油站位于出发位置东面 positioni 英里处,并且有 fueli 升汽油。 假设汽车油…...
为什么应用程序是特定于操作系统的?[计算机原理]
你把WINDOWS程序复制到MAC上使用,会发现无法运行。你可能会说,MAC是arm处理器,而WINDWOS是X86 处理器。但是在2019年,那时候MAC电脑还全是Intel处理器,在同样的X86芯片上,运行MAC和WINDOWS 程序还是无法互相…...
多项日常使用测试,带你了解如何选择AI工具 Deepseek VS ChatGpt VS Claude
多项日常使用测试,带你了解如何选择AI工具 Deepseek VS ChatGpt VS Claude 注:因为考虑到绝大部分人的使用,我这里所用的模型均为免费模型。官方可访问的。ChatGPT这里用的是4o Ai对话,编程一直以来都是人们所讨论的话题。Ai的出现…...
什么是循环神经网络?
一、概念 循环神经网络(Recurrent Neural Network, RNN)是一类用于处理序列数据的神经网络。与传统的前馈神经网络不同,RNN具有循环连接,可以利用序列数据的时间依赖性。正因如此,RNN在自然语言处理、时间序列预测、语…...
Flink运行时架构
一、系统架构 1)作业管理器(JobManager) JobManager是一个Flink集群中任务管理和调度的核心,是控制应用执行的主进程。也就是说,每个应用都应该被唯一的JobManager所控制执行。 JobManger又包含3个不同的组件。 &am…...
网络工程师 (6)操作系统概述
一、操作系统的定义 (一)基本定义 操作系统(Operating System,简称OS)是计算机系统中至关重要的基础性系统软件。它是计算机硬件与上层软件之间的桥梁,负责管理和控制整个计算机系统的硬件和软件资源&…...
【2025年数学建模美赛C题】第1-5问F奖解题思路+高级绘图+可运行代码
基于多模型分析的奥运会奖牌预测与影响因素研究 解题思路一、问题重述二、问题分析三、模型假设与符号说明四、数据预处理五、奖牌榜预测5.1 基于LSTM长短期记忆循环神经网络的预测模型的建立5.2 模型预测结果 六、首枚奖牌预测6.1 BP神经网络的建立6.2 模型预测结果 七、各国奖…...
StarRocks 安装部署
StarRocks 安装部署 StarRocks端口: 官方《配置检查》有服务端口详细描述: https://docs.starrocks.io/zh/docs/deployment/environment_configurations/ StarRocks架构:https://docs.starrocks.io/zh/docs/introduction/Architecture/ Sta…...
RoboMaster- RDK X5能量机关实现案例(一)识别
作者:SkyXZ CSDN:https://blog.csdn.net/xiongqi123123 博客园:https://www.cnblogs.com/SkyXZ 在RoboMaster的25赛季,我主要负责了能量机关的视觉方案开发,目前整体算法已经搭建完成,实际方案上我使用的上…...
llama.cpp LLM_ARCH_DEEPSEEK and LLM_ARCH_DEEPSEEK2
llama.cpp LLM_ARCH_DEEPSEEK and LLM_ARCH_DEEPSEEK2 1. LLM_ARCH_DEEPSEEK and LLM_ARCH_DEEPSEEK22. LLM_ARCH_DEEPSEEK and LLM_ARCH_DEEPSEEK23. struct ggml_cgraph * build_deepseek() and struct ggml_cgraph * build_deepseek2()References 不宜吹捧中国大语言模型的同…...
检测到联想鼠标自动调出运行窗口,鼠标自己作为键盘操作
联想鼠标会自动时不时的调用“运行”窗口 然后鼠标自己作为键盘输入 然后打开这个网页 (不是点击了什么鼠标外加按键,这个鼠标除了左右和中间滚轮,没有其他按键了)...
-bash: ./uninstall.command: /bin/sh^M: 坏的解释器: 没有那个文件或目录
终端报错: -bash: ./uninstall.command: /bin/sh^M: 坏的解释器: 没有那个文件或目录原因:由于文件行尾符不匹配导致的。当脚本文件在Windows环境中创建或编辑后,行尾符为CRLF(即回车和换行,\r\n)…...
15天基础内容总复习
总复习 一.day01内容 1.JVM,JRE,JDK的关系 JVM: java虚拟机,用来运行java程序的,JVM本身是不夸平台的,每个操作系统都需要安装针对本操作系统的JVM所以: java通过jvm的不夸平台实现了java的跨平台JRE:java运行环境,包含jvm和核心类库JDK:java开发工具包,包含开发工具和JRE三…...
