一:#define 替换文本,
最简单的预定义指令
#define MAX 100
1:格式注意 #define+空格(至少一个空格,最多可以多少个空格没有尝试过)+MAX(宏名;注意一般情况下,宏名需要使用全大写,虽然没有强制要求,但是这是现在默认的习惯)+空格(至少一个空格,最多可以多少个空格没有尝试过)+替换文本(可以是任何常量,数字常量,字符,字符串,都是可以的)
2:如上 宏名是客户定义标识符,在程序中如果定义一个变量,变量名和宏名重复,编译器就会报错
#include <stdio.h>
#include <stdlib.h>
#define MAX 100
int main()
{ int MAX=100;
printf("Hello world!\n");
return 0;
}
3:替换文本
可以是任意合法的常量,数字+字符+字符串,都是合法的
#define MAX 100
#define MAC 0x45
#define MAV "Clanguage"
#define MAB 'A'
4:宏名不可重复定义,但是不同的的宏名可以代替相同的 替代文本
#define MAX 100
#define MAX 0x45
#define MAV 100
#define MAB 'A'
编译阶段就会报错,
#define MAX 100
//#define MAX 0x45
#define MAV 100
#define MAB 'A'
第1行和第3行 虽然替换文本相同,但是编译阶段就能通过,也不会报错,在代码中遇到MAX,和MAV都用 100 代替。也很好理解,如果第1行和第2行,宏名相同,编译器就不知道到底是用100代替还是0x45代替
5:宏名的嵌套
也就是已经定义的宏名,可以放在另外一个宏定义的“替换文本”位置
#define MAX 100
#define MAC 0x45
#define MAV 100
#define MAB 'A'
#define MAN MAB
最后两行,就是宏定义的嵌套,如果你愿意基本可以无限套娃。其实本质和上面讲到的不同宏名 替代相同的“替代文本”是一样的
6:格式上还要注意一点,预定义结尾是不需要加“;”的。不要习惯就加上了。另外我们需要注意,宏定义,我们不称为“宏定义语句”,在C中语句是一定要在结尾加上;的。
7:宏定义,可以出现在任意位置,并且和在程序一开始的宏定义一样,在全局范围内,都能使用。
#include <stdio.h>
#include <stdlib.h>
#define MAX 100
#define MAC 0x45
#define MAV 100
#define MAB 'A'
#define MAN MAB
int Fun1(void);
int main()
{ int MAM=101;
#define MAA 100 //如果在主函数中进行一个宏定义,看看在其他函数中是否能使用
Fun1();
return 0;
}
int Fun1(void)
{
printf("在Fun1中打印MAA的值%d\n",MAA);
}
可以看出,在主函数中的宏定义,在函数Fun1中依然能够使用
8:替换文本“可以是任何常量表达式”
#define MAX (3*5+6/2+10%3)
#define MAC (MAX>5? 0:1)
#define MAG ((MAD&&MAF)||0)
#define MAH (sizeof(9))
#define MAS (MAX==102)
以上可以看到,替换文本可以是任意常量表达式,可以是任意合法的运算符(单目+双目+逻辑+位运算符)这里需要注意的是
#define MAH (sizeof(9))也是合法的,宏预定义 MAH=4,
看最后一行代码:#define MAS (MAX==102)是合法的表达式,如果MAX=102.则MAS=1,不等于则MAS =0。但是切记#define MAS (MAV=102)则是非法表达式,因为MAX是常量标识符,不能被重新赋值的。
9:宏定义还有一种比较特殊的用法
在不包含形参的被调用函数
#include <stdio.h>
#include <stdlib.h>
#define MAK Fun2()
int Fun2(int);
int main()
{ int MAM=101;
//如果在主函数中进行一个宏定义,看看在其他函数中是否能使用
MAK();
return 0;
}
int Fun2(void)
{
printf("表明Fun2被调用\n");
return 1;
}
,输出“fun2被调用”,需注意的是,如果被调用函数是有形参的,则不可用简单的宏替换,来实现函数调用。其实利用宏定义来调用无形参函数,本质上也是编译阶段的简单替换。
如果是带形参的函数则不能简单使用宏定义替换。
10:宏替换不能替换字符串
#define MAV "Clanguage"
int main()
{
char arry1[]="Clanguage"
printf("Clanguage\n");
}
我看有些资料上说,宏替换不能替代字符串,并给出以上的例子。说明字符数组并不等于MAV,打印也是“Clanguage”而不是MAV。其实这种额外的说明只是还没有明白,宏定义的本质。只有在代码段出现宏名,编译器用替代文本,来替换宏名。切记万万不是,出现替代文本时,用宏名去代替
小结:我们已经学习了,最简单的宏定义。我刚刚开始接触到宏定义时,有些不理解
如:#define MAX 100
然后代码段有如下判断语句:if(a<MAX){执行代码}; 直接使用if(a<100){执行代码}不也可以吗?这样不是更直接吗?代码更是一目了然。其实在代码量较小时,确是第二种写法更直观。
但是想象一下,如果代码量非常大,我们需要将if(a<100)修改成if(a<105),代码中有20出需要修改,这时预定义的优势是不是非常明显?
我们只需要将#define MAX 100修改成#define MAX 105即可。
另外还有一种情况:很多情况下预定义其实更直观。
有如下场景:我们做了一个期末考试评估系统一共四门科,Chinese(语文150)、match(数学120)、english(英语120),physical(物理100),括号里数字代表分值。
考生,语文>120,评价S。数学>100,评价S。英语>100,评价S,物理>90,评价S。
于是我们可以如下定义
#define S_CHINESE 120
#define S_MATCH 100
#define S_ENGLISH 100
#define S_PHYSICAL 90
int main()
{ int a[4]= {110,102,110,99}; //定义一个数组,里面依次是一个考生的语文/数学/英语/物理的考试成绩
int i;
for(i=0;i<4,i++)
{ switch(i) //第一种不使用宏替换
case 0:if(a[i]>120) printf("a的语文成绩是S");break;
case 1:if(a[i]>100) printf("a的数学成绩是S");break;
case 2:if(a[i]>100) printf("a的英语成绩是S");break;
case 2:if(a[i]>90) printf("a的物理成绩是S");break;
//第二种使用宏替换
case 0:if(a[i]> S_CHINESE) printf("a的语文成绩是S");break;
case 1:if(a[i]>S_MATCH) printf("a的数学成绩是S");break;
case 2:if(a[i]>S_ENGLISH) printf("a的英语成绩是S");break;
case 2:if(a[i]>S_PHYSICAL) printf("a的物理成绩是S");break;
}
}
第二种更直观,而且如果下一次考试,试卷难易发生变化,只需要修改宏定义即可
一:
#define MUL(a,b) a*b
int main()
{
int x=2,y=3,z;
z=MUL(x,y);
printf("输出z的值%d\n",z);
}
带参数宏定义指令,我们先定义一个实现乘法功能的宏定义
实际上编译后的源代码等价于
//#define MUL(a,b) a*b
int main()
{
int x=2,y=3,z;
z=2*3;
printf("输出z的值%d\n",z);
}
聪明的你或许已经看出来,其实编译器已经帮我们算好了,z的值,并直接赋值给z。
下面是分解教学看看编译器是怎么操作的:以下时我自己的理解
1:先进行函数替换(暂时先这么理解)z=a*b,
2:再将x替代a,再将y替换b 得到 z= x*y
有些书籍或资料,将带参数宏替换与函数调用做对比,并说明二者的相同和不同
如带参数宏定义里的参数,不需要做类型声明,也不用给分配存储空间,以及函数占用的代码段空间,二宏定义也不占用代码段的空间
这确是带参数宏定义在占用空间上的优势,比如在嵌入式系统(51单片机,stm32单片机中存储是比较珍贵的)。
但是问题来了,安卓或windows,linux系统开发中其实存储空间已经很大了,为什么还是广泛使用带参数宏定义
#include <stdio.h>
#include <stdlib.h>
#define MUL(a,b) a*b
int main()
{
int x=2,y=3,z,i,j=2;
z=MUL(x,y);
z=MUL(5,6);
for(i=0;i<=5;i++)
{ z=MUL(i,j);
printf("输出z的值%d\n",z);
}
//printf("输出z的值%d\n",z);
return 0;
}
图一
#include <stdio.h>
#include <stdlib.h>
//#define MUL(a,b) a*b
int mul(intx,inty);
int main()
{
int x=2,y=3,z,i,j=2;
z=mul(x,y);
z=mul(5,6);
for(i=0;i<=5;i++)
{ z=mul(i,j);
printf("输出z的值%d\n",z);
}
//printf("输出z的值%d\n",z);
return 0;
}
int mul(a,b)
{
return a*b;
}
图2:使用函数调用
图1,使用宏定义 当代码编译执行到z=MUL(x,y);这一步时,编译器直接将MUL(2,3)带入到宏定义#define MUL(a,b) a*b中,直接进行简单替换z=2*3。
图2, 使用函数调用,当代码运行z=mul(x,y);时开始调用函数int mul(a,b) ,将实参 传递给形参,然后运算器,累加器,寄存器工作,计算出a*b,然后返回a*b的值。把乘积再赋给z。同时还在调用前,对主函数中需要保护的状态和参数,还要执行入栈(push)保护,当调用函数执行结束,返回主函数时还要执行出栈(POP)操作。
所以你看出来了吗?宏定义就是把大量的工作在编译阶段,就交给编译器干了。而函数调用就是把工作交给运行中的代码去干。
也就造成了,在大量使用带参数宏定义的代码中,编译时工作量大一点,(可以理解为带参数的宏定义就是对编译器写的调用函数,),对比函数调用代码运行速度更快。
三:#define MAX(a) #a 与 #define MAX(a) ##
1:#define MAX(a) #a
#include <stdio.h>
#include <stdlib.h>
#define MAX(s) #s
int main()
{
printf(MAX(Hello \n));
printf(MAX(world!\n));
return 0;
}
宏定义中的#s,是指传入的参数强行格式化为字符串
格式上和带参数宏定义格式一致,MAX和()之间必须没有空格。
#define MAX(a) #a
#define MAX(b) #b
#define MAX(c) #c
也都是可以且合法的,注意并不是只能写成#s,这一点需要注意
另外我们想到这样的宏定义,每次只能格式化一个参数(也就是字符串),试试能不能同时格式化两个字符串,能不能格式化字符串的同时,传入正常参数
#include <stdio.h>
#include <stdlib.h>
#define MAX(s,a) #s #a
int main()
{
printf(MAX(Hello \n,xixi\n));
printf(MAX(world!\n,hah\n));
printf(MAX("1111\n","22222\n"));
return 0;
}
输出结果
结论,字符化可以同时传入多个参数
格式宏定义格式#define 宏名(参数1,参数2,....参数n) #参数1 #参数2 。。。。#参数n
这样理解#s,#a与一般宏定义后的“替换文本”和带参数宏定义的“合法表达式也不同”他是对宏定义的形参做出了说明,参数做了#s说明后,宏被调用时,实参就能用字符常量代替,且可以看到。如果传入的实参用“”包围。也是直接被当做字符串整体传递给宏定义的实参。
2:#define MAX(a) 字符串##a
字符串##a起到链接的作用,##(连接运算符):用于将两个参数连接成一个符号;需要特别注意的是,预定义阶段并不能直接拼接字符串。他并不能直接被printf()打印出来。
#define ARGUMENT(x, y) x##y
int argument(a, b) = 10;
// 上面这一行宏定义等价于以下代码
// int ab = 10;
注意:传入的实参,是不需要提前定义的,因为传入的实参,是需要成为一个变量的标识符ab;
#include <stdio.h>
#include <stdlib.h>
#define TIMEMANAGEMENT(X) function_##X
int function_1(void);
int main()
{ int i =0;
i=TIMEMANAGEMENT(1)();
printf("打印i的值是%d\n",i);
return 0;
}
int function_1(void)
{
return 5;
}
利用预定义拼接函数名,然后直接调用。把传入的实参“1”与function拼接起来
注意格式 拼接的字符串和参数之间不能有空格,也不能有