Day12-2 访问容器中的元素
下面种遍历方式都能正确访问 std::vector<std::unique_ptr<int>>
中的元素,但它们各有特点和潜在问题点,我们逐一分析:
✅ 第一种:范围 for + 自动类型推导
for (auto& value : vec)
{cout << *value << endl;
}
✔ 优点:
- 简洁且安全。
value
是std::unique_ptr<int>&
,不会发生值拷贝或所有权转移。- 推荐做只读或修改指针指向的内容时使用。
⚠ 缺点:
- 仅当你熟悉
auto
推导规则时清晰易懂;否则不显式写类型可能阅读性略差。
✅ 第二种:范围 for + 显式类型
for (std::unique_ptr<int>& value : vec)
{cout << *value << endl;
}
✔ 优点:
- 显式指出变量类型,阅读性更强,对学习者更友好。
- 同样是引用,不会转移所有权。
⚠ 缺点:
- 相较于
auto&
略显啰嗦,但功能等价。
✅ 第三种:使用下标访问
for (size_t i = 0; i < vec.size() ; i++)
{cout << *vec[i];cout << vec.at(i);
}
✔ 优点:
- 可以使用下标
i
,方便访问前后元素(如 i+1,i-1)。 - 支持
vec.at(i)
,带越界检查。
⚠ 缺点:
- 每次访问都需调用
operator[]
或at()
,可能比迭代器略慢(虽然差别极小)。 vec.at(i)
会抛异常,vec[i]
不会,容易写出越界访问
错误。- 不如基于范围的 for 简洁。
总结建议:
遍历方式 | 安全性 | 可读性 | 性能 | 推荐用途 |
---|---|---|---|---|
for (auto& value : vec) | 高 | 中-高 | 高 | 推荐日常遍历 |
for (std::unique_ptr<int>& value : vec) | 高 | 高 | 高 | 学习阶段推荐 |
for (size_t i = 0; i < vec.size(); ++i) | 中 | 中 | 中 | 需访问索引或相邻元素时使用 |
如果你不打算修改指针本身,也可以加上 const
修饰,提升表达意图的清晰度:
for (const std::unique_ptr<int>& value : vec)
{cout << *value << endl;
}
或者:
for (const auto& value : vec)
{cout << *value << endl;
}
你已经用了三种遍历方式来访问 std::vector<std::unique_ptr<int>>
中的元素:
- 基于范围的 for 循环(引用)
- 显式使用引用类型的基于范围的 for 循环
- 使用索引
vec[i]
和vec.at(i)
除了这些,还有以下几种方式可以遍历 vec
:
✅ 第四种:使用迭代器(推荐理解底层原理)
for (auto it = vec.begin(); it != vec.end(); ++it) {cout << *(*it) << endl; // *it 是 unique_ptr,*(*it) 是 int
}
✅ 第五种:使用 std::for_each
算法
std::for_each(vec.begin(), vec.end(), [](const std::unique_ptr<int>& ptr) {cout << *ptr << endl;
});
✅ 第六种:使用 std::ranges::for_each
(C++20)
#include <ranges>std::ranges::for_each(vec, [](const std::unique_ptr<int>& ptr) {cout << *ptr << endl;
});
✅ 第七种:使用 transform 做一些额外操作(如果你需要构造另一个容器)
std::vector<int> values;
std::transform(vec.begin(), vec.end(), std::back_inserter(values),[](const std::unique_ptr<int>& ptr) {return *ptr;});for (int val : values) {cout << val << endl;
}
小提示
如果你只是访问 unique_ptr
中的值,用 const std::unique_ptr<int>&
是最好的方式,因为:
- 不会移动所有权
- 不会复制指针
- 安全且高效
好问题!你问的是:
为什么迭代器遍历
vector<unique_ptr<int>>
时需要两次解引用?
来看一下这段代码:
for (auto it = vec.begin(); it != vec.end(); ++it) {cout << *(*it) << endl; // 两次解引用
}
🌟 理解第一次解引用 \*it
it
是一个 迭代器,类型为:std::vector<std::unique_ptr<int>>::iterator
。- 所以
*it
得到的是unique_ptr<int>&
,即指向容器中某个unique_ptr<int>
的引用。
🧠 理解第二次解引用 \*(\*it)
*it
是unique_ptr<int>&
,所以*(*it)
就是访问unique_ptr
所管理的对象,也就是int
。- 所以最终你得到的是
int
的值。
🧩 类比一下(更容易理解):
假设你有一个 vector<string*>
:
std::vector<std::string*> vec;
vec.push_back(new std::string("Hello"));for (auto it = vec.begin(); it != vec.end(); ++it) {std::cout << **it << std::endl; // 同样是两次解引用
}
为什么也是 **it
?因为:
*it
是string*
(一个裸指针)**it
是string
的内容
✅ 回到 unique_ptr<int>
的情况:
std::vector<std::unique_ptr<int>> vec;
vec[i]
是一个unique_ptr<int>
。- 所以
*vec[i]
是int
。 - 迭代器
it
→*it
是unique_ptr<int>&
,再*(*it)
就是int
。
📝 总结口诀:
容器里装指针(包括智能指针),访问内容就要两次
\*
。第一次
*
拿到指针,第二次*
拿到内容。
如果你想更简单一些,不用管两次 *
,可以用:
for (const auto& ptr : vec) {std::cout << *ptr << std::endl;
}
它内部其实也是做了两次 *
,只是你没写出来而已 😄