百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

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的单号呢?今天小编给大家分享一个新方法,一起来试试吧。需要哪些工具?安装一个快递批量查询高手多个快递单号怎么快速查询?运行【快递批量查...

掌握批量查询快递动态的技巧,一键查找无信息记录的两种方法解析

在快节奏的商业环境中,高效的物流查询是确保业务顺畅运行的关键。作为快递查询达人,我深知时间的宝贵,因此,今天我将向大家介绍一款强大的工具——快递批量查询高手软件。这款软件能够帮助你批量查询快递动态,一...

从复杂到简单的单号查询,一键清除单号中的符号并批量查快递信息

在繁忙的商务与日常生活中,快递查询已成为不可或缺的一环。然而,面对海量的单号,逐一查询不仅耗时费力,还容易出错。现在,有了快递批量查询高手软件,一切变得简单明了。只需一键,即可搞定单号查询,一键处理单...

物流单号查询,在哪里查询快递

如果在快递单号多的情况,你还在一个个复制粘贴到官网上手动查询,是一件非常麻烦的事情。于是乎今天小编给大家分享一个新的技巧,下面一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号怎么快速查询?...

取消回复欢迎 发表评论: