新闻详情

新闻详情

首页 / 资讯中心 / 详情

菜鸟教程C经典100例练习52:彻底搞懂按位或|的底层原理与应用

发布时间:2026/9/28 17:05:16来源:尧图网络
菜鸟教程C经典100例练习52:彻底搞懂按位或|的底层原理与应用
如果你也在刷《菜鸟教程C经典100例》你会发现一个规律前面几十道题练的是数学逻辑、循环、数组到了五十题左右画风突然一变开始反复折腾、|、^、移位这些“看起来不像C语言”的运算符。我第一次做到练习52的时候就愣了好一会儿——题目只有一句话学习使用按位或|。代码短得可怜但里面藏的东西非常多甚至可以说练到这一题才真正开始理解“C语言为什么贴近硬件”。这篇文章就把练习52彻底讲透原题在干什么、按位或的二进制规则、新手最容易被绕进去的地方、实际工程里拿它做什么、以及我当时踩过的几个坑。不管你是刚开始学C还是已经刷到半路有点迷糊照着我下面的步骤走一遍比光把代码抄一遍有用得多。1. 先还原题目第52题到底在练什么1.1 原题代码和输出结果菜鸟教程里的练习52原题是“学习使用按位或 |”。典型代码如下#include stdio.h int main() { int a, b; a 077; b a | 3; printf(b %d \n, b); b | 7; printf(b %d \n, b); return 0; }这段代码的运行结果是什么我第一次并没有猜对。它输出的是b 63 b 63很多新手看到这里会一头雾水077不是77吗77 | 3怎么算也不该是63吧这正是练习52的第一个“陷阱”C语言里以0开头的整数是八进制不是十进制。077是八进制对应十进制63对应二进制111111。3是十进制二进制是000011。两个二进制数逐位做或运算后结果仍然是111111也就是63。所以这道题表面上是练“按位或”运算符的用法实际上同时考了你三件事八进制字面量的识别、整数的二进制表示、以及|运算符的逐位规则。一箭三雕。1.2 为什么它被安排在第52题要理解这题的价值先看《菜鸟教程C经典100例》的整体编排。前十几道题是数字排列、奖金提成、完全平方数这类偏数学的题作用是让你熟悉变量、if、for、printf。中间几十道题开始练数组、函数、递归、字符串让代码从“算数”变成“处理数据”。到了51到55题左右题目集中出现位运算相关的练习51是按位与52是按位或|53是按位异或^54是取一个整数从右端开始的4到7位55是按位取反~。这个顺序不是随便排的。位运算要求你把数字看成“一串二进制位”而不是一个抽象的大小。前面学的 - * /都是对数值整体做运算位运算却要拆到每一个bit上。所以它必须放在你已经有足够的编程感受之后否则很容易变成死记硬背。练习52故意选了一个特别简短的代码就是希望你别把精力花在读长程序上而是老老实实去看二进制。1.3 逐行拆解练习52的代码我们一行一行看。int a, b;定义两个整型变量后面都当作二进制序列使用。a 077;注意这里是八进制。八进制的077转换成二进制是0b111111也就是6个1。换算过程八进制每一位对应3个二进制位。077拆成0和77严格说八进制字面量是0开头后面每一位都是0到7。所以077就是7和7二进制分别111和111连起来就是111111。十进制0打头的八进制数从右往左每位的权重是1、8、64。所以077 7*8 7 63。b a | 3;a是1111113的二进制是000011。逐位做或运算得到111111。因为a的低两位已经是1再和3做或低两位仍然是1不会变成2、不会进位只会保持1。printf(b %d \n, b);%d把它当十进制打印输出63。b | 7;这是复合赋值等价于b b | 7。b是1111117的二进制是000111。或运算后仍然是111111所以第二次输出还是63。到这里你应该能理解按位或的特性是“只要某一位已经是1再和任何数做或运算这一位都不会变”。如果想要“把某些位置成1”用|就是最安全的方式。2. 按位或的底层规则与新手最容易混淆的地方2.1 真值表与一个生活类比按位或的规则只有四条背下来非常容易左位右位结果位000011101111翻译成一句话就是两个bit里只要有一个是1结果就是1两个都是0结果才是0。用生活类比理解两个人合伙开一个“周末要不要加班”的投票规则是只要有一方想去就去。0代表不去1代表去。那0 | 0就是两个都不去结果不去0 | 1或1 | 0是有一个人想去结果去1 | 1更是去。这个模型和逻辑上的“或”非常像区别只在于位运算是一排bit同时投票。很多教材会用“合并flag”来解释按位或这个后面细说。现在你只要记住|是在二进制层面逐位比较并合并。2.2 按位或和逻辑或不是一回事这是新手最容易踩的坑。C语言里有两个长得差不多的“或”|按位或操作的是整数结果是整数。||逻辑或操作的是“真/假”结果是0或1。看两个例子int x 3 | 2; // x 3 int y 3 || 2; // y 13的二进制是112的二进制是10。按位或11 | 10 11所以x等于3。逻辑或3 || 2是先判断3为真、2为真再返回1表示“真”。新手写条件判断时容易把if (a || b)写成if (a | b)。如果恰好a是1、b是2那么a | b的结果是3非零条件依然成立不容易发现问题。但如果你用if ((a | b) 3)去判断“a或b是否等于3”逻辑可能完全错误。最稳妥的办法如果你要的是条件判断就写||如果你要的是位级合并才写|。还有一个区别是短路求值。a || b如果a已经是真b根本不会执行这对有副作用的表达式很重要。而a | b必须把两边都算完再做按位运算不存在短路。这也意味着|有时候会带来意外的性能损耗或副作用所以不能盲目替换。2.3 C语言里八进制、十六进制字面量必须一眼看懂练习52用077开头说明作者默认你了解八进制。但很多教程讲到这里一笔带过导致很多人根本不知道0前缀是什么意思。在C语言里十进制字面量直接写数字比如63。八进制字面量以0开头比如077、010、017。注意八进制数字只能出现0到7像089这种写法在C里是非法数字字面量。十六进制字面量以0x或0X开头比如0x3F、0Xff。练习52里077是八进制而3、7没有前缀是十进制。它们在做位运算时必须先转换成统一的二进制形式。这个过程靠肉眼心算刚开始会慢但多刷几道位运算题就习惯了。在printf中如果想把一个整数按不同进制打印出来可以这样#include stdio.h int main() { int b 63; printf(十进制: %d\n, b); printf(八进制: %o\n, b); printf(十六进制: %x\n, b); return 0; }输出十进制: 63 八进制: 77 十六进制: 3f注意八进制输出是77不是077printf不会自动带前缀。想看二进制则没有现成格式符得自己写函数转换或者用位运算一位一位取出来。练习52没有要求打印二进制但你自己动手写一个打印二进制的函数对理解位运算帮助极大。3. 为什么这道题值得反复做按位或的实际用武之地3.1 标志位合并权限管理的基础位运算最经典的工程应用就是“用一个整数存多个开关状态”。比如Linux文件权限只有读、写、执行三种用三个bit就能表示#define READ (1 0) // 二进制 001 #define WRITE (1 1) // 二进制 010 #define EXECUTE (1 2) // 二进制 100 int flags 0; flags | READ; // 加上读权限flags变成1 flags | WRITE; // 加上写权限flags变成3 flags | READ | WRITE; // 等价于 flags flags | 1 | 2还是3如果用加法flags READ第一次加1没问题但如果重复添加权限flags READ会把1加两次变成2含义就错了。用|则不同flags | READ不管重复执行多少次第0位始终是1不会进位不会污染其他位。这就是“置位操作”的好处。判断是否拥有某个权限也有固定写法if (flags READ) { printf(有读权限\n); }这里用的是按位与不是或但它和|是一对|用来置1用来判断。练习52只教了|但实际使用中一定会跟、~组合所以别只盯着一个运算符。3.2 嵌入式开发中“只置位不干扰其他位”如果你自学C语言是为了以后接触单片机按位或几乎是天天要用的操作。单片机里的寄存器控制经常是一大堆bit各自代表不同功能你想改其中一两个bit又不想动到其他bit最安全的手段就是|。举个常见例子假设一个32位寄存器GPIO_MODER你要把第4位和第5位置成某种模式。不能直接写GPIO_MODER 0x30;因为这样会把寄存器其他位全部清零可能让其他引脚配置失效。正确的做法是GPIO_MODER | (3 4);这里3 4是把二进制11左移4位变成110000再和原来的寄存器值做按位或效果是只把第4、5位变成1其他位该怎么着还怎么着。这是嵌入式开发里极其经典的“读-改-写”模式。练习52里的b | 7其实就是这个模式的最小演示。你把b想象成一个寄存器7就是你要“打开”的几个bit非常直观。3.3 用位或实现集合与状态压缩有时候程序里需要记录“哪些选项被选中”最笨的办法是定义十几个布尔变量或者开一个数组。但如果选项数量不超过几十个用一个整数的每个bit表示一项内存占用会小得多这种思路叫“位图”或“状态压缩”。比如要记录某个人喜欢的水果#define APPLE (1 0) // 1 #define BANANA (1 1) // 2 #define ORANGE (1 2) // 4 int likes 0; likes | APPLE; // 喜欢苹果 likes | ORANGE; // 又喜欢橙子likes变成5此时likes等于5二进制101表示苹果位和橙子位为1香蕉位为0。如果还要加上香蕉likes | BANANA; // 5 | 2 7这就是简单的集合求和每个元素占一个位用|往集合里添加元素。虽然C语言标准库没有原生bitset但自己实现并不难。等你后面学到链表、哈希表再回来看这个思路会发现它依然是底层优化的重要工具。4. 实操复盘手算077|3与我的踩坑记录4.1 完整手算推导过程不要只满足于运行出结果建议你把整个运算过程在纸上写一遍。下面是练习52第一步的推导077是八进制转二进制7 - 1117 - 111所以077 - 111111十进制63。3是十进制转二进制3 - 000011为了和6位对齐前面补0。逐位按位或bit位置543210077的二进制1111113的二进制000011或结果111111结果还是111111十进制63。第二步b | 7b现在是111111。7的二进制是000111。逐位或bit位置543210b1111117的二进制000111或结果111111结果不变。这个例子恰好说明当一个数已经包含某些位再次用|置位不会导致值变大或进位。这与加法有本质差异。4.2 我在练习52上踩过的三个坑第一个坑就是没认出八进制。我一开始把077当成十进制77心算77 | 377的二进制是10011013是0000011或完是1001111等于79。然后运行程序看到输出63还以为编译器出了问题。后来查了书才反应过来前导0是八进制标志。这个教训让我以后再看到0开头的数字都会警觉。第二个坑是|和||混用。练习52的代码里全是|做完后我心情不错顺手在后面写了个if (b || 1)结果一切正常。后来想把它改成位运算风格写成了if (b | 1)也正常因为非零。但问题在于一旦表达式结果等于0两种写法就会暴露区别。最典型的是判断某个位是否为0时你可能想写if ((b 4) 0)结果少写一对括号变成if (b 4 0)。优先级高于实际上成了b (4 0)结果可能完全不对。这类问题在C语言里非常常见只能靠多写多坑来记住。第三个坑是复合赋值运算符的理解。b | 7看起来像“把b或上7再赋给b”但很多人会下意识把它和b b || 7搞混。写代码时如果旁边正好有一个||很容易复制错。我的建议是在初学阶段尽量不要在同一个表达式里混用按位运算符和逻辑运算符。看到|就把它拆成b b | 7手算一遍确认没有歧义再往下写。4.3 扩展练习写一个置位函数做完原题我建议你马上写一个自己的工具函数把任意整数的第n位设置成1。函数签名可以这样int set_bit(int value, int n) { return value | (1 n); }调用效果#include stdio.h int set_bit(int value, int n) { return value | (1 n); } int main() { int x 0; x set_bit(x, 0); // 第0位置1得到1 x set_bit(x, 3); // 第3位置1得到9 printf(x %d\n, x); // 输出9二进制1001 return 0; }1 n的意思是把1左移n位生成一个只有第n位为1的数再和原值做或运算就能安全地把第n位置1。这是练习52的延伸也是后面所有位运算题的通用工具。如果你能独立写出set_bit、clear_bit、get_bit三个函数这一道题的练习价值就翻倍了。5. 做完练习52接下来怎么走5.1 趁热打铁刷完位运算小专题练习52在《经典100例》里不是孤立的后面紧跟着几个直接相关的练习。我强烈建议你不要跳题按顺序做练习53学习使用按位异或^。异或的特点是“相同为0不同为1”常用来做交换两个变量、数据加密、奇偶校验。和|放在一起对比会更容易分清楚。练习54取一个整数a从右端开始的4~7位。这道题要综合运用和是练习52之后最好的进阶训练。练习55学习使用按位取反~。取反配合可以实现“清零某些位”比如flags ~READ就是移除读权限。这几个题目做完你基本就掌握了C语言位运算的四大金刚。到时候再回头看练习52会发现它只是入了个门但正是这个门把很多没耐心的人挡在了后面。5.2 常见问题速查表常见疑问答案077为什么等于63以0开头是八进制077等于7*8763。0773为什么输出63而不是77b 7和b b和用还是用来添加标志1 n是什么把1左移n位生成一个只有第n位为1的整数常配合位运算会影响程序可读性吗会所以要用#define或const给常量命名并写注释。这张表里的问题基本上都是我在学习群里常见到的问题。刷题遇到报错时先对照表格自查一遍能省很多时间。5.3 我的个人体会说实话练习52是我整个100例里印象最深的一题。不是因为代码多难而是它让我第一次意识到计算机里的“数”不是抽象的大小而是一串看得见的bit。之前做数学题时a 077这种写法我可能随手改成a 77但经过这题之后我开始养成了“看到数字先想进制”的习惯。我也建议你把这道题当成一个转折点。前面50题练的是“我会用C语言写程序”从这道题开始你要追求的是“我理解程序在机器里是怎么跑的”。按位或只是很小的一步但这一步跨过去后面学指针、内存、文件操作都会更顺。练到这里的你已经比大多数只写数学题的新手更接近底层了。慢慢来把手算二进制的过程多走几遍收获会比跑通代码大得多。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

汽车电子嵌入式开发与测试全解析:从ECU到UDS诊断 2026/9/28 19:20:09

汽车电子嵌入式开发与测试全解析:从ECU到UDS诊断

1. 汽车电子到底「大」在哪里做汽车电子开发这几年,最常被问的一句话是:“你们做的是不是修车?”每次都要解释半天——修车是修故障车,我们做的是在车还没造出来之前,让那些藏在车门、方向盘、发动机舱里的控制器&…

阅读更多 →
汽车电子知识体系全解析:从CAN总线到UDS诊断与Simulink建模 2026/9/28 19:20:09

汽车电子知识体系全解析:从CAN总线到UDS诊断与Simulink建模

很多人一聊汽车电子,第一反应就是“水太深”。从单片机到总线协议,从诊断规范到建模仿真,随便拎一个方向出来都够啃半年的。这篇文章我把汽车电子这个领域做个大盘点式的拆解,从嵌入式开发、总线通信、诊断协议、测试验证到Simuli…

阅读更多 →
月之暗面AI Agent开发岗一面面经:TaoToken统一Key接入Cline的settings.json配置骨架 2026/9/28 19:20:09

月之暗面AI Agent开发岗一面面经:TaoToken统一Key接入Cline的settings.json配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Arch Linux 安装操作指南 2026/9/28 19:20:02

Arch Linux 安装操作指南

Arch Linux 安装操作指南 本指南整体方案为: UEFI LUKS2 全盘加密 Btrfs 子卷 systemd-boot UKI Secure Boot TPM2.0 自动解锁 交换文件 Snapper快照工具。 关于占位符:文中 /dev/nvme0n1 为示例磁盘,实际请用 lsblk 确认后替换。…

阅读更多 →
论文降重太磨人?这几款工具实测后差距明显 2026/9/28 19:20:02

论文降重太磨人?这几款工具实测后差距明显

写论文最磨人的环节是什么?不是找文献,不是列提纲,是降重。改到凌晨三点,把"随着社会的发展"换成"伴随时代进步",重复率降了,AI检测率又飙上去。两套指标来回拉扯,论文改得…

阅读更多 →
RK3588上YOLOv5 C++部署:交叉编译与RKNN推理完整指南 2026/9/28 19:20:02

RK3588上YOLOv5 C++部署:交叉编译与RKNN推理完整指南

1. 为什么非要交叉编译:RK3588上板的第一道坎1.1 先想清楚:为什么在你的电脑上编译的C程序,板子跑不了很多读者看到"交叉编译"四个字就开始头疼,其实这个东西的底层逻辑特别简单。我们现在用香橙派5(RK3588&…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉