预处理器:在程序运行之前查看程序。
预处理器把符号替换成表示的内容不做表达式计算和求值,就
是简单的转换文本。
#define
预处理器查找一行中以#开始的预处理器指令。指令可以出现在源文件中的任何地方。他的定义从开始一直到源文件末尾都有效。
#include <stdio.h>
#define TWO 2
#define OW "Consistency is the last refuge of the unimagina\
tive. - Oscar Wilde"
#define FOUR TWO*TWO
#define PX printf("X is %d.\n", x)
#define FMT "X is %d.\n"
int main(void)
{
int x = TWO;
PX;
x = FOUR;
printf(FMT, x);
printf("%s\n", OW);
printf("TWO: OW\n");
return 0;
}
注意宏换行时要与上面一行对齐,不然空格也会算进去。
使用宏声明数组
#define LIMIT 20
const int LIM = 50;
static int data1[LIMIT];
static int data2[LIM];
const int LIM2 = 2 * LIMIT;
const int LIM3 = 2 * LIM;
数组的声明可必须是
- 整型常量的组合,比如宏定义
- 枚举
- sizeof表达式
而const声明的数值不可以。
宏定义的空格
#define EIGHT 4 * 8
#define EIGHT2 4*8
这两个宏定义是不同的,一个是带空格的,而另一个不带空格。空格也是宏定义中的一部分。
重定义
#define LIM 20
#define LIM 20
可以重定义宏,但是重定义的值必须和原值相同。如果不同,则有些编译器会报错,有些会给出警告。
取消定义#undef
移除LIMIT定义后,就可以给LIMIT重新定义成一个新值。如果没有定义LIMIT,取消定义一样有效。
宏定义中使用参数
看上去像函数调用,但是和函数调用完全不同。 看下面实例
#include <stdio.h>
#define SQUARE(X) X*X
#define PR(X) printf("The result is %d.\n", X)
int main(void)
{
int x = 5;
int z;
z = SQUARE(x);
PR(z);
z = SQUARE(2);
PR(z);
PR(SQUARE(x+2));
PR(100/SQUARE(2));
printf("x is %d.\n", x);
PR(SQUARE(++x));
printf("After incrementing, x is %x.\n", x);
return 0;
}
宏参数作为字符串
#define PSQR(x) printf("The square of "#x" is %d.\n",((x)*(x)))
int main(void)
{
int y = 5;
PSQR(y);
PSQR(2 + 4);
return 0;
}
这里的参数会替换字符串中的"#x"
粘合剂符号##
#include <stdio.h>
#define XNAME(n) x ## n
#define PRINT_XN(n) printf("x"#n" = %d\n", x ## n);
int main(void)
{
int XNAME(1) = 14;
int XNAME(2) = 20;
int x3 = 30;
PRINT_XN(1);
PRINT_XN(2);
PRINT_XN(3);
return 0;
}
可变参数宏 ... 和 _ _VA _ ARGS_ _ 打印
可变参数宏是在宏中printf使用的
#include <stdio.h>
#include <math.h>
#define DEBUG(format, ...) printf(format, __VA_ARGS__)
#define PR(X, ...) printf("Message " #X " : " __VA_ARGS__)
int main(void)
{
DEBUG("%s: %d\r\n", "debug", 100);
double x = 48;
double y;
y = x*x;
PR(1, "x = %g\n", x);
PR(2, "x = %.2f, y = %.4f\n", x, y);
return 0;
}
宏,函数调用,内联函数
内联函数
把函数变成内联,编译器可能会把函数代码直接替换掉函数调用。 目的:把函数变为内联,目的是尽可能快的调用该函数。
inline int fun(int x)
{
return x * x;
}
int main()
{
int b = fun(2 + 3);
return 0;
}
内联会把函数变为下面形式
inline int fun(int x)
{
return x * x;
}
int main()
{
int b = (2 + 3) * (2 + 3);
return 0;
}
未给内联函数预留代码块, 所以无法获得内联函数地址,并且内联函数无法在调试器中显示。 一般我们不在头文件中放可执行代码,但是内联函数是个特例。 内联函数一般是比较简短的函数,我们可以把它写成内联形式。
三者区别
- 如果调用函数次数比较多:比如二十次,那么如果使用宏,就会产生二十个代码,形成冗余,这时我们使用函数更好,因为只有一个副本节省空间。函数代码量多,功能复杂,体积庞大。这时使用内联则不合适,因为
- 如果代码功能比较简单,而且函数调用次数少,则可以直接使用宏定义,更直接,简单。
条件宏
#include <stdio.h>
#define JUST_CHECKING
#define LIMIT 4
int main(void)
{
int i;
int total = 0;
for (i = 1; i <= LIMIT; i++)
{
total += 2*i*i + 1;
#ifdef JUST_CHECKING
printf("i=%d, running total = %d\n", i, total);
#endif
}
printf("Grand total = %d\n", total);
return 0;
}
#ifndef主要有两种作用:
- 防止某个宏被重复定义,如下代码,如果这个宏没有被定义过,那么我们就定义它。如果定义过就不定义了,这样在多个头文件的时候,就不会有重定义的错误。
#ifndef SIZE
#define SIZE 100
#endif
- 在头文件中使用,防止多次包含同一个文件(最常用的)。当包含头文件很多时,那么我们很可能在不同头文件中都包含了一个头文件,那么如果有些声明比如一些结构类型声明,出现两次就会报错。所以我们就使用下面的方式来防止这种情况。
#ifndef NAMES_H_
#define NAMES_H_
#define SLEN 32
struct names_st
{
char first[SLEN];
char last[SLEN];
};
typedef struct names_st names;
void get_names(names *);
void show_names(const names *);
char * s_gets(char * st, int n);
#endif
预定义宏
#include <stdio.h>
void why_me();
int main()
{
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__);
}
#include头文件包含
当预处理器发现#include指令,会把该文件包含到当前文件中,即把include的文件全部内容直接放入当前文件该处。 双引号,会查找当前工作目录中如果没有再去系统目录中查找。但是查找还是要看编译器,可能会查找当前源文件所在目录,也可能从项目文件所在目录查找。
头文件中存放的东西
全局变量
|