我一直看到人们写
类
#ifndef CLASS_H
#define CLASS_H
//blah blah blah
#endif
问题是,为什么他们不对包含类函数定义的.cpp文件也这样做?
假设我有 main.cpp
,并且main.cpp
包括class.h
。该class.h
文件没有include
任何内容,那么怎么main.cpp
知道class.cpp
?
FILE_H
,而不是CLASS_H
。
我一直看到人们写
类
#ifndef CLASS_H
#define CLASS_H
//blah blah blah
#endif
问题是,为什么他们不对包含类函数定义的.cpp文件也这样做?
假设我有 main.cpp
,并且main.cpp
包括class.h
。该class.h
文件没有include
任何内容,那么怎么main.cpp
知道class.cpp
?
FILE_H
,而不是CLASS_H
。
Answers:
首先,解决您的第一个查询:
当您在.h文件中看到此内容时:
#ifndef FILE_H
#define FILE_H
/* ... Declarations etc here ... */
#endif
这是一种防止多次包含头文件的预处理器技术,由于各种原因,头文件可能会出现问题。在项目编译期间,通常会编译每个.cpp文件。简单来说,这意味着编译器将获取您的.cpp文件,打开#included
它的任何文件,将它们全部连接成一个大文本文件,然后执行语法分析,最后将其转换为一些中间代码,对其他文件进行优化/执行任务,最后生成目标体系结构的程序集输出。因此,如果文件#included
在一个.cpp下多次出现文件,编译器将其文件内容追加两次,因此,如果该文件中有定义,则会出现编译器错误,提示您重新定义了变量。当文件在编译过程中由预处理器步骤处理时,第一次到达其内容时,前两行将检查是否FILE_H
已为预处理器定义了文件。如果没有,它将定义FILE_H
并继续处理它与#endif
指令之间的代码。下一次预处理器看到该文件的内容时FILE_H
,对它的检查将为false,因此它将立即向下扫描到,#endif
并在此之后继续。这样可以防止重新定义错误。
并解决您的第二个问题:
在一般的C ++编程中,我们将开发分为两种文件类型。一个是扩展名.h,我们称其为“头文件”。它们通常提供函数,类,结构,全局变量,typedef,预处理宏和定义等的声明。基本上,它们只是为您提供有关代码的信息。然后我们有了.cpp扩展名,我们称其为“代码文件”。这将为这些函数,类成员,任何需要定义的结构成员,全局变量等提供定义。因此.h文件声明代码,而.cpp文件实现该声明。因此,我们通常在编译期间编译每个.cpp文件。文件添加到对象中,然后链接这些对象(因为您几乎看不到一个.cpp文件包含另一个.cpp文件)。
如何解决这些外部问题是链接器的工作。当您的编译器处理main.cpp时,它将通过包含class.h来获取class.cpp中的代码声明。它只需要知道这些函数或变量的外观(这就是声明给您的外观)。因此,它将main.cpp文件编译为某个目标文件(称为main.obj)。同样,将class.cpp编译为class.obj文件。为了生成最终的可执行文件,将调用链接器以将这两个目标文件链接在一起。对于任何未解析的外部变量或函数,编译器将在存根位置放置一个存根。然后,链接器将使用此存根,并在另一个列出的目标文件中查找代码或变量,如果找到了该链接,则它将两个目标文件中的代码组合到一个输出文件中,并将存根替换为函数的最终位置或变量。这样,你在main.cpp中的代码可以调用函数和使用变量class.cpp当且仅当他们宣布class.h。
我希望这可以帮到你。
的CLASS_H
是一个包括防护件 ; 它用于避免同一头文件被多次(通过不同的路由)包含在同一CPP文件(或更准确地说是同一转换单元)中,否则将导致多定义错误。
CPP文件不需要包含保护,因为根据定义,CPP文件的内容只能读取一次。
您似乎已经将include防护解释为与import
其他语言(例如Java)中的语句具有相同的功能;事实并非如此。该#include
本身就是大致相当于import
其他语言。
至少在编译阶段没有。
将c ++程序从源代码转换为机器代码的过程分为三个阶段:
class.h
插入到的位置 #include "class.h
。由于您可能会在多个位置包含在头文件中,因此这些#ifndef
子句避免了重复的声明错误,因为只有在第一次包含头文件时才对preprocessor指令进行定义。总之,声明可以通过头文件共享,而声明到定义的映射则由链接器完成。
这是对头文件完成的,因此即使在每个预处理的源文件中包含的内容不止一次,其内容也只会出现一次(通常是因为它是从其他头文件包含的)。第一次包含该符号时,尚未定义该符号CLASS_H
(称为include Guard),因此包含了该文件的所有内容。这样做定义了符号,因此,如果再次包含它,文件的内容(在#ifndef
/ #endif
块内)将被跳过。
不需要对源文件本身执行此操作,因为(通常)其他文件不包含该文件。
对于最后一个问题,class.h
应包含该类的定义,其所有成员的声明,关联的函数以及其他任何内容,以便包含它的任何文件都具有使用该类的足够信息。这些功能的实现可以放在一个单独的源文件中。您只需要声明即可调用它们。
main.cpp不必知道class.cpp中的内容。它只需要知道要使用的函数/类的声明,这些声明就在class.h中。
链接器链接使用class.h中声明的函数/类的位置与其在class.cpp中的实现之间的链接
通常期望诸如.cpp
文件之类的代码模块被编译一次并链接到多个项目中,以避免不必要的重复逻辑编译。例如,g++ -o class.cpp
将产生class.o
,然后您可以将其从多个项目链接到using g++ main.cpp class.o
。
#include
正如您所暗示的那样,我们可以将其用作我们的链接器,但是当我们知道如何使用我们的编译器以较少的击键次数和较少的重复编译次数而不是我们的代码以更多的击键次数和更多的浪费来正确链接时,这将是愚蠢的重复编译...
但是,仍然需要将头文件包含在多个项目中的每个项目中,因为这为每个模块提供了接口。没有这些头文件,编译器将不会知道.o
文件引入的任何符号。
重要的是要意识到,头文件是那些模块的符号定义的引入。一旦意识到这一点,便可以理解多个包含可能导致符号的重新定义(这会导致错误),因此我们使用include防护措施来防止此类重新定义。