Rust 程序设计语言学习——智能指针

智能指针(smart pointers)是一类数据结构,它们的表现类似指针,但是也拥有额外的元数据和功能。智能指针的概念并不为 Rust 所独有;其起源于 C++ 并存在于其他语言中。Rust 标准库中定义了多种不同的智能指针,它们提供了多于引用的额外功能。

一、智能指针 box

最简单直接的智能指针是 box,其类型是 Box<T>。box 允许你将一个值放在堆上而不是栈上。留在栈上的则是指向堆数据的指针。

除了数据被储存在堆上而不是栈上之外,box 没有性能损失。不过也没有很多额外的功能。它们多用于如下场景:

  1. 当有一个在编译时未知大小的类型,而又想要在需要确切大小的上下文中使用这个类型值的时候
  2. 当有大量数据并希望在确保数据不被拷贝的情况下转移所有权的时候
  3. 当希望拥有一个值并只关心它的类型是否实现了特定 trait 而不是其具体类型的时候

因为 Box<T> 实现了 Deref trait,它允许 Box<T> 值被当作引用对待。当 Box<T> 值离开作用域时,由于 Box<T> 类型 Drop trait 的实现,box 所指向的堆数据也会被清除。

1.1 使用 Box 在堆上储存数据

Box<T> 是一种智能指针,它允许你拥有一个在堆上分配的值。Box 是 Rust 标准库中的一部分,并且是所有权系统的一部分,用于在 Rust 中管理内存。下面是一个使用 Box<T> 在堆上存储数据的简单例子:

fn main() {// 创建一个Box,它包含一个i32类型的值let mut b = Box::new(42);// 使用解引用运算符*来访问Box中的值println!("The value is: {}", *b);// 将Box中的值更改为一个新的值*b = 50;println!("The value has been changed to: {}", *b);// Box会自动在离开作用域时释放堆内存
}

在这个例子中,我们创建了一个包含 i32 类型值的 Box。我们使用 Box::new() 函数来在堆上分配内存,并存储一个值。然后,我们通过解引用运算符 * 来访问和修改这个值。当 b 离开作用域时,Rust的所有权系统会自动释放这个 Box 所占用的堆内存。

如果你想要在堆上存储更复杂的数据类型,比如结构体,你可以这样做:

struct Point {x: i32,y: i32,
}fn main() {// 使用Box来存储Point结构体的实例let mut box_point = Box::new(Point { x: 10, y: 20 });// 访问Point结构体中的字段println!("Point coordinates: ({}, {})", box_point.x, box_point.y);// 修改Point结构体中的字段box_point.x = 30;box_point.y = 40;println!("Point coordinates have been changed to: ({}, {})", box_point.x, box_point.y);
}

在这个例子中,我们定义了一个 Point 结构体,然后使用 Box 来存储这个结构体的实例。我们可以通过解引用 Box 来访问和修改结构体的字段。同样,当 box_point 离开作用域时,堆内存会被自动释放。

1.2 Box 允许创建递归类型

递归类型(recursive type)的值可以拥有另一个同类型的值作为其自身的一部分。但是这会产生一个问题,因为 Rust 需要在编译时知道类型占用多少空间。递归类型的值嵌套理论上可以无限地进行下去,所以 Rust 不知道递归类型需要多少空间。因为 box 有一个已知的大小,所以通过在循环类型定义中插入 box,就可以创建递归类型了。

下面的例子展示了如何使用 Box 来创建一个递归类型。这个例子中,我们将定义一个简单的链表,其中每个元素可以包含数据和指向下一个元素的链接。

enum List<T> {Cons(T, Box<List<T>>),Nil,
}impl<T> List<T> {// 创建一个新的链表元素fn new_cons(head: T, tail: List<T>) -> Self {List::Cons(head, Box::new(tail))}// 创建一个空的链表fn new_nil() -> Self {List::Nil}
}fn main() {// 创建一个简单的链表: 1 -> 2 -> 3 -> Nillet list = List::new_cons(1, List::new_cons(2, List::new_cons(3, List::new_nil())));// 打印链表的元素println!("List elements:");let mut cursor = &list;while let List::Cons(head, tail) = cursor {println!("{}", head);cursor = &(*tail); // 解引用Box}
}

运行结果

List elements:
1
2
3

在这个例子中:

  1. 我们定义了一个名为 List<T> 的枚举,它可以是 Cons(T, Box<List<T>>),表示链表的一个元素,其中包含数据 T 和指向下一个元素的 Box<List<T>>;或者是 Nil,表示链表的末尾。
  2. 我们为 List<T> 实现了两个方法:new_cons 用于创建一个新的链表元素,new_nil 用于创建一个空的链表。
  3. main 函数中,我们创建了一个简单的链表,其中包含三个元素:1, 2, 3。
  4. 我们使用一个循环来遍历并打印链表中的元素。在循环中,我们使用 while let 来匹配 List::Cons,并解引用 Box 来获取下一个元素。

二、通过 Deref trait 将智能指针当作常规引用处理

实现 Deref trait 允许我们重载解引用运算符(dereference operator)*(不要与乘法运算符或通配符相混淆)。通过这种方式实现 Deref trait 的智能指针可以被当作常规引用来对待,可以编写操作引用的代码并用于智能指针。

Deref trait,由标准库提供,要求实现名为 deref 的方法,其借用 self 并返回一个内部数据的引用。

下面是一个简单的例子,其中我们定义了一个结构体 MyBox,它将一个 i32 值封装起来,并为它实现了 Deref trait,使其可以像引用一样被解引用:

use std::ops::{Deref, DerefMut};struct MyBox<T> {value: T,
}impl<T> MyBox<T> {fn new(value: T) -> MyBox<T> {MyBox { value }}
}// 实现Deref trait,允许MyBox表现得像对内部值的引用
impl<T> Deref for MyBox<T> {type Target = T;fn deref(&self) -> &Self::Target {&self.value}
}// 可选:实现DerefMut trait,允许可变性地解引用MyBox
impl<T> DerefMut for MyBox<T> {fn deref_mut(&mut self) -> &mut Self::Target {&mut self.value}
}fn main() {let mut x = MyBox::new(5);println!("The value is: {}", *x); // 使用Deref trait解引用x*x = 10; // 使用DerefMut trait修改值println!("The value has been changed to: {}", *x);
}

运行结果

The value is: 5
The value has been changed to: 10

在这个例子中:

  1. 我们定义了一个名为 MyBox 的结构体,它包含一个泛型类型 T 的字段 value
  2. 我们为 MyBox 实现了 Deref trait,通过实现 deref 方法,它返回对 MyBox 内部 value 字段的不可变引用。
  3. 我们还可选地为 MyBox 实现了 DerefMut trait,这允许我们可变地解引用 MyBox。这是通过实现 deref_mut 方法来完成的,它返回对 value 字段的可变引用。
  4. main 函数中,我们创建了一个 MyBox 实例,并使用 * 运算符来解引用它,就像它是一个普通引用一样。我们还展示了如何通过解引用来修改 MyBox 内部的值。

Deref 强制转换如何与可变性交互

类似于如何使用 Deref trait 重载不可变引用的 * 运算符,Rust 提供了 DerefMut trait 用于重载可变引用的 * 运算符。

Rust 在发现类型和 trait 实现满足三种情况时会进行 Deref 强制转换:

  • T: Deref<Target=U> 时从 &T&U
  • T: DerefMut<Target=U> 时从 &mut T&mut U
  • T: Deref<Target=U> 时从 &mut T&U

前两个情况除了第二种实现了可变性之外是相同的:第一种情况表明如果有一个 &T,而 T 实现了返回 U 类型的 Deref,则可以直接得到 &U。第二种情况表明对于可变引用也有着相同的行为。

第三个情况有些微妙:Rust 也会将可变引用强转为不可变引用。但是反之是不可能的:不可变引用永远也不能强转为可变引用。因为根据借用规则,如果有一个可变引用,其必须是这些数据的唯一引用(否则程序将无法编译)。将一个可变引用转换为不可变引用永远也不会打破借用规则。将不可变引用转换为可变引用则需要初始的不可变引用是数据唯一的不可变引用,而借用规则无法保证这一点。因此,Rust 无法假设将不可变引用转换为可变引用是可能的。

三、使用 Drop Trait 运行清理代码

对于智能指针模式来说第二个重要的 trait 是 Drop,其允许我们在值要离开作用域时执行一些代码。可以为任何类型提供 Drop trait 的实现,同时所指定的代码被用于释放类似于文件或网络连接的资源。

在 Rust 中,可以指定每当值离开作用域时被执行的代码,编译器会自动插入这些代码。于是我们就不需要在程序中到处编写在实例结束时清理这些变量的代码 —— 而且还不会泄漏资源。

指定在值离开作用域时应该执行的代码的方式是实现 Drop trait。Drop trait 要求实现一个叫做 drop 的方法,它获取一个 self 的可变引用。

在Rust中,Drop trait用于定义当对象的所有权结束时应该执行的清理逻辑。这通常用于资源的清理,比如文件句柄、网络连接或者自定义的资源。

下面是一个实现了 Drop trait 的例子,这个例子中我们定义了一个简单的结构体 ResourceManager,它在被丢弃时打印一条消息:

struct ResourceManager {name: String,
}impl ResourceManager {// 创建一个新的ResourceManager实例fn new(name: &str) -> ResourceManager {ResourceManager {name: name.to_string(),}}// 使用方法来执行一些操作fn use_resource(&self) {println!("Using resource: {}", self.name);}
}impl Drop for ResourceManager {// Drop trait的实现fn drop(&mut self) {println!("Resource {} is being cleaned up.", self.name);}
}fn main() {let resource = ResourceManager::new("Resource1");resource.use_resource();// 当resource离开作用域时,Drop trait的drop方法会被自动调用
}

运行结果

Using resource: Resource1
Resource Resource1 is being cleaned up.

在这个例子中:

  1. 我们定义了一个结构体 ResourceManager,它有一个字段 name,用于存储资源的名称。
  2. 我们为 ResourceManager 实现了一个构造函数 new,它创建并返回一个新的 ResourceManager 实例。
  3. 我们定义了一个方法 use_resource,它模拟了使用资源的行为。
  4. 我们为 ResourceManager 实现了 Drop trait 的 drop 方法,它在 ResourceManager 实例被丢弃时执行,打印一条清理资源的消息。
  5. main 函数中,我们创建了一个 ResourceManager 实例,并使用它。当 resource 变量离开作用域时,Rust 的所有权系统会自动调用 drop 方法来执行清理。

四、智能指针 Rc

为了启用多所有权需要显式地使用 Rust 类型 Rc<T>,其为引用计数(reference counting)的缩写。引用计数意味着记录一个值的引用数量来知晓这个值是否仍在被使用。如果某个值为零个引用,就代表没有任何有效引用并可以被清理。

Rc<T> 用于当我们希望在堆上分配一些内存供程序的多个部分读取,而且无法在编译时确定程序的哪一部分会最后结束使用它的时候。如果确实知道哪部分是最后一个结束使用的话,就可以令其成为数据的所有者,正常的所有权规则就可以在编译时生效。

Rc<T> 通过维护一个引用计数来跟踪有多少个部分正在使用数据。当最后一个引用被销毁时,数据也会被自动释放。

下面是一个使用 Rc<T> 来共享数据的例子:

use std::rc::Rc;fn main() {// 创建一个Rc,包含一个字符串let text = Rc::new("Hello, Rust!".to_string());// 创建两个对text的引用let text_ref_a = Rc::clone(&text);let text_ref_b = Rc::clone(&text);// 打印引用计数,此时应该是3(原始的text和两个克隆的引用)println!("Reference count after creating references: {}", Rc::strong_count(&text));// 使用text_ref_a和text_ref_bprintln!("Text A: {}", text_ref_a);println!("Text B: {}", text_ref_b);// 当text_ref_a和text_ref_b离开作用域时,引用计数会减少
}

运行结果

Reference count after creating references: 3
Text A: Hello, Rust!
Text B: Hello, Rust!

这个例子中,我们首先使用 Rc::new 创建了一个包含字符串的 Rc。然后,我们使用 Rc::clone 来克隆 Rc 的引用,这会增加引用计数。我们打印出引用计数,以展示有多少个部分正在使用数据。当 text_ref_atext_ref_b 离开作用域时,引用计数会相应减少。最终,当原始的 text 也离开作用域时,引用计数将变为 0,数据将被自动释放。

请注意,Rc<T> 只适用于单线程环境。如果你需要在多线程环境中共享数据,你应该使用 Arc<T>(Atomic Reference Counted 类型),它提供了线程安全的引用计数。此外,Rc<T> 并不保证线程安全,因为引用计数的修改不是原子操作。如果你尝试在多线程环境中使用 Rc<T>,可能会导致数据竞争和其他问题。在这种情况下,使用 Arc<T> 是更安全的选择。

通过不可变引用, Rc<T> 允许在程序的多个部分之间只读地共享数据。如果 Rc<T> 也允许多个可变引用,则会违反第四章讨论的借用规则之一:相同位置的多个可变借用可能造成数据竞争和不一致。不过可以修改数据是非常有用的!

五、智能指针 RefCell

内部可变性(Interior mutability)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时也可以改变数据,这通常是借用规则所不允许的。为了改变数据,该模式在数据结构中使用 unsafe 代码来模糊 Rust 通常的可变性和借用规则。不安全代码表明我们在手动检查这些规则而不是让编译器替我们检查。

当可以确保代码在运行时会遵守借用规则,即使编译器不能保证的情况,可以选择使用那些运用内部可变性模式的类型。所涉及的 unsafe 代码将被封装进安全的 API 中,而外部类型仍然是不可变的。

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

不同于 Rc<T>RefCell<T> 代表其数据的唯一的所有权。那么是什么让 RefCell<T> 不同于像 Box<T> 这样的类型呢?回忆一下借用规则:

  • 在任意给定时刻,只能拥有一个可变引用或任意数量的不可变引用之一(而不是两者)。
  • 引用必须总是有效的。

对于引用和 Box<T>,借用规则的不可变性作用于编译时。对于 RefCell<T>,这些不可变性作用于 运行时。对于引用,如果违反这些规则,会得到一个编译错误。而对于 RefCell<T>,如果违反这些规则程序会 panic 并退出。

在编译时检查借用规则的优势是这些错误将在开发过程的早期被捕获,同时对运行时没有性能影响,因为所有的分析都提前完成了。为此,在编译时检查借用规则是大部分情况的最佳选择,这也正是其为何是 Rust 的默认行为。相反在运行时检查借用规则的好处则是允许出现特定内存安全的场景,而它们在编译时检查中是不允许的。静态分析,正如 Rust 编译器,是天生保守的。

因为一些分析是不可能的,如果 Rust 编译器不能通过所有权规则编译,它可能会拒绝一个正确的程序;从这种角度考虑它是保守的。如果 Rust 接受不正确的程序,那么用户也就不会相信 Rust 所做的保证了。然而,如果 Rust 拒绝正确的程序,虽然会给程序员带来不便,但不会带来灾难。RefCell<T> 正是用于当你确信代码遵守借用规则,而编译器不能理解和确定的时候。

类似于 Rc<T>RefCell<T> 只能用于单线程场景。如果尝试在多线程上下文中使用 RefCell<T>,会得到一个编译错误。

如下为选择 Box<T>Rc<T>RefCell<T> 的理由:

  1. Rc<T> 允许相同数据有多个所有者;Box<T>RefCell<T> 有单一所有者。
  2. Box<T> 允许在编译时执行不可变或可变借用检查;Rc<T> 仅允许在编译时执行不可变借用检查;RefCell<T> 允许在运行时执行不可变或可变借用检查。
  3. 因为 RefCell<T> 允许在运行时执行可变借用检查,所以我们可以在即便 RefCell<T> 自身是不可变的情况下修改其内部的值。

在不可变值内部改变值就是内部可变性模式。

5.2 RefCell 使用

RefCell<T> 是一种类型,提供了运行时借用检查的封装。它允许在有多个所有者的情况下运行时借用数据,即使借用规则在编译时可能无法满足。RefCell<T> 通常与 Rc<T> 结合使用,以实现在多个部分之间共享可变数据。

以下是一个使用 RefCell<T>Rc<T> 来共享可变数据的例子:

use std::cell::RefCell;
use std::rc::Rc;fn main() {// 创建一个包含i32的Rc<RefCell<T>>let value = Rc::new(RefCell::new(1));// 创建两个对value的引用let value_ref_a = Rc::clone(&value);let value_ref_b = Rc::clone(&value);// 修改value_ref_a中的值*value_ref_a.borrow_mut() += 1;// 修改value_ref_b中的值*value_ref_b.borrow_mut() += 1;// 打印value的当前值println!("Value after modifications: {}", value.borrow());
}

运行结果

Value after modifications: 3

这个例子中,我们首先使用 Rc::newRefCell::new 创建了一个包含 i32Rc<RefCell<i32>>。然后,我们使用 Rc::clone 来克隆 Rc 的引用,这会增加引用计数。我们使用 borrow_mut 方法来获取对内部数据的可变借用,并修改它。当我们调用 borrow_mut 时,RefCell 会在运行时检查借用规则,确保没有其他的可变借用或不可变借用正在发生。最终,我们使用 borrow 方法来获取对内部数据的不可变借用,并打印其值。

请注意,RefCell<T> 只适用于单线程环境。如果你需要在多线程环境中共享数据,你应该使用 Mutex<T>RwLock<T> 等同步原语。

六、引用循环与内存泄漏

Rust 的内存安全性保证使其难以意外地制造永远也不会被清理的内存(被称为内存泄漏(memory leak)),但并不是不可能。Rust 并不保证完全防止内存泄漏,这意味着内存泄漏在 Rust 中被认为是内存安全的。这一点可以通过 Rc<T>RefCell<T> 看出:创建引用循环的可能性是存在的。这会造成内存泄漏,因为每一项的引用计数永远也到不了 0,持有的数据也就永远不会被释放。

在 Rust 中,使用 Rc<T>RefCell<T> 可以创建循环引用,这可能导致内存泄漏,因为引用计数无法达到零,因此无法自动释放内存。以下是一个创建循环引用的例子:

use std::rc::Rc;
use std::cell::RefCell;struct Node {value: i32,next: RefCell<Option<Rc<Node>>>,
}fn main() {let a = Rc::new(Node {value: 1,next: RefCell::new(None),});let b = Rc::new(Node {value: 2,next: RefCell::new(None),});// 制造循环引用*a.next.borrow_mut() = Some(Rc::clone(&b));*b.next.borrow_mut() = Some(Rc::clone(&a));println!("Value a: {}, b: {}", a.value, b.value);println!("Reference count a: {}, b: {}", Rc::strong_count(&a), Rc::strong_count(&b));// 此时a和b相互引用,无法自动释放
}

运行结果

Value a: 1, b: 2
Reference count a: 2, b: 2

在这个例子中,我们创建了两个 Node 结构体实例 ab,它们通过 next 字段相互引用,形成了一个循环引用。由于 Rc<T> 的引用计数机制,只要存在至少一个引用,内存就不会被释放,这就导致了内存泄漏。

为了解决这个问题,我们可以使用 Weak<T>Weak<T> 是一种不拥有数据的智能指针,它不会增加引用计数。通常与 Rc<T> 一起使用,以打破循环引用。下面是修正后的代码:

use std::rc::{Rc, Weak};
use std::cell::RefCell;struct Node {value: i32,next: RefCell<Option<Weak<Node>>>,
}fn main() {let a = Rc::new(Node {value: 1,next: RefCell::new(None),});let b = Rc::new(Node {value: 2,next: RefCell::new(None),});println!("Reference count a: {}, b: {}", Rc::strong_count(&a), Rc::strong_count(&b));// 使用Weak来避免循环引用*a.next.borrow_mut() = Some(Rc::downgrade(&b));*b.next.borrow_mut() = Some(Rc::downgrade(&a));println!("Value a: {}, b: {}", a.value, b.value);// Weak指针不会增加Rc的引用计数println!("Reference count a: {}, b: {}", Rc::strong_count(&a), Rc::strong_count(&b));
} // a和b离开作用域,由于没有其他强引用,引用计数减到0,内存被释放
// 由于使用了Weak指针,即使存在循环引用,也不会导致内存泄漏

运行结果

Reference count a: 1, b: 1
Value a: 1, b: 2
Reference count a: 1, b: 1

在这个修正后的版本中,我们使用 Rc::downgrade 来将 Rc<Node> 转换为 Weak<Node>。这样,abnext 字段持有的是 Weak<Node> 类型的指针,而不是 Rc<Node>。当 ab 离开作用域时,它对应的 Rc<Node> 引用计数减少,由于没有其他强引用,ab 的内存会被释放。这样,循环引用的问题就被解决了。

参考链接

  1. Rust 官方网站:https://www.rust-lang.org/zh-CN
  2. Rust 官方文档:https://doc.rust-lang.org/
  3. Rust Play:https://play.rust-lang.org/
  4. 《Rust 程序设计语言》

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

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

相关文章

(史上最全的)Spring6框架学习教程

一、什么是Spring 1.javaWeb框架发展史 1、ServletJSPJavaBean(跳转页面、业务逻辑判断、数据库查询) 2、MVC三层架构(M Model pojo(User)V-view(USP)C-(controller-servlet)) (web-跳转页面service-业务逻辑判断 new UserService0;dao-数据库查询 new UserDao(); ) 3、使用…

二、八、十、十六进制介绍及相互转换

目录 1、引言&#xff1a; 2、进制介绍及区分 2.1 介绍 2.2 区分 2.3 各进制的数字组成 3、2进制与10进制 3.1 十进制的介绍 3.2 二进制的介绍 4、2进制与10进制的转换 4.1 二进制转十进制 4.2 十进制转二进制 5、8进制和16进制 5.1 八进制的介绍 5.2 十六进制的介…

告别抠图烦恼,1秒搞定100张图片背景更换!

想象一下&#xff0c;你手头有几十张甚至上百张证件照需要从白底换成蓝底&#xff0c;而你只能用Photoshop一张张抠图&#xff0c;调整&#xff0c;然后保存。这个过程不仅耗时&#xff0c;还容易出错&#xff0c;特别是当你急需处理大量图片时&#xff0c;简直让人抓狂。 千鹿…

“云+端”体系覆盖590余所学校,张家口如何建设“教育一朵云”?

当将装有小苏打的气球套在装有白醋的塑料瓶瓶口时,一场令人惊叹的化学反应开始上演——瓶内瞬间白雾腾起弥漫,气泡翻滚不息,气球逐渐吹了起来......这是在张家口教育云平台上的由学生及家长上传的关于“小苏打吹气球”的科学实验视频,而学生们与家长参与的便是张家口市中小学云…

昇思MindSpore学习入门-静态图高级编程技巧二

如何优化执行性能 使用jit_class 使用场景&#xff1a;使用jit_class装饰器修饰自定义类&#xff0c;提高执行性能。jit_class应用于静态图模式&#xff0c;在动态图模式下&#xff0c;jit_class会被忽略&#xff0c;不影响动态图模式的执行逻辑。 jit_class的介绍 用户在网…

快递代拿系统/快递代领系统

摘 要 在Internet高速发展的今天&#xff0c;我们生活的各个领域都涉及到计算机的应用&#xff0c;其中包括快递代拿系统的网络应用&#xff0c;在外国快递代拿已经是很普遍的方式&#xff0c;不过国内的快递代拿可能还处于起步阶段。快递代拿系统具有代取物品功能。快递代拿系…

武汉工程大学2020GPLT选拔赛

A-L1-1 I LOVE WIT_武汉工程大学2020GPLT选拔赛&#xff08;重现赛&#xff09;&#xff08;重现赛&#xff09;IR101 (nowcoder.com) #include<bits/stdc.h> #define endl \n #define mk make_pair #define int long long #define ll long long using namespace std; t…

androidstudio历史版本下载地址

Android Studio 下载文件归档 | Android Developers

嵌入式学习Day12---C语言提升

目录 一、指针数组 1.1.什么是指针数组 2.2. 格式 2.3.存储 2.4.与字符型二维数组相比 2.5.什么时候使用指针数组 2.6.练习 二、数组指针 2.1.什么是数组指针 2.2.格式 2.3.一维数组 2.3.特点 2.4.什么时候使用 三、指针和数组的关系 3.1.一维数组和指针 …

Linux系统将某ip拉入黑名单

在Linux系统中&#xff0c;如果您想将某个IP地址&#xff08;例如 147.78.103.44&#xff09;拉入黑名单&#xff0c;可以使用 iptables 或 firewalld。下面分别介绍这两种方法。 方法一&#xff1a;使用 iptables 打开终端。 使用以下命令添加规则&#xff1a; sudo iptable…

ps制作镂空文字

1、创建文字。 2&#xff0c;栅格化文字 2、载入选取 3.选择》修改》收缩 4、然后再点击删除键&#xff0c;镂空文字效果就做好了。 同样适用于简单的图片logo

HR模块中 HRP信息类型的相关函数

目录 1、新增:RH_INSERT_INFTY / RH_INSERT_INFTY_EXP2、修改:RH_UPDATE_INFTY / RH_UPDATE_INFTY_EXP3、读取信息类型:RH_READ_INFTY4、定界:RH_CUT_INFTY5、删除:RH_DELETE_INFTY1、新增:RH_INSERT_INFTY / RH_INSERT_INFTY_EXP 说明:这两个函数的用法差不多 代码举例…

AI智能名片小程序在预测性产品管理与营销中的深度应用探索

摘要&#xff1a;本文深入探讨了AI智能名片小程序在预测性产品管理与营销中的广泛应用及其带来的深远影响。通过详细分析该技术在数据收集、市场分析、用户画像构建、个性化推荐、客户关系管理以及风险预测等方面的具体实践&#xff0c;本文揭示了AI智能名片小程序如何助力企业…

刚起步的小型海外仓有必要上WMS系统吗?答案来了

首先&#xff0c;这里的自动化主要是指将库存管理、SKU管理&#xff0c;一件代发、财务管理等海外仓的日常作业流程通过WMS系统实现的自动化。 而不是指海外仓自动化机器人、智能导轨等硬件设备。 文章会针对刚起步的一些小型海外仓是否有必要使用WMS系统进行讨论&#xff0c…

近视配眼镜指南

一、配镜指南&#xff1a; 1.镜架选择 度数不高、光度简单、适应能力强&#xff0c;镜架不那么重要度数高、眼底情况复杂、光度复杂&#xff0c;镜架优于镜片做选择&#xff0c;好镜架不等于贵镜架好的镜架设计&#xff1a;尺寸合适、佩戴舒适、适合矫正视力&#xff08;配镜片…

捷报来袭!上半年HCIE通过120人!证书量总计1767

智汇云校捷报 —上半年华为认证证书量1767本— 2024年上半年&#xff0c;云校HCIA、HCIP、HCIE证书量总计1767本&#xff01; HCIA&#xff1a;1240本 HCIP&#xff1a;407本 HCIE&#xff1a;120本 祝贺以下学员通过HCIE认证&#xff01; 欢迎有考证需求的学员 咨询云校…

js 获取favicon

js 获取favicon 参考文档 挖个坑想着搞一个管理书签的插件。里边有各种推拽排序的功能方便操作管理&#xff0c;还能定时更新检查已经失效的网页链接&#xff0c;还能够根据现有的网站域名归类显示。吧啦吧啦等等功能。 但是其中有一个需要显示各种网站的图标&#xff0c;嗯…

Python文件打包exe文件

作者的一点话 你是否还在为py文件无法像其他可视化项目展示出来&#xff0c;制造图形界面的移动使用&#xff0c;那接下来我会与你一同使用它&#xff0c;并进行study&#xff0c;如有困惑&#xff0c;可随时联系。 然后&#xff0c;需要使用pysimplgui&#xff0c;如果…

react 样式管理方案除了 styled-components,还有什么推荐的

除了 styled-components&#xff0c;React 还有一些其他流行的样式管理方案: CSS Modules&#xff1a;允许你将 CSS 类名局部化&#xff0c;避免全局命名冲突。它与普通 CSS 语法相同&#xff0c;易于学习和使用[2]. Emotion&#xff1a;一个高性能、灵活的 CSS-in-JS 库。它支…

C++笔记---命名空间(namespace)

1. namespace的定义 通过在全局域内使用namespace关键字可以定义出一个域&#xff0c;这个域跟其他的所有域相互独立&#xff0c;不同的域可以定义同名变量&#xff0c;这样定义出的域也被称作是命名空间。 在命名空间中可以定义变量&#xff0c;结构体&#xff0c;函数&…