{ }codelper

学习 › C++

各语言特有 · 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';}
stdin
5
3 1 4 1 5
apple banana apple cherry banana apple
stdout
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& 接收。

练习题