关于宏定义 Bin(n),LongToBin(n),LongToBin(0x##n##L)
注:挖坟找到了 2013-12-25 在百度知道回答的这个问题,当时的账号 Estrivr 丢失了,再将此内容转载到此处,心血来潮再优化下解答。
链接1:https://zhidao.baidu.com/question/424150510.html?qbl=relate_question_0&word=%23define LongToBin(n)&dyTabStr=MCwxLDMsMiw1LDYsNCw3LDgsOQ%3D%3D
链接2:https://zhidao.baidu.com/question/1830121487019039060.html?fr=wwwt&word=%23define+LongToBin(n)&dyTabStr=MCwxLDMsMiw1LDYsNCw3LDgsOQ==
这个问题比较基础,那时候人也比较青涩,所以描述有些生硬、偏差,以下为优化后的解答——
具体宏定义代码为(单参数宏定义),
#define LongToBin(n) \ // DEF 1
( \
((n >> 21) & 0x80) | \
((n >> 18) & 0x40) | \
((n >> 15) & 0x20) | \
((n >> 12) & 0x10) | \
((n >> 9) & 0x08) | \
((n >> 6) & 0x04) | \
((n >> 3) & 0x02) | \
((n ) & 0x01) \
)
#define Bin(n) LongToBin(0x##n##L) // DEF 2
- 在 C 语言中,宏定义有一个作用是为了简化一些书写、增强代码可读性(格式:#define 标识符[(参数1,.....,参数n)] 被标识符代表的字符串),在这种情况下,标识符往往比被标识的字符串要简洁,用起来像函数,其与函数的区别在于:宏定义在编译之前、也就是预编译阶段,会完成展开替换——即字符文本的替换(源代码实际上就是字符文本的集合)。上面的这两个宏,也会先展开到具体调用的位置,展开后的代码才会进行编译。
- 再复杂的宏定义,只要将其逐步展开到具体的位置就好理解了,只是这里需要说明一下涉及到的一些不多用的符号——
" \ ":由于 C 语言的书写规范需求,为方便阅读,当一行代码需要换行时(通常因为一行太长),可以在行尾加上\,表示下一行内容也是属于本行内容(称为 续行)。而对于宏定义的格式,需要标识符、被标识符同属一行,因此用该符号告知编译器定义内容要多行结合起来,具体到以上DEF 1,即等价于以下写法——#define LongToBin(n) (((n>>21)&0x80)|((n>>18)&0x40)|((n>>15)&0x20)|((n>>12)&0x10)|(n>>9)&0x08)|((n>>6)&0x04)|((n>>3)&0x02)|(n&0x01)) // 没有换行的写法" ## ":是宏定义中的字符(串)连接符,即,将符号两端的字符(串)接为一个整体,如以上代码DEF 2中,在调用 Bin(n) 时,若 n=11111111,该宏会展开为 LongToBin(0x##11111111##L) ,去掉连接符##进而变为 LongToBin(0x11111111L),即 Bin(11111111) 等价于 LongToBin(0x11111111L),同样 Bin(11001001) 等价于 LongToBin(0x11001001L)。当然,如果 n=AbcDE,那 Bin(AbcDE) 也就是 LongToBin(0xAbcDEL),从格式上也是允许的,就像 A 中所述,宏在预编译阶段展开,只要格式没有问题,编译前不会有错误(只是再通过DEF 1对 LongToBin(0xAbcDEL) 展开后,得到的字符文本没有任何运算意义,到编译时则会报错)。
- 两个宏的作用/目的是:将 8 位的十进制数(或不带双引号的 8 个字符的字符串,如 11111111 )转为十六进制数(如 0xFF),这样从书写形式上,就能将有 8 个位的字符文本(看起来像一个 Byte 的二进制形式)与十六进制相对应,完全是为了书写、阅读的友好性。比如 Bin(11111111) => 0xFF,Bin(11001001) => 0xC9,Bin(10001000) => 0x88,这样就可以在写代码时很方便地观察一个字节里每个位的情况,展开后的值又是正确的。
- 基于 3 中的目标,再看具体的实现方案,
- 先将 8 位十进制数转化为
long型数(32 bits,如 11111111 转为 0x11111111L ,写成二进制是 0b 0001 0001 0001 0001 0001 0001 0001 0001,如 11001001 转为 0x11001001L ,写成二进制则是 0b 0001 0001 0000 0000 0001 0000 0000 0001),这便是代码中DEF2的作用,暂且称这一层展开的数为 中间数。 - 然后将
long型的中间数的每 半个字节 (4 bits)的最低位(非 0 即 1),按对应的高低顺序,整合到最低的单个字节(8 bits)的对应位上。想要实现这种转换,long型由高到低的有效位(半字节最低位)分别需要右移 21、18、15、12、9、6、3 和 0 个位置,再 位与 上 0xFF 达到保值的效果( 0xFF=0x80|0x40|0x20|0x10|0x08|0x04|0x02|0x01,位与 0xFF 也便是分别 位与 等号右侧的各个值后再 位或 起来)。这便是代码中DEF 1的作用。 - 调用时,先展开
DEF 2再展开DEF 1。
- 先将 8 位十进制数转化为
- 宏展开示例(含数值计算),
// 11001001 -> 0xC9 Bin(11001001) => LongToBin(0x11001001L) // 第一层展开 => ( \ // 第二层展开 ((0x11001001L >> 21) & 0x80) | \ // & 右侧数值,发生数据类型转换,char->long ((0x11001001L >> 18) & 0x40) | \ ((0x11001001L >> 15) & 0x20) | \ ((0x11001001L >> 12) & 0x10) | \ ((0x11001001L >> 9) & 0x08) | \ ((0x11001001L >> 6) & 0x04) | \ ((0x11001001L >> 3) & 0x02) | \ ((0x11001001L >> 0) & 0x01) \ ) => // 以下为数值计算 ( \ ((0b 0001 0001 0000 0000 0001 0000 0000 0001L >> 21) & 0b 0000 0000 0000 0000 0000 0000 1000 0000) | \ ((0b 0001 0001 0000 0000 0001 0000 0000 0001L >> 18) & 0b 0000 0000 0000 0000 0000 0000 0100 0000) | \ ((0b 0001 0001 0000 0000 0001 0000 0000 0001L >> 15) & 0b 0000 0000 0000 0000 0000 0000 0010 0000) | \ ((0b 0001 0001 0000 0000 0001 0000 0000 0001L >> 12) & 0b 0000 0000 0000 0000 0000 0000 0001 0000) | \ ((0b 0001 0001 0000 0000 0001 0000 0000 0001L >> 9) & 0b 0000 0000 0000 0000 0000 0000 0000 1000) | \ ((0b 0001 0001 0000 0000 0001 0000 0000 0001L >> 6) & 0b 0000 0000 0000 0000 0000 0000 0000 0100) | \ ((0b 0001 0001 0000 0000 0001 0000 0000 0001L >> 3) & 0b 0000 0000 0000 0000 0000 0000 0000 0010) | \ ((0b 0001 0001 0000 0000 0001 0000 0000 0001L >> 0) & 0b 0000 0000 0000 0000 0000 0000 0000 0001) \ ) => ( \ ((0b 0000 0000 0000 0000 0000 0000 1000 1000L) & 0b 0000 0000 0000 0000 0000 0000 1000 0000) | \ ((0b 0000 0000 0000 0000 0000 0100 0100 0000L) & 0b 0000 0000 0000 0000 0000 0000 0100 0000) | \ ((0b 0000 0000 0000 0000 0010 0010 0000 0000L) & 0b 0000 0000 0000 0000 0000 0000 0010 0000) | \ ((0b 0000 0000 0000 0001 0001 0000 0000 0001L) & 0b 0000 0000 0000 0000 0000 0000 0001 0000) | \ ((0b 0000 0000 0000 1000 1000 0000 0000 1000L) & 0b 0000 0000 0000 0000 0000 0000 0000 1000) | \ ((0b 0000 0000 0100 0100 0000 0000 0100 0000L) & 0b 0000 0000 0000 0000 0000 0000 0000 0100) | \ ((0b 0000 0010 0010 0000 0000 0010 0000 0000L) & 0b 0000 0000 0000 0000 0000 0000 0000 0010) | \ ((0b 0001 0001 0000 0000 0001 0000 0000 0001L) & 0b 0000 0000 0000 0000 0000 0000 0000 0001) \ ) => ( \ (0x00000088 & 0x00000080) | \ (0x00000440 & 0x00000040) | \ (0x00002200 & 0x00000020) | \ (0x00011000 & 0x00000010) | \ (0x00088008 & 0x00000008) | \ (0x00440040 & 0x00000004) | \ (0x02200200 & 0x00000002) | \ (0x11001001 & 0x00000001) \ ) => (0x00000080 | 0x00000040 | 0x00000000 | 0x00000000 | 0x00000008 | 0x00000000 | 0x00000000 | 0x00000001) => 0x000000C9 // 即,宏展开并数值运算后,形成以下对应关系, Bin(11001001) => 0x000000C9 - 应用场合,
- 单片机(尤其是 8 位机)开发过程中,一些寄存器的赋值,通常是一个字节代表多个功能标志,比如一组 GPIO 可能有 8 个端口,对应的输入输出状态寄存器、端口电平寄存器、端口上下拉使能寄存器等,初始化过程中可以整体赋值,同时又可以看到每个标志位的状态,很直观。
- 只要是对单个字节进行操作地场合,都可以使用这两个宏,但由于其形式上不容易扩展到更大内存单元,单字节之外的场景少用。
- 其他,
- 按照其运算思路,
|可以替换为+。 - 对于宏定义,预编译阶段只完成展开,即字符文本替换,而编译器则通常会将 只有立即数参与的表达式 计算出结果(提升代码执行效率)。
// 源码, unsigned char i = Bin(11001001); unsigned long l = Bin(11001001); // 预编译后, unsigned char i = (((0x11001001L >> 21) & 0x80) | ((0x11001001L >> 18) & 0x40) | ((0x11001001L >> 15) & 0x20) | ((0x11001001L >> 12) & 0x10) | ((0x11001001L >> 9) & 0x08) | ((0x11001001L >> 6) & 0x04) | ((0x11001001L >> 3) & 0x02) | ((0x11001001L >> 0) & 0x01)); // 无换行 unsigned long l = (((0x11001001L >> 21) & 0x80) | ((0x11001001L >> 18) & 0x40) | ((0x11001001L >> 15) & 0x20) | ((0x11001001L >> 12) & 0x10) | ((0x11001001L >> 9) & 0x08) | ((0x11001001L >> 6) & 0x04) | ((0x11001001L >> 3) & 0x02) | ((0x11001001L >> 0) & 0x01)); // 无换行 // 编译优化, unsigned char i = 0xC9; // 发生截断 unsigned long l = 0x000000C9; - 其他实现方案(枚举方案可以用一下)
参考链接:https://www.cnblogs.com/LittleTiger/p/4373887.html// 2. 直接按字节移位后组合,调用时不如上述方案方便 // 定义 #define Bin(a, b, c, d, e, f, g, h) \ ((a << 7) + (b << 6) + (c << 5) + (d << 4) + (e << 3) + (f << 2) + (g << 1) + (h << 0)) // 调用 unsigned char i = Bin(1,1,0,0,1,0,0,1); // 3. 枚举(枚举式宏定义) // 定义 typedef enum { _0b00000000, _0b00000001, _0b00000010, _0b00000011, ··· ··· _0b11111101, _0b11111110, _0b11111111, }BIN_VALUE; #define _0b00000000 0x00 #define _0b00000001 0x01 #define _0b00000010 0x02 #define _0b00000011 0x03 ··· ··· ··· ··· ··· ··· #define _0b11111101 0xFD #define _0b11111110 0xFE #define _0b11111111 0xFF // 调用 unsigned char i = _0b11001001;
- 按照其运算思路,

浙公网安备 33010602011771号