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 用法。
如果觉得文章对您有所帮助,可以请囊中羞涩的作者喝杯柠檬水,万分感谢,愿每一个来到这里的人都生活愉快,幸福美满。
微信赞赏
支付宝赞赏