【C语言进阶】预定义详解

一、预定义符号

预定义符号是C标准定义的宏定义符号
例如 FILE LINE DATE TIME STDC(如果编译器遵循ASCI C就返回1,否则未定义。)

int main()
{
	printf("%s\n",__FILE__);
	printf("%d\n",__LINE__);
	printf("%s\n",__DATE__);
	printf("%s\n",__TIME__);
	//printf("%s\n__STDC__);
}

在这里插入图片描述

二、#define

1.#define定义标识符

语法:
define name stuff

(而这个stuff可以是不同的类型)

1.#define MAX 100
2.#define reg register(给register起一个短一点的名字)
3.#define arr “hello world.”
我们也可以利用这个来写一个死循环

#define do_forever for(; ;)

当然在写代码的时候会有这样一个疑问,如果要写的stuff过长怎么办?这个时候我们可以分行写,但是除了最后一行外,每一行都要加一个反斜杠(续航符)

#define fad printf("name:%s\tage:%s\t \
                   tele:%s\taddre:%s\t",\
                    name,age\
                    tele,addre)

2.#define定义宏

#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏 (macro)或者定义宏(define macro)。

宏的申明方式
#define name(parament-list) stuff
其中parament-list是一个由逗号隔开的符号表,有可能会出现在后面的stuff中。
注意:参数列表的左括号必须与name紧邻,不能有空格或者空白,否则会被认为是stuff的一部分。

在定义宏时要合理使用括号,防止运算符的优先级问题

#define SQUARE(x) x*x

int main()
{
	int a = 4;
	printf("%d", SQUARE(a));
	return 0;
}

在这里插入图片描述
但如果换成SQUARE(4+1)呢

#define SQUARE(x) x*x

int main()
{
	//int a = 4;
	printf("%d", SQUARE(4+1));
	return 0;
}

在这里插入图片描述
我们可以看到结果是9,那是因为替换方式

#define SQUARE(x) 4+1*4+1

这样就提醒我们在写的时候最好带上括号

#define SQUARE(x) (x)*(x)

int main()
{
	//int a = 4;
	printf("%d", SQUARE(4+1));
	return 0;
}

在这里插入图片描述
但是这样写任然不够严密,在有些时候还是会出现错误

将上述代码中printf一部分换成
printf(“%f”, 1.0/SQUARE(4+1))

在这里插入图片描述
答案显然不对,按上述的替换法方式就变成了
#define SQUARE(x) 1.0/(4+1)*(4+1)
那么这样得出错误的结果也就不奇怪了
为了达到最初的目的,我们应该这样

#define SQUARE(x) ((x)*(x))

int main()
{
	//int a = 4;
	printf("%f", 1.0/SQUARE(4+1));
	return 0;
}

在这里插入图片描述

当然还有一个特殊的地方就是不要在定义标识符或者定义宏时加上分号(;),这样很容易在使用时出错

#define SQUARE(x) ((x)*(x));
 int main()
{
	if(1)
	int a=SQUARE(4);
	printf("%d", a);
	return 0;
}

编译预处理完,替换之后就变成了

if(1)
>int a=((4)*(4));;

if后面没有大括号跟两条语句是不对的

所以我们在定义标识符或者定义宏时不要加上分号,避免上述错误。

3.#define替换规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。

1.在调用宏时,首先多参数进行检查,看看是否包括任何由#define定义的符号,如果是他们首先被替换。
2.替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的只所替换。
3.最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
在这里有几个需要注意的地方
1.宏参数和#define定义中可以出现其他#define定义的符号,但是对于宏,不能出现递归。
2.当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

4.#和##

4.1 #号

如何把参数插入到字符串中?
首先我们要知道字符串是有自动连接的功能的的特点的。
比如:

int main()
{
    printf("hello""world");
    return 0;
}

在这里插入图片描述
而#号加在宏参数前时,可以将这个参数转化为字符串。

int main()
{
	int a = 10;
	printf("the value of a is %d\n", a);
	int b = 20;
	printf("the value of b is %d\n", b);
	float c = 30.0f;
	printf("the value of c is %f\n", c);
	return 0;
}

可以看出只有a,b,c和%d,%f不同,这个时候可以用宏来写

#define PRINT(x,format) printf("the value of "#x" is "format"\n",x)
int main()
{
	int a = 10;
	PRINT(a, "%d");
	int b = 20;
	PRINT(b, "%d");
	float c = 30.0f;
	PRINT(c, "%f");
	return 0;
}

4.2 ##号

##的作用是将两个字符合成一个字符。

#define A(x,y) printf("%d\n",x##y)
int main()
{
	int ab = 20;
	A(a, b);
	return 0;
}

在这里插入图片描述

三、函数与宏

1.代码长度

宏:每次使用时在编译的预处理阶段宏的代码就会插入到程序中,如果宏的代码量很大的话,那么整体的代码量也会大大增加。
函数:函数的代码只有一份,每次调用都从这里调用。

2.执行速度

宏比函数在速度和规模上都要更胜一筹。

3.操作符的优先级

宏可能会带来操作符的优先级的问题。
比如上面已经提过的:

#define SQUARE(x) x*x

int main()
{
	//int a = 4;
	printf("%d", SQUARE(4+1));
	return 0;
}

执行答案是9,但是我们的目的是算(4+1)*(4+1)

4.参数类型

宏:参数与类型无关,只要参数合法就可以使用
函数:只能在类型合适的表达式上使用,即参数必须声明为特定的类型。

当然,因为函数与参数无关也就不够严谨。
比如,对于比较大小,宏可以比较不同类型的大小(整形,浮点型等等)

#define MAX(x,y) ((x)>(y)?(x):(y))

5.调试

宏:由于宏是在编译预处理直接替换的,所以无法调试
函数:可以调试在vs2019下,按F10是逐过程,F11是逐语句(可以进入函数内部调试)。

6.递归

宏是不能递归的
函数可以,比如用递归写斐波那契数列

int fib(int n)
{
    if (n == 0 || n == 1)
        return n;
    else
         return fib(n - 1) + fib(n - 2);
}

7.带有副作用的参数

宏:宏参数可能被替换到多个位置,更容易出现问题
函数:只在传参时计算一次,结果不容易出错
比如:

#define MAX(x,y)  printf("%d",((x)>(y)?(x):(y)))
int main()
{
    int a = 1;
    int b = 2;
    MAX(++a, ++b);
    return 0;

我们这里想要比较a++和b++ 的大小,也就是2和3的大小
我们想要的答案应该是3才对
但事实上
在这里插入图片描述

这是因为经过替换后
#define MAX(x,y) printf(“%d”,((++a)>(++b)?(++a):(++b)))
所以答案为4

这也是提醒我们在使用宏时不要使用带有副作用的参数

四、条件编译

在编译一个程序的时候,可以用条件编译来选择将一条语句编译或者放弃。

#include <stdio.h>
#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 
	}
	return 0;
}

在这里插入图片描述

1.
#if 常量表达式
//...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif
2.多个分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
3.判断是否被定义
#if defined(symbol) //等于       #ifdef symbol
#if !defined(symbol)//       #ifndef symbol

4.嵌套指令(与if嵌套相似)
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif
  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值