字符串
Rust核心语言只有一种字符串类型:str,字符串 slice,通常以被借用的形式出现,&str。
它们是一些储存在别处的 UTF-8 编码字符串数据的引用。
比如字符串字面量被储存在程序的二进制输出中,即可执行程序的只读内存段中(rodata),字符串 slice 也是如此。
String 是由标准库提供,它是可增长的、可变的、有所有权的、UTF-8 编码的字符串类型。
谈到 “字符串”时,通常指的是 String 和字符串 slice &str 类型。
注意:String 和字符串 slice 是两个东西,但都是 UTF-8 编码。因为UTF-8 编码可以表示任何数据。
新建
// 空String
let mut s = String::new();
// to_string 方法,用于任何实现了 Display trait 的类型,字符串字面量也实现了它
let data = "initial contents";
let s = data.to_string();
let s = "initial contents".to_string();
// String::from 函数从字符串字面量创建
let s = String::from("initial contents");
// 第三种方式
let s: String = "also this".into();
更新
// push_str 方法来附加字符串 slice
// push_str 方法采用字符串 slice,因为并不需要获取参数的所有权
let mut s = String::from("foo");
s.push_str("bar");
let mut s1 = String::from("foo");
let s2 = "bar";
// s2的所有权被剥夺
s1.push_str(s2);
// 执行如下行报错
// println!("s2 is {}", s2);
// push 方法被定义为获取一个单独的字符作为参数
let mut s = String::from("lo");
s.push('l');
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // s1 被移动,不能继续使用
// + 运算符使用了 add 函数
// fn add(self, s: &str) -> String
// 标准库中的 add 使用泛型定义。这里的 add 签名使用具体类型代替泛型
// add 函数的 s 参数:只能将 &str 和 String 相加,不能将两个 String 值相加
// &s2 的类型是 &String 而不是 &str。为什么还能编译呢?
// 解引用强制转换(deref coercion),&String 可以被 强转(coerced)成 &str
// 它把 &s2 变成了 &s2[..]
// add 没有获取参数的所有权,所以 s2 在这个操作后仍然是有效的 String
// self 没有 使用 &
// s1 的所有权将被移动到 add 调用中,之后就不再有效
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{}-{}-{}", s1, s2, s3);
替换
// cat src/main.rs
fn main() {
let string_replace = String::from("I like rust. Learning rust is my favorite!");
let new_string_replace = string_replace.replace("rust", "RUST");
dbg!(new_string_replace); // 打印宏
let s = "12345";
let new_s = s.replace("3", "t");
dbg!(new_s); // 打印宏
}
[src/main.rs:4] new_string_replace = "I like RUST. Learning RUST is my favorite!"
[src/main.rs:7] new_s = "12t45"
不能索引
String 是一个 Vec 的封装
let len = String::from("Здравствуйте").len(); // 24
Rust不允许索引字符串的字符的原因:
(1)索引操作预期需要常数时间 (O(1)),但对于 String,Rust 必须从开头到索引位置遍历来确定有多少有效的字符,所以不是 (O(1))
(2)一个字符串字节值的索引并不总是对应一个有效的 Unicode 标量值,为了避免返回意外的值并造成不能立刻发现的 bug,请见示例
注意:如下字符串中的首字母是西里尔字母的 Ze,而不是阿拉伯数字 3
fn main() {
let hello = "Здравствуйте";
println!("hello={}", hello);
println!("the first data {}", &hello[0]);
}
// the type `str` cannot be indexed by `{integer}`
// you can use `.chars().nth()` or `.bytes().nth()`
改为如下:
fn main() {
let hello = "Здравствуйте";
println!("hello={}", hello);
println!("the first char {:?}", hello.chars().nth(0));
println!("the first byte {:?}", hello.bytes().nth(0));
}
// 当使用 UTF-8 编码时,З 的第一个字节 208,第二个是 151
// 208 自身并不是一个有效的字母
// the first char Some('З')
// the first byte Some(208)
不能像python那样使用索引去访问第n个字符
字符串 slice
使用 [] 和一个 range 来创建含特定字节的字符串 slice
let hello = "Здравствуйте";
let s = &hello[0..4]; // s是一个&str, “Зд”
// 如下panic
// &hello[0..1]
索引必须是usize类型,索引不能是负数。
如果起始索引是0,可以简写为&s[…n]
如果终止索引是String的最后一个字节,那么可以简写为&s[n…]
如果要引用整个String,那么可以简写为&s[…]
字符串切片引用的索引必须落在字符之间的边界位置
但是由于rust的字符串是UTF-8编码的,因此必须要小心
字符串的遍历
有效的 Unicode 标量值可能会由不止一个字节组成
// 操作单独的 Unicode 标量值
for c in "नमस्ते".chars() {
println!("{}", c);
}
// 返回每一个原始字节
for b in "नमस्ते".bytes() {
println!("{}", b);
}
比较 String、str、&str、&String、Box 或 Box<&str> 的区别
fn main() {
// String:动态的,可增长的字符串类型
// 它被分配在堆上并且可以修改
// 常用于需要创建或修改字符串的场合
let mut s1 = String::from("hello");
s1.push_str(", world!");
println!("s1={}", s1);
print_type_of(&s1); // alloc::string::String
// str:不可变的固定长度的字符串类型
// 通常以切片 &str 的形式出现
// 常用于固定的、不需要修改的字符串
let s2: &str = "Hello, world!";
println!("s2={}", s2);
print_type_of(&s2); // &str
// &String:指向 String 的引用,常用于函数参数,以便可以接受 String 类型也可以接受 &str 类型
let s31 = String::from("Hello, world!");
let t31: &String = &s31;
let s32 = String::from("Hello, world!");
let t32: &String = &s32;
println!("t31={}", t31); // &alloc::string::String
print_type_of(&t31);
println!("t32={}", t32); // &alloc::string::String
print_type_of(&t32);
// Box:一个堆分配的固定长度字符串
// 它的使用比较罕见,只有在一些特殊的场合才会用到。
let s41: Box<str> = Box::from("Hello, world!");
println!("s41={}", s41);
print_type_of(&s41); // alloc::boxed::Box<str>
// Box<&str>:一个罕见的类型,通常不会在 Rust 代码中出现。
// 它是一个指向 str 的堆分配引用
let s51: &str = "Hello, world!";
let t51: Box<&str> = Box::new(s51);
println!("t51={}", t51);
print_type_of(&t51); // alloc::boxed::Box<str>
}
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}
str 类型是硬编码进可执行文件,也无法被修改
String 则是一个可增长、可改变且具有所有权的 UTF-8 编码字符串文章来源:https://www.toymoban.com/news/detail-563524.html
fn main() {
let s = "Hello, Rust string!";
print_type_of(&s); // &str
let s = "Hello, Rust string!".to_string();
print_type_of(&s); // alloc::string::String
let s1 = &s;
print_type_of(&s1); // &alloc::string::String
let s2 = &s[0..5]; // 类型变换,&alloc::string::String 转 &str
print_type_of(&s2); // &str
}
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}
String和&str的相互转换
// &str 转为 String
let s = String::from("hello");
let s = "hello".to_string();
// String转为&str
let s = String::from("hello");
let slice = &s[..2];
println!("{slice}"); // 直接打印slice切片引用,没有解引用。这是因为deref 隐式强制转换,由编译器完成
附录
UTF-8 可以表示所有可打印的 ASCII 字符,以及不可打印的字符
UTF-8 还包括各种额外的国际字符,如中文字符和阿拉伯字符
UTF-8 表示字符时,每个代码都由一个或多个字节的序列来表示
(1)(0-127)ASCII 范围内的代码由一个字节表示
(2)(128-2047) 范围内的码位由两个字节表示
(3)(2048-65535) 范围内的代码点由三个字节表示
(4)(65536-1114111) 范围内的代码由四个字节表示文章来源地址https://www.toymoban.com/news/detail-563524.html
到了这里,关于Rust学习-字符串的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!