[重点!]头文件&源文件&编译&链接

C/C++支持分离式编译:一个程序可以分成多个部分保存在各个文件(头文件、源文件)中,在链接之前,各个文件(无论源文件还是头文件)是相互独立、没有关联的

分离式编译是指一个完整的程序或项目由若干个源文件共同实现,每个源文件单独编译生成目标文件,最后将该项目中的所有目标文件连接成一个单一的可执行文件的过程。

在这里插一嘴:

==#include==的作用

这是一个宏定义,众所周知,宏定义就是替换,比如a.cpp中要包含a.h,写作#include”a.h”

这一行就是将#include后的”a.h”替换为a.h中的所有代码

头文件里的内容就在这里“展开”了

但这并不能说明a.cpp和a.h是有关联的,只是a.cpp中包含了a.h,包含并不意味着有关联

因为#incldue是宏定义,是替换,跟其他的宏定义一样,比如#define N 100

众所周知,==在定义变量(自定义)或者函数时,头文件保存变量(自定义)和函数的声明,源文件保存变量和函数的实现==

头文件的作用

C/C++编译采用的是分离编译模式。在一个项目中,有多个源文件存在,但是它们总会有一些相同的内容,比如用户自定义类型、全局变量、全局函数的声明等。将这些内容抽取出来放到头文件中,提供给各个源文件包含,就可以避免想相同内容的重复书写,提高编程效率和代码安全性。所以,设立头文件的目的主要是:提供全局变量、全局函数的声明或公用数据类型的定义,从而实现分离编译和代码复用。

概括的说,头文件有如下三个作用。

  1. 加强类型检查,提高类型安全性。 使用头文件,可有效地保证自定义类型的一致性。虽然,在语法上,同一个数据类型(如一个class)在不同的源文件中书写多次是允许的,程序员认为他们是同一个自定义类型,但是,由于数据类型不具有外部连接特性,编译器并不关心该类型的多个版本之间是否一致,这样有可能会导致逻辑错误的发生。
  2. 减少公用代码的重复书写,提高编程效率。 程序开发过程中,对某些数据类型或者接口进行修改是在所难免的,使用头文件,只需要修改头文件中的内容,就可以保证修改在所有源文件中生效,从而避免了繁琐易错的重复修改。
  3. 提供保密和代码重用的手段。 头文件也是C++代码重用机制中不可缺少的一种手段,在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制库即可。用户只需要按照头文件的接口声明来调用库函数,而不必关心接口的具体实现,编译器会从库中连接相应的实现代码。(封装性)

但是其实头文件和源文件并没有关联

比如在a.h中声明了一个类a,包含成员变量和成员函数的声明,在a.cpp中包含类a成员函数的定义(实现)

其实在编写的时候,a.h和a.cpp没有关联,编译器并不知道它俩的关系

a.cpp中对于类a的成员函数进行了定义,但a.cpp中并没有类a的声明

而a.h和a.cpp并没有关联,也就是说编译器不知道a.cpp中的类a在哪儿声明的,而类a如果没有声明,这就是一个错误

所以这就是为什么a.cpp一定要包含a.h

如果再有一个b.h和b.cpp以及一个包含main函数的main.cpp

如果b中想要使用类a,则在b.h中包含类a的声明和定义就可以

我们知道,main.cpp中想要使用某个现有的变量或者函数,只要包含对应的头文件就可以

那么是不是只需要在b.h中包含头文件a.h(#include”a.h)就可以的

这是不可以的,因为a.h和a.cpp是没有关联的:a.h中只有a的声明,没有a的定义

如果此时运行的话,在运行时会报错:缺少a的定义

但是如果不运行是不会报错的,因为a.h中虽然没有a的定义,但是有声明,并没有语法和逻辑错误

缺少a的定义是属于编译错误

正确的做法是b.h中包含a.cpp而不是a.h

因为a.cpp中是对类a的成员函数的实现,而a.cpp中又包含a.h

话说回来,为什么main.cpp中可以包含头文件,不用包含源文件?因为链接

编译

编译是对项目中所有的源文件(注意只是源文件,不是头文件)进行编译,将它们“翻译”成为机器能识别的机器语言,每个源文件被编译后会生成一个对应的目标文件,里面是源文件代码被翻译成的机器语言

头文件是不进行编译的,理由在下面

众所周知,main函数是程序的入口,要想执行程序,就要执行main函数

含有main函数的源文件,在这里我叫它main文件吧

main文件也是源文件,也和其他源文件一样,在编译的时候会进行编译

main文件中会包含头文件

可是头文件中只有声明,没有定义,那怎么能用对应的变量和函数呢

链接

在编译之后,每个源文件都会生成一个目标文件

在执行程序时,编译之后,进行链接

main文件中含有头文件,链接就是通过头文件,找到对应的实现头文件中声明的内容的源文件,再找到这些源文件的目标文件,将这些目标文件跟main文件的目标文件“链接”起来,形成一个结合体打包起来——可执行文件

所以,因为有链接这一步,main文件中只包含头文件就行,因为链接时可以找到对应的源文件

而且链接是对于main文件而言的,也就是这一步只能用在main文件上,将main文件的目标文件 与 main文件包含的头文件对应的源文件的目标文件 结合起来

跟编译不同,一般的源文件不执行链接这个步骤,因为这是没有意义的,只有main文件是程序的接口

==总结==

==只有main文件才能只包含头文件,不包含源文件==

==只有库函数或者库里的类被一般源文件调用的时候,一般的源文件可以只包含对应的库头文件====一般的源文件想要调用别的 自定义的类 或者 自定义的函数 的时候,要在其头文件中包含对应的源文件而不是头文件==

==在定义变量(自定义)或者函数时,头文件保存变量(自定义)和函数的声明,源文件保存变量和函数的实现==

源文件如何根据#include来包含头文件:

  1. 系统库自带的头文件(库文件)用尖括号括起来,这样编译器会在系统库文件目录下查找。
  2. 用户自定义的文件用双引号括起来,编译器首先会在用户目录下查找,然后在到C++安装目录(比如VC中可以指定和修改库文件查找路径,Unix和Linux中可以通过环境变量来设定)中查找,最后在系统文件中查找。