在小学和中学时期,我们学过许多运算符,其中包括加、减、乘、除、开方、平方等等,而在C语言中,我们也有一些操作数据的符号,我们称之为操作符(运算符)。本文会将这些运算符 一 一讲解。
目录
一.算术操作符
二.移位操作符
1.左移操作符
2.右移操作符
(1)逻辑右移
(2)算术右移
三.逻辑操作符
1.逻辑与
2.逻辑或
3.逻辑非
4.经典例题
四.位操作符
1.按位与
2.按位或
3.按位异或
4.经典例题
五.赋值操作符
六.单目操作符
1.sizeof操作符
2.++和--
七.关系操作符
八.条件表达式(三目运算符)
九.逗号表达式
十.隐式类型转换
1.无符号整型
2.有符号整型
3.经典例题
十一.算术转换
算术运算符主要有+(加)、-(减)、*(乘)、\(除)、%(求余)这些,而加减乘这三个与我们数学中的无异。下面主要给大家讲解 / 和 % 这两个操作符
#include
int main()
{
int a = 5;
int b = 2;
float c = 2.0;
//除
//1.操作符两边都是整型
printf("%d\n", a / b); //2
//2.操作数有一边是浮点型
printf("%f\n", a / c); //2.5
//求余(两边必须为整型,字符型也是特殊的整型)
printf("%d", a % b); //1
return 0;
}
在C语言中除号两边都是整型则采取整数除法,若有一边为浮点型,则会发生算术转换(这个概念本文后面会提及),比如上图中a会转换为浮点型,浮点型之间的除法执行浮点数除法,因此结果为2.5。
注:取余操作符的操作数只能是整型。
<< 左移操作符
>> 右移操作符
移位操作符中的位代表的是二进制位
规则:左边抛弃,右边补零。
如下图:
注:数据在内存中是以补码的形式存放,正数的原码,反码,补码相同,在一个有符号数中,我们用最高位表示数的符号,我们称这一位为符号位,0表示正数,1表示负数。(这里仅仅简单提及一下,后面会出一篇博客详细讲解数据的储存方式)
下面用代码验证一下上图:
#include
int main()
{
int a = 1;
printf("%d\n", a << 1);
return 0;
}
果然结果正是我们预想的数值2。
右移操作符分为两种
1.逻辑右移
左边补零,右边丢弃
2.算数右移
左边补符号位,右边丢弃
C语言标准中并没有规定用哪一种,所以每个编译器定义的标准可能不同,我这里通过VS2022版本测试结果为-1,是情况二,算术右移。(注:以上所有测试条件都在32位环境中测试)
注:移位操作符不能移动负数位,比如 a << -1,这种写法是错误的。
&& 逻辑与
|| 逻辑或
! 逻辑非
逻辑与是这么定义的
1 && 0 ----- 0
0 && 1 ----- 0
1 && 1 ----- 1
只有两个操作数都为非零(真)结果才为1(真)
逻辑或是这么定义的
1 || 0 ------ 1
0 || 1 ------ 1
0 || 0 ------ 0
只要两个操作数都为零(假)的时候结果才为0(假)
逻辑非是这么定义的
! 1 ------ 0
! 0 ------ 1
即1(真)取非则成0(假),0(假)取非则成1(真)
下面有一道经典笔试题(from 360):
#include
int main()
{
int i = 0,a=0,b=2,c =3,d=4;
i = a++ && ++b && d++;
//i = a++||++b||d++;
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
return 0;
}
//程序输出的结果是什么?
这道题的答案是:
有很多小伙伴可能就疑惑了,为什么不是1 3 3 5呢,而是1 2 3 4,这是因为在逻辑与中,有一个有意思的现象,我们称之为短路现象,即当与左边的操作数为假的时候,整个表达式的结果就为假,后面的表达式就不进行计算了,什么意思呢?按本题思路来看,当a++为假的时候,整个表达式结果为0(假),如题这里a是后置++,且a的值恰好为0(假),因此后面的表达式就不进行运算,就得到了如上图的答案了。
同样,逻辑或也有这样的现象,当逻辑或的左边表达式结果为1(真),整个表达式的结果变为1(真),后面的表达式同样也不用参与运算。
所谓的位操作符中的位也指的是二进制位,具体操作符如下
& 按位与
| 按位或
^ 按位异或
注:他们的操作数也必须都为整数
与和逻辑与类似,不过按位与是将二进位每一位按位与。比如1和3按位与
代码测试结果如下:
如我们上图推测结果一样,结果为1。
按位或也是与逻辑或类似,他们计算的都是对应的二进制位,如下1按位或3
代码测试结果如下,依旧和我们算的一样,结果为3
按位异或则是指将两个数对应二进制位进行运算,如果相同则为0,相异则为1,如下图
始终记住相异为1,相同为0,下面代码也证实了上图
int main()
{
int a = 1;
int b = 3;
printf("%d\n", a ^ b);
return 0;
}
结果也正是我们推导的2。
小试牛刀,下面有一道经典面试题:
不能创建临时变量,实现两个数的交换(这两个数可能会很大)
看到这题,很多人可能会想到将两个变量相加然后,相减达到交换目的,如下代码
int main()
{
int a = 1;
int b = 3;
a = a + b; //4
b = a - b; //1
a = a - b; //3
return 0;
}
但是题目中有一条说明,这两个数可能会很大,这说明什么呢,假如这两个数定义为int型,那么假如这两个数都很大,如果相加,最终很有可能存不下这么大的数据,最后导致数据丢失,造成结果不准确的后果。以下就有一种方法可以保证不会造成数据丢失
int main()
{
int a = 1;
int b = 3;
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("a=%d, b=%d\n", a, b);
}
分析:假设数据只有8位,方便运算
a的补码:0000 0001
b的补码:0000 0011
a=a^b: 0000 0010 ------a = 2
b=a^b: 0000 0001 ------b = 3
a=a^b: 0000 0011 -----a = 1
这种方法非常巧妙,不需要相加,因此不可能造成数据丢失。
这个操作符很简单,但新手又很容易犯错误,因为这个操作符和数学中的相等是同一个符号,而C语言中的相等用两个等于号表示(“==”)。
= 赋值操作符
此外还拓展了符合赋值操作符,也就是把赋值操作符和以上操作符的结合
int a = 0;
+= a += 1; ----- a = a + 1;
-= a -= 1; ----- a = a - 1;
*= a *= 1; ----- a = a * 1;
/= a /= 1; ----- a = a / 1;
%= a %= 1; ----- a = a % 1;
>>= a >>= 1; ----- a = a >> 1;
<<= a <<= 1; ----- a = a << 1;
&= a &= 1; ----- a = a & 1;
|= a |= 1; ----- a = a | 1;
^= a ^= 1; ----- a = a ^ 1;
所谓单目操作符就是只有一个操作数的操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
以上操作符都为单目操作符,本文重点讲下列操作符
有很多新手甚至书籍会将这个当作一个函数,实际上,这只是一个操作符,如何证明这一点呢?我们都知道函数的调用后面必须得加小括号,我们可以测试以下sizeof后面不加小括号是否可以运行便可。
可以看到,我们得程序是可以写出来的,这也证实了sizeof并不是一个函数,而是一个运算符。
在C语言中,有前置++和后置++,当然也有前置--和后置--,下面则是前置和后置的区别
int a = 0;
int b;
前置:
b = ++a; ------ a += 1; b = a;
后置:
b = a++; ------ b = a; a += 1;
总体来说,前置++便是先加后用,后置++便是先用后加,--也同理。
关系操作符:
> 大于
< 小于
>= 大于等于
<= 小于等于
!= 不等于
== 等于
以上的关系操作符最后的值均为1或0
表达式1 ? 表达式2 :表达式3;
条件表达式与分支语句if极为相似,可以说功能是一致的
如果表达式1的值为真,整个表达式的值为表达式2的值
如果表达式1的值为假,整个表达式的值为表达式3的值
简单的运用以下,如代码
由于a小于b成立,则表达式的值为a的值,然后赋值给c
int main()
{
int a = 3;
int b = 4;
int c = 0;
c = a < b ? a : b;
//最后c的值为3
return 0;
}
语法:
表达式1,表达式2,表达式3.……表达式n
注意:逗号表达式会从第一项开始运算,整个逗号表达式的值为最后一项。
int main()
{
int a = 2;
int b = 5;
int c = (a++, b += a, c = b);
return 0;
}
根据我们上面的定义,我们先计算a++,再计算表达式b += a,最后计算c = b,整个表达式的值为最后一项。即c的最终结果为8。
C的整型算术运算总是至少以缺省整型类型的精度来进行的。
首先我们要提及以下整型提升的概念
整型提升:在表达式计算时,各种整型首先要提升为int类型,如果int类型不足以表示则要提升为unsigned int类型;然后执行表达式的运算。
表达式的整形运算要在CPU相应的运算器内进行,CPU内整型运算器(ALU)的操作数的字节长度 一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此即使是两个字符型相加,也会进行整型提升。那么如何进行整型提升
比如 char ch1 = 1;
变量ch1的二进制补码中只有8个字节,
0000 0001
在发生整型提升后,高位补零,直至32个比特位
比如 char ch2 = -1
变量ch2的二进制补码中也只有8个字节
1111 1111
在发生整型提升后,高位补符号位,ch2的符号位是1,所以补1直至32位。
猜猜下面四个表达式最终输出结果:
int main()
{
char c = 1;
printf("%d\n", sizeof(c));
printf("%d\n", sizeof(c + 1));
printf("%d\n", sizeof(c = c + 1));
printf("%d\n", c);
return 0;
}
最终结果如下图:
您猜对了吗?
第一个大家应该都能想到,char类型的大小是一个字节
第二个进行了整型提升,故结果为4
第三个其实根本就没有进行运算,只是判断了c的类型,故为1(sizeof里的表达式根本不会进行运算)
第四个由于第三个根本没有进行运算,所以答案还是为1
如果两个操作数是不同的类型,其中一个操作数会自动转换为另外一个操作数的类型
long double
double
float
unsigned long int
long int
unsigned int
int
如上列表,由下往上转换,如int类型加上一个float类型,最终的结果为float类型的值。不过在转换过程中很可能会出现精度丢失的情况。
以上就是本文的全部内容了,看到这里,如果觉得小编写的还不错就三连一下吧,感谢支持,我会继续向大家更新更好的内容。