学习油管大佬C++的一些随笔集

事情的起由是同学问我C++学得怎样的时候听我说就啃了谭浩强之后,叫我看一下这个油管的讲,事实上愈到后面,就真的啃不下去了,最终定格在了80集左右,感觉有空的话多看看这些C++知识还是可以的。链接如下:
Cherno C++

1.编译器是怎么工作的:

计算机底层运行的都是01二进制串,要运行类似这样的代码:

#include<iostream>
int main()
{
std::cout<<"hello world!";
return 0;
}

需要先将其转化为二进制机器码才可使机器正常运行,而转化这些代码为机器码的过程就叫做编译。

编译的第一步————配置(configure)

配置说白了就是电脑的环境不同,所下载的软件放在的电脑的哪个位置,所生成的编译文件要放哪。这可能是比较繁琐的过程,相信大家都有过为软件配置环境的过程。可以抽象的想象计算机像一间房子,不同的计算机的房子都不一样,同样的进门左转,小明家是厕所,小红家是厨房,进错了空间就完不成相应的目的了。所以,配环境是第一步,也是事半功倍的一步,所以还是要慢慢来,不然后边都白搞了,但是环境配好后就是起飞的第一步了。

编译的第二步————确定标准库和头文件的位置

这一步其实还是属于配置环境这一步。当你的源码中有类似

#include<iostream>

等等这些官方标准库函数和头文件时,需要告诉编译器它们在哪里,然后才才能将使用到它们的函数时能够跳到那一地方,将其代码进行编译。具体的操作就是通过配置文件,给出几个存放这些官方(standard library)和头文件(header)的目录,让编译器在遇到这些地方时能够在这些目录去找,而不是显示”找不到头文件或未声明相应的头文件”

编译的第三步————确定依赖关系

对于一个大型项目来说,源码文件的编译之间往往有依赖关系,比如用户自定义的头文件时,需要先编译。就好像定义一个类时,往往在头文件定义成员(往往包括数据成员和函数成员),然后在类实现cpp中要引入用户自定义的类头文件,进而写具体实现相应成员函数的代码实现。这时候就需要编译器确定编译的先后顺序。假定现有A文件依赖于B文件,此时编译器应要保证以下两点:

(1) 只有在B文件编译完成后,才开始编译A文件;
(2) 只有当B文件发生变化时,A文件会被重新编译。

编译顺序保存在一个叫做makefile的文件中,里面就有列出了相应的编译顺序,确定了哪个文件先编译,哪个文件后编译。而这一makefile文件是由configure脚本生成的,这也就解释为什么要configure是编译的第一步了,配置错误的话,编译器也不知道该生成怎样的makefile文件了。

编译的第四步————头文件的预编译

不同的源码文件,可能引用同一个头文件(如)。在编译的时候,头文件也必须一起编译。为节省时间,编译器会在编译源码之前,先编译头文件。这就保证了头文件只需编译一次即可,不必每次调用的时候都需重新编译。(事实上这里涉及到一个概念————“链接”)。

当然,并不是所有头文件的内容都会被预编译。用来声明#define命令的,就不会预编译。

编译的第五步————预处理(Preprocessing)

预编译完成后,编译器就开始替换掉源码中的头文件和宏。再具体的说明如下:

#include<iostream>
int main()
{
std::cout<<"hello world!";
return 0;
}

这样看似简单的代码就会编译为:
标准库中的函数的定义,包括但不限于上述代码中用到的冠以"<<"符号的定义,函数的重载以及定义等一大堆头文件的内容。其会很长很长,关键还是看所引入的那个库的大小了。注意这里引入的也只是头文件中的声明,具体的实现代码还要等到下面所说的链接部分。
接着才是上述写的代码的编译。

此时,插入的
另外,编译器在这一步还会移除与代码运行无关的注释。

编译的第六步————编译(Compilation)

在预处理(Preprocessing)后,编译器就开始生成机器码。经典的《计算机科学与技术导论》上面的步骤是:先把源码转为汇编码,然后将汇编码转为机器码。

最终得到的转码后的文件称为对象文件(Object file)

编译的第七步————连接(Linking)

前面所创建的对象文件还不能运行,必须要进一步转化为可执行文件(.exe)。事实上,这时候需要做的就是将先前(Preprocessing)所替换的类似函数声明的地方,引入相应的函数代码(通常是后缀名为.lib和.a的文件),添加到可执行文件中。这叫做链接(Link)。这种通过拷贝,将外部函数库添加到可执行文件中的方式,叫做静态链接(static linking),后面还会提到动态链接(dynamic linking)

第八步 安装(Installation)

上一步所生成的链接是在内存中进行的,即所生成的可执行文件(.exe)还在内存中。下一步,则需将生成的可执行文件保存到用户事先所指定的安装目录中去。

表面上,这一步很简单,就是将可执行文件(连带相关的数据文件)拷贝过去就行了。但是实际上,这一步还必须完成创建目录、保存文件、设置权限等步骤。这整个的保存过程就称为”安装”(Installation)。

编译的第九步————操作系统连接

可执行文件(.exe)成功安装后,必须以某种方式通知操作系统,让其知道可以使用这个程序。

这就要求在操作系统中,登记该程序的元数据:文件名、文件描述、关联后缀名等。

这就叫做”操作系统连接”。

编译的第十步————生成安装包
到这里时,对源码的编译过程基本上已经结束。但是,如果你只有源码交给用户,他们会认定你肯定是一个不友好的家伙。大部分用户想要的是直接能够运行的二进制可执行文件。这就要求开发者将上一步生成的可执行文件做成可以分发的安装包。(感觉这一步就跟我们日常生活很接近了,那就是上网下载某一个软件的安装包,然后执行安装程序后就可以直接用了~)

因此,编译器还必须有生成安装包的功能。通常是将可执行文件(连带相关的数据文件),以某种目录结构,保存成压缩文件包,交给用户。

编译的第十一步————动态链接(Dynamic linking)

正常情况下,到这一步时,程序已经可以运行了。至于可执行程序在运行期间(runtime)发生的事情,与编译器一概无关。但是,开发者可以在编译阶段选择可执行文件连接到外部函数库的方式,到底是静态链接(在编译时连接),还是动态连接(在程序运行时连接)。

这里解释一下动态连接的概念:
前面已经说过,静态连接就是把外部函数库拷贝到可执行文件中。这样做的好处是,适用范围比较广,不用担心用户运行的电脑中缺少某个库文件,但缺点是这样生成的安装包会比较大,而且在多个应用程序之间运行时,无法共享库文件。动态连接的做法正好相反,外部函数库不进入安装包,只在运行时动态引用。由此带来的好处是,安装包会比较小,多个应用程序可以共享库文件;其缺点就是依赖用户已经事先安装好了库文件,而且版本和安装位置都比较符合要求,否则就不能够正常运行。

现实中,大部分软件采用的是动态连接,共享库文件。这种动态共享文件在Linux平台中的后缀名为.so的文件,Windows平台中是.dll文件,Mac平台中是.dylib文件。