加入CSDN是出于记录,希望有不足之处多加指正。
1、预处理
C语言程序在编译的时候分为4个步骤: 第一步叫预处理:主要工作把#开头的语句先展开。所以说所有的#号开头的语句并不属于C语言的语法范畴。 1、头文件 #include 2、定义宏 #define 3、取消宏 #undef 4、条件编译 # if ,# ifndef, #ifdef ,#else ,#elif ,# endif 5、显示错误 #error 6、修改文件名及行号 #line , #file 7、向编译器传输指令 #progrma
语法:
- 每一个逻辑行都只能出现一个预处理的指令,如果需要用到多个物理行可以使用反斜杠来链接 \
- 预处理是整个编译过程中的第一步:预处理 -->汇编–>链接
- 可以通过编译选项 -E 来查看预处理的结果
$gcc file.c -o file.i -E
2、宏定义
宏(macro)实际上就是要给字符串,在源码经过预处理后就被替换到文件中。
#define PI 3.14
int main()
{
printf("%f\n ", PI);
}
当前的PI 就是一个宏,一般来说我们习惯使用全大写的字来表示宏,语法上并没有约束必须使用大写。PI 只会出现在你的源代码中,预处理过程中以及被完整地替换掉了。
作用:
- 提高程序地可读性,使用一个有意义的单词来表示一个无意义的数字/ 一个很长的路径/ 名字。
- 使得程序迭代/ 修改某些值更加方便,只需修改一个地方,程序中所有用到该宏的地方都会被修改,不需要一个一格的找出来并修改。
- 提高程序的运行效率,如果使用宏函数来替换比较简单而且重复性的函数可以使得程序的效率提高。
3、无参宏
#define PI 3.14
#define LCD_SIZE 800*480*4
int main()
{
printf("pi:%f\n" , PI);
mmap(NULL, LCD_SIZE,...... );
mmap(NULL, 800*480*4,......);
}
处理我们自己定义的宏以外系统也有一些预定义的宏
#define NULL ((void *)0)
#define PORT_READ 0x01;
#define EV_SYN 0x00
#define EV_KEY 0x00
#define EV_REL 0x02
#define EV_ABS 0x03
mmap (NULL , LCD_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, 3 ,0);
mmap(((void *)0) 800*480*4 , 0x1 | 0x2 , 0x01, 3 ,0)
4、带参宏
带参宏在使用的时候需要传递参数有点类似函数的调用。
#define MAX(a,b) a>b? a:b
#define MIN(a,b) a<b? a:b
int main()
{
int j = 99, k=244;
printf("max:%d\n" , MAX (j , k));
printf("min:%d\n" , MIN (j , k));
return 0;
}
从以上代码中可以看出来,带参宏无非就是把参数与宏中所对应的参数进行逐一替换并在预处理时把替换的字符串直接替换掉代码中宏调用的位置。
注意:
- 直接替换文,不做任何的语法判断更不会有任何的运算。
- 宏在编译的第一阶段(预处理)时就被替换。
- 宏在所有使用的地方都会被展开,浪费一定代码空间,另一方面可以换来一定的效率(代码运行时不需要额外的切换时间)。
5、带参宏的副作用
由于宏在替换的时候没有任何的语法检查、类型检查、数据运算,因此在用起来的时候会出现一些逻辑的小问题。
#define MAX(a,b) (a)>(b) ? (a) :(b)
#define MAX_1(a,b) a>b ? a : b
printf("max:%d\n" , MAX(j,k == 200 ? 888 : 999));
printf("max_1:%d\n" , MAX_1(j,k == 200 ? 888 : 999));
从以上代码中可以发现,宏在定义的时候有括号和没有括号的运算结果时截然不同,说明我们在使用带参宏的时候为了保证运算结果的正确性/优先级的问题不会出现,在使用带参宏的时候每一个参数都可以使用()括号把他们括起来。
6、无值宏
宏定义不一定需要给该宏赋值,无值宏经常出现在条件编译与头文件中
#ifndef __INPUT_H__
#define __INPUT_H__
7、条件编译
通过一个宏来判断某一段代码是否需要编译
#if MACRO
.....
#endif
实例:
#include <stdio.h>
#define MACRO 0
int main()
{
printf("%d__%s__%s__\n" , __LINE__, __FILE__ , __FUNCTION__);
#if MACRO
printf("%d__%s__%s__\n" , __LINE__, __FILE__ , __FUNCTION__);
#else
printf("%d__%s__%s__\n" , __LINE__, __FILE__ , __FUNCTION__);
#endif
return 0;
}
多路分支:
#define MACRO1 0
#define MACRO2 0
#define MACRO3 1
#define MACRO4 0
int main()
{
printf("%d__%s__%s__\n" , __LINE__, __FILE__ , __FUNCTION__);
#if MACRO1
printf("%d__%s__%s__\n" , __LINE__, __FILE__ , __FUNCTION__);
#elif MACRO2
printf("%d__%s__%s__\n" , __LINE__, __FILE__ , __FUNCTION__);
#elif MACRO3
printf("%d__%s__%s__\n" , __LINE__, __FILE__ , __FUNCTION__);
#elif MACRO4
printf("%d__%s__%s__\n" , __LINE__, __FILE__ , __FUNCTION__);
#endif
return 0;
}
8、条件编译的使用场景
控制调试语句:在成语中用宏把条件编译的语句给包裹起来,通过GCC编译选在来随意控制调试代码启用/停用。
gcc test.c -o test -DMACRO
以上语句中 -D 意味着define, MACRO 就是程序中用来做条件编译的判断条件的一个宏,这样就可以完全不需要打开代码/修改就能实现代码中某些模块是否需要启用/关闭。
int main()
{
#ifdef MACRO
printf("%d__%s__%s__\n" , __LINE__, __FILE__ , __FUNCTION__);
#endif
printf("%d__%s__%s__\n" , __LINE__, __FILE__ , __FUNCTION__);
#ifndef MACRO1
printf("%d__%s__%s__\n" , __LINE__, __FILE__ , __FUNCTION__);
#endif
printf("%d__%s__%s__\n" , __LINE__, __FILE__ , __FUNCTION__);
return 0;
}
注意:
- ifdef 用来判断某个宏是否已经被定义,如果有定义则会编译他所包含的代码块
- ifndef 用来判断某个宏是否没有被定义,如果没有则会编译他所包含的代码块
|