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

news2024/12/25 0:20:25

文章目录

    • 一,算术运算符
    • 二,自增运算符与自减运算符
    • 三,关系运算符
    • 四,逻辑运算符
      • 逻辑与(`&&`)
      • 逻辑或(`||`)
      • 逻辑非(`!`)
      • 最佳实践:行为单一原则
    • 五,位运算符
      • 按位与(`&`)
      • 按位或(`|`)
      • 按位异或(`^`)
      • 按位取反(`~`)
      • 左移(`<<`)
      • 右移(`>>`)
    • 六,逗号运算符
    • 七,运算优先级

在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),就是不好的表达式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1821652.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

最新区块链论文速读--CCF A会议 ICSE 2024 共13篇 附pdf下载 (2/2)

Conference&#xff1a;International Conference on Software Engineering (ICSE) CCF level&#xff1a;CCF A Categories&#xff1a;Software Engineering/System Software/Programming Languages Year&#xff1a;2024 Num&#xff1a;13 第1~7篇区块链文章请点击此处…

【产品经理】订单处理2

本次讲解订单初始化成功到ERP系统过程中的后续环节。 一、根据客服备注更新订单信息 初始化订单过程中&#xff0c;若订单中的客服备注信息对订单进行更新&#xff0c;包括可能改收货信息、改商品、加赠品、指定快递等。 注意&#xff1a;更新订单的过程中要注意订单当前状…

10M速率1553总线终端(RT)模块是依据SAE-AS5652标准设计

10M速率1553总线终端(RT)模块是依据SAE-AS5652标准设计的支持传输速率10Mbps的总线远程终端&#xff08;RT&#xff09;模块&#xff0c;采用SIP封装技术&#xff0c;支持LocalBus接口或UART通信访问&#xff0c;64K*16bits存储空间&#xff0c;灵活的RT数据存储&#xff0c;具…

Android Room数据库使用介绍

1.简介 Room是Google提供的Android架构组件之一&#xff0c;旨在简化数据库操作。它是SQLite的一个抽象层&#xff0c;提供了更易用和安全的API。 Room的总体架构: 2.Room数据库的基础概念 Entity Entity是Room中的数据表&#xff0c;每个Entity类对应一个SQLite表。 DAO …

有什么开放式耳机值得买?六点选购建议你要注意了

作为一名数码爱好者&#xff0c;专业的数码博主&#xff0c;我只想把好的产品介绍给大家&#xff0c;让大家避雷不好用的产品&#xff0c;最近&#xff0c;很多人私信我问我开放式的耳机怎么样&#xff1f;和别的耳机又有什么区别&#xff0c;我发现大家对于开放式耳机的了解少…

什么是计算机技术与软件(初级、中级、高级)考试(软考)?

一、软考是什么&#xff1f; 计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试&#xff08;以下简称计算机软件资格考试&#xff09;是原中国计算机软件专业技术资格和水平考试&#xff08;简称软件考试&#xff09;的完善与发展。计算机软件资格考试是由国家人力…

如何进行敏捷型数据治理?现行的数据治理体系是不是有瑕疵和遗漏?

敏捷型数据治理&#xff08;Agile Data Governance&#xff09;是一种灵活、迭代的方法&#xff0c;旨在快速响应和适应不断变化的业务需求和数据环境。与传统的数据治理方法相比&#xff0c;敏捷型数据治理更注重实践中的灵活性和速度&#xff0c;同时保持数据质量、隐私和安全…

Vue2数据响应式再次理解

今天遇到一个问题吧算是&#xff0c;项目用的vue2&#xff0c;期望把数据的某个数组清空&#xff0c;在组件内部调用this.xxarray [] 没问题&#xff0c;但是把数组引用传递到另外一个函数&#xff0c;执行赋值清空&#xff0c;会失效&#xff1b;大概的复原如下图 分析&#…

直播预约:存内计算加速大模型-未来智能计算的新引擎

直播简介: 在人工智能飞速发展的今天&#xff0c;大模型的训练和推理对计算资源的需求日益增长。传统计算架构已逐渐难以满足其对速度和效率的极致追求。本次直播&#xff0c;我们将深入探讨如何利用存内计算技术&#xff0c;为大模型带来革命性的加速效果。 直播亮点: 技术…

C++ 33 之 const 修饰静态成员

#include <iostream> #include <string.h> using namespace std;// 定义静态const数据成员时&#xff0c;最好在类内部初始化,避免在类外重复初始化&#xff0c;也为了代码的可读性和可维护性class Students03{ public:// 两种写法都可以const static int s_a 10;…

node 中间件使用例子

NodeJS在中间件领域有着较为广泛的应用&#xff0c;他能做一些中间层事件&#xff0c;把服务端一部分的代码抽出来&#xff0c;减少处理冗余事情付出的代价&#xff0c;同时让服务真正做业务处理而不用关心页面的事情 常见的应用场景有&#xff1a; 跨域&#xff1a;解决跨域问…

内存卡提示需要格式化?别急,这样拯救你的数据

一、内存卡突然提示需要格式化 在日常生活中&#xff0c;我们经常会使用到内存卡来存储照片、视频、文档等重要数据。然而&#xff0c;有时当我们试图访问内存卡时&#xff0c;却会遭遇一个令人头疼的问题——系统突然提示“内存卡需要格式化”。这意味着我们无法直接读取或写…

ARM32开发--IIC软实现

知不足而奋进 望远山而前行 目录 文章目录 前言 开发流程 GD32F4软件I2C初始化 GD32F4软件I2C引脚功能 写操作 读操作 总结 前言 在嵌入式系统开发中&#xff0c;软件实现的I2C通信协议扮演着至关重要的角色。本文将深入探讨如何在GD32F4系列微控制器上实现软件I2C功能…

『大模型笔记』缩放定律(scaling laws)是由记忆而非智力解释的吗?

MAC 文章目录 一. 缩放定律(scaling laws)是由记忆而非智力解释的吗?1. 视频原文内容2. 要点总结一般智能的定义规模最大化的论点性能衡量的方式及其影响大语言模型的基准测试大语言模型的本质与记忆基准测试插值的概念与基准测试实例人类和模型的推理与样本效率二. 参考文献一…

CTE-6作文

第一段 现象 引出原因 第二段 感受 举例 意义 危害 第三段 建议 展望 范文1 第一段 第二段 尾段 范文2 首段 第二段 尾段

诊所管理系统哪家会好一点

随着医疗行业的快速发展和信息化进程的加速&#xff0c;诊所作为医疗服务的重要基层单位&#xff0c;其运营管理效率与服务质量的提升愈发依赖于现代化的管理工具。诊所管理系统应运而生&#xff0c;旨在通过集成化、智能化的技术手段&#xff0c;帮助诊所实现诊疗流程优化、资…

用C语言实现扫雷

本篇适用于C语言初学者&#xff0c;主要涉及对于函数&#xff0c;数组&#xff0c;分支循环的运用。 目录 设计思想&#xff1a; 总代码&#xff08;改进后&#xff09;&#xff1a; 运行结果展示&#xff1a; 分布介绍&#xff1a; 声明&#xff1a; 代码主体部分&#…

精准定位,智慧提纯:高级数据提取策略

在数据驱动的时代&#xff0c;高级数据提取策略成为企业决策、科学研究以及各类项目成功的关键。数据提取&#xff0c;不仅仅是简单地收集信息&#xff0c;而是需要精准定位目标数据&#xff0c;并通过智慧提纯方法&#xff0c;从海量数据中提取出有价值、有深度的信息。本文将…

如何计算 GPT 的 Tokens 数量?

基本介绍 随着人工智能大模型技术的迅速发展&#xff0c;一种创新的计费模式正在逐渐普及&#xff0c;即以“令牌”&#xff08;Token&#xff09;作为衡量使用成本的单位。那么&#xff0c;究竟什么是Token呢&#xff1f; Token 是一种将自然语言文本转化为计算机可以理解的…

【成品设计】基于STM32的单相瞬时值反馈逆变器

《基于STM32的单相瞬时值反馈逆变器》 整体功能&#xff1a; 图13 软件框图 如图13所示&#xff0c;由于本设计中需要通过定时器中断执行一些程序&#xff0c;故首先对中断进行初始化。中断初始化以后即为对串口进行初始化&#xff0c;总共初始化了两个串口&#xff0c;第一个…