C++宏总结
C/C++宏总结C程序的源代码中可包括各种编译指令,这些指令被称为预处理指令。虽然它们实际上不是C语言的一部分,但却扩展了C程序设计的环境。
ASCI标准定义的C语言预处理程序包括下列命令:
#define,#error,#include,#if,#else,#elif,#endif,#ifdef,#ifndef,#undef,#line,#pragma
等。所有的预处理命令都以#开关。
1、#define
1.1、#define
的概念
#define
命令是C语言中的一个宏定义命令,它用来将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本。在预处理阶段,将宏名用字符串替换的过程称为宏替换。该命令有两种格式:一种是简单的宏定义,另一种是带参数的宏定义。
简单的宏定义:
#define <宏名> <字符串>
例:#define PI 3.1415926
带参数的宏定义:
#define <宏名>(<参数表>) <宏体>
例:#define MAX(a,b) ((a)>(b))?(a):(b)
1.2、宏替换发生的时机
为了能够理解#define
的作用,让我们来了解一下对C语言源程序的处理过程。当我们在一个集成的开发环境如Turbo C中将编写好的源程序进行编译时,实际经过了预处理、编译、汇编和链接几个过程。其中预处理器产生编译器的输出,它实现以下的功能:
文件包含
在
#include
所在位置将它所包含的头文件扩展为文件正文。条件编译
预处理器根据
#if、#elif、#ifdef
等编译命令及其后的条件,将源程序中的部分语句包含进来或排除在外,通常把排除在外的语句转换为空行。宏展开
预处理器将源程序中出现的对宏的引用展开成相应的宏定义。
注意:经过预处理器处理的源程序和之前的程序有什么不同?
在这个阶段进行的工作只是纯粹的替换和展开,没有任何计算功能,所以在学习命令时只要真正理解这一点,才不会对命令引起误解并误用。
1.3、#define
使用中常见的问题解析
- 当替换文本所表示的字符串为一个表达式时容易出现问题
#define N 2+2
int main(void)
{
int iMulti = N*N;
printf("%d\n", iMulti);
return 0;
}
误解:
#define N 2+2,N=4
,那么N*N=16,但实际上为8
分析:宏展开在预处理阶段完成,这个阶段负责宏替换,但不进行计算工作。所以N*N实际上被替换为2+2*2+2=8。
解决方法:将宏写成如下形式
#define N (2+2)
- 带参数的宏定义出现的问题
#define SQUARE(a) a*a
int main(void)
{
int iMulti = N*N;
printf("%d\n", SQUARE(2+2));
printf("a > b ? %d\n", MAX(3,4));
return 0;
}
误解:
SQUARE(2+2)=SQUARE(4)=4*4=16
,但实际上为8
分析:同例(1),也只是简单的宏替换,这里SQUARE(2+2)
将被替换为2+2*2+2=8
解决方法:将宏定义中出现的每个参数都加个括号。将SQUARE改写为
#define SQUARE(a) ((a)*(a))
所以在读别人的程序时,在程序很复杂时,可以将每个宏参替换成它所代表的字符串,不要自作主张地添加任何符号,完全展开后再进行相应的计算,就能避免出错。
1.4、注意事项
普通宏定义
(1)宏名一般用大写
(2)使用宏可提高程序的通用性和易读性,减少不一致性,减少输入错误和便于修改
(3)预处理是在编译之前的处理,而编译工作的任务之一就是语法检查,预处理不做语法检查
(4)宏定义末尾不加分号
(5)宏定义写在花括号的外边,作用域为其后的程序,通常在文件的最开头
(6)可以用#undef命令终止宏定义的作用域
(7)宏定义可以嵌套
(8)字符串””中永远不包含宏
(9)宏定义不分配内存,变量定义分配内存
(10)宏定义过长需要换行时,续行是在键入回车符之前先输入符号”\”,注意回车要紧跟在符号”\“之后,中间不能插入其它符号。带参数宏定义
(1)实参如果是表达式容易出问题
(2)宏名和参数的括号间不能空格
(3)宏替换只作替换,不作计算,不做表达式求解
(4)函数调用在编译后程序运行时进行,并且分配内存。宏替换在编译之前进行,不分配内存
(5)宏在哑实结合不存在类型,也没有类型转换
(6)函数只在一个返回值,利用宏可以设法得到多个返回值
(7)宏展开使得源程序变长,函数调用不会
(8)宏展开不占运行时间,只占编译时间,函数调用占运行时间(分配内存、保留现场,值传递、返回值)
1.5、关于#、##和#@的使用
#define _CONN x##y
#define _TOCHAR(x) #@x
#define _TOSTRING(x) #x
1)#x
表示给参数x加上””,转换成一个字符串
2)x##y
表示x连接y
3)#@x
表示给参数x加上一个’’,结果返回一个const char
注意:
#
、##
和#@x
会限制宏的展开,此时需要多加一层宏进行转换。
//例:
#define INT_X 1
#define INT_Y 2
#define _LINK(a,b) (a##b)
#define LINK(a,b) _LINK(a,b)
int main(void)
{
printf("cancat value = %d\n", _LINK(INT_X, INT_Y));//报错
printf("cancat value = %d\n", LINK(INT_X, INT_Y));//正确
return 0;
}
编译时出错:
identifier "INT_XINT_Y" is undefined
分析:上面我们提到##
符会限制宏的展开,这里宏INT_X和IN_Y的由于没有展开就进行拼接,所以会出错。
解决方法:多加一层宏先将INT_X和INT_Y进行宏展开,再使用##
连接符进行拼接操作。
1.6、关于…的使用
#define MyPrint(fmt, args) printf(fmt "\n", args)
#define MyPrint2(fmt, ...) printf(fmt "\n", ##__VA_ARGS__)
第一个宏指定变参名,如1所示。当我们输出MyPrint(“hello”)
,编译器将转化为printf(fmt “\n”,)
,出现语法错误。
第二个宏指定变参名,我们使用默认的宏VA_ARGS来替代它。注意##充当的作用是当VA_ARGS为空的时候,消除前面的那个逗号。所以当输出MyPrint(“hello”)
,编译器将转化为printf(fmt “\n”)
,语法通过。
//常见用法:
#ifdef _WIN32
#define TrimFilePath(x) strrchr(x,'\\')?strrchr(x,'\\')+1:x
#else
#define TrimFilePath(x) strrchr(x, '/')?strrchr(x,'/')+1:x
#endif
#define LogDebug(fmt, ...) \
printf("[DEBUG] [%s(%d)] : " fmt "\n", TrimFilePath(__FILE__), __LINE__, ##__VA_ARGS__)
#define LogWarn(fmt, ...) \
printf("[WARN ] [%s(%d)] : " fmt "\n", TrimFilePath(__FILE__), __LINE__, ##__VA_ARGS__)
#define LogError(fmt, ...) \
printf("[ERROR] [%s(%d)] : " fmt "\n", TrimFilePath(__FILE__), __LINE__, ##__VA_ARGS__)
#define LogInfo(fmt, ...) \
printf("[INFO ] [%s(%d)] : " fmt "\n", TrimFilePath(__FILE__), __LINE__, ##__VA_ARGS__)
2、#error
处理命令#error
强迫编译程序停止编译,主要用于程序调试。
//例1:下列程序在vs2013上运行
int main(void)
{
char bufA[MAXLINE];
strcpy(bufA, "hello");
return 0;
}
抛出错误:
error C4996: 'strcpy': This function or variable may be unsafe. Consider using strcpy_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
1> d:\program files (x86)\microsoft visual studio 12.0\vc\include\string.h(112) : see declaration of 'strcpy'解决方法:Property->C/C++->Preprocessor下添加宏定_CRT_SECURE_NO_WARNINGS
//例2:假设文件名为a.cpp,代码如下
#ifndef __cplusplus
#error Sorry, an error has occured!
#endif
int main(void)
{
printf("Hello, World!");
return 0;
}
当我们使用
gcc a.cpp
,将会抛错
a.cpp:4:2: error: #error Sorry, an error has occured!
3、#include
命令#include
使编译程序将另一源文件嵌入带有#include
的源文件,被读入的源文件必须用双引号或尖括号括起来。
显示指定路径名,则仅在指定的路径下搜索被嵌入的文件。即包含路径只搜索路径内。
例:
#include “../common/util.h”
,如果上级目录下common目录没有文件util.h,将出错。文件名用双引号括起来,检索首先是当前工作目录,如果未发现文件,则在命令行中指定的目录中搜索,如果还没有发现文件,则搜索实现时定义的标准目录。
例:UNIX系统下gcc编译器的搜索路径的选择
gcc –I ../common/ util.h sourceFile.c
(1)编译器首先通过参数-I指定的头文件的路径进行搜索,如果指定的路径有多个时,则按照指定路径的顺序搜索文件。
(2)通过查找gcc的环境变量C_INCLUDE_PATH、CPLUS_INCLUDE_PATH、OBJC_INCLUDE_PATH来搜索文件位置
(3)通过标准系统目录搜索
/usr/include
/usr/local/include
/usr/lib/gcc-lib/i386-linux/2.95.2/include (不同系统路径可能不一样)没有显示路径名且文件名被尖括号括起来,则首先在编译命令行中的目录中搜索。
例:#include < stdio.h>
更多信息参考 搜索路径的一般形式以及gcc搜索头文件的路径
4、条件编译指令
对程序源代码的各部分有选择地进行编译,该过程称为条件编译。
#if、#elif、#else、#endif
#if的含义是如果#if后面的表达式为ture,则编译它与#endif之前的代码,否则跳过这些代码,命令#endif标识一个#if块的结束,与条件判断类似。
//例:
#if defined(OS_HPUX)&&(defined(HPUX_11_11)|| defined(HPUX_11_23)
// for HP-UX 11.11 and 11.23
#elif defined(OS_HPUX) && defined(HPUX_11_31
// for HP-UX 11.31
#elif defined(OS_AIX)
// for AIX
#else
…
#endif
注意:跟在#if后面的表达式在编译时求值,因此它必须仅含常量及已定义过的标识符,不可使用变量(变量是在编译时求值)。表达式不许含有操作符sizeof(sizeof也是编译时求值)。
#ifdef
和#ifndef
它们分别表示“如果有定义”及“如果无定义”。
//例1:用在头文件中,防止头文件被反复包含。
#ifndef _HEAD_INC_
#define _HEAD_INC_
…
//Statement sequence
…
#endif
//例2:输出信息辅助代码调试
#define DEBUG
int main(void)
{
#ifdef DEBUG
printf("Debug information\n");
#endif
}
注意:
#ifdef
与#ifndef
可以用于#else,#elif
语句中,但必须以#endif
作为#if块的结束。更多信息参考C/C++宏的用法
5、#undef
命令#undef
取消其后那个已定义过的宏名定义,一般形式为:
#undef macroname
6、#line
#line
指令调用预处理器更改的编译器在内部存储的行号和文件名到特定行号和文件名,命令的基本形式如下:
#line digit-sequence ["filename"]
//例:
#line 151 "copy.c"
int main(void)
{
LogWarn("warn %s ", "error occur!");
}
输出:`[WARN ] [copy.c(214)] : warn error occur!`
更多信息参考line指令
强调内容
7、#pragma
更多信息参考pragma指令..