编译预处理指令:对源程序编译之前做一些处理,生成扩展C源程序
1、种类:
- 宏定义 #define
- 文件包含 #include
- 条件编译 #if–#else–#endif等
2、格式:
- “#”开头
- 占单独书写行
- 语句尾不加分号
3、宏定义
在C语言源程序中允许用一个标识符来表示一个字符串,称为“宏”。被定义为“宏”的标识符称为“宏名”。在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”
宏定义是由源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的
格式:#define 宏名 宏替换值
作用:在进行编译预处理的时候,编译器会对程序中出现的所有的宏名使用宏字符串去进行替换–宏替换仅为简单字符替换
实例:
#include <stdio.h>
#define PI 3.1415 //宏名PI,替换所用的字符串3.1415int main(int argc, char *argv[])
{int r = 5;float s;s = (float)PI * r * r;printf("s = %7.2f\n", s);r = 4;s = (float)PI * r * r;printf("s = %7.2f\n", s);return 0;
}
该程序预处理以后main函数会变成如下形式:
int main(int argc, char *argv[])
{int r = 5;float s;s = (float)3.1415 * r * r;printf("s = %7.2f\n", s);r = 4;s = (float)3.1415 * r * r;printf("s = %7.2f\n", s);return 0;
}
在C中经常使用宏来定义常量–所以经常把C中的常量叫做符号常量
#include <stdio.h>
#define PI 3.1415 //宏名PI,替换所用的字符串3.1415
#define RC PI * 5.0
#define VAL PI + 5 //在定义复杂的宏的时候一定要注意避免出现副作用,因为宏替换只是简单的字符替换
#define VAL1 ((PI) + 5)int main(int argc, char *argv[])
{int r = 5;float s;s = (float)PI * r * r;printf("s = %7.2f\n", s);r = 4;s = (float)PI * r * r;printf("s = %7.2f\n", s);s = RC; //s = 3.1415 * 5.0printf("s = %7.2f\n", s);s = VAL * r * r; //PI + 5 * r * rprintf("s = %7.2f\n", s);s = VAL1 * r * r; //PI + 5 * r * rprintf("s = %7.2f\n", s);return 0;
}
[root@localhost 10_bits]# gcc test3.c -o test3
[root@localhost 10_bits]# ./test3
s = 78.54
s = 50.26
s = 15.71
s = 83.14
s = 130.26
带参数宏:
#include <stdio.h>
#define PI 3.1415 //宏名PI,替换所用的字符串3.1415
#define MAX(x, y) x > y ? x : y //x和y成为带参宏的形参
int main(int argc, char *argv[])
{int num1 = 12;int num2 = 21;int res = MAX(num1, num2); //在使用带参宏的时候要将对应的实参传递给形参 -- 和函数调用是一样的printf("res = %d\n", res);return 0;
}
上述代码进行宏替换以后称为:
int main(int argc, char *argv[])
{int num1 = 12;int num2 = 21;int res = num1 > num2 ? num1 : num2; //在替换过程中除了将带参宏定义中的宏值替换宏名以外,还会将实参替换宏定义中的形参printf("res = %d\n", res);return 0;
}
带参宏和函数调用的区别:
- 带参数宏的代码在编译过程以后,宏代码会变成程序的本地代码(宏的代码和程序的代码在同一环境下),而函数调用中函数的代码要靠链接进来
- 对于一些简单的算法(算法中包含的语句不多的情况下),使用带参宏相比于使用函数调用,程序的运行效率要高
- 带参数的宏经常可以适用于不同的参数类型和返回值类型,所以他的通用性更好;但是他的安全性更差。在编译过程中,如果说是函数调用的话,对于实参传递给形参,对于将函数的返回值赋值给其他的对象都会进行类型判定,如果出现类型不兼容,在编译的过程中就会发现;而使用带参宏,由于在编译过程中只进行替换,往往不能发现类型方面的问题,经常都会在程序运行过程中出现意想不到的错误
#include <stdio.h>
#define PI 3.1415 //宏名PI,替换所用的字符串3.1415
#define MAX(x, y) x > y ? x : y/*
int MAX(int x, int y)
{return x > y ? x : y;
}
*/int main(int argc, char *argv[])
{int num1 = 12;int num2 = 21;double dnum1 = 1.23;double dnum2 = 2.32;double dres = MAX(dnum1, dnum2);int res = MAX(num1, num2);//res = MAX(dnum1, dnum2);printf("res = %d\n", res);printf("dres = %7.2f\n", dres);return 0;
}
[root@localhost 10_bits]# gcc test3.c -o test3
[root@localhost 10_bits]# ./test3
res = 2
dres = 2.32
4、文件包含
在编译预处理过程中,会将包含的文件的内容替换到本文件 中
在头文件中定义数据结构,函数原型
在一个c源程序中实现函数
在主程序中调用函数
比如在stu.h中定义学生结构体和函数原型
typedef struct Stu
{int no;char name[12];float score;
}Stu;void show_stu(Stu tmp);
在stu.c中实现stu.h中定义的函数
#include "stu.h"
#include <stdio.h>void show_stu(Stu tmp)
{printf("%d, %s, %7.2f\n", tmp.no, tmp.name, tmp.score);
}
在main.c中调用函数
#include <stdio.h>
#include "stu.h"int main(int argc, char *argv[])
{Stu st = {101, "Jack", 88.5};show_stu(st);return 0;
}
[root@localhost student_manager]# gcc stu.c main.c -o main
[root@localhost student_manager]# ./main
101, Jack, 88.50
5、条件编译
#include <stdio.h>int main(int argc, char *argv[])
{int num1 = 10;int num2 = 20;int res;#ifdef SUMres = num1 + num2;#elseres = num2 - num1;#endifprintf("res = %d\n", res);return 0;
}
该代码编译预处理以后会变成如下:
int main(int argc, char *argv[])
{int num1 = 10;int num2 = 20;int res;res = num2 - num1;printf("res = %d\n", res);return 0;
}
如果程序原型为:
#include <stdio.h>
#define SUMint main(int argc, char *argv[])
{int num1 = 10;int num2 = 20;int res;#ifdef SUMres = num1 + num2;#elseres = num2 - num1;#endifprintf("res = %d\n", res);return 0;
}
则编译预处理以后为:
int main(int argc, char *argv[])
{int num1 = 10;int num2 = 20;int res;res = num1 + num2;printf("res = %d\n", res);return 0;
}
所以条件编译是根据条件十分满足来确定需要编译的语句块
#ifdef … #else … #endif结构是根据指定的标识符是否定义来确定要编译的语句,如果指定标识符在条件编译语句之前已经定义了,选择ifdef部分的语句进行编译(ifdef或者else部分可以是多条件语句,并且不需要使用大括号),如果指定的标识符没有定义则选择else部分的语句进行编译
条件编译还有另外一种结构#ifndef … #else … #endif
所以前面多文件工程应该写成
stu.h
#ifndef __MYHEAD
#define __MYHEAD
typedef struct Stu
{int no;char name[12];float score;
}Stu;
#endif
head1.h
#ifndef __HEAD_1
#define __HEAD_1
#include "stu.h"void show_stu(Stu tmp);#endif
stu.c
#include "stu.h"
#include "head1.h"
#include <stdio.h>void show_stu(Stu tmp)
{printf("%d, %s, %7.2f\n", tmp.no, tmp.name, tmp.score);
}
main.c
include <stdio.h>
#include "stu.h"
#include "head1.h"int main(int argc, char *argv[])
{Stu st = {101, "Jack", 88.5};show_stu(st);return 0;
}
我们也经常使用条件编译语句来进行调试
#include <stdio.h>
#define SUM
//#define DEBUGERint main(int argc, char *argv[])
{int num1 = 10;int num2 = 20;int res;#ifdef DEBUGERprintf("ok1\n");#endif#ifndef SUMres = num1 + num2;#elseres = num2 - num1;#endifprintf("res = %d\n", res);#ifdef DEBUGERprintf("ok2\n");#endifreturn 0;
}