欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

玩转C语言:从入门到精通再到底层(四) - 运算符与类型转换

最编程 2024-08-05 12:11:07
...

一、算术运算符

算术运算符分为单目和双目操作:

  • 单目操作是指对一个操作数进行操作。例如: -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。
~ 位逻辑反
>> 右移
<< 左移