c语言基础——预处理详解
Posted 努力学习的少年
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c语言基础——预处理详解相关的知识,希望对你有一定的参考价值。
目录
1.预处理是什么?
预编译又称为预处理 , 是做些代码文本的替换工作。处理 # 开头的指令 , 比如拷贝 #include 包含的文件代码, #define 宏定义的替换 , 条件编译等,就是为编译做的预备工作的阶段,主要处理#开始的预编译指令,预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。
预处理主要工作:
1.头文件的包含 2.define宏定义的替换 3.条件编译的处理
2.预定义符号
- __FINE__ //进行编译的源文件
- __Line__ //文件当前的行号
- __Date__ //文件被编译的日期
- __TIME__ //文件被编译的时间
- __STDC__ //如果编译器遵循ANSI,其值为1,否则则未定;
上面这些是预定义符号都是语言内置的
注意:符号中的左右是两个横杠,是__FINE__,而不是_FINE_,
例如:
printf("文件:%s\\n", __FILE__);
printf("文件当前的行号:%d\\n", __LINE__);
printf("文件编译的日期:%s\\n", __DATE__);
printf("文件编译的时间:%s\\n", __TIME__);
3.define标识符
3.1 define 定义标识符
# define宏定义是个演技非常高超的替身演员,在编译器编译时,真身则会替换替身,在我们代码中我们会经常使用这个替身。
#define MAX 1000
int max = MAX;//在预处理阶段,MAX会替换为1000,相当于int max=1000;
3.2 define定义宏
#define SQUARE(x) x*x
int main()
{
printf("%d", SQUARE(5));//输出25
return 0;
}
当我们传5进去后,在预编译阶段,则SQUARE(5)会被替换为5*5,所以最终输出为25.
printf("%d", SQUARE(2 + 3));//输出11
为什么上面代码会输出11呢?
当我们传2+3进去后,在预编译阶段,则SQUARE(2+3)会被替换为2+3*2+3,在根据优先级计算,即可得出11,我们想要的是(2+3)*(2+3),但输出并没有符号我们预期,我们需要把宏修改一下。
#define SQUARE(x) (x)*(x)
printf("%d", SQUARE(2 + 3));//输出25
那么我们在来定义一个宏,求整数的两倍。
#define DOUBLE(x) (x)+(x)
printf("%d", DOUBLE(5));//输出10
printf("%d", 2*DOUBLE(5));//输出15
DOUBLE(5)输出10我们还是能够理解,但是2*DOUBLE(5)输出15是怎么回事呢,在预编译阶段,2*DOUBLE(5)则会被替换成2*(5)+(5),我们想要的是输出20的结果,那怎么办呢?那么我们在外边直接加个大括号即可。
#define DOUBLE(x) ((x)+(x))
总结:所以我再写宏定义时尽量给每个变量加个括号,最外层的括号也别省。例如之前#define SQUARE(x) (x)*(x),在外层的括号也加上去,变成
#define SQUARE (x) ((x)*(x))
命名约定
3.3 宏的题目
写一个宏,可以将一个整数的二进制位的奇数位和偶数位交换。
代码:
#define SWAP_BIT(n) n=(((n)&0x55555555)<<1)|(((n)&0xaaaaaaaa)>>1)
思路:先取出整数二进制的奇数位上的数,&上一个01010101010101010101010101010101提取出奇数位,对应的十六进制为0x55555555,在取出整数的二进制的偶数位上的数,&上一个10101010101010101010101010101010提取出偶数位,对应的十六进制为0xaaaaaaaa,然后对奇数位上的数左移一位(<<)就变成偶数位,对偶数位上的数右移一位(>>)就变成奇数位了,在对这两个数进行 | 就可以完成交换。
4.# 和 ##
4.1 #的作用
#VALUE会被预处理为“VALUE”
int a = 0;
double d = 1.11;
printf("the value of a is %d\\n", a);//输出:the value of a is 0
printf("the value of d is %f\\n", d);//输出:the value of d is 1.110000
那么我们怎样可以写一个宏来替代
printf("the value of a is %d\\n", a);
printf("the value of d is %f\\n", d);这两条语句呢?
首先我们必须先知道:
printf("hello " "world\\n");//输出hello world
#define PRINT(FORMAT,DATA) printf("the value of " #DATA " is " #FORMAT“\\n",DATA)
int a=10;
double d=1.11;
PRINT(%d, a);//输出:the value of a is 0
PRINT(%f, d);//输出:the value of a is 1.110000
所以我们就很好通过一个宏就替代上面那两条语句。
注意:宏中的#DATA在预编译时会替换成”DATA" ,#FORMAT会被替换为“FORMAT"。
4.2 ##的作用
#define ADD_TO_SUM_(num,value) sum##num+=value;
int sum1 = 1;
int sum2 = 2;
ADD_TO_SUM_(1, 10);//sum1增加10
ADD_TO_SUM_(2, 10);//sum2增加10
sum##num为sumnum(num为传进来的宏参数)
5.带副作用的宏参数
#define MAX(x,y) ((x)>(y))?(x):(y)
int main()
{
int a = 0;
int b = 0;
MAX(a++, b++);
printf("%d %d", a, b);//结果:a为1,b为2
return 0;
}
为什么运行结果a为1,b为2呢?
在预编译时,MAX(a++,b++)会被替还为 ((a++)>(b++))?(a++):(b++),
6.宏与函数的对比
#define MAX(x, y) ((x)>(y)?(x):(y))//宏实现
int Max(int a,int b)//函数实现
{
return a>b?a:b;
}
2.宏在预编译阶段中会直接将代码替换,而函数调用需要创建函数栈帧和返回值都需要消耗时间,所以宏的效率比函数要高。
的
宏的劣势:
1.每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度
2.由于宏是运行时将代码插入到程序中,所以宏是没办法调试。
3.宏的参数可以是任意类型的,所以不严谨。
4.宏会导致优先级的问题,容易出错。
5.宏是不能递归,函数可以递归。
7.#undef
撤销一个宏定义,如果现存的名字需要重新定义,那么需要先移除它的旧名字。
#define MAX 100
int main()
{
int MAX = 1000;//错误:MAX为宏定义,不能再重新定义
#undef MAX;//移除MAX的宏定义,下面的宏定义就不能用
int MAX = 1000;//正确
return 0;
}
8.条件编译
条件编译有三种形式:
方式一:
#ifdef 标识符
程序段一
#else
程序段二
#endif
上面的代码是,如果标识符有被#define定义过,则运行程序段一,如果没有定义过,则运行程序段二,如果没有程序段二(空),则#else可以没有 。
#define DEBUG 1
int main()
{
#ifdef DEBUG
printf("hello world\\n");
#else
printf("thank you\\n");
#endif
}
例如上面的代码,由于DEBUG被定义,所以在预编译的时候,编译器只会保留
int main()
{
printf("hello world\\n");
}
这段代码,其它代码被编译过滤掉,最终输出结果为hello world。
如果DEBUG没有定义,则编译器则会保留
int main()
{
printf("thank you\\n");
}
这段代码,其它代码被编译过滤掉,最终输出结果为 thank you。
方式二:
#ifndef 标识符
程序段 1
#else
程序段 2
#endif
与第一种形式差不多,只是将#ifdef改为#ifndef,如果标识符没有被定义,则运行程序段1,有被定义则运行程序段 2,与方式一差不多,只是逻辑相反,所以笔者我就不再举例子了。
方式三:
#if 常量表达式1
程序段 1
#elif 常量表达式2
程序段 2
#else
程序段 3
#endif
如果 常量表达式1的结果不为0,则为真,则只运行程序段 1,如果常量表达式结果为0(假),常量表达式为2不为0,则只运行程序段 2,如果常量表达式为1和常量表达式 2的结果都为0,则运行程序段 3。如果没有程序段 2和程序段 3(为空),则#elif和# else可以没有。
int main()
{
#if 10
printf("hello world\\n");
#elif 10
printf("you are very handsome\\n");
#else
printf("thank you\\n");
#endif
return 0;
}
对于上面的代码,由于常量表达式不为0,所以在预编译的时候,编译器只保留
int main()
{
printf("hello world\\n");
return 0;
}这段代码,其它代码被过滤掉。最终运行结果为 hello world ,如果常量表达式1的结果为0,则运行结果为 you are very handsome ,如果常量表达式1和常量表达2都为0,则运行结果为thank you。
假设我们有一个Add.h头文件,里面包含着几个函数的声明:
int Add_Int(int x, int y);
double Add_Double(double x, double y);
float Add_Float(float x, float y);
我们在test.c上引入Add.h的头文件,如下:
#include"Add.h"
int main()
{
int a = 0, b = 0;
Add_int(a, b);
return 0;
}
在预编译阶段时,则编译器会发生替换,替换的方式很简单:预处理器先删除这条指令,并用包含文件的内容替换。
则test.c文件预编译后会变成:
int Add_Int(int x, int y);
double Add_Double(double x, double y);
float Add_Float(float x, float y);
int main()
{
int a = 0, b = 0;
Add_int(a, b);
return 0;
}
但是如果我们的test.c文件多次包含头文件Add.h后,则预编译时会将Add.h的内容会包含多次,这就会导致test.c文件中的内容重复。
那么如何解决头文件的重复包含?答案是:条件编译
我们可以对Add.h头文件就行修改
#ifndef ADD_H
#define ADD_H 1
int Add_Int(int x, int y);
double Add_Double(double x, double y);
float Add_Float(float x, float y);
#endif
如果test.c引入一次Add.h头文件,由于没有定义#define Add_H,则预编译时会定义一次#define Add_H,并且将头文件中的内容给包含进去,如果在引入一次Add.h头文件,因为ADD_H已经被定义,那么条件编译不会将头文件的内容包含进来,所以这就很巧妙的解决这些问题。
或者我们有一种现代写法:
#pragma once
int Add_Int(int x, int y);
double Add_Double(double x, double y);
float Add_Float(float x, float y);
上面的两种方式都能避免我们的头文件的重复引用。
9.头文件被包含的方式
c语言提供了两种头文件的包含方式,它实际是宏替换的延伸,有两种格式:
格式一:
#include<stdio.h>
stdio为包含文件的名称,用尖括号括,预处理时会直接去库函数的头文件中查找,找不到则编译错误。
格式二:
#include"file.h"
”file.h为包含文件的名称,直接在当前目录下查找名为file.h的头文件,找不到在按系统指定的路径信息,搜索其他目录。找到文件后,用
点个赞呗~
以上是关于c语言基础——预处理详解的主要内容,如果未能解决你的问题,请参考以下文章