序 C++多文件结构和编译预处理命令
C++编译过程
※在一个较大的项目中往往有多个源程序文件,每个源程序文件称为一个编译单元。这时C++语法要求每一个类的定义(声明)必须出现在所有使用这个类的编译单元中。惯用的做法是将类的声明写在头文件中。
※通常一个项目至少划分为三个文件:类声明文件(.h)、类实现文件(.cpp)、类使用文件(.cpp,主函数文件)。对于更复杂的程序,每一个类都有单独的声明和实现文件。
库文件及其头文件
(From CNBLOG) 简单的说:库文件提供定义/实现,其头文件提供声明。
C 代码的编译过程: 预处理(需要头文件) -> 编译 -> 汇编 -> 链接(需要库文件); 执行时可能还有动态链接过程。
在早期的编程语言中Basic Fortan没有头文件的概念,c++/c语言的初学者虽然会使用头文件,但是常常不明其理。
1、通过头文件来调用库功能。 在很多场合,源代码不便(或不准)向用户公布, 只要向用户提供头文件和二进制的库即可,用户只需要按照头文件中的接口声明来调用库函数, 而不必关心接口是怎么实现的,编译器会从库中提取相应的代码; 2、头文件能加强类型安全检查, 某个 接口被实现或被使用时的方式如果与头文件的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担; 3、头文件可以提高程序的可读性;
一、编译预处理
包含指令(#include)
预处理指令#include(两种书写方式):(include等同于文件拼接)
1、#include<>:按照标准方式搜索要嵌入的文件,该文件位于编译环境的include子目录下,一般用于嵌入系统提供的标准文件。比如标准头文件iostream。
2、#include “.h”:表示首先在当前目录下搜索要嵌入的文件,如果没有,再按照标准方式搜索。
头文件包含原理:
头文件不建议include其他。include尽量写到cpp文件里。
头文件重复包含:
可以用#pragma once
让头文件只被编译一次。
头文件交叉包含:
- 情形0:
A.h和B.h互相包含,B.h中创建了A.h中的类型。main.cpp中#include “A.h”和#include “B.h”。 - 解决方案0:
- 加上#pragma once指令。
(否则会出现包含深度过大错误。)
(加上后会出现〖未声明的标识符〗错误和〖缺少”;”在…前面〗错误。因为A.h有#pragma once指令,所以B.h中#include “A.h”失效了。) - 删除掉main.cpp对A.h的包含。
(A.h中的#include “B.h”失效。)
- 加上#pragma once指令。
- 情形1:
A.h和B.h互相包含,classB实现中创建了classA类型,classA实现中创建了classB类型。 -
解决方案1:
把实现从头文件移到实现文件。 - 情形2:
A.h和B.h互相包含,A.h中的classA依赖B.h中的classB1,B.h中的classB2依赖A.h中的classA,其中classB2继承自classB1。 - 解决方案2:
分离B.h为B1.h和B2.h。
头文件被其他cpp文件包含:
- 情形:
除了A.cpp包含A.h外,B.cpp也包含A.h。 - 解决方案:
- A.h和B.h都最好不包含任何头文件。main.cpp中A.h和B.h都要包含。
- A.cpp先包含所需的头文件,然后包含A.h。
- B.cpp包含A.h之前必须先包含所需的头文件。最后包含B.h。(否则会报错〖缺少”;”在…前面〗〖缺少类型说明符-假定为int〗〖意外的标记位于”;”之前〗)
注意事项:
在头文件类声明中声明指针成员不需要依赖指针所指类型,因为指针只是一个固定整型变量。
宏定义指令(#define和#undef)
——-宏——-
★计算机科学里的宏是一种抽象的,根据一系列预定义的规则替换一定的文本模式。解释器或编译器在遇到宏时会自动进行这一模式替换。对于编译语言,宏展开在编译时发生,进行宏展开的工具常被称为宏展开器。
—–宏定义——
★一个标识符被宏定义后,该标识符便是一个宏名。这时,在程序中出现的是宏名,在该程序被编译前,先将宏名用被定义的字符串替换,这称为宏替换,替换后才进行编译,宏替换是简单的替换。
—–#define指令——
1、在C/C++中可以用于定义符号常量。(在C++中通常用const关键字)
2、可以定义带参数宏。(例如#define Sqr(x) x*x)
3、可以定义空符号。(定义它的目的仅仅是表示它已经定义过,通常配合条件编译指令一起使用,起到某些特殊作用)
——#undef指令——-
★删除由#define定义的宏,使之不再起作用。
条件编译指令
※使用条件编译指令,可以限定程序中的某些内容要在一定条件下才能参与编译。
—–#if / #elif / #else / #endif用法——–
#if 表达式
程序段
#endif
—–#ifdef / #else / #endif用法——–
#ifdef 标识符
程序段
#endif
补充:defined操作符:
★这是一个操作符,不是指令。
★用法:当某标识符定义过则”defined(标识符)”表达式的值为非0,否则表达式值为0。
★”#ifdef 标识符”等同于”#if defined(标识符)”。
外部变量/外部函数/标准库
外部变量和外部函数
(源文件如果要共用全局变量,使用extern
关键字。)
※如果一个变量除了在定义它的源文件中可以使用外,还能被其他文件使用,那么就称它为外部变量。在其他文件中如果要使用它则须用extern 类型 变量名
先声明。(引用性声明)(对应的是定义性声明)
※命名空间作用域中定义的变量和函数,默认情况下都都是外部的,可以被其他编译单元访问。
※如果不想让变量、函数暴露给其他编译单元。可以用namespace{...}
匿名命名空间。(曾经是用static
关键字)
标准C++库
★在C语言中,系统函数,系统的外部变量和一些宏定义都是放置在运行库中。C++的库中除了保留了大部分C语言系统函数外,还加入了预定义的模板和类。标准C++库是一个极为灵活可扩展的可重用软件模块的集合。
———标准C++类和组件的6种类型———
1、输入输出类;
2、容器类和ADT(抽象数据类型);
3、存储管理类;
4、算法;
5、错误处理;
6、运行环境支持;
—————–引入命名空间——————-
★格式:using namespace 命名空间名
★如果不引入命名空间,要使用命名空间中标识就必须加上命名空间名和和域操作符,格式”命名空间名::标识符”。