Skip to content

Commit 94cc55d

Browse files
committed
Update Cpp
1 parent 28bd88d commit 94cc55d

File tree

5 files changed

+60
-32
lines changed

5 files changed

+60
-32
lines changed

docs/cpp-compile.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ C++鼓励程序员将一些对象,通用函数放到独立的文件中,就
2222
关于这一点是有多种原因的,
2323
1. C语言比较落后,若不用头文件,便要用额外内存把接口信息存储到元数据(meta data)中,Java等后来出现的语言就是这么干的。虽然C++已经很现代,但仍然沿用了头文件的设计。
2424
2. 避免环形调用。比如A文件调用B文件的方法,B文件又去调用A文件的方法,在python里面就不能这么干。
25-
3. 为了能够**减少编译**。C++编译器既编译程序,也**管理链接器**。如果只修改了一个文件,则可以只重新编译该文件,然后将它与其他文件的编译版本链接。这使得大程序的管理更便捷。
25+
3. 为了能够**减少编译**。C++编译器既编译程序,也**管理链接器**。如果只修改了一个文件,则可以只重新编译该文件,生成一个目标文件,然后将它与其他无改动的目标文件进行链接。这使得大程序的管理更便捷。
2626

2727
大多数C++环境都提供了其他工具来帮助管理。例如,UNIX和Linux系统都具有make程序,可以跟踪程序依赖的文件以及这些文件的最后修改时间。运行make时,如果它检测到上次编译后修改了源文件,make将记住重新构建程序所需的步骤。
2828

@@ -71,7 +71,7 @@ void show_polar(polar dapos);
7171

7272
在头文件里面的定义而不会发生重复定义问题的有以下几种情况,
7373

74-
(1)对于static的变量和函数,不会发生重复定义问题,static具有只定义一次的特性,目前还未清楚编译器是如何处理的,但经过测试可知不会发生重复定义问题
74+
(1)对于static的变量和函数,不会发生重复定义问题,static具有只执行一次的特性
7575

7676
(2)inline函数,inline函数必须在头文件里定义,它在预处理阶段会进行代码替换,并非真正的函数,不会发生重复定义问题。
7777

@@ -170,7 +170,7 @@ bool Region::IsPlayerExist(Player* player) {
170170

171171
1. 一次性打包所有源文件。这是比较干脆的方式,一般用在整个项目都是一体的情况下,不需要单独提供接口给其他项目使用。
172172
2. 共享库。基础模块会被很多进程使用,则基础模块可以作为共享库,这样可能节省重复代码段占用的内存,可执行文件的大小也会小一点。
173-
3. 静态库。静态库其实就是提供一个不需要暴露源代码的目标文件,如果你写了一个库又不想开源,则可以考虑向他人提供目标文件和头文件即可。
173+
3. 静态库。静态库其实就是提供一个不需要暴露源代码的目标文件,如果你写了一个库又不想开源,则可以考虑向他人提供目标文件和头文件即可。这个也可以用作单独编译,每次修改部分文件则只需编译生成部分静态库。
174174

175175
### 4.1 统一打包
176176
假设我们的项目有main.cpp, test.h, test.cpp,在main.cpp里调用test.h声明的接口。

docs/cpp-constant.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
# 常量
22
## const
3-
当你把一个变量加上const后,意味着你或他人不能改变这个值,只能访问,由编译器会负责监督。
3+
当你把一个变量加上const后,意味着不能改变这个值,只能访问。编译器层面的实现原理是在编译时发现这个变量是常量,就直接把这个变量直接以符号表中的值进行替换。
4+
```cpp
5+
const int a = 999;
6+
7+
int main() {
8+
cout << a << endl;
9+
cout << 999 << endl; // 编译后
10+
}
11+
```
412

513
## constexpr
614
C++11提供了新的关键字constexpr,表示编译时求值,就是在编译的时候就把结果求出来,作用是允许把数据置于只读内存中以及提升性能。

docs/cpp-function.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ double z = area(x);
1313
```cpp
1414
double z = x * x * 3.14;
1515
```
16-
使用内联函数的有优点在于可以省去了从栈中获取函数的开销,频繁调用且简单的函数可以声明为inline,但只能在文件内部使用。注意,你要确保你的函数是可以轻易被编译器转换的,否则可能产生奇怪的问题。
16+
使用内联函数的有优点在于可以省去了从栈中获取函数的开销,频繁调用且简单的函数可以声明为inline。注意,你要确保你的函数是可以轻易被编译器转换的,否则可能产生奇怪的问题。有些简单的函数也可以在头文件直接定义为内联函数,因为最终不会产生实际的函数,所以不会报重复定义错误
1717

1818
## 函数模板
1919
C++ 的函数模板支持程序员将单个函数进行泛型编程,这一点是很多静态语言都没有的特性。<span id="function-template"></span>
@@ -131,9 +131,11 @@ cout << n1 << " " << n2 << endl;
131131
上面的例子中,我们重载了swap函数,并且函数名后面指定了`<Node>`,表明了对Node类型的swap函数进行具体化。
132132

133133
## 定义位置
134-
模板函数如果想被不同cpp文件使用,必须在头文件里面定义好,而不能在源文件里面定义,因为定义的链接发生在预处理阶段之后,如果不在头文件里面定义好,预处理阶段时根本不知道你的定义在哪里,自然也无法帮你生成函数了,进而无法产生链接,在链接阶段会报未定义错误
134+
模板函数如果想被不同cpp文件使用,必须在头文件里面定义好,即声明+实现,而不能在源文件里面定义。因为对应类型的函数版本的生成发生在预处理阶段之后,如果不在头文件定义好,预处理阶段时根本不知道你的定义在哪里,自然也无法帮你生成函数了。
135135

136-
但如果在cpp文件里显式具体化,则可以规避掉这个问题,因为一旦显式具体化后,编译器就会生成对应类型的函数,然后链接阶段多个目标文件合并后,就能找到对应的函数地址了。但注意,这样是做是不太好的,建议还是老实在头文件里面定义好。
136+
这也意味着每一个调用了相同类型模板函数的目标文件内部都包含了相同的生成代码,这会导致目标文件代码量膨胀。不过大部分编译器都会在链接阶段去除重复的生成代码。
137+
138+
但如果在cpp文件里显式具体化,则可以规避掉必须在头文件定义的问题,因为一旦显式具体化后,编译器就会生成对应类型的函数,然后链接阶段多个目标文件合并后,就能找到对应的函数地址了。但注意,这样是做是不太好的,建议还是老实在头文件里面定义好。
137139
```cpp
138140
// main.cpp
139141
#include "def.h"

docs/cpp-pointer.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ q = arr;
8989
```
9090
上面这种写法是二维数组指针的写法,其中`q`可以理解为一个指向`arr`第一个元素的指针,而后面的`[3]`则表示这个元素是一个长度为3的数组。
9191
92+
这两个名词很容易搞混,当你知道两个名词所表示的东西后就不应该再用这两个名词去形容了,因为可能过段时间你就忘记谁对应谁了,要么是元素类型是指针,要么是指向数组的指针,但把话说清楚肯定不会产生歧义。
93+
9294
### 3.3 二维数组 与 二维指针
9395
想把二维数组转为二维指针有下面这三种方法。
9496
```cpp
@@ -146,9 +148,9 @@ static double fee = 5.3;
146148
### 5.3 动态存储
147149
new从被称为****(heap)或**自由存储区**(free store)的内存区域分配内存。
148150

149-
数据的生命周期不完全受程序或函数的生存时间控制,也不受**作用域**[链接性](./cpp-variable.md#linkage)规则控制,完全由程序员掌管。
151+
数据的生命周期不完全受程序或函数的生存时间控制,也不受**作用域**控制,完全由程序员掌管。
150152

151-
通俗的讲,就是如果你new了一个变量,但不去delete它,无论在哪个代码块中,它都会一直留在内存当中。
153+
通俗的讲,就是如果你new了一个变量,但不去delete它,无论走到哪个代码块中,它都会一直留在内存当中。
152154

153155
## 6. 引用
154156
当你把变量a赋给一个引用变量b后,就相当于给a起了一个为b别名。当a或b改变时,b或a也会改变,本质上,它们是使用同一块内存空间的。
@@ -227,4 +229,17 @@ const int a = 100;
227229
const int& ay = a;
228230
```
229231

230-
还有些复杂的情况是指针,引用和常量三者搞在一起,这种就太过于复杂了,一般不会这么写。
232+
还有一种是指针类型的引用,前面说过,普通变量用来存储值,而指针变量用来一种存储地址,引用指针变量和引用普通变量没有本质区别,引用普通变量可以修改值,而引用指针变量则可以修改地址。
233+
```cpp
234+
int* a = new int(4);
235+
int*& ar = a;
236+
237+
ar = new int(5); // 直接换一个新的地址
238+
cout << *a << endl; // 5
239+
cout << *ar << endl; // 5
240+
241+
*a = 88; // 直接修改地址所在的内存
242+
cout << *a << endl; // 88
243+
cout << *ar << endl; // 88
244+
```
245+
而其他`&*``&&`等写法都是不存在的。

docs/cpp-variable.md

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ std::cout << R"+*(("hello") \n world)+*" << std::endl;
5858

5959
可以将一个指针指向某个硬件位置,其中包含了来自串行端口的时间或信息。在这种情况下,硬件(而不是程序)可能修改其中的内容。或者两个程序可能互相影响,共享数据。[[ref]](https://www.runoob.com/w3cnote/c-volatile-keyword.html)
6060

61-
编译器通常可能对某个变量进行优化,比如变量值**缓存在寄存器中**,程序在几条语句中多次使用了某个变量的值,则可能会直接使用寄存器中的值。这种优化假设变量的值不会被当前程序之外的程序进行修改
61+
如果对上面有关嵌入式的知识不太了解也没关系,我再举一个线程的例子。编译器通常可能对某个变量进行优化,比如变量值**缓存在寄存器中**,程序在几条语句中多次使用了某个变量的值,则可能会直接使用寄存器中的值。而另一个线程可能使用的是内存中的值,这样会导致两边不一致。volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值
6262

63-
正如前面所说的,改变这个值的可能不是程序本身,所以可以将变量声明为volatile,相当于告诉编译器,不要进行这种优化
63+
当然,线程这个例子是一个不太恰当的例子,因为多线程访问同一变量时,我们都会有读锁或者写锁进行保护
6464

6565
## 5. mutable
6666
可以用它来指出,即使结构或类变量为const,其某个成员也可以被修改。编译器可以在实际定义之后进行再链接
@@ -72,28 +72,22 @@ const data d {0};
7272
d.access = 1;
7373
```
7474
75-
## 6. 链接性和持续性
75+
## 6. static
7676
77-
这里涉及的知识点是链接性(linkage),当使用extern声明,变量将变为外部链接性。<span id="linkage"></span>
78-
79-
![](https://i.loli.net/2019/03/15/5c8ba1721f258.png)
80-
81-
82-
函数的链接性呢?C++不允许在一个函数中定义另外一个函数,因此所有函数的存储持续性都自动为**静态**的,即在整个程序执行期间都一直存在;并且链接性是**外部**的,即可以在文件间共享,所以上面例子中我们不需要对一个函数声明`extern`。
77+
静态(static)具有三个特性:
78+
1. 只执行一次
79+
2. 线程安全(C++11)
80+
3. 存在全局存储区
8381
84-
## 7. static
8582
上面图说明虽然静态变量static在整个程序执行期间都存在,但它意味着在
8683
1. 使用关键字static定义的全局变量的作用域为整个源文件,但是不能用于其他源文件(内部链接性)。定义在头文件则可以被导入到不同源文件。
8784
2. 在代码块中使用关键字static定义的变量被限制在该代码块内(局部作用域、无链接性)。
8885
3. 如果定义在[类](cpp-class.md)内部,则可在同类的多个对象之间实现数据共享。
8986
9087
需要明白,static变量不同于自动变量的是,虽然两者都需要在作用域范围内才能使用,但自动变量脱离作用域后便被回收,而static则是直到程序停止运行后才被回收。
9188
92-
### 7.1 局部静态
93-
局部静态(local static)具有两个特性:
94-
1. 只执行一次
95-
2. 线程安全(C++11)
96-
89+
### 6.1 局部静态
90+
根据静态的特性,使得局部静态的情况下会出现如下情况,
9791
```cpp
9892
struct Data { int x; };
9993
@@ -127,8 +121,15 @@ int main() {
127121
}
128122
```
129123

124+
## 7. 链接
125+
链接这个概念和编译过程有关,首先编译器会对源文件(`.cpp`)进行编译并生产目标文件(`.o`),如果有多个源文件,就会生成多个目标文件。然后就会进入链接阶段,把多个目标文件整合成一个可执行文件(`.exe`),之所以叫链接,是因为其中一个目标文件中调用的某个函数,它的实现可能在另一个目标文件里,所以要把这个目标文件和另一个目标文件进行链接。
126+
127+
链接的基础在于声明。能够进行链接的前提是能够进行声明,先有了声明,使用了声明,才能在链接阶段通过声明找到实现或定义。所以声明一个函数,也可以说作是使一个函数具备链接能力。
128+
129+
具体的编译过程会在编译章节里面说明,这里先提出链接是因为和后面讲到的extern有些关系。
130+
130131
## 8. extern
131-
关键字extern用于声明一个全局变量。你可以预先声明一个全局变量,相当于作为一个占位符,它允许你延迟定义一个变量
132+
关键字extern用于**声明**一个**全局变量**。注意是声明而不是定义,函数只要不实现函数体就可以预先声明,但变量的声明和定义通常是同时进行的,比如`int a;`,虽然没有赋值,但已经定义了一个`a`变量。`extern`关键字使得我们可以在任何作用域下声明变量,条件是这个变量在之后必须定义为全局变量
132133
```cpp
133134
int main(){
134135
{
@@ -139,7 +140,7 @@ int main(){
139140

140141
int gg = 9;
141142
```
142-
上面的例子gg的定义在main函数的后面,理论上,是不能在main里面使用gg的,但我们可以使用通过`extern int gg`声明gg是一个全局变量。除此之外,你还能用extern预声明一个全局函数。
143+
上面的例子gg的定义在main函数的后面,理论上,是不能在main里面使用gg的,但我们可以使用通过`extern int gg`声明gg是一个全局变量。除此之外,你还能用extern预声明一个全局函数,但函数本身就是可以声明的,这个用法有点鸡肋
143144
```cpp
144145
int main() {
145146
extern int test();
@@ -152,9 +153,11 @@ int test() {
152153
}
153154
```
154155

155-
再举个例子,我在file1.cpp 定义了一个变量`x`,而file2.cpp中想访问到这个变量,我们该怎么办?在python中直接使用import模块即可,但c++中你只能include一个`.h`文件。
156+
既然谈到了声明,是不是说明它也具备链接性。答案是肯定的,我们在头文件中用`extern`声明变量,随着这个头文件被导入到不同的源文件中,使得该变量在不同源文件中被定义或使用。
157+
158+
我在file1.cpp 定义了一个变量`x`,而file2.cpp中想访问到这个变量,我们该怎么办?
156159

157-
所以我们需要以`.h`文件作为中间人,在其中用`extern`**声明**该变量,使得它成为全局变量。注意这里仅仅是声明,而不是定义,说明并未分配存储空间
160+
所以我们需要以`.h`文件作为中间人,在其中用`extern`**声明**该变量。注意这里仅仅是声明,而不是定义,并未分配存储空间
158161
```cpp
159162
#ifndef HELLOWORLD_CH06_H
160163
#define HELLOWORLD_CH06_H
@@ -213,7 +216,7 @@ int a = 1; // don't do this
213216
```
214217
头文件里定义全局变量或函数(inline除外),一旦头文件被导入不同的cpp文件里,则会导致重复重复定义的问题。而使用extern只是声明,编译器允许重复声明。
215218

216-
如果想在头文件定义全局变量或函数,建议使用static,static具有只定义一次的特点,目前还未清楚编译器是如何处理的,但经过测试可知不会发生重复定义问题
219+
如果想在头文件定义全局变量或函数,可以将其定义static,因为static具有只执行一次的特点。编译器发现这个静态变量已经定义过了,就不会继续定义,便不会报multiple definition错误
217220
```cpp
218221
// def.h
219222
#ifndef DEF
@@ -235,7 +238,7 @@ extern还有一个作用通过声明`extern "C" { }`来把函数编译和链接
235238
// cmax.h
236239
#ifndef C_MAX_H
237240
#define C_MAX_H
238-
extern int max(int x, int y);
241+
int max(int x, int y);
239242
#endif
240243

241244
// cmax.c
@@ -318,7 +321,7 @@ using namespace Jill;
318321
```
319322

320323
### 9.1 使用命名空间的完整例子
321-
按照惯例,现在头文件中定义命名空间。同时对于命名空间中的变量,若想产生外部链接,需要声明为`extern`
324+
按照惯例,现在头文件中定义命名空间。同时对于命名空间中的变量,为了避免重复定义,需要声明为`extern`
322325
```cpp
323326
#ifndef HELLOWORLD_CH07_H
324327
#define HELLOWORLD_CH07_H

0 commit comments

Comments
 (0)