目录
预定义符号介绍
编辑
预处理指令 #define
#define 定义标识符
#define 定义宏
#define 替换规则
#define中#和##的使用
带副作用的宏参数
宏和函数的对比
命令行定义
预处理指令 #undef
预处理指令 #include
头文件被包含的方式:
本地文件包含
库文件包含
预处理指令 条件编译
1.常量条件编译
2.多个分支的条件编译
3.判断符号是否被定义
4.嵌套使用条件编译
预定义符号介绍
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
__FUNCTION__//当前代码所在函数
这些符号都是内置,可以通过这些符号记录一些代码信息或者说是日志信息,可以根据这些信息分析代码哪里出现问题
预处理指令 #define
#define 定义标识符
define name stuff
举例:
define MAX 100
如果在写代码的过程中使用了该符号,它会在预编译的阶段把该符号替换成相对应的stuff部分, 简单来说就是在代码中使用MAX,他会在预编译阶段把它替换成100
define不止可以定义常量,还可以定义类性相当于重新起个名字,甚至可以定义语句
#define MAX 1000
#define reg register //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;) //用更形象的符号来替换一种实现
#define CASE break;case //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \date:%s\ttime:%s\n" ,\__FILE__,__LINE__ , \__DATE__,__TIME__ )
其实就是把标识符后面的内容在预编译阶段全部替换上去,可以减少一定代码冗余,以及方便修改
可以加 ‘;’ 但是会把 ‘;’ 一起替换上去,可能会影响原代码,例如:
#define M 100;
int main()
{
int a = M;
//预编译之后就会变成
int a = 100;;
}
define定义不是语句不用加 ‘;’它的单位是行,如果嫌写在一行太长就可以用斜杠续行,方便自己与他人阅读
#define 定义宏
#define name( parament-list ) stuff
举例:
#define SQUARE( x ) x * x
name后面不能跟空格,要不然它会被认为是定义标识符,从而把后面的内容理解为stuff与定义标识符不同宏是有参数的,它可以分为两个部分替换,一个是参数部分替换,一个是除参数部分替换
这里需要注意的是宏与函数不同,宏本质是替换,如果我们在参数部分传进去一个表达式,这个表达式不会算出一个结果,而是直接放进去,可能会因为操作符的优先级不同而导致结果不是预期所想,例如:
#define SQUARE( X ) X * X
int main()
{
int a = SQUARE(3+1);//预期结果16
return 0;
}
//预编译之后
#define SQUARE( X ) X * X
int main()
{
int a = 3+1*3+1;//7
return 0;
}
要避免这种问题,本质上是要在替换时避免歧义,要想避免歧义就应该把宏定义的内容分离出来,就是加上括号,而宏内部又有参数的替换,所以又要避免参数替换与除参数部分替换(这里就称宏的框架吧)发生歧义,所以参数部分又要从宏的框架中分离出来。例如:
#define SQUARE( x ) ( (x)*(x) )
#define 替换规则
#define中#和##的使用
#:
当我们在定义宏时想在字符串中使用参数,就可以使用#,它可以把参数转成字符串,此操作可以做到一些字符串做不到的事情,例如:我想实现一个根据不同参数输出不同字符串的功能
使用用函数:
#include<stdio.h>
void My_Print(char name)
{printf("我是%c\n", name);
}
int main()
{char a = 'a';My_Print(a);char b = 'b';My_Print(b);char c = 'c';My_Print(c);char d = 'd';My_Print(d);}
使用宏:
#include<stdio.h>
#define PRINT(X) printf("我是"#X"\n");
int main()
{PRINT(a)//相当于将a替换成"a",就变成printf("我是""a""\n");PRINT(b)PRINT(c)PRINT(d)}
我们发现使用宏就不用大费周章去定义变量和函数,直接传文本进去就能使用
##:
可以把两个传进来的文本合成为一个文本
例如:
#include<stdio.h>
#define UNION(X,Y) X##Y;
int main()
{int a = UNION(12, 3)printf("%d", a);//输出123
}
带副作用的宏参数
x+1;//不带副作用
x++;//带有副作用
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?
这里我们得知道预处理器处理之后的结果是什么:
z = ( (x++) > (y++) ? (x++) : (y++));
所以输出的结果是:
x=6 y=10 z=9
宏和函数的对比
宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。
#include<stdio.h>
#define MAX(a, b) ((a)>(b)?(a):(b))
int Max(int a, int b)
{return a > b ? a : b;
}
int main()
{int a = 10;int b = 20;int ret=Max(a,b);printf("%d %d", ret, MAX(a, b));return 0;}
那为什么不用函数来完成这个任务?
1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。 所以宏比 函数在程序的规模和速度方面更胜一筹 。我们进入反汇编看一下比较一下就清楚了
2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之 这个宏怎可以适用于整形、长整型、浮点型等可以用于> 来比较的类型。 宏是类型无关的 。
当然和宏相比函数也有劣势的地方:
1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。2. 宏是没法调试的,因为宏是在预编译阶段就完成替换,而调试是在程序运行时调试,也就是已经变成可执行文件,所以在调试阶段就看不见宏做的那些工作。3. 宏由于类型无关,也就不够严谨。4. 宏可能会带来运算符优先级的问题,导致程容易出现错。 宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型 ,但是函数做不到。
总结:当需要实现一个简单的功能使用宏好,复杂就用函数好
命令行定义
在命令行时定义,就是预处理指令不在代码内部,而是用于启动编译过程添加,也就是预编译之前,简单来说就是不会出现在源文件。
#include <stdio.h>
int main()
{int array [ARRAY_SIZE];int i = 0;for(i = 0; i< ARRAY_SIZE; i ++){array[i] = i;}for(i = 0; i< ARRAY_SIZE; i ++){printf("%d " ,array[i]);}printf("\n" );return 0;
}
gcc -D ARRAY_SIZE=10 programe.c
预处理指令 #undef
用于取消define定义
#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。
预处理指令 #include
头文件被包含的方式:
本地文件包含
#include "filename.h"
本地文件包含查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标 准位置查找头文件。 如果找不到就提示编译错误。
库文件包含
查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。这样是不是可以说,对于库文件也可以使用 “” 的形式包含?答案是肯定的, 可以 。但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。
#include <filename.h>
避免嵌套文件包含
#pragma once
例如:
#include<stdio.h>
#define MAX(a, b) ((a)>(b)?(a):(b))
#undef MAX
int main()
{int a = 10;int b = 20;printf("%d ", MAX(a, b));return 0;}
预处理指令 条件编译
条件满足就编译,不满足被条件编译框住的部分就不让编译,用法与if语句类似,但是if存在于可执行文件中,也就是说程序无论走不走if都会在可执行文件中,而条件编译不同,条件满足就编译,不满足在预编译阶段就删除了,它不会存在于预编译之后文件。
1.常量条件编译
//格式
#if 常量表达式
//...
#endif//例如:------------------------------------
int main()
{
#define __DEBUG__ 0
#define __RELEASE__ 1
#if __DEBUG__<__RELEASE__//常量表达式由预处理器求值。printf("hello world");
#endif
}
2.多个分支的条件编译
//格式
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif//例如-----------------------------------------
#define VERSION 2#if VERSION == 1#define MESSAGE "Version 1.0"
#elif VERSION == 2#define MESSAGE "Version 2.0"
#else#define MESSAGE "Unknown version"
#endif#include <stdio.h>int main() {printf("%s\n", MESSAGE);return 0;
}
3.判断符号是否被定义
//格式--------------------------------------------------
//两种不同的写法和两种不同的操作
//如果符号存在
#if defined(symbol)
//...
#endif#ifdef symbol
//...
#endif//如果符号不存在
#if !defined(symbol)
//...
#endif#ifndef symbol
//...
#endif//例如:-----------------------------------------------------
//如果符号存在
#include<stdio.h>
#define DEBUG_MODE
int main() {
#if defined(DEBUG_MODE)printf("This is a debug message.\n");
#endif#ifdef DEBUG_MODEprintf("This is a debug message.");
#endifreturn 0;
}//如果符号不存在
int main() {
#if !defined(DEBUG_MODE)printf("N0 debug message.\n");
#endif#ifndef DEBUG_MODEprintf("No debug message.");
#endifreturn 0;
}
4.嵌套使用条件编译
#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif
#endif