详解宏定义(#define)

如题所述

第1个回答  2023-09-09

详解宏定义(#define)的解释如下:

C语言中用到宏定义的地方很多,如在头文件中为了防止头文件被重复包含,则用到:#ifndefcTest_Header_h#definecTest_Header_h//头文件内容#endif在我们常用的 stdio.h 头文件中也可以见到很多宏定义,如:

#define BUFSIZ1024//缓冲区大小#define EOF(-1)//表文件末尾#ifndef SEEK_SET#define SEEK_SET 0  //表示文件指针从文件的开头开始#endif#ifndef SEEK_CUR#define SEEK_CUR 1//表示文件指针从现在的位置开始#endif#ifndef SEEK_END#define SEEK_END 2//表示文件指针从文件的末尾开始#endif

从开始写C语言到生成执行程序的流程大致如下(姑且忽略预处理之前的编译器的翻译处理流程等),在进行编译的第一次扫描(词法扫描和语法分析)之前,会有由预处理程序负责完成的预处理工作。

预处理工作是系统引用预处理程序对源程序中的预处理部分做处理,而预处理部分是指以“#”开头的、放在函数之外的、一般放在源文件的前面的预处理命令,如:包括命令#include,宏命令#define等,合理地利用预处理功能可以使得程序更加方便地阅读、修改、移植、调试等,也有利于模块化程序设计。本文主要介绍宏定义的以下几个部分:

1、概念及无参宏

一种最简单的宏的形式如下:

一种最简单的宏的形式如下:#define宏名替换文本每个#define行(即逻辑行)由三部分组成:第一部分是指令#define自身,“#”表示这是一条预处理命令,“define”为宏命令。第二部分为宏(macro),一般为缩略语,其名称(宏名)一般大写,而且不能有空格,遵循C变量命令规则。

“替换文本”可以是任意常数、表达式、字符串等。在预处理工作过程中,代码中所有出现的“宏名”,都会被“替换文本”替换。这个替换的过程被称为“宏代换”或“宏展开”(macro expansion)。“宏代换”是由预处理程序自动完成的。在C语言中,“宏”分为两种:无参数和有参数。

无参宏是指宏名之后不带参数,上面最简单的宏就是无参宏。#define M 5//宏定义#define PI 3.14//宏定义int a[M];//会被替换为:int a[5];int b=M;// 会被替换为:int b =5;printf("PI= %.2f\n", PI);//输出结果为:PI=3.14

注意宏不是语句,结尾不需要加“;”,否则会被替换进程序中,如:#defineN10; //宏定义int c[N];//会被替换为:intc[10;];//error:…main.c:133:11:Expected']'以上几个宏都是用来代表值,所以被成为类对象宏(object-like macro,还有类函数宏,下面会介绍)。

如果要写宏不止一行,则在结尾加反斜线符号使得多行能连接上,如:#defineHELLO"hello\the world"注意第二行要对齐。

否则,如:#define HELLO"hello the wo\rld"printf("HELLO is%s\n",HELLO);//输出结果为: HELLO is hello the world也就是行与行之间的空格也会被作为替换文本的一部分而且由这个例子也可以看出:宏名如果出现在源程序中的“”内,则不会被当做宏来进行宏代换。

宏可以嵌套,但不参与运算:#define M5//宏定义#define MM M/宏的嵌套printf("MM=%d\n", MM);//MM被替换为:MM= M*M,然后又变成MM=55

宏代换的过程在上句已经结束,实际的5*5相乘过程则在编译阶段完成,而不是在预处理器工作阶段完成,所以宏不进行运算,它只是按照指令进行文字的替换操作。再强调下,宏进行简单的文本替换,无论替换文本中是常数、表达式或者字符串等,预处理程序都不做任何检查,如果出现错误,只能是被宏代换之后的程序在编译阶段发现。

宏定义必须写在函数之外,其作用域是#define开始,到源程序结束。如果要提前结束它的作用域则用#undef命令,如:#define M5//宏定义printf("M=%d\n",M);//输出结果为:M=5#defineM100 //取消宏定义printf("M=%d\n",M);//error:…main.c:138:24:Use of undeclaredidentifier'M'

也可以用宏定义表示数据类型,可以使代码简便:#define STU struct Student //宏定义STUstruct Student{ //定义结构体Student char *nameint sNo;};STU stu ={"Jack",20};       //被替换为:struct Student stu={"Jack",20};printf("name:%s,sNo:%d\n",stu.name,stu.sNo);

如果重复定义宏,则不同的编译器采用不同的重定义策略。有的编译器认为这是错误的,有的则只是提示警告。Xcode中采用第二种方式。如:#define M5//宏定义#define M100//重定义,warning:…main.c:26:9:'M'macroredefined

这些简单的宏主要被用来定义那些显式常量(ManifestConstants)(Stephen Prata,2004),而且会使得程序更加容易修改,特别是某一常量的值在程序中多次被用到的时候,只需要改动一个宏定义,则程序中所有出现该变量的值都可以被改变。而且宏定义还有更多其他优点,如使得程序更容易理解,可以控制条件编译等。

#define与#typedef的区别:两者都可以用来表示数据类型,如:#define INT1 inttypedef int INT2;两者是等效的,调用也一样:INT1a1=3;INT2a2=5;

但当如下使用时,问题就来了:#define INT1 int *typedef int*INT2;INT1 a1, b1;INT2 a2, b2;b1=&m;//...main.c:185:8:Incompatible pointer to integer conversion assigning to 'int' from'int*';remove&b2=&n;//OK

因为 INT1a1,b1;被宏代换后为:int*a1,b1;即定义的是一个指向int型变量的指针a1和一个int型的变量b1.而INT2a2,b2;表示定义的是两个变量a2和b2,这两个变量的类型都是INT2的,也就是int*的,所以两个都是指向int型变量的指针。

所以两者区别在于,宏定义只是简单的字符串代换,在预处理阶段完成。而typede不是简单的字符串代换,而是可以用来做类型说明符的重命名的,类型的别名可以具有类型定义说明的功能,在编译阶段完成的。

2、有参宏

C语言中宏是可以有参数的,这样的宏就成了外形与函数相似的类函数宏(function-likemacro),如:宏调用:宏名(实参表);printf(“MEAN=%d\n”,MEAN(7,9));//输出结果:MEAN=8

和函数类似,在宏定义中的参数成为形式参数,在宏调用中的参数成为实际参数。而且和无参宏不同的一点是,有参宏在调用中,不仅要进行宏展开,而且还要用实参去替换形参。如:#define M//无参宏#define COUNT(M) M*m//有参宏printf("COUNT=%d\n",COUNT(10)); //替换为:COUNT(10)=10*10//输出结果:COUNT=100

这看上去用法与函数调用类似,但实际上是有很大差别的。如:#defineCOUNT(M)M*M//定义有参宏int x=6;printf("COUNT=%d\n",COUNT(x+1));//输出结果:COUNT=13printf("COUNT=%d\n",COUNT(++x));//输出结果:COU//warning:...main.c:161:34:Multiple unsequencemodificationsto'x'

这两个结果和调用函数的方法的结果差别很大,因为如果是像函数那样的话,COUNT(x+1)应该相当于COUNT(7),结果应该是7*7=49,但输出结果却是21。原因在于,预处理器不进行技术,只是进行字符串替换,而且也不会自动加上括号(),所以COUNT(x+1)被替换为COUNT(x+1*x+1),代入x=6,即为6+1*6+1=13

而解决办法则是:尽量用括号把整个替换文本及其中的每个参数括起来:#defineCOUNT(M)((M)*(M))但即使用括号,也不能解决上面例子的最后一个情况,COUNT(++x)被替换为++x*++x,即为7*8=56,而不是想要7*7=49,解决办法最简单的是:不要在有参宏用使用到“++”、“–”等。

上面说到宏名中不能有空格,宏名与形参表之间也不能有空格,而形参表中形参之间可以出现空格:#define SUM(a,b)a+b//定义有参宏printf("SUM=%d\n",SUM(1,2));//调用有参宏。Build Failed!因为SUM被替换为:(a,b)a+b如果用函数求一个整数的平方,则是:intcount(int x){return x*x;}

所以在宏定义中:#defineCOUNT(M)M*M中的形参不分配内存单元,所以不作类型定义。而函数 int count(intx)中形参是局部变量,会在栈区分配内存单元,所以要作类型定义,而且实参与形参之间是“值传递”。而宏只是符号代换,不存在值传递。

宏定义也可以用来定义表达式或者多个语句。如:#define JI(a,b)a=i+3;b=j+5;//宏定义多个语句int i=5,j=10;intm=0,n=0;JI(m,n);//宏代换后为:m=i+3,n=j+5;printf("m=%d,n=%d\n",m,n);//输出结果为:m=8,n=15

3、#运算符

比如如果我们宏定义了:#define SUM(a,b)((a)+(b))我们想要输出“1+2+3+4=10”,用以下方式显得比较麻烦,有重复代码,而且中间还有括号:printf("(%d+%d)+(%d+%d)=%d\n",1,2,3,4,SUM(1+2,3+4));

那么这时可以考虑用#运算符来在字符串中包含宏参数,#运算符的用处就是把语言符号转化为字符串。例如,如果a是一个宏的形参,则替换文本中的#a则被系统转化为“a”。而这个转化的过程成为“字符串化(stringizing)”。用这个方法实现上面的要求:

#define SUM(a,b)printf(#a"+"#b"=%d\n",((a)+(b)))//宏定义,运用#运算符SUM(1+2,3+4);//宏调用//输出结果:1+2+3+4=10

调用宏时,用1+2代替a,用3+4代替b,则替换文本为:printf(“1+2””+”“3+4””=%d\n”,((1+2)+(3+4))),接着字符串连接功能将四个相邻的字符串转换为一个字符串:"1+2+3+4=%d\n"

4、##运算符

和#运算符一样,##运算符也可以用在替换文本中,而它的作用是起到粘合的作用,即将两个语言符号组合成一个语言符号,所以又称为“预处理器的粘合剂(Preprocessor Glue)”。用法:

#define NAME(n)num##n//宏定义,使用##运算符intnum0=10;printf("num0=%d\n",NAME(0));//宏调用NAME(0)被替换为num##0,被粘合为:num0。

5、可变宏:…和__VA_ARGS__

我们经常要输出结果时要多次使用prinf(“…”,…);如果用上面例子#defineSUM(a,b)printf(#a”+“#b”=%d\n”,((a)+(b))),则格式比较固定,不能用于输出其他格式。这时我们可以考虑用可变宏(VariadicMacros)。用法是:

#define PR(...)printf(__VA_ARGS__)//宏定义PR("hello\n");//宏调用//输出结果:hello

在宏定义中,形参列表的最后一个参数为省略号“…”,而“__VA_ARGS__”就可以被用在替换文本中,来表示省略号“…”代表了什么。而上面例子宏代换之后为:printf(“hello\n”);

还有个例子如:

#define PR2(X,...)printf("Message"#X":"__VA_ARGS__)//宏定义doublemsg=0;PR2(1,"msg=%.2f\n",msg);//宏调用//输出结果:Message1:msg=10.00

在宏调用中,X的值为10,所以#X被替换为”1”。宏代换后为:printf("Message""1"":""msg=%.2f\n",msg);接着这4个字符串连接成一个:printf("Message1:msg=%.2f\n",msg);要注意的是:省略号“…”只能用来替换宏的形参列表中最后一个!

相似回答