C语言初阶之操作符

1. 操作符分类

算术操作符
移位操作符
位操作符
赋值操作符
单目操作符
关系操作符
逻辑操作符
条件操作符
逗号表达式
下标引用、函数调用和结构成员

2. 算术操作符

 +      -     *      /      %
 加     减    乘     除     取模


我们详细来看一下除和取模。
在这里插入图片描述
在这里插入图片描述

注意:
1.除了% 操作符之外,其他的几个操作符可以作用于整数和浮点数。
2. 对于/ 操作符如果两个操作数都为整数,执行整数除法。而只要除号两端的操作数里面至少有一个是浮点数,执行的就是浮点数除法。
3. % 操作符的两个操作数必须为整数。返回的是整除之后的余数。

3. 移位操作符

移位操作符移动的是二进制位。

<<     左移操作符
>>     右移操作符

注意:移位操作符(左移、右移)的操作数都只能是整数。

移位操作符移动的是二进制补码。
所以在了解移位操作符之前,我们需先了解整数的二进制表达形式。
整数的二进制表示其实有3种: 原码反码补码
按照一个数的正负,直接写出它的二进制表示形式得到的就是原码,整型占4个字节(32bit)。
1、对于正整数:原码、反码、补码相同。
2、对于负整数: 原码:直接按照数字的正负写出的二进制序列。
        反码:原码的符号位不变,其他位按位取反(1 --> 0 , 0 -->1)。
        补码:反码+1。
在这里插入图片描述

第一位是符号位:为0,则为正数;
        为1,则为负数。
整数在内存中存储的是二进制的补码,所以在参与移位的时候,移动的都是补码。

3.1 左移操作符

移位规则:
左边丢弃、右边补0

在这里插入图片描述
例如(正整数左移):
在这里插入图片描述
例如(负整数左移):
在这里插入图片描述
注:我们打印的时候是打印的原码,我们内存存储的补码,所以我们求出来补码后,先去看是正数还是负数,正数补码原码相同直接打印,负数的补码化为原码再打印。
由上两个演示结果来看,<< 操作符还有乘2的效果。

3.2 右移操作符

移位规则:

首先右移运算分两种:

  1. 算术移位
    右边丢弃,左边补该值的原符号位(原来是负数则补1,是正数则补0)
  2. 逻辑移位
    右边丢弃,左边补0

在这里插入图片描述

在这里插入图片描述

例如 正整数右移(算数右移和逻辑右移结果相同):

例如(负整数算术右移):
在这里插入图片描述

例如(负整数逻辑右移):
在这里插入图片描述
大多数编译器采用的是算术移位。

警告⚠ :
对于移位运算符,不要移动负数位,这个是标准未定义的。

例如:
在这里插入图片描述
这种写法标准未定义。

4. 位操作符

&    按位与
|    按位或
^    按位异或

注意:他们的操作数必须是整数。

& - 按位与

对应的两个二进制位只要有0,按位与的结果就为0,
对应的两个二进制位同时为1,按位与的结果才为1。

在这里插入图片描述

| - 按位或

对应的两个二进制位只要有1,按位或的结果就为1,
对应的两个二进制位同时为0,按位或的结果才为0。

在这里插入图片描述
^ - 按位异或

对应的两个二进制位相同为0,
对应的两个二进制位相异为1。

在这里插入图片描述

练习1:
不能创建临时变量(第三个变量),实现两个整数的交换。

(1)正常创建变量的交换(不符合题意)
在这里插入图片描述

(2)算数逻辑交换(符合题意,但存在溢出的问题)
在这里插入图片描述
这种方法的缺陷在于:假设我们的a和b的值都接近int类型范围的最大值,这两者相加,就会超出int类型的范围最大值(溢出),这样可能会使得我们想得到的结果发生改变。

(3)位操作符交换
在这里插入图片描述
练习2:
编写代码实现:求一个整数存储在内存中的二进制中1的个数。

思路:将该整数 &(按位与) 1,判断二进制最低位是否为1,然后再将该整数二进制位向右移动一位,再次 &(按位与) 1,这样循环32次(整数是4个字节,32个比特位)就可以判断该整数的二进制位的每一位是否为1。
在这里插入图片描述

5. 赋值操作符

1、简单赋值操作符:=
注:一个等号 “ = ” 表示赋值,两个等号“ == ” 表示等于。
通过赋值符我们可以给初始变量进行初始化赋值,也可以将先前赋的值进行调整重新赋值。
例如:
在这里插入图片描述

赋值操作符可以连续使用,比如:
在这里插入图片描述
虽然这种方法在语法上支持,一般我们不建议使用连续赋值的方法,它不易调试,可读性不好,容易让人误解。
在这里插入图片描述

2、符合赋值操作符:

+=     加等
-=     减等
*=     乘等
/=     除等
%=     取模等
>>=    左移等
<<=    右移等
&=     按位与等
|=     按位或等
^=     按位异或等

这些运算符都可以写成复合的效果。
例如:
在这里插入图片描述
其他运算符一样的道理。其实用法都是一样的,就是简化我们的代码,这样写更加简洁。

6. 单目操作符

举例:加号操作符就是双目操作符,a+b,因为加号操作符有两个操作数。
但是
单目操作符:只有一个操作数的操作符。

!          逻辑反操作
-          负值
+          正值
&          取地址
sizeof     操作数的类型长度(以字节为单位)
~          对一个数的二进制按位取反
--         前置-- 、后置--
++         前置++ 、后置++
*          间接访问操作符(解引用操作符)
(类型)     强制类型转换

(1)逻辑反操作符:!
C语言中,0表示假,非0表示真。
逻辑反操作符把真变为假,假变为真。
比如:
在这里插入图片描述

(2)正值操作符:+  负值操作符: -
正值
在这里插入图片描述
负值
在这里插入图片描述

(3)取地址操作符:&
取出一个变量在内存中的起始地址。
在这里插入图片描述

(4)sizeof操作符
计算变量所占内存空间的大小,单位是字节。
计算类型所创建的变量占据空间的大小,单位是字节。

在这里插入图片描述
计算整个数组的大小,单位是字节。
在这里插入图片描述
注:sizeof是操作符,不是函数。

(5)~ 操作符
对一个数的二进制按位取反
在这里插入图片描述
在这里插入图片描述
练习:
把a的二进制中的第五位改为1
在这里插入图片描述

把之前改好的第五位再改回0
在这里插入图片描述
注: ~ 操作符对二进制位的正负数都可以进行按位取反!

(6) ++ 操作符:前置++ 、后置++
   - - 操作符:前置 - - 、后置 - -

前置++
在这里插入图片描述

后置++
1
前置 - -
在这里插入图片描述

后置 - -
在这里插入图片描述
还有别的用法,用后置 - - 举例:
在这里插入图片描述
在这里插入图片描述

(7) 间接访问操作符(解引用操作符):*
在这里插入图片描述

(8) 强制类型转换符:(类型)

如图编译器会发出警告,因为a是int类型,3.14是double类型。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7. 关系操作符

>         大于
>=        大于等于
<         小于
<=        小于等于
!=        不等于
==        等于

这些关系运算符比较简单,但是我们要注意一些运算符使用时候的陷阱。
警告:
在编程的过程中注意区分 == 和 = ,不要写错。

8. 逻辑操作符

&&     逻辑与
||     逻辑或

注意区分逻辑与按位与
注意区分逻辑或按位或
1&2----->0
1&&2---->1
1|2----->3
1||2---->1
逻辑与逻辑或只关注真假,不关注二进制位。

&&:逻辑与(并且的意思)

左右两边同时为真,整体为真,
左右两边有一边为假,整体为假

在这里插入图片描述
在这里插入图片描述
|| :逻辑或(或者的意思)

左右两边有一边为真,整体为真,
左右两边同时为假,整体为假

在这里插入图片描述
在这里插入图片描述

举例:判断闰年时用到的逻辑与逻辑或
在这里插入图片描述

特点:

逻辑与( && ) : 左边有一个判断为假后,右边的表达式不再计算
逻辑或( | | ) : 左边有一个判断为真后,右边的表达式不再计算

练习:
在这里插入图片描述

9. 条件操作符

条件操作符(三目操作符)

表达式1 ? 表达式2 : 表达式3

计算方法:

表达式1 ? 表达式2 : 表达式3
 真     √    X
 假     X   √

1为真,2计算,3不计算,2是整个表达式的结果
1为真,2不计算,3计算,3是整个表达式的结果

练习:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

10. 逗号表达式

表达式1,  表达式2,  表达式3,  ......表达式N

逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行,整个表达式的结果是最后一个表达式的结果。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

注:必须从左向右依次计算,前面的值是不可忽略的,因为最后一个表达式的值,可能会受到前面表达式值的影响而发生改变。

11. 下标引用、函数调用和结构成员

1、 [ ] 下标引用操作符
操作数:一个数组名 + 一个索引值
在这里插入图片描述

2、 ( ) 函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
在这里插入图片描述

3、结构成员

.      结构体对象 . 成员名
->     结构体指针->成员名

(*ps) . age
等价于
ps->age

在这里插入图片描述

12. 表达式求值

表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。

12.1 隐式类型转换

C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升

整型提升的意义:

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。

因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。

通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。

例:
在这里插入图片描述
b和c的值被提升为普通整型,然后再执行加法运算。
加法运算完成之后,结果将被截断,然后再存储于a中。

如何进行整体提升呢?

整形提升是按照变量的数据类型的符号位来提升的

在这里插入图片描述
正数和负数整型提升时,高位补充符号位,无符号整型提升时,最高位补0。

整形提升的例子:

例1:
在这里插入图片描述

例2:
在这里插入图片描述
例2中的a,b要进行整形提升,但是c不需要整形提升
a,b整形提升之后,变成了负数,所以表达式a == 0xb6,b == 0xb600 的结果为假,但是c不发生整形提升,则表达式c==0xb6000000 的结果为真。所以程序输出的结果是c。

例3:
在这里插入图片描述
例3中的,c只要参与表达式运算,就会发生整形提升,表达式+c ,就会发生提升,所以sizeof(+c) 是4个字节。
表达式-c 也会发生整形提升,所以sizeof(-c) 是4个字节,但是sizeof© ,就是1个字节。

12.2 算术转换

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换

long double
double
float
unsigned long int
long int
unsigned int
int

如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。

>= int 的时候,算术转换
<= int 的时候,整型转换

在这里插入图片描述

警告:
但是算术转换要合理,要不然会有一些潜在的问题。
在这里插入图片描述

12.3 操作符的属性

复杂表达式的求值有三个影响的因素。

  1. 操作符的优先
  2. 操作符的结合性
  3. 是否控制求值顺序。

两个相邻的操作符先执行哪个?
取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。

C语言操作符优先级、结合性参考表:
优先级从上往下依次递减
在这里插入图片描述

首先确定优先级,相邻操作符按照优先级高低计算。优先级相同的情况下,结合性才起作用。拿不准的就加上括号,或者拆开去写。

我们掌握了每个操作符的优先级与结合性,但不代表所有表达式都可以确定唯一计算路径,也有很多问题表达式,存在潜在问题。

例1:
在这里插入图片描述

注释:代码1在计算的时候,由于 * 比 + 的优先级高,只能保证,* 的计算是比 + 早,但是优先级并不能决定第三个 * 比第一个 + 早执行。

所以表达式的计算机顺序就可能是:
在这里插入图片描述
或者:
在这里插入图片描述

例2:
在这里插入图片描述
注释:同上,操作符的优先级只能决定自减–的运算在+的运算前面,但是我们并没有办法得
知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义
的。

例3:
在这里插入图片描述
表达式3在不同编译器中测试结果:非法表达式程序的结果
在这里插入图片描述

例4:
在这里插入图片描述

这个代码有没有实际的问题?
有问题!
虽然在大多数的编译器上求得结果都是相同的。
但是上述代码answer = fun() - fun() * fun(); 中我们只能通过操作符的优先级得知:先算乘法,
再算减法。

函数的调用先后顺序无法通过操作符的优先级确定。

例5:
在这里插入图片描述
Linux环境的结果:
在这里插入图片描述
VS2013环境的结果:
在这里插入图片描述
看看同样的代码产生了不同的结果,这是为什么?
简单看一下汇编代码,就可以分析清楚。
这段代码中的第一个 + 在执行的时候,第三个 ++ 是否执行,这个是不确定的,因为依靠操作符的优先级和结合性是无法决定第一个 + 和第三个前置++ 的先后顺序。

结论:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。

总结

感谢观看,欢迎三连,如有错误,欢迎指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值