面试题答案
一键面试1. 内存布局
String
:String
是一个可增长的字符串类型,它在堆上分配内存。其内存布局包含三个部分:指向堆上数据的指针,记录字符串长度的usize
类型变量,以及记录容量(即当前分配的堆内存大小)的usize
类型变量。例如,当创建一个String
对象let s = String::from("hello");
,指针指向包含'h', 'e', 'l', 'l', 'o', '\0'
的堆内存区域,长度为5,容量至少为5(可能更大以适应后续增长)。- 由于
String
拥有堆上的数据,它负责数据的分配和释放,当String
对象离开作用域时,堆上的数据会被释放。
&str
:&str
是字符串切片,它是一个指向UTF - 8编码字符串数据的不可变引用。其内存布局很简单,就是一个指向字符串数据的指针和一个记录字符串长度的usize
类型变量。例如,当有一个字符串字面量let s: &str = "world";
,指针指向存储'w', 'o', 'r', 'l', 'd'
的内存区域(字符串字面量存储在程序的只读数据段),长度为5。&str
并不拥有它所指向的数据,数据的生命周期由其所有者决定。
2. 所有权机制
String
:String
拥有它所包含的字符串数据的所有权。当一个String
对象被赋值给另一个变量或者作为参数传递给函数时,所有权会发生转移。例如:
let s1 = String::from("rust"); let s2 = s1; // s1的所有权转移给s2,此时s1不再有效
- 当
String
对象离开作用域时,Rust的Drop trait会被自动调用,释放其在堆上分配的内存。
&str
:&str
不拥有数据的所有权,它只是借用数据。这意味着多个&str
切片可以同时指向同一个字符串数据,只要数据的所有者还存在。例如:
let s = String::from("hello"); let slice1: &str = &s; let slice2: &str = &s[0..3];
- 由于
&str
只是借用,所以不会影响数据所有者的生命周期,也不会导致数据的释放。
3. 实际编程中如何正确选择使用
- 使用
String
的场景:- 当需要对字符串进行修改、增长或需要拥有字符串数据的所有权时,应使用
String
。比如,从用户输入读取字符串,因为用户输入的长度是不确定的,需要一个可增长的字符串类型。
use std::io; let mut input = String::new(); io::stdin().read_line(&mut input).expect("Failed to read line");
- 当需要将字符串传递给函数,并期望函数对其进行修改或拥有所有权时,使用
String
。
- 当需要对字符串进行修改、增长或需要拥有字符串数据的所有权时,应使用
- 使用
&str
的场景:- 当只需要读取字符串内容,并且不需要拥有字符串数据的所有权时,使用
&str
。比如,函数参数如果只是用于读取字符串内容,使用&str
作为参数类型可以避免不必要的所有权转移和数据复制。
fn print_str(s: &str) { println!("The string is: {}", s); } let s = "example"; print_str(s);
- 当需要处理字符串字面量时,字符串字面量的类型就是
&str
,所以在这种情况下自然使用&str
。
- 当只需要读取字符串内容,并且不需要拥有字符串数据的所有权时,使用
4. String
到&str
的自动解引用
- 函数调用场景:
- 当函数参数类型为
&str
,而传递的是String
类型的变量时,会发生自动解引用。例如:
fn print_str(s: &str) { println!("The string is: {}", s); } let s = String::from("auto deref"); print_str(&s); // 这里`&s`(类型`&String`)自动解引用为`&str`
- 当函数参数类型为
- 方法调用场景:
- 当
String
对象调用一个接收&str
参数的方法时,会自动解引用。例如:
let s = String::from("rust"); let contains_r = s.contains('r'); // `s.contains`方法接收`&str`,这里`&s`自动解引用为`&str`
- 当