|
| 1 | ++++ |
| 2 | +date = '2025-10-13T08:25:04+08:00' |
| 3 | +draft = true |
| 4 | +title = 'cpp学习笔记(6)' |
| 5 | ++++ |
| 6 | + |
| 7 | +## std::vector |
| 8 | + |
| 9 | +此向量非彼向量。 |
| 10 | + |
| 11 | +### 对于字符串 |
| 12 | + |
| 13 | +推荐使用`string_view`配合C风格的字符串,这样效率最高,只发生一次堆内存分配。 |
| 14 | + |
| 15 | +```cpp |
| 16 | +std::vector<std::string_view> names{ "Alex", "Betty", "Caroline", "Dave","Emily","Fred","Greg","Holly" }; |
| 17 | +``` |
| 18 | +### 列表初始化器 |
| 19 | +
|
| 20 | +- `std::vector vec(3)`会初始化一个全0,长度为3的向量; |
| 21 | +- `std::vecotr vec(2,3)`会初始化一个长度2,值都为3的向量。 |
| 22 | +- 但是`std::vector vec({3})`会初始化一个长度1值为3的向量。 |
| 23 | +
|
| 24 | +因为给出单参数的时候,会优先调用列表初始化器,这个初始化器的行为是初始化长度为参数的向量。 |
| 25 | +
|
| 26 | +### 下标 |
| 27 | +
|
| 28 | +当时设计让`size_t`是一个无符号的整数,从现在看来是一个错误,因为下标访问最容易导致环绕,最好的办法是不使用下标访问而是迭代器,次选是使用C风格的访问`.data()`。然后全部使用有符号整数。 |
| 29 | +
|
| 30 | +### for-each |
| 31 | +
|
| 32 | +推荐使用这些: |
| 33 | +
|
| 34 | +1. **修改元素副本**:`auto` |
| 35 | +2. **修改原始元素**:`auto` |
| 36 | +3. **只需要查看**:`const auto&`(大多数情况) |
| 37 | +
|
| 38 | +反向查看:使用`std::view::reverse()` |
| 39 | +
|
| 40 | +### 容量和长度 |
| 41 | +
|
| 42 | +容量是分配给向量的内存,长度是实际使用的部分。 |
| 43 | +
|
| 44 | +`pop`的时候长度减少容量不变;`push`时如果遇到容量瓶颈,会自动复制扩容(2/1.5)倍。 |
| 45 | +
|
| 46 | +`resize`成员函数会同时改变容量和长度,`reserve`只改变容量。 |
| 47 | +
|
| 48 | +`emplace_back`会显示调用构造函数,直接在栈顶构建对象,如果是是临时对象,那么要注意如果有`explicit`那么不能执行隐式转换。而`push_back`会发生一次拷贝。 |
| 49 | +
|
| 50 | +## std::array |
| 51 | +
|
| 52 | +`array` 大部分功能都与 `constexpr` 兼容。 |
| 53 | +
|
| 54 | +在 `constexpr` 表达式执行的从 `unsign` 到 `sign` 的转换是允许的,因为编译器对此信任程度很高。 |
| 55 | +
|
| 56 | +### .at()、[]和std::get() |
| 57 | +
|
| 58 | +- **`.at()`**: 运行时边界检查。(C++17后编译时检查) |
| 59 | +- **`std::get<index>(arr)`**: 编译时边界检查。 |
| 60 | +- **`[]`**: 不进行边界检查,C++11后可为 `constexpr` 。 |
| 61 | +
|
| 62 | +对于现代C++来说, `.at` 是更安全的选择。 |
| 63 | +
|
| 64 | +因为会在越界访问的时候抛出异常。而 `[]` 不会进行越界检查。 |
| 65 | +
|
| 66 | +### 用模板创建数组 |
| 67 | +
|
| 68 | +```cpp |
| 69 | +
|
| 70 | +template<typename T, size_t N> |
| 71 | +void passByRef(const std::array<T,N>& arr){ |
| 72 | + static_assert(N!=0); |
| 73 | + std::cout<<arr[N]; |
| 74 | +} |
| 75 | +``` |
| 76 | + |
| 77 | +由于模板参数是编译时常量,所以可以用 `static_assert` ,同时,更加推荐把这两句用 `std::get<N>(arr)` 替代。 |
| 78 | + |
| 79 | +在C++20,可以把 `size_t` 替换成 `auto` 。 |
| 80 | + |
| 81 | +### 在函数内返回array |
| 82 | + |
| 83 | +由于 `array` 是在栈上分配的,那么一般来说会执行返回值优化,但是移动语义就没有意义(因为不是在堆上分配的就不能仅转移指针)。如果在C++17之前为了避免不确定性,那用 `vector` ?但是怎么感觉开销在堆上更大了说... |
| 84 | + |
| 85 | +### 初始化聚合数组 |
| 86 | + |
| 87 | +列表初始化,但是兼容C带来的技术债。 |
| 88 | + |
| 89 | +```cpp |
| 90 | +constexpr std::array<House, 3> houses { // 1. 初始化 std::array |
| 91 | + { // 2. 初始化底层 C 风格数组 (元素列表) |
| 92 | + { 13, 1, 7 }, // 3. 初始化第一个 House 结构体 |
| 93 | + { 14, 2, 5 }, // 3. 初始化第二个 House 结构体 |
| 94 | + { 15, 2, 4 } // 3. 初始化第三个 House 结构体 |
| 95 | + } |
| 96 | +}; |
| 97 | +``` |
| 98 | + |
| 99 | +此时不使用CTAD,但是必须手动指定模板参数,或者,指定每一个元素的类型,此时可以CTAD: |
| 100 | + |
| 101 | +```cpp |
| 102 | +constexpr std::array houses { // 1. 初始化 std::array |
| 103 | + { |
| 104 | + Horse{ 13, 1, 7 }, // 3. 初始化第一个 House 结构体 |
| 105 | + Horse{ 14, 2, 5 }, // 3. 初始化第二个 House 结构体 |
| 106 | + Horse{ 15, 2, 4 } // 3. 初始化第三个 House 结构体 |
| 107 | + } |
| 108 | +}; |
| 109 | +``` |
| 110 | + |
| 111 | +### reference_wrapper |
| 112 | + |
| 113 | +容器中不能给模板传入 `int&` 但是可以传入一个 `std::reference_wrapper<int>` 起到同样的作用,或者直接用 `std::ref()` `std::cref()` 直接把变量转换成引用。 |
| 114 | + |
| 115 | +## 迭代器 |
| 116 | + |
| 117 | +迭代器一般使用 `!=end` 来判断是否到达末尾,因为不是所有迭代器都可以比较。 |
| 118 | + |
| 119 | +其本身是一个指针,指向当前的容器元素。 |
| 120 | + |
| 121 | +写的 `range based for` 实际上就是迭代器的语法糖。 |
| 122 | + |
| 123 | +## algorithems 库 |
| 124 | + |
| 125 | +这是一个算法库,用于偷懒(不是 |
| 126 | + |
| 127 | +### 补充知识 |
| 128 | + |
| 129 | +二元谓词:就是接受两个参数,返回一个布尔值的函数。 |
| 130 | + |
| 131 | +在算法库中,二元谓词都需要弱序,即非自反性(自己不能小于自己),非对称性(a小于b,那么b不能小于a),传递性。 |
| 132 | + |
| 133 | +对于 `Comp` 这个类型,我们假设前一个参数是 `a`,后一个是 `b`。那么有: |
| 134 | + |
| 135 | +> 对 `a` 和 `b` 进行某种运算,如果希望最后算法需要保持 `a` 排在 `b` 的前面,那么就返回 `true`;如果算法认为 `a` **不**应该排在 `b` 的前面,那么就返回 `false`。 |
| 136 | +
|
| 137 | +最简单的办法是,假设已经按照一个顺序排列,然后取出前两个元素为 `a` 和 `b`,将他们放入表达式,如果返回 `true`,那么这就是你要的顺序,为 `false` 则是另外一个顺序。 |
| 138 | + |
| 139 | +### std::sort |
| 140 | + |
| 141 | +sort 有第三个参数,传入一个二元谓词,在 `cppreference` 中类型为 `Comp` ,如果是一个函数的话,只需要传地址所以不用 `()`。这个函数必须严格弱序的。 |
| 142 | + |
| 143 | +### std::for_each |
| 144 | + |
| 145 | +`foreach` 可以用于方便的遍历可以迭代的容器。给定开始和结束的迭代器,和要调用的函数地址(这个函数只能传入一个参数,可用 `auto` ),就可以自动迭代。 |
| 146 | + |
| 147 | +同时和 `std::next` 配合使用,可以跳过一部分迭代器,获得很高的灵活性。这时候就有了比基于范围的 `for` 循环更加灵活。 |
| 148 | + |
| 149 | +### ranges (C++20) |
| 150 | + |
| 151 | +从C++20开始,很多算法添加了对应的重载,直接传入容器的地址就可以实现操作,即 `std::ranges::for_each(arr)`。 |
| 152 | + |
| 153 | +## 函数指针 |
| 154 | + |
| 155 | +C的函数指针如果不用 `typedef`将会非常复杂,而C++提供了一个模板,以一个传入 `int, int` 返回 `int` 的函数指针为例: |
| 156 | + |
| 157 | +```cpp |
| 158 | +#include<functional> |
| 159 | +std::function<int(int, int)> ptr {&foo}; |
| 160 | +std::function<int(int, int)> bar(){ |
| 161 | + // some implementation |
| 162 | +} |
| 163 | +``` |
0 commit comments