• 【Rust 笔记】15-字符串与文本(上)


    15 - 字符串与文本

    15.1-Unicode

    15.1.1-ASCII、Latin-1 与 Unicode

    • Unicode 与 ASCII 的所有 ASCII 码点都相同(0 ~ 0x7f)。

    • Unicode 将(0 ~ 0x7f)码点范围称为 Latin-1 编码块(ISO/IEC 8859-1)。

    • 即 Unicode 是 Latin-1 的超集:

      • Latin-1 转换为 Unicode:

        fn latin1_to_char(latin1: u8) -> char {
          latin1 as char
        }
        
        • 1
        • 2
        • 3
      • Unicode 转换为 Latin-1:

        fn char_to_latin1(c: char) -> Option<u8> {
          if c as u32 <= 0xff {
            Some(c as u8)
          } else {
            None
          }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7

    15.1.2-UTF-8

    • Rust 的 Stringstr 类型使用 UTF-8 编码格式表示文本。UTF-8 将字符编码为 1 到 4 个字节序列。
    • UTF-8 序列的格式限制:
      • 对于给定的码点,只有最短的编码才被认为是格式良好的,即不能用 4 个字节去编码只需 3 个字节的码点。
      • 格式良好的 UTF-8 不能对 0xd800 ~ 0xdfff,以及大于 0x10ffff 的数值编码。
    • UTF-8 的重要属性:
      • UTF-8 对码点 0 到 0x7f 的编码就是字节 0 到 0x7f,保存 ASCII 文本的字节是最有效的 UTF-8。ASCII 与 UTF-8 是可逆的,而 Latin-1 与 UTF-8 不具备可逆性。
      • 通过观察任意字节的前几位,就能知道它是某些字符 UTF-8 编码的首字节,还是中间字节。
      • 通过编码首字节的前几位就能知道编码的总长度。
      • 编码最长为 4 个字节,UTF-8 不需要无限循环,可用于处理不受信的数据。
      • 格式良好的 UTF-8 种,可以快速指出字符编码的起始和结束位置。UTF-8 的首字节与后续字节有明显区别。

    15.1.5 - 文本方向性

    • 有些文字是从左向右书写:属于正常情况下的书写或阅读方式,也是 Unicode 采用的顺序存储字符。
    • 有些文字是从右向左书写:字符串的首字节保存的是要写在最右边的字符的编码。

    15.2 - 字符(char)

    • char 类型是保存 Unicode 码点的 32 位值。
    • 范围为:0 到 0xd7ff,或者 0xe000 到 0x10ffff。
    • char 类型实现了 CopyClone,以及比较、散列、格式的所有常用特型。

    15.2.1 - 字符分类 —— 检测字符类别的方法

    • ch.is_numeric():数值字符,包括 Unicode 普通类别 Number; digitNumber; letter,但不包括 Number; other
    • ch.is_alphabetic():字母字符,包括 Unicode 的 “Alphabetic” 派生属性。
    • ch.is_alphanumeric():数值或字母字符,包括上面两个类别。
    • ch.is_whitespace():空白字符,包括 Unicode 字符属性 “WSpace=Y”。
    • ch.is_control:控制字符,包括 Unicode 的 Other, control 普通类别。

    15.2.2 - 处理数字

    • ch.to_digit(radix):决定 ch 是否是基数为 radix 的 ASCII 数字。如果是就返回 Some(num),其中 numu32。否则,返回 Noneradix 的范围是 2~36。如果 radix > 10,那么 ASCII 字母将作为值为 10~35 的数字。
    • std::char::from_digit(num, radix):把 u32 数值 num 转换为 char。如果 radix > 10ch 是小写字母。
    • ch.is_digit(radix):在 ch 是基数为 radix 下的 ASCII 数字时,返回 true。等价于 ch.to_digit(radix) != None

    15.2.3 - 字符大小写转换

    • ch.is_lowercase():判断 ch 是否为小写字母。
    • ch.is_uppercase():判断 ch 是否为大写字母。
    • ch.to_lowercase():将 ch 转换为小写字母。
    • ch.to_uppercase():将 ch 转换为大写字母。

    15.2.4 - 与整数相互转换

    • as 操作符可以把 char 转换为任何整数类型,高位会被屏蔽。
    • as 操作符可以把任何 u8 值转换为 charchar 类型也实现了 From<u8>。推荐使用 std::char::from_u32,返回 Option<char>

    15.3-String 与 str

    • Rust 的 Stringstr 类型只保存格式良好的 UTF-8。

    • String 类型能创建可伸缩缓冲区,用于保存字符串。本质为 Vec<u8> 的包装类型。

    • str 类型则就地操作字符串文本。

    • String 的解引用为 &strstr 上定义的所有方法,都可以在 String 上直接调用。

    • 文本处理的方法按照字节偏移量来索引文本,也按字节来度量长度,并不是按照字符。

    • Rust 会根据变量的名称,推测其类型,如:

      变量名推测类型
      stringString
      slice&str 或解引用为 &str 的类型,如 StringRc<String>
      chchar
      nusize,长度
      i, jusize,字节偏移量
      rangeusize 字节偏移量范围,可能是全限定 i..j,部分限定 i....j,或无限定..
      pattern任意模式类型:char, String, &str, &[char], FnMut(char) -> bool

    15.3.1 - 创建 String

    • String::new():返回全新的空字符串。没有分配在堆上的缓冲区,后续会根据需要分配。

    • String::with_capacity(n):返回全新的空字符串,同时在堆上分配至少容纳 n 字节的缓冲区。

    • slice.to_string():常用于通过字符串字面量创建 String。分配一个全新的 String,其内容就是 slice 的副本。

    • iter.collect():通过拼接迭代器的所有项(char&strString 值)来构建 String。如下删除字符串中的空格举例:

      let spacey = "man hat tan";
      let spaceless: String = spacey.chars().filter(|c| !c.is_whitespcae()).collect();
      assert_eq!(spaceless, "manhattan");
      
      • 1
      • 2
      • 3
    • slice.to_owned():将 slice 的副本作为一个全新分配的 String 返回。&str 类型不能实现 Clone,可以通过此方法达到克隆的效果。

    15.3.2 - 简单检查 —— 从字符串切片获得基本信息

    • slice.len():返回以字节计算的 slice 的长度。

    • slice.is_empyt():在 slice.len() == 0 时返回 true

    • slice[range]:返回借用 slice 中指定部分的切片。

    • 不能像 slice[i] 这样的格式取得一个位置索引的字符串切片。而是需要基于切片产生一个 chars 迭代器,让迭代器解析出相应字符串的 UTF-8:

      let par = "rust he";
      assert_eq!(par[6..].chars().next(), Some('e'));
      
      • 1
      • 2
    • slice.split_at(i):返回从 slice 借用的两个共享切片的元组,slice[..i]slice[i..]

    • slice.is_char_boundary(i):在 i 为字符边界上时返回 true

    • 切片可以比较相等、顺序和散列。

    15.3.3 - 向 String 追加和插入文本

    • string.push(ch):把字符 ch 追加到字符串末尾。

    • string.push_str(slice):追加 slice 的全部内容。

    • string.extend(iter):将迭代器 iter 生成的所有项追加到字符串。迭代器可以生成 charstrString 值。

    • string.insert(i, ch):在字节偏移值 i 的位置,向字符串中插入字符 chi 之后的所有字符向后移一位。

    • string.insert_str(i, slice):在字节偏移值 i 的位置,向字符串中插入 slice 的全部内容。

    • String 实现了 std::fmt::Write,因此可以使用 write!writeln! 宏,给 String 追加格式化文本。它们的返回值类型是 Result。需要在结尾添加 ? 操作符来处理错误。

      use std::fmt::Write;
      
      let mut letter = String::new();
      writeln!(letter, "Whose {} these are I think I know", "rustabagas")?;
      
      • 1
      • 2
      • 3
      • 4
    • + 操作符:在操作数为字符串时,可以用于拼接字符串操作。

    15.3.4 - 删除文本

    • string.shrink_to_fit():在删除字符串内容后,可以用来释放内存。
    • string.clear():将字符串重置为空字符。
    • string.truncate(n):丢弃字节偏移值 n 之后的所有字符。
    • string.pop():从字符串中删除最后一个字符,并以 Option<char> 作为返回值。
    • string.remove(i):从字符串中删除字节偏移值 i 所在的字符,并返回该字符,后面的字符会向前移动。
    • string.drain(range):根据戈丁字节索引的返回,返回迭代器,并且在迭代器被清除时删除相应字符。

    15.3.5 - 搜索与迭代的约定

    Rust 标准库与搜索和迭代文本相关的函数,遵循下述命名约定:

    • 大多数操作可以从左向右处理文本;
      • 名字以 r 开头的操作从右向左处理,如 rsplitsplit 的相反操作。
      • 改变处理方向,不仅会影响产生值的顺序,也会影响值本身。
    • 迭代器的名字如果以 n 结尾,就表示会对自己限定匹配的次数。
    • 迭代器的名字如果以_indices 结尾,表示会产生它们在切片中的字节偏移量,以及通常可迭代的值。

    15.3.6 - 搜索文本的模式

    • 模式(pattern):

      • 当标准库函数需要搜索(search)、匹配(match)、分割(split)或修剪(trim)文本时,会接收不同类型的参数,来表示要查找的内容。这些类型被称为模式。
      • 模式是可以实现 std::str::Pattern 特型的任何类型。
    • 标准库支持的 4 种主要模式:

      • char 作为模式用于匹配字符;

      • String&str&&str 作为模式,用于匹配等于模式的子字符串。

      • FnMut(char) -> bool 闭包作为模式,用于匹配闭包返回 true 的一个字符。

      • &[char] 作为模式,表示 char 值的切片,用于匹配出现在列表中的任一字符。

        let code = "\t    funcation noodle() {  ";
        assert_eq!(code.trim_left_matchs(&[' ', 't'] as &[char]),
            "function noodle() {  ");
        
        • 1
        • 2
        • 3
        • as 操作符,可以将字符数组字面量转换为 &[char]
        • &[char; n] 表示固定大小 n 的数组类型,不是模式类型。
        • &[' ', 't'] as &[char] 也可写作 &\[' ', '\t'][..]

    15.3.7 - 搜索与替换

    • slice.contains(pattern):在 slice 包含与 pattern 匹配的内容时返回 true

    • slice.starts_with(pattern)slice.ends_with(pattern):在 slice 的初始或最终文本与 pattern 匹配时返回 true

      assert!("2017".starts_with(char::is_numeric));
      
      • 1
    • slice.find(pattern)slice.rfind(pattern):在 slice 包含匹配 pattern 的内容时,返回 Some(i)i 是匹配项的字节偏移量。

    • slice.replace(pattern, replacement):返回以 replacement 替换所有 pattern 的内容之后得到的新 String

    • slice.replacen(pattern, replacement, n):功能与上同,但是最多替换前 n 个匹配项。

    15.3.8 - 迭代文本

    • slice.chars():基于 slice 的字符返回一个迭代器。

    • slice.char_indices():基于 slice 的字符及它们的字节偏移量返回一个迭代器。

      assert_eq!("elan".char_indices().collect::<Vec<_>>(),
          vec![(0, 'e'), (2, 'l'), (3, 'a'), (4, 'n')]);
      
      • 1
      • 2
    • slice.bytes():基于 slice 中的个别字节返回一个迭代器,暴露 UTF-8 编码。

      assert_eq!("elan".bytes().collect::<Vec<_>>(), vec![b'e', b'l', b'a', b'n']);
      
      • 1
    • slice.lines():基于 slice 中的文本行,返回一个迭代器。每行的终止符是 \n\r\n。这个迭代器所产生的值是从 slice 借用的 &str。并且,所产生的值不包含终止符。

    • slice.split(pattern):基于按照 pattern 分割 slice 得到的部分返回一个迭代器。两个相邻的匹配或者与 slice 开头、结尾的匹配都会返回空字符串。

    • slice.rsplit(pattern):功能同上,但是会从后向前扫描和匹配 slice

    • slice.split_terminator(pattern)slice.rsplit_terminator(pattern):功能与上两个方法相同,不过 pattern 被当成终止符,而不是分隔符。如果 pattern 恰好匹配 slice 的两头,那么迭代器不会生成表示匹配与切片两头之间空字符串的空切片。

    • slice.splitn(n, pattern)slice.rsplitn(n, pattern):与 splitrsplit 类似,但是最多把字符串分割成 n 个切片,从 pattern 的第 1 次匹配到第 n-1 次匹配。

    • slice.split_whitespace():基于空白 slice 分隔的部分返回一个迭代器。连续多个空白符作为一个分隔符。末尾的空白会被忽略。此处的空白与 char::is_whitespace 中的描述一致。

    • slice.matches(pattern)slice.rmatches(pattern):基于 patternslice 中找到的匹配项返回一个迭代器。

    • slice.match_indices(pattern)slice.rmatch_indices(pattern):与上同。不过产生的值是 (offset, match) 对,其中 offset 是匹配开始位置的字节偏移量,match 是匹配的切片。

    15.3.9 - 修剪

    • 修剪(trim)字符串:
      • 从字符串的开头和末尾去掉内容(通常是空白符)。
      • 常用于清理文件中读到的带缩进的文本,或者一行末尾意外带着的空白符,以便让结果更清晰
    • slice.trim():返回 slice 的子切片,不包含切片开头和末尾的空白符。
    • slice.trim_left():只忽略切片开头的空白符。
    • slice.trim_right():只忽略切片末尾的空白符。
    • slice.trim_matches(pattern):返回 slice 的子切片,不包含切片开头和末尾匹配 pattern 的内容。
    • slice.trim_left_match(pattern):仅对切片开头的内容执行匹配操作。
    • slice.trim_right_match(pattern):仅对切片末尾的内容执行匹配操作。

    15.3.10 - 字符串大小写转换

    • slice.to_uppercase():返回新匹配的字符串,其保存着转换为大写之后的 slice 文本。结果的长度不一定与 slice 相同。
    • slice.to_lowercase():与上面类似,但是转换的是小写之后的 slice 文本。

    15.3.11 - 从字符解析出其他类型

    • 所有常见的类型都实现了 std::str::FromStr 特型,拥有从字符串切片中解析值的标准方法。

      pub trait FromStr: Sized {
          type Err;
          fn from_str(s: &str) -> Result<Self, self::Err>;
      }
      
      • 1
      • 2
      • 3
      • 4
    • 用于存储 IPv4 或 IPv6 互联网地址的枚举(enum)类型 std::net::IpAddr 也实现了 FromStr

      use std::net::IpAddr;
      let address = IpAddr::from_str("fe80::0000:3ea9:f4ff:fe34:7a50")?;
      assert_eq!(address, IpAddr::from([0xfe80, 0, 0, 0, 0x3ea9, 0xf4ff, 0xfe34, 0x7a50]));
      
      • 1
      • 2
      • 3
    • 字符串切片的 parse 方法,可以将切片解析为任何类型。在调用时,需要写出给定的类型。

      let address = "fe80::0000:3ea9:f4ff:fe34:7a50".parse::<IpAddr>()?;
      
      • 1

    15.3.12 - 将其他类型转换为字符串

    • 实现了 std::fmt::Display 特型的打印类型,可以在 format! 宏中使用 {} 格式说明符。

      • 对于智能指针类型,如果 T 实现了 Display,则 Box<T>Rc<T>Arc<T> 也会实现:它们所打印出来的形式就是它们引用目标的形式。
      • VecHashMap 等容器没有实现 Display
    • 如果一个类型实现了 Display,则标准库会自动为其实现 std::str::ToString 特型:

      • 这个特型唯一的方法 to_string
      • 对于自定义类型建议实现 Display,而不是 ToString
    • 标准库的公共类型都实现了 std::fmt::Debug 特型:

      • 可以接收一个值并将其格式化为字符串形式,用于程序的调试。

      • Debug 生成的字符串,可以借助 format! 宏的 {:?} 格式说明符打印。

      • 自定义类型也可以实现 Debug,建议使用派生特型:

        #[derive(Copy, Clone, Debug)]
        struct Complex {
            r: f64,
            i: f64
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5

    15.3.13 - 作为其他类文本类型借用 —— 切片的借用

    • 切片和 String 实现了 AsRef<str>AsRef<[u8]>AsRef<Path>AsRef<OsStr>:使用这些特型作为自己参数类型的绑定,可以直接将切片或字符串传递给它们,及时这些函数需要的是其他类型。
    • 切片和 String 也实现了 std::borrow::Borrow<Str> 特型:HashMapBTreeMap 使用 BorrowString 可以作为表中的键。

    15.3.14 - 访问 UTF-8 格式的文本(字节表示的文本)

    • slice.as_bytes():借用 slice 的字节作为 &[u8]。获取的字节必须是格式良好的 UTF-8。
    • string.into_bytes():取得 String 的所有权并按值返回这个字符串字节的 Vec<u8>。获取的字节可以不是格式良好的 UTF-8。

    15.3.15 - 从 UTF-8 数据产生文本

    • str::from_utf8(byte_slice):接收一个 &[u8] 字节切片,返回一个 Result:如果 byte_slice 包含格式良好的 UTF-8,则返回 Ok(&str),否则返回错误。

    • String::from_utf8(vec):基于传入的 Vec<u8> 值构建一个字符串。

      • 如果 vec 保存着格式良好的 UTF-8,from_utf8 就返回 Ok(string),其中 string 就是取得 vec 所有权,并将其作为缓冲的字符串。

      • 如果字节不是格式良好的 UTF-8,则返回 Err(e),其中 e 是一个 FromUtf8Error 错误值。此时若调用 e.into_bytes() 则会得到原始的向量 vec,即可实现转换失败而不丢失原值。

        let good_utf8: Vec<u8> = vec![0xe9, 0x8c, 0x86];
        
        let bad_utf8: Vec<u8> = vec![0x9f, 0xf0, 0xa6, 0x80];
        let result = String::from_utf8(bad_utf8);  // 失败
        assert!(result.is_err());
        assert_eq!(result.unwrap_err().into_bytes(),
            vec![0x9f, 0xf0, 0xa6, 0x80]);
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
    • String::from_utf8_lossy(byte_slice):基于字节的共享切片 &[u8] 构建一个 String&str

    • String::from_utf8_unchecked:将 Vec<>u8 包装为一个 String 并返回它,要求必须是格式良好的 UTF-8。只能在 unsafe 块中使用。

    • str::from_utf8_unchecked:接收一个 &[u8],并将其返回为一个 &str,同样不会检查字节的格式是不是格式良好的 UTF-8。同样只能在 unsafe 块中使用。

    15.3.16 - 阻止分配

    fn get_name() -> String {
        std::env::var("USER").unwrap_or("whoever you are".to_string())
    }
    println!("Greetings, {}!", get_name());
    
    • 1
    • 2
    • 3
    • 4
    • 上述例子实现了问候用户的程序,在 Unix 上可以实现,但是在 Windows 上用户名用的是 USERNAME 字段,拿不到系统的用户名。

    • std::env::var 函数返回 String。而 get_name 可能返回所有型的 String,也可能是 &'static str'

    • 所以,可以使用 std::borrow::Cow(Clone-on-write 写时克隆)类型实现,可以保存所有型数据,也可以保存借用的数据。

      use std::borrow::Cow;
      
      fn get_name() -> Cow<'static, str> {
          std::env::var("USER")
              .map(|v| Cow::Owned(v))
              .unwrap_or(Cow::Borrowed("whoever you are"))
      }
      println!("Greetings, {}!", get_name());
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 如果成功读取 USER 环境变量,则 map 将得到的字符串作为 Cow::Owned 返回。
      • 如果失败,unwrap_or 将其静态的 &str 作为 Cow::Borrowed 返回。
      • 只要 T 实现了 std::fmt::Display 特型,那么 Cow<'a, T> 会得到与显示 T 一样的结果。
    • std::borrow::Cow 常用于可能需要,也可能不需要修改借用的某个文本时。

      • 在不需要修改的时候,可以继续借用它;

      • Cowto_mut 方法,确保 CowCow::Owned,必要时会应用值的 ToOwned 实现,然后返回这个值的可修改引用。

        fn get_title() -> Option<&'static str> { ... }
        
        let mut name = get_name();
        if let Some(title) = get_title() {
            name.to_mut().push_str(", ");
            name.to_mut().push_str(title);
        }
        println!("Greetrings, {}!", name);
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
      • 同时也可以在必要时才会分配内存。

    • 标准库为 Cow<'a, str> 提供了对字符串的特殊支持。如提供了来自 String&strFromInto 转换,因此上述 get_name 可以简写为:

      fn get_name() -> Cow<'static, str> {
          std::env::var("USER")
              .map(|v| v.into())
              .unwrap_or("whoever you are".into())
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
    • Cow<'a, str> 也实现了 std::ops::Addstd::ops::AddAssign 字符串重载,因此 get_title() 判断可以简写为:

      if let Some(title) = get_title() {
          name += ", ";
          name += title;
      }
      
      • 1
      • 2
      • 3
      • 4
    • 由于 String 可以作为 write! 宏的目标,因此上述代码也等效于:

      use std::fmt::Write;
      
      if let Some(title) = get_title() {
          write!(name.to_mut(), ", {}", title).unwrap();
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
    • 不是所有 Cow<..., str> 都必须是'static 生命期,在需要复制之前,可以一直使用 Cow 借用之前计算的文本。

    15.3.17 - 字符串作为泛型集合

    • String 实现了std::default::Default std::iter::Extend::default

      • default 返回一个空字符串。
      • extend 可以向一个字符串末尾追加字符、字符串切片或字符串。
    • &str类型也是实现了Default

      • 返回一个空切片。
      • 常用于在某些边界的情况。比如为包含字符串切片的结构派生 Default

    详见《Rust 程序设计》(吉姆 - 布兰迪、贾森 - 奥伦多夫著,李松峰译)第十七章
    原文地址

  • 相关阅读:
    母婴店怎么在微信小程序卖东西
    java毕业设计电子存证系统(附源码、数据库)
    微信小程序
    html+css+js制作LOL官网,web前端大作业(3个页面+模拟登录+链接)
    《网络安全笔记》第二章:Windows基础命令
    字符串与内存操作函数详解与模拟实现
    mysql面试内容点
    迅为RK3399开发板Ubuntu系统交叉编译Qt-命令行交叉编译Qt工程
    JS之简易deepCopy(简介递归)
    vue 页面监听vuex state值的变化
  • 原文地址:https://blog.csdn.net/feiyanaffection/article/details/125575125