Rust学习-字符串

字符串

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 编码字符串

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) 范围内的代码由四个字节表示

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值