当前位置: 首页 > news >正文

Rust 字符串与切片

字符串和切片

切片

let s = String::from("hello-world.");
let len = s.len();// 包头不包尾
println!("{}", &s[0..5]);  	// hello
println!("{}", &s[..5]);   	// hello
println!("{}", &s[6..]);	// world.
println!("{}", &s[..]);		// hello-world.
println!("{}", &s[0..len]);	// hello-world.

切片的类型标识:&str

fn main() {let mut s = String::from("hello world.");let word = first_word(&s); // 返回一个不可变引用// s.clear(); // ERROR 因为 clear() 需要操作可变引用println!("{}", word);
}fn first_word(s: &String) -> &str {&s[..1]
}

其它切片

let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];

字符串切片类型 &str 不具有所有权。

字符串字面量

let s = "hello";
let s: &str = "hello";

这种形式的字符串其类型是 &str 切面类型。

字符串

在 Rust 中,默认只有一种字符串类型,即 str ,其表现形式为 &str,是对字符串的不可变引用。

但是在标准库中提供了多种不同用途的字符串类型,其中最常用的是 String 类型。

&str 字符串切片类型和 String 类型的编码均采用 UTF-8(强制规定)。

除了 String 类型外,标准库中还提供:OsStringOsStrCsStringCsStr 等。

注意这些以 String 或者 Str 结尾:它们分别对应的是具有所有权和被借用的变量。

&str 和 String

特性&str 字符串切片String 动态字符串
所有权无所有权,仅仅只是借用
内存分配不分配空间,它指向现有的数据,没有则创建在堆上动态分配
可变性不可变(因为是只读引用)可变

将 &str 转为 String

let slice = "hello";
let string = String::from(slice); // 或 slice.to_string()

需要先将数据复制到堆上。

将 String 转为 &str

fn main() {let s = String::from("hello,world!");say_hello(&s);say_hello(&s[..]);say_hello(s.as_str());
}fn say_hello(s: &str) {println!("{}",s);
}

对 String 进行取引用即可,无需复制数据,直白点就是直接借用(引用)原数据。

字符串索引

let s = String::from("hello");
let i = s[0];

image-20250818162211035

Rust 中的 String 字符串并不能像 Python 或 JavaScript 那样直接通过 [index] 索引下标得到。

Rust String 字符串底层

查看 string.rs 源码可以发现,String 字符串的底层采用的是一个 u8 容器。也就是说 String 的基本单位是 1 个字节,是一个字节数组。

image-20250818162555892

在 UTF-8 中,hello 每个字符占用 1 字节,总共 4 字节。反卷 字符串每个占用 3 字节,总共 6 字节。所以无法通过索引来直接获取数据。如图:

image-20250818163613561

如果对 反卷 字符串进行 [0] 操作,则只能得到“反”字的第一个字节。

字符串的不同表现形式

image-20250818164055838

image-20250818164140404

示例

let s = String::from("反卷");
let ch = &s[0..3];
println!("{}", ch); // [SUCCESS] 反

现在看一下落在“反”中间的效果

let s = String::from("反卷");
let ch = &s[0..2];
println!("{}", ch); // ERROR
thread 'main' panicked at src/main.rs:263:16:
byte index 2 is not a char boundary; it is inside '反' (bytes 0..3) of `反卷`
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

字符串切片是切割的字节数组,采用的 UTF-8 编码,所以需要额外小心使得切割的边界要保证恰好落在边界上。

String 字符串常用操作

追加(Push)

fn main() {let mut s = String::from("Hello ");s.push_str("rust");println!("追加字符串 push_str() -> {}", s);
}

插入(Insert)

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

替换(replace)

replace

fn main() {let string_replace = String::from("I like rust. Learning rust is my favorite!");// 可适用于 String 和 &str 类型// (被替换的表达式, 要替换的新内容)let new_string_replace = string_replace.replace("rust", "RUST");// 方法是返回一个新的字符串dbg!(new_string_replace);
}
new_string_replace = "I like RUST. Learning RUST is my favorite!"

replacen

fn main() {let string_replace = "I like rust. Learning rust is my favorite!";// 可适用于 String 和 &str 类型// (被替换的表达式, 要替换的新内容, 替换次数)let new_string_replacen = string_replace.replacen("rust", "RUST", 1);// 方法是返回一个新的字符串dbg!(new_string_replacen);
}
new_string_replacen = "I like RUST. Learning rust is my favorite!"

replace_range

fn main() {let mut string_replace_range = String::from("I like rust!");// 该方法仅适用于 String 类型// (要被替换的范围, 要替换的新内容)string_replace_range.replace_range(7..8, "R");// 直接操作原来的字符串,不会返回新的字符串,所以需要使用 mut 修饰dbg!(string_replace_range);
}
string_replace_range = "I like Rust!"

删除(Delete)

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 中"

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

  • 作用:清空字符串
  • 返回:无
  • 备注:直接操作原来的字符串。
fn main() {let mut string_clear = String::from("string clear");string_clear.clear();dbg!(string_clear);
}
string_clear = ""

字符串拼接

+ 和 +=

let s1 = String::from("hello ");
let s2 = String::from("rust");
let res = s1 + &s2; // add(s1, &s2)
let mut res = res + "!"; // add(res, "!") 别忘记字面量是字符串切片类型,是引用的。
res += "!!!";
println!("{}", res); // hello rust!!!!

使用 ++= 时,相当于调用 add(self, s: &str) 这个函数,而该函数的第二个参数是字符串切片类型,因此必须要保证 ++= 的第二个操作数是字符串切片类型。

通过这种方式得到的一个新的字符串。可以加 mut 修饰符,也可以不加。

add

add() 方法的定义:

fn add(self, s: &str) -> String
fn main() {let s1 = String::from("hello,");let s2 = String::from("world!");// 在下句中,s1 的所有权被转移走了,因此后面不能再使用 s1let s3 = s1 + &s2;assert_eq!(s3,"hello,world!");// 下面的语句如果去掉注释,就会报错// println!("{}",s1);
}

为什么 s1 + &s2 中的 s1 的所有权被转移走了?

因为 add(self, s: &str) 函数的第一个参数 self 指向自身(Python 中的 self,Java 中的 this),也就是说该函数的作用是:在原字符串 s1 上追加 s2,最后得到一个新的字符串。

由于需要将 s1 传递给形参 self,自然就需要将所有权转移给 self,又因为 self 是不可变引用,所以当 add() 函数执行完成后将释放掉 self 的内存空间。此时外部的 s1 也就失效了!

由此可以推导和理解:

let s1 = String::from("A");
let s2 = String::from("B");
let s3 = String::from("C");let s = s1 + "-" + &s2 + "-" + &s3; // A-B-C
// let s = s1 + "-" + &s2 + &s3; // A-BC

问题:这 s1 s2 s3 这里面哪些失去了所有权?

答案:仅 s1 失去了所有权。(我相信很好理解。)

format

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

字符串转移

fn main() {// 通过 \ + 字符的十六进制表示,转义输出一个字符let byte_escape = "I'm writing \x52\x75\x73\x74!";println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape);// \u 可以输出一个 unicode 字符let unicode_codepoint = "\u{211D}";let character_name = "\"DOUBLE-STRUCK CAPITAL R\"";println!("Unicode character {} (U+211D) is called {}",unicode_codepoint, character_name);// 换行了也会保持之前的字符串格式// 使用\忽略换行符let long_string = "String literalscan span multiple lines.The linebreak and indentation here ->\<- can be escaped too!";println!("{}", long_string);
}

希望保持字符串的原样,不要转义:

fn main() {println!("{}", "hello \\x52\\x75\\x73\\x74");let raw_str = r"Escapes don't work here: \x3F \u{211D}";println!("{}", raw_str);// 如果字符串包含双引号,可以在开头和结尾加 #let quotes = r#"And then I said: "There is no escape!""#;println!("{}", quotes);// 如果字符串中包含 # 号,可以在开头和结尾加多个 # 号,最多加255个,只需保证与字符串中连续 # 号的个数不超过开头和结尾的 # 号的个数即可let longer_delimiter = r###"A string with "# in it. And even "##!"###;println!("{}", longer_delimiter);
}

操作 UTF-8 字符串

 let s = "中国人";let a = &s[0..2];println!("{}",a);

以字符的形式操作字符串

通过 chars() 函数

let s = "反卷大队长Blog";
for ch in s.chars() {print!("{} ", ch);
}

以字节的形式操作字符串

通过 bytes() 函数

let s = "反卷大队长Blog";
for ch in s.bytes() {print!("{} ", ch);
}
229 143 141 229 141 183 229 164 167 233 152 159 233 149 191 66 108 111 103
------------------------------------------
229 143 141 反
229 141 183 卷
229 164 167 大
233 152 159 队
233 149 191 长
66  		B
108 		l
111 		o
103 		g

获取子串

由于 Rust 的底层是一个字节数组,而非字符,没法绝对准确的确保边界,所以没办法很方便的得到字符串的子串。

只能借助第三方库,或者自己实现相关功能,可以考虑尝试下这个库:utf8_slice。

字符串深度剖析

为什么 String 可变,而 &str 不可变?

  • &str 字面量是直接硬编码到二进制文件中的。(其实就是各个段中)
  • String 是存储在堆中的。

向系统申请的内存空间总归是需要归还的,那么怎么归还呢?

  • 采用自动 GC 的话就会牺牲性能。
  • 采用手动管理内存的话就会牺牲安全。

解决方案:变量在离开作用域后,就自动释放其占用的内存。

https://fanjuanddz.com/article/42


文章转载自:

http://CudmV8YC.qwhbk.cn
http://OsLhVi7J.qwhbk.cn
http://qiySrN5r.qwhbk.cn
http://bwY4DGde.qwhbk.cn
http://EDbfWS7B.qwhbk.cn
http://z7vVA7ok.qwhbk.cn
http://elWJ9MYI.qwhbk.cn
http://Xl7TZj9G.qwhbk.cn
http://DccWKxxm.qwhbk.cn
http://xNANJHh4.qwhbk.cn
http://KbfOEKHj.qwhbk.cn
http://72Nkj9g5.qwhbk.cn
http://SQdYjodn.qwhbk.cn
http://9bL3IupK.qwhbk.cn
http://egdx21tS.qwhbk.cn
http://re5lc7Di.qwhbk.cn
http://4PU1SCXH.qwhbk.cn
http://CLy6Q5qh.qwhbk.cn
http://XjOVXp1p.qwhbk.cn
http://tO6JIQn8.qwhbk.cn
http://4DKrRO0W.qwhbk.cn
http://oJ0KxySH.qwhbk.cn
http://F5pYVjve.qwhbk.cn
http://sbsp6iKF.qwhbk.cn
http://TfWGbqmE.qwhbk.cn
http://TV20vKXh.qwhbk.cn
http://e9q5y0AB.qwhbk.cn
http://XCsGXOEI.qwhbk.cn
http://Tnt9mt5c.qwhbk.cn
http://dK4vC6tP.qwhbk.cn
http://www.dtcms.com/a/370603.html

相关文章:

  • 解析、创建Excel文件的开源库OpenXLSX介绍
  • 数据库中间件ShardingSphere v5.2.1
  • 大模型推理时的加速思路?
  • (数据结构)哈希碰撞:线性探测法 vs 拉链法
  • 如何进行神经网络的模型训练(视频代码中的知识点记录)
  • Linux--命名管道
  • 【继承和派生】
  • IDEA修改系统缓存路径,防止C盘爆满
  • scikit-learn零基础配置(含python、anaconda)
  • 《sklearn机器学习——模型的持久性》joblib 和 pickle 进行模型保存和加载
  • 深入浅出 JVM 类加载器:分类、双亲委派与打破机制
  • ViGAS、RAF、DiFF-RIR论文解读
  • 《Science》神经炎症综述思路套用:从机制到跨领域研究范式
  • macOS下arm编译缺少stdint.h等问题
  • JP4-7-MyLesson后台前端(二)
  • 机器学习高级-day01-曲线拟合
  • JAVA同城打车小程序APP打车顺风车滴滴车跑腿源码微信小程序打车源码
  • CentOS系统管理:useradd命令的全面解析
  • 小智AI编译
  • 【FastDDS】Layer Transport ( 04-TCP Transport )
  • 文件操作和IO
  • leetcode LCR 159 库存管理III
  • 使用 TCMalloc 检查内存使用情况和内存泄漏
  • Altium Designer(AD24)加载License文件方法
  • 【Gigascience】时空转录组测序探索小鼠心脏发育的细胞与分子基础
  • Ubuntu:Git SSH密钥配置的完整流程
  • 智能驾驶调研
  • 【Luogu_P8118】 「RdOI R3.5」Mystery【Slope Trick】【DP】
  • SSH服务远程安全登录
  • cds序列转换为pepperl脚本详细解读及使用