str

  • str类型是一种原生类型,str类型具有切片的所有特性,包括无固定大小和只读。
  • 因为str就是一个切片,通常会借用一个str,即&str。
  • str类型有两个字段组成,指向字符串数据的指针和长度。

字符串字面量定义在(“…”)内,是程序整个生命周期内都存在的str值。
因此字符串字面量的生命周期是静态的,表示为&'static str

字符串

  • 字符串(String)类型定义在Rust标准库中,是基于特化的向量实现的,由字符值组成。
  • 字符串和向量一样是可变的也是可增长的。
  • 字符串Striing类型包含3个字段:指向底层数组的指针、长度、容量。
    • 底层数组是分配的内存空间
    • 长度是按照utf-8编码占据的字节数
    • 容量是底层分配给数组空间的长度

创建字符串实例

  • 使用String::from(“str”)和str::to_string(),将str转成String
fn main() {
    let str1 = String::from("hello world1");
    let str2 = "hello world2".to_string();

    println!("{} {}",str1, str2);
}
  • 也可以通过new构造函数为String创造一个新的空字符串,然后将字符串追加进里面,前提是可变
fn main() {
    let mut new_str = String::new();
    new_str.push_str("hello world3");
    println!("{}",new_str);
}

如前所述,字符串是一种特殊的字符值向量,你甚至可以直接从向量创建一个字符串。

fn main() {
    let v = vec![65, 114, 107, 97, 110, 115, 97, 115];

    let str = String::from_utf8(v).unwrap();
    println!("{}",str);
}

在这个示例中,"Arkansas"这个码值被存储到一个向量v内,例如码值65对应的字符A,通过from_utf函数将这个向量转换成一个字符串

字符串长度

一个给定的unicode字符的长度是多少?这个问题看似简单实际上很复杂。
首先,这取决于你是指字符串的字符个数还是字节。一个utf-8字符可以用1-4个字节来描述,asscii字符,在unicode中是1字节。然而位于代码空间其他位置的字符大小可能是多个字节

  • Ascii是单字节大小
  • 希腊字符是2字节大小
  • 中文字符是3字节大小
  • 表情符号是4字节大小

对于ascii,字节长度和字符数量是相同的。而对于其他字符集可能会有所不同。len函数返回字符串中的字节数

fn main() {
    let str = "你好世界";
    println!("{}",str.len());
}

理应打印的是4,实际上却是12
要获取字符串中字符的数量,可以首先使用chars返回字符串字符的迭代器,然后在迭代器上调用count方法来统计字符数量

fn main() {
    let str = "你好世界";
    let size = str.chars().count();
    println!("{}",size);
}

扩展字符串

你可以扩展一个字符串String的值,但你不能扩展str类型的值。下面提供了几个方法

  • push 对于String追加char值
  • push_str 对于String追加一个str
fn main() {
    
    let mut str = String::new();
    str.push_str("hello world");
    str.push('!');

    println!("{}",str);
}
  • insert
  • insert_str

有时候,你可能不仅仅想在字符串末尾追加新内容,而是想将新内容插入到已有字符串中间的某个位置。

fn main() {
    let mut characters = "ac".to_string();
    characters.insert(1, 'b');
    println!("{}",characters);

    let mut numbers = "one  three".to_string();
    numbers.insert_str(4, "two");
    println!("{}", numbers);
}

字符串容量

作为特化的向量,String具有一个底层数组和一个容量。

  • 底层数组是存储字符串字符的空间,容量是底层数组的总大小,而长度则是字符串当前占用的大小。
  • 当长度超过容量时,底层数组必须重新分配并进行扩展,
    当底层数组重新分配发生时,会有性能损失。因此避免不必要的重新分配可以提供程序的性能。
fn main() {
    let mut str = '我'.to_string();

    println!("容量:{} 长度:{}",str.capacity(), str.len());

    str.push('是');
    println!("容量:{} 长度:{}",str.capacity(), str.len());

    str.push('谁');
    println!("容量:{} 长度:{}",str.capacity(), str.len());
}

上述例子将字符转换为字符串,然后每次追加一个字符,都引起字符串str的扩容,发生了两次重新分配,对应的3->8->16

如果一开始就能预估需要多大的字符值数组,那么在前面例子就可以给出更高效的写法,通过with_capacity()可以在创建字符串时手动指定容量大小

fn main() {
    let mut str = String::with_capacity(12);
    str.push('我');
    str.push('是');
    str.push('谁');
    println!("容量:{} 长度:{} 内容:{}",str.capacity(), str.len(), str);
}

访问字符串的值

我们知道,字符串本质上就是字符值数组,所以通过数组下标索引来访问吗?

fn main() {
    let str = "你好世界".to_string();
    let ch = str[1];
}

然后你将会得到类似这样的错误

error[E0277]: the type `str` cannot be indexed by `{integer}`

虽然错误本身是正确的,但没有解释清楚根本原因。
实际上,根本问题是: 对字符串使用索引进行访问是存在歧义的,我们无法确定索引值究竟对应的字节位置还是字符位置。没有解决这一歧义,继续这种操作会被编译器认为是不安全的。
因此,在Rust中直接通过索引来访问字符串中的字符是明确禁止的。
你可以通过字符串切片来访问String中的字符,起始索引和结束索引来表示字节位置,切片的结果是一个str

string[startIndex..endIndex]

示例

fn main() {
    let str = "你好世界".to_string();
    let ch = &str[3..=5];
    println!("{}", ch);
}

字符位置如下图所示
在这里插入图片描述

当尝试获取字符串切片的前两个字符,但是切片的位置是不正确的,就会引发一个panic
示例

fn main() {
    let str = "你好世界".to_string();
    let ch = &str[0..8];
    println!("{}", ch);
}

输出

byte index 8 is not a char boundary; it is inside '世' (bytes 6..9) of `你好世界`

在获取字符串切片之前,你可以手动调用str类型的is_char_boundary方法来确定给定索引位置是否与字符边界的开始对齐

fn main() {
    let str = "你好世界".to_string();
    println!("{}",str.is_char_boundary(0)); // true
    println!("{}",str.is_char_boundary(1)); //false
}

字符串里的字符

字符串由字符组成,一个很有用的操作是迭代每一个字符。比如对每个字符执行某种操作、对字符进行编码、统计字符数量、或者搜索并删除包含字母e的所有单词等等。
字符串的chars方法会返回一个字符串迭代器,方便我们遍历访问字符串中的每一个字符。

fn main() {
    let str = "你好世界".to_string();
    for ch in str.chars() {
        println!("{}", ch);
    }
}

也可以使用迭代器的nth方法读取某个位置的一个字符

fn main() {
    let str = "你好世界".to_string();
    let ch1 = str.chars().nth(1).unwrap();
    println!("{}", ch1);

}

格式化字符串

在需要&str的场合,可以借用字符串的&String来代替。这时String会继承str的所有方法,这种隐式转换的原理就是String类型为str实现了Deref trait,这种自动转换行为被称为Deref强制转换,但反过来,从str转换为String类型是不允许的

fn foo(str: &str) {
    println!("{}", str);
}
fn main() {
    let str = "hello".to_string();
    foo(&str);
}

格式化字符串

如果你需要创建格式化的字符串,那么可以使用format!()宏,这个宏与println!()的用法类似,不同的是,它返回一个格式化后的字符串,而不是直接输出。

fn main() {
    let left = 5;
    let right = 10;
    let result = format!("{}+{}={}",left, right, left+right);
    println!("{}",result);
}
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐