c语言操作符

在了解c语言中的操作符之前,我们先了解一下进制转换以及原码,反码,补码,相关的知识点,

进制转换

 二进制

用字母B表示,逢二进一,每一位中的数字都是0-1的数字表示,编译器中数字前面加上0b表示二进制

printf("%d\n",0b1010);
//最终打印结果为10
八进制

用字母O表示,逢8进一,每一位中的数字都是0-7,编译器中数字前面加上0,表示八进制,由于,每一位八进制的数字最多用3位二进制表示,所以二进制转8进制的时候是3位一组 

printf("%d\n",015);
//八进制的15转成十进制是13
十进制

用字母D表示,逢十进一,每一位中的数字都是0-9 

十六进制

用字母H表示,逢16进一,每一位的数字都是由0-9,A-F表示的,编译器中数字前面加上0x表示十六进制,由于16进制的每一位数字最多用 4位2进制表示,所以二进制转16进制的时候,4位一组。

printf("%d\n",0xf);
//最终打印结果是15
 R进制转10进制

八进制,十六进制,二进制转10进制都可以用权重法,就是每一位进制上的数字乘以这一位数所对应的权重,然后再把所有的乘积相加, 

10进制转R进制

10进制转二进制,八进制,十六进制用短除法,转几除几,一直除到商为0为止,按照从下往上的顺序取余数

2进制8进制互转

二进制转8进制取三合1,以小数点为分界点,小数点左边,从右往左,三位一组,小数点右边,从左往右,三位一组;

8进制转2进制,取1拆3法,

2进制16进制互转

二进制转16进制 ,从右往左,四位一组;

16进制转二进制,取一拆4法

8进制转16进制不能直接转换,通过中间媒介二进制

原码,反码,补码:

 注意!我们只考虑整数,不考虑小数,整数的二进制码有3种形式,原码,反码,补码,但是在计算机里是用补码表示和存储;为什么计算机用补码表示,稍后会有解释。

 一个整型一共占4个字节,一个字节8个比特,共32位,无论是原码,反码还是补码,都是最高位为符号位,剩余的为数值位,最高位为0代表正数,1代表负数。

正数的原码,反码,补码都相同。负数的原反补2各不相同。

原码: 

直接将整数翻译成二进制 

反码: 

符号位不变,其余的全部取反 

补码: 

反码加1, 补码转原码也是符号位不变,其余的取反加一

为什么计算机中整数用补码表示和存储? 

 cpu中只有加法器,没有减法器,他能同时处理加法减法,减法也可以转换成加法;

其次它能够将符号位和数值位统一处理。

举例:1-1=1+(-1) 

如果直接按照两个原码相加,结果是错误的不说,其次符号位和数值位也没得到好的统一处理 

1的原码是:00000000000000000000000000000001
-1原码是:10000000000000000000000000000001
最终相加的结果是:-2,很明显这个结果是错误的

 如果将他们都转成补码的形式运算:

1的补码:  00000000000000000000000000000001
-1补码:   11111111111111111111111111111111
相加后: 100000000000000000000000000000000
可以看到最终相加结果后是33位,但是一个整型只占32位,所以多出的一位会舍去,结果为0,解决了符号位
和数值位不能统一处理的尴尬

移位操作符: 

有两个注意点:

1.移位操作符只针对整数,不针对小数

float a=3.24;
int b=a>>1;
//这样的写法是错误的

2.没有移动负数位这一说法

int a=4;
int b=a>>-1;
//这样的写法也是错误的
左移操作符:<<

 移位规则是左边抛弃,右边补0

右移操作符: >>

右移分为算术右移逻辑右移 ,大多数编译器采用的都是算术右移;

算术右移的规则是,右边抛弃,左边按照原来的符号位填充,是负数就填充1,是正数就填充0;

逻辑右移的规则,简单粗暴,右边抛弃,左边补0,这就可能导致,原来是正数,右移后就变成了负数。

负数左移示例: 

int a=-10;
int b=a<<1;
//-10的原码:10000000000000000000000000001010
//反码:     11111111111111111111111111110101
//补码:      11111111111111111111111111110110
//左移1位后: 11111111111111111111111111101100
             10000000000000000000000000010011
//但是这是补码,我们还要转换成原码,才能得到最后的结果

 //补码转反码:10000000000000000000000000010011
 //反码加1   : 10000000000000000000000000010100
 //最终结果为-20

算术右移示例:

int a=-1
int b=a>>1;
printf("%d\n",b);
//a的原码:10000000000000000000000000000001
//反码   :11111111111111111111111111111110
//补码   : 11111111111111111111111111111111
//右移一位:  11111111111111111111111111111111
//补码转反码:10000000000000000000000000000000
//反码加一: 10000000000000000000000000000001
//结果是-1

 判断下面a,b的打印结果

int a=10;
int b=a<<1;
printf("%d\n",b);
printf("%d\n",a);
//b的结果是20,a的结果还是10,并不会发生改变,<<操作符就相当于+,-,

位操作符: 

注意点:

1. 位操作符只针对 整数,

2. 注意区分按位与&,按位或| 和逻辑与&&,逻辑或||的区别

按位与:&(有0则0,全1为1)
按位或:|   (有1则1,全0为0)
按位异或: ^ (相同为0,不同为1)

按位与&运算 :

int main()
{

int a=5;
int b=-6;
//a的补码:00000000000000000000000000000101
//b的原码:10000000000000000000000000000110
//b的反码:11111111111111111111111111111001

//b的补码:11111111111111111111111111111010
//a的补码:00000000000000000000000000000101
//按位与: 00000000000000000000000000000000
//最终结果为0

return 0;
}

按位或| 运算: 

int a=5;
int b=-6;
//a的补码:00000000000000000000000000000101
//b的补码: 11111111111111111111111111111010
//按位或:  11111111111111111111111111111111
//补码转反 10000000000000000000000000000000
//反码加1  10000000000000000000000000000001
//最终结果为-1

按位异或运算^:

int a=5;
int b=-6;
//a的补码:00000000000000000000000000000101
//b的补码: 11111111111111111111111111111010
//按位异或 11111111111111111111111111111111

//补码转反 10000000000000000000000000000000

//反码加1  10000000000000000000000000000001
//最终结果为-1

利用按位与&操作符判断某个数的二进制位最后1位是0还是1

//通过与1进行按位与
int a=23;
int b=a&1;
printf("%d\n",b);
//a的补码:00000000000000000000000000010111
//1的补码:00000000000000000000000000000001
//按位与 :00000000000000000000000000000001
//最终的结果确实为1

 通过右移操作符和按位与&操作符,判断某个二进制数中某一位是几:

//判断倒数第5位二进制位是几
int a=-17;
int b=a>>4;
int c=b&1;
printf("%d\n",c);

//a的原码:10000000000000000000000000010001
//a的反码:11111111111111111111111111101110
//a的补码:11111111111111111111111111101111
//a右移4位 11111111111111111111111111111110
//与1按位与00000000000000000000000000000001
//最终结果 00000000000000000000000000000000
//注意是判断的是补码的二进制位

按位取反操作符:~(这里的按位取反是包括符号位的)

现在有个题目,将13的二进制数的第5位变成 1

int a=13;
//13的倒数第5位2进制是0
//补码:                        00000000 00000000 00000000 00001101B
//与左移4位后的1进行按位或        00000000 00000000 00000000 00010000B
a=a|(1<<4);
printf("%d\n",a);
//最终打印结果为29

 利用按位取反~操作符再将13的倒数第五位2进制变成0

int a=29
//补码: 00000000 00000000 00000000 00011101B
         11111111 11111111 11111111 11101111B(这一串是左移4位后的1进行了按位取反操作)
         00000000 00000000 00000000 00001101B(最终第5位又变回0了)
a=a & ~(1<<4);
printf("%d\n",a);

逗号表达式 

1.逗号表达式就是用逗号隔开的表达式,逗号表达式也算是一种表达式

2.逗号表达式的优先级最低

3.逗号表达式从左往右依次执行,整个表达式的结果是最后1个表达式的结果 

//判断c是多少
int a=2;
int b=3;
int c=(a>b,a=b+2,a,b=a+1);
printf("%d\n",c);
//最终打印结果为6

 下标引用操作符:[ ]

 1.下标引用操作符是针对数组的

2.共有两个操作数,数组名加上索引值

int arr[10]={1,2,3,4};
printf("%d\n",arr[2]);
//打印下标为2的数组元素,也就是3

函数调用操作符:() 

1.函数调用操作符是针对函数的 

 2.调用函数时,必须带这个操作符

3.函数调用操作符最少有一个操作数,第一操作数必须是函数名,剩下的操作数就是传递给函数的参数值

int add(int x,int y)
{
return x+y;
}

int main()
{

int ret=add(2,3);
//add,2,3就是操作数
printf("%d\n",ret);


return 0;
}

 sizeof是不是函数?答案:不是,他也是一个操作符

int a=4;
int b=sizeof(a);
int c=sizeof a;
//这两种写法都是对的,可以把括号省略掉,通过这里就可以证明sizeof不是函数,因为函数必须带操作符

操作符的优先级与结合性 

 C语言的操作符有两个重要的属性,优先级与结合性,他们在一定程度上决定了表达式求值的计算顺序。

优先级

 如果一个表达式包含多个运算符,根据操作符的优先级运算,优先级高的,先执行,优先级低的,后执行;注意!只有相邻操作符才讨论优先级,隔得太远就没有讨论的必要了。

结合性

如果表达式中的运算符优先级相同,这时就需要看操作符的结合性了,比如3*4/2,乘法和除法的优先级相同,看结合性,根据下面的表可以看出,乘法除法的结合性是从左到右,

常见的运算符优先级顺序:(从高到低)

圆括号——( ),

由于圆括号的优先级最高,可以用它改变其他运算符的优先级

自增,自减——++,--

一元操作符,就是正负号——+,-

乘号,除号——*  ,/

加号,减号——+,  -

关系运算符——>,<,==

赋值运算符—— =

表达式求值:

表达式求值之前,必须要进行两种类型转换,一种是整型提升,一种是算术转换,当表达式中的值转换到适当的类型后,才开始计算,

整型提升:
什么是整型提升:

首先所谓的整型指的是整数类型的数据,比如char类型和short类型,在c语言中,整型算术运算总是以Int型的精度计算的,为了确保精度不会损失,字符型以及短整型的操作数会被转换为普通整型,这就是整型提升。

整型提升的意义:

整型提升最大的意义就在于在一定程度上减少了精度的缺失,由于表达式的整型运算要在cpu的相应运算器件内执行,cpu的整型运算器(ALU)的操作数的字节长度就是int的字节长度,同时也是cpu的通用寄存器的长度,而char类型占1个字节,short类型占两个字节,假如两个char类型的数据相加,可能会产生高位溢出,因为char类型只有8个比特位,而多出的一位就可能会丢失,但是如果进行了整型提升,就有32个比特位了。

通用cpu是难以直接实现两个8个比特字节相加运算,所以表达式中各种小于Int字节长度的整型值,都必须先转换为int或unsign int,才能送入cpu中去计算。

如何进行整型提升:

1.有符号整数是按照变量数据类型的符号位来进行来提升的

2.无符号整数直接高位补0

整型提升代码示例:(并计算最终打印结果)
char a=5;
//5是int型,占32位
//a的补码:00000000000000000000000000000101
//但是因为char型是1个字节,占8个比特位,容纳不下这么多比特,所以要对a进行截断
//00000101   ————截断后的a

//整型提升后的a: 00000000000000000000000000000101 

char b=127;
//b的补码:00000000000000000000000001111111
//截断后的b: 01111111

//整型提升后的b:00000000000000000000000001111111


char c=a+b;
//由于整型运算器alu的操作数的字节长度是int型的一样,不能直接实现两个8个比特字节相加,所以要对a,b进行整型提升,由于char是有符号整数,所以按照变量数据类型的符号位进行提升

//整型提升后的a,b相加
 00000000000000000000000000000101 
 00000000000000000000000001111111
=00000000000000000000000010000100
//由于char型只有8个比特位,所以我们还要进行截断
//截断后的c:10000100

printf("%d\n",c);
//由于%d打印十进制形式的有符号整数,整数占4个字节,32位,所以我们还需要进行整型提升
//整型提升后的c:11111111111111111111111110000100
//但是符号位是1是负数,而且这个是补码,所以我们要将它转换成原码
//反码:1000000000000000000000001111011
//原码:1000000000000000000000001111100=-124

 

算术转换:

如果某个操作符的各个操作数属于不同类型的,那么除非其中的一个操作数转换成另一个操作数的

类型,否则操作就无法正常进行。这样的操作我们称之为寻常算术转换。

算术转换表:

 

算术转换规则: 

如果某个操作数类型在上面的算术转换排名表中靠后,那么就需要转换为另一个操作数的类型,然后才能进行运算。

比如:float类型的和int类型的进行相加,由于int类型靠后,就需要将Int类型转换为float类型。

问题表达式: 

总结:操作符的属性,也就是优先级和结合性,并不能确定表达式的唯一计算路径,所以写代码时一定不要写出模棱两可,特别复杂的代码。

问题表达式1: 
a*b+c*d+e*f;
//虽然乘法的优先级比加法高,但是这并不代表e*f就一定会比第二个加号先执行
//这一串代码会存在两个计算路径
//计算路径1:
第一步:a*b
第二步:c*d
第三步:+
第四步:e*f
第五步:+

//计算路径2
第一步:a*b
第二步:c*d
第三步:e*f
第四步:+
第五步:+


虽然好像这两种计算路径结果一样,但是如果a,b等代表的不是变量,而是表达式,那么这个时候不同的计算路径就会影响到最终的结果,所以向这样的代码,我们最好拆分开写
//示例:
int x=a*b;
int y=c*d;
int z=e*f;

问题表达式2:
c+--c;
//根据操作符的优先级,--优先于+,会先执行--c,然后再执行c,但是我们是要在--c之后准备c的值,还是在--c之前,就准备好c的值呢,这就产生矛盾了
int c=5;
c=c+--c;
//最终表达式结果为4

//所以像上面的代码,我们还是拆分开写
int c=5;
int y=c;
int b=y+--c;

问题表达式3:

int fun()
{

static int a=1;
return ++a;

}

int main()
{

int answer=fun()-fun()*fun();
//即使乘法的优先级比加号高,也并不能决定先调用哪个fun函数,因为上面用来static来修饰变量a,所以每一次的函数返回
结果都是不同的;所以fun函数的调用顺序就会影响到最终的结果
printf("%d\n",answer);

return 0;
}

//像这样的代码我们还是拆开写
问题表达式4:
int i=1;
int ret=(++i)+(++i)+(++i);
printf("%d\n",ret);
//不同的编译器计算结果是不一样的,在vs2022的编译器上最终计算结果为12
//它的计算步骤
第一步:++i
第二步:++i
第三步:++i
第四步:+
第五步:+

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值