预处理器的任务
简单来讲,预处理器的任务就是执行源代码中的预处理指令,并对源代码进行相应的处理。因此,从预处理指令的类型来讲,预处理器的任务包括如下的几个部分:
将其他文件包含到当前文件中。
定义宏,用来取代某些复杂文本。
定义类似函数的宏,以更加灵活的方式控制源代码。
实施条件编译,即有选择地编译源代码的某些部分。
 
头文件中一般是函数、类等的声明,包含到当前文件中后,就可以在当前文件中引用头文件中的函数、类等。
 
宏的作用
使用预处理指令#define定义的宏主要有三个方面的作用:
替代字面常量。
替代运算符。
声明某个符号已经被定义,通常用于条件编译。
 
除了可以用宏替代字面常量,还可以用宏替代某些运算符,包括加、减、乘、除、逻辑与和逻辑非等,甚至函数和语句块的花括号。例如:
#define ADD     +
#define SUB     –
#define MUL     *
#define DIV     /
#define AND     &&
#define OR  ||
#define NOT     !
#define BEGIN {
#define END     }
#define OUT cout<<
利用上述宏定义,可以编写出貌似违反C++语法、但实际上合法的源代码。例如:
void Function()
BEGIN
    int x = 1 ADD 2;
    int y = 3 MUL 4;
    if(NOT x OR Y)
        BEGIN
        OUT Y;
    END
END
说明
除了简单地替换某些操作符,还可以定义一些类似函数的宏,即可以接受参数的宏。其定义和使用都类似于函数,但又与函数不同,详细内容请参考后面的小节。
声明已定义符号
利用预处理指令#define可以声明某个符号(宏名)已经定义过了,其格式如下:
#define SOME_NAME
如果在源代码中存在上述语句,则对预编译器来说,一个名称为SOME_NAME的宏已经被定义了,尽管没有指明要替换的文本。当然,也可以为该宏名指定要替换的文本,尽管没有必要。宏的这种特性通常应用在条件编译中。有关条件编译的内容将在后面介绍。
预定义的宏
在C++标准中,规定了一些预定义的宏。也就是说这些宏不需要由开发者定义,而是由预编译器提供,开发者只要使用即可。如表11-1所示是一些常用的预定义宏。
表11-1 常用的预定义宏
[插图]
当心
上述预定义宏两侧都是两道下画线,而不是一道。
其中宏__LINE__表示该宏所在行的行号。这个行号可以用预处理指令#line进行修改。预处理指令#line的使用格式如下:
#line新的起始行号
如果在源代码文件中存在上述预处理指令,则该指令的下一行将以新的起始行号为编号,以后各行以此为基准进行递增。譬如#line指令定义了新的起始行号为100,则在该指令的下一行使用宏__LINE__得到的值就是100。#line指令也可以修改宏__FILE__所表示的文件名,其格式如下:
#line新的起始行号 "新文件名"
下例使用预定义的宏输出某些源代码的信息,程序如示例代码11.1所示。
示例代码11.1
#include
#include
using namespace std;                       // 使用名称空间std
void fun()
{
    cout<<"函数名:     "<<__func__<<endl;
}
int main(int argc, char *argv[])           // 主函数
{
    cout<<"——输出预编译信息——"<<endl;
    cout<<"文件名:     "<<__FILE__<<endl;
    cout<<"行号:       "<<__LINE__<<endl;
    cout<<"预编译日期: "<<__DATE__<<endl;
    cout<<"预编译时间:  "<<__TIME__<<endl;
    fun();
    #line 100 "测试文件"
    cout<<"——修改行号和文件名——"<<endl;
    cout<<"文件名:     "<<__FILE__<<endl;
    cout<<"行号:       "<<__LINE__<<endl;
    system("PAUSE");                       // 暂停程序
    return EXIT_SUCCESS;
}
建立一个控制台工程,并将上述代码复制到源文件中,编译并运行,结果如图11-3所示。
[插图]
图11-3 输出预编译信息结果
带参数的宏
简单的宏定义,如上节例子所示,只能进行简单的文字替换,扩展能力有限。如果宏能够像函数那样带有参数,并根据参数的不同自动展开成不同的文本,则宏的扩展能力将大大提高。实际上,在C++标准中,是可以利用预处理命令#define定义带参数的宏的。
定义带参数的宏(类似与函数格式)
定义带参数宏的语法同定义函数类似,在紧跟宏名之后有用括号包围起来的参数列表,但没有返回值类型、参数类型等的声明。其语法如下:
#define宏名(参数1, 参数2, , 参数n) 可替换文本
如果可替换文本一行写不完,可以分成多行,并在每一行的结尾处加上续行符号“\”(除了最后一行)。其语法如下:
#define宏名(参数1, 参数2, , 参数n) 可替换文本行1 \
                                  可替换文本行2 \
                                 
                                  可替换文本行m
当心
要定义带参数的宏,则在宏名和左括号之间不能有空格(或制表符),否则就成了普通的宏定义,括号及其里面的内容也将会成为可替换文本的一部分。但是,在括号中的各参数之间可以任意添加空格。
在可替换文本中可以引用括号中的参数,从而根据参数的不同,展开成不同的源代码文本。例如定义一个比较两数大小的宏:
#define LESS(x, y)  x < y
假设在程序中有如下的引用:
bool result = LESS(3, 4);
则经过预编译后,上述语句将展开成如下的语句:
bool result = 3 < 4;
在上例中,LESS是带参数宏的名称,x和y则是该宏的形式参数(类似函数的形参)。在引用LESS宏时,可以用实参(类似函数的实参)3和4分别替换形参x和y。当进行宏展开时,可替换文本中用到形参的地方也将由相应实参一一替代。
下例定义一个宏,接受两个参数,然后比较这两个参数的大小,并输出其中的较小值,程序如示例代码11.2所示。
示例代码11.2
#include
#include
using namespace std;                    // 使用名称空间std
// 定义宏,输出两个数中的较小值
// 注意:以双斜杠引导的注释不能写到宏定义中,否则将成为宏的一部分
// 但是可以加包围的注释,而且必须加在续行符之前
#define LESS(x, y) {  \
  if( x < y ) \
  { \
    cout<<"较小值:"<<x<<endl; \
  } \
  else \
  { \
    cout<<"较小值:"<<y<<endl; \
  } \
}
int main(int argc, char *argv[])
// 主函数
{
  cout<<"——求较小值的宏——"<<endl;    // 输出提示信息
  LESS( 1, 2 )                           // 求1和2中的较小值,注意语句结尾没有分号
  LESS( 5, 4 )                           // 求5和4中的较小值,注意语句结尾没有分号
  cout<<endl;
  system("PAUSE");                       // 程序暂停执行
  return EXIT_SUCCESS;
}
建立一个控制台工程,并将上述代码复制到源文件中,编译并运行,结果如图11-4所示。
[插图]
图11-4 用宏求较小值并输出结果
提示
在上述例子中,使用了带参数的宏LESS,但是在语句的结尾处并没有分号。乍一看好像不符合C++的语法规范,但是实际上并没有错。这是因为宏是在预编译时处理的,而不是编译期。而且,宏展开后的结果符合C++语法规范,所以这些语句的结尾处没有分号并不会出错。
 
 
特别注意:宏主要是展开,并不是那么智能,若常量等按照展开思想,如#define a(x) x+x  调用为8*a(5),本来想是8*(5+5)为80,实际是8*5+5为45

#define宏作用的更多相关文章

  1. define宏定义中的#,##,@#及\符号

    define宏定义中的#,##,@#及\符号 在#define中,标准只定义了#和##两种操作.#用来把参数转换成字符串,##则用来连接两个前后两个参数,把它们变成一个字符串. 1.# (string ...

  2. iOS 7:漫谈#define 宏定义

           #define宏定义在C系开发中可以说占有举足轻重的作用.底层框架自不必说,为了编译优化和方便,以及跨平台能力,宏被大量使用,可以说底层开发离开define将寸步难行.而在更高层级进行开 ...

  3. iOS 7:漫谈#define 宏定义(转)

    iOS :漫谈#define 宏定义 #define宏定义在C系开发中可以说占有举足轻重的作用.底层框架自不必说,为了编译优化和方便,以及跨平台能力,宏被大量使用,可以说底层开发离开define将寸步 ...

  4. 面试问题5:const 与 define 宏定义之间的区别

    问题描述:const 与 define 宏定义之间的区别 (1) 编译器处理方式不同     define宏是在预处理阶段展开:     const常量是编译运行阶段使用: (2) 类型和安全检查不同 ...

  5. #define宏与const的区别

    1.#define宏的用法 #define用宏名代替一个字符串,这样便于修改,提高了程序的可移植性.编译器在编译预处理时只对宏做文本替换,而不进行类型检查,所以替换后可能产生一些副作用. 带参数的宏类 ...

  6. #ifndef, #define, #endif 作用

    #ifndef, #define, #endif 作用 https://www.cnblogs.com/challenger-vip/p/3386819.html

  7. #define宏常量和const常量的区别

    C++ 语言可以用const 来定义常量,也可以用#define 来定义常量.但是前者比后者有更多的优点:(1) const 常量有数据类型,而宏常量没有数据类型.编译器可以对前者进行类型安全检查.而 ...

  8. #define宏定义

    1 #define的概念  #define命令是C语言中的一个宏定义命令,它用来将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本.  该命令有两种格式:一种是简单的宏定义, ...

  9. 如何正确使用const(常量),define(宏)

    前言 在开发中,也许我们会经常使用到宏定义,或者用const修饰一些数据类型,经常有开发者不知道怎么正确使用,导致项目中乱用宏定义与const修饰符.本篇主要介绍在开发中怎么正确使用const与def ...

随机推荐

  1. java static学习

    原创,转载请注明来源sogeisetsu的博客园 static,在类里面定义公共的属性,它可以统一修改,并只占一个内存.从而达到方便修改和少占内存的目的 先放上代码,您可以先越过代码,看后面的讲解内容 ...

  2. LOJ 3120: 洛谷 P5401: 「CTS2019 | CTSC2019」珍珠

    题目传送门:LOJ #3120. 题意简述: 称一个长度为 \(n\),元素取值为 \([1,D]\) 的整数序列是合法的,当且仅当其中能够选出至少 \(m\) 对相同元素(不能重复选出元素). 问合 ...

  3. discuz开发实现自动获取后台入口代码

    一般discuz后台入口默认是admin.php,不过部分用户为了安全可能会修改后台入口文件名称,可以用代码 '.ADMINSCRIPT.'?frame=no&action=tools& ...

  4. 如何让ThinkPHP支持模糊搜索

    最近ytkah在做一个ThinkPHP的项目时发现了一个问题,搜索的功能只能检索出以*为开头的内容,不能检索出中间的词.例如:搜索包含6775的产品,搜索结果为空,而搜索000-6775 就有两个结果 ...

  5. 10-cmake语法-CMakeParseArguments

    include(CMakeParseArguments) 是为了使用 cmake_parse_arguments(),看样子是用来解析输入参数的. 给出参考: https://cmake.org/pi ...

  6. Sublime Text3 设置

    主题:Spacegrey.sublime-theme 配色方案:Mariana 自动保存 参考:https://www.cnblogs.com/mzzz/p/6178341.html "sa ...

  7. 【字符串】 manacher算法

    Algorithm Task 给定一个字符串,求其最长回文子串 Limitations 要求时空复杂度均为线性且与字符集大小无关. Solution 考虑枚举回文串的对称轴,将其对应的最长回文子串长度 ...

  8. L1和L2正则化(转载)

    [深度学习]L1正则化和L2正则化 在机器学习中,我们非常关心模型的预测能力,即模型在新数据上的表现,而不希望过拟合现象的的发生,我们通常使用正则化(regularization)技术来防止过拟合情况 ...

  9. C# byte数组与16进制间的相互转换

      1.byte数组转16进制字符串 /// <summary> /// 将一个byte数组转换成16进制字符串 /// </summary> /// <param na ...

  10. Java 集合系列之六:工具类Collections和Arrays基本操作

    1. Collections Collections类主要是完成了两个主要功能 提供了若干简单而又有用的算法,比如排序,二分查找,求最大最小值等等. 提供对集合进行包装的静态方法.比如把指定的集合包装 ...