【社区投稿】深入再谈智能指针、AsRef引用与Borrow借用
深入再谈智能指针、AsRef
引用与Borrow
借用
这是一个具有深度的技术主题。每次重温其理论知识,都会有新的领悟。大约 2 年前,我曾就这一技术方向撰写过另一篇短文《从类型转换视角,浅谈Deref<Target = T>
, AsRef<T>
, Borrow<T>
和From<T> trait
差异》。在那篇文章中,我依据当时的经验知识,归纳了自定义智能指针、引用和借用的代码实现方式,并罗列了一些原则。但那种“快餐式”的知识分享存在不少遗憾。两年后的今天,我凭借更多的知识积累,利用春节长假的时间,重拾此话题,力求通过一篇长文将(广义的)Rust
“引用”阐述清楚。
名词解释
为了统一对非具名抽象概念的理解,我们先对几个Rust
常见项的中文表述进行约定。
@Rustacean
一般发音为/ˌrʌstəˈsiːən/
。它是由单词Rust
和词根-acean
(表示“……的人”的后缀)构成。它被用作Rust
技术栈程序员的自称。
自定义引用
代表std::convert::AsRef<F>
或std::convert::AsMut<F>
的特征实现类。例如,泛型类型参数<T: AsRef<F>>
表示类型T
是F
的自定义引用。普通引用、自定义引用与智能指针之间的对比关系可以被归纳为

自定义借用
代表std::borrow::Borrow<F>
或std::borrow::BorrowMut<F>
特征的实现类。比如,泛型类型参数<T: Borrow<F>>
表示类型T
是F
的自定义借用,也可读作“T
被借入作为F
”。
胖引用
代表动态分派特征对象trait Object
的普通引用。例如,&dyn AsRef<std::path::Path>
。
胖智能指针
代表动态分派特征对象trait Object
的智能指针。例如,Box<dyn AsRef<std::path::Path>>
。
胖指针
是对胖引用与胖智能指针的统称。
泛型覆盖实现
英文全称是Blanket Implementation
,它是作用于泛型类型参数的特征实现块。简单来说,泛型覆盖实现允许 @Rustacean 为一批满足特定泛型条件的类型统一实现某个特征,而无需为每个具体类型单独实现,这样可以减少代码重复。例如,impl<T: ?Sized> Borrow<T> for T { /* 特征成员方法的实现 */ }
。该定义的关键在于特征实现块的“受体”不是一个具体类型,而是满足泛型参数限定条件的一批具体类型。
特征成员方法
对应英文词条是trait method
,指在特征定义内声明的成员方法以及在特征实现块内实现的成员方法。
智能指针内部值的数据类型
以<T: Deref<Target = F>>
为例,在文章中,
要么,将内部值数据类型记作
F
,出于文字简洁的目的。要么,将其记作
<T as Deref>::Target
关联类型,以强调当前是在智能指针讨论上下文内。
<T: ?Copy>
特征限定条件
首先,这个“泛型(类型)参数限定条件”记法从语法规则上肯定是错的,因为即便最新版rustc
也仅只认识?Sized
一个“不确定”限定条件。
其次,<T: ?Sized>
代表“直至编译时,还不能确定被限定的泛型(类型)参数T
是否有可度量的字节大小”。即,类型T
是DST
的。
模仿?Sized
,我想用<T: ?Copy>
表示“直至编译时,还不能确定被限定的泛型(类型)参数T
是否可复制”。注:【可克隆】不等同于【可复制】 — 这是两码事。
概述
接下来,文章正文将从下图《自定义引用、自定义借用与智能指针的解引用方式分类》开始逐层深入展开论述。

解引用的触发方式不同
首先,对“自定义引用”与“自定义借用”的解引用操作都要求 @Rustacean 必须手动调用对应的特征成员方法(
解引用自定义引用
<T as AsMut>::as_mut(&mut T) ➜ &mut F
— 来自【标准库】对trait AsMut
的直接实现<&mut T as AsMut>::as_mut(&mut &mut T) ➜ &mut F
— 来自【标准库】对trait AsMut
的泛型覆盖实现
<T as AsRef>::as_ref(&T) ➜ &F
— 来自【标准库】对trait AsRef
的直接实现<&T as AsRef>::as_ref(&&T) ➜ &F
— 来自【标准库】对trait AsRef
的泛型覆盖实现<&mut T as AsRef>::as_ref(&&mut T) ➜ &F
— 来自【标准库】对trait AsRef
的泛型覆盖实现
只读
可变
解引用自定义借用
[只读]
<T as Borrow>::borrow(&T) ➜ &F
[可变]
<T as BorrowMut>::borrow_mut(&mut T) ➜ &mut F
)才能完成从T
/&T
/&mut T
至&F
/&mut F
的(纯手动)解引用类型转换。举个[例程 1]
use ::std::path::{ Path, PathBuf };
fn print<K: AsRef<Path>>(file_path: K) {// 注意:由于编译器不会自动生成对 file_path.as_ref() 成员方法的调用语句,// 因此开发者必须显式地手动调用 <K as AsRef>::as_ref(&K)。let file_path: &Path = file_path.as_ref();println!("文件路径= {}", file_path.display());
}
fn main() {let string_2_path = String::from("/etc/<String>");let path_buf_2_path = PathBuf::from("/etc/<PathBuf>");// &T ➜ &F 在 print() 函数调用之后,保留变量所有权不被消费掉。print(&string_2_path); // &String ➜ &Pathprint(&path_buf_2_path); // &PathBuf ➜ &Path// &&T ➜ &Fprint(&&string_2_path); // &&String ➜ &Pathprint(&&path_buf_2_path); // &&PathBuf ➜ &Path// T ➜ &F 在 print() 函数调用之后,消耗掉了变量所有权。print(string_2_path); // String ➜ &Pathprint(path_buf_2_path); // PathBuf ➜ &Path
}
前面我们介绍了解引用自定义引用和自定义借用的方式,接下来看看对“智能指针”引用的解引用处理有什么不同。
对“智能指针”引用的解引用处理(&T ➜ &F
或&mut T ➜ &mut F
)则是由(前端)编译器rustc
自动完成的。具体地讲,编译器会在语义分析过程中
先识别出
在成员方法调用语句中,对应
&self
或&mut self
形参的智能指针引用实参&T
或&mut T
,以及在函数、闭包、成员方法调用语句中,对应其它普通引用形参的智能指针引用实参
&T
或&mut T
再将&T
或&mut T
原地替换为对它们的解引用特征成员方法调用表达式
<T as Deref>::deref(&T) ➜ &F
或<T as DerefMut>::deref_mut(&mut T) ➜ &mut F
接着,判断上一步返回值中的F
是否又是智能指针?
要么,解引用后的
&F
已匹配于函数形参的数据类型要么,
F
已不再是智能指针,且不能进一步解引用了
若是,则绕回第二步将
F
当作T
接着递归解引用F
。递归处理会持续进行,直至若递归解引用被持续执行多次,那么在原来
&T
实参位置上会出现一条对T
的.deref()
成员方法调用链条,或在原来&mut T
实参位置上会出现一条对T
的.deref_mut()
成员方法调用链条。否则,则直接进行下一步。
最后,判断&F
是否匹配于函数形参的数据类型
若匹配,则此段编译成功,并执行后续编译处理。
否则,整体编译失败。
上述文字描述较为抽象,一图胜千言,请参考下图。注意:执行图中操作的行为主体是rustc
,而不是 @Rustacean。

再举个[例程2],佐证上述理论总结。
use ::std::path::{ Path, PathBuf };
fn print(file_path: &Path) {println!("文件路径= {}", file_path.display());
}
fn main() {let mut path_buf = PathBuf::from("/etc/<&PathBuf>");// 场景一:在成员方法调用中,对 &self 与 &mut self 的自动解引用// (1) &mut T ➜ &mut F 修改智能指针内部值的内部状态信息path_buf.push("usr");// (2) &T ➜ &F 读取智能指针内部值的内部状态信息println!("文件路径= {}", path_buf.display());// 场景二:在普通函数调用中,对智能指针引用的自动解引用// 模拟了 OOP 编程中的函数重载。print(&path_buf); // &T ➜ &Flet path: &Path = path_buf.as_path();print(path);// 不存在 T ➜ &F,所以下面会编译失败// print(PathBuf::from("/etc/<PathBuf>"));
}
最后,对“智能指针”所有权变量的解引用(T ➜ F
)就得具体问题具体分析了。@Rustacean 需分三步渐进推导:
第一步:确认如何处理智能指针的解引用结果F
?
是要【替换】智能指针的内部值。例如,赋值语句
*t = new_value
— 千万别忘了以let mut
声明智能指针变量t
。还是【拾出】内部数据的所有权值。例如,
let value = *t
。
第二步:若是后者(拾出所有权值),再接着判断智能指针内部值的类型F
是否满足trait Copy
限定条件?
若
where F: Copy
成立,那么被拾取出的所有权值其实是F
值的复本。若
F
是?Copy
,那么rustc
就会判定本次编译操作整体失败。知识点回顾,Rust
变量的所有权规则禁止从外层数据结构搬移出它内部字段的所有权值,因为禁止“掏空”数据结构留空坑位null
。
第三步,确认拾出智能指针内部值F
的手段方式
【手动解引用】使用一元解引用操作符
*
拾出所有权值。例如,let f_copy = *t;
。【自动解引用】调用
F
数据结构上“消费”self
所有权的成员方法。例如,PathBuf::into_boxed_path(self) ➜ Box<Path>
。“
此处"自动解引用"的本质就是
rustc
在MIR
中间代码生成前,将智能指针成员方法调用语句中对应self
形参的智能指针实参T
替换为对T
的解引用表达式*<T as Deref>::deref(&T)
。
下面是对【手动解引用】过程的详细图解

此外,使用一元解引用操作符*
替换(可变)智能指针内部值的代码套路可概括为:
声明和初始化一个可变智能指针变量。
在解引用该智能指针变量的同时,对其做赋值处理。这对
Cpp
语法的一比一复刻真让人看着就亲切!
用伪码表示大约是如下的样子:
// 1. 假设结构体 SmartPointer实现了 Deref<Target = PathBuf> 特征。
let mut path_buf_sp = SmartPointer::new(PathBuf::from("/etc/abc"));
// 2. 该[解引用+赋值]语句总是能编译成功,无论 <SmartPointer as Deref>::Target 是否满足 trait Copy 限定条件
*path_buf_sp = PathBuf::from("/etc/abc1");
将上述“解引用智能指针所有权变量(T ➜ F
)”的知识点都捏合至[例程3],可向读者呈现:
// 注释内容同样很精彩和更重要。
mod generic_deref {use ::std::ops::{ Deref, DerefMut };pub struct GenericDeref<T> {value: T}impl<T> GenericDeref<T> {pub fn new(value: T) -> Self {GenericDeref { value }}}impl<T> Deref for GenericDeref<T> {type Target = T;fn deref(&self) -> &Self::Target {&self.value}}impl<T> DerefMut for GenericDeref<T> {fn deref_mut(&mut self) -> &mut Self::Target {&mut self.value}}
}
use ::std::path::PathBuf;
use generic_deref::GenericDeref;type Deref1 = GenericDeref<PathBuf>;
type Deref2 = GenericDeref<i32>;fn main() {let mut i32_wrapper = Deref2::new(1);// 用法1:替换智能指针的内部字段值。这总是能够被成功地完成*i32_wrapper = -1;// 用法2:拾取出智能指针内部字段的所有权值。// 因为 <T as Deref2>::Target 是满足 trait Copy 限定条件的 i32 类型,// 所以如下解引用操作才能成功通过编译:// (1) 调用"消耗所有权的"成员方法,和自动解引用。assert_eq!(0, i32_wrapper.leading_zeros());// (2) 使用解引用操作符 * 手动解引用。解引用结果是智能指针内部值的复本。assert_eq!(-1, *i32_wrapper);let mut path_buf_wrapper = Deref1::new(PathBuf::from("/etc/abc"));// 用法1:替换智能指针内部值。这总是能够被成功地完成*path_buf_wrapper = PathBuf::from("/etc/abc1");// 用法2:拾取出智能指针内部字段的所有权值。// 因为 <T as Deref2>::Target 未满足 trait Copy 限定条件,// 所以如下解引用操作未能通过编译:// (1) 调用消耗所有权的成员方法,自动解引用。// let path_buf = path_buf_wrapper.into_boxed_path();// (2) 使用解引用操作符,手动解引用。// let path_buf = *path_buf_wrapper;
}
编译时,触发解引用的时间窗口不同

由上图可总结出
解引用类型转换“自定义引用”和“自定义借用”的代码是由人工手动添加于程序文件编写阶段;而
解引用“智能指针”的处理逻辑则是由(前端)编译器
rustc
有条件地注入于编译过程中的“语义分析”之后和“MIR
中间代码生成”之前。
我甚至猜测:“rustc
对智能指针追加的解引用表达式不是人类可读的Rust
代码,而是面向语义分析器的AST
子节点树”。
解引用的技术原理不同
自定义引用
对“自定义引用”的解引用处理是建立在Rust
泛型类型系统的“通用底盘”基础之上。凭借FST
的静态分派机制,Rust
能模拟出OOP
的同一函数形参兼容于不同类型实参的“重载”效果。举个例子,正是因为【标准库】预置了类型&str
、String
与std::path::PathBuf
对trait AsRef<std::path::Path>
的特征实现和定义这些类型可作为std::path::Path
的自定义引用,所以[例程4]中模仿多态的函数调用才有能通过编译检查
use ::std::{ convert::AsRef, path::{ Path, PathBuf } };
//
// 因为该函数的唯一【形参】兼容于任何“可解引用为 &Path 的”自定义引用【实参】。
//
fn print_fst<T: AsRef<Path>>(file_path: T) {let file_path: &Path = file_path.as_ref(); // 手动解引用,而不是自动解引用println!("文件路径fst= {}", file_path.display());
}
fn main() {let str_file_path = "/etc/<str>";let string_file_path = "/etc/<string>".to_string();let path_buf = PathBuf::from("/etc/<PathBuf>");//// 所以,形似 OOP 函数重载的多态调用语句才有机会出现在 Rust 程序内。//print_fst(str_file_path); // &str ➜ &Path// 美中不足,这都是按所有仅传值。print_fst(string_file_path); // String ➜ &Pathprint_fst(path_buf); // PathBuf ➜ &Path// 因消费掉了变量所有权,所以 string_file_path 与 path_buf 都将不可再被访问
}
又因为“一味地按所有权传值”是件非常“内耗的”程序设计选择,所以故事并没有止步于此。【标准库】还为“自定义引用”的引用(甚至,引用的引用递归)提供了泛型覆盖实现,以使自定义引用的引用们(比如,&T
、&&T
、&&mut T
、&mut T
、&mut &mut T
)继续是初始被引用值F
的“自定义引用”。以下是摘录于【标准库】源码的泛型覆盖实现块签名:
impl<T: ?Sized, F: ?Sized> AsRef<F> for &T where T: AsRef<F> { .. }
读作:若类型
T
是类型F
的只读自定义引用,那么T
的只读引用&T
也同样是类型F
的只读自定义引用。impl<T: ?Sized, F: ?Sized> AsMut<F> for &mut T where T: AsMut<F> { .. }
读作:若类型
T
是类型F
的可变自定义引用,那么T
的可变引用&mut T
也同样是类型F
的可变自定义引用。
依旧感觉文字描述苍白无力,我还是接着画张图吧!

由此,我们就能将按所有权传值的[例程4]升级改造为仅按引用传值的[例程5]。
use ::std::{ convert::AsRef, path::{ Path, PathBuf } };
//
// 因为该函数的唯一【形参】兼容于任何“可解引用为 &Path 的”自定义引用【实参】。
//
fn print_fst<T: AsRef<Path>>(file_path: T) {let file_path: &Path = file_path.as_ref(); // 手动解引用,而不是自动解引用println!("文件路径fst= {}", file_path.display());
}
fn main() {let string_file_path = "/etc/<string>".to_string();let path_buf = PathBuf::from("/etc/<PathBuf>");//// 因为【标准库】预置了对"自定义引用"的引用的泛型覆盖实现,所以//// 1. AsRef<F> 实现类也就具备了部分“自动解引用”能力,和能够按引用传值。print_fst(&string_file_path); // &String ➜ &Pathprint_fst(&path_buf); // &PathBuf ➜ &Path// 2. 甚至,引用的引用也能传值。print_fst(&&string_file_path); // &&String ➜ &Pathprint_fst(&&path_buf); // &&PathBuf ➜ &Path// 最后,再消费掉变量的所有权print_fst(string_file_path); // String ➜ &Pathprint_fst(path_buf); // PathBuf ➜ &Path
}
阅读至此,擅长发散思维的读者一定已经开始掂量如何将泛型覆盖实现的效用推广至自定义引用的
DST
的动态分派胖指针,以及智能指针
,而不仅限于普通引用。棒!这个思路是十分正确的,而且它对胖引用(比如,&dyn AsRef<F>
)也确实有效。举个[例程6]
use ::std::{ convert::AsRef, ops::Deref, path::{ Path, PathBuf } };
//
// 该函数的唯一【形参】兼容于任何“可解引用为 &Path 的”自定义引用【实参】。
//
// 静态分派形参
fn print_fst<T: AsRef<Path>>(file_path: T) {let file_path: &Path = file_path.as_ref(); // 手动解引用,而不是自动解引用println!("[静态分派]文件路径fst= {}", file_path.display());
}
// 动态分派形参
fn print_dst(file_path: &dyn AsRef<Path>) {let file_path: &Path = file_path.as_ref(); // 手动解引用,而不是自动解引用println!("[动态分派][普通引用]文件路径fst= {}", file_path.display());
}
fn main() {let string_file_path = "/etc/<string>".to_string();let path_buf = PathBuf::from("/etc/<PathBuf>");//// 因为【标准库】预置了对"自定义引用"的引用的泛型覆盖实现,所以//// 1. 对动态分派的函数形参,其实参也能兼容。print_dst(&string_file_path);print_dst(&path_buf);// 2. 对静态分派的函数形参,其实参依旧能“自动解引用”。print_fst(&string_file_path);print_fst(&path_buf);
}
但这对智能指针就不一定成立了,无论它是动态分派的特征对象(例如,Box<dyn AsRef<F>>
),还是静态分派的泛型(例如,Box<T> where T: AsRef<F>
)。至少由【标准库】直供的智能指针数据结构都定义了自己专属的trait AsRef<F>
,trait AsMut<F>
,trait Borrow<F>
与trait BorrowMut<F>
实现块和为它们定义了独立解释的语义功能。于是,智能指针实例自身的特征成员方法(比如,<Box<T> as AsRef>::as_ref(&Box<T>)
)就会遮蔽掉其内部值上的同名成员方法<Deref::Target as AsRef>::as_ref(&Deref::Target)
,进而阻断模拟“自动解引用”的泛型匹配。再举个[例程7]
use ::std::{ convert::AsRef, ops::Deref, path::{ Path, PathBuf } };
//
// 该函数的唯一【形参】兼容于任何“装箱于Box<T>智能指针且可解引用为 &Path 自定义引用的”实参。
//
// 静态分派形参
#[allow(dead_code)]
fn print_fst<T: AsRef<Path>>(file_path: T) {let file_path: &Path = file_path.as_ref(); // 手动解引用,而不是自动解引用println!("[静态分派]文件路径fst= {}", file_path.display());
}
// 动态分派形参
fn print_dst(file_path: Box<dyn AsRef<Path>>) {let file_path: &Path = file_path.deref().as_ref(); // 手动解引用,而不是自动解引用println!("[动态分派][智能指针]文件路径fst= {}", file_path.display());
}
fn main() {//// 定义“装箱于Box<T>”智能指针的 &Path 自定义引用。//let string_file_path = Box::new("/etc/<string>".to_string());let path_buf = Box::new(PathBuf::from("/etc/<PathBuf>"));// 1. 没有自动解引用,因为`<Box as AsRef>::as_ref(&Box)`的返回值是// &String 与 &PathBuf,而不是 &Path。所以,下面六条语句都会编译失败// print_fst(&string_file_path);// print_fst(&path_buf);// print_dst(&string_file_path);// print_dst(&path_buf);// print_fst(string_file_path);// print_fst(path_buf);// 2. 即便是直接传智能指针的所有权值,对胖智能指针的拆箱也得一步变两步完成// (1) 从 Box<T> 中拆箱出 自定义引用// (1) 从 自定义引用 拆箱出 &Path// 最后,才能调用 Path 类型上的成员方法print_dst(string_file_path);print_dst(path_buf);
}
别慌张,办法总比问题多!就 @Rustacean 本地定义的智能指针而言,我在文章正文最后一节分享了一段解决此痛点的《智能指针【条件化特征实现块】补丁》,并对其从工作原理至可复用宏定义都做了讲解。
智能指针
在*.rs
程序文件编译过程中,(前端)编译器rustc
会在AST
语义分析后、MIR
生成前,对满足特定条件的智能指针实例“定点”注入解引用特征成员方法的调用表达式。
判断某个实例是否是智能指针?
根据数据结构是否实现过trait Deref
特征,辨认智能指针实例。因为trait DerefMut
是trait Deref
的子特征,所以“粗线条地”识别智能指针,就不用专门对trait DerefMut
做限定条件检查。
“定点”注入解引用表达式的代码位置筛选条件
要么,智能指针实例正作为一元解引用操作符
*
的操作数,且该指针关联类型Deref::Target
满足trait Copy
限定条件。即,智能指针内部值是可复制的。场景复现,请参考[例程3]的第31与42行。
若
<Deref::Target: ?Copy>
,则编译失败,因为取不出智能指针内部值的复本来。
要么,智能指针实例正作为函数、成员方法、甚至闭包调用语句中非对应
self
/&self
/&mut self
形参的引用类型实参。场景复现,请参考[例程2]的第15行。
要么,智能指针实例正作为该指针关联类型
Deref::Target
成员方法调用语句中对应&self
/&mut self
形参的引用类型实参。场景复现,请参考[例程2]的第10与12行。
要么,智能指针实例正作为该指针关联类型
Deref::Target
成员方法调用语句中对应self
形参的所有权实参,且Deref::Target
还得满足trait Copy
限定条件。场景复现,请参考[例程3]的第36行。
若
<Deref::Target: ?Copy>
,则编译失败。
智能指针的语义功能
虽然智能指针可作为模仿OOP
编程风格(比如,继承)的反模式语法糖,但它的首要任务却是从如下两个维度(同时或之一地)增强其Deref::Target
类型内部值的语义功能:
所有权关系 ownership。例如,
Rc<T>
被当作其内部值T
的“引用”,和按所有权传值,并摆脱普通引用规则的诸多限制。内存存储位置 storage。还是以
Rc<T>
为例,它腾挪内部值T
从【栈】内存至【堆】内存。然后,构造多个指向相同【堆】数据的【栈】(所有权)“引用”变量。
自定义借用
对“自定义借用”的解引用处理也是建立在Rust
泛型类型系统的“通用底盘”基础之上的。但它的首要用途已不再是模仿函数重载多态性的语法糖,而是(以<T: Borrow<F>>
为例)
强制【借用
T
】与【被借用的值F
】都对外呈现相同的:
哈希值 --- 意味着处理逻辑一致的
trait std::hash::Hash
实现等价关系 --- 意味着处理逻辑一致的
trait std::cmp::Eq
实现排序关系 --- 意味着处理逻辑一致的
trait std::cmp::Ord
实现
督促 @Rustacean 对【借用T
】与【被借用的值F
】编写处理逻辑一致的特征实现块,当需要对它们实现除std::borrow::Borrow
与std::borrow::BorrowMut
之外的特征时。比如,我们一般预期【借用T
】与【被借用的值F
】都能被print!
宏打印输出相同的内容,通过给它们编写处理逻辑一致的trait std::fmt::Display
特征实现块。
换句话说,只要某个类型T
实现了trait Borrow<F>
或trait BorrowMut<F>
,那么类型T
与F
【必有】相同的“哈希值”和“判等+排序”偏好。
【可选但有理由期望】对其它
trait
的实现,也有处理逻辑一致的特征实现块。
这是一套非常重要的约束规则。
同时从概念冠名上,
T
是F
的自定义借用,和T
被借用作为F
现实意义
令我恍然大悟的是,普通引用&T
/&mut T
与被引用值T
之间处理行为的高度一致性也是源于这套【自定义借用】约束规则,因为【标准库】为任何普通引用都预置了如下对trait Borrow<F>
与trait BorrowMut<F>
的泛型覆盖实现:
impl<T: ?Sized> Borrow<T> for &T { .. }
读作:任何类型
T
的只读引用&T
同时也T
自身的只读自定义借用。impl<T: ?Sized> BorrowMut<T> for &mut T { .. }
读作:任何类型
T
的可变引用&mut T
同时也T
自身的可变自定义借用。
于是才有我凭经验知识与死记硬背才掌握的经验法则:
“比较两个值的引用是否相等”就等效于“比较该引用背后的所有权值是否相等”,而不是匹配这两个引用是否指向同一处内存地址。即,assert!(&1 == &1)
等效于assert!(1 == 1)
,而不是assert!(std::ptr::eq(&1, &1))
。
举个[例程8]更形象。
use ::std::{ path::PathBuf, ptr };
fn main() {let path1 = PathBuf::from("/a/b/c");let path2 = PathBuf::from("/a/b/c");// 根据自定义借用的限定规则,比较引用就相当于比较被引用的值,assert!(&path1 == &path2); // 断言成功assert!(path1 == path2); // 断言成功// 而不是匹配引用的内存地址是否是指向的同一处。assert!(ptr::eq(&path1, &path2)); // 断言失败
}
到这,发散思维的读者必定又要发问:“这套约束规则对【智能指针】有啥影响呀?”。我快速回答:“没影响,因为【标准库】未提供面向Deref(Mut)
限定条件的Borrow(Mut)
泛型覆盖实现”。另外,只要Deref(Mut)
实现类不定义与其关联类型Deref::Target
重名的成员方法,那么rustc
自动解引用机制就能保证:
智能指针不仅能点出其内部值的全部
pub
成员方法,更会保持与内部值的trait method
完全一致的处理行为。但,很可惜,当面对泛型类型匹配时,智能指针却不能“透明”呈现出其内部值的
trait
“形状”。还是讲,办法总比问题多。采用ambassador crate,借助 crate 作者预定义的过程宏,便可:给智能指针数据结构增补实现其内部值才实现的
trait
。将智能指针的
trait method
实现委托给内部值实现的trait method
。于是,从宏观效果来看,智能指针与它的内部值既对外呈现相同的泛型
trait
“形状”,还保持了一致的trait method
处理逻辑,这简直是完美致极!可是,这一切就已经与rustc
自动解引用没关系了。
故事依旧未结束。甚至,一个惊艳接着另一个惊艳。【自定义借用】的约束规则还大幅提升了Map
和Set
类“可检索”数据结构的查询效率。简单地讲,【自定义借用】允许 @Rustacean
既能,将所有权值作为键数据保存于
Map
和Set
数据结构中,以满足容器占有子元素的要求。又可,使用更轻量级的自定义借用(算是广义引用的一种)作为对键数据匹配查询的搜索条件。
进而避免,为每次检索操作,都重新构造一个所有权值作为【键】的查询条件 — 内存效率极低。举个[例程10],让读者更形象地体会一下
use ::std::collections::HashMap;
fn main() {let mut map: HashMap<String, i32> = HashMap::new();// 向 Map 内保存字符串的所有权作为【键】let key123 = String::from("123");let key124 = String::from("124");map.insert(key123, 123);map.insert(key124, 124);// 在这一步涉及了 trait Borrow<F> 的两个知识点:// 1. 因为 String 是 &str 的自定义借用,所以 String 与 &str// 有相同的等价偏好与 hash 值。于是,由 String 为内容保存// 的键,就能由 &str 为检索条件给匹配出来。// 2. 因为 &i32 就是 i32 的自定义借用,&i32 与 i32 就具备相// 同的等价偏好,所以就允许由 &i32 引用之间的判等来断言其// 背后 i32 值是否相等。assert_eq!(map.get("123"), Some(&123));
}
写到这里,我有感而发:“哪有什么天生的易用体质,只是有【标准库】替我们负重前行”。
反身性Reflexivity
相比于自定义引用,自定义借用还具备“反身性Reflexivity”,因为【标准库】为任何类型都预置了如下对trait Borrow<F>
与trait BorrowMut<F>
的泛型覆盖实现:
impl<T: ?Sized> Borrow<T> for T { .. }
读作:任何类型
T
就是它自身的只读自定义借用。impl<T: ?Sized> BorrowMut<T> for T { .. }
读作:任何类型
T
就是它自身的可变自定义借用。
为了证明反身性的存在,我再举个[例程9]佐证一下
use ::std::{ borrow::Borrow, path::PathBuf };
// 注意下面函数形参的类型不是引用 &PathBuf 哟,而是像所有权值的类型!
fn print_fst<T: Borrow<PathBuf>>(file_path: T) {let file_path: &PathBuf = file_path.borrow(); // 手动解引用,而不是自动解引用println!("文件路径fst= {}", file_path.display());
}
fn main() {let path_buf = PathBuf::from("/etc/<PathBuf>");// 1. 任何类型 T 的普通引用 &T 同时也是该类型 T 自身的自定义借用。// 所以,即便函数的形参不是引用,我们也能将引用作为它的实参。这是兼容的不违和!print_fst(&path_buf);// 2. trait Borrow<F> 支持【反身性】。即,// 任何类型 T 就是它自身的"自定义借用"print_fst(path_buf);
}
在上段代码中,第3行的函数签名未以引用&PathBuf
为形参类型,而是将所有权的泛型类型T
作为形参类型。但
第11行既能传递引用
&path_buf
作为实参 — 自定义借用的泛型覆盖实现。同时,第14行又能传所有权变量
path_buf
作为实参 — 自定义借用的反身性
因为它们都是原始变量path_buf
的【自定义借用】。
智能指针的条件化AsRef
特征实现块
不同于普通引用,智能指针被允许定义它自己的(特征)实现块和实现它自己的(特征)成员方法。于是,智能指针内部值(Deref::Target
)的同名成员方法就会被智能指针自身的成员方法给遮蔽掉和失效,因为rustc
在对self
/&self
/&mut self
的实参执行解引用处理前,就检索到了“目标”成员方法和提前进入函数调用处理流程 — 只匹配名,而不匹配参数列表。这不仅造成程序设计上的难点,更导致【普通引用】与【智能指针】对被引用的【自定义引用】处理逻辑的不一致。以自定义引用<T: AsRef<F>>
例,
&T
的as_ref()
特征成员方法返回&F
,而Box<T>
的as_ref()
特征成员方法却返回&T
它们虽同为T
的“引用”但同名成员方法却返回不同类型的解引用值。对此,标准库的技术选择是放任此“不和谐的”存在。但,我忍不了。我要把【智能指针】对【自定义引用】内部值的处理逻辑“掰直”对齐于【普通引用】。具体做法也不难,
第一步,给每个自定义本地智能指针(数据结构),都增补如下一段对trait AsRef<F>
与trait AsMut<F>
的【条件化特征实现块】
欲了解更多“条件化实现块”的精彩内容,请请移步至我的另一篇主题文章《在 Rust 中令人印象深刻的三大【编译时】条件处理》
这里突出强调“本地智能指针”是因为
Rust
编译沙箱的孤儿原则导致“给任何非当前crate
的数据结构实现标准库的AsRef<F>
或AsMut<F>
特征都会被编译器拒绝”。
type SmartPointer = /* 前文代码定义的"智能指针"结构体类名 */;
impl<F> AsRef<F> for SmartPointer
where <SmartPointer as Deref>::Target: AsRef<F> {fn as_ref(&self) -> &F {self.deref().as_ref()}
}
impl<F> AsMut<F> for SmartPointer
where <SmartPointer as Deref>::Target: AsMut<F> {fn as_mut(&mut self) -> &mut F {self.deref_mut().as_mut()}
}
这段增补程序块所完成的任务可概括为:
若智能指针关联类型
Deref::Target
同时不满足AsRef<F>
和AsMut<F>
特征限定条件,那就什么也不做,也不添加新特征实现块。否则,继续。若智能指针关联类型
Deref::Target
满足AsRef<F>
特征限定条件,那就给智能指针数据结构增补trait AsRef<F>
特征实现块,和实现特征成员方法<SmartPointer as AsRef<F>>::as_ref(&SmartPointer)
返回&F
。若智能指针关联类型
Deref::Target
满足AsMut<F>
特征限定条件,那就给智能指针数据结构增补trait AsMut<F>
特征实现块,和实现特征成员方法<SmartPointer as AsMut<F>>::as_mut(&mut SmartPointer)
返回&mut F
。
此外,即便上例中的SmartPointer
智能指针已实现过面向关联类型<SmartPointer as Deref>::Target
的自定义引用特征
trait AsRef<<SmartPointer as Deref>::Target>
trait AsMut<<SmartPointer as Deref>::Target>
之一(或全部)也不会与此处新增补的trait AsRef<F>
与trait AsMut<F>
特征实现块冲突,因为它们的泛型类型参数不一样。
第二步,在调用智能指针实例上的as_ref()
与as_mut()
特征成员方法时,有一点儿麻烦,因为需要分辨两种情况:
智能指针数据结构上未定义过面向其它类型的
trait AsRef<_>
和trait AsMut<_>
特征实现块,那么从智能指针实例直接点出新增补的as_ref()
或as_mut()
特征成员方法即可,不会有任何的歧义。否则,
要么,采用
TurboFish
语法,从成员方法调用表达式,标注泛型参数值和关联目标特征实现块。例如,println!("{}", <SmartPointer as AsMut<i32>>::as_mut(&mut smartPointer));
。要么,定义独立的赋值语句,从赋值变量的类型声明,标注泛型参数值和关联目标特征实现块。例如,
let mut value: i32 = smartPointer.as_mut();
。
可复用的宏定义
因为上面那段增补代码几乎伴随着我本地定义的每个智能指针类,所以它还被特意零成本抽象为如下一段宏定义,以方便复用。因为这个功能太小,所以我还未将其发包于crates.io
仓库。
// 宏定义
macro_rules! smart_pointer_patch_builder {[@ref ($struct: ty)] => {impl<F> std::convert::AsRef<F> for $structwhere <$struct as std::ops::Deref>::Target: AsRef<F> {fn as_ref(&self) -> &F {use ::std::ops::Deref;self.deref().as_ref()}}impl<F> std::convert::AsMut<F> for $structwhere <$struct as std::ops::Deref>::Target: AsMut<F> {fn as_mut(&mut self) -> &mut F {use ::std::ops::DerefMut;self.deref_mut().as_mut()}}};
}
// 宏调用样例
type SmartPointer = /* 前文代码定义的"智能指针"结构体类名 */;
// 装配条件化的 AsRef 与 AsMut 特征实现块
smart_pointer_patch_builder!{ @ref (SmartPointer) }
为了向 @Rustacean 推销我做的这个宏,我还特地做了一套用法展示[例程11]
macro_rules! smart_pointer_patch_builder {[@ref ($struct: ty)] => {impl<F> std::convert::AsRef<F> for $structwhere <$struct as std::ops::Deref>::Target: AsRef<F>,{fn as_ref(&self) -> &F {use ::std::ops::Deref;self.deref().as_ref()}}impl<F> std::convert::AsMut<F> for $structwhere <$struct as std::ops::Deref>::Target: AsMut<F>,{fn as_mut(&mut self) -> &mut F {use ::std::ops::DerefMut;self.deref_mut().as_mut()}}};
}
// 定义实现了`trait AsRef<F>`与`trait AsMut<F>`特征的智能指针内部值
mod wrapping {use ::std::convert::{ AsRef, AsMut };#[derive(Debug)]pub struct Wrapping(i32);impl AsRef<i32> for Wrapping {fn as_ref(&self) -> &i32 {&self.0}}impl AsMut<i32> for Wrapping {fn as_mut(&mut self) -> &mut i32 {&mut self.0}}impl Wrapping {pub fn new(value: i32) -> Self {Wrapping(value)}}
}
// 定义本地智能指针类型
mod smart_pointer {use ::std::{ convert::{ AsRef, AsMut }, ops::{ Deref, DerefMut } };use super::wrapping::Wrapping;#[derive(Debug)]pub struct SmartPointer {value: Wrapping}impl Deref for SmartPointer {type Target = Wrapping;fn deref(&self) -> &Self::Target {&self.value}}impl DerefMut for SmartPointer {fn deref_mut(&mut self) -> &mut Self::Target {&mut self.value}}impl SmartPointer {pub fn new(value: i32) -> Self {SmartPointer { value: Wrapping::new(value) }}}// 下面是【标准库】对【智能指针】与`AsRef<F>`/`AsMut<F>`特征的惯例处理impl AsRef<<SmartPointer as Deref>::Target> for SmartPointer {fn as_ref(&self) -> &<SmartPointer as Deref>::Target {&self.value}}impl AsMut<<SmartPointer as Deref>::Target> for SmartPointer {fn as_mut(&mut self) -> &mut <SmartPointer as Deref>::Target {&mut self.value}}
}
use ::std::{ convert::AsRef, ops::Deref };
use smart_pointer::SmartPointer;
// 为本地智能指针数据结构增补`trait AsRef<F>`与`trait AsMut<F>`特征实现块。
smart_pointer_patch_builder!{ @ref (SmartPointer) }fn main() {let sp = SmartPointer::new(12);println!("sp = {:?}", sp);println!("sp.deref() = {:?}", sp.deref());let ref_as: &i32 = sp.as_ref();println!("sp.as_ref() = {:?}", ref_as);
}
结束语
作为从春节前半个月我就开始着手筹备的倾心大作,这篇长文算是比较全面地汇总了我在生产实践与理论知识沉淀过程中对&T
普通引用,<T: AsRef<F>>
自定义引用,<T: Borrow<F>>
自定义借用和<T: Deref<Target = F>>
智能指针四个Rust
泛化“引用”项的最新冥悟。文章不仅长,还着实有点儿生涩。感谢耐心的读者能坚持阅读至文章结束。创作不易,请读者们点个赞呗!
相关文章:
【社区投稿】深入再谈智能指针、AsRef引用与Borrow借用
深入再谈智能指针、AsRef引用与Borrow借用 这是一个具有深度的技术主题。每次重温其理论知识,都会有新的领悟。大约 2 年前,我曾就这一技术方向撰写过另一篇短文《从类型转换视角,浅谈Deref<Target T>, AsRef<T>, Borrow<T&g…...
C++ Primer Plus第十二章课后习题总结
1. 对于下面的类声明: class Cow {char name[20];char * hobby;double weight;public:Cow();Cow(const char * nm, const char * ho, double wt);Cow(const Cow c&);~Cow();Cow & operator(const Cow & c);void ShowCow() const; // display all cow d…...
Xavier 初始化:深度网络权重初始化的经典之作
Xavier 初始化:深度网络权重初始化的经典之作 发音:美 [zeɪvjər] n.泽维尔(男子名) 在深度学习的发展历程中,权重初始化对神经网络训练的成功至关重要。随机初始化的简单方法在浅层网络中尚可,但在深层…...
【量化策略】动量反转策略
【量化策略】动量反转策略 🚀量化软件开通 🚀量化实战教程 技术背景与应用场景 动量反转策略是一种基于市场行为分析的量化交易策略,它假设股票价格在经历一段时间的持续上涨或下跌后,会出现反转。这种策略适用于那些希望通过…...
菜鸟之路Day23一一JavaScript 入门
菜鸟之路Day23一一JavaScript 入门 作者:blue 时间:2025.3.10 文章目录 菜鸟之路Day23一一JavaScript 入门0.概述1.JS的引入方式2.JS的基础语法2.1输出语句2.2变量2.3数据类型2.4运算符2.5类型转换 3.函数4.JS对象4.1Array对象4.2String对象4.3Js自定义…...
FreeSWITCH 简单图形化界面40 - 使用mod_curl模块进行http请求
FreeSWITCH 简单图形化界面40 - 使用mod_curl模块进行http请求 0、界面预览00、简介1、编译安装1.1 编辑模块配置文件 2、使用2.1 拨号规则GET 请求POST 请求JSON 数据 2.2 Lua 脚本GET 请求POST 请求JSON 数据 3 、示例3.1 示例 1:提交 CDR 到第三方接口3.2 示例 2…...
TCP/IP原理详细解析
前言 TCP/IP是一种面向连接,可靠的传输,传输数据大小无限制的。通常情况下,系统与系统之间的http连接需要三次握手和四次挥手,这个执行过程会产生等待时间。这方面在日常开发时需要注意一下。 TCP/IP 是互联网的核心协议族&…...
HTTP与HTTPS的深度解析:技术差异、安全机制及应用场景
引言 HTTP(超文本传输协议)作为互联网通信的核心协议,自1991年诞生以来,经历了从HTTP/1.0到HTTP/3的多次迭代。然而,随着网络安全威胁的升级,纯HTTP协议因缺乏加密机制逐渐暴露其局限性。本文将重点解析HT…...
DrissionPage:更高效的动态爬虫实践(实例)
场景分析 代码重构对比 原Requests方案痛点 DrissionPage方案优势 重构后完整代码 关键技术点解析 1. 会话保持与指纹模拟 2. 智能请求重试 3. 反反爬策略 4. 混合模式扩展 性能对比测试 适用场景建议 常见问题解决 场景分析 原代码通过Requests直接调用B站API接…...
Triplet Loss原理及 Python实现
Triplet loss最初是谷歌在 FaceNet: A Unified Embedding for Face Recognition and Clustering 论文中提出的,可以学到较好的人脸的embedding Triplet Loss 是一种用于训练特征嵌入(feature embedding)的损失函数,广泛应用于人脸…...
2025人工智能AI新突破:PINN内嵌物理神经网络火了
最近在淘金的时候发现基于物理信息的神经网络(简称PINN)也是个研究热点,遂研读了几篇经典论文,深觉这也是个好发论文的方向,所以火速整理了一些个人认为很值得一读的PINN论文和同学们分享。 为了方面同学们更好地理解…...
深入探索Matter协议:开发Matter智能家居设备的基本步骤
随着家居智能化程度的提高,智能家居设备之间相互连接的网络虽然提升了家庭便利性,但也变得越来越复杂,难以管理。将亚马逊Alexa、Ring门铃、谷歌Nest Hub和苹果HomeKit等各种设备连接起来,并确保这些不同设备和操作系统能够良好地…...
搜广推校招面经四十五
快手主站推荐算法 这个是做因果选券的,如果大家的工作和这个有关,可以看看 一、有没有分析特征对各个的贡献度,怎么做? 传统的特征重要度衡量方法,就不介绍了。什么基于树模型的、SHAP值、LIME等。 但其实实际工程中…...
python之replace,strip,split命令
1. replace() 方法 功能:替换字符串中的指定子串 语法:str.replace(old, new[, count]) 特点: 全部替换(默认)或指定替换次数区分大小写返回新字符串,原字符串不变 示例: text "Hello…...
C语言处理字符串的十个函数(附带大量实例)
C语言的字符串处理函数主要集中在 <string.h> 头文件中,使用这些函数前必须包含该头文件。 字符串处理函数操作的对象通常是字符串(以 \0 结尾的字符数组),它们极大地方便了文本处理任务。 以下是我们将要讲解的主要函数&…...
【10】单片机编程核心技巧:指令周期与晶振频率
【10】单片机编程核心技巧:指令周期与晶振频率 🌟 核心概念 单片机的运算速度与时间控制,本质上由 指令周期 和 晶振频率 共同决定。理解这两者的关系,是掌握单片机底层控制的关键。 📌 1. 节拍与指令周期 …...
GitLab的Dockerfile 追踪
为了在 GitLab 上准备每个平台的 Docker 镜像文件,并实现完整的 Dockerfile 追踪,可以按照以下步骤进行操作: 项目准备 首先,确保你有一个 GitLab 项目,并且本地已经克隆了该项目的仓库。如果还没有项目,可…...
HTML基础
前言 什么是 HTML? HTML 是一种用于创建网页结构的标记语言,通过标签(Tag)定义内容的结构和呈现方式。 浏览器解析 HTML 文档后,将其渲染为可视化网页。 一、HTML 语法 1. HTML 基本骨架 所有 HTML 文档必须包含以下…...
静态时序分析:无法满足的生成时钟(TIM-255警告、UITE-461或PTE-075错误)
相关阅读 静态时序分析https://blog.csdn.net/weixin_45791458/category_12567571.html?spm1001.2014.3001.5482 在阅读本文前,强烈建议首先阅读介绍生成时钟的文章,尤其是其中关于时钟极性和反相的相关内容。 静态时序分析:SDC约束命令cr…...
SpringBoot日常:集成shareingsphere-jdbc
文章目录 pom依赖application.yml配置log4j2.xml实体类MapperServicecontroller调用插入接口调用查询接口 本章内容我们来聊聊如何将shareingsphere-jdbc集成到我们自己的springboot项目中,本章采用的shareingsphere-jdbc版本是5.1.2,springboot项目是2.…...
Java 生成图形验证码
一、图形验证码的意义 图形验证码是一种广泛应用于互联网领域的安全验证机制,它通过向用户展示包含字符、数字、图形等信息的图片,要求用户正确识别并输入其中的内容,以此来区分用户是人类还是机器程序。图形验证码具有多方面重要意义&#…...
nextjs15简要介绍以及配置eslint和prettier
目录 一、Next.js 何时使用服务器端渲染(SSR)?何时使用静态生成(SSG)? 1、服务器端渲染(SSR - getServerSideProps) 2、 静态生成(SSG - getStaticProps) …...
插入排序算法优化
一 插入排序概述 插入排序是稳定的原地排序算法,核心思想是逐步构建有序序列。对于未排序部分的每个元素,在已排序序列中从后向前扫描,找到合适位置插入。时间复杂度为: 最优:O(n)(已有序) 最差:O(n^2)(完全逆序) 平均:O(n^2) 二 二分查找优化(减少比较次数)…...
python学智能算法(七)|KNN邻近算法
【1】引言 前述学习进程中,已经了解了一些非常经典的智能算法,相关文章包括且不限于: python学智能算法(三)|模拟退火算法:深层分析_模拟退火 动画演示-CSDN博客 python学智能算法(四&#x…...
LabVIEW闭环控制系统硬件选型与实时性能
在LabVIEW闭环控制系统的开发中,硬件选型直接影响系统的实时性、精度与稳定性。需综合考虑数据采集速度(采样率、接口带宽)、计算延迟(算法复杂度、处理器性能)、输出响应时间(执行器延迟、控制周期&#x…...
JavaScript(Web APIs)
这个阶段两天也能看完 目录 壹_DOM-获取元素 00、获取DOM元素(根据CS选择器来获取DOM元素) 01、修改元素内容 02、修改CSS 03、H5自定义属性 04、定时器 贰_DOM-事件基础 00、事件监听 01、事件类型 02、事件对象 03、环境对象 04、回调函数 叁_DOM-事…...
创建Electron35 + vue3 + electron-builder项目,有很过坑,记录过程
环境: node v20.18.0 npm 11.1.0 用到的所有依赖: "dependencies": {"core-js": "^3.8.3","vue": "^3.2.13","vue-router": "^4.5.0"},"devDependencies": {"ba…...
机器视觉条形光源应用解析
在机器视觉中,条形光源是一种常见的照明设备,通过其特殊的形状和光路设计,能够有效解决检测中的光照均匀性、反光抑制、对比度增强等问题。以下是关于条形光源的详细解析: 1. 条形光源的基本结构与类型 结构:由多个LED灯珠沿直线或弧形排列,通常封装在长条形外壳中,可单…...
苹果商店上架流程,app上架发布流程
苹果商店地址 https://appstoreconnect.apple.com/login 其他地址:开发 - Apple Developer 1.更新代码 将项目的代码更新到最新,更新成功后右下角会给出提示 2.打开模拟器 鼠标右键可以选择设备(Device) 3.测试运行 如下图可以看到已经识别到设备了,点击运行即可,运行到模…...
大数据技术在土地利用规划中的应用分析
大数据技术在土地利用规划中的应用分析 一、引言 土地利用规划是对一定区域内的土地开发、利用、整治和保护所作出的统筹安排与战略部署,对于实现土地资源的优化配置、保障社会经济的可持续发展具有关键意义。在当今数字化时代,大数据技术凭借其海量数据处理、高效信息挖掘等…...
【Axure资料】110套优质可视化大屏模板+图表组件+科技感元件等
本作品集包含110套高保真可视化大屏模板,以及丰富的图表组件和科技感元件,旨在满足各行业对数据可视化展示的需求。所有模板和组件均基于Axure RP 9软件设计,确保高质量的交互体验和视觉效果。 作品类型:Axure原型模板 兼容版本&…...
TCP-IP协议通信模型
一、TCP/IP协议概述 TCP/IP协议即传输控制协议/互联网协议,也被称为网络通讯协议。它包含了一系列构成互联网基础的网络协议,是Internet的核心协议。 二、TCP/IP协议通信模型 文件中提到了TCP/IP协议通信模型,但未详细展开其具体层次结构和…...
VMware下载安装Ubuntu详解
一、Linux简介 1、不同领域的主流操作系统 桌面操作系统服务器操作系统移动设备操作系统嵌入式操作系统 1.1、桌面操作系统 Windows(用户数量最多)Mac OS(苹果电脑操作系统)Linux(用户数量少) 1.2、服…...
wpf label 内容绑定界面不显示
<Label Content"{Binding LabelText}" /> ... public string LabelText {get;set;}后端改变值后,界面内容并不显示 查看资料后改动如下 private string _labelText; public string LabelText{get { return _labelText; }set { _labelText value; OnPropertyCh…...
VC++ 获取目的IP的路由
GetBestRoute 函数获取到目的IP的最佳匹配路由。 第一个参数为:destination(目的IP) 第二个参数为:source(源IP) 通常不需要指定第二个source,这个一般用来匹配具体某一个网卡接口路由的&…...
海外跨境专线是什么?如何搭建海外跨境专线?
网络跨境专线——这一名词你听说过吗?如果你在跨境经济、国际贸易或网络通信领域工作,那它一定是你日常工作的一个重要话题。今天我们就来聊聊网络跨境专线的概念和搭建方法,希望能够为你在这一领域的探索提供一些帮助。 一、什么是网络跨境…...
【神经网络】python实现神经网络(二)——正向推理的模拟演练
一.神经网络假设 在开始讲解之前,首先我们假设有这样一套神经网络,一共有三层: 其中,关于神经网络的权重、偏置的符号定义如下(如果不知道什么是权重和偏置,可以参考我之前写过的一篇文章:【机器学习】机器学习是什么意思): 以下文章将沿用以上这个设…...
【从零开始学习计算机科学】操作系统(八)IO管理
【从零开始学习计算机科学】操作系统(八)IO管理 IO管理IO设备IO设备的分类IO控制器设备控制器的IO端口编址设备数据传输控制方法缓冲技术缓冲区的分类常用的缓冲技术虚拟设备IO设备的分配IO相关的软件的设计IO管理 IO设备 IO设备就是可以将数据输入到计算机,或者可以接收计…...
MySQL的安装及配置
一.以安装包方式下载 1.进入MySQL官网,下载安装包 官网链接:https://downloads.mysql.com/archives/installer/ 2.安装MySQL 二.压缩包方式下载 下载位置:mysql下载位置 解压缩后位置:D:\mysql-8.0.15-winx64 在主目录下复制…...
macOS 终端优化
macOS 安装、优化、还原、升级 Oh My Zsh 完全指南 🚀 Oh My Zsh 是 macOS 终端增强的利器,它能提供强大的自动补全、主题定制和插件支持,让你的终端更高效、更炫酷。本文将全面介绍 如何安装、优化、还原、重新安装和升级 Oh My Zsh&#x…...
Java三种注释方式
Java有三种注释:单行注释(//,从//到行尾被忽略,用于简单说明),多行注释(/* */,可包含多行内容,用于解释逻辑或禁用代码),文档注释(/**…...
Java 无 GUI 浏览器:HtmlUnit 入门及实战 [特殊字符]
文章目录 HtmlUnit 入门功能简介入门案例更多功能HtmlUnit 实战需求页面分析编码参考⭐ 本文目标: HtmlUnit 框架入门HtmlUnit 框架实战:实现 HtmlUnit 访问 Web 页面,并实现 Web 页面按钮点击,同时获取到下载的文件。HtmlUnit 入门 🚀 官网:https://htmlunit.sourcefo…...
前端如何发布npm包
1. 初始化项目 确保你的项目已经初始化,并生成 package.json 文件。如果没有,运行以下命令: npm init -y 按照提示填写项目信息(如项目名称、版本、描述等),生成 package.json 文件。 2. 安装 Webpack 在项目目录下安装 Webpack 及其相关工具: npm install webpack…...
光谱相机检测肉类新鲜度的原理
光谱相机通过分析肉类样本在特定波长范围内的光谱反射特性,结合化学与生物指标的变化规律,实现对其新鲜度的无损检测。其核心原理可概括为以下方面: 一、光谱特征与物质成分的关联性 物质特异性吸收/反射 不同化学成分(如水分…...
【计算机网络】深入解析 HTTP 请求中的 header 类型:Cookie 的概念、特点和应用场景:登录和用户认证
网络原理— HTTP 请求“报头”(header) Cookie 是什么 HTTP报头中的Cookie,用大白话来说,就像你去餐厅吃饭时拿到的一张会员卡: 初次访问 (清除该网站的所有 Cookie 后重新访问该网站,效果相同): 当你第一次访问一个网…...
红队OPSEC(安全运营)个人总结
OPSEC又称:运营安全,是指在红队的视角下,蓝队对我方的威胁。 OPSEC漏洞的五个流程: 关键信息识别:指红队的关键信息不泄露,包括但不限于红队的攻击意图,能力,人员,活动及…...
《Python基础教程》附录B笔记:Python参考手册
《Python基础教程》第1章笔记👉https://blog.csdn.net/holeer/article/details/143052930 附录B Python参考手册 Python标准文档是完整的参考手册。本附录只是一个便利的速查表,当你开始使用Python进行编程后,它可帮助你唤醒记忆。 B.1 表…...
GWO-CNN-BiLSTM-Attention多变量多步时间序列预测 | Matlab实现灰狼算法优化卷积双向长短期记忆融合注意力机制
GWO-CNN-BiLSTM-Attention多变量多步时间序列预测 | Matlab实现灰狼算法优化卷积双向长短期记忆融合注意力机制 目录 GWO-CNN-BiLSTM-Attention多变量多步时间序列预测 | Matlab实现灰狼算法优化卷积双向长短期记忆融合注意力机制预测效果基本介绍程序设计参考资料 预测效果 基…...
Python实例:PyMuPDF实现PDF翻译,英文翻译为中文,并按段落创建中文PDF
基于PyMuPDF与百度翻译的PDF翻译处理系统开发:中文乱码解决方案与自动化排版实践 一 、功能预览:将英文翻译为中文后创建的PDF 二、完整代码 from reportlab.lib.pagesizes import letter from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle...
【数据结构】初识集合框架及背后的数据结构(简单了解)
目录 前言 如何学好数据结构 1. 什么是集合框架 2. 集合框架的重要性 3. 背后所涉及的数据结构以及算法 3.1 什么是数据结构 3.2 容器背后对应的数据结构 3.3 相关java知识 3.4 什么是算法 3.5 基本关系说明(重要,简单了解) 前言 …...