第一章 How C++ Works?
项目:D:Dev:CppSeriesHello World
文章目录- The better C++ Course for primers by the Cherno(个人抄录与注解笔记)
- 前言
- 一、How C++ Works?
- (1)Welcome to C++.
- (2)How C++ Works?
- (2.1)C++总体概述
- (2.2)本节例子
- (2.3)How the C++ Compiler Works?
- (2.3.1)基本概念
- (2.3.2)本节例子
- (2.4)How the C++ linkers Works?
- (2.4.1)基本概念
- (2.4.2)本节例子
- 抄录总结
前言
以下是笔者关于学习The Cherno C++ Course的个人抄录和注解笔记。本文旨在抄录Cherno课程中所涉及的重要和根本知识,并依据个人所掌握的代码理解程度,添加注释说明。Cherno的讲解除了告诉你如何写好,写快C++代码的同时,他还告诉你–当我们在编写C++代码时,编译器背后到底发生了什么。
学而时习之,不亦说乎?
一、How C++ Works?(1)Welcome to C++.
- C++的使用目标对象:当你需要编写高性能代码时(如:编写大型的游戏项目时),那么C++是挺好的选择。当你需要编写一般性能代码时(如:编写工具类的程序),那么C#或者Java都是挺好的选择。(注:使用C++编写的代码其效率不一定比虚拟机语言(如:C#或者Java)更好。例如:当你同样在编写垃圾代码时,虚拟机语言一般对你所写的代码进行优化,但是C++不会。)
- 如果你在学习过程中,善用收索引擎(如:Google,Baidu,BIng)去解决你在学习课程中不理解的地方,同时,如果你对学习C++充满热情,并且真心愿意学习C++,你绝对没有问题的。
- 本课程将讨论如何使用C++库,如何避免C++编程中出现的常见错误,C++自身是如何工作的,如何正确使用指针和内存,如何正确使用模板,如何使用宏为多个平台进行编程,*如何自己编写数据结果,并同时让我们所写的 数据结构比库函数更好用,如何使用编译器内联函数来介绍底层优化,如何使用汇编语言(比如写Map和SSE)等等。
(2.1)C++总体概述
图1-2-1 C++运行工作的流程图。1
使用C++编写程序的简要步骤如下: 2
- 编写源代码:当你使用文本编辑器编写文本内容,并将其保存到文件中,这个文件就是程序的源代码。通常,我们将源代码的后缀拓展名设置为.cpp。
- 编译源代码:这意味着运行一个程序,要求编译器将源代码(如.cpp文件)翻译为主机(如:使用windows64位系统的电脑)使用的内部语言——机器语言。包含了翻译后的程序的文件,就是程序的目标代码(object code)(在windows系统下,目标代码的后缀格式为.obj)。
- 链接目标代码和其他代码,并生产可执行文件:通常,在我们编写C++程序时,都会使用C++标准库(library)。而在C++标准库当中,包含一些列计算机函数的目标代码,这些计算机函数可以帮助我们解决基本的编写任务。同时,链接是指,将目标代码(如:。obj文件)和其他代码一同组合起来,生成最终可执行行的二进制文件(.exe文件)(即,一般生活中,被大众称呼为应用软件。)
(2.2)本节例子
代码如下:
#includeint main() { std::cout << "Hello World!" << std::endl; std::cin.get(); }
注解:
- 预处理指令:#include
被称为预处理指令(任何以#开头的,都是预处理指令)。并且,当编译器接收到一个源文件时,它所处理的第一件事为,预处理你所有的预处理指令(当计算机在处理#include 这样的预处理指令时,该预处理指令发生在真正的编译之前
)。 - "iostream": 在预处理#include
时,iostream文件当中的所有内容都会被黏贴到目前这个main.cpp文件。这里,我们之所以include一个iostream,是因为我们需要函数cout的声明。cout函数使我们能够打印东西到控制台(console)。 - "main":main函数是我们程序的入口点(entry point)(即,当计算机运行我们的程序时,计算机会从main函数里的代码开始执行)。一般地,计算机会一行一行地执行我们地程序。但,有一些东西可以改变或者打破计算机的执行顺序。如:控制流语句(control flow statements)或者调用其他函数。 所以,本节程序第一个被执行的语句时std::cout << "Hello World!" << std::endl;然后是std::cin.get();。
- “返回值”:对于熟悉函数的同学,你们可能会注意到,main函数的返回类型其实是整型(int)。然而此处我们并没有返回值,这是因为main函数是个例外,你不需要从主函数返回任何类型的值,如果你不返回任何值,main函数会返回0,请记住,这只使用于main函数。
- std::cout << "Hello World!" << std::endl;这一语句,对于初学C++同学来说,可能看起来非常奇怪,但其实写成这样是非常不幸的,因为你第一次看它的时候确实看不出来意思。首先,这些向左的带角的括号<<,看起来有点像左位移符号(bit shift),其实是被重载的符号,你得把它看作一个函数。但实际上,运算符就是函数。 所以,我们在这边做的就是,我们把"Hello World!"字符串传入cout中,而cout帮助我们把它打印在控制台上。然后,我们传入一个endl(end line)。endl会告诉我们的控制台前进到另一行。
- cin.get();在这种情况下,其实就是等待我们输入回车键,然后才会接着执行下一行代码。(一般地,最后使用cin.get();语句,可以使控制台不立即关闭。)当然,我们底下一行什么也没有,所以我们的程序会在cin.get(); 这一行暂停,知道我们按下回车键。最后,当我们在这一行按下回车键时,main函数会返回0,说明我们的程序运行成功了。
(2.3.1)基本概念
- 问题引出:在开始学习C++是如何编译源代码时,让我们退后一步,想象一下大的蓝图是什么,C++编译器到底负责什么。
- 分析:我们使用文本编辑器编写关于C++的文本内容,就是文本(text)而已。我们需要某种方法将文本(text)转化成应用,从而在计算机运行。从text到可执行二进制文件(.exe),基本有两个主要操作发生者,一个是Compiler,另一个是linker。 本小节回答Compiler。C++编译器要做的工作就是把text转变为中继格式Object Files,然后Object Files会传入linker,linker会进行linking工作。简言之,当我们在讨论Compiler编译产出这些Object Files时,其实Compiler还进行其他几件工作。以下为Compiler进行的具体工作。
- 预处理阶段:首先,Compiler会预处理(pre-process)我们编写的代码,也就是所有的预处理指令(如:#include
)会在此时被评估。 - 标记和解析阶段:其次,Compiler在进行预处理操作之后,它会进入标记(tokenizing)和解析(parsing)阶段。该阶段把我们所编写的C++文本(text),处理成Compiler能懂和处理的语言,即抽象语法树(abstract syntax tree)。 所以,Compiler把我们编写的代码文本(text)转化成常数数据(constant data)或者转化成指令(instruction)。当Compiler创建了一颗抽象语法树后,就可以产生代码了,该代码才是真正CPU可以执行的机器码。
- 总结:
图1-2-2 Compiler工作的流程图。
当Compiler生成抽象语法树之后,我们也会得到一些其他数据,比如某个地方存储着我们所有的常数变量(constant variavles)。以上就是Compiler的基本工作内容,不是非常复杂。当然你的代码越来越复杂,它也越来越复杂。
(2.3.2)本节例子
代码如下:
main.cpp:
#includevoid log(const char* message); int main() { log("Hello World!"); std::cin.get(); }
log.cpp:
#includevoid log(const char* message) { std::cout << message << std::endl; }
- 结果:如果我们进入output目录debug下,你可以看到它生成了一个.exe。
而我们回到项目目录下,你可以看到它生成了main.obj,log.obj。
综上所述,Compiler给每个.cpp,也就是每个translation unity(编译单元),生成了.obj。本质上,你得意识到C++根本不在乎文件。文件这种东西在C++中不存在。 For example,在Java,你的类(class)名必须跟文件名相同,而你的文件夹结构也得跟package一样。之所以如此,是因为Java(虚拟机语言)需要某些文件存在;而C++完全不是这回事。C++没有文件这种东西。在C++中,文件只是用来个Compiler提供源码的某种方法。 你只需要告诉Compiler文件类型和编译器如何处理它。 例如,我可以新建一个.cherno文件让Compiler去编译,完全没有问题。
只要我告诉Compiler这是个C++文件,请按C++编译。你最好记住,在C++中,文件不代表任何东西,请记住,这非常重要。 (接下来Cherno还有一段十分钟的例子解析,并通过汇编语言,从底层展示编译器如何进行预处理,以及我们该如何从预处理角度优化我们编写的代码,由于笔者笔力有限,如果阅读到此处,笔者建议读者最好再观看Cherno关于How the C++ Compiler Works的解析。) - 总结:Compiler(这里指代C++编译器)根据我们提供的源代码文件(source file),生成目标文件(Object File)。目标文件(Object File)是由机器码构成的二进制文件。
(2.4)How the C++ linkers Works?
(2.4.1)基本概念
- 问题引出:So what is linking? What does the C++ linkers actually do?
- 分析:linking是编译源代码后的下一个执行阶段。一旦我们编译了文件,我们接下来需要一个链接(linking)的过程。linking的主要工作是找到每个符号和函数的位置,并将它们链接在一起。又由上一小节有,每个文件被编译成一个独立的.obj文件作为translation unity(编译单元)。实际上,这些编译单元(translation unity)没法沟通。如果,我们决定将程序写在多个C++文件的话,我们需要一种方法将这些文件链接到一个程序。 以上就是linker的主要目的。即使你没有外部文件中的函数,比如说,你已经将整个程序写在一个文件里了,应用程序仍然需要知道entry point(入口点)(即,main函数)在哪里。
(2.4.2)本节例子
代码如下:
math.cpp:
#includevoid log(const char* message) { std::cout << message << std::endl; } int Multiply(int a, int b) { log("multiply"); return a * b; }
-
分析:在此处,我们有两个函数log和multiply。multiply函数实际上调用了log函数,打印出multiply这个单词到控制台,然后返回a*b,该函数非常的简单。然而,这并不是一个实际的应用。因为,显然它不包含main函数。
你要意识到的第一件事情是,代码运行前有两个阶段——One is Compiling, the other is linking。以下是区分Compiling和linking的方法。 -
区分Compiling和linking的方法:如果你在VS下按下了Ctrl+F7,或者你按下了编译按钮,只有Compling会发生,而linking则完全不会发生。
然而,如果你build了你的项目,或者你按下了F5键,它会先Compling,然后再linking。 -
对上述代码按下Crtl+F7时:你会发现一切都Ok,因为Compiling成功了。
它产生了相应的math.obj文件,即(Object File),一切似乎都很好。然而,如果右击我的项目,并按下build,你会发现你实际上得到了一个linking Error:入口点必须被定义。
- 本节简要介绍了How C++ Works?
- 关于How the C++ Compiler Works? How the C++ linkers Works?的具体细节,还请观看Cherno的视频讲解。
- 其中如何区分Compiling Error和linkers Error是Cherno所讲述的重点内容,这对于初学者而言,是重要的间接经验。若果你有幸阅读到这里,请务必回看How the C++ linkers Works?这部分视频。
- 在下一章节,我们将关注C++当中如何更好地使用变量参数。
C++ Primer Plus(第6版 中文版 图1.3 编程步骤) ↩︎
C++ Primer Plus(第6版 中文版 段落1.4 程序创建的技巧) ↩︎



