C语言入门系列:运算符及其优先级


在C语言编程中,运算符是构建程序逻辑的基础,它们用于执行计算、比较值、控制数据流等操作。

C语言中的运算符有50多种,我们可以分门别类的学习。

一,算术运算符

算术运算符用于执行基本的数学运算,如加、减、乘、除和取模(求余数)。

  • + 正值运算符(一元运算符)
  • - 负值运算符(一元运算符)
  • + 加法(二元运算符)
  • - 减法(二元运算符)
  • * 乘法
  • / 除法(结果为浮点数,如果两边都是整数,则结果向下取整)
  • % 取模(求余数)
  1. + 正值运算符(一元运算符)

    该运算符用于改变数值的符号,当应用于一个正数或零时,没有实际效果。

    int x = +5; // x 的值为 5,这里 + 运算符没有实际改变数值。
    

    正值运算符通常可以省略,但在某些上下文中使用它能增加代码的清晰度。
    如果在一个值为负数的变量前面加上正号会有什么效果呢?

     int y = -5;
     int x = +y;
    

    变量x的值还是-5,因为+不会改变正负值。

  2. - 负值运算符(一元运算符)

    用于改变数值的符号,将一个数变为它的负数。

    int y = -10; // y 的值为 -10。
    

    负值运算符是改变符号的最直接方式,尤其在数学运算和条件判断中常见。

    int y = -5;
    int x = -y;
    

    变量x的值是5,因为-会改变正负值,这是与+有区别的地方。

  3. + 加法(二元运算符)

    将两个数值相加。

    int a = 3;
    int b = 4;
    int sum = a + b; // sum 的值为 7。
    
  4. - 减法(二元运算符)

    从一个数值中减去另一个数值。

    int c = 10;
    int d = 5;
    int difference = c - d; // difference 的值为 5。
    
  5. * 乘法

    将两个数值相乘。

    int e = 3;
    int f = 4;
    int product = e * f; // product 的值为 12。
    
  6. / 除法(结果为浮点数,如果两边都是整数,则结果向下取整)

    进行除法运算,如果除数和被除数都是整数,结果将是整数(向下取整);如果其中至少有一个是浮点数,则结果为浮点数。

    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;
    
  7. % 取模(求余数)

    返回除法的余数。

    int i = 6 % 4; // i 的值为 2,因为 6 除以 4 的余数是 2。
    

    取模运算符只能用于整数,结果的符号取决于被模数(左边的数)的符号。

  8. 赋值运算的简写形式

    如果变量对自身的值进行算术运算,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;
}

在这里插入图片描述

关系表达式常用于ifwhile结构。

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的值在ik之间。因为关系运算符是从左到右计算,所以实际执行的是下面的表达式。

(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");
}

在这个例子中,因为ab都大于0,所以a > 0 && b > 0的判断结果为真,会执行打印语句。

短路特性:如果左边的表达式为假,右边的表达式将不会被评估,因为整个表达式的结果已经确定为假。

 int a = -5, b = 10;
 
 if (a > 0 && b > 0) {
     printf("Both a and b are positive.\n");
 }

上面代码中,因为a = -5if判断中 a > 0false,整个逻辑与的结果肯定是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的值就已经变了。执行右侧表达式的时候,是用新的值在计算,这极有可能不是我们的真实意图。

逻辑运算符在编写条件控制语句(如ifwhile)时特别有用,通过它们可以构建复杂的逻辑判断,实现精确的程序流程控制。

五,位运算符

位运算符对整型数据的二进制位进行操作。

  • & 按位与
  • | 按位或
  • ^ 按位异或
  • ~ 按位取反
  • << 左移
  • >> 右移

按位与(&

对两个操作数的每一位进行逻辑与操作。如果两个位都是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

在这个例子中,ab的二进制表示分别是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

这里,cd按位或后得到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

这里,ef的对应位上不相同的位产生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位相当于乘以42的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位就相当于除以221次方),所以上述示例右移一位,相当于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),就是不好的表达式。
  • 32
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小手追梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值