预处理详解(其一)

目录

一、预定义符号(预定义宏名):

二、#define(宏定义):

1、对象式宏(类对象式宏):   宏替换 (定义符号)

定义常量:

定义关键字(具有特殊意义的标识符):

其他的用法:

是否要加分号:

2、函数式宏(类函数式宏):宏展开(定义宏)

有参数函数宏:

无参数函数式宏:

​编辑

存在问题:

宏的副作用:

技巧:运用 逗号表达式:

 3、宏替换(规则):

4、函数式宏(宏定义)与函数对比:

简单的运算:优先用宏

对比图:

 三、#和##运算符:

1、#运算符:

2、##运算符:

 四、命名约定:

五、总结:


在C语言中凡是以“#”开头的均为预处理命令

一、预定义符号(预定义宏名):

c语言设置了一些预定义的符号(宏名),可以直接使用,在预处理阶段处理的,

__FILE__      //进行编译的源文件
__LINE__      //文件当前的行号
__DATE__      //文件被编译的日期
__TIME__      //文件被编译的时间
__STDC__      //如果编译器遵循ANSI C,其值为1,否则未定义
__FUNCTION__  //进行编译的函数__FILE__      

 使用:

上述预定义符号可在日志中使用,记录相应的信息写到文件里 

二、#define(宏定义):

1、对象式宏(类对象式宏):   宏替换 (定义符号)

    使用时宏,我们用到的是宏定义中的宏名,理解为幻术,其实它本来是suff,但是在幻术下看到的是name这个标识符,可是幻术终归是幻术;预处理节点就会打破幻术,展现其本来的样子(宏替换)宇智波一族有点厉害哈

不接收参数,只是根据宏定义做简单的字符串替换操作.

定义常量:

MAX  为宏名  100为替换的内容  在预处理阶段会用100去替换掉所有用MAX这个标识符:

#define MAX 100//之后的MAX会替换成100

int main()
{
	int a[MAX] = { 1,0 };
	return 0;
}

定义关键字(具有特殊意义的标识符):

指的是c语言中自带的(或者说被赋予了特殊意义的)标识符;宏定义它 可以理解为给这个标识符弄出来了一个别名。好比有些歌手会用一个艺名类似。

//定义标识符
#define INT int
INT main()
{
	INT a = 10;
	printf("%d", a);
	return 0;
}

简化关键字:有时候觉得关键字太长了,可以为它创建一个简短的名字

#define Cnt  const
int main()
{
	Cnt int a = 10;
	printf("%d", a);
	return 0;
}

其他的用法:

用更形象的标识符来替换一种实现;

#define do_forever  for(;;)
int main()
{
	//恭喜进入死循环,for循环里啥也没有就是死循环了
	do_forever;
	return 0;
}

在写case语句时,将break加上:

#define CASE break;case  //在写case语句的时候自动把 break写上。
int main()
{
	int i = 1;
	switch (i)
	{
	case 1:         // 等同于     case 1:
		CASE 2 :       //等同于barek;  case 2:
			CASE 3 :   //等同于break;  case 3:   break;
			break;
	}
	return 0;
}

定义的 stuff 过长时,我们可以分行写,除了最后一行都要加一个反\在后面表示续行

#define DEBUG_PRINT printf("file:%s\tline:%d\t \
									date:%s\ttime:%S\n",\
										__FILE__,__LINE__,\
										__DATE__,__TIME__)
int main()										
{
	DEBUG_PRINT;						
	return 0;
}									

是否要加分号:

从这些用法中,发现了么????我么都没有在宏定义末尾加“ ; ”(分号/终止符)

容易出错,我们写代码写完一个语句,习惯性的会打分号,所以不建议在宏定义时加分号

注意:宏对象和宏名之间是有空格的

总结:定义对象可以是常数,也可以是一段代码

2、函数式宏(类函数式宏):宏展开(定义宏)

        函数式宏不仅进行简单的字符串替换,而且还要包含参数的替换。函数式宏,顾名思义:具有了与函数相似的特征(和函数有区别的),可以进行传参。在使用时,我们可以进行传参,传参后,会在预处理阶段进行:将替换的内容进行宏展开,paranment-list 是一个有逗号隔开的符号表。可能出现在 stuff 中如:max(x,y)    

 注意:左括号与name不能存在空白,必须紧挨在一起,否则参数列表就会被识别成为suff的一部分(将参数列表看出了对象式宏)

思路:将变量传进去,然后进行宏展开

有参数函数宏:

 就是要传参的宏,看下面求平方的函数式宏:

无参数函数式宏:

和不要传参的函数类似,不需要传参

存在问题:

1、

 看起来是正常的,但是我换一个参数穿上去,出错了,和预想的不一样啊

因为是替换文本时直接将n+1传过去,然后宏展开。并不会先计算了在传过去,在还没有进行运算就进行了替换,如下的样子:

2、给其变量加了括号还会有问题出现,我们想要的因该是100 却是55

因为宏展开为:10 *(5)+(5)

 为了避免这种情况发生我们在宏定义时,每个参数每个表达式都加括号,就不会出现这种问题了

宏的副作用:

宏参数 在宏的定义中出现 超过⼀次 的时候,如果参数带有副作⽤,那么你在使⽤这个宏的时候就可能出现危险,导致不可预测的后果。副作⽤就是表达式求值的时候出现的永久性效果。

如下:我们传的是x++和y++,

#define MAX(a,b) ((a)>(b)?(a):(b))

int main()
{
	int x = 3;
	int y = 5;
	int z = MAX(x++, y++);
	printf("x=%d\ny=%d\nz=%d\n", x, y, z);
	return 0;
}

 运算如下:y在不经意间表达式执行了2次

技巧:运用 逗号表达式:

("a,b"对于左侧的a进行判断,判断结果被省去,对于右侧b进行判断所得到类型和值就是"a,b"的类型和值)

我们发现上面的函数式宏,后面的都是一个stuff都是一个语句

由逗号连接的二个表达式可以在语法上可以视为一个表达式(当然了只要由运算符连接的多个表达式都可以看做是一个表达式表达式后加一个 ;(分号)就会变成表达式语句;若是宏定义中需要2个及以上的表达式,我们用逗号运算符连接,让其在语法上为一个表达式

如下:代码,不仅能够做到响铃,还能打印字符串

//响铃
#define alert() (putchar('\a'),puts("加油!"))
int main()
{
	alert();
	return 0;
}

 3、宏替换(规则):

 在程序中扩展#define定义符号和宏:

1. 在调⽤宏时,⾸先对参数进⾏检查,看看是否包含任何由#define定义的符号。如果是,它们⾸先 被替换。

2. 替换⽂本随后被插⼊到程序中原来⽂本的位置。对于宏,参数名被他们的值所替换。

3. 最后,再次对结果⽂件进⾏扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程

注意: 1. 宏参数和#define定义中可以出现其他#define定义的符号。,不能出现递归

2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

4、函数式宏(宏定义)与函数对比:

简单的运算:优先用宏

1. ⽤于调⽤函数和从函数返回的代码可能⽐实际执⾏这个⼩型计算⼯作所需要的时间更多。所以宏⽐ 函数在程序的规模和速度⽅⾯更胜⼀筹。(宏的运算速度更快

2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使⽤。反之 这个宏怎可以适⽤于整形、⻓整型、浮点型等可以⽤于 > 来⽐较的类型。(宏的参数与类型无关)

宏的劣势:

1、每次使⽤宏的时候,⼀份宏定义的代码将插⼊到程序中。除⾮宏⽐较短,否则可能⼤幅度增加程序的⻓度。

2、宏是没法调试的。

3、宏由于类型⽆关,也就不够严谨。

4、宏可能会带来运算符优先级的问题,导致程容易出现错

对比图:

 三、#和##运算符:

1、#运算符:

#运算符将宏的⼀个参数转换为字符串字⾯量。它仅允许出现在带参数的宏的替换列表中。

#运算符所执⾏的操作可以理解为”字符串化“。也就是说,可以将参数变成字符串

 如图 #n = “n”;为啥我的#n没有放到双引号里面呢????

#define PRIN(n) (printf(#n"的值为%d",n))

int main()
{
	int n = 10;
	PRIN(n);
	return 0;
}

因为 c语言支持连接相邻的字符串常量,“ABC”“EFD” 连接起来就是“ABCDEF”,如下

2、##运算符:

##可以把位于它两边的符号合成⼀个符号,它允许宏定义从分离的⽂本⽚段创建标识符。为记号粘合这样的连接必须产⽣⼀个合法的标识符。否则其结果就是未定义的。

 粘合符-》    A ## B  合成一个标识符(字母,下划线开头《非数字开头》)主要是对特殊的标识符进行粘合

在我们用某些函数时 定义类似,只是数据类型有差异时,我们可以用宏定义来建立函数,相当于宏定义给我们提供了模板,可以根据类型创建函数

//函数符号
#define GNERIC_MAX(type) \
					type type##_max(type x,type y)\
					{\
						return x>y?x:y;\
					}
//定义函数
GNERIC_MAX(int);

int main()
{
	int x = 4;
	int y = 6;
	int z = int_max(x, y);
	printf("%d", z);
	return 0;
}

 四、命名约定:

一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。

① 把宏名全部大写。

② 函数名不要全部大写。

五、总结:

#define声明、定义都不同,宏定义不占内存空间,因为宏在预处理阶段就会被替换掉,到了编译的阶段是没有宏存在的,它在预编译阶段就被处理了 ,所以不占内存空间

点击  》》》》其二

  未                                完                         待                              续

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值