C语言-基础入门-学习笔记(15):预处理

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. 替换字符串

  1. 简化书写
    对于一些较长的但固定的语句,可以使用宏对象来简化他们的书写。
printf("--------------------------------------");
简化:
#define PRI_DIVISION printf("--------------------------------------")
  1. 改变输出结构
  2. 不要使用宏对象来简化数据类型的书写:简化数据类型的书写最好使用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时,执行小写转换。
在这里插入图片描述

发布了55 篇原创文章 · 获赞 76 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_42826337/article/details/103144373
今日推荐