一个项目的生成过程
- 一个项目可以有多个源程序文件(.cpp)组成
- 每个源文件都会编译成一个目标文件(.o或者.obj)
- 如果源程序有多个,则会编译生成多个目标文件
- 这些目标文件最终会被链接,从而生成一个可执行文件
编译阶段所做的事情:
- 预处理
- 编译,包括词法分析、语法分析、目标代码、优化等
- 汇编,产生目标文件
c语言一般提供三种预处理功能
- 宏定义
- 文件包含
- 条件编译
用一个指定宏名替换一串内容。其中宏名就是一个标识符。
#define 宏名 被替换的内容
#define PI 3.1415926//用PI替换3.1415926
上述代码作用
用PI来代替3.1415926,那么在程序源码中写的是PI(也就是上述一般形式中的宏名),在预处理阶段,所有在该#define语句行之后的代码中出现的PI都会被替换成3.1415926
#define PI 3.1415926;
res=2*PI;//不报错,等价于2*3.1415926;;
res=PI*2;//报错,等价于3.1415926;*2;
#define命令出现在程序中函数的外面,宏名的有效范围是#define之后到本源程序文件结束,不能跨文件使用,如果需要在另一个源程序使用,则需要重新定义
#define 宏名(参数表) 被替换的内容
用上述“被替换的内容”代替“宏名(参数表)”
#define s(a,b) a*b
...
int area=s(3,2);
上述案例中,用了宏S(3,2),系统把3、2分别代替宏定义中的形参a、b,最终用3*2替换了S(3,2)。所以程序代码“intArea=S(3,2);”就等价于“int Area=3*2;”
对一般形式中提到的“被替换的内容”,要从左到右处理。如果“被替换的内容”中有“宏名”后列出的形参,如a、b,则将程序代码中相应的实参(可以是常量、变量或者表达式)代替形参,如果“被替换的内容”中的项并不是“宏名”后列出的形参,则保留,如上面a*b中的“*”就会被保留。
#define PI 3.1415926
#define s(r) PI*r*r
int main() {
double area;
area = s(3);//等价与3*3.1415926*3.1415926
return 0;
}
#define s(r) PI*(r)*(r)
这样S(1+5)在替换展开后才能变成3.1415926*(1+5)*(1+5)。
函数调用是先求出实参表达式的值,然后传递给形参,带参数的宏值进行简单的内容替换,宏展开时不求值,如上面的s(1+5),宏展开时并不求1+5的值,只是原样用实参替换掉形参
函数调用在程序运行阶段时执行,涉及内存分配等一列工作;而宏展开是在编译阶段进行,且宏展开时并不分配内存
宏的参数没有类型的说法,展开只是指定内容的替换
宏展开每进行一次,源程序代码都会有所增加,所以宏的次数增多,源程序代码就会增多,但函数调用不会使源程序代码增多
宏展开只占用编译时间,不占用运行时间,而函数调用占用运行时间(分配内存、传递参数、执行函数体、返回值等)
简单总结就是,宏在编译阶段进行,其展开只是进行简单的内容替换,不涉及内存分配等一系列问题,也正是因为内容替换的原因,因此在源程序中每使用一次宏展开,源程序代码量就会增加。
所谓“文件包含”,是指一个文件可以将另外一个文件的全部内容包含进来,也就是将另外的文件包含到本文件中。C语言中,通过#include命令来实现。其一般形式如下:
#include "文件名"
a.cpp源程序文件中原有的内容为A,最上面有一个#include "head.h",这意味着head.h文件中的内容B也属于a.cpp,所以最终a.cpp的完整内容应该是内容B在上面(因为#include语句在上面),内容A在下面,要认识到,使用了#include之后,就等价于把其他文件的内容包含到当前文件中来,所以当前文件的程序代码长度增加了。
#include所包含的文件名可以用"",也可以用<>,区别在于:
一般情况下,在生成可执行文件的过程中,源程序文件中的所有代码行都参加编译,但有时候希望对其中的一部分内容只在满足一定的条件下才进行编译,也就是对一部分内容指定编译的条件,也有的时候,希望当满足某条件时对一组语句进行编译,而当条件不满足时编译另外一组语句,这都叫条件编译。
条件编译用得也比较频繁,尤其是写一些跨操作系统平台的代码,例如这个代码既要求能在Windows下编译运行,也能在Linux下编译运行,但程序代码中有些特殊的系统调用函数只能在Windows下编译运行或者只能在Linux下编译运行,此时,就有必要使用条件编译
#ifdef 标识符
程序段1
#else
程序段2
#endif
当标识符被定义过(使用#define来定义),则对程序段1进行编译,否则对程序段2进行编译
#ifndef 标识符
程序段1
#else
程序段2
#endif
若标识符未被定义,则编译程序段1,否则编译程序段2
#if 表达式
程序段1
#else
程序段2
#endif
当指定表达式为真,则编译程序段1,否则编译程序段2
项目开发也许会面临跨平台的问题,为了增加程序代码在各平台之间的可移植性,往往采用条件编译,如果不用条件编译,就很难解决同一套程序代码在Windows平台下和Linux平台下都能够在不修改源代码的情况下编译通过并生成可执行文件的问题。
1.头文件head.h有定义
int g_globalh1=8;
2.头文件head2.h中有定义
int g_globalh2=5;
3.源程序文件MyProject.cpp
#include "head.h"
#include "headq2.h"
int main()
{
cout<
目前为止上述代码暂时不会出错,假如现在由于一些原因需要在head2.h中包含头文件head.h,即head2.h的内容修改为
#include "head.h"
int g_globalh2=5;
此时再编译主程序,就会出现重定义错误,因为将源文件MyProect.cpp中的宏定义
#include "head.h"
#include "headq2.h"
展开后的结果是
int g_globalh1=8;
int g_globalh1=8;
int g_globalh2=5;
显然globalh1被定义了两次,因此编译的时候提示出现重定义错误。
既然重复#include的问题时有发生,无法避免,那么如何解决这个问题呢?这就要从.h头文件本身入手,通过使用#ifndef、#define、#endif解决这个问题。
1.改造头文件head.h
#ifndef __HEAD__
#define __HEAD__
int g_globalh1=8;
#endif
2.改造头文件head2.
#ifndef __HEAD2__
#define __HEAD2__
#include "head.h"
int g_globalh2.h
#endif
如此修改后再次编译,不难发现,编译通过并能成功执行
通过使用#ifndef、#define、#endif的组合,避免了.h头文件中的内容被多次#include。
例如当head.h第一次被#include到MyProject.cpp中时,#ifndef HEAD条件成立,因此下面两行代码都被#include到MyProject.cpp中#define __HEAD__ int g_globalh1=8;
但是假如第二次head.h被#include到MyProject.cpp中时,#ifndef HEAD条件就不成立了(因为#define HEAD代码行的存在),这样,上面两行内容就不会再次被#include到MyProject.cpp中,从而避免了重定义等错误的发生。
参考:
《c++新经典》