预编译与变长参数函数

news/2024/12/5 12:22:04/

目录

  • 预处理指令
    • #pragma
    • #error
  • 变长参数列表函数
    • 变长参数函数
    • 变长参数的宏定义
    • 实现原理
  • 预定义宏
  • 预处理常量表达式
  • 其他
    • #/##

预处理指令

#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):将某个报警设置为失效——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,#error,static_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;


http://www.ppmy.cn/news/709458.html

相关文章

原生JS封装cookie

1&#xff0c;什么是coolie http的每一次请求之间是没有联系的。也就是说&#xff0c;你对同一个网页请求两次&#xff0c;这两次之间没有联系&#xff0c;因为每一次请求都会有三次握手四次挥手的过程&#xff0c;所以http协议是一种无状态协议&#xff0c;意思是说不能保持访…

Android studio 三大模拟器比较

1.本身自带的------&#xff08;缺点&#xff1a;又卡又慢&#xff0c;自己不对比试试真是不知道卡死了&#xff09; 2.genymotion模拟器-----&#xff08;缺点&#xff1a;安装有点小麻烦&#xff09; 下载地址&#xff1a;https://www.genymotion.com/download/ 有免费版本…

java HiveMetaStoreClient kerberos 亲测通过

场景描述 最近在搞大数据的工作&#xff0c;有时候写代码需要提前在本地做验证&#xff0c;比如&#xff1a;数据格式。 本地验证麻烦的地方有三点: 本地代码连大数据集群&#xff0c;xml 文件怎么配置。kerberos 认证怎么做。java 依赖包的不兼容。 最好的办法就是踩一次坑…

华硕z790主板安装ubuntu2204后系统日志膨胀

查看系统日志 日志文件大小超过10G&#xff0c;以下内容不断重复 Jul 3 10:23:19 server3 kernel: [325275.103731] pcieport 0000:00:01.1: AER: Corrected error received: 0000:02:00.0 Jul 3 10:23:19 server3 kernel: [325275.103732] nvme 0000:02:00.0: PCIe Bus Er…

手机投屏不是全屏怎么办_一招搞定手机投屏不是全屏问题,手机投屏自适应全屏...

一招搞定手机投屏不是全屏问题&#xff0c;手机投屏自适应全屏 在统计7月份咨询量的时候&#xff0c;发现就手机用户而言&#xff0c;咨询比较多的不再是怎么投屏&#xff0c;而是怎么全屏。 因为电视机、各种投屏软件、投屏器的流畅&#xff0c;手机投屏到大屏幕已经不是什么“…

vivoX30是android5的吗,深度剖析揭秘opporeno5质量和vivox30区别是?选哪个更好?独家揭秘报道...

这二款opporeno5和vivox30区别不是很大的&#xff0c;经过对比我购的是opporeno5&#xff0c;这款opporeno5做工很细腻&#xff0c;价格还是很划算的&#xff0c;我自己也是才购不久的&#xff0c;外形外观&#xff1a;蓝色很好看&#xff0c;颜色很正拍照效果&#xff1a;像素…

中国手机发展史:从1G通讯技术到华为5G时代

从2018年开始&#xff0c;5G手机和折叠屏手机就一直在我们的耳边挥之不去&#xff0c;手机里的app更加是每天都在推荐关于5G的信息给自己&#xff0c;这使得就算是一个不怎么了解手机的人都知道了这两个概念。可是在大家都知道了5G和折叠屏的概念的时候&#xff0c;有多少人知道…

python 手机_Python 实现手机销售/()

刚刚学了Python的函数,写了个作业,留着以后再看. 2017-12-09 简单实现一个手机销售系统增删改查的功能. 手机品牌     手机价格   库存数量 vivoX9       2798     25 iphone7(32G)   4888     31 iphone7(128G)   5668     22 iphone7P(12…