Rust - 字符串:str 与 String

在其他语言中,字符串通常都会比较简单,例如 “hello, world” 就是字符串章节的几乎全部内容了。

但是Rust中的字符串与其他语言有所不同,若带着其他语言的习惯来学习Rust字符串,将会波折不断。

所以最好先忘记脑中已有的关于字符串的知识,从头开始学习Rust字符串。

首先来看段逻辑简单的代码:

fn main() {let my_name = "Pascal";greet(my_name);
}fn greet(name: String) {println!("Hello, {}!", name);
}

greet 函数接受一个字符串类型的 name 参数,然后打印到终端控制台中,非常好理解,那么这段代码能不能通过编译呢?

error[E0308]: mismatched types--> src/main.rs:3:11|
3 |     greet(my_name);|           ^^^^^^^|           ||           expected struct `std::string::String`, found `&str`|           help: try using a conversion method: `my_name.to_string()`error: aborting due to previous error

编译后发现报错了。编译器提示 greet 函数需要一个 String 类型的字符串,却传入了一个 &str 类型的字符串。思考分析这个错误,它表明的信息是:我们写下的 let my_name = “Pascal”; 语句,my_name并不是 String 类型,而是 “&str” 类型。

相信如果按照其他编程语言的思路,此时就像遇到了无法理解的现象,我们需要抛开这种成见,从头开始学习Rust字符串的原理。

在学习字符串之前,先来看看什么是切片。

(一)Slice(切片)

如果你有其他编程语言的经验更好理解切片。

切片并不是 Rust 独有的概念,在 Go 语言中就非常流行。Rust切片它允许我们引用集合中部分连续的元素序列,而不是引用整个集合。即“从一个大蛋糕中切出一小块来”。

我们以数组为例,从a数组中引用“ [2, 3] ”,语句是这么写的:

let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
println!("{:?}", slice); // Rust 使用 {:?} 来打印数组切片
//执行结果:[2, 3]

请先不要纠结切片的语法,接下来我们会详细说明。

而对于 Rust 字符串的切片而言,从原理上讲即 “对 String 类型中某一部分的引用”,语句是这么写的:

let s = String::from("hello world");let hello = &s[0..5];
let world = &s[6..11];

hello 没有引用整个 String s,而是引用了 s 的一部分内容,通过 [0…5] 的方式来指定。

这就是创建切片的语法,使用方括号包括的一个序列:[开始索引…终止索引],其中开始索引是切片中第一个元素的索引位置,而终止索引是最后一个元素后面的索引位置,也就是这是一个 右半开区间 [a, b)

在切片数据结构内部会保存开始的位置和切片的长度,其中长度是通过 终止索引 - 开始索引 的方式计算得来的。

对于 let world = &s[6…11]; 来说,world 是一个切片,该切片的指针指向 s 的第 7 个字节(索引从 0 开始, 6 是第 7 个字节),且该切片的长度是 5 个字节。

在使用 Rust 的 …range 序列语法 时,如果我们想从索引 0 开始,可以使用如下的方式,这两个写法是等效的:

let s = String::from("hello");let slice = &s[0..2];
let slice = &s[..2];

同样的,如果我们的的切片想要包含 String 的最后一个字节,则可以这样使用:

let s = String::from("hello");let len = s.len();let slice = &s[4..len];
let slice = &s[4..];

我们也可以截取完整的 String 切片:

let s = String::from("hello");let len = s.len();let slice = &s[0..len];
let slice = &s[..];

在对字符串使用切片语法时需要格外小心,切片的索引必须落在字符之间的边界位置,也就是 UTF-8 字符的边界。

例如中文在 UTF-8 中占用三个字节,下面的代码就会崩溃:

 let s = "中国人";let a = &s[0..2];println!("{}",a);  //索引是一个右半开区间 [a, b),[0..2]显然拿不到完整的字符

注意:slice 是一种引用,所以它没有所有权。

这点并不难理解,结合上面我们已经提到的例子,slice 它允许我们引用集合中部分连续的元素序列,而不是引用整个集合。

而引用并不涉及 所有权转移,所以它并没有所有权。

(二)字符串Slice:&str

现在我们已经了解了Rust中的切片。接下来我们要引出Rust中很重要的一个概念:在Rust中,字符串切片的类型标识就是 &str,而我们说的“字符串字面量”类型标识也是 &str

现在,我们就能回过头再审视一下我们最初写下的这段报错的代码:

fn main() {let my_name = "Pascal";greet(my_name);
}fn greet(name: String) {println!("Hello, {}!", name);
}

编译器提示 greet 函数需要一个 String 类型的字符串,却传入了一个 &str 类型的字符串。

即:my_name的本质上其实是字符串字面量(&str类型),他并不是一个 动态字符串(String类型)。

也就是说,下面两行代码是写法不同但本质相同的代码:

let my_name = "Pascal";
let my_name: &str = "Pascal";

也许你会有一个问题,既然 my_name 是一个字符串切片引用,它引用的来源是谁呢,是从谁身上切的?

在 Rust 中,字符串字面量(如我们的 "Pascal")是在编译时由编译器处理的。当我们在代码中写入一个字符串字面量时,编译器会将其视为一个静态分配的字符串。这些字符串字面量通常存储在程序的只读数据段(在内存中)。

当你执行 let my_name = "Pascal"; 时,my_name 是一个对字符串字面量的引用。这个引用是一个字符串切片(&str),它指向存储在只读内存段中的 “Pascal” 字符串。

字符串切片 &str 是一个引用,它不拥有数据,只是指向数据。

切片指向了程序可执行文件中的某个点,这也是为什么字符串字面量是不可变的,因为 &str 是一个引用,引用是不能改变它的数据的。

根据这些内容,我们小小修改一下代码,就可以执行成功了:

fn main() {let my_name = "Pascal";greet(my_name);
}fn greet(name: &str) {println!("Hello, {}!", name);
}
//Hello, Pascal!

所有权、引用、借用、slice 这些概念都是为了让 Rust 程序在编译时确保了内存安全。

(三)Rust字符串的定义

顾名思义,字符串是由字符组成的连续集合,但是在上一节中我们提到过,Rust 中的字符是 Unicode 类型,因此每个字符占据 4 个字节内存空间,但是在字符串中不一样,字符串是 UTF-8 编码,也就是字符串中的字符所占的字节数是变化的(1 - 4),这样有助于大幅降低字符串所占用的内存空间。

Rust 在语言级别上,只有一种字符串类型: str

而它通常是以引用类型出现 &str,也就是上文提到的字符串切片。虽然语言级别只有上述的 str 类型,但是在标准库里,还有多种不同用途的字符串类型,其中使用最广的即是 String 类型,即动态字符串。

str 类型是硬编码进可执行文件,也无法被修改,但是 String 则是一个可增长、可改变且具有所有权的 UTF-8 编码字符串(动态字符串),当 Rust 用户提到字符串时,往往指的就是 Strin 类型和 &str 字符串切片类型,这两个类型都是 UTF-8 编码

除了 String 类型的字符串,Rust 的标准库还提供了其他类型的字符串,例如 OsString,OsStr,CsString 和CsStr 等,注意到这些名字都以 String 或者 Str 结尾了吗?

  • OsString 拥有字符串数据,而 OsStr 是其对应的借用类型。
  • CsString 拥有字符串数据,而 CsStr 是其对应的借用类型。

(四)动态字符串:String

(1)String 与 &str 的转换

在之前的代码中,已经见到好几种从 &str 类型生成 String 类型的操作:

let s = String::from("hello,world")
let s = "hello,world".to_string()

那么如何将 String 类型转为 &str 类型呢?答案很简单,取引用即可:

fn main() {let s = String::from("hello,world!");say_hello(&s);say_hello(&s[..]);say_hello(s.as_str()); //s.as_str() 创建了一个s字符串的引用
}fn say_hello(s: &str) {println!("{}",s);
}//结果:
//hello,world!
//hello,world!
//hello,world!
(2)字符串索引

在其它语言中,使用索引的方式访问字符串的某个字符或者子串是很正常的行为,但是在 Rust 中就会报错:

let s1 = String::from("hello");
let h = s1[0];
//报错:`String` cannot be indexed by `{integer}`

这是因为 Rust 强调内存安全和数据完整性。在 Rust 中,字符串是以 UTF-8 编码存储的,这意味着一个字符可能由多个字节组成。直接通过字节索引访问可能会导致字符串在逻辑上被错误地分割,从而破坏字符的完整性。

而要在 Rust 中安全地访问字符串中的字符或子串,我们可以使用以下几种方法,简单了解一下:

  1. 使用 chars() 方法:这个方法返回一个迭代器,迭代器中的每个元素都是字符串中的一个 Unicode 字符。你可以通过迭代器来访问每个字符。
let s = "hello";
for c in s.chars() {println!("{}", c);
}
  1. 使用 char_indices() 方法:这个方法返回一个迭代器,迭代器中的元素是 (byte_index, char) 形式的元组,其中 byte_index 是字符在字符串中的字节索引。
let s = "hello";
for (i, c) in s.char_indices() {println!("{}: {}", i, c);
}
  1. 使用 get() 方法:这个方法允许你通过字节索引来安全地访问字符串中的字符。如果索引有效,它会返回 Some(&char),否则返回 None。
let s = "hello";
if let Some(c) = s.get(0) {println!("{}", c);
} else {println!("Index out of bounds");
}
  1. 使用切片语法:如果你需要访问子串,可以使用切片语法 &s[start…end]。这里的 start 和 end 是字节索引,但 Rust 会确保切片操作不会在字符边界上切割。
let s = "hello";
let slice = &s[0..2]; // "he"
println!("{}", slice);
  1. 使用 split_off() 方法:如果你需要从字符串中分离出一部分,可以使用 split_off() 方法,它会返回一个新的 String,包含从指定索引开始的所有字符。
let mut s = String::from("hello");
let part = s.split_off(2); // s 现在是 "he", part 是 "llo"
println!("s: {}, part: {}", s, part);
(3)字符串操作

由于 String 是可变字符串,下面介绍 Rust String字符串的修改,添加,删除等常用方法。

(a)追加

在字符串尾部可以使用 push() 方法追加字符 char,也可以使用 push_str() 方法追加字符串字面量。这两个方法都是在原有的字符串上追加,并不会返回新的字符串

由于字符串追加操作要修改原来的字符串,则该字符串必须是可变的,即字符串变量必须由 mut 关键字修饰。

fn main() {let mut s = String::from("Hello ");s.push_str("rust");println!("追加字符串 push_str() -> {}", s);s.push('!');println!("追加字符 push() -> {}", s);
}//运行结果:
//追加字符串 push_str() -> Hello rust
//追加字符 push() -> Hello rust!
(b)插入

可以使用 insert() 方法插入单个字符 char,也可以使用 insert_str() 方法插入字符串字面量。与 push() 方法不同,这俩方法都需要传入两个参数,第一个参数是字符(串)插入位置的索引,第二个参数是要插入的字符(串)。

索引从 0 开始计数,如果越界则会发生错误。由于字符串插入操作要修改原来的字符串,则该字符串必须是可变的,即字符串变量必须由 mut 关键字修饰

fn main() {let mut s = String::from("Hello rust!");s.insert(5, ',');println!("插入字符 insert() -> {}", s);s.insert_str(6, " I like");println!("插入字符串 insert_str() -> {}", s);
}//运行结果:
//插入字符 insert() -> Hello, rust!
//插入字符串 insert_str() -> Hello, I like rust!
(c)替换

如果想要把字符串中的某个字符串替换成其它的字符串,那可以使用 replace() 方法。与替换有关的方法有三个。

  • replace
  • replacen
  • replace_range

replace 方法可用于 String&str 类型。

replace() 方法接收两个参数,第一个参数是要被替换的字符串,第二个参数是新的字符串。该方法会替换所有匹配到的字符串。

该方法是返回一个新的字符串,而不是操作原来的字符串

fn main() {let string_replace = String::from("I like rust. Learning rust is my favorite!");let new_string_replace = string_replace.replace("rust", "RUST");println!("{}",new_string_replace);
}//运行结果:
//I like RUST. Learning RUST is my favorite!

replacen 方法可用于 String&str 类型。

replacen() 方法接收三个参数,前两个参数与 replace() 方法一样,第三个参数则表示替换的个数。

该方法是返回一个新的字符串,而不是操作原来的字符串

fn main() {let string_replace = "I like rust. Learning rust is my favorite!";let new_string_replacen = string_replace.replacen("rust", "RUST", 1);println!("{}",new_string_replace);
}//运行结果:
//I like RUST. Learning rust is my favorite!

replace_range 方法仅适用于 String 类型。

replace_range 接收两个参数,第一个参数是要替换字符串的范围(Range),第二个参数是新的字符串。

该方法是直接操作原来的字符串,不会返回新的字符串。该方法需要使用 mut 关键字修饰

fn main() {let mut string_replace_range = String::from("I like rust!");string_replace_range.replace_range(7..8, "R");println!("{}",new_string_replace);
}
//运行结果:
//I like Rust!
(d)删除

与字符串删除相关的方法有 4 个,他们分别是 pop()remove()truncate()clear()。这四个方法仅适用于 String 类型。

pop —— 删除并返回字符串的最后一个字符

该方法是直接操作原来的字符串。但是存在返回值,其返回值是一个 Option (枚举) 类型,如果字符串为空,则返回 None

fn main() {let mut string_pop = String::from("rust pop 中文!");let p1 = string_pop.pop();let p2 = string_pop.pop();dbg!(p1);dbg!(p2);dbg!(string_pop);
}/*
p1 = Some('!',
)
p2 = Some('文',
)
string_pop = "rust pop 中"
*/

PS:dbg!() 宏是一个非常有用的调试工具。它允许你在运行时打印变量的值,而不需要添加任何额外的打印语句。

remove —— 删除并返回字符串中指定位置的字符

该方法是直接操作原来的字符串。但是存在返回值,其返回值是删除位置的字符串,只接收一个参数,表示该字符起始索引位置。remove 方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。

fn main() {let mut string_remove = String::from("测试remove方法");println!("string_remove 占 {} 个字节",std::mem::size_of_val(string_remove.as_str()));// 删除第一个汉字string_remove.remove(0);// 下面代码会发生错误// string_remove.remove(1);// 直接删除第二个汉字// string_remove.remove(3);dbg!(string_remove);
}//string_remove 占 18 个字节
//string_remove = "试remove方法"

truncate —— 删除字符串中从指定位置开始到结尾的全部字符

该方法是直接操作原来的字符串。无返回值。该方法 truncate() 方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。

fn main() {let mut string_truncate = String::from("测试truncate");string_truncate.truncate(3);dbg!(string_truncate);
}//string_truncate = "测"

clear —— 清空字符串

该方法是直接操作原来的字符串。调用后,删除字符串中的所有字符,相当于 truncate() 方法参数为 0 的时候。

fn main() {let mut string_clear = String::from("string clear");string_clear.clear();dbg!(string_clear);
}//string_clear = ""
(e)连接

使用 + 或者 += 连接字符串

使用 + 或者 += 操作符连接字符串时,要求操作符右边的参数必须为字符串的切片引用(Slice)类型。

其实当调用 + 的操作符时,相当于调用了 std::string 标准库中的 add() 方法,这里 add() 方法的第二个参数是一个引用的类型。因此我们在使用 + 时, 必须传递切片引用类型,不能直接传递 String 类型。

+ 是返回一个新的字符串,所以变量声明可以不需要 mut 关键字修饰

fn main() {let string_append = String::from("hello ");let string_rust = String::from("rust");let result = string_append + &string_rust; // &string_rust会自动解引用为&strlet result = result + "!"; // `result + "!"` 中的 `result` 是不可变的println!("连接字符串 + -> {}", result);
}//连接字符串 + -> hello rust!

使用 format! 连接字符串

format! 这种方式适用于 String&strformat! 的用法与 print! 的用法类似。

fn main() {let s1 = "hello";let s2 = String::from("rust");let s = format!("{} {}!", s1, s2);println!("{}", s);
}//hello rust!

(五)String与&str总结

在 Rust 中,String&str 都是处理字符串数据的类型,但它们之间的关系和用途有所不同。以下是它们之间的关系和区别:

  1. 所有权与借用
    • String 拥有其数据,它是一个动态分配在堆上的可变字符串类型。这意味着 String 可以增长和缩小,并且可以被修改。
    • &str 是一个字符串切片,它是一个对字符串数据的不可变引用。它不拥有数据,而是借用了其他字符串数据的一部分或全部。
  2. 内存管理
    • String 需要管理其在堆上的内存,因此它需要负责分配和释放内存。这使得 String 可以动态地改变其大小。
    • &str 只是一个引用,它不需要管理内存,因为它指向的数据可能来自 String、字符串字面量或其他数据源。
  3. 可变性
    • String 是可变的,你可以更改其内容,如添加、删除或修改字符。
    • &str 是不可变的,你不能更改它指向的数据。如果你需要修改数据,你必须在 String 上进行操作。
  4. 使用场景
    • 当你需要一个可以修改的字符串时,使用 String
    • 当你需要读取或处理字符串数据,但不需要修改它时,使用 &str
  5. 转换
    • 你可以从 String 创建一个 &str 引用,只需通过引用 String 实例即可,如 &my_string
    • 你也可以从字符串字面量创建一个 &str 引用,因为字符串字面量本身就是 &str 类型。
    • &str 创建 String 可以通过 to_string() 方法或 String::from() 函数,这涉及到从不可变引用到可变数据的转换,可能需要复制数据。
  6. 生命周期
    • String 拥有其数据,因此它不受生命周期的限制。
    • &str 是一个引用,它依赖于数据的生命周期。如果 &str 引用的数据超出了作用域,那么 &str 引用将不再有效。

总的来说,String&str 在 Rust 中提供了灵活的字符串处理机制,允许你根据需要选择最合适的类型。String 提供了可变性和动态内存管理,而 &str 提供了对字符串数据的不可变引用,这两者在不同的场景下都非常有用。

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

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

相关文章

LiveNVR监控流媒体Onvif/RTSP功能-支持电子放大拉框放大直播视频拉框放大录像视频流拉框放大电子放大

LiveNVR监控流媒体Onvif/RTSP功能-支持电子放大拉框放大直播视频拉框放大录像视频流拉框放大电子放大 1、视频广场2、录像回看3、RTSP/HLS/FLV/RTMP拉流Onvif流媒体服务 1、视频广场 视频广场 -》播放 ,左键单击可以拉取矩形框,放大选中的范围&#xff…

汽车总线之----FlexRay总线

Introduction 随着汽车智能化发展,车辆开发的ECU数量不断增加,人们对汽车系统的各个性能方面提出了更高的需求,比如更多的数据交互,更高的传输带宽等。现如今人们广泛接受电子功能来提高驾驶安全性,像ABS防抱死系统&a…

网络安全 DVWA通关指南 DVWA Weak Session IDs(弱会话)

DVWA Weak Session IDs(弱会话) 文章目录 DVWA Weak Session IDs(弱会话)Low LevelMedium LevelHigh LevelImpossible Level 参考文献 WEB 安全靶场通关指南 相关阅读 Brute Force (爆破) Command Injection(命令注入…

SpringSecurity-用户认证

1、用户认证 1.1 用户认证核心组件 我们系统中会有许多用户,确认当前是哪个用户正在使用我们系统就是登录认证的最终目的。这里我们就提取出了一个核心概念:当前登录用户/当前认证用户。整个系统安全都是围绕当前登录用户展开的,这个不难理…

基于Spring JDBC AbstractRoutingDataSource 实现动态数据源

AbstractRoutingDataSource 实现动态数据源 AbstractRoutingDataSource 即抽象的路由数据源,提供了动态数据源切换的机制。你可以通过实现它的 determineCurrentLookupKey() 方法,根据不同的条件返回对应的数据源 key,基于这点可以根据外部输…

C语言 fwirte 函数 - C语言零基础入门教程

目录 一.fwirte 函数简介二.fwirte 函数使用三.猜你喜欢 零基础 C/C 学习路线推荐 : C/C 学习目录 >> C 语言基础入门 一.fwirte 函数简介 C 语言文件读写,fread 函数用于读取文件中的数据到指定缓冲区中,而 fwrite 函数用于把缓冲区数据写入到文件…

从1岁活到80岁很平凡 chatgpt 到底能干啥

有人说:一个人从1岁活到80岁很平凡,但如果从80岁倒着活,那么一半以上的人都可能不凡。 生活没有捷径,我们踩过的坑都成为了生活的经验,这些经验越早知道,你要走的弯路就会越少。 Introduction ChatGPT是一款基于人工智能技术的聊天机器人,可以自动回复用户的问题和提供…

【算法题】72. 编辑距离-力扣(LeetCode)

【算法题】72. 编辑距离-力扣(LeetCode) 1.题目 下方是力扣官方题目的地址 72. 编辑距离 给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。 你可以对一个单词进行如下三种操作: 插入一个字符删除一个字符替换一个…

公交IC卡收单管理系统 多处 SQL注入致RCE漏洞复现

0x01 产品简介 公交IC卡收单管理系统是城市公共交通领域中不可或缺的一部分,它通过集成先进的集成电路技术(IC卡)实现了乘客便捷的支付方式,并有效提高了公共交通运营效率。系统集成了发卡、充值、消费、数据采集、查询和注销等多个功能模块,为公交公司和乘客提供了全面、…

使用shardingsphere实现mysql数据库分片

在大数据时代,随着业务数据量的不断增长,单一的数据库往往难以承载大规模的数据处理需求。数据库分片(Sharding)是一种有效的数据库扩展技术,通过将数据分布到多个数据库实例上,提高系统的性能和可扩展性。…

详细解读,F5服务器负载均衡的技术优势

在现代大规模、高流量的网络使用场景中,为应对高并发和海量数据的挑战,服务器负载均衡技术应运而生。但凡知道服务器负载均衡这一名词的,基本都对F5有所耳闻,因为负载均衡正是F5的代表作,换句通俗易懂的话来说&#xf…

曲面构件的布尔运算

1.前言 布尔运算算法有多种,可以根据几何数据表达方式分为Brep布尔运算、CSG布尔运算、网格布尔运算等,而网格布尔运算又又多种,如BSP方式、八叉树方式,博主实现过Brep布尔运算、BSP和八叉树两种网格布尔运算。详细可参考博主文章…

threejs加载高度图渲染点云,不支持tiff

问题点 使用的point来渲染高度图点云&#xff0c;大数据图片无效渲染点多&#xff08;可以通过八叉树过滤掉无效点增加效率&#xff0c;这个太复杂&#xff09;&#xff0c;但是胜在简单能用 效果图 code 代码可运行&#xff0c;无需npm <!DOCTYPE html> <html la…

Springboot + netty + rabbitmq + myBatis+mysql流量消峰

目录 0.为什么用消息队列1.代码文件创建结构2.pom.xml文件3.三个配置文件开发和生产环境4.Rabbitmq 基础配置类 TtlQueueConfig5.建立netty服务器 + rabbitmq消息生产者6.建立常规队列的消费者 Consumer7.建立死信队列的消费者 DeadLetterConsumer8.建立mapper.xml文件9.建立ma…

使用 Higress AI 插件对接通义千问大语言模型

前言 什么是 AI Gateway AI Gateway 的定义是 AI Native 的 API Gateway&#xff0c;是基于 API Gateway 的能⼒来满⾜ AI Native 的需求。例如&#xff1a; 将传统的 QPS 限流扩展到 token 限流。将传统的负载均衡/重试/fallback 能力延伸&#xff0c;支持对接多个大模型厂…

Xcode16 iOS18 编译问题适配

问题1&#xff1a;ADClient编译报错问题 报错信息 Undefined symbols for architecture arm64:"_OBJC_CLASS_$_ADClient", referenced from:in ViewController.o ld: symbol(s) not found for architecture arm64 clang: error: linker command failed with exit co…

【Redis】初识 Redis

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 文章目录 1. Redis是什么2. 浅谈分布式3. Redis的特性3.1 在内存中存储3.2 可编程性3.3 扩展性3.4 持久化3.5 集群3.6 …

C++ 刷题 使用到的一些有用的容器和函数

优先队列 c优先队列priority_queue&#xff08;自定义比较函数&#xff09;_c优先队列自定义比较-CSDN博客 373. 查找和最小的 K 对数字 - 力扣&#xff08;LeetCode&#xff09; 官方题解&#xff1a; class Solution { public:vector<vector<int>> kSmallestP…

如何检测并阻止机器人活动

恶意机器人流量逐年增加&#xff0c;占 2023 年所有互联网流量的近三分之一。恶意机器人会访问敏感数据、实施欺诈、窃取专有信息并降低网站性能。新技术使欺诈者能够更快地发动攻击并造成更大的破坏。机器人的无差别和大规模攻击对所有行业各种规模的企业都构成风险。 但您的…

【含文档】基于Springboot+微信小程序 的高校二手商品交易平台(含源码+数据库+lw)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统定…