各语言特有 · 42
C++ 引用与 STL
& 引用和 const 引用,vector、map、set、unordered_map,接收 lambda 的算法,以及复制、失效、自动插入三个陷阱。
本单元适用于 C++。
C 既没有引用也没有容器,只能用指针和自己管理的数组。
Python、Java、C#、JavaScript 有各自的集合(见列表、字典两课),列表传过去总是共用的,所以不需要 &。
在 C++ 里按值传递会复制,用引用 & 则直接操作原对象。
下面的示例通过引用把 vector 翻倍,用排序、查找、求和等 STL 算法,再用 std::map 统计单词个数。
#include <algorithm>#include <iostream>#include <map>#include <numeric>#include <set>#include <string>#include <vector> void doubleAll(std::vector<int>& v) { // & : 원본을 바꿔요 for (auto& x : v) x *= 2;} int total(const std::vector<int>& v) { // const& : 복사 없이 읽기만 return std::accumulate(v.begin(), v.end(), 0);} void print(const std::vector<int>& v) { for (size_t i = 0; i < v.size(); i++) std::cout << (i ? " " : "") << v[i]; std::cout << '\n';} int main() { int n; std::cin >> n; std::vector<int> v(n); for (auto& x : v) std::cin >> x; for (auto x : v) x = 0; // 복사본만 바뀌어요 print(v); doubleAll(v); print(v); std::cout << total(v) << '\n'; std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; }); print(v); auto it = std::find(v.begin(), v.end(), 8); std::cout << (it - v.begin()) << ' ' << std::set<int>(v.begin(), v.end()).size() << '\n'; std::map<std::string, int> count; std::string w; while (std::cin >> w) count[w]++; for (const auto& [word, c] : count) std::cout << word << ' ' << c << '\n'; std::cout << count.contains("kiwi") << ' ' << count.size() << '\n'; int k = count["kiwi"]; // 없으면 0으로 새로 넣어요 std::cout << k << ' ' << count.size() << '\n';}5 3 1 4 1 5 apple banana apple cherry banana apple
3 1 4 1 5 6 2 8 2 10 28 10 8 6 2 2 1 4 apple 3 banana 2 cherry 1 0 3 0 4
要点
- 按值传递会复制
vector的全部元素,所以在函数里修改,原对象也不变。 - 引用
int&、std::vector<int>&是同一个变量的另一个名字。
示例中的doubleAll(v)会把原对象翻倍。
引用在创建时就必须指定绑定对象,之后不能改绑,而且和指针不同,不能为空(null)。 - 只读的话就用
const std::vector<int>&接收。
没有复制开销,不小心修改还会编译出错。 - STL 容器:
std::vector(可增长的数组)、std::map(按键排序的字典,查找 O(log n))、std::set(有序且不重复的集合)、std::unordered_map(哈希字典,平均 O(1),没有固定顺序)。 - 算法(
<algorithm>、<numeric>)接收迭代器范围v.begin(), v.end()。
给std::sort传 lambda[](int a, int b) { return a > b; }就从大到小排,std::find返回找到位置的迭代器(找不到返回v.end()),所以it - v.begin()就是下标。 std::accumulate(v.begin(), v.end(), 0)是求和,最后那个0的类型就是和的类型(0是int,和很大时用0LL)。
各语言的写法
| C++ | 基本习惯:小的值(int、double)按值传,string、vector、map 这类大的用 const& 传。for (const auto& [word, c] : count) 能同时取出键和值(C++17)。C++20 还可以像 std::ranges::sort(v) 这样直接传整个范围 |
|---|
常见错误
range-for 遍历的是副本
for (auto x : v) x = 0;→ x 是元素的副本,v 一点都没变(示例第一行 3 1 4 1 5)。clang 在 -Wall 下会警告 "set but not used"
要修改就写 auto& x,只读又想避免复制就写 const auto& x。
push_back 之后还用旧引用
int& first = v[0];
v.push_back(9);
std::cout << first;→ 大小超过容量时,vector 会搬到新内存,之前拿到的迭代器、指针、引用全部失效。再用就是未定义行为
push_back 之后按下标重新取。
也不要在 range-for 遍历同一个 vector 时往里 push_back。
用 map[key] 判断键是否存在
int k = count["kiwi"];→ 看起来像读取,但键不存在时会用 0 插入新项。示例中条目数从 3 变成 4。所以 const std::map& 上根本不能用 []
只想知道在不在,用 count.contains("kiwi")(C++20)、count.count(...) 或 count.find(...);不存在时想报错,就用 count.at(...)(抛出 std::out_of_range)。
各语言的差异
要修改元素用
for (auto x : v) 遍历的是副本。要修改元素用
auto&,只读用 const auto&;函数参数里的大对象也用 const& 接收。