最强操作符学习之路(C语言详解)

本文详细介绍了C语言中的算术、移位、位操作符及其使用规则,包括整型提升、表达式求值顺序、类型转换等概念。重点讲解了操作符的优先级和结合性,并通过实例解释了为何某些表达式可能导致不确定性结果。此外,还探讨了复合赋值符、单目操作符以及类型转换在编程中的应用。
摘要由CSDN通过智能技术生成

索引:

一. 各种操作符的介绍。
二. 表达式求值

一。各种操作符的介绍。
1,算数操作符
+    -   *   /   %
/    分子分母如果都是整数 那么结果必然是整数 ,如果分子分母至少有一个浮点数 那么结果就是浮点数 这里面涉及到的算术转换在下面会提到  
%   取模操作符,取余数,这个操作符只能作用与整数   下面我用代码阐述一下我介绍的上述两点
#include<stdio.h>
int main() {                                                                       
    int a = 3;                                                                                  
    int b = 2;
    float c = 0.2;
    int d = a / b;
    int e = a % b;
    printf("%d %d ", d, e);
    return 0;
}                                                          这段代码输出的应该是 1  1

 

 这样写程序运行的很良好 但是如果这样

 程序虽然不报错 但是会出现警告 实际上算出的浮点数 因为上述代码是非要把给结果f设置成int类型    这样如果数字大了 可能会造成精度丢失  这是一种不好的操作习惯 我们要避免

但如果把取模操作符中某一个数字换成浮点数 那么程序就会报错   

在讲下列操作符之前,我们得先知道一个概念,每个数字都有三个码——原码,反码,补码,数字在电脑中储存的二进制位都是该数字的补码   但是电脑上面显示的却是数字的原码

三者关系如下

以int类型整数为例,因为int是四个字节 32个比特位 所以由32位组成,最前面的一位是符号位,0为正,1为负

如果正数 eg:1  它的原码,反码,补码都是00000000000000000000000000000001

负数不一样 eg  -2   原码:10000000000000000000000000000010

                               反码: 111111111111111111111111111111111101  除符号其他全部取反

                               补码: 111111111111111111111111111111111110  反码+1等于补码

下述操作符若无明显提示  那么电脑操作的就是补码

2.移位操作符

<< 左移操作符
>> 右移操作符
   
注:移位操作符的操作数只能是整数。
操作的对象都是二进制位  
 eg:int a = 4;
  
它的二进制位:00000000000000000000000000000100(补码);
2.1 左移操作符
<<:
移位规则:
左边抛弃、右边补0
上述的4可能不明显 我们换成15

15的二进制位:

00000000000000000000000000001111
我们把这个左移两位:变成了00000000000000000000000000111100
现在这个数字是

我们看看代码具体是怎么操作的

再看一个图加深一下理解

>> 右移操作符 

移位规则:
首先右移运算分两种:
1. 逻辑移位
左边用 0 填充,右边丢弃
2. 算术移位
左边用原该值的符号位填充,右边丢弃
至于为什么有两种?
因为整数分为有符号整形和无符号整形
有符号整型右操作符实行的是算术移位  就是当把数字的二进制位补码向右移动是  最左边(最高位)的数字是0 那么左边全补0 最左边的数字(最高位)是1,那么左边全补1
而无符号整形右移补的都是0.
什么叫有无符号整形?
eg:-1 的二进制位  11111111111111111111111111111111补码
下面代码我列举了-1分别是有符号整型和无符号整型在都向右移动两位的区别

第一个-1我用的是有符号整型  int 

第二个-1我用的是无符号整型unsigned   区别很明显啦 

4. 位操作符

& // 按位与
| // 按位或
^ // 按位异或
注:他们的操作数必须是整数。
& // 按位与: 两个数的二进制位只有同时为1时候才为1,不同的话就为0
| //按位或:   两个数的二进制位只有同时为0时候才为0,不同的话就为1,同时为1也是1
^ // 按位异或: 两个数相同为0,相异为1

题目:
不能创建临时变量(第三个变量),实现两个数的交换。
编写代码实现:求一个整数存储在内存中的二进制中1的个数。
思考一下这两个题目 我会在下一篇博客阐述这两题的具体解法
5.复合赋值符
其实就是赋值操作符的延申
eg: a = a+1;可以写成a+=1;a = a*1写成a*=1;
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=     了解即可 
6. 单目操作符
!           逻辑反操作
-           负值
+           正值
&           取地址
sizeof       操作数的类型长度(以字节为单位)
~           对一个数的二进制按位取反
--           前置、后置 --
++           前置、后置 ++
*           间接访问操作符 ( 解引用操作符 )
( 类型 )       强制类型转换
先阐述!与~

在c语言中,!和~均表示取反,这两个的区别在于:

  1. ! :代表逻辑取反,即:把非0的数值变为0,0变为1;
  2. ~ :表示按位取反,即在数值的二进制表示方式上,将0变为1,将1变为0;

至于取地址& 与sizeof 我在这就不过多阐述了 在我之前的博客都有详解

 还有一个就是前置++与后置++的问题了

个人理解:

前置++:先++后使用

后置++:先使用后++

减法也是一样的

eg:
// 后置 ++ --
#include <stdio.h>
int main ()
{
    int a = 10 ;
    int x = a ++ ;
   int z = ++a;//先对a增加1变成11,然后赋值给z
    // 先对 a 先使用,再增加,这样 x 的值是 10 ;之后 a 变成 11
    int y = a -- ;
    // 先对 a 先使用,再自减,这样 y 的值是 11 ;之后 a 变成 10
    return 0 ;
}
*           间接访问操作符(解引用操作符)
这个我目前理解为是跟指针有关的
eg: 
#include<stdio.h>
int main() {
    int a = 0;
    int* p = &a;//那么这边p就是a的地址 *p代表的就是a
    *p = 20;//*存在告诉编译器p是一个指针变量  
    printf("%d\n", a);
    return 0;
}
还有二级指针 三级指针 我在后续会介绍
(类型)       强制类型转换:强制类型转换是把变量从一种类型转换为另一种数据类型。

   强制类型转换算是C语言中常见常考的一项内容,如对于类型处理不好,将会产生错误结果。对于某些类型的转换编译器可隐式地自动进行,不需人工干预,称这种转换为自动类型转换;而有些类型转换需要编程者显式指定,通常,把这种类型转换称为强制类型转换

计算机硬件进行算术操作时,要求各操作数的类型具有相同的大小(存储位数)及存储方式。

eg;#include<stdio.h>
int main() {
    double a = 6.7;
    int b = 2;
    int c = b + (int)a;
    printf("%d", c);
    return 0;
}
如果不加(int)那么这个b会先转换成浮点型 然后在复制给整型c 这样的话可能会出现某些误差或者警告,而加了(int)后计算机会将a识别成整型  不会产生什么误差
而eg:int (x+y)表示整型
   
          (double) x表示浮点型
至于在计算机中数字是如何计算的    这里面涉及到    表达式求值


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

C 的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为 整型
提升
整型提升的意义
表达式的整型运算要在 CPU 的相应运算器件内执行, CPU 内整型运算器 (ALU) 的操作数的字节长度
一般就是 int 的字节长度,同时也是 CPU 的通用寄存器的长度。
因此,即使两个 char 类型的相加,在 CPU 执行时实际上也要先转换为 CPU 内整型操作数的标准长
度。
通用 CPU general-purpose CPU )是难以直接实现两个 8 比特字节直接相加运算(虽然机器指令
中可能有这种字节相加指令)。所以,表达式中各种长度可能小于 int 长度的整型值,都必须先转
换为 int unsigned int ,然后才能送入 CPU 去执行运算

总结:如果是在计算中小于int的类型计算机会先将其转换成int类型  在进行下面的计算,  

// 实例 1
char a , b , c ;(换成short也是一样)
...
a = b + c ;
b c 的值被提升为普通整型,然后再执行加法运算。
那么整型如何提升  怎么证明整型提升存在呢?
整形提升是按照变量的数据类型的符号位来提升的
// 负数的整形提升
char c1 = - 1 ;
变量 c1 的二进制位 ( 补码 ) 中只有 8 个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为 1
提升之后的结果是:
11111111111111111111111111111111
// 正数的整形提升
char c2 = 1 ;
变量 c2 的二进制位 ( 补码 ) 中只有 8 个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为 0
提升之后的结果是:
00000000000000000000000000000001
// 无符号整形提升,高位补 0
证明整型提升:
#include<stdio.h>
int main()
{
    char a = 0xb6;
    short b = 0xb600;
    int c = 0xb6000000;
    if (a == 0xb6)
        printf("a");
    if (b == 0xb600)
        printf("b");
    if (c == 0xb6000000)
        printf("c");
    return 0;
}
输出:c
因为a和b发生了整型提升 且a b是有符号类型 所以整型提升后变成的是负数  
a在内存中二进制是 1100 0110  通过上述对整型提升的阐述可以看出提升后a
的第一位是1 是负数 所以才这样讲
但c不用发生任何变化  因此可证明
再举个例子:
int main ()
{
char c = 1 ;
printf ( "%u\n" , sizeof ( c ));                                  输出:1  4   4
printf ( "%u\n" , sizeof ( + c ));
printf ( "%u\n" , sizeof ( - c ));
return 0 ;
}
sizeof()括号内部不会进行计算eg:
 #include <stdio.h>
  int main()
  {
  int i;
  i = 10;
  printf("%d\n", i);
  printf("%d\n", sizeof(++i));
  printf("%d\n", sizeof(i++));
  printf("%d\n", i);
  return 0;
  }

输出:10 4 4 10

如果sizeof的操作数是一个表达式的话,这个表达式时不会被计算的。
  sizeof当预处理看就行了,它后面括号里的东西,根本不求值,只根据C的一堆规则判断结果类型,然后返回结果类型的大小

上述我们了解到char类型和short类型需要转换成int 那么之后呢?——算数转换

long double
double
float
unsigned long int
long int
unsigned int
int
如果没有特别说明 在两种不同类型(类型大小大于等于int) 遵循下面的自动转化成上面的类型
如:
int a = 9;
double c = 6.7;
b =  a+c;//这边b的类型就是double 就理解为double的精度更高吧
赋值中的类型转换 
当赋值运算符两边的运算对象类型不同时,将要发生类型转换, 转换的规则是:把赋值运算符右侧表达式的类型转换为左侧变量的类型。

double a = 2.5;
int b = a;//b的值为2 计算机自动省略小数点后面的数 但是这会造成精度丢失 不建议使用

现在我们拿到了数字类型都知道了  但是计算是还应该考虑一个东西

复杂表达式的求值有三个影响的因素。
1. 操作符的优先级
2. 操作符的结合性
3. 是否控制求值顺序
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
操作符优先级

 

记忆方法:

记住一个最高的:构造类型的元素或成员以及小括号。
记住一个最低的:逗号运算符。
剩余的是一、二、三、赋值。
意思是单目、双目、三目和赋值运算符。
在诸多运算符中,又分为:
算术、关系、逻辑。
两种位操作运算符中,移位运算符在算术运算符后边,逻辑位运算符在逻辑运算符的前面。再细分如下:
算术运算符分 * , / , % 高于 + , - 。
关系运算符中,〉,〉 = , < , <= 高于 == ,! = 。
逻辑运算符中,除了逻辑求反(!)是单目外,逻辑与( && )高于逻辑或( || )。
逻辑位运算符中,除了逻辑按位求反(~)外,按位与(&)高于按位半加(^),高于按位或( | )。
这样就将15种优先级都记住了,再将记忆方法总结如下:
去掉一个最高的,去掉一个最低的,剩下的是一、二、三、赋值。双目运算符中,顺序为算术、关系和逻辑,移位和逻辑位插入其中。(其实我直接也不能完全记住 所以在写代码的过程中还是加小括号吧 hh
 

知道操作符的结合性以及优先级 计算机的结果未必·一定

eg:

a*b + c*d + e*f

两种理解:
a * b
c * d
a * b + c * d
e * f
a * b + c * d + e * f
a * b
c * d
e * f
a * b + c * d
a * b + c * d + e * f
eg:c + --c;
注释:同上,操作符的优先级只能决定自减 -- 的运算在 + 的运算的前面,但是我们并没有办法得
知, + 操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义
的。
int main()
{
 int i = 10;
 i = i-- - --i * ( i = -3 ) * i++ + ++i;
 printf("i = %d\n", i);
 return 0; }

再举个例子

int main()
{
 int i = 10;
 i = i-- - --i * ( i = -3 ) * i++ + ++i;
 printf("i = %d\n", i);
 return 0; }
在不同编译器中测试结果:非法表达式程序的结果

总结 :我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题 的(所以遇到这种表达式我们不必纠结 它本身就是错的 hh)
  • 8
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

每天少点debug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值