核心原理一句话:
for n in &vec 拿到的是 &T,不是 T。要不要写 *n,取决于你后面这个地方需要的是“引用”还是“值本身”。
1. 先看最基础例子
fn main() {
let nums = vec![1, 2, 3];
for n in &nums {
println!("{}", n);
}
}
这里:
for n in &nums
等价于:
for n in nums.iter()
所以 n 的类型是:
&i32
不是:
i32
也就是说,循环里的 n 是每个元素的引用。
2. *n 是什么意思?
let nums = vec![1, 2, 3];
for n in &nums {
let value = *n;
println!("{}", value);
}
这里:
*n
表示:
把引用
n解开,拿到它指向的真实值。
如果:
n: &i32
那么:
*n: i32
所以可以理解为:
n 是引用 *n 是引用指向的值
3. 为什么有时候不用 * 也能打印?
比如:
fn main() {
let nums = vec![1, 2, 3];
for n in &nums {
println!("{}", n);
}
}
这里 n 是 &i32,但也能打印。
原因是 println! 很智能,Rust 标准库里对很多引用类型也实现了格式化能力。
也就是说:
println!("{}", n);
可以打印引用指向的内容。
你写成下面这样也可以:
println!("{}", *n);
这两个对于 i32 来说效果一样。
4. 什么时候必须用 *?
当你要把引用里的值当成真正的值来用时,通常需要 *。
例如:
fn main() {
let nums = vec![1, 2, 3];
for n in &nums {
let x: i32 = *n;
println!("{}", x);
}
}
因为:
n
是:
&i32
而变量 x 需要的是:
i32
所以要写:
let x: i32 = *n;
再比如做计算:
fn main() {
let nums = vec![1, 2, 3];
for n in &nums {
let result = *n + 10;
println!("{}", result);
}
}
这里推荐写:
*n + 10
因为你要对真实的数字做加法。
5. 修改 Vec 里的值时,经常必须用 *
fn main() {
let mut nums = vec![1, 2, 3];
for n in &mut nums {
*n *= 10;
}
println!("{:?}", nums);
}
输出:
[10, 20, 30]
这里:
for n in &mut nums
循环里的 n 类型是:
&mut i32
如果你要修改它指向的真实值,就要写:
*n *= 10;
也就是:
把 n 指向的那个 i32 乘以 10
不能写:
n *= 10;
因为 n 本身是一个引用,不是数字。
6. 但是调用方法时,很多时候不用 *
比如:
fn main() {
let names = vec![
String::from("zhangsan"),
String::from("lisi"),
];
for name in &names {
println!("{}", name.len());
}
}
这里:
name
的类型是:
&String
但是你可以直接写:
name.len()
不用写:
(*name).len()
原因是 Rust 在方法调用时会自动解引用。
也就是说,编译器会帮你把:
name.len()
理解成类似:
(*name).len()
这叫:
自动解引用 auto deref
所以这两个写法都可以:
name.len(); (*name).len();
但实际开发中通常写:
name.len();
更自然。
7. 可变引用调用方法时,也经常不用 *
fn main() {
let mut names = vec![
String::from("张三"),
String::from("李四"),
];
for name in &mut names {
name.push_str("同学");
}
println!("{:?}", names);
}
这里:
name
的类型是:
&mut String
但是可以直接调用:
name.push_str("同学");
不用写:
(*name).push_str("同学");
因为方法调用会自动解引用。
8. 但是如果你要整体替换这个值,要用 *
比如:
fn main() {
let mut names = vec![
String::from("张三"),
String::from("李四"),
];
for name in &mut names {
if name == "张三" {
*name = String::from("王五");
}
}
println!("{:?}", names);
}
这里:
*name = String::from("王五");
表示:
把
name这个引用指向的原始 String 替换掉。
不能写:
name = String::from("王五");
因为 name 是引用变量,不是 String 本身。
9. for &n in &nums 是另一种写法
对于 i32 这种简单类型,可以这样写:
fn main() {
let nums = vec![1, 2, 3];
for &n in &nums {
println!("{}", n + 10);
}
}
注意这里是:
for &n in &nums
不是:
for n in &nums
区别是:
for n in &nums
此时:
n: &i32
而:
for &n in &nums
此时:
n: i32
这个 &n 是模式匹配,意思是:
循环时把
&i32解开,直接拿到里面的i32。
所以:
for &n in &nums {
println!("{}", n + 10);
}
等价于:
for n_ref in &nums {
let n = *n_ref;
println!("{}", n + 10);
}
10. 为什么 Vec<String> 不能随便 *name?
看这个例子:
fn main() {
let names = vec![
String::from("张三"),
String::from("李四"),
];
for name in &names {
let s = *name; // 错误
println!("{}", s);
}
}
为什么错误?
因为:
name: &String
*name 得到的是 String。
但是 String 不是简单复制类型,不能从引用后面直接把它搬出来。
如果允许这样做,就相当于把 Vec 里的字符串移动走了。
正确写法有几种。
只是读取,用引用
for name in &names {
println!("{}", name);
}
需要复制一份,用 clone
for name in &names {
let s = name.clone();
println!("{}", s);
}
这里 clone() 会复制一份新的 String。
如果真的要拿走所有权,不要用 &names
for name in names {
println!("{}", name);
}
这样 name 的类型就是:
String
但是循环结束后,原来的 names 就不能再用了。
11. 为什么 i32 可以 *n,String 不行?
因为 i32 实现了 Copy。
let nums = vec![1, 2, 3];
for n in &nums {
let x = *n; // 可以
}
这里不是把 Vec 里的数字移动走,而是复制了一份。
但是 String 没有实现 Copy:
let names = vec![String::from("张三")];
for name in &names {
let s = *name; // 错误
}
因为 String 管理堆内存,不能随便隐式复制。
总结:
| 类型 | *引用 后能不能直接赋值给新变量 |
原因 |
|---|---|---|
i32 |
可以 | i32 是 Copy |
bool |
可以 | bool 是 Copy |
char |
可以 | char 是 Copy |
String |
不可以 | String 不是 Copy |
Vec<T> |
不可以 | Vec<T> 不是 Copy |
| 自定义 struct | 看是否实现 Copy |
默认不是 Copy |
12. 三种遍历方式对比
假设:
let nums = vec![1, 2, 3];
方式一:拿走所有权
for n in nums {
println!("{}", n);
}
此时:
n: i32
循环后 nums 不能用了。
方式二:不可变借用
for n in &nums {
println!("{}", n);
}
此时:
n: &i32
循环后 nums 还能继续用。
方式三:可变借用
for n in &mut nums {
*n += 1;
}
此时:
n: &mut i32
可以修改 Vec 里的元素。
13. 什么时候不用 *?
常见不用 * 的情况:
1. 只是打印
println!("{}", n);
2. 调用方法
name.len();
name.push_str("abc");
方法调用会自动解引用。
3. 函数本来就需要引用
fn print_num(n: &i32) {
println!("{}", n);
}
fn main() {
let nums = vec![1, 2, 3];
for n in &nums {
print_num(n);
}
}
这里 n 已经是 &i32,所以不用 *。
如果你写:
print_num(*n);
反而错,因为函数要的是 &i32,你给的是 i32。
14. 什么时候要用 *?
常见需要 * 的情况:
1. 需要真实值
let x: i32 = *n;
2. 做数值计算
let result = *n + 10;
3. 修改引用指向的值
*n += 1;
4. 替换引用指向的对象
*name = String::from("新名字");
15. 最实用的判断方法
你可以按这个规则判断:
n 是 &T 如果后面需要 &T:不用 * 如果后面需要 T:用 * 如果是方法调用:通常不用 *,Rust 会自动解引用 如果要修改引用指向的值:通常要用 *
比如:
for n in &nums {
println!("{}", n); // 不用 *,打印引用也可以
let x = *n; // 要 i32,所以用 *
let y = *n + 1; // 要计算,所以用 *
print_ref(n); // 函数要 &i32,不用 *
print_value(*n); // 函数要 i32,用 *
}
完整例子:
fn print_ref(n: &i32) {
println!("引用:{}", n);
}
fn print_value(n: i32) {
println!("值:{}", n);
}
fn main() {
let nums = vec![1, 2, 3];
for n in &nums {
print_ref(n);
print_value(*n);
}
}
16. 一句话总结
for n in &vec 时:
n 是引用
所以:
n // 引用本身,类型是 &T *n // 引用指向的值,类型是 T
但是 Rust 在一些场景会帮你自动处理,比如:
println!("{}", n);
name.len();
name.push_str("abc");
所以你会感觉“有时候要 *,有时候不用 *”。
最关键的判断是:
当前这个地方到底需要的是引用,还是引用背后的真实值?
需要引用,就不用 *。
需要真实值,就用 *。