• macro-name replacement-text 宏 调试开关可以使用一个宏来实现 do { } while(0)


    C++ 预处理器_w3cschool https://www.w3cschool.cn/cpp/cpp-preprocessor.html

    C++ 预处理器

    预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理。

    所有的预处理器指令都是以井号(#)开头,只有空格字符可以出现在预处理指令之前。预处理指令不是 C++ 语句,所以它们不会以分号(;)结尾。

    我们已经看到,之前所有的实例中都有 #include 指令。这个宏用于把头文件包含到源文件中。

    C++ 还支持很多预处理指令,比如 #include、#define、#if、#else、#line 等,让我们一起看看这些重要指令

    #define 预处理

    #define 预处理指令用于创建符号常量。该符号常量通常称为,指令的一般形式是:

    #define macro-name replacement-text 
    

    当这一行代码出现在一个文件中时,在该文件中后续出现的所有宏都将会在程序编译之前被替换为 replacement-text。

    函数宏

    您可以使用 #define 来定义一个带有参数的宏

    #include <iostream>

    using namespace std;

    #define PI 3.14159
    #define MIN(a,b) (((a)<(b))?a:b)

    int main() {
    cout << "Value of PI :"<<PI<<endl;
    int i,j;
    i=100;
    j=30;
    cout << "The minium is " << MIN(i,j)<<endl;
    return 0;
    }

    条件编译

    有几个指令可以用来有选择地对部分程序源代码进行编译。这个过程被称为条件编译。

    条件预处理器的结构与 if 选择结构很像。请看下面这段预处理器的代码:

    #ifndef NULL
       #define NULL 0
    #endif

    #include <iostream>
    using namespace std;
    #define PI 3.14159
    #define MIN(a,b) (((a)<(b))?a:b)
    #define DEBUG
    int main() {
    	cout << "Value of PI :"<<PI<<endl;
    	int i,j;
    	i=100;
    	j=30;
    	cout << "The minium is " << MIN(i,j)<<endl;
    
    #ifdef DEBUG
    	cerr << "Trace:Inside main function"<< endl;
    #endif
    
    #if 0
    	cout << MKSTR(HELLO C++) << endl;
    #endif
    
    	cout<<"The minium is " << MIN(i,j)<<endl;
    
    #ifdef DEBUG
    	cerr<<"Trace: Coming out of main function"<<endl;
    #endif
    
    	return 0;
    }
    

      

    [root@flink mediaC]# g++ main.cpp ; ./a.out;
    Value of PI :3.14159
    The minium is 30
    Trace:Inside main function
    The minium is 30
    Trace: Coming out of main function
    [root@flink mediaC]#

    # 和 ## 运算符

    # 和 ## 预处理运算符在 C++ 和 ANSI/ISO C 中都是可用的。# 运算符会把 replacement-text 令牌转换为用引号引起来的字符串。

    #include <iostream>
    using namespace std;
    #define MKSTR(x) #x
    int main() {
    	cout << MKSTR(HELLO C++)<< endl;
    	return 0;
    }
    

      HELLO C++

    让我们来看看它是如何工作的。不难理解,C++ 预处理器把下面这行:

    cout << MKSTR(HELLO C++) << endl; 

    转换成了:

    cout << "HELLO C++" << endl; 


    #include <iostream>
    using namespace std;
    #define concat(a,b) a##b
    int main() {
    	int xy=100;
    	cout << concat(x,y);
    	return 0;
    }
    

      

    当上面的代码被编译和执行时,它会产生下列结果:

    100
    

    让我们来看看它是如何工作的。不难理解,C++ 预处理器把下面这行:

    cout << concat(x, y); 

    转换成了:

    cout << xy; 


    #include <iostream>
    using namespace std;
    int main() {
    	cout << "Value of __LINE__:"<<__LINE__<<endl;
    	cout << "Value of __FILE__:"<<__FILE__<<endl;
    	cout << "Value of __DATE__:"<<__DATE__<<endl;
    	cout << "Value of __TIME__:"<<__TIME__<<endl;
    	return 0;
    }  

    [root@flink mediaC]# g++ main.cpp ; ./a.out;
    Value of __LINE__:4
    Value of __FILE__:main.cpp
    Value of __DATE__:Oct 24 2018
    Value of __TIME__:22:16:43
    [root@flink mediaC]#

    https://baike.baidu.com/item/宏/2648286?fr=aladdin

    宏(英语:Macro),是一种批量处理的称谓。
    计算机科学里的宏是一种抽象(Abstraction),它根据一系列预定义的规则替换一定的文本模式。解释器编译器在遇到宏时会自动进行这一模式替换。对于编译语言,宏展开在编译时发生,进行宏展开的工具常被称为宏展开器。宏这一术语也常常被用于许多类似的环境中,它们是源自宏展开的概念,这包括键盘宏和宏语言。绝大多数情况下,“宏”这个词的使用暗示着将小命令或动作转化为一系列指令。
     
    C++宏定义
    https://baike.baidu.com/item/C++宏定义/462816
    带参数的宏定义的一般形式如下:
    #define <宏名>(<参数表>) <宏体>
    其中, <宏名>是一个标识符,<参数表>中的参数可以是一个,也可以是多个,视具体情况而定,当有多个参数的时候,每个参数之间用逗号分隔。<宏体>是被替换用的字符串,宏体中的字符串是由参数表中的各个参数组成的表达式。例如:
    #define SUB(a,b) a-b
    如果在程序中出现如下语句:
    result=SUB(2, 3);
    则被替换为:
    result=2-3;
    如果程序中出现如下语句:
    result= SUB(x+1, y+2);
    则被替换为:
    result=x+1-y-2;
    在这样的宏替换过程中,其实只是将参数表中的参数代入到宏体的表达式中去,上述例子中,即是将表达式中的a和b分别用2和3代入。
    我们可以发现:带参的宏定义与函数类似。如果我们把宏定义时出现的参数视为形参,而在程序中引用宏定义时出现的参数视为实参。那么上例中的a和b就是形参,而2和3以及x+1和y+2都为实参。在宏替换时,就是用实参来替换<宏体>中的形参。
    注意问题
    在使用宏定义时应注意的是:
    (a) 在书写#define 命令时,注意<宏名>和<字符串>之间用空格分开,而不是用等号连接。
    (b) 使用#define定义的标识符不是变量,它只用作宏替换,因此不占有内存。
    (c) 习惯上用大写字母表示<宏名>,这只是一种习惯的约定,其目的是为了与变量名区分,因为变量名
    通常用小写字母。
    如果某一个标识符被定义为宏名后,在取消该宏定义之前,不允许重新对它进行宏定义。取消宏定义使用如下命令:
    #undef<标识符>
    其中,undef是关键字。该命令的功能是取消对<标识符>已有的宏定义。被取消了宏定义的标识符,可以对它重新进行定义。
    宏定义可以嵌套,已被定义的标识符可以用来定义新的标识符。例如:
    #define PI 3.14159265#define R 10#define AREA (PI*R*R)注意事项
    在使用带参数的宏定义时需要注意的是:
    (1)带参数的宏定义的<宏体>应写在一行上,如果需要写在多行上时,在每行结束时,使用续行符 ""结
    束,并在该符号后按下回车键,最后一行除外。
    (2)在书写带参数的宏定义时,<宏名>与左括号之间不能出现空格,否则空格右边的部分都作为宏体。
    例如:
    #define ADD (x,y) x+y
    将会把"(x,y)x+y"的一个整体作为被定义的字符串。
    (3)定义带参数的宏时,宏体中与参数名相同的字符串适当地加上圆括号是十分重要的,这样能够避免
    可能产生的错误。例如,对于宏定义
    #define SQ(x) x*x
    当程序中出现下列语句:
    m=SQ(a+b);
    替换结果为:
    m=a+b*a+b;
    这可能不是我们期望的结果,如果需要下面的替换结果:
    m=(a+b)*(a+b);
    应将宏定义修改为:
    #define SQ(x) (x)*(x)
    对于带参的宏定义展开置换的方法是:在程序中如果有带实参的宏(如"SUB(2,3)"),则按"#define"命令行中指定的字符串从左到右进行置换。如果串中包含宏中的形参(如a、b),则将程序语句中相应的实参(可以是常量、变量或者表达式)代替形参,如果宏定义中的字符串中的字符不是参数字符(如a-b中的-号),则保留。这样就形成了置换的字符串。
    (4) 定义带参数的宏后,使用时最好避免使用表达式传参。这样可以在复杂的宏定义中避免(3)中出现的问题
     
    C/C++杂记:NULL与0的区别、nullptr的来历 - malecrab - 博客园 https://www.cnblogs.com/malecrab/p/5569707.html
    某些时候,我们需要将指针赋值为空指针,以防止野指针。
     
    有人喜欢使用NULL作为空指针常量使用,例如:int* p = NULL;。
    也有人直接使用0值作为空指针常量,例如:int* p = 0;。
     
    前者可能觉得:NULL作为空指针常量,名字很形象,可读性较强。
    后者可能觉得:NULL并不是C/C++语言的关键字,而是一个在标准库头文件<stddef.h>中定义的宏,因此要使用NULL,可能需要直接或简介地包含<stddef.h>头文件,比较麻烦。
     
    问题一:NULL与常数0值有何区别?
     
    要弄清楚这个问题,我们采用问与答的形式来描述。
     
    问:NULL到底是什么?
     
    答:NULL是一个宏。
     
    问:它的值是多少?
     
    答:C/C++标准规定:它的值是一个空指针常量(null pointer constant),由实现定义。#1,#2
     
    问:什么样的值才能称之为空指针常量?
     
    答:C语言中常数0和(void*)0都是空指针常量;C++中(暂且忽略C++11)常数0是,而(void*)0 不是。#3,#4
     
    问:NULL宏是在哪里定义的?
     
    答:通常是在C标准库的<stddef.h>头文件中,不过别的头文件中可能也有定义。
     
    问:一般编译器的<stddef.h>头文件中NULL宏是如何定义的?
     
    答:以gcc或clang编译器为例,NULL的定义大致如下(稍有简化):
    #if defined(__cplusplus)
    # define NULL 0    // C++中使用0作为NULL的值
    #else
    # define NULL ((void *)0)    // C中使用((void *)0)作为NULL的值
    #endif
    问:为什么C中(void*)0是空指针常量,而C++中不是?
     
    答:因为C语言中任何类型的指针都可以(隐式地)转换为void*型,反过来也行,而C++中void*型不能隐式地转换为别的类型指针(例如:int*p = (void*)0;使用C++编译器编译会报错)。#5,#6
     
    问:既然C/C++标准中,常数0都可作为空指针常量,为什么不统一使用0?
     
    答:个人觉得由于(void*)0更能体现指针的意义,而常数0更多的时候是用作整数。因此,C语言中NULL定义选择了(void*)0。(仅供参考)
     
    问题二:C++11中为什么要引入nullptr?
     
    考虑着这样一个函数重载的情形:
    复制代码
    #include <stddef.h>
    void foo(int) {}     // #1
    void foo(char*) {}   // #2
    int main() {
        foo(NULL); // 调用#1还是#2?
    }
    复制代码
    从字面上来讲,NULL是个空指针常量,我们可能会觉得:既然是个指针,那么应该调用#2。但事实上调用的却是#1,因为C++中NULL扩展为常数0,它是int型。
     
    根本原因就是:常数0既是整数常量,也是空指针常量。
     
    为了解决这种二义性,C++11标准引入了关键字nullptr,它作为一种空指针常量。#7例如:
     
    void foo(int) {}     // #1
    void foo(char*) {}   // #2
    int main() {
        foo(nullptr); // 它会毫无异议地调用#2
    }
    附注:
     
    [#1] C99: 7.17-p3:
        The macros are
            NULL
        which expands to an implementation-defined null pointer constant; and ...
     
    [#2] C++03: 18.1-p4:
        The macro NULL is an implementation-defined C + + null pointer constant in this International Standard(4.10).
     
    [#3] C99: 6.3.2.3-p3:
        An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.
     
    [#4] C++03: 4.10-p1:
        A null pointer constant is an integral constant expression (5.19) rvalue of integer type that evaluates to zero.
     
    [#5] C99: 6.3.2.3-p1:
        A pointer to void may be converted to or from a pointer to any incomplete or object type. A pointer to any incomplete or object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.
     
    [#6] C++03: 4.10-p2:
        An rvalue of type “pointer to cv T,” where T is an object type, can be converted to an rvalue of type “pointer to cv void.”
     
    [#7] C++11: 4.10-p1:
        A null pointer constant is an integral constant expression (5.19) prvalue of integer type that evaluates to zero or a prvalue of type std::nullptr_t.
     
    参考:
     
    (1) C99/C++03/C++11标准文档
     
     
     
     
     
    #define Py_ALLOW_RECURSION 
      do { unsigned char _old = PyThreadState_GET()->recursion_critical;
        PyThreadState_GET()->recursion_critical = 1;
    
    #define Py_END_ALLOW_RECURSION 
        PyThreadState_GET()->recursion_critical = _old; 
      } while(0);
    

      

     
     
     In summary, the do ... while is there to work around the shortcomings of the C preprocessor. When those C style guides tell you to lay off the C preprocessor, this is the kind of thing they're worried about.
     
     
     










  • 相关阅读:
    es6中新增的字符串函数
    模板字符串
    jsp注释
    EL表达式(自己看的)
    在禁用cookie时操作Session
    urlEncoder和urlDecoder的作用和使用
    jsp中写java代码的方法
    通过类加载器在WEB应用中获取资源文件路径
    统计在线人数
    Mysql数据库操作简单版
  • 原文地址:https://www.cnblogs.com/rsapaper/p/9841438.html
Copyright © 2020-2023  润新知