文章目录
- 一,算术运算符
- 二,自增运算符与自减运算符
- 三,关系运算符
- 四,逻辑运算符
- 逻辑与(`&&`)
- 逻辑或(`||`)
- 逻辑非(`!`)
- 最佳实践:行为单一原则
- 五,位运算符
- 按位与(`&`)
- 按位或(`|`)
- 按位异或(`^`)
- 按位取反(`~`)
- 左移(`<<`)
- 右移(`>>`)
- 六,逗号运算符
- 七,运算优先级
在C语言编程中,运算符是构建程序逻辑的基础,它们用于执行计算、比较值、控制数据流等操作。
C语言中的运算符有50多种,我们可以分门别类的学习。
一,算术运算符
算术运算符用于执行基本的数学运算,如加、减、乘、除和取模(求余数)。
+
正值运算符(一元运算符)-
负值运算符(一元运算符)+
加法(二元运算符)-
减法(二元运算符)*
乘法/
除法(结果为浮点数,如果两边都是整数,则结果向下取整)%
取模(求余数)
-
+ 正值运算符(一元运算符)
该运算符用于改变数值的符号,当应用于一个正数或零时,没有实际效果。
int x = +5; // x 的值为 5,这里 + 运算符没有实际改变数值。
正值运算符通常可以省略,但在某些上下文中使用它能增加代码的清晰度。
如果在一个值为负数的变量前面加上正号会有什么效果呢?int y = -5; int x = +y;
变量x的值还是-5,因为
+
不会改变正负值。 -
- 负值运算符(一元运算符)
用于改变数值的符号,将一个数变为它的负数。
int y = -10; // y 的值为 -10。
负值运算符是改变符号的最直接方式,尤其在数学运算和条件判断中常见。
int y = -5; int x = -y;
变量x的值是5,因为
-
会改变正负值,这是与+
有区别的地方。 -
+ 加法(二元运算符)
将两个数值相加。
int a = 3; int b = 4; int sum = a + b; // sum 的值为 7。
-
- 减法(二元运算符)
从一个数值中减去另一个数值。
int c = 10; int d = 5; int difference = c - d; // difference 的值为 5。
-
*
乘法将两个数值相乘。
int e = 3; int f = 4; int product = e * f; // product 的值为 12。
-
/
除法(结果为浮点数,如果两边都是整数,则结果向下取整)进行除法运算,如果除数和被除数都是整数,结果将是整数(向下取整);如果其中至少有一个是浮点数,则结果为浮点数。
float g = 6.0 / 4; // g 的值为 1.5,因为其中一个数为浮点数。 int h = 6 / 4; // h 的值为 1,整数除法向下取整。
下面是另一个例子。
int score = 5; score = (score / 20) * 100;
上面的代码,你可能觉得经过运算,score会等于25,但是实际上score等于0。这是因为score / 20是整除,会得到一个整数值0,所以乘以100后得到的也是0。
为了得到预想的结果,可以将除数20改成20.0,让整除变成浮点数除法。
score = (score / 20.0) * 100;
-
%
取模(求余数)返回除法的余数。
int i = 6 % 4; // i 的值为 2,因为 6 除以 4 的余数是 2。
取模运算符只能用于整数,结果的符号取决于被模数(左边的数)的符号。
-
赋值运算的简写形式
如果变量对自身的值进行算术运算,C 语言提供了简写形式,允许将赋值运算符和算术运算符结合成一个运算符。
+= -= *= /= %=
下面是一些例子。
i += 3; // 等同于 i = i + 3 i -= 8; // 等同于 i = i - 8 i *= 9; // 等同于 i = i * 9 i /= 2; // 等同于 i = i / 2 i %= 5; // 等同于 i = i % 5
二,自增运算符与自减运算符
自增(++
)和自减(--
)运算符用于增加或减少变量的值,分为前置和后置两种形式,影响着运算符所在表达式的值以及变量改变的时机。
- 前置:先改变变量的值,再进行其他运算。
- 后置:先参与其他运算,之后改变变量的值。
#include <stdio.h>
int main() {
int x = 5;
printf("前置自增: %d\n", ++x); // 输出: 6,x现在是6
printf("后置自增: %d\n", x++); // 输出: 6,但x现在变为7
printf("x现在的值: %d\n", x); // 输出: 7
return 0;
}
自增运算符和自减运算符的位置对结果有影响,为了可读性,建议单独使用,不要与其他运算符在一个表达式中混合使用。
int i = 42;
int j;
j = (i++ + 10);
j = (++i + 10)
可以改用下面的写法。
j = (i + 10);
i++;
i++;
j = (i + 10);
虽然多了两行代码,但是可读性强,便于维护。
三,关系运算符
关系运算符用于比较两个操作数的大小,返回布尔值(真或假,在C中用1表示真,0表示假)。
==
等于!=
不等于<
小于>
大于<=
小于等于>=
大于等于
#include <stdio.h>
int main() {
int m = 5, n = 3;
printf("m == n: %d\n", m == n); // 输出: 0 (假)
printf("m != n: %d\n", m != n); // 输出: 1 (真)
printf("m < n: %d\n", m < n); // 输出: 0 (假)
printf("m > n: %d\n", m > n); // 输出: 1 (真)
return 0;
}
注意,C 语言中,0表示伪,所有非零值表示真。
使用关系运算符的表达式的计算结果是整数,要么是0,要么是1:
- 1,表示真(true)
- 0,表示假(false)
int main()
{
int num = 100;
double num2 = 100;
printf("num=%lf\n", num);
printf("num2=%lf\n", num2);
return 0;
}
关系表达式常用于if
或while
结构。
if (x == 6) {
printf("x is 6.\n");
}
注意,相等运算符==
与赋值运算符=
是两个不一样的运算符,不要混淆。有时候,可能会不小心写出下面的代码,它可以运行,但很容易出现意料之外的结果。
if (x = 6) {
printf("do something...");
}
上面示例中,原意是x == 6
,但是不小心写成x = 6
。C语言中,表达式是有返回结果的,x = 6
这个式子返回值为6,根据非0为真,该判断条件始终为真,不合原意。
为了防止出现这种错误,可将变量写在等号的右边。
if (6 == x) {
printf("do something...");
}
如果把==
误写成=
,编译器就会报错。
/* 语法错误 */
if (6 = x) {
printf("do something...");
}
另一个需要避免的错误是,多个关系运算符不宜连用。
i < j < k
上面示例中,连续使用两个小于运算符。
这是合法表达式,不会报错,但是通常达不到想要的结果,即不是保证变量j
的值在i
和k
之间。因为关系运算符是从左到右计算,所以实际执行的是下面的表达式。
(i < j) < k
上面式子中,i < j
返回0或1,所以最终是0或1与变量k进行比较。
如果想要判断变量j的值是否在i和k之间,应该使用下面的写法。
i < j && j < k
四,逻辑运算符
逻辑运算符用于组合布尔表达式,判断多个条件是否同时满足。
&&
逻辑与||
逻辑或!
逻辑非
逻辑与(&&
)
&&
运算符用于连接两个布尔表达式。当且仅当两边的表达式都为真(非零值)时,整个表达式才为真(非零值)。
int a = 5, b = 10;
if (a > 0 && b > 0) {
printf("Both a and b are positive.\n");
}
在这个例子中,因为a
和b
都大于0,所以a > 0 && b > 0
的判断结果为真,会执行打印语句。
短路特性:如果左边的表达式为假,右边的表达式将不会被评估,因为整个表达式的结果已经确定为假。
int a = -5, b = 10;
if (a > 0 && b > 0) {
printf("Both a and b are positive.\n");
}
上面代码中,因为a = -5
,if
判断中 a > 0
为false
,整个逻辑与的结果肯定是false
,右边的表达式无需计算。
逻辑或(||
)
||
运算符同样用于连接两个布尔表达式。如果任意一边的表达式为真,整个表达式就为真。
int x = 3, y = 7;
if (x < 0 || y > 5) {
printf("At least one condition is true.\n");
}
虽然x
大于0,使得左边的条件假,但是右边的表达式y > 5
为真,整体表达式就为真,因此会执行打印语句。
||
运算符同样具有短路特性
,如果左边的表达式为真,整体表达式为真,右边的表达式将不会被执行。
int x = -3, y = 7;
if (x < 0 || y > 5) {
printf("At least one condition is true.\n");
}
x = -3
,逻辑或左侧表达式x < 0
为真,已经可以断定整体表达式为真,故而不需要检查y > 5
。
逻辑非(!
)
!
是一个一元运算符,用于取一个布尔表达式的相反值。如果表达式为真,则结果为假;反之亦然。
int z = 0;
if (!z) {
printf("z is zero or false.\n");
}
在这个例子中,因为z
的值为0,被视为逻辑假,所以!z
的结果为真,会执行打印语句。
逻辑非运算符仅作用于紧随其后的表达式,优先级高于逻辑与和逻辑或。
最佳实践:行为单一原则
写代码时,最佳实践是让代码的行为符合预期,遵守行为单一原则,即表达式的行为是明确的、单一的。
如下代码,逻辑运算符的执行顺序是先左后右,x++ < 10
这个表达式具备两个效果:
- ① x < 10
- ② x = x + 1
if((x++ < 10) && (x + y < 20))
执行左侧表达式后,变量x的值就已经变了。执行右侧表达式的时候,是用新的值在计算,这极有可能不是我们的真实意图。
逻辑运算符在编写条件控制语句(如if
、while
)时特别有用,通过它们可以构建复杂的逻辑判断,实现精确的程序流程控制。
五,位运算符
位运算符对整型数据的二进制位进行操作。
&
按位与|
按位或^
按位异或~
按位取反<<
左移>>
右移
按位与(&
)
对两个操作数的每一位进行逻辑与操作。如果两个位都是1,则结果位为1;否则为0。
int a = 0b1101; // 13 in decimal
int b = 0b1011; // 11 in decimal
int result = a & b; // 0b1001, which is 9 in decimal
在这个例子中,a
和b
的二进制表示分别是1101和1011,按位与操作后得到的是1001,即十进制的9。
按位或(|
)
对两个操作数的每一位进行逻辑或操作。如果至少有一个位是1,则结果位为1;否则为0。
int c = 0b1010; // 10 in decimal
int d = 0b0110; // 6 in decimal
int result = c | d; // 0b1110, which is 14 in decimal
这里,c
和d
按位或后得到1110,即十进制的14。
按位异或(^
)
对两个操作数的每一位进行逻辑异或操作。如果两个位不同,则结果位为1;否则为0。
int e = 0b1100; // 12 in decimal
int f = 0b1010; // 10 in decimal
int result = e ^ f; // 0b0110, which is 6 in decimal
这里,e
和f
的对应位上不相同的位产生1,最终得到10的二进制表示0110。
按位取反(~
)
对一个操作数的每一位进行逻辑非操作。即,0变成1,1变成0。
int g = 0b1010; // 10 in decimal
int result = ~g; // 0b0101, which is -11 in decimal for signed int
注意,result
是按位取反后的值,对于有符号整数,最高位为1表示负数,因此这里解释为-11。
左移(<<
)
将一个操作数的所有位向左移动指定位数,右侧空出的位用0填充。
int h = 0b0001; // 1 in decimal
int result = h << 2; // 0b0100, which is 4 in decimal
这里,h
左移两位后,原来的1变成了100,即十进制的4。
左移运算符相当于将运算数乘以2
的指定次方,比如左移2
位相当于乘以4
(2
的2次方)。
上述案例左移2
位,相当于原数1
乘以4
,所以结果是4
。
当有符号整数进行位运算“左移(<<)”时,规则是“符号位不变,移出位丢弃,空出位补0
”。即正整数左移N位时低位依次填充N个0,负整数左移N位时低位依次填充N个0。
例如:
0000 0010 << 1 = 0000 0100
0000 1010 << 2 = 0010 1000
1000 0010 << 1 = 1000 0100
1000 1010 << 3 = 1001 0000
左移不改变有符号整数的符号位。
注意:左移的位数超过该数值类型的最大位数时,编译器会用左移的位数去模类型的最大位数,然后按余数进行移位。
如:
int i=1; //设int为32位
i=i<<33; // 33%32=1,相当于 i=i<<1
int main()
{
int num = 1;
int num2 = num << 33;
printf("左移前num=%d\n", num);
printf("num左移33位后num2=%d\n", num2);
return 0;
}
右移(>>
)
将一个操作数的所有位向右移动指定位数,对于有符号整数,左侧空出的位用符号位填充(即保持符号不变),对于无符号整数,用0填充。
int i = 0b1010; // 10 in decimal
int result = i >> 1; // 0b0101, which is 5 in decimal
这里,i
右移一位后,变成了0101,即十进制的5。
右移运算符相当于将运算数除以2
的指定次方,比如右移1
位就相当于除以2
(2
的1
次方),所以上述示例右移一位,相当于10
除以2
,结果是5
。
但是,对于负数,右移位操作存在一个左移位操作不曾面临的问题:从左边移入的位,可以选择两种方案。
- ①一种是逻辑移位,左边移入的位用0填充;
- ②一种是算数移位,左边移入的位由原先该值的符号位决定,符号位为1则移入的位均为1,符号位为0则移入的为均为0,这样就能够保持原数的正负形式不变。例如:
0000 0010 >> 1 = 0000 0001
0000 1010 >> 2 = 0000 0010
1000 0010 >> 1 = 1100 0001
1000 1010 >> 3 = 1111 0001
对于有符号值,到底是采用逻辑移位还是算数移位取决与编译器,不能保证所有的编译器采用同样的方式。
因此,最佳实践是右移运算符不用于有符号数
。
六,逗号运算符
逗号运算符,
用于连接多个表达式,从左到右依次计算每个表达式,整个表达式的值为最后一个表达式的值。
#include <stdio.h>
int main() {
int x = (2 + 3, 4 - 1, 5 * 2);
printf("结果: %d\n", x); // 输出: 10,因为最后一个表达式5*2的结果是10
return 0;
}
七,运算优先级
运算符的优先级决定了在没有括号明确指定顺序的情况下,哪些运算符会先被计算。
一般而言,算术运算符优先于关系运算符,逻辑运算符又在它们之后,位运算符通常具有较高优先级。使用括号()
可以强制改变运算顺序。
下面是部分运算符的优先级顺序(按照优先级从高到低排列)。
- 圆括号(())
- 自增运算符(++),自减运算符(–)
- 一元运算符(+和-)
- 乘法(*),除法(/)
- 加法(+),减法(-)
- 关系运算符(<、>等)
- 赋值运算符(=)
由于圆括号的优先级最高,可以使用它改变其他运算符的优先级。
int x = (3 + 4) * 5;
上面示例中,由于添加了圆括号,加法会先于乘法进行运算。
如果两个运算符优先级相同,则根据运算符是左结合,还是右结合,决定执行顺序。大部分运算符是左结合(从左到右执行),少数运算符是右结合(从右到左执行),比如赋值运算符(=)。
运算优先级的最佳实践:
- ①完全记住所有运算符的优先级没有必要,解决方法是多用圆括号,防止出现意料之外的情况,也有利于提高代码的可读性。
- ②表达式要清晰易读,不要写过于复杂的表达式。如:
res = (res = res++ + 2) + (a = ++b - 2)
,就是不好的表达式。