【C语言】预处理

1.预定义符号

  • __FILE__编译的文件名
  • __LINE__文件当前的行号
  • __DATE__文件编译的日期
  • __TIME__文件编译的时间
  • __STDC__如果遵循ANSI C标准,值为1。否则未定义。

下面打印各个符号的值:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

int main()
{
	printf("__FILE__ : %s\n", __FILE__);
	printf("__LINE__ : %d\n", __LINE__);
	printf("__DATE__ : %s\n", __DATE__);
	printf("__TIME__ : %s\n", __TIME__);
	return 0;
}

输出:

__FILE__ : H:\gitee\c_program_pratice\test_2023_2_20\test_2023_2_20\test.c
__LINE__ : 7
__DATE__ : Feb 20 2023
__TIME__ : 11:16:05

由于vs2022不遵循ANSI C标准,所以是未定义的。

在这里插入图片描述

2. #define和#undef

(1).define定义标识符

比如:

#define MAX_VAL 		30				//定义常量
#define DataType 		int				//类型替换
#define do_forever 		for(;;)			//死循环

我们可以得出#define的语法就是:#define 标识符 表达式。

注意:
最好不要加;这有可能让程序编译不了,举个例子:

#define MIN_VAL  10;
if (1)
	min = MIN_VAL;
else
	min = -1;

min = MIN_VAL后面已经有分号;了,#define后面还有一个,
在这种情况下代码可能会编译不通过。

(2).#define定义宏

使用#define时,也可以把参数替换到文本中,这种实现就叫做宏(macro)。
语法结构为:#define name(para-list) stuff。
举个例子:
这里定义了SQUARE的宏,来计算某个数的平方。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#define SQUARE(n) n * n

int main()
{
	int num = 3;
	int square = SQUARE(num);
	printf("%d\n", square);
	return 0;
}

输出:

9

这个宏还有一点问题,只要我们的输入一变,结果就可能出问题。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#define SQUARE(n) n * n

int main()
{
	int square = SQUARE(3 + 1);
	printf("%d\n", square);
	return 0;
}

输出:

7

出现这样的结果是因为在进行宏替换时,不会事先计算表达式的值,而是直接进行替换。
SQUARE(3 + 1) = 3 + 1 * 3 + 1 = 7
所以输出的才是7

正确的写法为:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#define SQUARE(n) (n)*(n)

int main()
{
	int square = SQUARE(3 + 1);
	printf("%d\n", square);
	return 0;
}

输出:

16

但是新的问题又产生了,如果int square = 32 / SURARE(3 + 1),我们期望的结果是2。
但是结果并不是,上述式子等价于int square = 32 / (3 + 1) * (3 + 1)结果为32。
下面是代码和运行结果:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#define SQUARE(n) (n)*(n)

int main()
{
	int square = 32 / SQUARE(3 + 1);
	printf("%d\n", square);
	return 0;
}

输出

32

我们希望的是整个宏的计算结果是一个整体,那么还要整体加一个括号。
最终的写法为:#define SQUARE(n) ((n)*(n))

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#define SQUARE(n) ((n)*(n))

int main()
{
	int square = 32 / SQUARE(3 + 1);
	printf("%d\n", square);
	return 0;
}

输出:

2

注意:
为了使宏在进行数值运算的时候结果更加符合预期,我们要对每个参数加括号,当作一个表达式。还要对宏整体加括号,把宏的计算结果当作一个整体。

(3).宏的替换规则

如果宏嵌套了宏,会把最外层的宏替换掉,再作为参数,进行宏替换。
如:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#define SQUARE(n) ((n)*(n))
#define SUM(a, b) ((a) + (b))

int main()
{
	int val = SQUARE(SUM(1, 2));
	printf("%d\n", val);
}

输出:

9

SQUARE(SUM(1, 2))      ~~~~     替换成SQUARE( ((1)+(2)) )
SQUARE( ((1)+(2)) )替换成( (((1)+(2))) * (((1)+(2))) )

(4).带副作用的宏参数

我们有时才函数传参时会传a++, sum++, b--之类的参数,可是作为宏参数时可能引起大麻烦。

比如,你写了一个宏:DOUBLE(n) ( (n) + (n) )
此时,正常传参DOUBLE(2)                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                                       结果为: 4
但是,如果a = 1a++作为参数DOUBLE(a++)               ~~~~~~~~~~~~~              结果为: 5
结果为5并不是我们想要的,这是因为:DOUBLE(a++) = ((a++) + (a++))
a自增了两次,所以结果才为5;
所以在宏传参时,要注意会不会改变原来参数的结果,比如传a++时可以传a + 1;

(5).#undef

#undef的作用是移除一个宏, 其用法如下:

#undef name

比如:

#define MIN_VAL 3
#undef MIN_VAL

3. 条件编译

在写代码的时候,有一些代码有时需要,有时不需要,这很抽象我们举个例子。
写一个获得一个随机数组的函数,我们可以选择不打印这个数组,也可以选择打印这个数组。
代码如下:

int* GetArr(int size)
{
	//设置种子
	srand((unsigned int)time(NULL));

	//开辟空间
	int* arr = (int*)malloc(sizeof(int) * size);

	//给数组里的元素赋值
	for (int i = 0; i < size; i++)
	{
		arr[i] = (int)rand() % 100 + 1;
	}

//如果定义了__print__宏,则编译下面的代码
#ifdef __print__
	for (int i = 0; i < size; i++)
	{
		printf("%d ", arr[i]);
	}
#endif
	
	return arr;
}

在这里只需要关注条件编译的代码,其他的代码可以不关注

#ifdef __print__
	for (int i = 0; i < size; i++)
	{
		printf("%d ", arr[i]);
	}
#endif

这里用到了#ifdef __print__和#endif
夹在中间的代码,有时参与编译,有时不参与编译。这要看条件#ifdef __print__
意思是,如果定义了__print__这个宏,就编译下面的代码,否则下面的代码不参与编译。

下面完整运行一遍这个函数:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<time.h>

int* GetArr(int size)
{
	//设置种子
	srand((unsigned int)time(NULL));

	//开辟空间
	int* arr = (int*)malloc(sizeof(int) * size);

	//给数组里的元素赋值
	for (int i = 0; i < size; i++)
	{
		arr[i] = (int)rand() % 100 + 1;
	}

//如果定义了__print__宏,则编译下面的代码
#ifdef __print__
	for (int i = 0; i < size; i++)
	{
		printf("%d ", arr[i]);
	}
#endif
	
	return arr;
}

int main()
{
	//得到范围从1-100的三个元素的数组
	int* arr = GetArr(3);
	return 0;
}

当我们没有定义宏的时候,运行main函数,没有任何输出。
但当我们定义__print__宏时,就会输出生成的数组。
即:

#define __print__

运行,输出:

92 53 30

因为是数据是随机的,每次运行都不一样,可以在自己跑一下代码。
这就是条件编译的妙处,你需要编译某段代码时,定义一个宏就可以解决。
而不是不需要的时候就不写,需要的时候又要花时间把代码重写一遍。
下面介绍几个条件编译中常用到的语句。

1.#if 和#endif

用法和平时用到的if语句差不多
下面是#if的语法说明:

#if 常量表达式

#endif

下面是一个简单示例:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
#if 1
	printf("1\n");
#endif
	return 0;
}

输出:

1

我们把1改成0,就不会输出了

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
#if 0
	printf("1\n");
#endif
	return 0;
}

2.多分支#if

多分支#if跟平常用的if else if else语法结构差不多,非常类似。
其语法结构如下:

#if      ~~~~     常量表达式

#elif    ~~   常量表达式

#else

#endif

3.#ifdef和#if defined()

#ifdef#if defined()要表达的都是一个意思:如果定义了xxx
基本语法:

#ifdef symbol
#if defined(symbol)

#ifndef symbol
#if !defined(symbol)

下面是这几个语句的使用:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#define ADD

int main()
{
	int val = 1;

//ifdef的使用
#ifdef ADD	
	val += 1;
#endif

//if defined()的使用
#if defined(ADD)
	val += 1;
#endif
	
//ifndef的使用
#ifndef ADD
	val -= 1;
#endif

//if !defined()的使用

#if !defined(ADD)
	val -= 1;
#endif

	printf("val = %d\n", val);
	return 0;
}

输出:

3

这里只定义了ADD,val自增了两次,后面的自减代码是不参与编译的,所以val不会自减。
最终val = 3。

4.头文件包含

头文件包含这块,最经常见到的问题就是:
#include"filename.h"和#include<filename.h>的区别?

#include"filename.h"首先会从源代码的目录里开始找,如果找不到,再去标准库里找头文件

#include<filename.h> 会直接去标准库下找头文件

当然了,头文件包含还有很多要注意的,这里就先略过了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值