宏与函数C语言详解

目录

一.#define 定义宏

二.无参数的宏

三.有参数的宏

四.参数宏和函数比较

五.undef与命令行定义

六.#与##的妙用

1.#

 2.##




一.#define 定义宏

在 C 语言中采用命令 #define 来定义宏。该命令会把一个名称指定成名称后面的代码,例如一个常量值或者一条语句。在定义了宏之后,无论宏名称出现在源代码的何处,在代码编译的预处理阶段预处理器都会把它用定义时指定的代码替换掉。

#define ARRAY_SIZE 100
double data[ARRAY_SIZE];

二.无参数的宏

//格式:   #define 宏名称 替换文本
#define MAX 100
#define ROW 100
#define RANDOM (-1.0 + 2.0*(double)rand() / RAND_MAX)

在预处理阶段,MAX和ROW,RANDOM会被识别并被替换为对应的文本。一般的我们进行宏定义时名称都用大写。

三.有参数的宏

另一类宏是具有形式参数的宏。带有形参的宏通常也称为类函数宏(function-like macro)。


//格式:
//    #define 宏名称( [形参列表] )  替换文本

在预处理阶段处理展开这类宏时,它会将调用宏时指定的实际参数取代替换文本中对应的形参然后再将替换文本替换进去。

​#define DOUBLE(x) x + x
//定义宏,为求对应x的两倍

​

事实上,这样子定义宏还是不够规范的。会导致一定的问题,如下例子: 

问题一:

#define DOUBLE(x) x+x
int main()
{
  printf("%d",DOUBLE(4)*3+1);
  return 0;
}
//替换后函数真实代码:首先4被放入替换文本“x+x”的x位置,替换文本变成4+4,然后再直接替换DOUBLE(4)
int main()
{
  printf("%d",4+4*3+1);
  return 0;
}
//我们想要要的其实是4的两倍8乘3再加一的结果,但是对于宏的替换来说,它只替换。这样意味着,我们写宏时要注意规避这样因优先级引起的问题。

这个问题的解决办法是在宏定义表达式两边加上一对括号就可以了。

​
#define DOUBLE(x) (x+x)
int main()
{
  printf("%d",DOUBLE(4)*3+1);
  return 0;
}
//替换后函数真实代码:首先4被放入替换文本“(x+x)”的x位置,替换文本变成(4+4),然后再直接替换
int main()
{
  printf("%d",(4+4)*3+1);
  return 0;
}

​

问题二:

​
​
#define SQUARE(x) x*x
int main()
{
  printf("%d",SQUARE(4+1));
  return 0;
}
//替换后函数真实代码:
int main()
{
  printf("%d",4+1*4+1);
  return 0;
}
//我们想要要的其实是5平方的结果,但是对于宏的替换来说,它只替换。这样意味着,我们写宏时要注意规避这样因优先级引起的问题。
//改进:
#define SQUARE(x) (x)*(x)
​//替换后函数真实代码:
int main()
{
  printf("%d",(4+1)*(4+1));
  return 0;
}

​

注意:所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。例:

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

另外,宏可以实现多行定义,只需要在每行最后加上字符'\':

#define SWAP(x, y)\
x = x + y;\
y = x - y;\
x = x - y;

四.参数宏和函数比较

属 性#define定义宏函数
代 码 长 度每次使用时,宏代码都会被插入到程序中。除了非常
小的宏之外,程序的长度会大幅度增长
函数代码只出现于一个地方;每
次使用这个函数时,都调用那个
地方的同一份代码

例:当宏代码量比较多且使用频率较高时,预处理过后每个有SWAP()的地方都会加三行代码,最后代码会比较冗余,这也是为什么多次使用的代码我们会封装成函数来使用。 

#define SWAP(x, y)\
x = x + y;\
y = x - y;\
x = x - y;

int main()
{
	int a = 10, b = 20;
	SWAP(a, b)
	printf("%-2d  %d\n", a, b);

	a--, b--;
	SWAP(a, b)
	printf("%-2d  %d\n", a, b);

	a--, b--;
	SWAP(a, b)
	printf("%-2d  %d\n", a, b);

	a--, b--;
	SWAP(a, b)
	printf("%-2d  %d\n", a, b);

	a--, b--;
	SWAP(a, b)
	printf("%-2d  %d\n", a, b);

	return 0;
}
属 性#define定义宏函数
执 行 速 度更快存在函数的调用和返回的额外开
销,所以相对慢一些

例:

属 性#define定义宏函数
操 作 符 优 先 级宏参数的求值是在所有周围表达式的上下文环境里,
除非加上括号,否则邻近操作符的优先级可能会产生
不可预料的后果,所以建议宏在书写的时候多些括
号。
函数参数只在函数调用的时候求
值一次,它的结果值传递给函
数。表达式的求值结果更容易预
测。

例:

#define DOUBLE(x) (x) + (x)
int a = 5;
printf("%d\n" ,10 * DOUBLE(a));

  具体就可以参考上面的两个问题。

属 性#define定义宏函数
带 有 副 作 用 的 参 数参数可能被替换到宏体中的多个位置,所以带有副作
用的参数求值可能会产生不可预料的结果。
函数参数只在传参的时候求值一
次,结果更容易控制。

 因为宏是单纯的替换,如果宏参数在宏的定义中出现超过一次的时候,参数带有副作用的影响回会因为使用次数而变大。在使用这个宏的时候就可能导致不可预测的后果。副作用就是指:表达式求值的时候出现的永久性效果。

//例:
//x++ y--
//都会改变x,y的值
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 10;
y = 20;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?
//替换后:
//z = ( (x++) > (y++) ? (x++) : (y++));
//会发现x++这种式子出现多次,导致x++次数不是我们想要的
//直接使用max函数将不会,只是在参数传递x++,y++,函数体内不会出现带副作用的式子
属 性#define定义宏函数
参 数 类 型宏的参数与类型无关,只要对参数的操作是合法的,
它就可以使用于任何参数类型。
函数的参数是与类型有关的,如
果参数的类型不同,就需要不同
的函数,即使他们执行的任务是
相同的。

例子:

#define SWAP(x, y)\
x = x + y;\
y = x - y;\
x = x - y;

void swap(int* a,int* b)
{
	int tmp;
	tmp = *a;
	*a = *b;
	*b = tmp;
}
int main()
{
	int a = 10, b = 20;
	double c = 19.0;
	swap(&a, &b);
	swap(&a, &c);//error
	SWAP(a,c)
	printf("%-2d  %d\n", a, b);
	return 0;
}

甚至,宏可以传入类型等函数不能设置的类型,如下: 

属 性#define定义宏函数
调 试宏是不方便调试的函数是可以逐语句调试的

 因为在预编译阶段宏部分被替换了,但你运行起来以后无法进入宏内部调试。

属 性#define定义宏函数
递 归宏是不能递归的函数是可以递归的

因为宏无法终止递归,将陷入死循环,而函数却可以实现递归。

五.undef与命令行定义

undef用于移除定义

#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除

命令行定义
许多C 的编译器允许在命令行中定义符号。用于启动编译过程。例如:

当我们根据同一个源文件要编译出一个程序的不同版本的时候(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大些,我们需要一个数组能够大些。)

#include <stdio.h>
int main()
{
int array [ARRAY_SIZE];
int i = 0;
for(i = 0; i< ARRAY_SIZE; i ++)
{
array[i] = i;
}
for(i = 0; i< ARRAY_SIZE; i ++)
{
printf("%d " ,array[i]);
}
printf("\n" );
return 0;
}

//linux 环境演示  编译指令,实现编译时指定大小
gcc -D ARRAY_SIZE=10 programe.c
 

六.#与##的妙用

1.#

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

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

答案是确定的:都是输出hello bit,从这点看我们会发现字符串是有自动连接的特点的。

问题一:

可以看到如果程序里需要大量的这样打印较为繁琐,那么利用字符串拼接和宏的特性,当我们遇到很多这样的类似输入时能不能用宏简化操作呢?答案也是肯定的。我们可以定义下面的宏:

#define PRINT(FORMAT, VALUE)\
printf("the value is "FORMAT"\n", VALUE);

这样我们就不用每次打印都写重复代码了。

 问题二:

 在上个题的基础上,如果我们需要加上a,d,s之类的表达式如何做呢?显然,我们不能直接在字符串里加"value",因为这样它只是固定的单词,而不会根据表达式变化而变化。这时候就可以使用#来解决这个问题。

#define PRINT(FORMAT, VALUE)\
printf("the value of " #VALUE "is "FORMAT "\n", VALUE); 
//# ,根据不同的宏参数把它变成对应的字符串。

 2.##

##可以把位于它两边的符号合成一个符号。它允许宏定义从分离的文本片段创建标识符。如果定义下列宏:

#define ADD_TO_SUM(num, value) \
sum##num += value;

 

可以较方便使用名字基本相同的多个变量的操作,但需要注意:##连接起来必须产生一个合法的标识符。否则其结果就是未定义的。比如本宏就不能写ADD_TO_SUM(10,10);因为拼接出sum10并不存在。

  • 7
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值