C语言复习(3)——操作符详解

C语言复习(3)——操作符详解

在这里插入图片描述

3.1 算数操作符

+ - * / %

除了%(取余数)操作符,其余操作符的都是既适用于整数类型又适用于浮点类型。当 / 操作符两边都是整数时,他执行整除运算,其他情况下执行浮点数除法。

3.2 移位操作符

顾名思义,移位操作符就是将一个数的二进制位向左或向右移动。首先看一下原码和补码的转换
左移操作符 <<
右移操作符 >>

	a<<i;
	//左操作数 a 将移动由右操作数 i 指定的位数

左右操作数必须为整数。
在左移位中,值最左边的几位被丢弃,右边多出来的几个空位由0补齐。
在计算机系统中,数值一律用补码来表示和存储。

	int main()
{
	int a = 8;
	//移位前(32位)
	//00000000 00000000 00000000 00001000
	a = a << 1;
	//向左移1位后
	//00000000 00000000 00000000 00010000
	printf("%d\n", a);
	return 0;
}

a=2 ^ 3 --> 左移1位 -->a=2 ^ 4
在这里插入图片描述
移出左边界的那几位丢失,右边空出来的几位由0补上。
右移操作比左移多出一个问题,看进行移位的是否为符号数,其中最高位表示符号位,1表示负数,0表示正数。

正数的补码是原码自身。
负数补码是通过原码计算得到,计算过程为:符号位不变,其余位按照原码取反加1。

如果进行移位的是无符号数,则进行的是逻辑移位,左边移入的位由0填充。若是有符号数,则为算数移位,左移的位又原先该值的符号位决定,符号位为1则移入的位均为1,若为0则均移入0,这样可以保证原数的正负形式保持不表
右移
在这里插入图片描述
对于有符号数来说,其算数左移和逻辑左移是相同的,他们只是在右移时不同,而且只有当操作数是负值时才不一样。
注意避免以下形式的移位

a << -5;

练习:使用移位操作来计算一个二进制数中有多少个1

int count_one_bits(unsigned value)//计数函数
{
int one = 0;
for (one = 0; value != 0; value >>= 1)
{
	if (value%2==1 )//最低位为1,计数增1
	{
		one++;
	}
}
return one;
}

int main()
{
	unsigned int value;//避免右移位的歧义 接收无符号数
	scanf("%d",&value);
	int i=count_one_bits(value);
	printf("有%d个1\n", i);
	return 0;
}

3.3 位操作符

&  |  ^

以上三个操作符分别执行 异或逻辑运算。
进行 & 操作时,如果两个位都是1,结果为1,否则结果为0;
进行 | 操作时,如果两个位都是0,结果为0,否则结果为1;
进行 ^ 操作时,如果两个位不同则为1,相同结果为0。
在这里插入图片描述

举例
a:    00101110
b:    01011011
a&b:  00001010
a|b:  01111111
a^b:  01110101

利用移位操作符和位操作符可以操纵整型值中的单个位。
例如:把指定位置的位设置为1
0000——将第二位设为1——0010

int value=0;
value = value | 1 << 2;//移位运算符的优先级比位运算符高

把指定的位清零
0010——将第二位设为0——000

value=value&~(1<<2);

取反操作符 :按位取反

~

3.4 赋值

3.4.1赋值操作符

把右操作数的值存储于左操作符存储的指定位置。

x=y+3;

赋值也是个表达式,表达式就会有返回值,赋值表达式的值就是左操作数的新值,它可以作为其他赋值操作符的右操作数。

	z=x=y+3;
	//求值顺序是从右到左,所有这个表达式等于:
	z=(x=y+3);
	//它的意思和下面语句完全相同
	z=x=y+3;
	z=x;

注: 使用上述 连等 操作时注意声明的变量是相同类型,如果x为字符型变量,其他为整型变量,那么如果y+3的值超过字符型的1字节范围,就会被x截断后存储在x中,同时z也会受到影响。

int main()
{
	char x;//1字节=8比特位 x默认为符号数 取值范围在  [-2^7,2^7-1]
	int z;//4字节=32比特位 z范围在 [-2^31,2^31-1] 
	z=x = pow(2, 7);
	printf("%d\n", x);
	printf("%d\n", z);
	return 0;
}

在这里插入图片描述
此处因x的截断,缩小了z的取值范围。
还有一个常见的错误 先看看以下代码:

char ch;
....
while(  ( ch==getchar() ) != EOF  )...

EOF所能提供的位数比字符型所能提供的位数要多。当getchar()的返回值首先被ch截短后存储。然后这个截短的值被提升为整型并和EOF比较。如果读取的值为\377的字节时,循环将会终止,因为,这个值截短再提升之后与EOF相等。

3.4.2 复合赋值符

+=   -=   *=   /=   %=
<<=  >>=  &=   |=   ^=

以+=操作符举例

a+=expression;

等价于:

a=a+(expression);//注意括号,在赋值之前已完整求值

复合赋值符的优势:
a表达式如果较为复杂,复合赋值符则只需写一次,降低书写错误,而且效率更高,同时增值表达式都在等号右边,方便代码的阅读。

3.5单目操作符

只接受单个操作数的操作符,他们是:

!  ++  -  &  sizeof
~   --  +  *  (类型) 

下面我们逐一介绍。
操作符执行逻辑反操作,如果操作数为真,其结果为假,如果操作数为假,其结果为真。和关系操作符一样,这个操作符产生一个结果,0或1。

~操作符对整型类型进行求反操作,二进制数中原先为1的位变为0,原先为0的位变为1。

-操作符产生操作数的负值。

+操作符产生操作数本身,其实什么都不干,只为和-操作符凑成一对。

& 操作符产生操作数的地址,操作如下:

int a,*b;
...
b=&a;

&操作符取整型变量a的地址,并把它赋值给指针变量。

*间接访问操作符,它与指针一起使用,用于访问指针所指向的值。以上述代码为例,b的值为a的地址,*b的值则是a的值。

sizeof 操作符返回操作数的类型长度,单位:字节。操作数可以是表达式或单个变量,也可以是类型名。

	sizeof(int);
	sizeof (x);

第一个表达式返回整型的字节数,取决于编译器。第二个表达式返回x所占据的字节数,如果x为数组名,则返回该数组的长度。
当然x也可以是表达式,但是当判断表达式的长度时并不会对表达式求值,
在这里插入图片描述
在这里插入图片描述
可见sizeof(a=b+1)没有对a赋值。

(类型)操作符称为强制类型转换,把表达式的值转换为另外的类型,为了使整型变量a对应的浮点数值,可以这么写:

(float)a;

强制类型装换的优先级很高,把他放在表达式中只会改变表达式第一个项目的类型。如果要对整个表达式的结果强制类型转换,则必须把整个表达式用括号括起来。
类型转换使用场合很多,例如使用malloc函数时,返回的是void*变量,则需使用强制类型转换来把返回值变成自己想要的指针变量,例如

char* str=(char*)malloc(15);
//在指针str的位置往后开辟15个字节的空间

具体见C库函数—malloc

++--操作符分别为前缀和后缀形式
前缀形式的++操作符出现在操作数的左边,表达式的值就是增加1后的值(先加再用)
后缀形式的++操作符出现在操作数的右边,操作数仍会+1,但是表达式的值是操作数增加前的值(先用再加)--同理
举例

int a,b,c,d;
a=b=10;
c=a++;
//a先用再加,c为10,a增加到11
d=++b; 
//b先加再用,b增加到11,d得到值11

注意不可以把a++视为变量,它只是一个值,所以不能出现以下的代码

a++=10;

3.6 关系操作符

>  >=    <  <=     !=  ==

如果两端的操作数复合操作符指定的关系,表达式结果为1,如果不符合,表达式的结果为0。
于是可以产生一些简写写法

if(expression!=0)....
if(expression)....

if(expression==0)....
if(!expression)....

注意在出现测试相等性的地方出现赋值符是合法的,而并非语法错误。

x=4;
if(x=5)
	执行某些任务

x得到值4,但接下来我们把5赋值给x,而不是把x与5比较,从而丢失了原来x的值,而且由于表达式的值是x的新值(非0),故if语句将始终为真。
为了避免这种情况可以反过来写

if(5==x)

如果写成:

if(5=x)

编译器则为报错,应为无法给一个常量赋值。

3.7 逻辑操作符

&&   ||

首先看一下 &&

expression1 && expression2

如果expression1和expression2都为真,表达式的结果才为真。任何一个表达式为假,则整个表达式为假。这种表达式的有趣之处在于他会控制子表达式求值的顺序。例如下面的这个表达式:

a>5 && a<10

&&优先级比<> 的优先级都要低,,所以子表达式实际上是按下面的方式组合的:

(a>5) && (a<10)

工作原理:&&操作符的左操作数总是首先进行求值,如果他的值为真,然后紧接着对右操作数进行求值。如果左操作数为假,那么右操作数就不再进行求值,因为整个表达式结果一定为假。

||操作符

expression1 || expression2

工作原理:两者其一为真则整个表达式为真,||表达式也是首先对左操作数求值,如果他的值为真,那么就不用对右操作数求值,因为整个表达式的值已经确定了。这种行为称为 “短路求值”

注意 位操作符和逻辑操作符可能产生混淆,||&&操作符具有短路性质,如果表达式的值根据 左值就能确定,就不用往后 求右操作数的值。但|&操作符需要左右操作数求值。
其次,逻辑操作符用于测试0值和非0值,位操作符比较操作数中的位。

if(a>b && c<d)...
if(a>b &  c<d)...

因为> <操作符产生的结果为0或1,所以上述两条语句产生的的结果是一样的。但是如果a是1,b是2,下一对语句就不会产生同样的结果:

if(a && b)...
if(a & b)...

因为a,b皆为非零值,所以第一条语句的值为真,但第二条语句为假,因为在二进制位中没有一个位是相同的1。

3.8 条件操作符

expression1?expression2:expression3;

右三个操作数的运算符称为三目运算符,其同样具有短路求值的性质,首先计算expression1,为真(非0),则整个条件语句为expression2的值,expression3不会求值。但是如果expression1为假(0),则整个条件语句为expression3的值,expression2不会求值

什么地方可以用到条件运算符呢?

if(a>0)
{
	b[2*c+d*(e/5)]=3;
}
else
{
	b[2*c+d*(e/5)]=-3;
}

b数组长长的下标表达式需要写两次,有些麻烦,这里就可以使用条件操作符:

b[2*c+d*(e/5)]=a>0?3:-3;

这样就能降低多次书写导致写错的概率,同时方便阅读。

3.9 逗号操作符

exp1,exp2,exp3,....,expn;

表达式自左向右逐个求值,整个表达式的值就是expn(最后一个)表达式的值。擅于使用逗号,可简化代码,例如

a++;
b++;
while(a>0)
{
	...
	a++;
	b++;
}

可简化为

while(a++,b++,a>0)
{
	...
}

3.10 下标引用

数组中的下标引用操作和简介访问表达式是等价的:

array[小标]
*(array+(下标))

具体使用见以后的指针章节。

3.11 总结

1.算数移位操作仅在右移位负值中其作用,其余为逻辑移位。
2.误用=而不是==进行比较
3.误用|替代||,误用&替代&&
4.注意求值顺序(短路求值)
5.使用复合赋值符(++=>>=…)便于程序维护
6.使用逗号操作符消除多余代码
7.使用条件操作符替代if简化表达式
8.关于整型提升可以见整型提升

青山不改 绿水长流
  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值