c++ 预处理之正整型实现方法

 更新时间:2020年4月25日 17:30  点击:2176

虽然通过一系列的奇技淫巧,让预处理也图灵完备一把,但是用预处理来做计算,真的很吃力不讨好。因为预处理一开始设计出来的目的,就没什么野心,原本就仅仅只是为了做简简单单的文本替换工作,并没有想过要成为正儿八经的编程语言,即便是最最缩水版脚本语言的功能要求都达不到。只是后来,实在是大量要求要批量自动生成代码,特别是c++11之前的版本玩什么模板元编程,铺天盖地的要有大量相似的代码。这些代码用其他工具来生成,当然形式会更加漂亮,但是始终还是用原生的预处理来做这种事情会更加的方便,否则每次修改,都要运行一遍外部工具,都麻烦啊!本人是倾向于用预处理来生成代码的。另外,c++11之后,的确原来很多需要宏来生成代码的场合已经不必要了,但是因为c++11的类型推导能力大大加强了之后,发现又有一大波地方可以用宏来生成代码了。并不是说C++中的宏是必不可少之物,但是用了宏,真的可以减少很多很多的重复代码,起码纸面上的代码清爽了很多。

预处理的原生数据类型就只有符号,然后符号只支持##的并接运算,同时,预处理也能识别并接后的结果(否则,并接运算就没意义了),如果是宏函数,就进行调用操作,如果是宏符号,就替换文本,如果什么都不是,就什么都不做,保留符号。但是这样的弱鸡类型,显然远远不能满足离经叛道的码猿需要。经过大量的宏编程的尝试之后,可以很肯定一点,预处理里面只能再模拟出来一种数据类型,那就是正整数,虽然通过补码运算来仿真负数,但是由于预处理里面的符号不能包含减号(-)字符,当然要花大力气捣鼓负整数也是可以的,只是使用上也不方便也不直观,性价比不高,基本上,必须用宏来生成代码的地方,都可以不需要负整数的。

另外,预处理也没有变量类型的概念,不要说强类型,就连弱类型也不是,完全就是无类型。正整数类型的概念全靠码猿人肉编译器来维护,一个循环的宏代码生成一般都是来来回回也不知道调用了多少层宏调用,任何一个地方出错,有时候是几吨密密麻麻的中间失败代码(编译器的预处理缓冲溢出,弃械投降),有时候就完全没有输出,没有任何一丁点的提示,简直是大海捞针的找问题。因此,在用宏循环生成代码时,必须小心翼翼,步步为营,不得不感慨,正儿八经语言里面的类型真是好东西啊。

其实,数据类型并不重要,重要的是数据上能够支持的运算集合以及这些运算能运用的场合。
好了,回到上文,我们用_ZPP_INC_N搞了10个数,通过复制粘贴,可以把N增加到255。实际运用中,完全足够用了。

#define _ZPP_INC_JOIN(_A, _B) _ZPP_INC_JOIN_IMP1(_A, _B)
#define _ZPP_INC_JOIN_IMP1(_A, _B) _ZPP_INC_JOIN_IMP2(~, _A##_B)
#define _ZPP_INC_JOIN_IMP2(p, res) res

#define PP_INC(x, ) _ZPP_INC_JOIN(_ZPP_INC_, x)
#define _ZPP_INC_0 1
#define _ZPP_INC_1 2
#define _ZPP_INC_2 3
#define _ZPP_INC_3 4
#define _ZPP_INC_4 5
#define _ZPP_INC_5 6
#define _ZPP_INC_6 7
#define _ZPP_INC_7 8
#define _ZPP_INC_8 9
#define _ZPP_INC_9 10
...
#define _ZPP_INC_255 256

同样的方式,再如法泡制PP_DEC,从256开始,一直递减到0为止。对于大于256的数,就不支持了,那就都是未定义操作。这样子,通过PP_INC(n),就得到n+1;而PP_DEC(n),则是n-1。比如PP_INC(PP_DEC(9)),其结果肯定是9了。很好,这样子,在预处理中就实现了自然数自增1和自减1的运算了。另外,对于大于256的数,比如512传递给PP_INC,就只得到一个_ZPP_INC_512的符号,完全没有任何意义。

然后,两个自然数是否相等的判断,也非常重要,必须支持。但是,在此之前,要实现一个宏函数PP_NOT,用来判断入参是否为0。为0的话,则函数返回1,否则,就返回0。也即是:
PP_NOT(0) == 1
PP_NOT(23) == 0,或者 PP_NOT(var) == 0。
记住,预处理提供给我们的原生类型就只有符号和##并接运算,除此之外,别无他物。好像工具太简陋,能完成目的吗?不得不佩服有些码猿的脑洞。以下代码是这样运作的,假设PP_NOT生成以下的调用形式,先不管PP_ARG1,至于符号~,是这样子的,可以看成普通的变量名字,它就是占位符,因为预处理只识别逗号(,),和小括号,至于其他符号,完全无视,那些是C/C++编译阶段才关心的符号。
PP_NOT(0) = PP_ARG1(~, 1, 0)
PP_NOT(n) = PP_ARG1(_ZPP_NOT_n, 0)
然后,让PP_ARG1取第二个参数(码猿的计数是从0开始的,也即是,0即是1,1即是2),就完成任务了。至于_ZPP_NOT_n是什么鬼,那个只是中间生成的临时符号,可以舍弃。我们只需对_ZPP_NOT_0做特别处理。因此,代码可以这样写了。PP_PROBE()用以生成两个入参
#define PP_PROBE() ~, 1
#define _ZPP_NOT_0 PP_PROBE()
#define PP_NOT(_X, ...) PP_IS(PP_JOIN(_ZPP_NOT_, _X))
# define PP_IS(...) PP_ARG1(__VA_ARGS__, 0)

这样子之后,显然PP_NOT(n)就可以变成PP_ARG1(_ZPP_NOT_n, 0)的形式了。PP_NOT不是只需一个入参吗?为何后面还要带省略号,纯粹是为了后面各种变态的运用,取悦编译器。已经用宏来写代码了,就不必再遵守什么清规戒律,只要能完成任务就行了。

至于PP_ARG1的实现,就很简单了,如下所示,
#define PP_ARG0(_0, ...) _0
#define PP_ARG1(_0, _1, ...) _1
#define PP_ARG2(_0, _1, _2, ...) _2

然后通过两次取反的函数,再补上函数PP_BOOL,如果入参>0,就返回1,否则返回0,类似于整型到bool的强制类型转换。
#define PP_BOOL(_X, ...) PP_NOT(PP_NOT(_X))

有了这些的铺垫之后,要比较两个自然数是否相等,就简单了。其实没什么神秘的,就是针对从0到255,重复256个以下形式的#define语句,
#define _ZPP_0_EQUALS_0 PP_PROBE()
#define _ZPP_1_EQUALS_1 PP_PROBE()
#define _ZPP_2_EQUALS_2 PP_PROBE()
...
#define PP_EQUALS(x, y) PP_IS(PP_CONCAT4(_ZPP_, x, _EQUALS_, y))
PP_EQUALS就是将入参并接成_ZPP_x_EQUALS_y的形式,只要x和y相同,也即是说,它们在上面的表格中,那么,道理就如同PP_NOT的实现那样,最后结果就是1了。其实,预处理中没有判断这种玩意,只有表格,只有并接,只有查表。所谓的图灵完备,说白了,没有玄虚的,就是建表,然后查表。对相等比较取反PP_NOT,自然就得到不相等的判断函数。
#define PP_UN_EQUALS(x, y) PP_NOT(PP_IS(PP_CONCAT4(_ZPP_, x, _EQUALS_, y)))
再次建表,就可以得到bool运算的函数,或与
#define PP_OR(a,b) PP_CONCAT3(_ZPP_OR_, a, b)
#define _ZPP_OR_00 0
#define _ZPP_OR_01 1
#define _ZPP_OR_10 1
#define _ZPP_OR_11 1

#define PP_AND(a,b) PP_CONCAT3(_ZPP_AND_, a, b)
#define _ZPP_AND_00 0
#define _ZPP_AND_01 0
#define _ZPP_AND_10 0
#define _ZPP_AND_11 1

再准备一张表格,将字节映射到8个二进制位。
#define _ZPP_BINARY_0 (0, 0, 0, 0, 0, 0, 0, 0)
#define _ZPP_BINARY_1 (0, 0, 0, 0, 0, 0, 0, 1)
#define _ZPP_BINARY_2 (0, 0, 0, 0, 0, 0, 1, 0)
#define _ZPP_BINARY_3 (0, 0, 0, 0, 0, 0, 1, 1)
#define _ZPP_BINARY_4 (0, 0, 0, 0, 0, 1, 0, 0)
...
然后通过模拟计算机组成原理里面的加减乘除的原理,就可以实现四则运算了。对了,整个预处理库的代码都在压缩包上,功能比boost的预处理库强多了,但是代码却少了很多,也容易理解多了,所有代码在vs下面正常运行,其他平台还没有测试。代码包:preprocessor.rar

[!--infotagslink--]

相关文章

  • C++ STL标准库std::vector的使用详解

    vector是表示可以改变大小的数组的序列容器,本文主要介绍了C++STL标准库std::vector的使用详解,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2022-03-06
  • C++中取余运算的实现

    这篇文章主要介绍了C++中取余运算的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
  • 详解C++ string常用截取字符串方法

    这篇文章主要介绍了C++ string常用截取字符串方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • C++调用C#的DLL程序实现方法

    本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
  • C++中四种加密算法之AES源代码

    本篇文章主要介绍了C++中四种加密算法之AES源代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。...2020-04-25
  • C++ 整数拆分方法详解

    整数拆分,指把一个整数分解成若干个整数的和。本文重点给大家介绍C++ 整数拆分方法详解,非常不错,感兴趣的朋友一起学习吧...2020-04-25
  • C++中 Sort函数详细解析

    这篇文章主要介绍了C++中Sort函数详细解析,sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变...2022-08-18
  • C++万能库头文件在vs中的安装步骤(图文)

    这篇文章主要介绍了C++万能库头文件在vs中的安装步骤(图文),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
  • 详解C++ bitset用法

    这篇文章主要介绍了C++ bitset用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • 浅谈C++中的string 类型占几个字节

    本篇文章小编并不是为大家讲解string类型的用法,而是讲解我个人比较好奇的问题,就是string 类型占几个字节...2020-04-25
  • C++ Eigen库计算矩阵特征值及特征向量

    这篇文章主要为大家详细介绍了C++ Eigen库计算矩阵特征值及特征向量,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-04-25
  • C++ pair的用法实例详解

    这篇文章主要介绍了C++ pair的用法实例详解的相关资料,需要的朋友可以参考下...2020-04-25
  • VSCode C++多文件编译的简单使用方法

    这篇文章主要介绍了VSCode C++多文件编译的简单使用方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-03-29
  • C++中的循环引用

    虽然C++11引入了智能指针的,但是开发人员在与内存的斗争问题上并没有解放,如果我门实用不当仍然有内存泄漏问题,其中智能指针的循环引用缺陷是最大的问题。下面通过实例代码给大家介绍c++中的循环引用,一起看看吧...2020-04-25
  • C++随机点名生成器实例代码(老师们的福音!)

    这篇文章主要给大家介绍了关于C++随机点名生成器的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • C++如何删除map容器中指定值的元素详解

    map容器是C++ STL中的重要一员,删除map容器中value为指定元素的问题是我们经常与遇到的一个问题,下面这篇文章主要给大家介绍了关于利用C++如何删除map容器中指定值的元素的相关资料,需要的朋友可以参考借鉴,下面来一起看看吧。...2020-04-25
  • C++ 约瑟夫环问题案例详解

    这篇文章主要介绍了C++ 约瑟夫环问题案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下...2021-08-15
  • C++中cin的用法详细

    这篇文章主要介绍了C++中cin的用法详细,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • 基于C++中常见编译错误的总结详解

    本篇文章是对C++中的常见编译错误进行了详细的分析介绍,需要的朋友参考下...2020-04-25
  • c++优先队列(priority_queue)用法详解

    这篇文章主要介绍了c++优先队列(priority_queue)用法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25