Skip to content

Commit 32edb12

Browse files
committed
feat: add some learning notes
learncpp2-4
1 parent 7502ce5 commit 32edb12

File tree

6 files changed

+505
-3
lines changed

6 files changed

+505
-3
lines changed

content/posts/learncpp_1/index.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: "cpp学习笔记(1)"
33
date: 2025-09-11T08:10:38+08:00
44
draft: false
5-
lastmod: 2025-09-20T21:59:46+08:00
5+
lastmod: 2025-09-30T10:28:08+08:00
66
---
77

88
## 前言
@@ -78,7 +78,9 @@ statement expression(原谅我不会翻译):只含一个表达式的语句
7878

7979
3. 同时还有一个大杀器:`constexpr`
8080

81-
这个关键字的意思,指的是能够在编译时就确定常量(甚至函数),而不是运行时,从而把运行时开销转换成编译时间,这是不是算是性能优化呢?
81+
这个关键字的意思,指的是能够在编译时就确定常量(甚至函数),而不是运行时,从而把运行时开销转换成编译时间。
82+
83+
现在,对于简单的函数,不含`new/delete``cin/cout`,随机数生成的函数,我们都推荐使用`constexpr`
8284

8385
需要注意的是,在C++11~20的不同版本中,支持的类型略有差异,比如后期才支持流程控制的for/while/if。
8486

content/posts/learncpp_2/index.md

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
+++
22
date = '2025-09-19T19:13:57+08:00'
3-
draft = true
3+
draft = false
44
title = 'cpp学习笔记(2)'
5+
lastmod = '2025-09-25T10:55:18+08:00'
56
+++
67
## 符号变量
78

@@ -47,3 +48,93 @@ std::string_view name {"John"};
4748
---
4849
4950
不建议用`string_view`来返回,除非它是用C式的字符串初始化的。
51+
52+
对于用`string_view`来说,推荐用法:
53+
54+
```cpp
55+
std::string_view s{"aaa"sv};
56+
```
57+
58+
实现零成本抽象,原因嘛,以后再补充...
59+
60+
## 对于流程控制的一些建议
61+
62+
对于只有两个分支的`if`,可以考虑用三元运算符`? :`
63+
64+
如果表达式本身只有为`0/1`两种可能,那么就无须写`==`,直接把表达式扔上去即可。
65+
66+
## static与内部链接
67+
68+
在变量前加入`static`关键字,不仅仅会使变量在整个程序的生命周期有效,还会使得无法被外部文件访问此变量。
69+
70+
现代cpp已经不推荐使用此关键字了。转而替代的是匿名命名空间,后者可以实现里面的变量直接在当前文件访问,其他文件不可访问。
71+
72+
没加`static`的变量默认是全局变量,在别的文件使用`extern`引入,`const`默认就是内部变量。
73+
74+
> static的最佳用法
75+
76+
`static`最适合用于函数内的计数场景,比如调用了函数几次,只需在函数内声明一个静态局部变量即可。
77+
78+
不推荐使用此方法实现相同的输入不同的输出内容,这其实也不符合fp的原则。如果必须要这么用,那么最好的办法是将条件一起传入。[参考](https://www.learncpp.com/cpp-tutorial/static-local-variables/)
79+
80+
## inline关键字
81+
82+
从cpp17开始,完善了`inline`,使得`constexpr`对于编译时常量跨文件有了最佳解决方案。
83+
84+
`inline`本义其实指的是把函数展开到调用的地方,但是修饰`constexpr`时,它被赋予了不同的意义。
85+
86+
`inline`修饰`constexpr`特殊,当在多个文件中定义的时候,会告诉链接器从任意一份中取出一份定义,并且在所有文件中共享,同时并不违反ODR原则,这使得把`inline`放在文件头里面,就可以向引入这个头文件的所有源文件进行分发。
87+
88+
`inline`同时还可以修饰命名空间,修饰非匿名的命名空间可以使得本来需要域解析操作符的使用,直接被透传到全局的命名空间中去。
89+
90+
下面是一个嵌套的命名空间的例子,使得接口能够安全的对当前文件中的其他函数暴露:
91+
92+
```cpp
93+
#include <iostream>
94+
95+
namespace V1 // declare a normal namespace named V1
96+
{
97+
void doSomething()
98+
{
99+
std::cout << "V1\n";
100+
}
101+
}
102+
103+
inline namespace V2 // declare an inline namespace named V2
104+
{
105+
namespace // unnamed namespace
106+
{
107+
void doSomething() // has internal linkage
108+
{
109+
std::cout << "V2\n";
110+
}
111+
112+
}
113+
}
114+
115+
int main()
116+
{
117+
V1::doSomething(); // calls the V1 version of doSomething()
118+
V2::doSomething(); // calls the V2 version of doSomething()
119+
120+
doSomething(); // calls the inline version of doSomething() (which is V2)
121+
122+
return 0;
123+
}
124+
```
125+
126+
`inline`作用于`V2`命名空间,使得内部内容能够无须域解析操作符,但是匿名命名空间保护性最强,使得内部内容只能有当前文件访问,无法暴露给外部文件。
127+
128+
> In modern C++, the term inline has evolved to mean “multiple definitions are allowed”.
129+
130+
总结下,`inline`除了内联的意义,其实还有:
131+
132+
1. 对于变量: 在头文件中定义,使得此头被多次引入的时候只使用共享的一次,避免每次都加载到文件,在头文件被多次引用的时候很有用(c++17)。
133+
2. 对于函数:在头文件中定义的函数需要加(但是不推荐,等到后面看到类的构造函数再会来补充),声明的函数无须加。
134+
3. 对于命名空间: 把某个命名空间中的内容暴露到全局的命名空间,使得其中内容访问无须加域解析操作符。
135+
136+
## using语句和using声明
137+
138+
语句指的是用一个命名空间,这个一般范围过大,不推荐,此处狠狠批评那些教用`using namespace std;`的人。
139+
140+
using声明是可以接受的,因为它比起别名更加清晰,而不是只让写代码的人看懂。

content/posts/learncpp_3/index.md

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
+++
2+
date = '2025-09-23T08:50:10+08:00'
3+
draft = false
4+
title = 'cpp学习笔记(3)'
5+
lastmod = '2025-09-26T09:27:20+08:00'
6+
+++
7+
8+
## 循环,条件与分支
9+
10+
这个东西老生常谈了,我本来没有看的欲望,但是有一道题目很有意思,在此记录:
11+
12+
> 从 1 开始,向上计数,将任何只能被 3 整除的数字替换为“fizz”一词,将任何只能被 5 整除的数字替换为“buzz”一词,将任何可被 3 和 5 整除的数字替换为“fizzbuzz”一词,如果还可被7整除,那么替换为“fizzbuzzpop”。
13+
14+
首先,介绍一个名词:分支预测。
15+
16+
简单来说,现代CPU在进行多分支的判断(`if-else`)的时候,其实会去猜程序下一步的路径,如果猜对了,那么效率会提升;如果猜错了,那么只能把流水线上面的内容清空,然后重新执行指令。
17+
18+
所以无论如何,应该尽量避免写`if-else`
19+
20+
题目给出的答案是这么写的:
21+
22+
```cpp
23+
// h/t to reader Waldo for suggesting this quiz
24+
#include <iostream>
25+
26+
void fizzbuzz(int count) {
27+
for (int i{1}; i <= count; ++i) {
28+
bool printed{false};
29+
if (i % 3 == 0) {
30+
std::cout << "fizz";
31+
printed = true;
32+
}
33+
if (i % 5 == 0) {
34+
std::cout << "buzz";
35+
printed = true;
36+
}
37+
if (i % 7 == 0) {
38+
std::cout << "pop";
39+
printed = true;
40+
}
41+
42+
if (!printed)
43+
std::cout << i;
44+
45+
std::cout << '\n';
46+
} // end for loop
47+
}
48+
49+
int main() {
50+
fizzbuzz(150);
51+
52+
return 0;
53+
}
54+
```
55+
56+
能运行吗?能,优雅吗?我不太满意。
57+
58+
59+
所以说,有没有一个办法,不同于这个思路,同时又能更计算机一点?
60+
61+
评论区的思路:
62+
63+
```cpp
64+
#include <iostream>
65+
66+
void fizzbuzz(int n)
67+
{
68+
for (int i{ 1 }; i <= n; i++)
69+
{
70+
int f = (i%7 == 0)*4 + (i%5 == 0)*2 + (i%3 == 0);
71+
switch (f)
72+
{
73+
case 0:
74+
std::cout << i;
75+
break;
76+
case 1:
77+
std::cout << "fizz";
78+
break;
79+
case 2:
80+
std::cout << "buzz";
81+
break;
82+
case 3:
83+
std::cout << "fizzbuzz";
84+
break;
85+
case 4:
86+
std::cout << "pop";
87+
break;
88+
case 5:
89+
std::cout << "fizzpop";
90+
break;
91+
case 6:
92+
std::cout << "buzzpop";
93+
break;
94+
case 7:
95+
std::cout << "fizzbuzzpop";
96+
break;
97+
}
98+
std::cout << '\n';
99+
}
100+
return;
101+
}
102+
103+
int main()
104+
{
105+
fizzbuzz(22);
106+
}
107+
```
108+
109+
这个方法非常巧妙,把每一个分支映射到了一个二进制的向量空间上面,这将会极大的提高性能,因为对于CPU来说,移位操作是原生的。但是,带来的就是更多相似字符串的重复占用,不知道这是不是良好的实践呢?
110+
111+
如果还想减少的话,还是用`if`来去测试位,不过这样又引入了分支。
112+
113+
其实这么用的地方还是很多的,POSIX的权限就是个例子。但是,用的时候就是想不到啊!
114+
115+
## 标准输出,标准错误和标准输入
116+
117+
这三者是这样的:
118+
119+
|名字|文件描述符|在c++中的对应|
120+
|----|----|----|
121+
|标准输入|0|`std::cin`|
122+
|标准输出|1|`std::cout`|
123+
|标准错误|2|`std::cerr`|
124+
125+
## 类型转换
126+
127+
### 数值提升(numberic promotion):
128+
129+
同类型,把一个更短的类型转换成一个更长的类型,安全,不会造成精度丢失。
130+
131+
只有两条路径:
132+
133+
1. `bool`,有/无负号的短整形,字符型->`int`->`unsigned int`
134+
2. `float`->`double`
135+
136+
注意,从`int``long`不是提升,是转换。
137+
138+
### 数值转换(numberic conversion):
139+
140+
基本类型的转换,包括:
141+
142+
窄化(narrowing conversion):长的类型转化成一个更短的类型,在{}中不被允许。
143+
144+
> 但是`constexpr`是允许的,因为是编译时求值,所以实际上是截断不是转化。
145+
146+
### 算术转换(arithmetic conversion):
147+
148+
对基本算术操作符两端的不同类型进行转换,有些规则:
149+
150+
1. 整数转浮点。
151+
2. 对于无符号/有符号:
152+
153+
- 如果没超过有符号上限,那就转换成有符号。
154+
- 超过了,那就转换成无符号。
155+
156+
`static_cast`在编译时检查(类型在编译时是确定的),`dynamic_cast`在运行时检查。
157+
158+
不建议使用C-Style的转换,因为他可能使用上面中的任意一个以及`const_cast`
159+
160+
## auto
161+
162+
对于`auto`来说,必须要先确定得到的结果是什么,才能做推断,不能对没有初始化值的变量做推断。
163+
164+
在用`std::cin`的时候,给的类型是`istream`,所以自动推断会推出这个类型,肯定不是你想要的结果。
165+
166+
`auto`不会保留`const`的修饰,所以如果需要保留,用`const auto`
167+
168+
---
169+
170+
### auto的尾随返回值
171+
172+
这其实一点都不`auto`,只是为了让函数名对齐,然后用`->`给出返回类型罢了。
173+
174+
```cpp
175+
auto add(int x, int y) -> int;
176+
auto divide(double x, double y) -> double;
177+
auto printSomething() -> void;
178+
auto generateSubstring(const std::string &s, int start, int len) -> std::string;
179+
```

0 commit comments

Comments
 (0)