预处理指令

#pragma

#pragma是一条预处理指令,用来向编译器传达语言标准以外的信息

  • _Pragma:_Pragma是与#pragma功能一样的操作符,格式为_Pragma (字符串字面常量),如_Pragma("once"),相比#pragma,由于_Pragma是一个操作符,因此可以用在一些宏中,而#pragma则不能在宏中展开
  • #pragma once:只编译一次
  • 字节对齐
    • 指定结构体对齐,括号中指定对其字节数
    • #pragma pack(show):显示当前内存对齐的字节数,vs不会打印出来,但会在编译阶段给出一个警告,说明当前对齐字节数
    • #pragma pack(push [, identifier] [, n]):将当前对齐字节数压入栈顶,并设置n为新的对齐字节数;
    • #pragma pack(pop [, identifier] [, n])#pragma pack(pop)会弹出栈顶对齐字节数,并设置默认对齐字节
  • #pragma message:用于自定义编译信息输出到终端,一般和#if配合使用,用在控制版本号;在vs下编译时需要用()将message括起来
  • 警告设置,只对当前文件有效
    • #pragma warning(push):存储当前报警设置
    • #pragma warning(push, n):存储当前报警设置,并设置报警级别为n,n为从1到4的自然数
    • #pragma warning(pop):恢复之前压入堆栈的报警设置,在一对push和pop之间的任何设置都将在后面失效
    • #pragma warning(disable: n):将某个报警设置为失效
    • #pragma warning(default: n):将报警设置为默认
  • 编辑器中的代码收缩(vs中可用):
#pragma region my_region
void Test() {}
#pragma endregion my_region

#error

预处理指令:#error “...”, 当预处理器遇到该指令时停止编译并将后面的自定义错误消息输出,通过和预处理指令#if配合,在预处理阶段进行断言。
附:

  • 运行时断言assert(expression)(头文件:#include<cassert>#include<assert.h>):expression为false触发;如果要禁用assert宏,在包含头文件之前#define NDEBUG。assert会极大影响性能。
  • 编译期断言static_assertstatic_assert(断言表达式,字符串提警告信息),通常需要返回一个bool值。static_assert断言表达式的结果必须是在编译时期可以计算的表达式,即常量表达式。编译期断言的一个简单实现:
#define STATIC_CHECK(expr) { char unnamed[(expr) ? 1: 0]; }

替代上面实现的一个较好的做法是依赖一个名称带有意义的template,如下:

template<bool> struct CompileTimeError;
template<> struct CompileTimeError<true> {};
#define STATIC_CHECK(expr) (CompileTimeError<(expr) != 0>())

可以说,assert#errorstatic_assert三者分工明确,都是在不同时期的断言,根据需要而定

变长参数列表函数

变长参数函数

在C++代码中应该优先使用变参模板,通过变参模板使用类型安全的变长参数列表。C风格变长参数列表不知道参数类型和个数,所以并不是安全的。
C风格可变长参数函数:#include<stdarg.h> (C++可以用:#include<cstdarg>),包括以下宏:

  • va_list
  • va_start
  • va_arg
  • va_end
    变长参数函数的简单实现举例:
#include <cstdio>
#include <cstdarg>
void debugOut(const char* str, ...) {
    va_list ap;
    va_start(ap, str);
    vfprintf(stderr, str, ap);
    va_end(ap);
}
// 调用
debugOut("This is debugOut test: %s, %d str", "hello world", 2);

变长参数的最后一个参数为省略号 … 代表任意数目和类型的参数。如果要访问这些参数:

  1. 声明一个va_list的变量
  2. 调用va_start对其进行初始化,va_start的第二个参数必须是参数列表中最右边的已命名变量。所有具有变长参数列表的函数都至少需要一个已命名的参数
  3. 如果要访问实际参数,可以使用va_arg(),接收va_list作为第一个参数,以及需要解析的参数的类型作为第二个参数。但是如果不提供显式的方法就无法知道参数列表的结尾是什么。例如,可以将第一个参数作为参数个数的计数,或者当参数是一组指针时可以要求最后一个指针是nullptr。
  4. 调用va_start之后必须要调用va_end()以确保函数结束后,栈处于稳定的状态。
void printInts(size_t num, ...) {
    va_list ap;
    va_start(ap, num);
    for (size_t i = 0; i < num; i++) {
        int temp = va_arg(ap, int);
        std::cout << temp << " ";
    }
    va_end(ap);
    std::cout << std::endl;
}

所以函数原型里的…符号前一般都需要一个先导参数,用于标识可变参数的起始位置,并且一般都是用来标识参数个数时使用的

变长参数的宏定义

变长参数的宏定义是指在宏定义中参数列表的最后一个参数为省略号,而预定义宏__VA_ARGS__则可以在宏定义的实现部分替换省略号所代表的字符串

#define PR(...) printf(__VA_ARGS__)    // 给PR传个参数就能打印出来了

实现原理

得益于C语言默认的cdecl调用惯例的自右向左压栈传递方式;cdecl调用惯例保证了参数的正确清除,有些调用惯例是由被调用方负责清除堆栈的参数,但被调用方不知道有多少参数被传递进来,所以没办法清除,cdecl是调用方负责清除堆栈,因此没有这个问题

预定义宏

  • __cplusplus: 判断C++标准的宏,可以通过该值判断支持哪个C++标准,如在C++03标准中__cplusplus值为199711L ,C++11中为201103L
#if __cplusplus>=201103L
...
#endif
  • __LINE__:当前源代码行号;
  • __FILE__:当前源文件名,字符串;
  • __DATE__:当前编译日期,格式(内容)为月 日 年
  • __TIME__:当前编译时间,格式为hh:mm:ss;
  • __func__:当前所在函数名。按照标准定义,编译器会隐式地在函数的定义之后定义__func__标识符。C++11标准允许在类或者结构体中使用;但不允许将__func__作为默认函数值,因为__func__还未被定义。在类函数中只会显示当前的函数名,而不会显示类名等修饰的部分
void hello()  {    
    static const char *__func__ = "hello";
    ...
}
// 在自定义类型中使用
struct TestStruct
{
       TestStruct() : name(__func__) {}
       const char* name;
};
  • __STC__:当要求程序严格遵循ANSI C标准时该标识被赋值为1(暂时不知道有什么用)

预处理常量表达式

如果要查询某个头文件是否存在,使用C++11中的__has_include("filename")__has_include(<filename>)预处理器常量表达式。如果头文件存在则返回1,否则返回0。例子:

#if __has_include(<optional>)
  #include<optional>
#elif __has_include(<experimental/optional>)
  #include<experimental/optional>
#endif

其他

#/##

这两个都是处理变量名而不是变量值

  • #:将宏定义中的变量名转化为字符串,具体为把#后的形参转化成一个字符串,注意不是值,只能修饰带参数的宏的形参
  • ##: 变量名称的字符串连接,注意不是值的连接,但拼接完之后不是字符串,是一个名称,中间可以有空格存在,如:
define AAA(aaa) int symbol ## aaa = 10;

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐