一、移位操作符:<< 左移 和 >> 右移
移位操作符用于对整数的二进制补码进行位移处理,其使用有两个关键前提:
- 仅适用于整数类型
- 不允许移动负数位(如
a << -1会导致编译错误)
1. 左移操作符 <<:高位丢弃,低位补0
左移操作的本质是将一个数的二进制表示整体向左移动指定的位数。左边超出的部分被直接舍弃,右边空出的位置全部补0。
核心规则:左移n位 = 去掉左侧n位 + 右侧填充n个0
实用规律:在不发生溢出的情况下,左移1位相当于乘以2,左移n位相当于乘以2n,且执行效率高于常规乘法运算。
num >> -1
代码示例:左移1位实战
#include <stdio.h>
int main()
{
int a = 10; // 二进制补码:00000000 00000000 00000000 00001010
int b = a << 1; // 左移1位 → 00000000...00010100(十进制20)
printf("a=%d, b=%d\n", a, b); // 输出结果:a=10, b=20
return 0;
}
注意:左移操作不会改变原变量的值,而是生成一个新的结果值。上例中变量a仍保持为10不变。
2. 右移操作符 >>:主流为算术右移
右移分为两种形式,但在实际开发中,VS、GCC等主流编译器均采用算术右移,因此只需重点掌握此方式即可。
- 算术右移:左侧根据符号位补齐(正数补0,负数补1),右侧移出部分丢弃
- 逻辑右移:左侧始终补0,右侧移出位丢弃(了解即可)
实用规律:无溢出时,右移1位 ≈ 除以2并向下取整。例如5右移1位得2,-5右移1位得-3。
代码示例1:正数右移
#include <stdio.h>
int main()
{
int a = 10; // 补码:00000000...00001010(符号位为0)
int b = a >> 1; // 算术右移1位 → 00000000...00000101(对应十进制5)
printf("a=%d, b=%d\n", a, b); // 输出结果:a=10, b=5
return 0;
}
代码示例2:负数右移(符号位决定补位)
#include <stdio.h>
int main()
{
int a = -10; // 补码:11111111...11110110(符号位为1)
int b = a >> 1; // 算术右移1位 → 11111111...11111011(对应十进制-5)
printf("a=%d, b=%d\n", a, b); // 输出结果:a=-10, b=-5
return 0;
}
二、位操作符:& | ^ ~(操控二进制位的核心工具)
位操作符直接作用于整数的二进制补码每一位,常用于嵌入式系统寄存器配置或算法性能优化场景。以下是四个基本位操作符的详细说明:
1. 按位与 &:有0则0,全1才1
按位与操作逐位比较两个操作数,只有当两个对应位都为1时,结果位才为1;否则为0,类似于“严格门禁”机制。
核心规则:逐位计算,遇0出0,全1出1
常见应用:判断奇偶性——通过 n & 1 判断最低位是否为1,若结果为1则是奇数,为0则是偶数。
n & 1
2. 按位或 |:有1则1,全0才0
按位或操作中,只要两个对应位中有一个为1,结果位即为1;仅当两位置均为0时,结果才为0。
核心规则:逐位运算,见1出1,全0出0
典型用途:设置特定位为1,比如启用某个标志位。
代码示例:按位或运算
#include <stdio.h>
int main()
{
int a = 5; // 二进制:0101
int b = 3; // 二进制:0011
int c = a | b; // 结果:0111 → 十进制7
printf("a|%d = %d\n", b, c); // 输出:5|3 = 7
return 0;
}
3. 按位异或 ^:相同为0,相异为1
异或操作在两个位相同时输出0,不同时输出1,具有可逆性和交换性,是许多算法题的关键技巧。
核心特性:
- a ^ a = 0
- a ^ 0 = a
- 支持交换律和结合律
经典用法:不用临时变量交换两个整数的值。
代码示例:异或交换变量(经典面试题)
#include <stdio.h>
int main()
{
int x = 10, y = 20;
x = x ^ y;
y = x ^ y; // y = (x^y)^y = x
x = x ^ y; // x = (x^y)^x = y
printf("x=%d, y=%d\n", x, y); // 输出:x=20, y=10
return 0;
}
4. 按位取反 ~:0变1,1变0
按位取反是一元操作符,对操作数的每一位进行翻转:0变为1,1变为0。
注意点:由于涉及补码表示,~a 实际等于 -(a+1)。
代码示例:按位取反运算
#include <stdio.h>
int main()
{
int a = 5; // 二进制:0000...0101
int b = ~a; // 取反后:1111...1010 → 补码表示为-6
printf("~%d = %d\n", a, b); // 输出:~5 = -6
return 0;
}
实战练习:统计二进制中1的个数(面试高频题)
利用位操作高效统计一个整数二进制表示中1的个数,常用方法包括循环右移检测最低位,或使用 n & (n-1) 消除最右的1。
#include <stdio.h>
int count_ones(int n)
{
int count = 0;
while (n)
{
n = n & (n - 1); // 每次消除一个1
count++;
}
return count;
}
int main()
{
int num = 15; // 二进制:1111
printf("Number of 1s in %d: %d\n", num, count_ones(num)); // 输出:4
return 0;
}
三、逗号表达式:, 从左到右依次执行,取最后一个值
逗号表达式由多个子表达式组成,用逗号分隔,执行顺序为从左至右,整个表达式的值为最后一个子表达式的值。
语法结构:expr1, expr2, ..., exprN → 返回 exprN 的值
应用场景:常用于for循环中多变量控制,或简化某些复合操作。
代码示例:逗号表达式实战
#include <stdio.h>
int main()
{
int a = 1, b = 2, c;
c = (a++, b++, a + b); // 先执行a++和b++,最后返回a+b的值
printf("a=%d, b=%d, c=%d\n", a, b, c); // 输出:a=2, b=3, c=5
return 0;
}
四、下标引用与函数调用操作符:[] 和 ()
1. 下标引用操作符 []:数组访问专用
[] 是专门用于访问数组元素的操作符,其本质是基于指针的偏移运算。arr[i] 等价于 *(arr + i)。
代码示例:下标引用操作符
#include <stdio.h>
int main()
{
int arr[5] = {10, 20, 30, 40, 50};
for (int i = 0; i < 5; i++)
{
printf("arr[%d] = %d\n", i, arr[i]);
}
return 0;
}
2. 函数调用操作符 ():执行函数调用
() 是唯一能够触发函数执行的操作符,可以接受零个或多个参数,并返回函数的返回值。
代码示例:函数调用操作符
#include <stdio.h>
void greet()
{
printf("Hello from function call!\n");
}
int add(int x, int y)
{
return x + y;
}
int main()
{
greet(); // 使用()调用函数
int result = add(3, 4); // 调用add函数
printf("3 + 4 = %d\n", result);
return 0;
}
1. 按位与 &:全1为1,有0则0
按位与运算遵循“严格门禁”原则——只有两个对应位都为1时,结果才为1;只要其中任意一位是0,结果即为0。
核心规则:逐位进行逻辑与操作,全1出1,其余情况均为0
典型应用:实现指定位清零。方法是将需要置0的位与0对齐,其余位与1对齐,然后执行按位与操作即可完成清零。
num >> -1
#include <stdio.h>
int main()
{
int a = -8; // 补码形式:11111111...11111000
int b = 6; // 补码形式:00000000...00000110
int c = a & b; // 逐位与运算:每一位只要有0 → 结果为00000000...00000000(十进制0)
printf("c=%d\n", c); // 输出结果:c=0
return 0;
}
2. 按位或 |:有1则1,全0才0
按位或的操作逻辑与按位与相反,更像是“宽松放行”机制——只要两个位中有一个为1,结果就为1;仅当两者都为0时,结果才为0。
核心规则:逐位判断,存在1则输出1,仅全0时输出0
常见用途:用于将特定二进制位置1。做法是将目标位置设为1,其他位置设为0,再进行按位或操作。
#include <stdio.h>
int main()
{
int a = -8; // 补码:11111111...11111000
int b = 6; // 补码:00000000...00000110
int c = a | b; // 逐位或:出现1的位置均保留 → 得到11111111...11111110(对应十进制-2)
printf("c=%d\n", c); // 输出结果:c=-2
return 0;
}
3. 按位异或 ^:相同为0,相异为1
按位异或是位运算中的“明星操作符”,因其简洁的规则和强大的特性,广泛应用于算法设计中,尤其在不使用临时变量交换数值等问题上表现突出。
核心规则:逐位比较,若两数相同则结果为0,不同则为1
三大关键性质(必须掌握):
a ^ a = 0
a ^ 0 = a
a ^ b = b ^ a
高频应用场景:无需额外变量即可交换两个整型值,且无溢出风险,比加减法更安全可靠。
#include <stdio.h>
int main()
{
int a = 3, b = 5;
printf("交换前:a=%d, b=%d\n", a, b); // 初始状态:a=3, b=5
a = a ^ b; // 第一步:a 存储 a 和 b 的异或值
b = a ^ b; // 第二步:b = (a^b)^b = a,恢复原 a 值
a = a ^ b; // 第三步:a = (a^b)^a = b,恢复原 b 值
printf("交换后:a=%d, b=%d\n", a, b); // 最终:a=5, b=3
return 0;
}
4. 按位取反 ~:0变1,1变0
按位取反是对一个数的二进制补码表示中每一位(包括符号位)进行全面反转的操作,即0变为1,1变为0,因此被称为“反转大师”。
核心规则:对每一位执行翻转操作,0→1,1→0,符号位同样参与运算
重要提示:由于符号位也会被反转,正数取反后通常变成负数(除非原数为-1),而负数取反也可能变为正数,但绝大多数情况下结果为负。
#include <stdio.h>
int main()
{
int a = -1; // 补码:11111111...11111111(全为1)
int b = ~a; // 取反后:00000000...00000000(对应十进制0)
printf("b=%d\n", b); // 输出结果:b=0
return 0;
}
实战练习:统计整数二进制中1的个数(面试高频题)
利用 n & (n - 1) 的技巧,每次运算可以消除n的二进制表示中最右侧的一个1,循环执行直到n为0,循环次数即为1的个数,效率极高。
n & (n-1)
#include <stdio.h>
int main()
{
int n = 0, count = 0;
printf("请输入一个整数:");
scanf("%d", &n);
while (n) {
n = n & (n - 1); // 每次清除最右边的1
count++;
}
printf("二进制中1的个数:%d\n", count);
return 0;
}
示例:输入15(二进制1111),循环4次输出4;输入10(二进制1010),循环2次输出2,准确高效!
三、逗号表达式:, 从左到右依次执行,结果取最后一个
逗号表达式是一种常用于简化代码结构的语法特性,它允许将多个表达式串联在一起,按照顺序逐一执行,但整个表达式的最终值只取决于最后一个子表达式的返回值。
核心规则:所有左侧表达式都会被执行,但整体表达式的结果等于最后一个表达式的值
常用场景:合并多个操作为单一表达式,例如在for循环中同时更新多个变量,或简化条件判断语句
#include <stdio.h>
int main()
{
int a = 1, b = 2;
// 逗号表达式示例:依次执行 a>b, a=b+10, a, b=a+1,最终结果为最后一个表达式 b=a+1 的值
int c = (a > b, a = b + 10, a, b = a + 1);
printf("c=%d\n", c); // 输出 c 的值为 a+1 后的结果
return 0;
}
int c = (a > b, a = b + 10, a, b = a + 1);
// 执行流程:1. 判断 a>b(结果为假);2. 执行 a = b + 10 得 a=12;3. 取 a 的值(此处被忽略);4. 执行 b = a + 1 得 b=13 → 最终 c = 13
printf("c=%d\n", c); // 输出结果:c=13
// 简化循环条件写法:先执行赋值与计数,再进行判断
int x = 0;
while (x = get_val(), count_val(x), x > 0) {
// 此处进行具体业务逻辑处理
}
return 0;
注意:逗号表达式外层的括号不可省略!若省略可能导致因运算符优先级问题引发逻辑错误,务必小心使用。
四、下标引用操作符与函数调用操作符:[] 和 ()
这两个符号我们每天都在使用,但你可能从未意识到它们也是C语言中正式的操作符。它们都属于多操作数操作符,且操作数的数量并不固定。
1. 下标引用操作符 [] —— 数组访问的核心工具
用于访问数组元素的 [] 就是下标引用操作符。它的两个操作数分别是数组名(如 arr)和下标值(如 i),基本形式如下:
arr[i]
其本质原理基于指针运算:
arr[i] = *(arr + i)
(数组名代表首元素地址,arr+i 指向第 i 个元素的地址,解引用后即可获取对应值)
关键提醒:数组下标从 0 开始计数!对于一个长度为10的数组:
arr[10]
其合法下标范围是 0 到 9,访问越界将导致未定义行为,甚至程序崩溃。
代码示例:下标引用操作符的使用
#include <stdio.h>
int main()
{
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
printf("%d\n", arr[4]); // 使用操作符 [],操作数为 arr 和 4 → 访问第5个元素(值为5)
printf("%d\n", *(arr + 4)); // 等价于 arr[4] → 输出结果同样为5
return 0;
}
[]
2. 函数调用操作符 () —— 实现函数执行的关键
当我们调用函数时使用的圆括号 (),正是函数调用操作符。它至少有一个操作数——函数名,后续可接零个或多个参数(以逗号分隔)。
核心规则:
函数名(参数1, 参数2, ...)
即操作数包括:函数名 + 所有传入的实际参数
示例说明:
printf("hi"):该调用的操作数为 printf 和字符串 "hi"
add(3,5):该调用的操作数为 add、3 和 5
代码示例:函数调用操作符的应用
#include <stdio.h>
// 定义加法函数
int add(int x, int y) {
return x + y;
}
// 定义无参函数
void test() {
printf("函数调用成功?\n");
}
int main()
{
printf("Hello C!\n"); // 操作数:printf 与 "Hello C!" → 输出 Hello C!
int c = add(3, 5); // 操作数:add、3、5 → c 的值为 8
test(); // 操作数:test(无参数)→ 输出提示信息
printf("3+5=%d\n", c); // 输出最终结果:3+5=8
return 0;
}
()
总结回顾
至此,你已经掌握了移位操作符、位操作符(这两者极为重要,建议多加练习)、逗号表达式,以及数组下标引用和函数调用操作符。这些知识并非仅限理论——
- 移位与位操作在嵌入式开发、算法优化中有广泛应用;
- 逗号表达式能有效简化复杂语句结构;
- 下标和函数调用操作符则是日常编程中最基础也最频繁使用的工具。
接下来的内容将更加实用:我们将深入讲解结构体成员访问操作符
.
和
->
(处理结构体数据不可或缺),以及操作符的优先级与结合性规则(避免常见陷阱),还有表达式求值过程中的典型误区(许多开发者在此栽跟头)。敬请期待后续内容!

雷达卡


京公网安备 11010802022788号







