C语言-基础入门-学习笔记(15):预处理
一、宏定义
C语言中的所有预处理命令都以字符’#'开头。宏定义是预处理指令的一种,以#define开头。
根据宏定义中是否含有参数,可以将宏定义分为两种:宏对象和宏函数。
宏对象即为无参数的宏定义,形式如下:
#define 宏名 宏对象体
宏展开时,会将C程序代码中的所有宏名替换为宏对象体。
#define SIZE 6
···
int array[SIZE] = {0};
···
预处理后得到:
int array[6] = {0};
宏函数即有参数的宏定义,形式如下:
#define 宏名(参数列表) 宏函数体;
宏展开时,会将宏名及其调用参数替换为宏函数体,同时将宏函数体中使用的参数置换为传入的参数。
#define M_PRINT_INT(a){printf("%d",a);}
···
M_PRINT_INT(b);
···
预处理后得到:
printf("%d",b);
当为宏函数时,{}花括号扩住的部分为用宏名替换的部分,因此一定要完整
二、宏对象
1. 定义宏对象
宏对象的宏名后不带参数。并且宏名可以为任意字符,但尽量不要是关键字,那样会导致后面所有关键字都被替换,从而造成失效。
只要是放在宏对象位置处的所有字符都会被替换为宏名,即使有多行:
#define HELLO_WORLD printf("hello "); \
printf("world!\n")
范例1
#include <stdio.h>
#define int double
#define HELLO_WORLD \
printf("Hello,"); \
printf("world!\n")
int main(void){
int a = 2.2; //这里关键字int被替换为double
double b = 2.2;
printf("a = %d\n",a);
printf("b = %d\n",b);
HELLO_WORLD;
return 0;
}
- 预处理过程中不会对字符串做任何检查,只是单纯地将宏对象替换为宏名(包括符号)。
- 宏对象定义允许嵌套使用。宏展开时自上而下逐个替换。
#define SIZE1 5
#define SIZE2 6
#define SIZE3 SIZE1 + SIZE2
- 宏名可以用大写字母表示,以便与变量区别。
- 宏对象可以被重复定义,但是重复定义的内容必须一样。
2. 替换字符串
- 简化书写
对于一些较长的但固定的语句,可以使用宏对象来简化他们的书写。
printf("--------------------------------------");
简化:
#define PRI_DIVISION printf("--------------------------------------")
- 改变输出结构
- 不要使用宏对象来简化数据类型的书写:简化数据类型的书写最好使用typedef。
范例2
#include <stdio.h>
#define _D "%d "
#define _F "%f "
#define _X "%x "
#define _LF "\n"
int main(void){
printf(_D _F _X _LF,23,2.4,0x24);
return 0;
}
3. 说明形参属性
一个函数形参列表中的参数按用途可以分为两种:一种用于传入初始值,另一种用于带回函数结果。如果使用变量名来显示它们的区别,会增加变量名长度。这时,可以借助一个对象体为空的宏对象来辅助说明参数属性:
#define IN //空的宏对象,表示输入,不占用内存
#define OUT //空的宏对象,表示输出,不占用内存
int copy_array(
IN int * source,
IN int m;
OUT int * destination,
OUT int &n);
这样就可以很清晰地看出来source、m为带入函数的初始值,destination、n为带回函数执行结果。
4. 宏的作用域
作用域为本文件中从被定义的语句开始一直到该文件结束。 对于常用的宏定义,一般都写在头文件中,当源文件需要使用时,只需包含该头文件即可。
C语言提供了一个预处理指令来结束宏定义的作用域,形式如下:
#undef 宏名
范例3
#include <stdio.h>
int main(void){
int a = 0;
//AAA; //作用域外报错
#define AAA a+=20
AAA;
printf("a = %d\n",a);
#undef AAA
//AAA; //作用域外报错
#define AAA a+=30
AAA;
printf("a = %d\n",a);
return 0;
}
三、宏函数
宏对象可以实现字符串替换,而宏函数可以实现与函数类似的参数替代。
1. 定义宏函数
宏函数是指用宏定义实现的函数,形式如下:
#define 宏名(参数列表) 宏函数体
调用表达式为:
宏名(参数列表)
宏展开后,程序中的宏函数调用表达式都被宏函数体代替。
在定义宏函数时,宏名后的参数列表要紧挨着宏名,不然会被认为宏函数体的部分。
#define MIN(x,y) (x<y)?x:y; //正确
#define MIN (x,y) (x<y)?x:y; //错误
2. 宏函数与函数
- 宏函数的展开是简单的字符串替换,而函数是参数的传递。
- 宏函数是不经计算而直接替换的,而函数调用则是先计算实参的值,再将该值传递给形参。
- 宏函数中运算的是实参本身,而函数体中操作的是形参。
- 宏函数是在编译前进行的,函数是在编译后进行的。
- 宏函数由于是直接替换,所以不占用内存。
- 调用函数时,程序需要保存程序执行状态;函数返回时,要恢复原先保存的程序状态。而宏函数没有这些额外的开销。
范例4
#include <stdio.h>
#define INCREASE(x,y){++x;++y;}
void increase(int x,int y){
++x;
++y;
}
#define SQUARE(x) ((x)*(x))
int square(int x){
return x*x;
}
int main(void){
int r1 = 3;
int r2 = 4;
int a = 0;
printf("r1 = %d, r2 = %d\n",r1,r2);
INCREASE(r1,r2);
printf("r1 = %d, r2 = %d\n",r1,r2);
increase(r1,r2);
printf("r1 = %d, r2 = %d\n",r1,r2);
a = 4;
printf("\nSQUARE(a++) = %d\n",SQUARE(++a));
a = 4;
printf("square(a++) = %d\n",square(++a));
return 0;
}
函数执行过程不改变r1、r2的值,但宏函数会改变其自身的值。
宏函数是单纯地将函数进行替换,所以在后期计算时可以将替换后的带进去再进行结果的分析。
四、条件编译
条件编译通过条件判断的方法来实现编译代码的选择。
1. #ifdef 命令
1. 形式1
#ifdef 标识符
程序段 1
#else
程序段 2
#endif
其中程序段由若干处理命令和语句组成。如果程序已经宏定义了标识符,则编译程序段1;如果该标识符未被宏定义,则对程序段2进行编译。
该命令可以进行跨平台编译。
#ifdef SYSTEM_16
const int INT_SIZE = 16;
#else
const int INT_SIZE = 32;
#endif
如果SYSTEM_16在该代码前被宏定义,说明该机型为16位机,将INT_SIZE置为16;否则置为32.
2. 形式2
#ifdef 标识符
程序段 1
#endif
如果标识符已被宏定义,则编译程序段1.
#if DEBUG
调试代码
#endif
在调试代码程序开头定义一个空的DEBUG宏对象即可
#define DEBUG
3. 形式3
#ifdef指令可以嵌套使用。
#ifdef SYSTEM_8
const int INT_SIZE = 8;
#else
#ifdef SYSTEM_16
const int INT_SIZE = 16;
#else
#ifdef SYSTEM_32
const int INT_SIZE = 32;
#else
默认状态
#endif
#endif
#endif
范例5
#include <stdio.h>
//#define DEBUG //宏定义调试模式
//进入调试模式就是将他们的值都打印出来
#define PRI_DEBUG(x,y,z){ \
printf("x = %d, y = %d, z = %d\n",x,y,z); \
}
int main(void){
int x = 0;
int y = 0;
int z = 0;
printf("Input two integer:");
scanf("%d %d",&x,&y);
printf("x = %d, y = %d\n",x,y);
//如果程序中宏定义了DEBUG,那么执行PRI_DEBUG
#ifdef DEBUG
PRI_DEBUG(x,y,z);
#endif
z = x;
#ifdef DEBUG
PRI_DEBUG(x,y,z);
#endif
y = x;
#ifdef DEBUG
PRI_DEBUG(x,y,z);
#endif
x = z;
printf("x = %d, y = %d\n",x,y);
return 0;
}
如果将#define DEBUG屏蔽,也就是关闭DEBUG模式,则不进行PRI_DEBUG
2. #ifndef 命令
#ifndef 和 #ifdef 完全相反,前者相当于if not define(如果没有宏定义);后者相当于if define(如果宏定义)。
1. 形式1
#ifndef 标识符
程序段1
#else
程序段2
#endif
如果标识符未被宏定义,则编译程序段1;如果被定义,编译程序段2.
#ifndef SYSTEM_8
#ifndef SYSTEM_16
#ifndef SYSTEM_32
默认状态执行方法
#else
const int INT_SIZE = 32;
#endif
#else
const int INT_SIZE = 16;
#endif
#else
const int INT_SIZE = 8;
#endif
2. 形式2
#ifndef 标识符
程序段1
#endif
总结: 在使用多个 #ifdef 时,利用 #ifdef 配合 #else 进行选择,程序段紧跟在 #ifdef 下一行,最后一共有多少个 #ifdef 就配合多少个 #endif 进行结束;而在使用 #ifndef 时,进行各种 #ifndef 判断,并由内而外进行对应程序段的执行,并配合 #else 和 #endif ,逐层退出。
3. #if 命令
1. 形式1
#if 表达式
程序段1
#else
程序段2
#endif
如果常量表达式的值为真(非0),执行程序段1,否则执行程序段2。
2. 形式2
#if 表达式
程序段1
#endif
3. 形式3
#if 表达式1
程序段1
#elif 表达式2
程序段2
···
#elif 表达式4
程序段4
#else
程序段5
#endif
多层判断版本,例如:
#ifndef SYSTEM_TYPE == 1
const int INT_SIZE = 8;
#elif SYSTEM_TYPE == 2
const int INT_SIZE = 16;
#elif SYSTEM_TYPE == 3
const int INT_SIZE = 32;
#else
默认执行方式
#endif
4. #defined 宏函数
defined 标识符
defined (标识符)
#ifdef AAA
#ifndef BBB
等价于
#if defined AAA
#if !defined BBB
如果标识符已被宏定义,则该表达式值为真;否则为假。
五、文件包含
1. #include 命令
文件包含的预处理命令的调用形式为:
#include <文件名>
#include "文件名"
文件包含语句的作用在编译前,使用指定文件名的文件内容替代#include语句。
当放在尖括号中,系统会使用标准查找方式,从C语言标准库文件所在的目录中寻找;
当放在双引号中,系统会现在用户当前目录中寻找要包含的文件,再到标准库中寻找。
范例6
//main.c
#include <stdio.h>
#include "file1.h"
void print_OK(int a){
printf("OK,%d\n",a);
}
int main(void){
#if AAA
print_OK(a);
#endif
return 0;
}
//file1.h
#define AAA 2
const int a = 3;
2. 注意事项
由于文件允许被多次包含和嵌套包含,因此很可能会导致一个文件被重复多次包含,甚至循环包含。
范例7
//main.c
#include <stdio.h>
#include "file1.h"
#inculde "file2.h"
void print_OK(int a){
printf("OK,%d\n",a);
}
int main(void){
printf_OK(a1);
printf_OK(a2);
return 0;
}
//file1.h
#include "file2.h"
const int a1 = 3;
//file2.h
#include "file1.h"
const int a2 = 3;
由于file1.h和file2.h发生了循环嵌套,所以陷入了死循环。
练习1
使用宏对象设计printf函数的如下输出形式:
一行内输出2个整数;
一行内输出2个字符;
一行内输出1个整数和1个字符
#include <stdio.h>
#define _2I "%d, %d\n" //简化2个整数的输出格式的书写
#define _2C "%c, %c\n" //简化2个字符的输出格式的书写
#define _IC "%d, %c\n" //简化1个整数和1个字符的输出格式的书写
int main(void){
int int_A = 4;
int int_B = 7;
char ch_A = 'a';
char ch_B = 'A';
printf(_2I, int_A,int_B);
printf(_2C, ch_A,ch_B);
printf(_IC, int_A,ch_B);
return 0;
}
练习2
使用条件编译来实现字符大小写的转换函数,要求一个版本为转换大写,另一个为转换小写。
#include <stdio.h>
#include <string.h>
//#define CAPITAL
#define SIZE 200
#ifdef CAPITAL
const char _Z = 'z';
const char _A = 'a';
const int gap = 'A' - 'a';
#else
const char _Z = 'Z';
const char _A = 'A';
const int gap = 'a' - 'A';
#endif
//大小写转换函数
void converse(char * str){
int i = 0;
int len = strlen(str);
for(i=0;i<len;i++){
if(str[i] <= _Z && str[i] >= _A)
str[i] += gap;
}
}
int main(void){
char str[SIZE] = "\0";
printf("Please input a word:\n");
gets(str);
converse(str);
printf("After conversing:\n");
puts(str);
return 0;
}
当含有#define CAPITAL时,执行大写转换。
当屏蔽掉#define CAPITAL时,执行小写转换。