1.#
假如希望在字符串中包含宏参数,ANSI C允许这样作,在类函数宏的替换部分,#符号用作一个预处理运算符,它可以把语言符号转化程字符串。例如,如果x是一个宏参量,那么#x可以把参数名转化成相应的字符串。该过程称为字符串化
在使用#define定义宏时,可使用操作符#在字符串中输出实参。Eg:#define AREA(x,y) printf(“长为“#x”,宽为“#y”的长方形的面积:%d\n”,(x)*(y));
2.##
##运算符可以使用类函数宏的替换部分。另外,##还可以用于类对象宏的替换部分。这个运算符把两个语言符号组合成单个语言符号。例如:
#define X(n) x##n
这样宏调用:
X(5)
展开后:
x5
与操作符#类似,操作符##也可用在带参宏中替换部分内容。该操作符将宏中的两个部分连接成一个内容。例如,定义如下宏:
#define VAR(n) v##n
当使用一下方式引用宏:
VAR(1)
预处理时,将得到以下形式:
v1
如果使用以下宏定义:
#define FUNC(n) oper##n
当实参为1时,预处理后得到一下形式:
oper1
3.可变宏 ...和_ _VA_ARGS_ _
实现思想就是宏定义中参数列表的最后一个参数为省略号(也就是三个点)。这样预定义宏_ _VA_ARGS_ _就可以被用在替换部分中,以表示省略号代表什么。比如:
#define DBUG(...) printf(_ _VA_ARGS_ _)
DBUG("hello world\n");相当于:printf("hello world\n");
DBUG("int= %d, float= %.2f",5,2.55);相当于:printf("int= %d, float= %.2f",5,2.55);
省略号只能代替最后面的宏参数。
#define W(x,...,y)错误!
示例:
#include <stdio.h>
#define X(n) x ## n
#define PRINT_XN(n) printf("x" #n " = %d\n", x ## n);
#define DBUG(...) printf(__VA_ARGS__)
int main(void)
{
short *p;
int X(1) = 20; // int x1 = 20;
int X(2) = 30; // int x2 = 30;
PRINT_XN(1); // printf("x1 = %d\n", x1);
PRINT_XN(2); // printf("x2 = %d\n", x2);
DBUG("hello world\n");
DBUG("int= %d, float= %.2f\n",5,2.55);
return 0;
}
六.其他预处理命令
6.1.预定义的宏名
ANSI C标准预定义了五个宏名,每个宏名的前后均有两个下画线,避免与程序员定义相同的宏名(一般都不会定义前后有两个下划线的宏)。这5个宏名如下:
● __DATE__:当前源程序的创建日期。
● __FILE__:当前源程序的文件名称(包括盘符和路径)。
● __LINE__:当前被编译代码的行号。
● __STDC__:返回编译器是否位标准C,若其值为1表示符合标准C,否则不是标准C.
● __TIME__:当前源程序的创建时间。
Eg:
#include<stdio.h>
int main()
{
int j;
printf("日期:%s\n",__DATE__);
printf("时间:%s\n",__TIME__};
printf("文件名:%s\n",__FILE__);
printf("这是第%d行代码\n",__LINE__);
printf("本编译器%s标准C\n",(__STD__)?"符合":"不符合");
return 0;
}
6.2.重置行号和文件名命令------------#line
使用__LINE__预定义宏名赈灾编译的程序行号。使用#line命令可改变预定义宏__LINE__与__FILE__的内容,该命令的基本形如下:
#line number[“filename”]
其中的数字为一个正整数,可选的文件名为有效文件标识符。行号为源代码中当前行号,文件名为源文件的名字。命令为#line主要用于调试以及其他特殊应用。
Eg:
1:#include<stdio.h>
2:#include<stdlib.h>
4:#line 1000
6:int main()
7:{
8: printf("当前行号:%d\n",__LINE__);
9: return 0;
10:}
在以上程序中,在第4行中使用#line定义的行号为从1000开始(不包括#line这行)。所以第5行的编号将为1000,第6行为1001,第7行为1002,第8行为1003.
6.3.修改编译器设置命令 ------------#pragma
#pragma命令的作用是设定编译器的状态,或者指示编译器完全一些特定的动作。#pragma命令对每个编译器给出了一个方法,在保持与C语言完全兼容的情况下,给出主机或者操作系统专有的特征。其格式一般为:
#pragma Para
其中,Para为参数,可使用的参数很多,下面列出常用的参数:
Message参数,该参数能够在编译信息输出窗口中输出对应的信息,这对于源代码信息的控制是非常重要的,其使用方法是:
#pragma message(消息文本)
当编译器遇到这条指令时,就在编译输出窗口中将消息文本显示出来。
另外一个使用比较多得pragma参数是code_seg.格式如:
#pragma code_seg([“section_name”[,section_class]])
它能够设置程序中函数代码存放的代码段,在开发驱动程序的时候就会使用到它。
参数once,可保证头文件被编译一次,其格式为:
#pragma once
只要在头文件的最开始加入这条指令就能够保证头文件被编译一次。
6.4.产生错误信息命令 ------------#error
#error命令强制编译器停止编译,并输出一个错误信息,主要用于程序调试。其使用如下:
#error 信息错误
注意,错误信息不用双括号括起来。当遇到#error命令时,错误信息将显示出来。
例如,以下编译预处理器命令判断预定义宏__STDC__,如果其值不为1,则显示一个错误信息,提示程序员该编译器不支持ANSI C标准。
#if __STDC__!=1
#error NOT ANSI C
#endif
七.内联函数
在使用#define定义带参数宏时,在调用函数时,一般需要增加系统的开销,如参数传递,跳转控制,返回结果等额外操作需要系统内存和执行时间。而使用带参数宏时,通过宏替换可再编译前将函数代码展开导源代码中,使编译后的目标文件含有多段重复的代码。这样做,会增加程序的代码量,都可以减少执行时间。
在C99标准钟,还提供另外一种解决方法:使用内联函数。
在程序编译时,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体来进行替代。显然,这种做法不会产生转去转回得问题。都是由于在编译时将函数体中的代码被替代到程序中,因此会增加目标代码量,进而增加空间的开销,而在时间开销上不像函数调用时那么大,可见它是以增加目标代码为代码来换取时间的节省。
定义内联函数的方法很简单,只要在定义函数头的前面加上关键字inline即可。内联函数的定义与一般函数一样。例如,定于一个两个整数相加的函数:
#include<stdio.h>
#include<stdlib.h>
inline int add(int x,int y);
inline int add(int x,int y)
{
return x+y;
}
int main()
{
int i,j,k;
printf("请输入两个整数的值:\n");
scanf("%d %d",&i,&j);
k=add(i,j);
printf("k=%d\n",k);
return 0;
}
在程序中,调用函数add时,该函数在编译时会将以上代码复制过来,而不是像一般函数那样是运行时被调用。
内联函数具有一般函数的特性,它与一般函数所不同之处在于函数调用的处理。一般函数进行调用时,要讲程序执行权转导被调函数中,然后再返回到调用到它的函数中;而内联函数在调用时,是将调用表达式用内联函数体来替换。在使用内联函数时,应该注意如下几点:
● 在内联函数内部允许用循环语句和开关语句。
● 内联函数的定义必须出现在内联函数第一次被调用之前。
其实,在程序中声明一个函数为内联时,编译以后这个函数不一定是内联的,
即程序只是建议编译器使用内联函数,但是编译器会根据函数情况决定是否使用内联,所以如果编写的内联函数中出现循环或者开关语句,程序也不会提示出错,但那个函数已经不是内联函数了。
一般都是讲一个小型函数作为内联函数。