【C语言】预处理操作

  在编译一个C语言涉及很多步骤。其中第一个步骤就是预处理阶段。

1、预处理详解

1.1、预定义符号

下面这章表总结了由预处理器定义的符号。它们的值或者是字符串常量,或者是十进制数字常量。__ FILE __ 和 __ LINE __ 分别用于查看文件的磁盘地址和源文件代码行号,__ DATE __ 和 __ TIME 分别代表日期和时间, STDC __ 辨别编译器是否遵循ANSI C。
在这里插入图片描述
具体展示:

#include<stdio.h>
int main()
{
	int i = 0;
	FILE* pf = fopen("log.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return -1;
	}
	for (i = 0; i < 10; i++)
	{
		fprintf(pf, "file:%s line=%d date:%s time:%s i=%d\n", __FILE__, __LINE__, __DATE__, __TIME__, i);
	}
	fclose(pf);
	pf = NULL;

	return 0;
}

在这里插入图片描述

1.2、#define

1.2.1 #define 定义标识符

#define name stuff

比如

#define MAX 1000
//如果是 #define MAX 1000;
//max = 1000;; 不太合适
if(condition)
	max = MAX;
else
	max = 0;
1.2.2 #define 定义宏

#define 机制有一个规则,运行把参数替换到文本中,这种实现被称为宏或者定义宏。

下面是宏的声明方式:

#define name( parament-list ) stuff
其中parament-list 是一个由逗号分隔开的符号表,它们可能出现在stuff中。

注意:
参数列表的左括号必须与name紧贴。
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。

下面看宏的替换
比如有一个

#define SQUARE( x ) x*x

SQUARE(5);将会等于55,那么如果是SQUARE(5 + 1),就会等于5 + 1 * 5 +1。
这就比较清晰的提醒我们,进行宏定义的时候,最好是别省括号。
比如x * x写成 ((x)
(x)),这样能使我们更准确的得到预期效果。

1.2.3 #define 替换规则
  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
  3. 最后再看看是否还有其它的#define定义的符号。如果是就重复1,2步骤。

注意:

  1. 宏参数和#define 定义中可以出现其它#define 定义的符号。但是对于宏,不能出现递归。
  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
1.2.4 #和##

如何把参数插入到字符串中?

char* p ="hello ""world\n";
printf("hello," world\n);
printf("%s",p);

这里输出的是不是
hello world?
是,我们发现字符串是有自动连接的特点的。


当字符串作为宏参数的时候想做为字符串放入字符串中。
用# ,把一个宏参数变成对于的字符串。
也就是下面这个情况
将i+3作为参数传给VALUE,并且通过#将参数原本的内容作为一个字符串放入字符串。

int i = 10;
#define PRINT(FORMAT, VALUE)\
	printf("the value of " #VALUE " is "FORMAT "\n", VALUE);
...
PRINT("%d", i+3); //产生什么效果?

在这里插入图片描述

##的作用

##能将两边的符号合成一个符号
注意:
这样的连接必须产生一个合法的标识符。否则结果就是未定义的。

#define CAT(str, Num) str##Num

int main()
{
	char* str1 = "hello world";
	printf("%s\n", CAT(str, 1)); //"hello world"
	printf("%s\n", str1); //"hello world"

	return 0;
}

1.2.4 带副作用的宏参数

x+1; 不带副作用的
x++; 带副作用的

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);

输出结果:6,10,9。

1.2.4 宏和函数对比

宏通常被应用与执行简单的运算。
比如在需要一些简单的加减乘除操作,宏的优秀性就比较突出。
因为:

  1. 宏比函数在程序的规模和速度方面更胜一筹
  2. 宏与类型无关

但是宏也有缺点

  1. 每次使用宏的时候,需要将宏定义的代码插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  2. 宏是无法调试的。
  3. 宏因为和类型无关,所以不够严谨
  4. 宏可能会带来优先级的问题,容易使得程序出错。
1.2.4 命名约定

宏名全部大写
函数名不要全部大写

1.3 #undef

#undef NAME
用于移除一个宏定义

1.4 条件编译指令

当有些调试性的代码,删除可以,保留又碍事,所以我们可以选择性的编译。
比如:

#define __DEBUG__

int main()
{
	int i = 0;
	int arr[10] = { 0 };
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
#ifdef __DEBUG__
		printf("%d\n", arr[i]);
#endif //__DEBUG__
	}
	return 0;
}

还有其它的常见条件编译指令:

#define M 6

int main()
{
#if M<5
	printf("haha\n");
#elif M==5
	printf("heihei\n");
#else 
	printf("hey!\n");
#endif

	return 0;
}

ifndef == if not define
ifdef == if define

#define MAX 100

int main()
{
#ifndef MAX  //#indef MAX
	printf("max\n");
#endif
	return 0;
}

1.5 实现OFFSETOF

宏定义offsetof是用来观察结构体成员的偏移地址的。
offsetof(type, m_name)
比如

#include <stddef.h>
struct S
{
	char c1;
	int a;
	short c2;
	char c3;
};

int main()
{
	struct S s = { 0 };
	printf("%d\n", offsetof(struct S, c1));
	printf("%d\n", offsetof(struct S, a));
	printf("%d\n", offsetof(struct S, c2));
	printf("%d\n", offsetof(struct S, c3));
}

在这里插入图片描述

从地址为0的位置访问结构体成员,就能得到对应的地址以及相等的偏移量。

struct S
{
	char c1;
	int a;
	short c2;
	char c3;
};

#define OFFSETOF(type,name) (int)&(((type*)0)->name)

int main()
{
	struct S s = { 0 };
	printf("%d %d %d %d",OFFSETOF(struct S, c1), OFFSETOF(struct S, a),
		OFFSETOF(struct S, c2), OFFSETOF(struct S, c3));
	return 0;
}

本章完

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值