字符串字面量和全局变量、static变量一样位于程序运行之后虚拟地址空间中的代码区。
关于虚拟地址空间,之前有一小节详细分析过:Rust 06: 变量分配在堆还是栈上 + 虚拟地址空间
const g_array: [i32; 5] = [10; 5];
static G_VAR: i32 = 1000;
#[test]
fn test09_string() {
let s: &str = "test string";
//字符串字面量,位于代码区的ROData段
println!("&str: {:p}", s);//&str: 0x7ff77e4c6b88
println!("{:p}", &g_array);//位于data段:0x7ff6c5fc6bb8
println!("{:p}", &G_VAR);//位于data段:0x7ff77e4c6200
}
字符串字面量位于代码区的ROData段,是只读(Read Only)的,在Rust代码中字符串字面量会被处理成字符串切片类型,即&str
。如果想要对字符串进行追加等操作,必须先将ROData段的字符串字面量复制一份到堆上,构造一个String
类型出来。
String内部其实是一个Vec,是一个可变长度的类型,末尾可以追加字符。String
类型和Vec
类型占用24个字节。
pub struct String {
vec: Vec<u8>,
}
println!("size: {}", std::mem::size_of::<Vec<u8>>());//24
println!("size: {}", std::mem::size_of::<String>());//24
String对象的内存结构,也就是Vec
的内存结构,为 8(ptr) + 8(capacity) + 8(length)
。
从字符串字面量构造出一个堆上的String有多种方式:
"xxx".to_owned()
方法内部掉clone(),将字符串字面量从ROData区复制一份到堆上,并返回堆上数据的所有权;String::from("xxx")
内部实际调用的是"xxx".to_owned();"xxx".to_string()
内部实际调用的是String::from(“xxx”);clone()
:let s1: String = "Hello Rust!".to_owned();
let s2: String = String::from("Hello Rust!");
let s3: String = "Hello Rust!".to_string();
assert_eq!(s1,s2);
assert_eq!(s2,s3);
assert_eq!(s1,s3);
println!("s1: {:p}", &s1);//s1: 0x32a62fec08
println!("s2: {:p}", &s2);//s2: 0x32a62fec20
println!("s3: {:p}", &s3);//s3: 0x32a62fec38
可以看到,&s1、&s2、&s3是3个虚拟地址比较小,互不相同的栈地址。但是,实际上他们最终指向的字符串内容本身是位于堆上。
根据以上信息,我们尝试画一下let s: String = String::from("Hello");
的内存布局:
let s1: String = "Hello Rust!".to_owned();
let s2: &String = &s1;
let s3: &str = "Hello Rust!";
let slice1: &str = &s1[0..5];//对String类型进行切片引用
let slice2: &str = &s2[0..5];//对&String类型进行切片引用
let slice3: &str = &s3[0..5];//对&str切片类型,进行切片引用
println!("{}", slice1);
println!("{}", slice2);
println!("{}", slice3);
还是先来做个小测试,验证一些想法:
// String内部其实是一个Vec,是一个可变长度的类型,末尾应该可以追加字符。
println!("size: {}", std::mem::size_of::<Vec<u8>>());//24
println!("size: {}", std::mem::size_of::<String>());//24
let s1: String = "Hello Rust!".to_owned();
let s2: String = String::from("Hello Rust!");
let s3: String = "Hello Rust!".to_string();
// 将占用的24个字节,当成3个usize取出
let raw1: [usize; 3] = unsafe { std::mem::transmute(s1) };
// raw1[0]是一个堆内存地址
// ptr: 0x2749d72c2d0, cap: 11, len: 11
println!("ptr: 0x{:x}, cap: {}, len: {}", raw1[0], raw1[1], raw1[2]);
let raw2: [usize; 3] = unsafe { std::mem::transmute(s2) };
// ptr: 0x2749d72c190, cap: 11, len: 11
println!("ptr: 0x{:x}, cap: {}, len: {}", raw2[0], raw2[1], raw2[2]);
// ptr: 0x2749d72c1d0, cap: 11, len: 11
let raw3: [usize; 3] = unsafe { std::mem::transmute(s3) };
println!("ptr: 0x{:x}, cap: {}, len: {}", raw3[0], raw3[1], raw3[2]);
借助unsafe
关键字和std::mem::transmute
可以将变量对应的值的内存暴露出来。对比虚拟内存地址,我们发现
s1、s2、s3实际上是位于堆上不同的位置。也就是说s1、s2、s3分别是从ROData区的"Hello Rust"字面量复制了一份放到堆内存上面。
从&[u8]
类型中构造一个str
出来。
pub fn from_utf8_lossy(v: &[u8]) -> Cow<'_, str> { ... }
from_utf8_lossy()
能够处理输入的u8
序列中包含非法utf8字符
的情况:
utf8字符
),在结果中用U+FFFD
(�字符)替代。此时,返回结果是一个具有所有权的str
类型。u8
序列(构成的字符)的借用。from_utf8_lossy()
的返回结果是一个 Cow
(Clone-On-Write
)枚举类型。// 转换成功
let list = vec![b'o', b'k', b':', b'?', 0x44];
let str1 = String::from_utf8_lossy(&list);
println!("str1={:?}", str1.into_owned());//str1="ok:?D"
// 部分字符被�替代
let str2 = String::from_utf8_lossy(b"fail:\xF0\x90\x80").into_owned();
println!("str2={:?}", str2);//str2="fail:�"
从Vec
类型中构造一个String
出来。
pub fn from_utf8(vec: Vec<u8>) -> Result<String, FromUtf8Error> { ... }
utf8字符
序列,返回String
;FromUtf8Error
;Result
。use std::string::FromUtf8Error;
let list = vec![b'a', b'b', 0x3f, 0x44];
let str3: Result<String, FromUtf8Error> = String::from_utf8(list);
// 转换成功,通过unwrap()拿到Result中的合法值
println!("str3={:?}", str3.unwrap());//str3="ab?D"
// 转换失败
let str4: Result<String, FromUtf8Error> = String::from_utf8(vec![b'\xF0', b'\x90', b'\x80']);
println!("str4 is error: {:?}", str4.is_err());//str4 is error: true
//字符串转i32等数值类型
let x:i32 = "123".parse::<i32>().unwrap();
let x:u8 = "123".parse::<u8>().unwrap();
// 字符串转u8类型的切片
let x: &[u8] = "123".as_bytes();
// 数值类型转String
let s: String = 123i32.to_string();
let s: String = 123u8.to_string();
let s = ['a','b','c','d'].iter().collect::<String>();
println!("{s}");//abcd
let s = (vec!['a','b','c','d']).iter().collect::<String>();
println!("{s}");//abcd
let s = ["ab","cd"].concat();
println!("{s}");//abcd
let s = (vec!["ab","cd"]).concat();
println!("{s}");//abcd
let s = ["ab","cd"].join("-");
println!("{s}");//ab-cd
let s = (vec!["ab","cd"]).join("-");
println!("{s}");//ab-cd
is_alphabetic()
,判断字符是否是A-Za-z
。
is_ascii_digit()
,判断字符是否是0-9
。
let s: &str = "abc123";
for c in s.chars() {
if c.is_alphabetic() {
println!("{} 是字母", c);
} else if c.is_ascii_digit() {
println!("{} 是数字", c);
}
}
// String的追加
let mut s1: String = String::from("Hello ");
s1.push_str("Rust!");
s1.push('!');
s1.write_char('!');
s1 += " Hello Rust!";
println!("{}", s1);//Hello Rust!!! Hello Rust!
//repeat()
let x = "Hello".repeat(20);//HelloHello
// String的insert和remove方法
let mut ss = String::with_capacity(256);
ss.insert(0, 'f');
ss.insert(1, 'o');
ss.insert(2, 'o');
assert_eq!("foo", ss);
ss.remove(0);
ss.insert(0, 't');
assert_eq!("too", ss);
ss.clear();
assert_eq!("", ss);
// Chars是一个u8类型的迭代器,
// 遍历String中的u8字符
let chs: Chars = s1.chars();
for ch in chs {
println!("{}", ch);
}
let byts: &[u8] = s1.as_bytes();
for b in byts {
println!("{}", b);
}
let x = "Hello".to_ascii_lowercase();
println!("{x}");//hello
let y = "Hello".to_ascii_uppercase();
println!("{y}");//HELLO
let x = "hello".find("ll");//Some(2)
if "hello kitty".contains("kitty") {
println!("haha kitty");
}
let letters = "a b c d".split(" ").for_each(|x: &str| {
print!("{x} ");// a b c d
});
根据换行符(\n
或者\r\n
)进行分割。
let s = "abc\ndef\r\nghi";
for x in s.lines() {
println!("${}$", x);
}
根据1到多个ascii
空白字符’ ’ \t \n \r进行分割。
let s = "a b\nc\r\nd\re ";
for x in s.split_ascii_whitespace() {
println!("${}$", x);
}
根据1到多个Unicode
空白字符’ ’ \t \n \r \u00a0 \u2009进行分割。
let s = "a b\nc\r\nd\re\u{00a0}f\u{2009}";
for x in s.split_whitespace() {
println!("${}$", x);
}
assert_eq!(true, "lemon tree".starts_with("lemon"));
assert_eq!(true, "lemon tree".ends_with("tree"));
可以借助remove()
和insert()
,但是这2个方法都会导致字符串整体迁移,是O(n)复杂度的方法。如果字符串比较长,这样做代价很大。
// remove和insert都是O(n)复杂度的
// remove会导致删除位置后面的所有字符整体前移1个位置
// insert会导致插入位置后面的所有字符整体后移1个位置
let mut s1:String = String::from("Hello");
s1.remove(0);
s1.insert(0, 'X');
assert_eq!("Xello", s1);
对于追求极致效率的rust而言,提供了unsafe
关键字。这里有一个O(1)复杂度的unsafe
方法:
// 如何高效修改String中的一个字符
let mut s1:String = String::from("Hello");
unsafe {
let s1_bytes: &mut [u8] = s1.as_bytes_mut();
s1_bytes[0] = b'X';
println!("s1 new={}", s1);//s1 new=Xello
}
为了调用更加方便,我们完全可以自定义一个根据位置修改字符的trait
,并且为String
类型实现这个trait
。
trait SetByIndex {
fn set_by_index(&mut self, idx: usize, c: u8);
}
impl SetByIndex for String {
fn set_by_index(&mut self, idx: usize, c: u8) {
if idx<0 || idx>=self.len() {
panic!("Index out of bounds: {}, expected: [0,{})", idx, self.len());
}
unsafe {
let _buf: &mut [u8] = self.as_bytes_mut();
_buf[idx] = c;
}
}
}
let mut ss = "Hello".to_owned();
ss.set_by_index(8, b'X');
println!("ss={}", ss);//ss=Xello