一、算术运算符
算术运算符分为单目和双目操作:
- 单目操作是指对一个操作数进行操作。例如: -a是对a进行一目负操作。
- 双目操作(或多目操作)是指两个操作数(或多个操作数)进行操作。
1.双目运算符
- : 加法运算
- : 减法运算
- : 乘法运算
/ : 除法运算
% : 求余运算(又叫模运算)
重点说一下后两个运算符:
- 除法运算符左右两边的数据类型决定了运算结果的类型。两边都是整数结果为整数,有任一方是小数,结果为小数。如果两个整数相除有余数,舍弃余数。运算符右边的数不能为0
- 整除运算符左右两边的数据必须都是整数,结果是这两个数相除的余数值。如果能整除,结果为0
int a = 3;
int b = 2;
int sum, diff, product, res, mod;
// +
sum = a + b;
printf("sum = %d\n", sum);//5
// -
diff = a - b;
printf("diff = %d\n", diff);//1
// *
product = a * b;
printf("product = %d\n", product);//6
// /
res = a / b;
printf("res = %d\n", res);//1
// %
mod = a % b;
printf("mod = %d\n", mod);//1
接下来我们来看它的本质,在相应的ide应该有对象的汇编代码
(去掉打印后的)
idivl 除数存储在ax寄存器 余数在dx寄存器
Cpluse`main:
0x100000f50 <+0>: pushq %rbp
0x100000f51 <+1>: movq %rsp, %rbp
0x100000f54 <+4>: xorl %eax, %eax
0x100000f56 <+6>: movl $0x0, -0x4(%rbp)
0x100000f5d <+13>: movl %edi, -0x8(%rbp)
0x100000f60 <+16>: movq %rsi, -0x10(%rbp)
0x100000f64 <+20>: movl $0x3, -0x14(%rbp) //局部变量a
0x100000f6b <+27>: movl $0x2, -0x18(%rbp) //局部变量b
0x100000f72 <+34>: movl -0x14(%rbp), %ecx
0x100000f75 <+37>: addl -0x18(%rbp), %ecx // a+b的值放到寄存器ecx
0x100000f78 <+40>: movl %ecx, -0x1c(%rbp) //寄存器ecx 的值放到局部变量sum中存储
0x100000f7b <+43>: movl -0x14(%rbp), %ecx
0x100000f7e <+46>: subl -0x18(%rbp), %ecx //a-b的值放到寄存器cx前4个字节
0x100000f81 <+49>: movl %ecx, -0x20(%rbp) //寄存器ecx 的值放到局部变量diff中存储
0x100000f84 <+52>: movl -0x14(%rbp), %ecx
0x100000f87 <+55>: imull -0x18(%rbp), %ecx //a*b的值放到寄存器ecx前4个字节
0x100000f8b <+59>: movl %ecx, -0x24(%rbp) //寄存器ecx 的值放到局部变量product中存储
0x100000f8e <+62>: movl -0x14(%rbp), %ecx
0x100000f91 <+65>: movl %eax, -0x30(%rbp)
0x100000f94 <+68>: movl %ecx, %eax
0x100000f96 <+70>: cltd
0x100000f97 <+71>: idivl -0x18(%rbp) //a/b的值放到寄存器eax前4个字节
0x100000f9a <+74>: movl %eax, -0x28(%rbp) //寄存器ecx 的值放到局部变量res中存储
-> 0x100000f9d <+77>: movl -0x14(%rbp), %eax
0x100000fa0 <+80>: cltd
0x100000fa1 <+81>: idivl -0x18(%rbp) //a%b的余数放到寄存器edx前4个字节
0x100000fa4 <+84>: movl %edx, -0x2c(%rbp) //寄存器ecx 的值放到局部变量mod中存储
0x100000fa7 <+87>: movl -0x30(%rbp), %ecx
0x100000faa <+90>: movl %ecx, %eax
0x100000fac <+92>: popq %rbp
0x100000fad <+93>: retq
2.单目运算符
- ++ 操作数加1
- -- 操作数减1
这几句最终达到的效果相同,在混编中执行的逻辑也完全相同
x = x + 1;
x++;
++x;
但是进行赋值操作就会导致差异
x = m++; // 表示将m的值赋给x后, m加1。
x = ++m; // 表示m先加1后, 再将新值赋给x。
二、赋值语句中的数据类型转换
1. 赋值运算符
int a = 0;
a = a + 5;
a += 5;
a = a - 3;
a -= 3;
第二行和第三行意思相同,第四行和第五行意思相同,可以互相替换。这是一种C语言中的简便写法。
2. 类型转换
类型转换是指不同类型的变量混用时的类型改变。
2.1 隐式类型转换
基本原则:
- 在赋值语句中, 等号右边的值转换为等号左边变量所属的类型
- 不同类型混合计算时,结果类型为数据类型级别较高的
- 所有的浮点预算都是以double进行的
数据类型级别顺序:
char, short < int < float < double
int a, b = 3;
float f = 1.5;
a = f * b; // 整数和浮点类型运算,结果为浮点类型,因为它类型级别高
printf("a = %d\n", a);
a = f; // 把一个浮点类型的变量赋值给整数类型,小数部分会被自动舍去
printf("a = %d\n", a);
这里有个重点问题需要强调,浮点类型的和整数类型计算时,结果为浮点类型。我们看一个常见问题。
float f;
int a = 5;
f = a / 2;
printf("f = %f\n", f);
这段程序的运行结果是:
f = 2.0000
原因在于,a是个整数,2也是个整数,它们的计算结果也是整数。此时,就已经舍弃了计算结果中的小数部分。因此,赋值时就自然没有小数部分。
如果我们需要得到f = 2.5怎么办呢?可以这样写:
float f;
int a = 5;
f = a / 2.0;
printf("f = %f\n", f);
因为整数a和浮点数2.0计算的结果是浮点型(double),因此保留了小数部分。之后再把=右边的double类型转换成左边的float类型。仔细想想,能理解吧。
2.2 强制类型转换
在计算中,我们常常需要主动要求计算机改变变量的类型。这是可以这样做。
(数据类型)(表达式或变量)
按照这种格式写,右边部分的类型就会被强制转换成左边括号中的类型。看看具体代码:
float f;
f = 6.6 / 3;
printf("f = %f\n", f);
f = (int)6.6 / 3;
printf("f = %f\n", f);
f = (int)(6.6 / 3);
printf("f = %f\n", f);
三、关系运算符
1.关系运算符
关系运算符的作用是比较符号两边的元素
> 大于
>= 大于等于
< 小于
<= 小于等于
== 等于
!= 不等于
关系运算符都是双目运算符,其结合性均为左结合。关系运算符的优先级低于算术运算符,高于赋值运算符。也就是说,在一个没有括号的关系运算表达式中,<、<=、>、>=要先于==和!=发挥作用。相同级别的预算符从左向右计算。
2.关系表达式
关系表达式的一般形式为:
例:
a + b > c - d
x > 3 / 2
‘a’ + 1 < c
-i - 5 * j == k + 1
也允许出现嵌套的情况。例:
a > (b > c)
a != (c == d)
关系表达式的值是“真”和“假”,用“1”和“0”表示。如:
5 > 0的值为“真”,即为1;
(a = 3) > (b = 5)由于不成立,故其值为假,即为0。
例:
char c = 'k';
int i = 1, j = 2, k = 3;
float x = 3e + 5, y = 0.85;
printf("%d, %d\n", 'a' + 5 < c, -i - 2 * j >= k + 1);
printf("%d, %d\n", 1 < j < 5, x - 5.25 <= x + y);
printf("%d, %d\n", i + j + k == -2 * j, k == j == i + 5);
- 字符变量以它对应的ASCII码参与运算
- 对于含多个关系运算符的表达式,如k == j == i + 5,根据运算符的左结合性,先计算k == j,该式不成立,其值为0,再计算0 == i + 5,也不成立,故表达式值为0。
表达式为真,值为1。表达式为假,值为0
需要注意的是,C语言中经常用0代表假,非零代表真。
四、逻辑运算符
逻辑运算符相当于数学中的(且、或、非),我们叫做“逻辑与”、“逻辑或”和“逻辑非”。
&& 逻辑与
|| 逻辑或
! 逻辑非
前两个是双目运算符,最后一个是单目预算符。
逻辑与:两边都为真时返回真,否则返回假;
逻辑或:只要任意一个为真就返回真,否则返回假;
逻辑非:符号右边是真,则返回假;符号右边是假,则返回真。
三、按位运算符
C语言和其它高级语言不同的是它完全支持按位运算符。这与汇编语言的位操作有些相似。
按位运算符有:
& 位逻辑与 // 参加运算的两个数,换算为二进制(0、1)后,进行与运算。只有当相应位上的数都是1时,该位才取1,否则该为为0。
| 位逻辑或 //参加运算的两个数,换算为二进制(0、1)后,进行或运算。只要相应位上存在1,那么该位就取1,均不为1,即为0。
^ 位逻辑异或 //参加运算的两个数,换算为二进制(0、1)后,进行异或运算。只有当相应位上的数字不相同时,该为才取1,若相同,即为0。
~ 位逻辑反
>> 右移
<< 左移