Skip to content

Commit 012fd5c

Browse files
committed
new: cpp6,7
1 parent ed45207 commit 012fd5c

File tree

2 files changed

+394
-0
lines changed

2 files changed

+394
-0
lines changed

content/posts/learncpp_6/index.md

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
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+
```

content/posts/learncpp_7/index.md

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
+++
2+
date = '2025-10-22T16:29:09+08:00'
3+
draft = true
4+
title = 'cpp学习笔记(7)'
5+
+++
6+
7+
## 命令行参数
8+
9+
早就知道在 `c` 中是可以在 `main` 中获取从命令行传入的参数的。但是对于解析是一窍不通,这里简单说一下如何获取整形。
10+
11+
不过,这些方法都仅仅适用于简单的字符串处理,而一个简单的带参 `cli` 程序,基本都要求参数能够无视顺序,可选参数,这些统统都不能实现...
12+
13+
### `std::stringstream`
14+
15+
这个函数用于把字符串转换成流,然后再把流输出到整形。
16+
17+
```cpp
18+
std::stringstream convert{arcv[1]};
19+
int num{};
20+
if(convert>>num){
21+
std::cout<<num;
22+
}
23+
```
24+
25+
### std::stoi
26+
27+
这个就是现代C++的方法了。
28+
29+
直接转换就行:
30+
31+
```cpp
32+
int num{std::stoi(argv[1])};
33+
std::cout << num;
34+
```
35+
36+
## lambda表达式
37+
38+
lambda表达式看起来像这样子:
39+
40+
```cpp
41+
[<capture>](<parameter list>) -> <return type> {<body>};
42+
```
43+
44+
参数列表可以省略,返回值也可以省略,会自动推断。
45+
46+
所以说,最简的 `lambda` 表达式:`[]{}`
47+
48+
### 捕获
49+
50+
对于以下变量来说,不需要捕获也可使用:
51+
52+
- 全局变量,静态局部变量,线程局部变量
53+
- 常量表达式(包括被编译器视为常量表达式的变量)
54+
55+
捕获语法是 `lambda` 表达式中一个不可缺少的部分,词法分析就是通过 `[]` 来发现这是一个表达式的。
56+
57+
> `lambda` 表达式不和 `{}` 一样,不能访问上级大括号中的局部变量。它只能访问静态和全局的变量,以及 `constexpr`
58+
59+
而捕获其实就是把原来应该能让 `lambda` 能够使用的局部变量让它使用。
60+
61+
捕获的时候可以声明新变量的,在 `[]` 内,可以使用当前块内能访问的变量来参与运算。
62+
63+
捕获有两种类型:默认捕获和单个捕获。
64+
65+
### 按对象捕获
66+
67+
假设有变量 `a` `b`:
68+
69+
- 传值:`[a,b]`。默认传入变量的**常量**值,如果需要修改,需要在大括号前加上 `mutable`
70+
- 传引用:`[&a,&b]`
71+
72+
### 默认捕获
73+
74+
默认捕获会捕获 `lambda` 中提到的所有变量。
75+
76+
- `[=]`:传入值
77+
- `[&]`:传入引用
78+
79+
不允许同时使用默认捕获和按对象的捕获。
80+
81+
### 创建闭包
82+
83+
提到闭包这个词,就不得不提函数式编程了。简单来说,函数是“一等公民”,就像我们传统的命令式编程的变量(对象)一样,可以作为参数传递,可以被返回,可以运行时创建等等。
84+
85+
那么闭包就是保存了当前上下文中某些状态的函数。
86+
87+
在C++中,想实现一个闭包,还是得定义一个普通函数(虽然说跟一般的匿名函数印象不太一样):
88+
89+
```cpp
90+
std::function<int(int)> multiply(int factor) {
91+
return [factor](int base) { return base * factor; };
92+
}
93+
int main() {
94+
auto multiply_3 = multiply(3);
95+
auto multiply_4 = multiply(4);
96+
std::cout << multiply_3(3) << '\n';
97+
std::cout << multiply_4(4);
98+
}
99+
```
100+
101+
`multiply_3` 和 `multiply_4` 就是一个闭包。
102+
103+
### 实现一个简单的函数
104+
105+
#### `lambda` 表达式可以作为 `lambda` 类型的变量存储。
106+
107+
```cpp
108+
auto square{[](int base) { return base * base; }};
109+
std::cout << square(3);
110+
```
111+
112+
#### 也可以声明的时候就调用:
113+
114+
```cpp
115+
[]() { std::cout << "Hello world!\n"; }();
116+
// 前一个括号是形参列表,后一个括号表示调用函数
117+
```
118+
119+
值得提的一点是,这其实不是真正的函数,实际上只是重载了 `()` 实现像函数一样的调用。
120+
121+
### 在算法库的应用
122+
123+
前面在 `<algorithm>` 中,最后一个参数是比较函数的地址,其实我们可以直接在这里写 `lambda` 函数。
124+
125+
### lambda推断
126+
127+
对于实参推断(给出 `(auto a, auto b)` 的情况,只要函数体里面的能匹配,那么没有问题,因为编译器会自动生成重载的 `lambda` 表达式。
128+
129+
对于返回值,要求所有返回的地方一致才能推断,否则,请使用 `->` 手动指定返回值类型。
130+
131+
### 带捕获的 `lambda` 拷贝
132+
133+
如果对带捕获的 `lambda` 表达式操作的时候,目标的类型是 `std::function`,那么会发生隐式类型转换,将 `lambda` 闭包进行深拷贝,使得多次使用 `std::function` 构造的多个对象出现独立的拥有原始闭包状态的副本。
134+
135+
在 `lambda` 构造的时候使用了 `mutable` 的值捕获,那么每次捕获的变量都是原始的捕获值。
136+
137+
解决方法是使用 `std::ref` 来包装 `lambda` 表达式。对于传入参数不一致的情况,它会返回一个 `std::reference_wrapper` 对象,保证内部存储的是对原始 `lambda` 对象的引用,从而保证每次调用的是同一个 `lambda` 表达式对象。
138+
139+
140+
## 运算符重载
141+
142+
有三种办法重载:
143+
144+
**友元函数**:如果想要重载操作数是类对象的运算符,为了获取类内成员的访问权限,可以把此重载函数以友元的形式声明在类内。
145+
146+
**普通函数**:同时可以使用 `getter` 和 `setter` 来避免直接访问内部的成员变量。
147+
148+
**类成员函数**:仅适用于左侧操作数是类成员的情况,此时右操作数就作为参数。
149+
150+
一些选择建议:
151+
152+
- 重载赋值、下标(`[]`)、函数调用、成员选择(`->`) -> 类成员函数
153+
- 重载一元运算符 -> 类成员函数
154+
- 不修改左操作数 -> 普通/友元函数
155+
- 左操作数是内置类型无法修改 -> 普通/友元函数
156+
- 左操作数可以修改 -> 类成员函数
157+
158+
重载函数一般返回左侧的对象**的引用**(以便可以连续用 `.` 调用)或者返回一个同类型的对象(用于连续使用重载运算符比如 `+=`)。
159+
160+
### 重载一元+/-
161+
162+
由于一元的 `+/-` 是类成员函数,而且没有参数,所以不会和二元的混淆。
163+
164+
### 重载自增/减
165+
166+
前自增/减参数列表没有参数,而后自增/减有一个 `int` 用于占位,从而将两者区分。
167+
168+
由于后自增/减要返回原来的值,所以需要一个临时变量用于返回,相比前自增/减多了开销,更倾向使用前自增/减。
169+
170+
### 下标访问重载
171+
172+
下标访问的重载返回值一般使用 `auto&`,从而作为左值被修改。
173+
174+
在C++23之前,如果定义类的时候使用 `const`,那么就需要添加一个成员重载函数:
175+
176+
```cpp
177+
const auto& operator[](T t) const;
178+
```
179+
180+
这个函数被常量对象优先使用,被非常量对象次优先使用。
181+
182+
在C++23,用右值引用解决此问题。
183+
184+
### 飞船运算符
185+
186+
外来语言的符号,用于比较两个数大小关系。
187+
188+
返回下面类型:
189+
190+
**`strong_ordering`**: 有`equivalent``equal``greater``less`
191+
192+
**`weak_ordering`**: 比 `strong` 少了相等,因为是弱,只有等效。
193+
194+
**`partial_ordering`**: 相比 `weak` 多了 `unordered`,用于表示无法比较的情况。
195+
196+
从上往下可以隐式转换。
197+
198+
### 类型转换重载
199+
200+
类型转换重载也是 `operator` 操作符,不过重载的目标其实有点像C的强制转换:
201+
202+
```cpp
203+
class MyClass{
204+
//...
205+
public:
206+
operator int() {
207+
// conversion
208+
return <intType_obj>;
209+
}
210+
};
211+
```
212+
213+
无须返回值,也无须参数,因为显而易见的返回目标类型,传入的是这个类对象,这也必须是一个类成员函数。
214+
215+
建议加上 `explicit` 关键字,因为我们不希望隐式转换带来的不确定性。
216+
217+
> 如何平衡类型转换重载和转换构造函数?
218+
219+
假设要把A转换成B:
220+
221+
A可改,B不可改。那么就定义一个类型转换的重载。
222+
223+
A不可改,B可改。那么就定义一个参数只含A的转换构造函数,让B去接收A。
224+
225+
A、B都不可改。那么定义一个普通函数实现转换吧。
226+
227+
### 复制运算符重载
228+
229+
在动态分配内存中,有时为了避免自己赋值给自己,用一个 `if` 来设置保护防止出现空指针问题。
230+
231+
在三/五/零原则中,如果不带指针,那么应该不应该定义这个复制运算符重载。(其他:析构、复制构造,移动运算符,移动构造函数)

0 commit comments

Comments
 (0)