宏的使用
来源:互联网 发布:知之网官网 编辑:程序博客网 时间:2024/05/18 03:34
一、
宏,即
宏是一项历史久远的技术,
准确地说,宏的技术包含在另外一个指称范围更广的技术之中,即编译预处理技术;只是由于宏是其最重要的部分,因此有时这两个术语也互换使用。在本文中,也没有作很严格的区分。
二、
宏在很长一段时间内都仅仅是作为常规编程的一个补充手段,而且没有引起大多数人的注意,直到微软公司的
三、
有一些程序员,受所谓“纯面向对象”的影响太深,对宏的应用反感很强烈,这是不应该的。从实用的角度看,有很多场合是无法使用其他的手段来取代宏的。如果要做跨平台的库或者应用,宏就更是无法避免。迄今为止我还没有看到任何跨平台的
四、
宏的本质就是具有一定规则的文本替换。具体实例详见后文。
惯例以及好的习惯:
1、
2、
3、
4、
特性:
1、
在一个宏定义中,引用之前已经定义过的另外一个宏是可以的。例如:
#definePI
#defineDOUBLE_PI
2、
如果不想让某个宏继续存在,则可以使用
注意事项:
1、
2、
3、
在定义数据类型时,和
1、
2、
3、
#definePSTR
则如下变量声明会有问题:
PSTR p1, p2;
将会被展开为:
char* p1, p2;
p2
而用:
typedef char* PSTR;
则不会导致此问题。
不过也有
typedef void VOID;
的形式,只好乖乖地用宏。
注意
假如有以下代码段:
void foo();
void bar()
{
}
在较老的编译器上无法通过,因为我们原来的概念中,函数
五、
1、
不带参数的宏,有时也被称为“对象模样的宏”。(具体示例参见后文。)
2、
带参数的宏,有时也被称为“函数模样的宏”。(具体示例参见后文。)
3、
1)
这是最常用的用法。例如:
#definePI
这样做的最大好处是维护方便,例如将来为了提高精度,可以将数值改为
2)
为了防卫重复声明和
#ifndef __DUMMY_H__
#define __DUMMY_H__
//
#endif // __DUMMY_H__
这种方法在某些较新的编译器中有了替代方案,如对于微软公司的编译器,现在则提倡使用#pragmaonce
但宏的方式则兼容性最好。
顺便说一下这个宏的名字的生成规则。一般说来,其中的字母是文件名的大写,
3)
条件编译也是最常用的预处理技术之一。理论上讲,条件编译可以不依赖于宏,不过事实是,如果离开宏定义,条件编译也就没有了实际作用,唯一的作用可能就剩下用
条件编译的基本形式如下:
#ifdef
程序段
#elif
程序段
#else
程序段
#endif
上面的
顺便说一下,
条件编译大量用在同一份代码需要在不同平台和
实际上,上一条的“头文件防卫”其实是条件编译的具体应用之一。
4)
和上面的积累比起来,这个只是一个单个的应用示例,不过却相当常见。
#defineARRAYOF(x)
同义异名的宏通常还有:
5)
下面列出的,是几乎所有的编译器都支持的预定义宏的名称,其各自的含义见同行上的说明。
1.
2.
3.
4.
5.
6.
注意
六、
1、
在介绍其他的高级使用方式之前,先大致介绍一下宏的几条特殊展开规则。
1)
如:
#define foo
则展开后,
再如:
#define foo
#define bar
则展开后,
提示
2)
如以下代码:
#definefoo()
foo();
funcptr = foo;
上述代码中,
3)
假如有如下定义:
#define min(X,Y)
那么,则有如下展开结果:
min(,b)
min(a,)
min(,)
min((,),)
min()
min(,,)
4)
注意
当一个宏参数被放进宏的定义体时,这个宏参数会首先被全部展开(有例外,见下文)。当展开后的宏参数被放进宏的定义体时,预处理器对新展开的宏定义体进行第二次扫描,并继续展开。例如:
#define_PARAM(x)
#definePARAM(x)
PARAM(_PARAM(1));
因为
例外情况是,如果
#define_PARAM(x)
#definePARAM(x)
PARAM(_PARAM(1));
使用这个规则可以打印出宏被展开后的样子,便于分析代码:
#define_TO_STRING(x)
#defineTO_STRING(x)
TO_STRING
如果在
#pragma message( TO_STRING(PARAM(_PARAM(1))) )
2、
#
单
双
这种抽象的描述很难让人搞明白,其实一个简单的例子就可以说明。
假设我们要实现一个文本协议的网络服务器(就像
我们可以这样写:
struct CCommand
{
};
struct CCommand commands[] =
{
};
如果使用了串接符
#defineCOMMAND(NAME)
struct CCommand commands[] =
{
};
3、
宏的参数还可以是比较复杂些的表达式。如下例:
#define FUNC_DECL(ret_t, name,para_list)
FUNC_DECL(int, IntegerAdd, (int n1, int n2));
在实际的应用中,我们声明了一个计算整数相加的函数
4、
注意
示例代码:
#define SHOW(...) printf(__VA_ARGS__)
#include <stdio.h>
int main()
{
SHOW("%d %s %s", 1, "Hello", "/n");
return 0;
}
变参宏有两种写法:
1)
#define printerr(fmt,...)
2)
#define printerr(fmt,args...)
同
printerr(fmt,);
的形式。这时的替换过程为如下,
printerr("Error!/n",);
被替换为:
fprintf(stderr, "Error!/n",);
这就引入了一个语法错误,不能正常编译。这个问题一般有两个解决方法。
首先,
printerr(fmt);
而上述的宏的使用仍然会被替换变成:
fprintf(stderr, "Error!/n",);
很明显,这里还会产生编译错误(据称非本例的某些情况下不会产生编译错误)。
除了这种方式外,
#define printerr(fmt, ...) fprintf(stderr, templt,##__VAR_ARGS__)
这时,
printerr("Error!/n");
被转化为:
fprintf(stderr, "Error!/n");
这样将不会产生编译错误。
5、
实际上,在现有的任何流行观点中,都会把
基于此,我们应该可以想到。
20, 30, 50, 80
我们就可以在另一个源文件中这样写代码:
static const int ary[] =
{
#include "array.dat"
};
6、
1)
1.
2.
3.
4.
5.
6.
7.
8.
9.
2)
在实际的编程过程中,我们有时还会有让某个宏(通常是“函数模样的宏”,下文不再特别指出)不发生作用的需要。当然,最彻底的办法是把对该宏的引用全部删除(或者注释掉)。但这种方法的代价很高,如果是一个使用比较普遍的宏的话,可能会需要修改非常多的源代码。这还不是要命的,更要命的是,第二天就发现需要恢复回来。在这种情形下,我们就需要能够构造出一种宏定义,可以保证在宏的引用仍然存在的情况下,宏本身不发生任何作用。
我们使用输出调试信息的
1.
2.
假定我们的输出信息是使用
#ifdef _DEBUG
#defineTRACE
#else
#define TRACE
#endif
这样写貌似正确,但是却有隐患。原因在于
#define TRACE(exp)
这个形式虽然解决了上面的问题,却又带来了新的问题,就是参数只能有一个(此处暂不考虑最新的支持可变参数的语法)。
这是个相当棘手的问题,也令不少专家为难,目前最好的解决方案是:
#defineTRACE
虽然看起来古怪了些,但确实管用,跨编译器的兼容性也非常强。究竟
另外,在微软的编译器下,一度存在一个让宏无效的标准写法:
#defineDO_NOTHING(exp)
它除了有参数限制之外(不过
3)
通常情况下,为了使函数模样的宏(参见五
SwapTwoIntegers(x, y);
但是如果是下面的定义情况:
#define SwapTwoIntegers(x, y){
而又进行了如下的使用:
if(a > b)
else
{
// ...
}
这样就会由于多出的那个分号而产生编译错误。为了避免出现编译错误,同时又能兼容这种写法,我们可以把宏定义为如下形式:
#define SwapTwoIntegers(x, y)
这样就不会有分号引起的问题。
4)
这是个很简单的应用实例,却极具杀伤力:
#defineprivate
#defineprotected
#defineclass
如果你把这几行代码放在源文件的最开头,那么,对于
至于最后一个宏为什么是必须的,也留作思考。
5)
#define FPOS(type,field)
同义异名的宏通常还有:
6)
#define swapn(a, b)
这种应用大概可以属于奇技淫巧的范畴了。
七、
1、
由于宏的实质是文本替换,先于编译器编译源文件之前执行(这也是为什么称之为预编译的原因),所以我们不能寄望于它可以对宏定义代码的合法性
在后续的
2、
如果宏的参数是一个函数,或者一个表达式,那么就有可能被调用
比如:
#define min(x, y) ((x) > (y) ? (y) : (x))
c = min(a, foo(b));
这时
#define min(x, y)({
({...})
注意
这是函数调用作为参数的情况。表达式作为参数的情况就更常见,例如我们这样调用:
c = min(a, ++b);
3、
由于宏的本质是文本替换,而我们的使用通常又是用少量文本来表示更多文本的方式,从而在事实上形成代码量的非直观性的增加,有可能会对最终生成的二进制代码的大小产生影响,导致一定程度的膨胀。
八、
不使用临时变量完成两个整形变量的值交换操作。这也是一个会被经常面试到的问题。通常情况下,我们都会借助于一个临时变量来完成这一操作。解法如下:
int a, b;
int t;
t = a; a = b; b = t;
这种算法易于理解,特别适合帮助初学者了解计算机程序的特点,是赋值语句的经典应用。在实际软件开发当中,此算法简单明了,不会产生歧义,便于程序员之间的交流,一般情况下碰到交换变量值的问题,都应采用此算法,我们可以称之为标准算法。
但上题的要求恰恰是不能使用临时变量,于是有人用算术运算达到了相同的目的:
int a, b;
a = b - a;
b = b - a;
a = b + a;
通过以上运算,
它的原理是:把
具体过程:
1、
2、
3、
交换完成。
此算法与标准算法相比,多了三个计算的过程,但是没有借助临时变量。
而我们在前文给出的算法,即如下定义:
#define swap(a,b)
是基于更为精巧的一种解法。此算法能够实现是由异或运算的特点决定的,通过异或运算能够使数据中的某些位翻转,其他位不变。这就意味着任意一个数与任意一个给定的值连续异或两次,值不变。
即:
- 22、宏的使用
- ASSERT 宏的使用
- 宏工具的使用
- assert()宏的使用!
- 宏的使用
- offsetof宏的使用
- C++宏的使用
- C++宏的使用
- 宏定义的使用
- 宏定义的使用
- 宏定义的使用
- velocity 宏的使用
- TARGET_IPHONE_SIMULATOR宏的使用
- 宏的一些使用
- MTVERIFY宏的使用
- 宏的使用
- __P宏的使用
- 宏定义的使用
- MySQL常用命令
- Scanner 类中的 next() 与 nextLine() 区别
- 值得发表,恕我直言。
- Shell脚本编程快速上手
- 矩阵求逆及行列式求值 - 未来再来添点更多矩阵计算功能
- 宏的使用
- Windows 8之失败,电脑作为娱乐设备VS工作工具
- 蝶雙飛雙
- HDU - 1506 Largest Rectangle in a Histogram
- 队列的顺序存储结构及其基本运算的实现
- 《Node.js开发指南》MicroBlog项目的问题汇总
- java反射机制(1)
- Button的使用(五):从xml中加载
- Android——Intent和Intent过滤器