C预处理器在程序执行之前查看程序,根据程序中的预处理指令,预处理器把符号缩写替换成其表示的内容。基本上他的工作就是文本替换。
翻译程序的第一步
- 首先,编译器把源代码中出现的字符映射到源字符集。
- 第二,定位每个反斜杠后面跟着的换行符实例,并删除它们。将多个物理行替换成一个逻辑行:
//两个物理行
printf("That is wond\
erful!\n");
//转换成一个逻辑行:
printf("That is wonderful!\n");
- 第三,编译器将文本划分成预处理记号序列、空白序列和注释序列,编译器将用一个空格字符替换每一条注释。
int/*这看起来并不是空格*/fox;
//会变成:
int fox;
明示常量:#define
使用#define来定义明示常量,也叫符号常量。
预处理器指令从#开始运行,到后面的第一个换行符为止,也就是说指令的长度仅限于一行(逻辑行)。
#include<stdio.h>
#define TWO 2
#define OW "This is a\
test.have a try" //反斜杠把该定义延续到下一行
#define FOUR TWO*TWO //预处理不做计算,只是简单替换
#define PX printf("X is %d.\n",x)
#define FMT "X is %d.\n"
//在#define中使用参数--类函数宏,圆括号中可以有一个或多个参数。
#define SQUARE(X) X*X
#define PR(X) printf("The result is %d.\n",X);
//预处理粘合剂:##运算符
#define XNAME(n) x ## n
#define PRINT_XN(n) printf("x" #n " = %d\n", x ## n);
int main(void)
{
int x = TWO;
PX;
x = FOUR;
printf(FMT,x);
printf("%s\n",OW);
printf("TWO:OW\n");
x = SQUARE(x);
PR(x);
//宏定义只是简单替换,
//这里变成:printf("The result is %d.\n", x + 2 * x + 2); 16+2*16+2==50
PR(SQUARE(x + 2));
//这里变成:printf("The result is %d.\n", x++ * x++); 16*17==272
PR(SQUARE(x++));
//##运算符
int XNAME(1) = 14; //变成 int x1 = 14;
int XNAME(2) = 20; //变成 int x2 = 20;
int x3 = 30;
PRINT_XN(1); //变成 printf("x1 = %d\n", x1);
PRINT_XN(2); //变成 printf("x2 = %d\n", x2);
PRINT_XN(3); //变成 printf("x3 = %d\n", x3);
return 0;
}
/*
结果:
X is 2.
X is 4.
This is a test.have a try
TWO:OW
The result is 16.
The result is 50.
The result is 272.
x1 = 14
x2 = 20
x3 = 30
*/
文件包含:#include
当预处理器发现#include时,会查看后面的文件名,并把文件的内容包含到当前文件中,即替换源文件中的#include指令。
#undef指令
#undef指令用于取消已定义的#define指令
假如有如下定义:
#define LIMIT 100
然后又有如下指令:
#undef LIMIT
将移除上面的定义,可以将LIMIT重新定义一个新值,即使原来没有定义LIMIT,取消LIMIT定义仍然有效,如果想使用一个名称,又不确定之前是否用过,为了安全起见,可以使用#undef指令取消改名字的定义。
条件编译
可以使用这些指令告诉编译器根据编译时的条件执行或忽略代码块。
#include<stdio.h>
#define TEST
#ifdef TEST //如果已经用#define定义了MAVIS,则执行下面指令
#include "string.h"
#define NUM 999
#else //否则
#include "math.h"
#define NUM 666
#endif //条件结束,必须以#endif结尾
int main(void)
{
#ifdef TEST
printf("this is a test: %d\n",NUM);
#endif
#ifndef SIZE //#ifndef 和#ifdef指令的用法类似,不过它的意思相反,如果未定义SIZE,则执行下面代码段
#define SIZE 2
puts("size is not defined!");
printf("size = %d\n", SIZE);
#endif
// #if指令很像C语言中的if
#if SIZE == 1
puts("size == 1");
#elif SIZE == 2
puts("size == 2");
#else
puts("size == 0");
#endif
/*较新的编译器提供另一种方法测试名称是否定义
即,用#if defined (SIZE) 来代替 #ifdef VAX
*/
#if defined (SIZE)
puts("size!!!");
#endif
return 0;
/*
结果:
this is a test: 999
size is not defined!
size = 2
size == 2
size!!!
*/
}
预定义宏
#include<stdio.h>
void why_me();
int main(void)
{
printf("The file is %s.\n", __FILE__);//当前源代码文件名的字符串字面量
printf("The date is %s.\n", __DATE__);//预处理的日期
printf("The time is %s.\n", __TIME__);//翻译代码的时间
//printf("The version is %ld.\n", __STDC_VERSION__);
printf("This is line %d.\n", __LINE__);// 当前源代码所在的行号
printf("This function is %s,\n", __func__);//预定义标识符,不是预定义宏,当前所在函数的函数名称
why_me();
return 0;
}
void why_me(){
printf("This function is %s\n", __func__);
printf("This is line %d\n", __LINE__);
}
/*
结果:
The file is C:\Users\LUO\Desktop\C\code-train\predef.c.
The date is Sep 24 2020.
The time is 20:14:14.
This is line 10.
This function is main,
This function is why_me
This is line 20
*/
泛型选择
在程序设计中,泛型编程指那些没有特定类型,但一旦指定一种类型,既可以转换成指定类型的代码。C++在模板中可以创建泛型算法,然后编译器根据指定的类型自动使用实例化代码。C11新增了一种表达式,叫作泛型选择表达式,可根据表达式的类型选择一个值。
-Generic(x, int: 0,float: 1, double: 2, default: 3)
_Generic是C11的关键字,第一个项是一个表达式,后面每个项都由一个类型、一个冒号和一个值组成。
#include<stdio.h>
#define MYTYPE(X) _Generic((X),int:"data is int",float:"data is float",double:"data is double",default:"other")
int main(void)
{
int d = 4;
printf("%s\n", MYTYPE(d)); //d是int类型, MYTYPE(4)得”int”
printf("%s\n", MYTYPE(d*2.0)); //d*2.0是double类型,, MYTYPE(8.0)得”double”
printf("%s\n", MYTYPE(3L)); //3L是long类型, MYTYPE(3L)得”other”
printf("%s\n", MYTYPE(&d)); //&d是int *类型, MYTYPE(&d)得”other”
return 0;
}
/*
结果:
data is int
data is double
other
other
*/
内联函数(C99)
通常函数的调用都有一定的开销,因为函数的调用包括建立调用、传递参数、跳转到函数代码并返回。内联函数就是以空间换时间,使函数的调用更加快捷。
标准规定:具有内部链接的函数可以成为内联函数,还规定了内联函数的定义与调用该函数的代码必须在同一个文件中。因此最简单的办法是使用inline和static修饰。通常,内联函数应定义在首次使用它的文件中,所以内联函数也相当于函数原型。
#include<stdio.h>
inline static void test()
{
puts("test!");
}
int main()
{
...
test();
...
}
- 由于并未给内联函数预留单独的代码块,所以无法获得内联函数的地址,另外,内联函数无法在调试器中显示。
- 由于内联函数具有内部链接,所以在多个文件中定义同一个内联函数不会产生什么影响。
- 如果多个文件都需要使用同一个内联函数,可以将它定义在h头文件中。