22.智能指针(下)

标题

  • 五、引用计数智能指针
    • 5.1 共享引用计数智能指针共享数据
    • 5.2 使用Box定义三个共享链表
    • 5.3 使用Rc代替Box
    • 5.4 引用计数增加实验
  • 六、RefCell和内部可变性模式
    • 6.1 通过RefCell在运行时检查借用规则
    • 6.2 内部可变性:不可变值的可变借用
      • 1)内部可变性的用例:mock对象
      • 2)创建测试场景
    • 6.3 RefCell在运行时记录借用
    • 6.4 结合Rc和RefCell拥有多个可变数据所有者
  • 七、引用循环会导致内存泄漏
    • 7.1 概念
    • 7.2 制造引用循环
    • 7.3 解决方案
      • 1)避免引用循环:将Rc变成Weak
      • 2)Strong VS Weak
      • 3)创建带有父子结点的树形数据结构

五、引用计数智能指针

  • 引用计数Rc<T>通过引入的数量记录着某个变量是否仍在被使用;
  • 如果引用计数为0,则代表没有任何有效引用,此时可以被清理;
  • Rc<T>用于堆上分配的内存被程序的多个部分读取,且无法确定哪一部分最后结束使用;
  • Rc<T>只能用于单线程;

5.1 共享引用计数智能指针共享数据

  • 创建两个共享第三个列表所有权的示例
    在这里插入图片描述
  • 列表a从5开始;
  • 列表bc分别从3和4开始,然后共享a的部分;

5.2 使用Box定义三个共享链表

enum List {Cons(i32, Box<List>),Nil,
}use crate::List::{Cons, Nil};fn main() {let a = Cons(5,Box::new(Cons(10,Box::new(Nil))));let b = Cons(3, Box::new(a));let c = Cons(4, Box::new(a));
}
  • 代码无法编译通过
  • 当创建列表b时,a的所有权被移进了b;
  • 当创建列表c时,a的所有权已经被移动,所以c无法创建成功;

5.3 使用Rc代替Box

enum List {Cons(i32, Rc<List>),Nil,
}use std::rc::Rc;
use crate::List::{Cons, Nil};fn main() {let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));let b = Cons(3, Rc::clone(&a));let c = Cons(4, Rc::clone(&a));
}
  • 在List中,使用Rc<T>代替Box<T>
  • 创建b和c时,克隆a所包含的Rc<List>,这使引用计数分别加1并允许a和b以及a和c之间共享Rc<List>中数据的所有权;
  • 也可以将Rc::clone(&a)换成a.clone()Rc::clone只会增加引用计数而不会执行深拷贝;

5.4 引用计数增加实验

  • 打印引用计数值,以便直接看着引用计数的变化;
fn main() {let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));println!("count after creating a = {}", Rc::strong_count(&a));let b = Cons(3, Rc::clone(&a));println!("count after creating b = {}", Rc::strong_count(&a));{let c = Cons(4, Rc::clone(&a));println!("count after creating c = {}", Rc::strong_count(&a));}println!("count after c goes out of scope = {}", Rc::strong_count(&a));
}
  • 运行结果如下
    在这里插入图片描述
  • 初始计数为1,每调用一个克隆都会使计数加1;
  • 离开作用域后会自动减少计数;

通过不可变引用,Rc<T>允许在程序的多个部分之间只读的共享数据;

六、RefCell和内部可变性模式

  • 内部可变性(Interior mutability)是Rust的一个设计模式,可让使用者在有不可变引用时也可以改变数据(这通过是规则所禁止的);
  • 为了改变数据,该模式在数据结构中使用unsafe代码来模糊Rust通常的可变性和借用规则;
  • 当可以确保代码在运行时会遵守借用规则,即使编译器不能保证的情况,可以选择使用那些运用内部可变性模式的类型。所涉及的unsafe代码将被封装进安全的API中,而外部类型仍然是不可变的;

6.1 通过RefCell在运行时检查借用规则

  • RefCell<T>代表其数据的唯一所有权;
  • RefCell<T>的借用规则的不可变性作用于运行时
  • RefCell<T>只能用于单线程场景;

6.2 内部可变性:不可变值的可变借用

  • 当有不可变值时,不能可变地借用它;
  • 如下的编译报错
fn main() {let x = 5;let y = &mut x;
}
  • 可以在特定情况下,令一个值在其方法内部能够修改自身,而在其他代码中仍然视为不可变;
  • RefCell<T>是一个获得内部可变性的方法;

1)内部可变性的用例:mock对象

  • 测试替身(test double) 代表一个测试中替代某个类型的类型;
  • mock对象是特定类型的测试替身,它们记录测试过程中发生了什么以便可以说明操作是否正确;
  • Rust没有内建mock对象功能,可以自己创建一个与mock对象有着相同功能的结构体;

2)创建测试场景

  • 编写一个记录某个值与最大值的差距的库;
  • 根据当前值与最大值的差距来发送消息;
  • 例如:可以记录用户所允许的API调用数量限额;

src/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>where T: Messenger {pub fn new(messenger: &T, max: usize) -> LimitTracker<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!");}}
}
  • Message trait拥有send方法,这是定义的mock对象所需要拥有的接口;
  • set_value方法可以改变传递的value参数的值;
#[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);for item in &mock_messenger.sent_messages{println!("{}", item);}assert_eq!(mock_messenger.sent_messages.len(), 1);}
}
  • 测试代码定义了一个MockMessenger结构体,其要发送的字段为一个String值的Vec;
  • MockMessenger结构体实现Messenger trait,这样就可以为LimitTracker提供一个MockMessenger;
  • 测试用例中
    1. 创建一个MockMessager实例,send_message存储着空vector;
    2. 创建LimitTracker,传入MockMessenger实例和最大值100;
    3. 调用LimitTracker的set_value方法,并传入80,这将运行self.messenger.send("Warning: You've used up over 75% of your quota!");代码;
    4. 最后断言sent_messages的长度为1,打印其值;

编译报错
在这里插入图片描述

  • 由于send方法获取了self的不可变引用,因此不能修改MockMessenger来记录消息;
  • 也不能按照信息使用&mut self替代,否则send的参数与Messenger trait的参数不符合;
  • 将这两项也修改;
pub trait Messenger {fn send(&mut self, msg: &str);
}impl Messenger for MockMessenger {fn send(&mut self, message: &str) {self.sent_messages.push(String::from(message));}
}
  • 还是如下报错;

在这里插入图片描述

  • 这就使得内部可变性有了用武之地!
  • 通过RefCell来储存sent_messages就可以解决这个问题;

修改的部分如下,采用注释进行对比

#[cfg(test)]
mod tests {use super::*;use std::cell::RefCell;struct MockMessenger {// sent_messages: Vec<String>,sent_messages: RefCell<Vec<String>>,}impl MockMessenger {fn new() -> MockMessenger {// MockMessenger { sent_messages: vec![] }MockMessenger { sent_messages: RefCell::new(vec![])}}}impl Messenger for MockMessenger {fn send(&self, message: &str) {// self.sent_messages.push(String::from(message));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);for item in mock_messenger.sent_messages.borrow().iter(){println!("{}", item);}assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);}
}
  • sent_messages字段的类型由Vec<String>改为RefCell<Vec<String>>
  • 在new函数中新建了一个RefCell<Vec<String>>实例替代空vector;
  • 在send方法中,使用RefCell的borrow_mut方法获取RefCell中值的可变引用(是一个vector);
  • 最后的打印以及断言中,使用RefCell的borrow以获取vector的不可变引用;
  • 最后输出了vector中的字符串并通过了测试;
    在这里插入图片描述

6.3 RefCell在运行时记录借用

  • 一般情况下使用&&mut分别创建不可变和可变引用;
  • 对于RefCell<T>,分别使用borrowborrow_mut方法创建不可变和可变引用;
  • borrow方法返回Ref<T>类型的智能指针,borrow_mut方法返回RefMut<T>类型的智能指针;
  • RefCell<T>记录当前有多少个活动的Ref<T>RefMut<T>智能指针;
  • 每次调用borrow,RefCell<T>将活动的不可变借用计数加1,当Ref<T>值离开作用域时,不可变借用计数减1;
  • RefCell<T>的实现会在运行时出现panic;
impl Messenger for MockMessenger {fn send(&self, message: &str) {let mut one_borrow = self.sent_messages.borrow_mut();let mut two_borrow = self.sent_messages.borrow_mut();one_borrow.push(String::from(message));two_borrow.push(String::from(message));}
}
  • 代码在send函数里相同的作用域创建两个可变借用;
  • 编译能正常通过,运行测试时失败;

在这里插入图片描述

  • 错误提示already borrowed: BorrowMutError,就是RefCell<T>在运行时处理违反借用规则时的报错;

6.4 结合Rc和RefCell拥有多个可变数据所有者

  • RefCell<T>的一个常见用法是与Rc<T>结合;
  • Rc<T>允许对相同数据有多个所有者,只能提供数据的不可变访问;
  • 一个储存了RefCell<T>Rc<T>,可以得到有多个所有者且可以修改的值;
#[derive(Debug)]
enum List {Cons(Rc<RefCell<i32>>, Rc<List>),Nil,
}use crate::List::{Cons, Nil};
use std::rc::Rc;
use std::cell::RefCell;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(6)), Rc::clone(&a));let c = Cons(Rc::new(RefCell::new(10)), Rc::clone(&a));*value.borrow_mut() += 10;println!("a after = {:?}", a);println!("b after = {:?}", b);println!("c after = {:?}", c);
}
  • 创建Rc<RefCell<i32>>实例并存储在变量value中;
  • 在a中用包含value的Cons成员创建了一个List;
  • 克隆value后a和value 以便a和value都能拥有其内部值 5 的所有权;
  • 将列表a封装进Rc<T>,当创建列表b和c时,他们都可以引用a;
  • 对value调用borrow_mut解引用Rc<T>以获取内部的RefCell<T>值;
  • borrow_mut方法返回RefMut<T>智能指针,可以对其使用解引用运算符并修改其内部值;
    在这里插入图片描述
  • 输出a,b,c时,可以看到他们都拥有修改后的值15;
  • 通过使用RefCell<T>可以拥有一个表面上不可变的List;
  • 可以使用RefCell<T>中提供内部可变性的方法来在需要时修改数据;
  • RefCell<T>的运行时借用规则检查也确实保护我们免于出现数据竞争;

标准库中也有其他提供内部可变性的类型,比如 Cell,它类似 RefCell 但有一点除外:它并非提供内部值的引用,而是把值拷贝进和拷贝出 Cell。还有 Mutex,其提供线程间安全的内部可变性。

七、引用循环会导致内存泄漏

7.1 概念

  • Rust的内存安全可以保证很难发生内存泄露,但并非完成不可能!
  • 使用Rc<T>RefCell<T>创造出循环引用,引用数量不会减少为0,就会发生内存泄漏;

7.2 制造引用循环

use std::rc::Rc;
use std::cell::RefCell;
use crate::List::{Cons, Nil};#[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 initial rc count = {}", Rc::strong_count(&a));println!("a next item = {:?}", a.tail());let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));println!("a rc count after b creation = {}", Rc::strong_count(&a));println!("b initial rc count = {}", Rc::strong_count(&b));println!("b next item = {:?}", b.tail());if let Some(link) = a.tail() {*link.borrow_mut() = Rc::clone(&b);}println!("b rc count after changing a = {}", Rc::strong_count(&b));println!("a rc count after changing a = {}", Rc::strong_count(&a));// Uncomment the next line to see that we have a cycle;// it will overflow the stack// println!("a next item = {:?}", a.tail());
}
  • Cons成员的第二个元素是RefCell<Rc<List>>,因此能够修改Cons成员所指向的List;
  • tail方法方便在有Cons成员的时候访问第二项;
  • 在main函数中创建了一个链表以及一个指向a中链表的b链表;
  • if let语句中使用a.tail()取出a的第二个元素,将它指向b,形成一个引用循环:两个List互相指向彼此;

在这里插入图片描述

  • 开始创建a时的引用数量为1,a的下一个元素是Nil;
  • b创建之后a的引用个数变为2,b的引用数量为1,b的下一个元素就是a;
  • 修改a的第二个元素的指向后,a和b都有两个引用。

在这里插入图片描述

  • 将main函数的最后一个println!的注释取消,就会发现在循环打印;

7.3 解决方案

  • 开发者采用自动化测试、代码评审等;
  • 重新组织数据结构,使得一部分引用拥有所有权而另一部分没有;

1)避免引用循环:将Rc变成Weak

  • Rc::cloneRc<T>实例的strong_count加1,Rc<T>的实例只有在strong_count为0时才会被清理;
  • Rc<T>实例通过调用Rc::downgrade方法可以创建值的Weak Reference(弱引用);
  • 调用Rc::downgrade时会得到Weak<T>类型的智能指针;
  • 调用Rc::downgrade时会将weak_count加1;
  • weak_count无需计数为0就能使Rc<T>实例被清理;

2)Strong VS Weak

  • 强引用(Strong Reference)是关于如何共享Rc<T>实例的所有权;
  • 弱引用(Weak Reference)并不表示所有权关系;
  • 当强引用数量为0时,弱引用会自动断开,因此弱引用不会创建循环引用;
  • Weak<T>实例上调用upgrade方法,返回Option<Rc<T>>,以此保证指向弱引用的值仍然存在;

3)创建带有父子结点的树形数据结构

加入子节点

use std::rc::Rc;
use std::cell::RefCell;#[derive(Debug)]
struct Node {value: i32,children: RefCell<Vec<Rc<Node>>>,
}fn main() {let leaf = Rc::new(Node {value: 3,children: RefCell::new(vec![]),});let branch = Rc::new(Node {value: 5,children: RefCell::new(vec![Rc::clone(&leaf)]),});
}
  • 在Node结构体中:
    • Rc<Node>保证了所有的子结点共享所有权;
    • RefCell保证了能修改其他节点的子结点;
  • 在main函数中:
    • 创建了值为3且没有子节点的Node实例leaf
    • 创建了值为5且以leaf作为子节点的实例branch
    • 这就可以通过branch.children从branch中获得leaf,反过来不行(可以通过添加parent解决);

加入父结点

  • 要使能够从leaf中获得branch,需要再加一个parent;
  • parent的类型如果是Rc<T>则会产生循环引用;
  • 换个思路:
    • 父节点应该拥有子节点:父节点被销毁了,子节点也应该被销毁;
    • 子节点不应该拥有父节点:子节点被销毁,父节点应该依然存在;
    • 因此应该使用弱引用类型Weak<T>,具体的是RefCell<Weak<Node>>
use std::rc::{Rc, Weak};
use std::cell::RefCell;#[derive(Debug)]
struct Node {value: i32,parent: RefCell<Weak<Node>>,children: RefCell<Vec<Rc<Node>>>,
}fn main() {let leaf = Rc::new(Node {value: 3,parent: RefCell::new(Weak::new()),children: RefCell::new(vec![]),});println!("leaf parent = {:#?}", leaf.parent.borrow().upgrade());let branch = Rc::new(Node {value: 5,parent: RefCell::new(Weak::new()),children: RefCell::new(vec![Rc::clone(&leaf)]),});*leaf.parent.borrow_mut() = Rc::downgrade(&branch);println!("leaf parent = {:#?}", leaf.parent.borrow().upgrade());
}
  • 对应Node结构体
    • 添加引向父节点的结构parent: RefCell::new(Weak::new()),
  • 对于main函数
    • 创建leaf结点:值为3,由于没有父结点,因此创建了空的weak引用实例;
    • 创建branch结点:值为5,父结点也为空,leaf仍作为子结点;
    • 第一个打印语句通过父结点的borrow()方法获取不可变引用,然后用upgrade()方法打印出来(为空);
    • 通过leaf.parent.borrow_mut()获取leaf的父结点的可变引用,然后通过解引用将其指向branch
    • 接着打印leaf的父结点,没有无限的输出也表示没有造成循环引用;

在这里插入图片描述

可视化strong_count和weak_count的改变

  • 创建新的内部作用域并放入branch的创建;
  • 观察Rc<Node>实例的strong_countweak_count值变化;
fn main() {let leaf = Rc::new(Node {value: 3,parent: RefCell::new(Weak::new()),children: RefCell::new(vec![]),});println!("leaf strong = {}, weak = {}",Rc::strong_count(&leaf),Rc::weak_count(&leaf),);{let branch = Rc::new(Node {value: 5,parent: RefCell::new(Weak::new()),children: RefCell::new(vec![Rc::clone(&leaf)]),});*leaf.parent.borrow_mut() = Rc::downgrade(&branch);println!("branch strong = {}, weak = {}",Rc::strong_count(&branch),Rc::weak_count(&branch),);println!("leaf strong = {}, weak = {}",Rc::strong_count(&leaf),Rc::weak_count(&leaf),);}println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());println!("leaf strong = {}, weak = {}",Rc::strong_count(&leaf),Rc::weak_count(&leaf),);
}

运行结果
在这里插入图片描述

  • 创建leaf之后,其Rc<Node>的强引用为1,没有弱引用;
  • 进入内部作用域
    • branch的强引用计数为1,弱引用计数也为1(leaf.parnet引向了branch);
    • leaf的弱引用计数为2(一个是本身的,另一个是branch的branch.children储存了leaf的Rc<Node>的拷贝),弱引用计数仍然为0;
  • 离开内部作用域后
    • branch的作用域也就结束了,leaf的Rc<Node>强引用减少为0,因此相应的branch被丢弃;
    • 来自leaf.parent的弱引用计数为1,这与Node是否被丢弃无关,因此没有产生任何内存泄漏!
  • 离开内部作用域后访问leaf的父节点,再次得到None。
  • 到程序结尾,由于leaf又是Rc<Node>唯一的引用了,因此最后打印leafRc<Node>的强引用计数为1,弱引用计数为0;

管理计数和值的逻辑都内建于Rc<T>Weak<T>以及它们的Drop trait实现中。通过在Node定义中指定从子节点到父节点的关系为一个Weak<T>引用,就能够拥有父节点和子节点之间的双向引用而不会造成引用循环和内存泄漏。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/32541.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

论文《Universal Graph Convolutional Networks》笔记

【UGCN】论文提出一个基本问题&#xff0c;即是否不同的网络结构属性应该采用不同的传播机制。通过实验发现&#xff0c;对于完全同配性、完全异配性和随机性的网络&#xff0c;1-hop、2-hop和k-nearest neighbor&#xff08;kNN&#xff09;邻居分别更适合作为信息传播的邻域。…

maven的安装以及配置

前言&#xff1a; Maven是一个强大的构建自动化工具&#xff0c;主要用于Java项目。它解决了软件开发中的两个方面&#xff1a; 构建和依赖管理&#xff1a;Maven通过在项目对象模型&#xff08;POM&#xff09;文件中指定依赖关系&#xff0c;简化了项目构建和依赖管理的过程…

【笔记】事务隔离级别以及MVCC解决幻读

事务提交可能碰到的问题&#xff1a; &#xff08;1&#xff09;脏读&#xff1a;事务1对数据进行修改但还没提交&#xff0c;事务2读取修改后的数据&#xff0c;之后事务1执行错误&#xff0c;回滚了&#xff0c;此时事务2的数据是错误的脏数据。 &#xff08;2&#xff09;不…

数学建模系列(2/4):建模入门

目录 引言 1. 如何开始数学建模 1.1 选择和描述问题 1.2 提出基本假设 1.3 确定模型类型 2. 建模的数学基础 2.1 线性代数基础 矩阵运算 线性方程组的解法 2.2 微分方程基础 常微分方程 偏微分方程 2.3 统计与概率基础 描述性统计 概率基础 3. 模型的求解方法 …

将Vite添加到您现有的Web应用程序

Vite&#xff08;发音为“veet”&#xff09;是一个新的JavaScript绑定器。它包括电池&#xff0c;几乎不需要任何配置即可使用&#xff0c;并包括大量配置选项。哦——而且速度很快。速度快得令人难以置信。 本文将介绍将现有项目转换为Vite的过程。我们将介绍别名、填充webp…

基于CSDN的Markdown文本编辑器的博客界面优化 | HTML | 文本标签 | 图像标签 | 个人主页引导

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 今天毛毛张分享的内容是如何在CSDN的Markdown编辑器中实现上图的效果&#xff0c;如果觉得能帮助到你的话就点击个人主页点点关注吧❗ 文章目录 1.前言2.基础知识3.字…

(南京观海微电子)——DC-DC和LDO的原理及应用区别

LDO: 低压差线性稳压器&#xff0c;故名思意为线性的稳压器&#xff0c;仅能使用在降压应用中&#xff0c;也就是输出电压必需小于输入电压。 优点&#xff1a;稳定性好&#xff0c;负载响应快&#xff0c;输出纹波小。 缺点&#xff1a; 效率低&#xff0c;输入输出的电压…

[C++][设计模式][模板方法]详细讲解

目录 1.动机2.理解1.设计流程对比1.结构化软件设计流程2.面向对象软件设计流程 2.早绑定与晚绑定 3.模式定义4.要点总结5.代码感受1.代码一 -- 结构化1.lib.cpp2.app.cpp 2.代码二 -- 面向对象1.lib.cpp2.app.cpp 1.动机 在软件构建过程中&#xff0c;对于某一项任务&#xff…

Nginx负载均衡之Memcached缓存模块

Nginx 的 ngx_http_memcached_module 模块本身并没有提供缓存功能&#xff0c;它只是一个将用户请求转发到 Memcached 服务器的代理模块。 在以 Memcached 服务器为缓存应用的方案中&#xff0c;Memcached 作为内容缓存的存储服务器&#xff0c;用户通过 URL 为 Memcac…

古文字识别笔记

前置知识 部件&#xff1a;大部分的汉字是由若干组笔画结构拼合而成的&#xff0c;这些相对独立的笔画结构称为「部件」。 部件是大于基本笔画&#xff08;例如&#xff1a;点、横、撇、捺等&#xff09;而小于或等同于 偏旁 的结构单位。 例如「测」字有三个部件&#xff1a;…

视觉新纪元:解码LED显示屏的视角、可视角、最佳视角的最终奥秘

在璀璨夺目的LED显示屏世界里&#xff0c;每一个绚烂画面的背后&#xff0c;都离不开三个关键概念&#xff1a;视角、可视角与最佳视角。这些术语不仅是衡量显示效果的重要标尺&#xff0c;也是连接观众与精彩内容的桥梁。让我们一起走进这场视觉盛宴&#xff0c;探索那些让LED…

【C++】——二叉搜索树(详解)

一 二叉搜索树概念 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树: ✨若它的左子树不为空&#xff0c;则左子树上所有节点的值都小于根节点的值 ✨若它的右子树不为空&#xff0c;则右子树上所有节点的值都大于根节点的值 …

Go 与 Java 字符编码选择:UTF-8 与 UTF-16 的较量

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

淦!在外包开发的三年给整废了,备战两个月终拿到Android阿里字节哈啰offer总结,阿里P6+这回稳了!

面试时候就感觉不靠谱&#xff0c;因为面试地点是位于近江附近的望江国际里面的温州银行&#xff0c;面试前网上搜了广电运通的信息&#xff0c;说是国企&#xff0c;所以我就硬着头皮接下 offer 了&#xff0c;没想到面试 Android 结果做的 C&#xff0c;而且也是驻场开发。 …

RocketMQ 和 Kafka 关于消息队列的推拉模式是怎么做的?

引言&#xff1a;在当今的大数据和分布式系统中&#xff0c;消息队列扮演着至关重要的角色&#xff0c;它们作为系统之间通信和数据传输的媒介&#xff0c;为各种场景下的数据流动提供了可靠的基础设施支持。在消息队列的设计中&#xff0c;推拉模式是两种常见的消息传递机制&a…

02 Shell编程之条件语句(补充实验部分)

1、双分支if语句的补充&#xff08;实验部分&#xff09; 例如&#xff0c;要编写一个连通性测试脚本&#xff0c;通过位置参数来提供目标主机地址&#xff0c;然后根据ping检测结果给出相应的提示 &#xff08;能ping通的&#xff0c;回馈一个信息&#xff1a;该服务器是开启…

浔川AI社宣布正式开创“浔川AI助手”——浔川AI社

这是浔川AI社的标志。 2024.6.22晚8点35分宣布&#xff0c;浔川AI社正式开创“浔川AI助手” 全面发展。 据浔川AI社报道称‘“浔川AI助手”内容包含全部&#xff0c;写作、聊天......都有。’ 让我们敬请期待&#xff01;

【JAVA】精致的五角星

输出的这幅图像中&#xff0c;一颗精致的金色五角星跃然于深红色背景之上&#xff0c;绽放出迷人的光彩。 要绘画这颗五角星&#xff0c;首先要了解五角星的构造和角度问题。我们可以分为内五边形&#xff0c;和外五边形。内五边形从他的中心到每个外点&#xff0c;连接起来&am…

ECharts词云图(案例一)+配置项详解

ECharts词云图&#xff08;案例一&#xff09;配置项详解 ECharts 是一款由百度团队开发的基于 JavaScript 的开源可视化图表库&#xff0c;它提供了丰富的图表类型&#xff0c;包括常见的折线图、柱状图、饼图等&#xff0c;以及一些较为特殊的图表&#xff0c;如词云图。从版…

带百分比的进度条控件(ProgressBar)源码

带百分比的进度条控件&#xff08;ProgressBar&#xff09;&#xff1a; 源码下载地址&#xff1a;https://download.csdn.net/download/wgxds/89472915