C|深入理解库中随处可见的宏 魔兽世界跟随攻击队长目标宏
liebian365 2024-10-24 14:36 8 浏览 0 评论
C预处理器在程序编译之前查看程序(故称之为预处理器)。根据程序是的预处理指令,预处理器把符号缩写替换成其表示的内容。预处理器可以包含程序所需的其他文件,可以选择让编译器查看哪些代码。
预处理指令以#开头,到后面的第一个换行符为止。也就是说,指令的长度仅限于一个逻辑行(预处理前,编译器会将多行物理行处理为一个逻辑行)。
编译器在预处理前的翻译处理:
I 编译器把源代码中出现的字符映射到源字符集,包括处理多字节字符和三字符序列。
II 代码物理行换行的处理,编译器定位每个反斜杆后面跟着换行符(按下Enter键在源代码文件中换行所生成的字符,而不是指符号表征\n)的实例,并删除它们,把多个物理行转换成一个逻辑行。因为预处理表达式的长度必须是一个逻辑行(一个逻辑行可以是多个物理行)。
III 编译器把文本划分成预处理记号(token,由空格、制表符或换行符分隔的项)序列、空白序列(一个空格替换所有空白序列,但不包括换行符)和注释序列(用一个空格替换)。
以上三步后,开始进行预处理阶段。
#define一般用来定义宏,做宏替换或头文件的保护性定义,分三类:
宏的名称不允许有空格,遵循C变量的命名规则。
替换体也叫替换列表,一旦预处理器在程序中找到宏的实例后,会用替换体代替该宏。
从宏变成最终替换文本的过程称为宏展开(macro expansion)。
#define PRINTX printf("x is %d\n",x)可以理解为:
define a macro, named PRINTX, replaced by printf("x is %d\n",x)
1 宏定义符号常量
#define指令可以用来定义明示常量(manifest constant,也叫符号常量)
下面是使用宏来定义符号常量的实例:
/* simple preprocessor examples */
#include <stdio.h>
#define TWO 2 /* you can use comments if you like */
#define OW "Consistency is the last refuge of the unimagina\
tive. - Oscar Wilde" /* a backslash continues a definition to the next line */
#define FOUR TWO*TWO
#define PRINTX printf("X is %d.\n", x)
#define FMT "X is %d.\n"
int main(void)
{
int x = TWO; /* int x = 2; */
PRINTX; /* printf("x is %d\n",x); */
x = FOUR; /* X = TWO*TWO → X = 2*2 只做替换,包括循环替换,不做计算 */
printf(FMT, x); /* printf("X is %d.\n", x); */
printf("%s\n", OW); /* printf("%s\n", 2); */
printf("TWO: OW\n");/* 双引号中的字符,即使有定义宏,也不做替换,#宏参数例外*/
getchar();
return 0;
}
/*
X is 2.
X is 4.
Consistency is the last refuge of the unimaginative. - Oscar Wilde
TWO: OW
*/
对于常量(或字面量)替换,也可以使用const定义。但用作静态数组大小时,一些编译器并不支持const定义的常量。
那么,何时使用字符常量?对于绝大部分数字常量,应该使用字符常量。如果在算式中使用字符常量代替数字,常量名能更清楚地表达该数字的含义。如果是表示数组大小的数字,用符号常量后更容易改变数组的大小和循环次数。如果数字是系统代码(如EOF),用符号常量表示的代码更容易移植(只需要改变EOF的定义)。助记、易更改、可移植,这些都是符号常量很有价值的特性。
2 函数宏(带参数的宏)
函数宏(带参数的宏)的定义和使用看起来都像函数,都使用圆括号,但是两者却完全不同,特别是宏参数和函数参数不完全相同(宏参数替换不做计算,不求值,只替换字符序列),使用函数宏还可能会有一些陷阱。
/* macros with arguments */
#include <stdio.h>
#define SQUARE(X) X*X
#define PR(X) printf("The result is %d.\n", X)
int main(void)
{
int x = 5;
int z;
printf("x = %d\n", x);
z = SQUARE(x);
printf("Evaluating SQUARE(x): ");
PR(z);
z = SQUARE(2);
printf("Evaluating SQUARE(2): ");
PR(z);
printf("Evaluating SQUARE(x+2): ");
PR(SQUARE(x+2));
printf("Evaluating 100/SQUARE(2): ");
PR(100/SQUARE(2));
printf("x is %d.\n", x);
printf("Evaluating SQUARE(++x): ");
PR(SQUARE(++x));
printf("After incrementing, x is %x.\n", x);
getchar();
return 0;
}
/*
x = 5
Evaluating SQUARE(x): The result is 25.
Evaluating SQUARE(2): The result is 4.
Evaluating SQUARE(x+2): The result is 17.
Evaluating 100/SQUARE(2): The result is 100.
x is 5.
Evaluating SQUARE(++x): The result is 49.
After incrementing, x is 7.
*/
SQUARE(x+2)展开后:
SQUARE(x+2*x+2),
因为运算符的优先级不一样,所以计算的结果不同于预期。如果是函数参数,会先计算x+2,就不存在优先级的区别。是否有改善的办法呢?我们知道,圆括号可以有最高的优先级,所以需要用足够多的圆括号来确保最高优先级(就像预先做了求值一样),单个参数使用圆括号,整体也使用圆括号:
#define SQUARE(X) ((X)*(X))
但对于SQUARE(++x),展开后是:
++x*++x
x经过了两次自增,并不是期望的一次自增。所以,一般尽量不要做宏中使用自增运算符。
3 用宏参数创建字符串:#运算符
在第一个实例中说到双引号中的字符,即使有定义宏,也不做替换,宏参数例外,C允许在字符串中包含宏参数,但宏参数需以字符#开头,就像特殊的转义字符一样。
看下面的实例:
/* substitute in string */
#include <stdio.h>
#define PSQR(x) printf("The square of " #x " is %d.\n",((x)*(x)))
int main(void)
{
int y = 5;
PSQR(y);
PSQR(2 + 4);
getchar();
return 0;
}
/*
The square of y is 25.
The square of 2 + 4 is 36.
*/
3 用宏参数连结字符串:##运算符
与#运算符类似,##运算符可用于类函数宏的替换部分,能够连结字符串,组成一个新的标识符:
// use the ## operator
#include <stdio.h>
#define XNAME(n) x ## n
#define PRINT_XN(n) printf("x" #n " = %d\n", x ## n);
int main(void)
{
int XNAME(1) = 14; // becomes int x1 = 14;
int XNAME(2) = 20; // becomes int x2 = 20;
int x3 = 30;
PRINT_XN(1); // becomes printf("x1 = %d\n", x1);
PRINT_XN(2); // becomes printf("x2 = %d\n", x2);
PRINT_XN(3); // becomes printf("x3 = %d\n", x3);
getchar();
return 0;
}
/*
x1 = 14
x2 = 20
x3 = 30
*/
4 变参宏:...和__VA_ARGS__
stdarg.h提供的可变参数使用的就是宏定义:
typedef char * va_list;
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )
C通过把宏参数列表中最后的参数写成...来实现这一功能,这样,预定义宏可用在替换部分中,表明省略号代表什么。
// variadic.c -- variadic macros
#include <stdio.h>
#include <math.h>
#define PR(X, ...) printf("Message " #X ": " __VA_ARGS__)
int main(void)
{
double x = 48;
double y;
y = sqrt(x);
PR(1, "x = %g\n", x);
PR(2, "x = %.2f, y = %.4f\n", x, y);
getchar();
return 0;
}
/*
Message 1: x = 48
Message 2: x = 48.00, y = 6.9282
*/
5 宏和函数的选择
有些编程任务既可以用带参数的宏完成,也可以用函数完成。各自有适应的一些场合。
使用宏比使用普通函数复杂一些,稍有不慎会产生奇怪的副作用。
宏和函数的选择实际上是时间和空间的权衡。宏展开产生内联代码,略占空间,但没有普通函数的跳转,时间较省。特别是用在嵌套循环体内时。当然,在C++中,有一种更优的替换方案,就是使用inline内联函数。
C作为一种强类型语言,普通函数需要明确数据类型,而宏不存在这一限制,不用担心变量类型,因为其本身就只是做字符替换,并不做变量类型检查,当然这也是宏的另一种缺陷。
对于一些简单的函数,程序员通常使用宏:
#define MAX(X,Y) ((X) > (Y) ? (X) : (Y))
#define ABS(X) ((X) < 0 ? -(X) : (X))
#define ISSIGN(X) ((X) == '+' || (X) == '-' ? 1 : 0)
6 取消宏定义
#undef指令可以取消宏定义,如:
#define LIMIT 444
#undef LIMIT
7 头文件的保护性定义
#ifndef指令判断后面的标识符是否未定义,可以防止相同的宏被重复定义。但更常用来防止多次(重复)包含一个文件,也就是保护性定义:
#ifndef _HEADERNAME_H_
#define _HEADERNAME_H_
... // 头文件内容
#endif
8 预定义宏
C编译器有一些预定义宏和标识符:
// predef.c -- predefined identifiers
#include <stdio.h>
void why_me();
int main()
{
printf("The file is %s.\n", __FILE__);
printf("The date is %s.\n", __DATE__);
printf("The time is %s.\n", __TIME__);
printf("The version is %ld.\n", __STDC_VERSION__);
printf("This is line %d.\n", __LINE__);
printf("This function is %s\n", __func__);
why_me();
getchar();
return 0;
}
void why_me()
{
printf("This function is %s\n", __func__);
printf("This is line %d.\n", __LINE__);
}
/*
This is line 11.
This function is main
This function is why_me
This is line 21.
*/
9 宏的经典应用
在MFC的消息映射,就是宏的一个经典应用:
#define DECLARE_MESSAGE_MAP() \
private: \
static const AFX_MSGMAP_ENTRY _messageEntries[]; \
protected: \
static AFX_DATA const AFX_MSGMAP messageMap; \
static const AFX_MSGMAP* PASCAL _GetBaseMessageMap(); \
virtual const AFX_MSGMAP* GetMessageMap() const; \
#define BEGIN_MESSAGE_MAP(theClass, baseClass) /
const AFX_MSGMAP* theClass::GetMessageMap() const /
{ return &theClass::messageMap; } /
AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = /
{ &baseClass::messageMap, &theClass::_messageEntries[0] }; /
AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = /
{ /
#define END_MESSAGE_MAP() /
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } /
}; /
宏的定义一般放到头文件中,如一些库中就通常包含有宏定义。
宏替换表面看起来有很多缺陷,但在一些库中却用得很普通,如果不是很熟悉的话,一些源代码还真是看得云里雾里的。
-End-
相关推荐
- 快递查询教程,批量查询物流,一键管理快递
-
作为商家,每天需要查询许许多多的快递单号,面对不同的快递公司,有没有简单一点的物流查询方法呢?小编的回答当然是有的,下面随小编一起来试试这个新技巧。需要哪些工具?安装一个快递批量查询高手快递单号怎么快...
- 一键自动查询所有快递的物流信息 支持圆通、韵达等多家快递
-
对于各位商家来说拥有一个好的快递软件,能够有效的提高自己的工作效率,在管理快递单号的时候都需要对单号进行表格整理,那怎么样能够快速的查询所有单号信息,并自动生成表格呢?1、其实方法很简单,我们不需要一...
- 快递查询单号查询,怎么查物流到哪了
-
输入单号怎么查快递到哪里去了呢?今天小编给大家分享一个新的技巧,它支持多家快递,一次能查询多个单号物流,还可对查询到的物流进行分析、筛选以及导出,下面一起来试试。需要哪些工具?安装一个快递批量查询高手...
- 3分钟查询物流,教你一键批量查询全部物流信息
-
很多朋友在问,如何在短时间内把单号的物流信息查询出来,查询完成后筛选已签收件、筛选未签收件,今天小编就分享一款物流查询神器,感兴趣的朋友接着往下看。第一步,运行【快递批量查询高手】在主界面中点击【添...
- 快递单号查询,一次性查询全部物流信息
-
现在各种快递的查询方式,各有各的好,各有各的劣,总的来说,还是有比较方便的。今天小编就给大家分享一个新的技巧,支持多家快递,一次能查询多个单号的物流,还能对查询到的物流进行分析、筛选以及导出,下面一起...
- 快递查询工具,批量查询多个快递快递单号的物流状态、签收时间
-
最近有朋友在问,怎么快速查询单号的物流信息呢?除了官网,还有没有更简单的方法呢?小编的回答当然是有的,下面一起来看看。需要哪些工具?安装一个快递批量查询高手多个京东的快递单号怎么快速查询?进入快递批量...
- 快递查询软件,自动识别查询快递单号查询方法
-
当你拥有多个快递单号的时候,该如何快速查询物流信息?比如单号没有快递公司时,又该如何自动识别再去查询呢?不知道如何操作的宝贝们,下面随小编一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号若干...
- 教你怎样查询快递查询单号并保存物流信息
-
商家发货,快递揽收后,一般会直接手动复制到官网上一个个查询物流,那么久而久之,就会觉得查询变得特别繁琐,今天小编给大家分享一个新的技巧,下面一起来试试。教程之前,我们来预览一下用快递批量查询高手...
- 简单几步骤查询所有快递物流信息
-
在高峰期订单量大的时候,可能需要一双手当十双手去查询快递物流,但是由于逐一去查询,效率极低,追踪困难。那么今天小编给大家分享一个新的技巧,一次能查询多个快递单号的物流,下面一起来学习一下,希望能给大家...
- 物流单号查询,如何查询快递信息,按最后更新时间搜索需要的单号
-
最近有很多朋友在问,如何通过快递单号查询物流信息,并按最后更新时间搜索出需要的单号呢?下面随小编一起来试试吧。需要哪些工具?安装一个快递批量查询高手快递单号若干怎么快速查询?运行【快递批量查询高手】...
- 连续保存新单号功能解析,导入单号查询并自动识别批量查快递信息
-
快递查询已经成为我们日常生活中不可或缺的一部分。然而,面对海量的快递单号,如何高效、准确地查询每一个快递的物流信息,成为了许多人头疼的问题。幸运的是,随着科技的进步,一款名为“快递批量查询高手”的软件...
- 快递查询教程,快递单号查询,筛选更新量为1的单号
-
最近有很多朋友在问,怎么快速查询快递单号的物流,并筛选出更新量为1的单号呢?今天小编给大家分享一个新方法,一起来试试吧。需要哪些工具?安装一个快递批量查询高手多个快递单号怎么快速查询?运行【快递批量查...
- 掌握批量查询快递动态的技巧,一键查找无信息记录的两种方法解析
-
在快节奏的商业环境中,高效的物流查询是确保业务顺畅运行的关键。作为快递查询达人,我深知时间的宝贵,因此,今天我将向大家介绍一款强大的工具——快递批量查询高手软件。这款软件能够帮助你批量查询快递动态,一...
- 从复杂到简单的单号查询,一键清除单号中的符号并批量查快递信息
-
在繁忙的商务与日常生活中,快递查询已成为不可或缺的一环。然而,面对海量的单号,逐一查询不仅耗时费力,还容易出错。现在,有了快递批量查询高手软件,一切变得简单明了。只需一键,即可搞定单号查询,一键处理单...
- 物流单号查询,在哪里查询快递
-
如果在快递单号多的情况,你还在一个个复制粘贴到官网上手动查询,是一件非常麻烦的事情。于是乎今天小编给大家分享一个新的技巧,下面一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号怎么快速查询?...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)