【跟小嘉学 Rust 编程】十五、智能指针
系列文章目录
【跟小嘉学 Rust 编程】一、Rust 编程基础
【跟小嘉学 Rust 编程】二、Rust 包管理工具使用
【跟小嘉学 Rust 编程】三、Rust 的基本程序概念
【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念
【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据
【跟小嘉学 Rust 编程】六、枚举和模式匹配
【跟小嘉学 Rust 编程】七、使用包(Packages)、单元包(Crates)和模块(Module)来管理项目
【跟小嘉学 Rust 编程】八、常见的集合
【跟小嘉学 Rust 编程】九、错误处理(Error Handling)
【跟小嘉学 Rust 编程】十一、编写自动化测试
【跟小嘉学 Rust 编程】十二、构建一个命令行程序
【跟小嘉学 Rust 编程】十三、函数式语言特性:迭代器和闭包
【跟小嘉学 Rust 编程】十四、关于 Cargo 和 Crates.io
【跟小嘉学 Rust 编程】十五、智能指针
文章目录
- 系列文章目录
- @[TOC](文章目录)
- 前言
- 一、智能指针
- 1.1、智能指针(smart point)
- 1.2、Box 堆内存分配
- 1.2.1、场景1:堆内存上分配数据
- 1.2.2、场景2: cons list
- 1.3、Deref 解引用
- 1.3.1、Deref trait
- 1.3.2、三种 Deref 转换
- 1.4、Drop 释放资源
- 1.4.1、Drop trait
- 1.4.2、使用 std::mem::drop 来提前 drop
- 1.5、引用计数智能指针(`RC<T>` 和 `Arc<T>`)
- 1.5.1、`RC<T>`
- 1.5.1、原子引用计数(Atomic reference counter)
- 1.6、Cell 与 RefCell 内部可变性
- 1.6.1、内部可变性(interior mutability)
- 1.6.2、Cell<T>
- 1.6.3、RefCell<T>
- 1.6.4、Cell 和 RefCell
- 1.6.5、解决借用冲突
- 1.7、Weak 和引用循环
- 1.7.1、引用循环和内存泄漏
- 1.7.2、Weak
- 1.7.3、unsafe
- 总结
文章目录
- 系列文章目录
- @[TOC](文章目录)
- 前言
- 一、智能指针
- 1.1、智能指针(smart point)
- 1.2、Box 堆内存分配
- 1.2.1、场景1:堆内存上分配数据
- 1.2.2、场景2: cons list
- 1.3、Deref 解引用
- 1.3.1、Deref trait
- 1.3.2、三种 Deref 转换
- 1.4、Drop 释放资源
- 1.4.1、Drop trait
- 1.4.2、使用 std::mem::drop 来提前 drop
- 1.5、引用计数智能指针(`RC<T>` 和 `Arc<T>`)
- 1.5.1、`RC<T>`
- 1.5.1、原子引用计数(Atomic reference counter)
- 1.6、Cell 与 RefCell 内部可变性
- 1.6.1、内部可变性(interior mutability)
- 1.6.2、Cell<T>
- 1.6.3、RefCell<T>
- 1.6.4、Cell 和 RefCell
- 1.6.5、解决借用冲突
- 1.7、Weak 和引用循环
- 1.7.1、引用循环和内存泄漏
- 1.7.2、Weak
- 1.7.3、unsafe
- 总结
前言
指针是一个包含了内存地址的变量,该内存地址引用或执行了另外的数据。在Rust中最常见的指针类型就是引用。不同的是在Rust中引用被赋予更深的含义就是借用其他变量的值。
主要教材参考 《The Rust Programming Language》
一、智能指针
1.1、智能指针(smart point)
智能指针是一个复杂的数据结构,包含了比引用更多的信息,例如元数据,当前长度,最大可用长度等。
在之前章节实际上我们已经见识过多种智能指针了,例如动态字符串 String 和动态数据 Vec。
智能指针往往是基于结构体实现,它与我们自定义的结构体最大的区别在于它实现了 Deref 和 Drop 特征:
- Deref:可以让智能指针像引用那样工作,这样你就可以写出同时支持智能指针和引用的代码,例如 *T
- Drop:允许你就指定智能指针超出作用域后自动执行的代码,例如数据清理等收尾工作
1.2、Box 堆内存分配
在Rust 中,所有值默认都是在栈内存上分配,通过创建 Box<T>
可用把值装箱,使它在堆上分配。Box<T>
是一个智能指针,因为它实现了 Deref trait,它允许Box<T>
值被当作引用对待,当 Box<T>
值离开作用域时,由于它实现了 Drop trait ,首先删除其指向堆堆数据,然后删除自身。
使用场景
- 在编译时,某类型的大小无法确定,但使用该类型时,上下文却需要知道它确切的大小;
- 当你有大量数据,想移交所有权,但需要确保在操作时数据不会被复制;
- 使用某个值,你只关心它是否实现了特定的 trait ,而不关心它的具体类型;
1.2.1、场景1:堆内存上分配数据
fn main() {let a = Box::new(1); // Immutableprintln!("{}", a); // Output: 1let mut b = Box::new(1); // Mutable*b += 1;println!("{}", b); // Output: 2
}
Box 的主要特性是单一所有权,即同时智能有一个人拥有对其指向数据的所有权,并且同时智能存在一个可变引用或多个不可变引用,这一点与Rust中其他属于堆上的数据行为一致。
1.2.2、场景2: cons list
cons list 是来自 Lisp 语言的一种数据结构。cons list 里面每个成员都包含两个元素:当前项都值和下一个元素。cons list 里的最后一个成员只包含一个 nil 值,没有下一个元素。
Box<T>
是一个指针,Rust知道它需要多少空间,因为指针的大小不会基于它指向的数据的大小变化而变化。
use crate::List::{Cons, Nil};fn main() {let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3,Box::new(Nil))))));
}enum List {Cons(i32, Box<List>),Nil,
}
1.3、Deref 解引用
1.3.1、Deref trait
Deref Trait 允许我们重载解引用运算符 *
。实现 Deref 的智能指针可以被当作引用来对待,也就是说可以对智能指针使用 *
运算符来解引用。
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> Deref for Box<T> {type Target = T;fn deref(&self) -> &T {&**self}
}
1.3.2、三种 Deref 转换
在之前,我们讲的都是不可变的 Deref 转换,实际上 Rust 还支持将一个可变的引用转换成另一个可变的引用以及将一个可变引用转换成不可变的引用,规则如下:
当 T: Deref<Target=U>,可以将 &T 转换成 &U,也就是我们之前看到的例子
当 T: DerefMut<Target=U>,可以将 &mut T 转换成 &mut U
当 T: Deref<Target=U>,可以将 &mut T 转换成 &U
1.4、Drop 释放资源
1.4.1、Drop trait
Drop trait 主要作用是释放实现者实例拥有的资源,它只有一个方法 drop。当实例离开作用域时会自动调用该方法,从而调用实现者指定的代码。
#[stable(feature = "rust1", since = "1.0.0")]
unsafe impl<#[may_dangle] T: ?Sized> Drop for Box<T> {fn drop(&mut self) {// FIXME: Do nothing, drop is currently performed by compiler.}
}
1.4.2、使用 std::mem::drop 来提前 drop
Rust 不允许手动调用 Drop trait 的 drop 方法,但是可以 使用标准库的 std::mem::drop 来提前 drop。
1.5、引用计数智能指针(RC<T>
和 Arc<T>
)
1.5.1、RC<T>
RC<T>
主要用于同一个堆上所有分配的数据区域需要多个只读访问的情况,比起使用比起使用 Box<T>
然后创建多个不可变引用的方法更优雅也更直观一些,以及比起单一所有权,Rc<T>
支持多所有权。
Rc 为 Reference Counter 的缩写,即为引用计数,Rust 的 Runtime 会实时记录一个 Rc<T>
当前被引用的次数,并在引用计数归零时对数据进行释放(类似 Python 的 GC 机制)。因为需要维护一个记录 Rc<T>
类型被引用的次数,所以这个实现需要 Runtime Cost。
use std::rc::Rc;fn main() {let a = Rc::new(1);println!("count after creating a = {}", Rc::strong_count(&a));let b = Rc::clone(&a);println!("count after creating b = {}", Rc::strong_count(&a));{let c = Rc::clone(&a);println!("count after creating c = {}", Rc::strong_count(&a));}println!("count after c goes out of scope = {}", Rc::strong_count(&a));
}
需要注意
RC<T>
是完全不可变,可以理解为同一个内存上的数据同时存在多个只读指针RC<T>
只适用单线程,尽管从概念上讲不同线程间只读指针是完全安全的,但是由于RC<T>
没有实现多个线程间保证计数一致性,如果你尝试多线程内使用,会报错;
1.5.1、原子引用计数(Atomic reference counter)
此时引用计数就可以在不同线程中安全的被使用了。
use std::thread;
use std::sync::Arc;fn main() {let a = Arc::new(1);thread::spawn(move || {let b = Arc::clone(&a);println!("{}", b); // Output: 1}).join();
}
1.6、Cell 与 RefCell 内部可变性
1.6.1、内部可变性(interior mutability)
内部可变性(interior mutability) 是 Rust 的设计模式之一,它允许你在只持有不可变引用的前提下对数据进行修改,数据结构中使用了 unsafe 代码来绕过 Rust 正常的可变性和借用规则。
1.6.2、Cell
Cell 和 Refcell 在功能上没有区别,区别在于 Cell 适用于 T 实现 Copy 的情况
1.6.3、RefCell
由于 Cell 类型针对的是实现了 Copy 特征的值类型,因此在实际开发中,Cell 使用的并不多,因为我们要解决的往往是可变、不可变引用共存导致的问题,此时就需要借助于 RefCell 来达成目的。
Rust 规则 | 智能指针带来的额外规则 |
---|---|
一个数据只有一个所有者 | Rc/Arc 让一个数据可以拥有多个所有者 |
要么多个不可变借用,要么一个可变借用 | RefCell 实现编译器可变、不可变引用共存 |
违背规则导致编译错误 | 违背规则导致运行时 panic |
可以看出,Rc/Arc 和 RefCell 合在一起,解决了 Rust 中严苛的所有权和借用规则带来的某些场景下难使用的问题。但是它们并不是银弹,例如 RefCell 实际上并没有解决可变引用和引用可以共存的问题,只是将报错从编译期推迟到运行时,从编译器错误变成了 panic 异常:
1.6.4、Cell 和 RefCell
- Cell 只适用于 Copy 类型,用于提供值,而RefCell 用于提供引用
- Cell 不会panic ,而 RefCell 会
- Cell 没有额外的性能损耗
从 CPU 来看,损耗如下:
- 对 Rc 解引用是免费的(编译期),但是 * 带来的间接取值并不免费
- 克隆 Rc 需要将当前的引用计数跟 0 和 usize::Max 进行一次比较,然后将计数值加 1
- 释放(drop) Rc 需要将计数值减 1, 然后跟 0 进行一次比较
- 对 RefCell 进行不可变借用,需要将 isize 类型的借用计数加 1,然后跟 0 进行比较
- 对 RefCell 的不可变借用进行释放,需要将 isize 减 1
- 对 RefCell 的可变借用大致流程跟上面差不多,但是需要先跟 0 比较,然后再减 1
- 对 RefCell 的可变借用进行释放,需要将 isize 加 1
1.6.5、解决借用冲突
在 Rust 1.37 版本中新增了两个非常实用的方法:
- Cell::from_mut,该方法将 &mut T 转为 &Cell
- Cell::as_slice_of_cells,该方法将 &Cell<[T]> 转为 &[Cell]
1.7、Weak 和引用循环
1.7.1、引用循环和内存泄漏
Rust 的内存安全机制可以保证很难发生内存泄漏。但是不代表不会内存泄漏。一个典型的例子就是同时使用 Rc 和 RefCell 创建循环引用,最终这些引用的计数都无法被归零,因此 Rc 拥有的值也不会被释放清理。
use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;#[derive(Debug)]
enum List {Cons(i32, RefCell<Rc<List>>),Nil,
}impl List {fn tail(&self) -> Option<&RefCell<Rc<List>>> {match self {Cons(_, item) => Some(item),Nil => None,}}
}fn main() {let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));println!("a的初始化rc计数 = {}", Rc::strong_count(&a));println!("a指向的节点 = {:?}", a.tail());// 创建`b`到`a`的引用let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));println!("在b创建后,a的rc计数 = {}", Rc::strong_count(&a));println!("b的初始化rc计数 = {}", Rc::strong_count(&b));println!("b指向的节点 = {:?}", b.tail());// 利用RefCell的可变性,创建了`a`到`b`的引用if let Some(link) = a.tail() {*link.borrow_mut() = Rc::clone(&b);}println!("在更改a后,b的rc计数 = {}", Rc::strong_count(&b));println!("在更改a后,a的rc计数 = {}", Rc::strong_count(&a));// 下面一行println!将导致循环引用// 我们可怜的8MB大小的main线程栈空间将被它冲垮,最终造成栈溢出// println!("a next item = {:?}", a.tail());
}
如何防止循环引用
- 开发者去注意细节
- 使用 Weak
1.7.2、Weak
Weak 类似 RC 但是和 RC持有所有权不同,Weak 不必持有所有权,仅仅保存一份指向数据的弱引用,如果你要想访问数据,需要通过 Weak 指针的 upgrade 方法实现,该方法返回个类型为 Option<Rc<T>>
的值。
所谓弱引用就是不保证引用关系存在,如果不存在,就返回None。
因为 Weak 引用不计入所有权,因此它无法阻止所引用的内存值被释放掉,而且 Weak 本身不对值的存在性做任何担保,引用的值还存在就返回 Some,不存在就返回 None。
Weak | RC |
---|---|
不计数 | 计数 |
不拥有所有权 | 拥有值的所有权 |
不阻止值被释放(drop) | 所有权计数归零,才能drop |
引用存在返回some,不存在返回None | 引用值必定存在 |
通过 upgrade 取到Option<Rc<T>> 再取值 | 通过 Deref 自动解引用,取值无需任何操作 |
弱引用非常适合如下场景
- 持有一个 Rc 对象的临时引用,并且不在乎引用的值是否依然存在
- 阻止 Rc 导致的循环引用,因为 Rc 的所有权机制,会导致多个 Rc 都无法计数归零
1.7.3、unsafe
除了使用 Rust 标准库提供的这些类型,你还可以使用 unsafe 里的裸指针来解决这些棘手的问题,但是由于我们还没有讲解 unsafe。
虽然 unsafe 不安全,但是在各种库的代码中依然很常见用它来实现自引用结构,主要优点如下:
- 性能高,毕竟直接用裸指针操作
- 代码更简单更符合直觉: 对比下
Option<Rc<RefCell<Node>>>
总结
以上就是今天要讲的内容
相关文章:
【跟小嘉学 Rust 编程】十五、智能指针
系列文章目录 【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学…...

Python爬虫基础之正则表达式
目录 一、什么是正则表达式? 二、re.compile()编译函数 三、group()获取匹配结果函数 四、常用匹配规则 4.1匹配单个字符 4.2匹配前字符次数 4.3匹配原生字符串 4.4匹配字符串开头和结尾 4.5分组匹配 五、re.match()开头匹配函数 六、re.search()全文搜索…...

【LeetCode】双指针妙解有效三角形的个数
Problem: 611. 有效三角形的个数 文章目录 题目分析讲解算法原理复杂度Code 题目分析 首先我们来分析一下本题的思路 看到题目中给出的示例 题目的意思很简单,就是将给到的数字去做一个组合,然后看看这三条边是否可以构成三角形。那判断的方法不用我说&a…...

mysql 计算两点之间距离
先说一下我们可能会用到的一些场景,这样同学们可以先评估,该篇文章是否对你有帮助! 场景: 假设 美团,我点外卖时,系统会让我先进行定位,比如我定位在了 A 点,系统就会给我推荐&…...
c语言自定义头文件是什么情况下使用?一般在什么情况下引用自定义的头文件?一般在自定义头文件中写什么代码?
c语言自定义头文件是什么情况下使用?一般在什么情况下引用自定义的头文件?一般在自定义头文件中写什么代码? C语言自定义头文件是一种用来封装函数和变量声明的文件,它通常用于将一组相关的函数和变量的声明集中在一个地方&#…...
electron应用打包成功纪念一下
electron应用打包成功纪念一下,以前曾经行过后来打包各种报错,现在有空就尝试解决一下 首先安装nvm能够方便切换node版本 curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash 顺利安装后你用nvm list查看node列表时会…...

远程办公中安全远程访问解决方案
什么是安全远程访问 安全的远程访问是一个至关重要的过程,可让您使用互联网从远处完全控制某人的设备。为了确保安全,为受保护的远程访问采取了额外的身份验证和加密措施。 为什么安全远程访问解决方案很重要 当 IT 技术人员从远处帮助人们解决计算机…...
StartUp启动框架-Android启动性能
简述 当谈论Android应用程序的启动性能时,StartUp启动框架是一个不可忽视的关键工具。它旨在优化应用程序的启动过程,确保用户在打开应用时能够迅速获得流畅、高效的体验。让我们来深入了解StartUp框架的作用和重要性,以及它是如何改善Andro…...
Positive Technologies:五分之四的网络攻击具有针对性
Positive Technologies 对 2023 年第二季度的相关网络威胁进行了分析。报告显示,自今年年初以来,有针对性的攻击数量增加了 10%,目前占 78%。专家们注意到利用漏洞的大规模攻击和大量用户个人数据的泄露。此外,在此期间࿰…...
clickhouse的另类表引擎
clickhouse常用的MergeTree引擎外,还有特殊的引擎 1,memory引擎,顾名思义,数据是存储在内存中,数据不会被压缩也不会倍格式化转换数据在内存中保存的形态与查询时看到的如出一辙,重启ck数据丢失 2ÿ…...
Uniapp新版本打包后覆盖安装,新增的页面无法跳转,需退出重新启动才可以打开的解决方案
最近写uniapp项目,发现一个坑,在新版本覆盖安装后直接打开APP,新增的页面竟然无法跳转,需要重新启动才可以正常打开,在网上查了很多方法,最终总结下来有以下几点: 1.看打的是debug包还是releas…...

系统架构设计高级技能 · 面向服务架构设计理论与实践
点击进入系列文章目录 系统架构设计高级技能 面向服务架构设计理论与实践 一、SOA的相关概念1.1SOA的定义1.2 业务流程与业务流程执行语言 二、SOA的发展史三、SOA与微服务的区别三、SOA的参考架构四、SOA的主要协议规范五、SOA的设计标准要求六、SOA的作用与设计原则七、SOA的…...
QT注册界面练习(信号与槽实现页面跳转)
一、注册界面练习思路以及具体代码 在完成注册页面搭建的前提下,通过信号与槽机制实现多组件之间的相互通信,实现页面跳转。 基本步骤: 首先,将注册页面的登录按钮与成功登陆信号绑定,当用户名与密码均匹配时…...

MySQL从入门到精通【进阶篇】之 主从复制详解
文章目录 0.前言1. 主从复制简介2. 主从复制的工作流程主从复制过程中的日志文件作用(Binary Log)和中继日志(Relay Log) 3. MySQL主从复制的配置4. 参考资料 0.前言 MySQL的主从复制和读写分离是数据库领域的基本概念࿰…...

vue使用qrcodejs2生成二维码
目录 概要 构建展示的vue组件qrcode.vue 组件的使用 概要 项目中用到需要展示二维码的样式,想到了qrcode 例如: 前提:安装包 npm install qrcodejs2 --save 构建展示的vue组件qrcode.vue <template><div style"width: …...
python注释
任何编程语言都少不了注释,Python也不例外,以下是Python注释的具体用法: 单行注释 Python编程语言的单行注释常以#开头,单行注释可以作为单独的一行放在被注释代码行之上,也可以放在语句或者表达式之后。 实例&…...
update-alternatives详解
1.功能作用 update-alternatives是dpkg的实用工具,用来维护系统命令的符号链接,以决定系统默认使用什么命令。 在Debian系统中,我们可能会同时安装有很多功能类似的程序和可选配置,如Web浏览器程序(firefox,konquero…...
JavaScript 编写更好的条件语句
在任何编程语言中,代码需要根据不同的条件在给定的输入中做不同的决定和执行相应的动作。 例如,在一个游戏中,如果玩家生命点为0,游戏结束。在天气应用中,如果在早上被查看,显示一个日出图片,如…...
聊聊PBE算法
序 本文主要研究一下PBE算法 PBE PBE即Password Based Encryption,基于口令的加密,它是一种组合算法,即一般是哈希对称算法,比如PBEWithMD5AndDES,就是用MD5做哈希,用DES做加解密,而其密钥则…...

用MFC打开外部程序
在MFC(Microsoft Foundation Classes)中,你可以使用ShellExecute函数来打开Notepad并加载指定的文件。ShellExecute函数是Windows API的一部分,它可以执行与操作系统相关的操作,例如打开文件、运行程序等。 以下是在M…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...

超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...

《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

STM32HAL库USART源代码解析及应用
STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...
uniapp 字符包含的相关方法
在uniapp中,如果你想检查一个字符串是否包含另一个子字符串,你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的,但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...