在C语言里,|是位或运算符,属于按位操作符的一种。它会对两个操作数的每一个二进制位独立进行计算,只要对应位上有一个是1,结果的该位就是1,只有两位都是0时结果位才是0。这和日常写条件判断时用的||逻辑或完全不同,很多初学者因为符号相似而用错地方,导致程序行为异常。

一、|运算符的基本计算规则
假设我们有两个unsigned char类型的变量,a的值为12,二进制是00001100;b的值为10,二进制是00001010。当执行a | b时,CPU会按位对齐,逐位做或运算:最末位0和0得0,倒数第二位0和1得1,以此类推,最终得到00001110,也就是十进制的14。
这种按位计算不关心数值整体的真假,只关心每一个比特。因此即使操作数是负数,在以补码形式存储的系统中,也同样按补码的每一位来处理。下面是一段简单的演示代码:
#include <stdio.h>
int main(void) {
unsigned char a = 12; // 00001100
unsigned char b = 10; // 00001010
unsigned char c = a | b; // 00001110
printf("a | b = %un", c); // 输出 14
return 0;
}
从这段代码可以看到,|直接产出一个新的整数值,而不是像逻辑或那样只返回0或1。也正因如此,它常常出现在需要拼装状态字的底层开发中。
二、|与||的核心区别
最容易混淆的是位或|和逻辑或||。逻辑或只判断左右两边表达式的真假,只要一边非零就返回1,且具备短路特性:如果左边为真,右边根本不会执行。而|两边都会完整求值,再按位计算。
如果把|误用到if条件里,比如if (x | y),只要x和y按位或后整体不是0,条件就成立,但这和if (x || y)在语义上差别很大。当x和y是标志位组合时可能碰巧没问题,但一旦涉及副作用函数调用,就会因为少了短路而出错。示例如下:
#include <stdio.h>
int foo() {
printf("foo calledn");
return 1;
}
int bar() {
printf("bar calledn");
return 2;
}
int main(void) {
// 位或:两个函数都会执行
int a = foo() | bar();
printf("a = %dn", a); // 3
// 逻辑或:bar不会执行,因为foo返回真
int b = foo() || bar();
printf("b = %dn", b); // 1
return 0;
}
上面的代码运行后会发现,使用|时foo和bar都被调用,而||只调用了foo。在性能敏感或含有修改全局状态的函数时,这种差异会引发难以排查的问题。
三、典型使用场景
在系统编程和嵌入式开发中,硬件寄存器通常用不同比特表示不同功能开关。我们可以用|把多个配置宏合并后一次性写入。例如某个控制寄存器bit0代表使能,bit1代表中断允许,相关掩码定义如下:
#define ENABLE_BIT 0x01 // 00000001 #define INT_BIT 0x02 // 00000010 // 同时打开使能和中断 reg = ENABLE_BIT | INT_BIT; // 得到 0x03
这种方式比多次赋值更清晰,也方便用&配合~来关闭某些位。另一个常见场景是权限标记,比如读、写、执行分别占用不同位,用|组合出用户权限。它的优势在于节省空间且判断迅速,只需一次按位与就能知道是否包含某项权限。
四、使用时的注意事项
首先,|的操作数必须是整数或字符等整型家族类型,不能直接用于浮点数。如果传给它float,编译器会报错或隐式转换导致结果不符合预期。其次,在表达式混合了算术运算符时,要注意优先级:|的优先级比==、+等都低,所以写if ((a | b) == 3)时括号不能省。
另外,当操作数宽度不同,比如char和int做|,会发生整型提升,短类型先被提升为int再计算。虽然多数情况不影响结果,但在处理带符号扩展的负char时要小心。养成用unsigned类型做位运算的习惯,可以减少符号位带来的意外。
总结来说,C语言里的|是按位或,逐位计算、无短路、返回整型值;它和逻辑或||在语义与行为上完全不同,写条件判断切记不要混用。