C中宏的学习

1宏定义:

简单的宏定义有如下格式:

 [#define   指令  (简单的宏)] 

 #define  标识符   替换列表 

替换列表是一系列的C语言记号,包括标识符关键字字符常量字符串字面量运算符和标点符号。当预处理器遇到一个宏定义时,会做一个 “标识符”代表“替换列表”的记录。在文件后面的内容中,不管标识符在任何位置出现,预处理器都会用替换列表代替它。不要在宏定义中放置任何额外的符号,否则它们会被作为替换列表的一部分。

1.1第一种宏定义的方法:程序中

#define PI 3.14f

宏不是变量,不加分号,就是给数字起名字;

变量占内存,有地址;编译器直接用数字把宏的名字替换掉,没有在内存开辟空间存储数据

区别:变量可以更改,宏一旦定义后不可以改变
gcc -E define.c   ——>预处理,添加宏
预处理指令#define可以用于实现宏定义,宏可以用来当做数字的名称,不是C语言语句,不用加分号 也不可以加赋值操作符
宏名称之间不可以加空格

1.2第二种宏定义的方法:命令行中定义宏

可以在gcc命令行中使用-D选项定义宏

gcc -DPI = 3.14 文件名

2.带参数的宏

 2.1[#define指令—带参数的宏]  

#define 标识符(x1, x2,…,xn)替换列表  

其中x1, x2,…,xn是标识符(宏的参数)。这些参数可以在替换列表中根据需要出现任意次。  在宏的名字和左括号之间必须没有空格如果有空格,预处理器会认为是在定义一个简单的宏,其中(x1,x2,…,xn)是替换列表的一部分。当预处理器遇到一个带参数的宏,会将定义存储起来以便后面使用。在后面的程序中,如果任何地方出现了标识符(y1,y2,…,yn)格式的宏调用(其中y1,y2,…,yn是一系列标记),预处理器会使用替换列表替代,并使用y1替换x1,y2替换x2,依此类推。 

 2.2带参数的宏可以包含空的参数列表,如下例所示:

 #define getchar() getc(stdin)   空的参数列表不是一定确实需要,但可以使getchar更像一个函数。

(没错,这就是<stdio.h>中的getchar,getchar的确就是个宏,不是函数——虽然它的功能像个函数。) 

2.3使用带参数的宏替代实际的函数的优点:  

1) 、  程序可能会稍微快些。一个函数调用在执行时通常会有些额外开销——存储上下文信息、复制参数的值等。而一个宏的调用则没有这些运行开销。  

2) 、 宏会更“通用”。与函数的参数不同,宏的参数没有类型。因此,只要预处理后的程序依然是合法的,宏可以接受任何类型的参数。

2.4但是带参数的宏也有一些缺点:  

1) 、 编译后的代码通常会变大。每一处宏调用都会导致插入宏的替换列表,由此导致程序的源代码增加(因此编译后的代码变大)。宏使用得越频繁,这种效果就越明显。当宏调用嵌套时,这个问题会相互叠加从而使程序更加复杂。

2) 、宏参数没有类型检查。当一个函数被调用时,编译器会检查每一个参数来确认它们是否是正确的类型。如果不是,或者将参数转换成正确的类型,或者由编译器产生一个出错信息。预处理器不会检查宏参数的类型,也不会进行类型转换。 

 3) 、无法用一个指针来指向一个宏。C语言允许指针指向函数。这一概念在特定的编程条件下非常有用。宏会在预处理过程中被删除,所以不存在类似的“指向宏的指针”。因此,宏不能用于处理这些情况。  

4) 、宏可能会不止一次地计算它的参数。函数对它的参数只会计算一次,而宏可能会计算两次甚至更多次。如果参数有副作用,多次计算参数的值可能会产生意外的结果。

#define  CIRCLE(r)  2*PI*(r)

printf("圆的周长是%g\n",CIRCLE(radius));
宏的名字必须是大写
宏名跟参数之间也不要加括号
定义的求圆面积的宏:
#define AREA(r) PI*(r)*(r)

3宏跟函数的区别

 函数有输入参数,输出参数之分;

而宏也可以带参数,并且宏的参数既是输入参数又是输出参数,宏没有返回值的概念

总结注意:
1.在定义宏时考虑到程序中进行运算的优先性,所以宏的参数在使用时要用小括号包括起来
2.宏的计算结果要用小括号包括起来
3.自增自减的计算结果尽量不要作为宏的参数来使用
如:
#define SECPH (60*60)
#define NEG(n)  0 - (n)
#define SQUARE(n)  ((n)*(n))
printf("SQUARE(++value)是%d\n",SQUARE(++value));//结果是36

4宏的定义二

(1)#操作符可以把宏的某个参数变成字符串字面值;
#define   STR(n)   #n

参数前面加上一个#表示把它变成了一个字符串的字面值(字符串字面量(string literal)是指双引号引住的一系列字符,双引号中可以没有字符,可以只有一个字符,也可以有很多个字符), 简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号. 它仅允许出现在带参数的宏的替换列表中


(2)## 操作符可以把宏的某个参数代表的标识符和其他内容合并成为一个新的标识符;在C语言的宏中,"##"被称为 连接符(concatenator),它是一种预处理运算符, 用来把两个语言符号(Token)组合成单个语言符号。 这里的语言符号不一定是宏的变量。并且双井号不能作为第一个或最后一个元素存在.  ##运算符可以将两个记号(例如标识符)“粘”在一起,成为一个记号。

#define  PIR(n)  p_##n  //可以保证变量名前面两个字符肯定是p_,p_跟参数可以合并成一个新的标识符

int *PIR(value) = null;

5编译器预定义宏的练习

printf(“行号是%d\n”,__LINE__);
printf("文件名是%s\n",__FILE__);
printf("日期是%s\n",__DATE__);
printf("时间时%s\n",__TIME__);
printf("%sC标准\n"__STDC__?"符合":"不符合");//表示当前编译器是否支持c语言规范

编译器内部提供了一些预定义的宏,在编译的时候编译器把他们替换成正确的内容!

6宏都需要遵守的规则:

 1) 、宏的替换列表可以包含对另一个宏的调用。例如,我们可以用宏PI来定义宏TWO_PI: #definePI     3.14159 #defineTWO_PI  (2*PI) 当预处理器在后面的程序中遇到TWO_PI时,会将它替换成(2*PI)。接着,预处理器会重新检查替换列表,看它是否包含其他宏的调用(在这个例子中,调用了宏PI)。预处理器会不断重新检查替换列表,直到将所有的宏名字都替换掉为止。 

 2) 、预处理器只会替换完整的记号,而不会替换记号的片断。因此,预处理器会忽略嵌在标识符名、字符常量、字符串字面量之中的宏名。

3) 、一个宏定义的作用范围通常到出现这个宏的文件末尾。由于宏是由预处理器处理的,他们不遵从通常的范围规则。一个定义在函数中的宏并不是仅在函数内起作用,而是作用到文件末尾。  

4) 、宏不可以被定义两遍,除非新的定义与旧的定义是一样的。小的间隔上的差异是允许的,但是宏的替换列表(和参数,如果有的话)中的记号都必须一致。  

5) 、宏可以使用#undef指令“取消定义”。

#undef指令有如下形式: [#undef指令]  

#undef  标识符  其中标识符是一个宏名。例如,指令 #undef N 会删除宏N当前的定义。(如果N没有被定义成一个宏,#undef指令没有任何作用。)#undef指令的一个用途是取消一个宏的现有定义,以便于重新给出新的定义。

6)、 宏定义中圆括号     在我们前面定义的宏的替换列表中有大量的圆括号,加圆括号的原则:

首先,如果宏的替换列表中有运算符,那么始终要将替换列表放在括号中:  #define TWO_PI (2*3.14159) 

其次,如果宏有参数,每次参数在替换列表中出现时都要放在圆括号中: #define SCALE(x) ((x)*10) 没有括号的话,我们将无法确保编译器会将替换列表和参数作为完整的表达式。编译器可能会不按我们期望的方式应用运算符的优先级和结合性规则。

7)、 创建较长的宏,在较长的宏中的逗号运算符;

 在创建较长的宏时,逗号运算符会十分有用。特别是可以使用逗号运算符来使替换列表包含一系列表达式。例如,下面的宏会读入一个字符串,再把字符串显示出来:  #define ECHO(s) (get(s), puts(s))        gets函数和puts函数的调用都是表达式,因此使用逗号运算符连接它们是合法的。

6.使用#define来为常量命名有许多显著的优点: 

 1) 、 程序会更易读。一个认真选择的名字可以帮助读者理解常量的意义。否则,程序将包含大量的“魔法数”,使读者难以理解。 

 2) 、 程序会更易于修改。我们仅需要改变一个宏定义,就可以改变整个程序中出现的所有该常量的值。“硬编码的”常量会更难于修改,特别是有时候当他们以稍微不同的形式出现时。

3) 、可以帮助避免前后不一致或键盘输入错误。

4) 、可以对C语法做小的修改。实际上,我们可以通过定义宏的方式给C语言符号添加别名,从而改变C语言的语法。例如,对于习惯使用Pascal的begin和end(而不是C语言的{和})的程序员,可以定义下面的宏:  #define BEGIN  {     #define END    }   我们甚至可以发明自己的语言。例如,我们可以创建一个LOOP“语句”,来实现一个无限循环:  #define LOOP   for (;;)  当然,改变C语言的语法通常不是个好主意,因为它会使程序很难被其他程序员所理解。 

5) 、对类型重命名。我们通过重命名int创建了一个Boolean类型: #define BOOL int   虽然有些程序员会使用宏定义的方式来实现此目的,但类型定义仍然是定义新类型的最佳方法。  

6) 、控制条件编译。宏在控制条件编译中起重要的作用。


7、条件编译练习:

7.1  指令介绍

#空指令,无任何效果
#include包含一个源代码文件
#define定义宏
#undef取消已定义的宏
#if如果给定条件为真,则编译下面代码
#ifdef如果宏已经定义,则编译下面代码
#ifndef如果宏没有定义,则编译下面代码
#elif如果前面的#if给定条件不为真,当前条件为真,则编译下面代码
#endif结束一个#if……#else条件编译块
#error停止编译并显示错误信息

7.2使用如下预处理指令可以实现条件编译

#ifdef 宏名称(#ifndef  宏名称)
  。。。。
#else
  。。。。。
#endif
其中#ifdef和#ifndef意思相反
使用#if 和#elif可以根据任何逻辑表达式实现条件编译,并且可以从更多语句组中进行选择!


#define  ONE
int main()
{
#ifdef ONE//如果定义了ONE的宏编译
#ifndef TWO//如果没有定义TWO的宏编译
  printf("1\n");
#else
  printf("2\n");
#endif
  return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值