C|深入理解库中随处可见的宏 魔兽世界跟随攻击队长目标宏
liebian365 2024-10-24 14:36 18 浏览 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-
相关推荐
- 记录一个ComboBox的设置问题,你可能没遇到过
-
ComboBox这个控件使用频率太高了,我从VC6编程开始就用它,一直用到C#到现在的Net6,要说我这么一个编程老手还能在它身上栽跟头,我都不敢相信。但是今天竟然被它无情的戏耍了。记录下这个问题,看...
- 组合框(Combo Box)应用之一_combo简单组合框
-
【分享成果,随喜正能量】对别人期待太高,本质上是对自身无能的逃避和推托,与其期待别人,不如依靠自己。你不害怕孤独,就不再寄期望于他人陪伴;你有底气解决问题,就不在寄期望于他人向你伸出援手。一个人期待值...
- Qt之QComboBox定制(二)_qt on_combobox_activated
-
上一篇文章Qt之QComboBox定制讲到了qt实现自定义的下拉框,该篇文章主要实现了列表式的下拉框,这一节我还将继续讲解QComboBox的定制,而这一节我将会讲述更高级的用法,不仅仅是下拉列表框,...
- 从零开始系列,用C#做软件产品:私人日记(九)ComboBox入门
-
第八节的内容早已写好发布,结果一直在审核中,不知道触动了哪条神经。评论中看到有一些网友都在问为什么不用WPF来开发,在这里我统一说明下:1)WPF界面设计相对复杂。由于它是矢量的,需要额外有很多容器做...
- QT-QSharedMemory_qt450-10是什么材料
-
1.QSharedMemory介绍...
- 进入Python的世界19-pyqt6不只是UI设计,其他模块功能如何运用
-
今天是大年初四,继续探讨pyqt6,给出使用的建议。PyQt6绝不仅仅局限于UI设计...
- 从零开始学Qt - 10:一文读懂Qt的元对象系统
-
Qt本身并不是一种编程语言,它实质上是一个跨平台的C++开发类库。它是用标准C++编写的,为开发GUI应用程序和非GUI应用程序提供了各种类。Qt对标准C++进行了扩展,引入一些新的概念和功能,例如信...
- QT实现抖动文字和滚动文字,附源码
-
前言不知道大家有没有发现今天的文章有什么不一样,哈哈,我自己胡拼乱凑弄了一个logo,好不好看就先不说了,最起码萌萌哒...当然这不是今天的重点,在做logo的时候,我原本想让文字动起来的,奈何技术有...
- Qt Concurrent的使用_qt是什么意思
-
1.简介QtConcurrent命名空间提供了高级api,使得无需使用诸如互斥、读写锁、等待条件或信号量等低级线程原语就可以编写多线程程序。使用QtConcurrent编写的程序会根据可用的...
- Qt使用FFmpeg播放视频_qt使用ffmpeg播放视频功能
-
一、使用场景...
- Qt中使用匿名函数lambda表达式_匿名函数lambda
-
一、为什么要使用匿名函数lamdba首先,lambda表达式可以使代码变得简单,C++中,一个lambda表达式表示一个可调用的代码单元。如代码:...
- EtherCAT运动控制卡开发教程之Qt(中):小线段连续轨迹加工
-
今天,正运动小助手给大家分享一下EtherCAT运动控制卡开发教程之Qt,主要介绍一下如何通过Qt编程实现小线段轨迹连续加工,暂停与继续。...
- 送亲人,用1小时制作精美电子相框 | Qt 示例
-
今天给大家分享:...
- Python之面向对象:综合应用,基于GUI实现会动的游戏英雄
-
引言本打算以上一篇文章作为面向对象模块的收尾,但是,犹豫了许久,还是决定再补充一篇,也就是今天这篇文章,打算基于Python的PyQt6/PySide6框架开发一个GUI程序,模拟实现一个在电脑桌面活...
- Qt——常用数据类型_qt基本数据类型
-
1.基础类型因为Qt是一个C++框架,因此C++中所有的语法和数据类型在Qt中都是被支持的,但是Qt中也定义了一些属于自己的数据类型,下边给大家介绍一下这些基础的数类型。...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 记录一个ComboBox的设置问题,你可能没遇到过
- 组合框(Combo Box)应用之一_combo简单组合框
- Qt之QComboBox定制(二)_qt on_combobox_activated
- 从零开始系列,用C#做软件产品:私人日记(九)ComboBox入门
- QT-QSharedMemory_qt450-10是什么材料
- 进入Python的世界19-pyqt6不只是UI设计,其他模块功能如何运用
- 从零开始学Qt - 10:一文读懂Qt的元对象系统
- QT实现抖动文字和滚动文字,附源码
- Qt Concurrent的使用_qt是什么意思
- Qt使用FFmpeg播放视频_qt使用ffmpeg播放视频功能
- 标签列表
-
- 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)