Rust 字符和字符串支持的 API

楔子

本次我们来看看字符类型和字符串支持的一些方法。

字符所拥有的方法

Rust 的字符使用单引号,比如 'a',但需要说明的是,C 里面的 'a' 本质上就是一个 u8 整数,而 Rust 则不是。在 Rust 里面,'a' 代表的是单个 Unicode 字符,b'a' 才是 u8 整数。

fn main() {
    println!("{} {}", b'a', 'a');  // 97 a
    // b'憨' 是不合法的,u8 所能表示的范围不超过 255
    println!("{} {}", b'a', '憨');  // 97 憨
    // Rust 的 char 占 4 字节(底层使用 u32),所以能存储任意字符
    println!("{} {}", '😁', '😂');  // 😁 😂
    // 我们也可以使用 \u + 十六进制码点
    println!("{}", '\u{1f62d}');  // 😭
}

下面来看看 char 类型数据支持的方法。

from_u32:基于 u32 整数创建 char 类型字符,因为 char 的底层使用的就是 u32

fn main() {
    let c = '💯';
    println!("{}", c as u32);  // 128175
    // 转成 16 进制,返回 String
    // 或者也可以使用 to_string_radix(进制) 转成指定进制的格式
    let hex = format!("{:x}", 128175);
    println!("{}", hex);  // 1f4af
    println!("{}", '\u{1f4af}');  // 💯

    // 如果有了 unicode 码点,那么可以通过 from_u32 直接创建
    // 返回的是 Option<char>,因为并非所有 u32 都是有效的字符
    // 如果整数不对的话,就会创建失败
    println!("{}", char::from_u32(128175).unwrap())  // 💯
}

如果创建的是 ASCII 字符,那么还可以通过下面两种方式。

fn main() {
    // 仅限 ASCII 字符
    println!("{} {}", char::from(97), 97 as char);  // a a
}

is_digit:判断一个字符是否是数字

fn main() {
    // 对于十进制来讲,9 是一个数字,但对二进制而言就不是了
    println!("{}", '9'.is_digit(10));  // true
    println!("{}", '9'.is_digit(2));  // false
    // 对于十进制来讲,f 不是一个数字,但对十六进制来说就是数字
    println!("{}", 'f'.is_digit(10));  // false
    println!("{}", 'f'.is_digit(16));  // true
}

len_utf8:或者字符占的字节数

fn main() {
    println!("{}", 'a'.len_utf8());  // 1
    println!("{}", '憨'.len_utf8());  // 3
    println!("{}", '😭'.len_utf8());  // 4
}

encode_utf8:将一个字符转成 u8 数组

fn main() {

    let c = '夜';
    let mut buf = [0u8; 4];
    // 将编码后的内容拷贝到 buf 中,这个过程会修改 buf
    // 所以 buf 要可变,然后传递可变引用
    c.encode_utf8(&mut buf);
    println!("{:?}", &buf[.. c.len_utf8()]);  // [229, 164, 156]
}

is_lowercase:判断字符是否为小写

fn main() {
    println!("{} {}", 'a'.is_lowercase(), 'A'.is_lowercase());  // true false
    // 非 ASCII 字符一律返回 false
    println!("{} {}", '憨'.is_lowercase(), '😁'.is_lowercase());  // false false

    // 除了 is_lowercase 之外,还有 is_uppercase(是否为大写)
    // 非 ASCII 字符同样一律返回 false
    println!("{} {}", 'a'.is_uppercase(), 'A'.is_uppercase());  // false true
    println!("{} {}", '憨'.is_uppercase(), '😁'.is_uppercase());  // false false
}

is_whitespace:判断字符是否为空白字符

fn main() {
    println!("{} {}", ' '.is_whitespace(), '\n'.is_whitespace());  // true true
}

is_numeric:判断字符是否为具有数值属性,注意它和 is_digit 的区别

fn main() {
    println!("{}", '①'.is_digit(16));  // false
    println!("{}", '①'.is_numeric());  // true
    println!("{}", '¾'.is_numeric());  // true
    println!("{}", '⑩'.is_numeric());  // true
}

is_alphabetic:判断字符是否为具有字符属性,一般来说,只要不是 emoji 或者 ① 这种,都为 True

fn main() {
    println!("{}", '憨'.is_alphabetic());  // true
    println!("{}", '😭'.is_alphabetic());  // false
}

还有一个 is_alphanumeric,如果字符满足 is_numeric() 或 is_alphabetic() 为真,那么该结果也为真。

is_ascii:判断字符是否为 ASCII 字符

fn main() {
    println!("{}", 'A'.is_ascii());  // true
    println!("{}", '憨'.is_ascii());  // false
}

to_lowercase:将一个字符转成小写

fn main() {
    println!("{}", 'A'.to_lowercase());  // a
    // 对于非 ASCII 字符,转化的结果还是它本身
    println!("{}", '憨'.to_lowercase());  // 憨
    println!("{}", '😭'.to_lowercase());  // 😭
    
    // 同理还有 to_uppercase 转大写
}

to_digit:将一个字符转成数字

fn main() {
    // 注意:这是将字符转成数字,不是获取它的 Unicode 码点
    println!("{}", '9'.to_digit(10).unwrap());  // 9
    println!("{}", 'a'.to_digit(16).unwrap());  // 10
    // 如果获取字符的 Unicode 码点,那么应该使用 as
    println!("{}", '9' as u32);  // 57
    println!("{}", 'a' as i32);  // 97
    // 如果是无符号整数,那么还可以使用 from,不过使用 as 是最方便的
    println!("{}", u64::from('a'));  // 97
}

以上就是字符的一些方法。

字符串所拥有的方法

看完了字符,再看看字符串,在所有语言中,Rust 的字符串是最复杂的。

创建一个字符串

fn main() {
    // 基于整数创建字符串
    let s1: String = 123.to_string();
    // 基于浮点数创建字符串
    let s2: String = 3.14.to_string();
    // 基于 char 创建字符串
    let s3: String = 'A'.to_string();
    // 基于字符串字面量创建字符串
    let s4: String = "Hello World".to_string();

    // 以上是其它结构转成字符串,非常简单,直接调用 to_string 即可
    // 但是字符串如何转回去呢?对于整数来说,可以使用 from_str_radix
    println!("{}", i32::from_str_radix(&s1, 10).unwrap() == 123);  // true
    // 或者还可以调用字符串的 parse 方法,由于可能解析失败,因此返回值类型是 Result<转换后的类型, 错误>
    // 这里必须指定类型,不然 Rust 不知道你想将字符串解析成哪一种类型
    let num: Result<i32, _> = s1.parse();
    println!("{}", num.unwrap() == 123);  // true
    // 或者还可以这么做,s1.parse::<u32> 表示解析后的类型是 Result<u32, _>
    println!("{}", s1.parse::<u32>().unwrap() == 123);  // true

    // 解析浮点数也是如此
    println!("{}", s2.parse::<f64>().unwrap() == 3.14);  // true

    // 解析成 char
    println!("{}", s3.parse::<char>().unwrap() == 'A');  // true

    // 解析成字符串字面量,字符串字面量本质上就是字符串切片,类型为 &str
    println!("{}", &s4[..] == "Hello World");  // true
}

len:获取字符串的长度

fn main() {
    let s1: String = 123.to_string();
    let s2: String = 3.14.to_string();
    let s3: String = 'A'.to_string();
    let s4: String = "Hello World".to_string();

    // 调用 len 方法时传递的是引用,所以不会剥夺所有权
    println!("{} {} {} {}", s1.len(), s2.len(), s3.len(), s4.len());  // 3 4 1 11
    // 字符串字面量也可以调用,因为这些方法接收的是 &str
    println!("{}", "Hello World".len())  // 11
}

is_empty:判断字符串是否为空

fn main() {
    println!("{} {}", "".to_string().is_empty(), "".is_empty());  // true true
    println!("{} {}", " ".to_string().is_empty(), " ".is_empty());  // false false
}

as_bytes:基于字符串切片创建 u8 数组切片

fn main() {
    // 转成 u8 数组切片后,总长度为 6 字节
    let bytes: &[u8] = "夜ser".as_bytes();
    println!("{:?}", bytes);  // [229, 164, 156, 115, 101, 114]
    // 也可以基于 u8 数组切片生成字符串,返回 Result<String, FromUtf8Error>
    // 但需要注意的是,from_utf8 接收的是动态数组
    let s = String::from_utf8(Vec::from(bytes));
    println!("{}", s.unwrap());  // 夜ser
}

chars:基于字符串切片创建 char 字符迭代器

fn main() {
    let s = "夜ser";
    // s.chars.count() 统计出来的是字符个数,s.len() 是字节的个数
    println!("{} {}", s.len(), s.chars().count());  // 6 4
    let mut chars = s.chars();
    // 遍历时,chars 内部结构会发生变化,所以要声明为 mut
    println!("{:?}", chars.next());  // Some('夜')
    println!("{:?}", chars.next());  // Some('s')
    println!("{:?}", chars.next());  // Some('e')
    println!("{:?}", chars.next());  // Some('r')
    println!("{:?}", chars.next());  // None
    println!("{:?}", chars.next());  // None
}

除了 chars 之外,还有一个 char_indices,遍历它的时候会得到一个元组。

fn main() {
    let s = "夜ser";
    let mut chars = s.char_indices();
    println!("{:?}", chars.next());
    println!("{:?}", chars.next());
    loop {
        if let Some((idx, c)) = chars.next() {
            println!("{:?} {}", idx, c);
        } else {
            break
        }
    }
    /*
    Some((0, '夜'))
    Some((3, 's'))
    4 e
    5 r
    */
}

注意:它的索引还是以字节为单位返回的。

当然啦,我们也可以采用 for 循环进行遍历。

fn main() {
    let s = "夜ser";
    let mut chars = s.char_indices();
    for c in chars {
        println!("{:?}", c);
    }
    /*
    (0, '夜')
    (3, 's')
    (4, 'e')
    (5, 'r')
    */
}

可能有人觉得这么做不太方便,能不能通过索引的方式进行访问呢?

fn main() {
    let s = "夜ser";
    // s.chars() 表示创建字符迭代器
    // collect 表示将字符迭代器的字符收集起来,然后创建 Vec<char> 数组
    let vector: Vec<char> = s.chars().collect();
    println!("{}", vector[0]);  // 夜
    println!("{}", vector[1]);  // s

    // 同样的,基于 vector 创建迭代器,然后将里面的元素收集起来,创建字符串
    // 所以 collect 之后,Rust 不知道你要创建啥,需要通过类型指定
    // 此时我们就以字符为单位,实现了字符串的截取
    // 如果是 s[0..2],那么是有问题的,因为这是以字节为单位,此时会得到 '夜' 的前两个字节
    println!("{}", vector[..2].iter().collect::<String>());  // 夜s
}

还是比较简单的。

split_whitespace:以空白为分隔符,对字符串进行分隔

fn main() {
    let s = "Hello Cruel\n\nWorld";
    // 返回一个迭代器,调用 next 进行遍历
    // 因为调用 next 会更改迭代器的内部状态,所以要声明为可变
    let mut s_split = s.split_whitespace();
    println!("{:?}", s_split.next());  // Some("Hello")
    println!("{:?}", s_split.next());  // Some("Cruel")
    println!("{:?}", s_split.next());  // Some("World")
    println!("{:?}", s_split.next());  // None
    println!("{:?}", s_split.next());  // None

    // 或者也可以调用 collect 将迭代器的元素收集起来,构建数组,里面的元素都是字符串切片
    let vector = s.split_whitespace().collect::<Vec<_>>();
    println!("{:?}", vector);  // ["Hello", "Cruel", "World"] 
}

即使字符串有连续多个空白也没关系,所有的空白符都会被扔掉。

lines:和 split_whitespace 用法一样,但它以 \r\n 和 \n 对字符串进行分隔

fn main() {
    let s = "Hello   \r\r\nCruel\n\nWorld";
    // 或者也可以调用 collect 将迭代器的元素收集起来,构建数组,里面的元素都是字符串切片
    let vector = s.lines().collect::<Vec<_>>();
    println!("{:?}", vector);  // ["Hello   \r", "Cruel", "", "World"]
}

contains:判断字符串是否包含某个子串

fn main() {
    let s = "古明地觉";
    println!("{:?}", s.contains("明地"));  // true
}

starts_with / ends_with:判断字符串是否以某个子串开头 / 结尾

fn main() {
    let s = "古明地觉";
    println!("{:?}", s.starts_with("古"));  // true
    println!("{:?}", s.ends_with("觉"));  // true
}

split:将字符串按照某个子串进行分隔

fn main() {
    let s = "hello cruel world";
    let vector = s.split(" ").collect::<Vec<_>>();
    println!("{:?}", vector);  // ["hello", "cruel", "world"]

    let vector = "我是我是我".split("是").collect::<Vec<_>>();
    println!("{:?}", vector);  // ["我", "我", "我"]

    // 分隔符不存在,则返回本身
    let vector = "我是我是我".split("你").collect::<Vec<_>>();
    println!("{:?}", vector);  // ["我是我是我"]
}

Rust 的 split 比较强悍,它还可以指定一个函数。

fn main() {
    // 我希望得到 ["a", "b", "c", "d", "e"],但它们之间的分隔符长得都不一样
    let s = "a|b-c(d[e";
    // 只要 "|-([" 里面包含指定的字符,那么就进行分隔
    let vector = s.split(|sub| "|-([".contains(sub)).collect::<Vec<_>>();
    println!("{:?}", vector);  // ["a", "b", "c", "d", "e"] 
}

rsplit:和 split 用法一致,但它是从右往左分隔

fn main() {
    let s = "hello cruel world";
    let vector = s.split(" ").collect::<Vec<_>>();
    println!("{:?}", vector);  // ["hello", "cruel", "world"]
    let vector = s.rsplit(" ").collect::<Vec<_>>();
    println!("{:?}", vector);  // ["world", "cruel", "hello"] 
}

除了 split 和 rsplit 之外,还有 splitn 和 rsplitn,但这个 n 不是最大分割次数,而是分隔后最多返回多少项。

fn main() {
    let s = "hello cruel world";
    // 最多返回两项
    let vector = s.splitn(2, " ").collect::<Vec<_>>();
    println!("{:?}", vector);  // ["hello", "cruel world"]
    let vector = s.rsplitn(2, " ").collect::<Vec<_>>();
    println!("{:?}", vector);  // ["world", "hello cruel"] 
}

trim:去除字符串首尾的空白符

fn main() {
    let s = "   \n\r我是谁\r\n  ";
    println!("{}", s.trim());  // 我是谁
    // 去除首部的空白符
    println!("{}", s.trim_start());
    // 去除尾部的空白符
    println!("{}", s.trim_end());
}

trim_matches:去除字符串首尾的指定字符

fn main() {
    let s = "aaab我是谁baaa";
    println!("{}", s.trim_matches('a'));  // b我是谁b
    println!("{}", s.trim_start_matches('a'));  // b我是谁baaa
    println!("{}", s.trim_end_matches('a'));  // aaab我是谁b

    // 也可以指定一个函数
    println!("{}", s.trim_matches(|c| c == 'a' || c == 'b'));  // 我是谁
    // 或者指定一个数组切片
    println!("{}", s.trim_matches(&['a', 'b'][..]))  // 我是谁
}

is_ascii:判断字符串是否全部由 ASCII 字符组成

fn main() {
    println!("{} {}", "abc".is_ascii(), "abc憨".is_ascii());  // true false
}

replace:对字符串的指定部分进行替换,返回一个 String

fn main() {
    let date = "2020::01::01".to_string();
    println!("{}", date.replace("::", "-"));  // 2020-01-01
    // 如果替换的部分不存在,那么内容保持不变
    println!("{}", date.replace("xx", ""));  // 2020::01::01
    // data 并没有失去所有权,因为传递的是引用
    println!("{}", date);  // 2020::01::01

    // 除了 replace 还有 replacen,可以指定替换的最大个数
    println!("{}", date.replacen("::", "-", 1));
}

对字符串进行大小写转换,返回一个 String

fn main() {
    let s = "Hello 古明地觉";
    println!("{}", s.to_lowercase());  // hello 古明地觉
    println!("{}", s.to_uppercase());  // HELLO 古明地觉
}

repeat:将一个字符串重复 N 次,返回一个 String

fn main() {
    let s = "Hello";
    println!("{}", s.repeat(3));  // HelloHelloHello
}

以上就是字符和字符串相关的一些 API 用法。

posted @ 2023-10-23 19:00  古明地盆  阅读(289)  评论(0编辑  收藏  举报