预处理是C言语的一个重要知识点,它能改进程序设计的环境,有助于编写易移植、易调试的程序。因而,咱们有必要把握好预处理指令,在自己编程的时分灵敏的运用它,使得编写的程序结构优秀,愈加易于调试和阅览。接下来我尽或许的把预处理中重要知识点向读者解说清楚,使读者能够在自己今后编程的进程中娴熟的运用预处理指令。
C言语的预处理主要有三个方面:
1、文件的包括
2、宏界说
3、条件编译
一、文件包括的方法有下面两种
1、#include "文件名"
2、#include <文件名>
它们之间的差异在于:<文件名>体系到头文件目录查找文件, "文件名"则先在当时目录查找,假如没有才到头文件目录查找;当然咱们也能够运用在指令行来指定头文件途径办法。还要留意便是假如在源文件包括的头文件之间呈现调用的状况,那么被调用的头文件要呈现在调用头文件的前面。
二、宏界说
宏界说的运用有两种方法,一种不带参数,而别的一种带参数。
1、不带参数
格局: #define 标识符 字符串
信任上面这个格局咱们并不生疏,下面仍是来看看怎么运用吧。当然在解说之前咱们的看看运用进程中的如下几个留意关键:
(1)预处理不做语法查看,所以咱们选用的时分要特别当心
(2)宏界说写在函数的花括号外边,效果域为这以后的程序,通常在文件最初部分,直到用#undef指令停止宏界说的效果域
(3)不要在字符串中运用宏,假如宏名呈现在字符串中那么将依照字符串进行处理
下面来看段代码的运用。
[html] view plaincopy#include
#define N 9
int main ()
{
int i,a[N];
for(i=0;i
{
a[i]=i;
printf("%d\t",a[i]);
if((i+1)%3==0)
printf("\n");
}
//#undef N
printf("%d\n",N);
}
运转成果为:
[html] view plaincopy0 1 2
3 4 5
6 7 8
9
Press any key to continue
咱们在此主要是介绍下宏的效果域问题,当在以上代码中注释掉#undef N时,接下来的打印句子能够正常的打印出;可是当咱们没有注释掉#undef N的时分就会呈现error C2065: ‘N’ : undeclared identifier过错,提示N没有界说。接下来看看带参数的宏的运用。
2、带参数
#define 宏名(参数表) 字符串
留意关键:
(1)宏名和参数的括号间不能有空格
(2)宏替换只作替换,不做核算,不做表达式求解,这点要特别留意
(3)函数调用在编译后程序运转时进行,而且分配内存。宏替换在编译前进行,不分配内存
(4)宏的哑实结合(所谓的哑实结合相似于函数调用进程中实参代替形参的进程)不存在类型,也没有类型转化。
(5)宏打开使源程序变长,函数调用不会
下面来看看linux下一个典型的运用:
#define min(x,y) ({ typeof(x) _x = (x); typeof(y) _y = (y); (void) (&_x == &_y); _x < _y ? _x : _y; })
#define max(x,y) ({ typeof(x) _x = (x); typeof(y) _y = (y); (void) (&_x == &_y); _x > _y ? _x : _y; })
在上面的两个宏中咱们发现有这么一句代码(void) (&_x == &_y);或许不少读者有点发懵的感觉,这啥意思呢?!其实咱们细细剖析就知道,首要看看“==”,这是一个逻辑表达式,它要求两头的比较类型有必要一起,假如咱们的&x和&y类型不一起,如一个为char*,另一个为int*,不是同一个类型,当咱们运用gcc编译的时分就会呈现正告信息,vc6则会报错error C2446: ‘==’ : no conversion from ‘char *’ to ‘int *’。这句代码(void) (&_x == &_y); 在此的功用就相当于履行一个简略的判别操作,咱们用来判别x和y的类型是否一起。别小看了这句代码,假如学会了运用它会给你的代码带来不少的快捷。下面给出一个小小的案例:
[cpp] view plaincopy#include
void print()
{
printf("hello world!!!\n");
return ;
}
void main(int argc,char*argv)
{
print();
return ;
}
运转成果为:
[cpp] view plaincopyhello world!!!
Press any key to continue
现在咱们来修正下代码后看看运转成果:
[cpp] view plaincopy#include
void print()
{
printf("hello world!!!\n");
return ;
}
void main(int argc,char*argv)
{
#define print() ((void)(3))
print();
return ;
}
运转成果为:
[cpp] view plaincopyPress any key to continue
这儿的成果没有了咱们之前的那句hello world!!!,能够看出这个时分函数并没有被调用,这是因为咱们运用了#define print() ((void)(3)),使得之后调用函数print()转化为了一个空操作,所以这个函数在接下来的代码中都不会被调用了,就像被“冲刷掉”了相同。看到这儿你是不是想起咱们之前的那篇《C言语的那些小秘密之断语》了呢,咱们相同能够运用这种办法来完成断语的封闭,办法与之相似,在此就不再解说了,有爱好的读者能够自己试试。讲到这儿好像应该完毕了,可是仔细的读者会有别的一个疑问?在#define min(x,y) ({ typeof(x) _x = (x); typeof(y) _y = (y); (void) (&_x == &_y); _x < _y ? _x : _y; })中,咱们为什么要运用像typeof(y) _y = (y)这样的转化呢?而不直接运用typeof(x)==typeof(y)或许(void) (&x == &y); x < y ? x : y; 呢?假如咱们运用typeof(x)==typeof(y)就比如运用了char==int相同,这是不允许的。咱们运用一个typeof(y) _y = (y)这样的转化,这是为了避免x和y为一个表达式的状况,如x=i++之类的,假如不转化的话i++就多履行了几回操作,得到的不是咱们想要的成果,可是假如咱们运用了typeof(y) _y = (y)这样的转化,那就不会呈现这样的问题了。下面咱们来看看怎么运用宏界说完成变参,先看看完成办法。
#define print(…) printf(__VA_ARGS__)
看看上面的宏,其间“…”指可变参数。完成的可变参数的完成方法便是运用“…”所代表的内容代替__VA_ARGS__,看看下面的代码就知道了。
[cpp] view plaincopy#include
#define print(…) printf(__VA_ARGS__)
int main(int argc,char*argv)
{
print("hello world—-%d\n",1111);
return 0;
}
运转成果为:
[cpp] view plaincopyroot@ubuntu:/home/shiyan# ./arg
hello world—-1111
接着往下看。
#define printf (tem, …) fprintf (stdout, tem, ## __VA_ARGS__)
如有对fprintf不熟悉的读者能够自己查查函数手册,在此不再解说。
[cpp] view plaincopy#include
#define print(temp, …) fprintf(stdout, temp, ##__VA_ARGS__)
int main(int argc,char*argv)
{
print("hello world—-%d\n",1111);
return 0;
}
运转成果为:
[cpp] view plaincopyroot@ubuntu:/home/shiyan# ./arg
hello world—-1111
temp在此的效果为设定输出字符串的格局,后边“…”为可变参数。现在问题来了,咱们在宏界说中为什么要运用“##”呢?假如咱们没有运用##会怎么样呢?看看下面的代码:
[cpp] view plaincopy#include
#define print(temp, …) fprintf(stdout, temp, __VA_ARGS__)
int main(int argc,char*argv)
{
print("hello world\n");
return 0;
}
编译时产生了如下过错:
[cpp] view plaincopyroot@ubuntu:/home/shiyan# gcc arg.c -o arg
arg.c: In function ‘main’:
arg.c:7:2: error: expected expression before ‘)’ token
为什么会呈现上面的过错呢,现在咱们来剖析下,咱们进行下宏替换,print("hello world\n")就变为了fprintf(stdout, "hello world\n",)这样咱们就发现了后边呈现了一个逗号,所以导致了过错,假如有“##”就不会呈现这样的过错了,这是因为假如可变参数被疏忽或为空的时分,“##”操作将使预处理器去除去它前面的那个逗号。假如存在可变参数时分,它也能正常作业。讲了“##”,咱们当然也要讲讲“#”。先来看看下面一段代码:
[cpp] view plaincopy#include
#define return_exam(p) if(!(p)) \
{printf("error: "#p" file_name:%s\tfunction_name:%s\tline:%d .\n",\
__FILE__, __func__, __LINE__); return 0;}
int print()
{
return_exam(0);
}
int main(int argc,char*argv)
{
print();
printf("hello world!!!\n");
return 0;
}
运转成果为:
[cpp] view plaincopyroot@ubuntu:/home/shiyan# ./arg
error: 0 file_name:arg.c function_name:print line:9 .
hello world!!!
咱们发现在运转成果中打印出了犯错的文件名、函数名、以及行号。选用宏界说来检测函数的返回值是否正确,只是是为了体现出咱们要解说的宏,所以代码做了最大的简化作业,读者在自己编写代码时分要学会这样的检测方法。“#”的效果便是将这以后边的宏参数进行字符串化操作,便是在宏变量进行替换之后在其左右各加上一个上双引号,这就使得"#p"变味了""p""咱们发现这样的话刚好两头的“""”就消失了。下面来看看最终一个知识点条件编译。
三、条件编译
条件编译指令#if、#else、#elif、#endif、#ifdef、#ifndef,条件编译指令的意思很简略,跟咱们学习的if句子相似。
一般格局
#if 常量表达式
程序段1;
[#else
程序段2;]
#endif
功用:当表达式为非0(“逻辑真”)时,编译程序段1,不然编译程序段2。
一般格局
#ifdef 标识符
程序段1;
[#else
程序段2;]
#endif
功用:当“标识符”现已被#define指令界说过,则编译程序段1,不然编译程序段2。
#ifndef 标识符
程序段1;
[#else
程序段2;]
#endif
功用:当“标识符”未被#define指令界说过,则编译程序段1,不然编译程序段2。
学习了条件编译指令之后,咱们在调试代码的时分,就不要再为所欲为的删减代码了,假如咱们不想某段代码被编译就能够运用条件编译指令来将其注释掉。如:
#if (0)
注释代码段;
#endif
就能够完成代码的注释了,需求的时分也能够将其启用,而不会为需求从头修改代码时,发现已被删去而头疼了。
其间值得留意的当地为,常量表达式在编译时求值,所以表达式只能是常量或许现已界说过的标识符,不能为变量,也不能够为那些在编译时分求值的操作符,如sizeof。
下面来看段代码:
[cpp] view plaincopy#include
#define N 1
int main(int argc,char*argv)
{
int a=3;
#if(a)
printf("#if后边的表达式为变量\n");
#endif
#if(N)
printf("#if后边的表达式已界说,且不为0—success\n");
#else
printf("#if后边的表达式已界说,且不为0—fail\n");
#endif
return 0;
}
运转成果为:
[cpp] view plaincopy#if后边的表达式已界说,且不为0—success
Press any key to continue
看看上面的代码咱们的表达式为变量a时并没有打印出来,所以咱们不能在这以后的表明中运用变量。假如咱们运用sizeof操作符会怎么样呢?为了加深形象看看下面的代码后成果吧。
[cpp] view plaincopy#include
int main(int argc,char*argv)
{
int a=9;
#if(sizeof(a))
printf("#if后边的表达式含有sizeof操作符\n");
#endif
return 0;
}
编译呈现了如下过错:
[cpp] view plaincopyfatal error C1017: invalid integer constant expression
所以咱们在运用条件编译的时分要紧记这两点,常量表达式不能为变量和含有sizeof等在编译时求值的操作符。
接下来看看这儿要讲的最终一个#pragma指令。
一般格局为:
#pragma 参数
下面给出几种常常运用的方法
1、#pragma message("音讯")
看看下面一段代码。
[cpp] view plaincopy#include
#define FDSA
int main(int argc,char*argv)
{
#ifdef FDSA
#pragma message("FDSA 现已界说过了")
#endif
return 0;
}
编译的时分咱们能够在编译输出窗口中看到了输出“FDSA 现已界说过了”,经过这种方法咱们能够在一些咱们想要的当地输出许多咱们需求的信息。
2、#pragma once
假如咱们在头文件的最初部分参加这条指令,那么就能确保咱们的头文件只是被编译一次。
3、#pragma hdrstop
该指令表明编译头文件到此为止,后边的无需在进行编译了。
4、#pragma pack()
设定字节的对齐长度,这个指令咱们在《C言语的那些小秘密之字节对齐》中现已解说了,在此不再复述。
5、#pragma warning(disable:M N;once:H;error:K)
表明不显现M和N号的正告信息,H号正告信息只陈述一次,把K号正告信息作为一个过错来处理。
到此关于预处理的解说就完毕了。因为自己水平有限,博客中的不当或过错之处在所难免,殷切希望读者批评指正。一起也欢迎读者一起讨论相关的内容,假如愿意沟通的话请留下你名贵的定见。