首页 > 其他分享 >C语言运算符&优先级

C语言运算符&优先级

时间:2023-01-19 18:22:28浏览次数:35  
标签:优先级 示例 int C语言 运算符 ++ 表达式

运算符

优先级这一块即使你用了很久C语言, 如果不刻意记忆, 也是容易搞混的.

C 语言的运算符非常多,一共有 50 多种,可以分成若干类。

算术运算符

算术运算符专门用于算术运算,主要有下面几种。

  • +:正值运算符(一元运算符)
  • -:负值运算符(一元运算符)
  • +:加法运算符(二元运算符)
  • -:减法运算符(二元运算符)
  • *:乘法运算符
  • /:除法运算符
  • %:余值运算符

(1)+-

+-既可以作为一元运算符,也可以作为二元运算符。所谓“一元运算符”,指的是只需要一个运算数就可以执行。一元运算符-用来改变一个值的正负号。

int x = -12;

上面示例中,-12这个值变成-12

一元运算符+对正负值没有影响,是一个完全可以省略的运算符,但是写了也不会报错。

int x = -12;
int y = +x;

上面示例中,变量y的值还是-12,因为+不会改变正负值。

二元运算符+-用来完成加法和减法。

int x = 4 + 22;
int y = 61 - 23;

(2)*

运算符*用来完成乘法。

int num = 5;
printf("%i\n", num * num); // 输出 25

(3)/

运算符/用来完成除法。注意,两个整数相除,得到还是一个整数。

float x = 6 / 4;
printf("%f\n", x); // 输出 1.000000

上面示例中,尽管变量x的类型是float(浮点数),但是6 / 4得到的结果是1.0,而不是1.5。原因就在于 C 语言里面的整数除法是整除,只会返回整数部分,丢弃小数部分。

如果希望得到浮点数的结果,两个运算数必须至少有一个浮点数,这时 C 语言就会进行浮点数除法。

float x = 6.0 / 4; // 或者写成 6 / 4.0
printf("%f\n", x); // 输出 1.500000

上面示例中,6.0 / 4表示进行浮点数除法,得到的结果就是1.5

下面是另一个例子。

int score = 5;
score = (score / 20) * 100;

上面的代码,你可能觉得经过运算,score会等于25,但是实际上score等于0。这是因为score / 20是整除,会得到一个整数值0,所以乘以100后得到的也是0

为了得到预想的结果,可以将除数20改成20.0,让整除变成浮点数除法。

score = (score / 20.0) * 100;

(4)%

运算符%表示求模运算,即返回两个整数相除的余值。这个运算符只能用于整数,不能用于浮点数。

int x = 6 % 4; // 2

负数求模的规则是,结果的正负号由第一个运算数的正负号决定。

11 % -5 // 1
-11 % -5 // -1
-11 % 5 // -1

上面示例中,第一个运算数的正负号(11-11)决定了结果的正负号。

(5)赋值运算的简写形式

如果变量对自身的值进行算术运算,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
  • 左值右值和数据对象

    数据对象: 用于保存变量或者数组的数据存储区, 用来保存数据的内存空间.

    左值: 用于标识一个特定数据对象的名字或表达式

    对象指的是实际的数据存储, 左值用来表示内存中的对象, 是用来识别或定位这个存储对象的标识符.

    右值: 能赋给可修改的左值的数据.

自增运算符,自减运算符

C 语言提供两个运算符,对变量自身进行+ 1- 1的操作。

  • ++:自增运算符
  • --:自减运算符
i++; // 等同于 i = i + 1
i--; // 等同于 i = i - 1

这两个运算符放在变量的前面或后面,结果是不一样的。++var--var是先执行自增或自减操作,再返回操作后var的值;var++var--则是先返回操作前var的值,再执行自增或自减操作。

int i = 42;
int j;

j = (i++ + 10);
// i: 43
// j: 52

j = (++i + 10)
// i: 44
// j: 54

上面示例中,自增运算符的位置差异,会导致变量j得到不同的值。这样的写法很容易出现意料之外的结果,为了消除意外,可以改用下面的写法。

/* 写法一 */
j = (i + 10);
i++;

/* 写法二 */
i++;
j = (i + 10);

上面示例中,变量i的自增运算与返回值是分离的两个步骤,这样就不太会出错,也提高了代码的可读性。

关系运算符

C 语言用于比较的表达式,称为“关系表达式”(relational expression),里面使用的运算符就称为“关系运算符”(relational operator),主要有下面6个。

左结合, 同优先级左边的运算符先找操作数, 由左往右依次执行.

优先级较高的:

  • > 大于运算符
  • < 小于运算符
  • >= 大于等于运算符
  • <= 小于等于运算符

优先级较低的:

  • == 相等运算符
  • != 不相等运算符

下面是一些例子。

a == b;
a != b;
a < b;
a > b;
a <= b;
a >= b;

关系表达式的结果, 是个整型值, 而非布尔值. 一般用于条件判断.

关系表达式通常返回01,表示真伪。C 语言中,0表示伪,所有非零值表示真。比如,20 > 12返回112 > 20返回0

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

if (x == 3) {
  printf("x is 3.\n");
}

注意,相等运算符==与赋值运算符=是两个不一样的运算符,不要混淆。有时候,可能会不小心写出下面的代码,它可以运行,但很容易出现意料之外的结果。

if (x = 3) ...

上面示例中,原意是x == 3,但是不小心写成x = 3。这个式子表示对变量x赋值3,它的返回值为3,所以if判断总是为真。

为了防止出现这种错误,有的程序员喜欢将变量写在等号的右边。

if (3 == x) ...

这样的话,如果把==误写成=,编译器就会报错。

/* 报错 */
if (3 = x) ...

另一个需要避免的错误是,多个关系运算符不宜连用。

i < j < k

上面示例中,连续使用两个小于运算符。这是合法表达式,不会报错,但是通常达不到想要的结果,即不是保证变量j的值在ik之间。因为关系运算符是从左到右计算,所以实际执行的是下面的表达式。

(i < j) < k

上面式子中,i < j返回01,所以最终是01与变量k进行比较。如果想要判断变量j的值是否在ik之间,应该使用下面的写法。

i < j && j < k

逻辑运算符

逻辑运算符提供逻辑判断功能,用于构建更复杂的表达式,主要有下面三个运算符。

  • !: 逻辑非运算符(改变单个表达式的真伪).
    • 单目运算符, 右结合性, 优先级: ! > && > ||
  • &&:逻辑与运算符(两侧的表达式都为真,则为真,否则为伪)。
  • ||:逻辑或运算符(两侧至少有一个表达式为真,则为真,否则为伪)。
    • 上面两个双目运算符, 左结合性.

逻辑表达式中, 逻辑运算符的操作数可以是常量, 也可以是变量, 表达式.

逻辑表达式的值是整型.

优先级: ! > 算术运算符 > 关系运算符 > && > || > 赋值 > 逗号

下面是与运算符的例子。

if (x < 10 && y > 20)
  printf("Doing something!\n");

上面示例中,只有x < 10y > 20同时为真,x < 10 && y > 20才会为真。

下面是否运算符的例子。

if (!(x < 12))
  printf("x is not less than 12\n");

上面示例中,由于否运算符!具有比<更高的优先级,所以必须使用括号,才能对表达式x < 12进行否运算。当然,合理的写法是if (x >= 12),这里只是为了举例。

对于逻辑运算符来说,任何非零值都表示真,零值表示伪。比如,5 || 0会返回15 && 0会返回0

逻辑运算符还有一个特点,它总是先对左侧的表达式求值,再对右边的表达式求值,这个顺序是保证的。如果左边的表达式满足逻辑运算符的条件,就不再对右边的表达式求值。这种情况称为“短路”。

if (number != 0 && 12/number == 2)

上面示例中,如果&&左侧的表达式(number != 0)为伪,即number等于0时,右侧的表达式(12/number == 2)是不会执行的。因为这时左侧表达式返回0,整个&&表达式肯定为伪,就直接返回0,不再执行右侧的表达式了。

由于逻辑运算符的执行顺序是先左后右,所以下面的代码是有问题的。

while ((x++ < 10) && (x + y < 20))

上面示例中,执行左侧表达式后,变量x的值就已经变了。等到执行右侧表达式的时候,是用新的值在计算,这通常不是原始意图。

  • 短路法则

    在表达式 a && b中, 一旦a的结果为假(false, 0), b就不再执行了. 1&&2&&4&&0&&2&&3&&9 = 0

    在表达式 a || b中, 一旦a的结果为真(true, 1), b就不再执行了. 0||0||1||||0||0||0||1 = 1

位运算符

C 语言提供一些位运算符,用来操作二进制位(bit)。 驱动开发最常用到的运算符.

~ & | ^ << >> 六个位运算, 进行某位清零, 置1, 取反, 异或, 状态查询等操作.

C语言中, 用整数来代替逻辑表达式的布尔值.

(1)取反运算符

取反运算符是一个一元运算符,用来将每一个二进制位变成相反值,即0变成11变成0

// 返回 01101100
~ 10010011

上面示例中,~对每个二进制位取反,就得到了一个新的值。

注意,~运算符不会改变变量的值,只是返回一个新的值。

(2)与运算符&

与运算符&将两个值的每一个二进制位进行比较,返回一个新的值。当两个二进制位都为1,就返回1,否则返回0

// 返回 00010001
10010011 & 00111101

上面示例中,两个八位二进制数进行逐位比较,返回一个新的值。

与运算符&可以与赋值运算符=结合,简写成&=

int val = 3;
val = val & 0377;

// 简写成
val &= 0377;

(3)或运算符|

或运算符|将两个值的每一个二进制位进行比较,返回一个新的值。两个二进制位只要有一个为1(包含两个都为1的情况),就返回1,否则返回0

// 返回 10111111
10010011 | 00111101

或运算符|可以与赋值运算符=结合,简写成|=

int val = 3;
val = val | 0377;

// 简写为
val |= 0377;

(4)异或运算符^

异或运算符^将两个值的每一个二进制位进行比较,返回一个新的值。两个二进制位有且仅有一个为1,就返回1,否则返回0。 按位异或, 相同为0, 不同为1. 1^1 0^0 结果为0 1^0结果为1.

// 返回 10101110
10010011 ^ 00111101

异或运算符^可以与赋值运算符=结合,简写成^=

int val = 3;
val = val ^ 0377;

// 简写为
val ^= 0377;

(5)左移运算符<<

左移运算符<<将左侧运算数的每一位,向左移动指定的位数,尾部空出来的位置使用0填充。

// 1000101000
10001010 << 2

上面示例中,10001010的每一个二进制位,都向左侧移动了两位。

左移运算符相当于将运算数乘以2的指定次方,比如左移2位相当于乘以4(2的2次方)。

左移运算符<<可以与赋值运算符=结合,简写成<<=

int val = 1;
val = val << 2;

// 简写为
val <<= 2;

(6)右移运算符>>

右移运算符>>将左侧运算数的每一位,向右移动指定的位数,尾部无法容纳的值将丢弃,头部空出来的位置使用0填充。

// 返回 00100010
10001010 >> 2

上面示例中,10001010的每一个二进制位,都向右移动两位。最低的两位10被丢弃,头部多出来的两位补0,所以最后得到00100010

注意,右移运算符最好只用于无符号整数,不要用于负数。因为不同系统对于右移后如何处理负数的符号位,有不同的做法,可能会得到不一样的结果。

右移运算符相当于将运算数除以2的指定次方,比如右移2位就相当于除以4(2的2次方)。

右移运算符>>可以与赋值运算符=结合,简写成>>=

int val = 1;
val = val >> 2;

// 简写为
val >>= 2;
  • 逻辑移位和算术移位

算术移位, 需要考虑整型的正负. 空出来位, 根据正负不同, 用1还是0来补待定.

逻辑移位, 不需要考虑正负号. 空出来的位一律补零

  • 位运算的隐式转换

    << 左操作数是char or short会隐式转换成int

    <<的右操作数也要限制在合法取值范围.

条件运算符

表达式1 ? 表达式2 : 表达式3 如 a > b ? 1 : 0

若表达式1为真, 取表达式2的值, 否则取表达式3的值.

int a = 1, b = 2;
int c = a > b ? 1+2 : 3*4 //c的结果为12

右结合性

如果条件表达式套娃, 先执行右边的.

int a = 1, b = 2;
int c = a>b?2: a<b?3:0; //结果是3 右边的条件表达式先执行.

逗号运算符

逗号运算符用于将多个表达式写在一起,从左到右依次运行每个表达式。

逗号表达式的优先级, 是最低的, 反向登顶.

x = 10, y = 20;

上面示例中,有两个表达式(x = 10y = 20),逗号使得它们可以放在同一条语句里面。

逗号运算符返回最后一个表达式的值,作为整个语句的值。

int x;
x = 1, 2, 3;

上面示例中,逗号的优先级低于赋值运算符,所以先执行赋值运算,再执行逗号运算,变量x等于1

运算符结合性

结合性就是一串运算符, 是从左往右依次执行, 还是从右往左依次执行.

当表达式中的运算符优先级相同, 那么运算符就会根据结合性来挑选操作数.

  • 左结合与右结合

    左边的运算符先挑选对象, 依次向右执行

    右边的运算符先挑选运算对象, 依次向左执行

    求知顺序和结合性无关, 和编译器有关.

  • 右结合的运算符

    单目, 赋值, 条件

运算优先级

优先级指的是,如果一个表达式包含多个运算符,哪个运算符应该优先执行。各种运算符的优先级是不一样的。

表达式中,优先级高的运算符先选择选择操作对象.

优先级用来确定运算符的操作对象, 而不是用来确定运算次序的, 后者是编译器决定的.

C语言优先级共16级, 16级最高 1级最低.

3 + 4 * 5;

上面示例中,表达式3 + 4 * 5里面既有加法运算符(+),又有乘法运算符(*)。由于乘法的优先级高于加法,所以会先计算4 * 5,而不是先计算3 + 4

如果两个运算符优先级相同,则根据运算符是左结合,还是右结合,决定执行顺序。大部分运算符是左结合(从左到右执行),少数运算符是右结合(从右到左执行),比如赋值运算符(=)。

5 * 6 / 2;

上面示例中,*/的优先级相同,它们都是左结合运算符,所以从左到右执行,先计算5 * 6,再计算6 / 2

运算符的优先级顺序很复杂。下面是部分运算符的优先级顺序(按照优先级从高到低排列)。

  • 圆括号(()
  • 自增运算符(++),自减运算符(--
  • 一元运算符(+-
  • 乘法(*),除法(/
  • 加法(+),减法(-
  • 关系运算符(<>等)
  • 赋值运算符(=

由于圆括号的优先级最高,可以使用它改变其他运算符的优先级。

int x = (3 + 4) * 5;

上面示例中,由于添加了圆括号,加法会先于乘法进行运算。

完全记住所有运算符的优先级没有必要,解决方法是多用圆括号,防止出现意料之外的情况,也有利于提高代码的可读性。

  • 16级最高 从左到右结合 后缀

    [] : 数组下标

    (): 函数调用

    .: 成员选择运算

    ->: 间接成员选择运算

    ++ --: 后自增, 后自检 (a++ a--)

    (类型名){值列表}: C99新增的复合字面值

  • 15级

    • 从右到左 前缀

      ++ --: 前自增, 前自减(++a --a)

    • 从右到左 一元

      &: 求指针运算

      *: 间接访问

      +: 求原值

      -: 求负值

      ~: 按位取反值

      !: 逻辑非

      sizeof: 求数据类型长度

  • 14级 从右到左 一元

    (类型名) : 强制类型转换

  • 13级 从左到右 二元

    * / % : 乘 除 求余数

  • 12级 从左到右 二元

    + - : 加 减

  • 11级 从左到右 二元

    << >>: 移位运算, 左移, 右移

  • 10级 从左到右 二元

    < <= : 小于 小于等于

    > >= : 大于, 大于等于

  • 9级 从左到右 二元

    == !=: 等于 不等于

  • 8级 从左到右 二元

    & : 按位与

  • 7级 从左到右 二元

    ^ : 按位异或

  • 6级 从左到右 二元

    |: 按位或

  • 5级 从左到右 二元

    && : 逻辑与

  • 4级 从左到右 二元

    ||: 逻辑或

  • 3级 从右到左 三元

    ?: : 条件运算

  • 2级 从右到左 二元

    = += -= *= /= %= <<= >>= &= ^= |=: 复制

  • 1级(最低) 从左到右 二元

    ,: 顺序求值

记忆:

[] () . -> ++/--(后) (类型名){值列表}

++/-- (前缀)

~, !, 单目, 算术, 关系, 逻辑, 条件, 赋值,逗号 依次递减

位逻辑运算符高于逻辑运算符

易错的优先级

  • .优先级高于*

    比如 *p.f 结构体p中, f是个指针. 那这个表达式其实就是*(p.f), 取这个指针里的值.

  • .的优先级高于++

    p.age++ 就是(p.age)++

  • [] 的优先级高于*

    int *ap[]; 等价于 int* (ap[]);数组里存放的是int *类型的指针

  • 函数()优先级高于*

    int* fp(); 指针函数 返回指针.

  • 关系运算符 大于 位操作

    a & mask != 0; 等价于 a & (mask != 0);

  • 关系运算符 大于 赋值

    c=getchar() != EOF; 等价于 c = (getchar() != EOF)

  • 算术运算符 高于 移位运算符

    i << 4 + j; 等价于i << (4+j);

  • 逗号运算符优先级最低.

    i = 1, 2, 3*4; i是1, 逗号表达式最后是12.

C语言的序列点

副作用

副作用是指对数据对象或文件的修改

定义一个表达式的目的可能是取得这个表达式的值, 有的表达式有副作用, 有的没有.(++/--)

未定义

包含多个不确定副作用的代码的行为, 称为未定义 . 比如:

int a = 10;
int b = a++ * a++;

上面这两行代码, gcc 下编译b是100, tc下编译b是110.

序列点

程序运行中一个特殊的点, 在该点上, 所有副作用都在进入下一步之前发生.

C标准规定: 在两个序列点之间, 一个对象所保存的值最多只能被修改一次. 而且前一个值只能用于解决将要保存的值. 如果要写入某个对象, 则在同一表达式中, 对该对象的访问应该只局限于计算将要写的值. 只有能在确保修改之前才访问变量的表达式为合法表达式.

在两个序列点之间, 连续两次修改, 并且访问该变量, 会产生不确定性, 产生未定义行为.

a[i] = i++ 这种未定义行为. 只能交给编译器解释.

  • 引入序列点的目的

    • 消除编译器解释表达式时的歧义
    • 若序列点不能解决歧义, 就是未定义行为, 编译器自行解释并结算结果.
  • 主要序列点

    • 完整表达式的尾部

    • && || ?: 逗号操作符处

      if(1+2 || 3*4) 按优先级会先算3*4, 但因为序列点, 会从左到右一次执行, 再根据短路原则.

      1, 2+3, 4*5; 逗号也是先从左开始执行

    • 函数调用时, 实参表内全部参数求值结束, 函数执行第一条指令之前.

如何避免未定义行为

  • 确保一个表达式最多只修改一个对象. (对象可以是一个简单变量, 数组成员, 或指针指向的位置)

  • 如果一个变量在一个表达式中出现1次以上, 且在表达式中被修改, 则要确保该对象所有的读访问都被用于计算它的最终值. 如 i = i+1;

  • 尽量不要在同一个语句中改变同一个变量2次以上.

    i=i++; i++ * i++;这个上面举过的反例, 不同编译器结果不同.

标签:优先级,示例,int,C语言,运算符,++,表达式
From: https://www.cnblogs.com/nevertoolate22/p/17061936.html

相关文章

  • STM32F103和AIR32F103的FreeRTOS中断优先级
    关于ArmCortexM系列内核的中断优先级https://community.arm.com/arm-community-blogs/b/embedded-blog/posts/cutting-through-the-confusion-with-arm-cortex-m-int......
  • C语言学院教学信息管理系统[2023-01-19]
    C语言学院教学信息管理系统[2023-01-19]30、某学院教学信息管理系统功能:1、每一条记录包括一位教师的职工号、姓名、职称、性别、3门主讲课程(课程名称、开课学期、课......
  • C语言核酸检测系统[2023-01-19]
    C语言核酸检测系统[2023-01-19]项目九:核酸检测系统1.教学内容实现一个简单的核酸检测系统,业务包括:将被检测人员的信息精准记录在系统中,并实时更据,实现精准监控并快速......
  • C语言中的整型数据类型(你真的了解吗)
    1.整型数据类型C语言里面的整数数据类型类型名称C语言中的关键字注释字符型char表示一个很小的整数短整型short表示一个不怎么大的整数整型int生活中一般的整数都可以表示......
  • 第一个C语言程序(从Hello World开始)
    程序员之间有一个约定俗成的习惯,我们在学习任何编程语言时,所写的第一个程序,就是在显示屏上打印一行字符​​“HelloWorld”​​。这个习惯出自哪里呢,首先回顾C语言的历史,就......
  • C语言基础--函数
    目录一、什么是函数二、函数的创建三、函数的使用四、返回值的使用五、什么是形参和实参六、默认值形参七、函数的递归一、什么是函数编程中的函数是将一些需要复用的代......
  • C语言基础--数组详细说明
    目录一.什么是数组二、一维数组1.一维数组创建2.一维数组的使用2.1索引值2.2遍历数组2.3如何使用sizeof()计算出数组的长度三、二维数组1.二维数组的创建2.二维数组的使......
  • C语言高校实验室预约登记系统[2023-01-18]
    C语言高校实验室预约登记系统[2023-01-18]23、高校实验室预约登记系统功能:(1)显示实验室能够提供的实验名称、编号、实验内容、实验联系人等信息;(2)管理员能够对系统中各实......
  • C语言《高级语言程序设计课程设计》[2023-01-18]
    C语言《高级语言程序设计课程设计》[2023-01-18]2022级3班高级语言程序设计课程设计说明书一、设计任务与要求《高级语言程序设计课程设计》是在完成《高级语言程序设计......
  • C++入门篇之重载运算符和重载函数
    C++允许在同一作用域中的某个函数 和运算符 指定多个定义,分别称为函数重载 和运算符重载。重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声......