关于宏定义 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;
- 按照其运算思路,
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY