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.关于整型提升可以见整型提升
青山不改 绿水长流