C 语言操作符通关笔记:底层逻辑与实战技巧

📅 2026/7/14 7:39:16 👁️ 阅读次数 📝 编程学习
C 语言操作符通关笔记:底层逻辑与实战技巧

操作符核心知识点梳理

一、核心定位

操作符是编程的 “基础工具”,贯穿变量运算、逻辑判断、代码简化、内存访问的全过程,本次复习重点是理清分类、吃透用法、掌握底层逻辑、规避常踩坑点

回顾编程知识点时,发现自身对不少操作符的用法仍有混淆,尤其是位操作、移位操作的底层逻辑掌握不扎实,还有操作符优先级、表达式求值经常出错,因此特意梳理这份复习笔记,吃透二进制及位操作相关底层知识,掌握操作符属性及表达式求值规则,为后续编程实战(表达式编写、bug 排查、底层优化)筑牢基础,也方便后续快速回顾。

结合自身复习节奏,全文按基础铺垫→分类梳理→重点突破→底层逻辑→属性与求值→坑点避坑→实战复盘展开,覆盖 11 个核心要点,兼顾基础与难点,以复习视角记录梳理过程和易错点,适合需要系统复习操作符的小伙伴参考。

二、基础铺垫

2.1 二进制和进制转换

  • 核心基础:计算机底层是以二进制(0、1)存储数据的,所有操作符对数据的运算,本质都是对二进制位的操作 —— 这是理解移位、位操作符的前提,需重点牢记。
  • 重点进制转换:主要梳理十进制与二进制(整数 + 小数简要梳理)、二进制与十六进制的转换方法;其中十六进制主要用于简化二进制书写,在后续位操作场景中高频使用,是重点记忆内容。
  • 复习技巧:无需死记复杂推导,重点牢记十进制转二进制除 2 取余、二进制转十进制按位加权求和,结合 1-2 个简单示例辅助记忆,比如将十进制 5 转为二进制,反复练 2 遍即可熟练掌握。
二进制转化成十进制
二进制:1 0 1 1 十进制:1*2^3 + 0*2^2 + 1*2^1 + 1*2^0 = 11
十进制转化为二进制

十进制:15 → 除 2 取余得二进制:01111→ 简化为1111(去除高位无意义 0)。

二进制转八进制

二进制每三位对应一位八进制;原因是八进制基数 8 是二进制基数 2 的 3 次方(8=2³),3 位二进制数的取值范围,能完整覆盖 1 位八进制数的所有可能,二者一一对应,转换时直接按位映射即可。示例:二进制101 010 111→ 按三位分组直接转换为八进制。

二进制转十六进制

与转八进制逻辑完全相同;核心原因是十六进制基数 16 是二进制基数 2 的 4 次方(16=2⁴)。转换规则:二进制每四位对应一位十六进制,4 位二进制数的所有取值,能完整覆盖 1 位十六进制数的全部可能,二者一一对应,转换时直接按位映射即可。

2.2 原码、反码、补码

  • 核心用途:解决计算机中负数的表示与运算问题,移位、位操作符的运算均基于补码进行,属于底层关键知识点,必须吃透,否则位运算易出错。
  • 三者定义(以整数为例):结合理解整理易懂定义,方便回顾:
    • 原码:符号位(0 正 1 负)+ 数值的二进制绝对值,简单直观但无法直接用于负数运算,这是反码、补码出现的原因。
    • 反码:正数反码 = 原码;负数反码 = 符号位不变,数值位按位取反。
    • 补码:正数补码 = 原码;负数补码 = 反码 + 1(重点);计算机实际存储和运算的是补码,所有整数运算都要基于补码分析。

易错点:负数补码转原码易出错,总结两种通用方法,可交叉验证:

  1. 补码 - 1 得反码,反码按位取反得原码;
  2. 补码直接按位取反 + 1 得原码。

无符号数:无符号位,每一位都是数值位,原码、反码、补码的二进制表示完全相同。

三、操作符整体分类

核心原则:按功能用途分类,每个类别明确「核心作用 + 典型代表 + 使用场景」,拒绝孤立记忆,衔接底层基础知识点,确保知识串联而非零散记忆。

3.1 移位操作符(单独分类,重点突破薄弱点)

  • 核心定义:操作二进制位,将数据的二进制补码位向左 / 向右移动指定位数;仅适用于整数,浮点数使用会报错,运算基于补码进行。
  • 核心代表及用法:结合示例记忆,避免混淆:
    • 左移<<:左边舍弃,右边补 0;等价于整数乘以 2 的 n 次方,效率高于乘法,无符号 / 有符号整数通用该规则。示例代码:
      #include <stdio.h> int main() { int num = 4; int n = num << 1; // 仅返回移位结果,不修改num本身 printf("n = %d\n", n); // 输出8 printf("num = %d\n", num); // 输出4,原值不变 // 若要修改原值,写num = num << 1 或 num <<= 1 return 0; }
    • 右移>>:分两种实现,主流编译器默认算术右移
      1. 算术右移(主流):左边补符号位,右边舍弃;
      2. 逻辑右移:左边补 0,右边舍弃。需注意编译器差异,避免结果异常。

易错点

  1. 移位位数不能为负数(C 语言标准未定义,不同编译器行为不一致);
  2. 有符号数右移的符号位补位规则需牢记,否则易出现运算结果异常。

建议:自行推导int a=4(二进制 100)左移 1 位、int b=-4右移 1 位的补码运算过程,掌握移位底层逻辑。

3.2 位操作符(&、|、^、~)

:操作数必须是整数,运算基于补码进行。

  • 核心定义:直接操作数据的二进制补码位,仅适用于整数;常用于底层优化、进制转换、状态判断等场景。
  • 核心代表及用法
    1. &(按位与):对应位均为 1,结果为 1;否则为 0;常用用途:判断某一位是否为 1、清零指定位。
    2. |(按位或):对应位有一个为 1,结果为 1;否则为 0;常用用途:置 1 指定位。
    3. ^(按位异或):重点复习操作符;对应位不同为 1,相同为 0;核心特性a^a=0a^0=a;基于特性可实现交换两个数、翻转指定位(结尾有重点补充)无临时变量交换两个整数代码:
      #include <stdio.h> int main() { int a = 10; int b = 20; a = a ^ b; b = a ^ b; a = a ^ b; printf("a = %d b = %d\n", a, b); // 输出a=20 b=10 return 0; }
    4. ~(按位非):对应位按位取反(0 变 1,1 变 0);注意:负数补码取反后符号位会变化,运算时需关注符号位。

关键区分:易混淆位操作符(&、|)与逻辑操作符(&&、||),前者操作二进制位,后者操作逻辑值;比如判断两个数是否都为非 0,应使用&&而非&

3.3 单目操作符

  • 核心定义:只有一个操作数,用于对单个数据进行运算;易和双目操作符混淆,需逐一梳理。
  • 核心代表及用法
    1. 算术类++(前置 / 后置自增)、--(前置 / 后置自减)、+(正号,可省略)、-(负号,对补码取反 + 1);前置 / 后置自增自减为重点复习内容,后续单独深度拆解。
    2. 逻辑类!(逻辑非);规则:将非 0 值转为 0,0 转为 1;示例:!5=0!0=1
    3. 取值 / 取地址&(取地址,获取变量内存地址)、*(取值,通过地址获取变量值);需结合指针基础理解,避免误用。
    4. 强制转换(类型)(将一个数据强制转为指定类型);注意:浮点数转整数会舍弃小数部分,存在精度丢失问题,需谨慎使用。
    5. sizeof:计算变量 / 类型所占内存大小(单位:字节);易混点:与strlen区分 ——sizeof计算内存大小,strlen计算字符串长度(不包含结束符'\0')。

3.4 逗号表达式(单独分类,明确用法,记录易错点)

  • 核心定义:用逗号,连接多个表达式,从左到右依次执行,整个表达式的结果是最后一个表达式的值
  • 核心特性:优先级最低,常用于简化代码(如 for 循环的初始化 / 更新部分)。
  • 示例
    int a=1, b=2; int c = (a++, b++, a+b); // 执行顺序:a=2→b=3→c=5,最终结果为5
  • 易错点:易误将前面表达式的结果作为最终结果;前面的表达式仅执行,不影响最终结果,最终结果仅由最后一个表达式决定。

3.5 访问类操作符(下标 + 函数调用 + 结构成员,结合实战梳理)

3.5.1 下标访问[]
  • 用于访问数组元素,格式:数组名[下标]核心知识点:本质等价于*(数组名+下标),结合指针基础理解数组访问逻辑。
  • 易错点:下标从 0 开始,编译器不检查下标越界,运行时易出现异常,需注意判断下标范围。
3.5.2 函数调用()
  • 用于调用函数,格式:函数名(参数列表);参数列表可为空(无参函数),但必须保留括号,遗漏会导致编译报错。
3.5.3 结构成员访问操作符(两种形式)

两种形式易混淆,结合示例记忆,核心用于访问自定义结构体的成员属性;先明确结构体的基础概念

结构体基础
  • 核心作用:把不同类型的变量打包成一个整体,解决普通变量只能单独存储单一类型数据的问题;比如描述学生(学号 int、姓名 char []、成绩 float),可将多个不同类型属性整合为 “学生类型”,方便管理。
  • 定义原因:实际编程中描述的事物均为多属性,普通变量会让属性相互独立,无法体现 “属于同一事物” 的关联,结构体可解决该问题。
  • 核心定义:关键字struct,分两步:先声明结构体类型(告诉编译器自定义类型的结构),再定义该类型的变量(与int a定义格式一致)。

结构体完整示例代码

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> // 字符串赋值需用到strcpy函数 // 声明书本结构体(用于嵌套) struct Book { // 书本属性 char bname[20]; // 书名 float price; // 价格 }B1; // 声明类型同时定义全局变量B1 // 声明学生结构体(嵌套Book结构体) struct MyStruct { // 学生属性 char name[20]; // 名字 short age; // 年龄 char sex[10]; // 性别 char id[20]; // 学号 struct Book b; // 支持嵌套结构体 }A1; // 声明类型同时定义全局变量A1 int main() { // 定义局部结构体变量并整体初始化(成员顺序需与声明一致) struct MyStruct A2 = { "zhangsan",19,"男","2025345",{"C语言",22.0}}; printf("学生A2的信息:\n"); printf("A2学生的书本名称:%s\n", A2.b.bname); // 嵌套结构体成员访问:变量.嵌套结构体.成员 // 给全局结构体变量A1赋值并读取 printf("请输入A1学生信息:\n"); printf("姓名:"); scanf("%s", A1.name); // 字符数组直接传名,无需& printf("年龄:"); scanf("%hd", &A1.age); // short类型用%hd格式符 printf("性别:"); scanf("%s", A1.sex); printf("A1的性别:%s\n",A1.sex); return 0; }

结构体使用关键注意点

  1. 字符串成员必须用字符数组(如char name[20]),新手阶段暂不使用char *name(涉及指针);
  2. 字符数组赋值不能直接用=(如A1.name = "张三"是错误的),需用strcpy函数,且必须包含头文件#include <string.h>
结构体成员访问操作符详解
  1. .(点操作符):用于结构体变量访问成员;格式:结构体变量.成员名;示例:stu.nameA2.b.price
  2. ->(箭头操作符):用于结构体指针访问成员;格式:结构体指针->成员名等价于(*结构体指针).成员名;示例:p->name等价于(*p).name(p 为结构体指针)。

3.6 其他常用操作符(补充完善,避免遗漏)

  1. 赋值操作符=(基础赋值)、+=-=*=/=%=&=|=^=<<=>>=(复合赋值);等价规则:复合赋值 = 运算 + 赋值,如a += b等价于a = a + b,简化代码书写。
  2. 关系操作符><>=<===(等于判断)、!=(不等于);作用:判断大小 / 相等关系,返回布尔值(0 或非 0),用于条件判断;易混点==(判断相等)与=(赋值),后续重点梳理。
  3. 逻辑操作符&&(逻辑与)、||(逻辑或);用于组合条件判断,具有短路求值特性(可简化代码、避免无效运算),后续单独拆解。
  4. 三元运算符condition ? expr1 : expr2;用于简化简单的 if-else 语句;规则:condition 为真,执行 expr1;为假执行 expr2;示例:求两数最大值max = a > b ? a : b

四、操作符的核心属性(重点补充:优先级、结合性)

核心意义:优先级和结合性直接决定表达式的运算顺序,是表达式求值经常出错的核心原因;复习重点是牢记高频优先级 + 结合性规则,避免写错表达式。

4.1 优先级

  • 定义:多个操作符共存于一个表达式时,优先级高的先运算;类比数学中的 “先乘除后加减”。
  • 高频优先级梳理(从高到低):
    1. 最高优先级:()(括号,可强制改变运算顺序)、[]->.(访问类操作符);技巧:不确定运算顺序时,加括号即可,安全且清晰。
    2. 核心顺序:单目操作符 > 算术操作符 > 关系操作符 > 逻辑操作符 > 赋值操作符 > 逗号表达式(最低)
  • 易错点
    1. 单目操作符优先级高于算术操作符(如!a + b,先算!a再算加法);
    2. 赋值操作符优先级低于关系操作符(如a = b == c,先算b==c再将结果赋值给 a)。

4.2 结合性

  • 定义:多个优先级相同的操作符共存时,结合性决定运算方向(从左到右 / 从右到左);与优先级共同决定表达式运算顺序,缺一不可。
  • 高频结合性规则
    1. 从左到右(左结合):算术、关系、逻辑、逗号、访问类、大部分双目操作符;示例:a + b + ca > b > c,按从左到右运算。
    2. 从右到左(右结合):单目操作符、赋值操作符、三元运算符;示例:a = b = c(先算b=c再算a=b)、+++a(等价于++(+a))。

实用技巧:不确定运算顺序时,直接加括号(()优先级最高),避免依赖优先级和结合性记忆出错,这是写代码的通用规范。

五、表达式求值

核心:基于操作符的优先级、结合性,结合底层逻辑(补码运算),分析表达式的执行顺序和结果;重点解决 “求值异常” 问题,避免逻辑错误。

5.1求值的三大依据

  1. 操作符优先级;
  2. 操作符结合性;
  3. 操作数的求值顺序;重点:多数操作符未定义操作数求值顺序(如a++ + ++a),不同编译器运算结果不同,需避免写出此类代码。

5.2重点梳理不同类型表达式求值

  1. 含单目操作符的表达式:结合优先级、结合性分析执行顺序;示例:++a + b,先算++a再算加法。
  2. 含位操作、移位操作的表达式:核心步骤 —— 先将操作数转为补码,再按位 / 移位规则运算,最后转回原码得到结果;需结合补码知识分步推导。
  3. 含逗号、赋值操作的表达式:明确逗号表达式 “从左到右执行,结果为最后一个表达式的值”;牢记赋值操作的右结合特性;示例:a = (b++, c++, b+c),先执行逗号表达式,再将结果赋值给 a。

5.3关键易错点

避免写出 “操作数求值顺序未定义” 的表达式(如a = ++a + 1a++ + ++a),此类代码可移植性差,不同编译器运行结果不同,极易出现 bug。

六、重点操作符深度拆解

原则:聚焦移位、位、单目、访问类等薄弱、易出错的操作符,结合底层基础和操作符属性,搭配 “用法 + 示例 + 易错点”,确保吃透核心逻辑。

6.1 移位操作符 & 位操作符深度拆解

  1. 移位操作符:补充负数移位示例(如int a=-4a>>1的补码运算过程),分步推导补码移位逻辑,明确算术右移的符号位补位规则,理解原理而非死记结果。
  2. 位操作符:重点拆解^(按位异或),梳理核心应用场景:
    • 交换两个数(无临时变量);
    • 翻转指定位;
  3. 实战延伸:位操作符的底层优化技巧,示例:判断奇数用a & 1(效率高于a % 2 == 1),因位运算是 CPU 直接支持的底层运算,速度更快。

6.2 单目操作符深度拆解(易混淆点突破)

  1. 自增自减(++/--):核心区分 ——前置先运算再使用,后置先使用再运算;示例:int a=2; int b=a++ + ++a;,不同编译器因操作数求值顺序未定义,结果不同,避免依赖固定结果写代码。
  2. sizeof 与 * 的区别:示例:int a[5];sizeof(a)为数组总大小(5*4=20 字节,int 占 4 字节),sizeof(*a)为首元素大小(4 字节),明确二者计算对象的差异。
  3. 强制转换:演示风险点 —— 精度丢失(int a=3.14;结果为 3)、指针强制转换风险((int*)100;将数值转为指针),提醒自己谨慎使用。

6.3 结构成员访问操作符拆解(贴合实战,解决混淆)

  1. 核心示例:定义学生结构体,分别用结构体变量结构体指针访问成员,验证等价关系:p->name(*p).name(p 为结构体指针)。
  2. 核心规则结构体变量用.,结构体指针用 ->,牢记无混淆。
  3. 易错点:结构体指针访问成员时误用.(如p.name),会直接编译报错,写代码时需反复检查。

七、高频坑点 & 易错点总结

核心:梳理写代码、复习时经常用错、混淆的点,每个坑点搭配 “错误示例 + 正确用法”,结合底层逻辑和操作符属性加深记忆,减少 bug。

坑点 1:混淆位操作符(&、|)与逻辑操作符(&&、||)

  • 错误示例:if(a & b)(本意判断 a、b 均非 0);
  • 正确用法:if(a && b)
  • 核心区别:前者操作二进制位,后者操作逻辑值(0 / 非 0)。

坑点 2:移位操作符的误用

  • 错误行为:移位位数为负数、忽略有符号数右移的符号位补位规则;
  • 后果:不同编译器行为不一致,运算结果异常;
  • 规避:移位位数为非负整数,有符号数右移牢记算术右移规则。

坑点 3:原码、反码、补码混淆

  • 错误行为:用原码直接进行负数运算;
  • 正确用法:所有整数运算(移位、位操作、算术运算)均基于补码进行;
  • 规避:运算前先将操作数转为补码,运算后再转回原码得到结果。

坑点 4:操作符优先级 / 结合性记忆错误

  • 错误示例:a = b + c * d误先算b+c
  • 解决方案:不确定运算顺序时,直接加括号,安全且清晰。

坑点 5:单目操作符与双目操作符混用

  • 错误示例:++a + b误先算a+b再自增;
  • 规避:牢记单目操作符优先级高于算术等双目操作符。

坑点 6:结构成员访问操作符误用

  • 错误示例:结构体指针用.(p.name)、结构体变量用 ->(stu->name);
  • 核心规则:指针用 ->,变量用.

坑点 7:逗号表达式结果误用

  • 错误示例:int c = (a++, b++);误将a++结果作为 c 的值;
  • 核心规则:逗号表达式结果为最后一个表达式的值(此例为b++)。

坑点 8:写出操作数求值顺序未定义的表达式

  • 错误示例:a++ + ++aa = ++a + 1
  • 后果:不同编译器结果不同,代码可移植性差;
  • 规避:坚决避开此类表达式,拆解为多条语句书写。

八、复习复盘 & 实战小练习

8.1 核心知识点复盘

  1. 底层基础:二进制转换、原码 / 反码 / 补码是移位、位操作符的核心前提,补码运算规则为底层运算基础,需牢记。
  2. 操作符分类:掌握移位、位、单目、访问类、逗号表达式的用法,能区分易混淆类别,快速选择合适的操作符。
  3. 核心属性:优先级、结合性决定表达式运算顺序,记住高频规则,不确定时加括号。
  4. 表达式求值:掌握求值方法,避开未定义求值顺序的陷阱,结合底层逻辑分步分析。

8.2 简单实战练习

  1. 练习 1:进制转换 + 补码运算;将十进制 - 5 转为二进制补码,计算补码左移 1 位、右移 1 位的结果,再转回十进制,检验补码和移位操作的掌握程度。
  2. 练习 2:位操作符应用;用按位异或交换两个整数,用按位与判断一个数是否为奇数,手写代码检验用法,熟悉底层优化技巧。
  3. 练习 3:单目操作符 + 表达式求值;分析int a=3; int b=++a + a-- + sizeof(a);的结果,结合优先级、结合性分步推导。
  4. 练习 4:访问类操作符;定义结构体指针,用.->两种方式访问其成员并赋值,检验结构成员访问操作符的掌握。
  5. 练习 5:优先级 / 结合性;修正表达式int a=2+3*4>5&&6;的写法,明确运算顺序并计算结果。
  6. 练习 6:坑点排查;手写一段包含位操作符、结构成员访问、表达式求值陷阱的错误代码,找出错误并修正,检验高频坑点的掌握。

九、总结

  1. 延伸思考:操作符不仅是编程的基础工具,其底层逻辑(补码、位运算)更是后续学习指针、内存优化、底层开发的核心;本次复习到位,能大幅提升后续编程效率和代码健壮性,为更难的知识点学习筑牢基础。
  2. 如果在操作符复习中也有混淆的知识点、常踩的坑,欢迎补充交流,分享位操作符、表达式求值的实用技巧,互相巩固复习、查漏补缺、共同进步。

补充:按位异或交换两个数的底层逻辑解析

要理解异或交换代码,需先掌握异或运算的3 个核心特性(交换逻辑的基础):

异或运算(^)的核心特性

异或运算规则:两个二进制位相同,结果为 0;两个二进制位不同,结果为 1(简单记:同 0 异 1)。基于规则推导的 3 个关键特性(对任意整数 x、y 都成立):

  1. 自反性x ^ x = 0(一个数和自己异或,结果一定是 0);
  2. 恒等性x ^ 0 = x(一个数和 0 异或,结果还是它自己);
  3. 交换律 / 结合律x ^ y = y ^ xx ^ y ^ z = x ^ (y ^ z)(异或的运算顺序不影响结果)。

逐行拆解交换逻辑(带变量值推导)

初始值:a = 10b = 20;用A表示初始 a (10),B表示初始 b (20),区分 “原始值” 和 “更新后的值”。

  1. 第一步a = a ^ b执行后:a = A ^ B(10^20),b 仍为原始值 B (20),未被修改;
  2. 第二步b = a ^ b代入 a 的新值:b = (A^B) ^ B→ 结合结合律 + 自反性:A ^ (B^B) = A ^ 0 = A;本质:将原始 a 的值 (10) 赋给了 b,此时 b=10(A),a=10^20(A^B);
  3. 第三步a = a ^ b代入当前 a 和 b:a = (A^B) ^ A→ 结合交换律 + 自反性:B ^ (A^A) = B ^ 0 = B;本质:将原始 b 的值 (20) 赋给了 a,此时 a=20(B),b=10(A),交换完成。

具体数值验证(十进制)

已知:10 ^ 20 = 3030 ^ 20 = 1030 ^ 10 = 20

  1. 第一步:a = 10^20 = 30→ a=30,b=20;
  2. 第二步:b = 30^20 = 10→ a=30,b=10;
  3. 第三步:a = 30^10 = 20→ a=20,b=10。

异或交换的特点

  • 优点
  1. 无临时变量:不需要定义第三个变量(如 temp),节省少量内存空间;
  2. 运算效率高:位运算是 CPU 直接支持的底层运算,比普通临时变量交换法速度更快。
  • 注意点

交换的两个变量必须是不同的内存地址!如果 a 和 b 指向同一块内存(如数组中交换同一个下标元素、a = &b),执行后变量值会变为 0(第一步a=a^b就成了x^x=0,后续步骤均为 0)。

  • 开发建议

实际开发中,临时变量交换法temp=a;a=b;b=temp)可读性更高,无内存地址限制,是更通用的方式;异或交换法仅适用于追求极致性能的场景。

拓展:位操作符经典笔试应用

n = n & (n - 1)是二进制位操作的经典技巧,核心作用:消除数字 n 的二进制补码中「最右侧的那个 1」,每执行一次消去一个 1,直到 n 变为 0。

应用 1:统计二进制中 1 的个数
方法一(基础版):

就像求一个十位数的方法一样,通过不断/10的方法,我们不断/2,直到余数为0(有局限,适用于正数)

#include <stdio.h> int main() { int a = 10; scanf("%d", &a); int sum = 0; while (a) { if (a % 2 == 1) { sum ++; } a /= 2; } printf("Sum: %d\n", sum); return 0; }

输入一个正数5(101,原码等于补码),结果为2是正确的

如果输入-1呢?

原码:1000 0000 0000 0000 0000 0000 0000 0001(最高位 1 是负号,其余是数值位)

反码:1111 1111 1111 1111 1111 1111 1111 1110(符号位不变,数值位取反)

补码:1111 1111 1111 1111 1111 1111 1111 1111(反码 + 1)

代码在逻辑和实际运行显然都是错误的!

方法二(改进局限版)

任何一个数&1得到二进制最低位,然后>>1是计算机中提取二进制每一位的经典方法,核心是通过 &1 取二进制最低位、>>1 右移舍弃最低位,循环执行就能从低位到高位依次拿到一个数的所有二进制位,不管正数还是负数(按补码处理)都适用。

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> //方法二 int main() { int a = 0; scanf("%d", &a); int count = 0; for (int i = 0; i < 32; i++) //考虑负数的情况,循环32次 { if (a & 1) //if((a & 1) == 1) { count++; } a >>= 1; } printf("Sum: %d\n", count); return 0; }

那么这样每次就要循环一遍,效率低下!

方法三(效率最高)
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> //方法三:n = n & (n-1) 消去最右侧1,循环次数=1的个数 int main() { int a = 0; scanf("%d", &a); int count = 0; while (a) { a = a & (a - 1); // 每次消去最低位的1 count++; // 消去一次则计数+1 } printf("Sum: %d\n", count); return 0; }
应用 2:判断一个数是否是 2 的次方数

核心原理:2 的次方数的二进制补码中仅有 1 个 1;若 n 是 2 的次方数,则n & (n - 1) == 0(唯一的 1 被消去,结果为 0)。判断条件if ((n & (n - 1)) == 0)→ 成立则为 2 的次方数。

拓展:二进制位置 0 / 置 1 的实现

需求:将 13 的二进制序列的第 5 位修改为 1,再改回 0(位序从 0 开始计数)。

步骤分析
  1. 13 的 32 位二进制(原 / 反 / 补码):0000 0000 ... 0000 1101
  2. 第 5 位置 1:需构造掩码B=1<<4=16(二进制0000 0000 ... 0001 0000),通过a |= B置 1(按位或可保留原有位,指定位置 1);
  3. 第 5 位改回 0:构造掩码~B(二进制1111 1111 ... 1110 1111),通过a &= ~B置 0(按位与可保留原有位,指定位置 0)。
实现代码
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { int a = 13; printf("原数为%d\n", a); // 输出13 int b = 1 << 4; // 构造第5位为1的掩码,16 a |= b; // 第5位置1 printf("第5位修改为1后为%d\n", a); // 输出29 a &= (~b); // 第5位改回0 printf("第5位改回0后为%d\n", a); // 输出13 return 0; }
运行结果

本文部分代码:Luminous/Luminousbegin - Gitee.com

我的代码仓库(练习仓库):Luminous/Luminousbegin - Gitee.com

完......