提示:c语言的操作符非常丰富,并且某些操作符可能直接影响二进制位,要把这些操作符灵活的使用是需要深刻的理解并且长期使用的。
目录
前言
提示:这里可以添加本文要记录的大概内容:
本节核心内容,掌握一些不常见但用起来很高级的操作符。
提示:以下是本篇文章正文内容,下面案例可供参考
一、原码、反码、补码是什么?
首先我们知道,整数的二进制有三种表示方法,即原码、反码、补码。
在计算机内一个正整数是由4个字节构成的,一个字节是由8个比特位构成的,所以说一个正整数是由32个比特位来决定的,也就是说由32个二进制数来表示的。其中最高位表示“符号位”,其余31位表示”数值位”。
对整数而言,符号位0表示整数,1表示负数。
正整数:
原码、反码、补码相同
负整数:
原、反、补码三种表达方式都不同
原码:按照正负数的形式直接改写成二进制数。
反码:符号位保持不变,同时其它全部按位取反得到反码。
补码:在反码的基础上+1。
*对于整形来说,计算机中存储的都是补码。
*对于无符号位整数来讲,要按照正整数对待
二、移位操作符
左移操作符:<<
右移操作符:>>
移位操作符只能对整数使用!!!
左移操作符:
左边丢弃右边补0
#include<stdio.h>
int main()
{
int num=10;
int b = num<<1;
printf("%d",num);
printf("%d",b);
return 0;
}
通过打印,我们发现b的值变成了20,但是num的值保持不变。这说明在使用移位操作符时,并不改变自身的数值。
———————————————————————————————————————————
右移操作符:
1.逻辑右移:右边丢弃,左边补0。
2.算数右移:右边丢弃,左边用原该值的符号位进行填充。
逻辑右移:
算数右移:
注意:不能移动负数位(这个是标准中为进行定义的)
例:
int num = 0;
num>>-1; //error
三、按位操作符
按位操作符包括:& 按位与
| 按位或
^ 按位异或
~ 按位取反
注:他们的操作数必须为整数
1.按位与 &
按位与 - 二进制中的变化:有0为0,两个都是1才为1。
例子如图:
int main()
{
int a = 5;
int b = -6;
int c = a & b; //按位与 - 对应的二进制位,有0则为0,两个同时为1才是1
//00000000000000000000000000000101 ——>5的原码
//10000000000000000000000000000110 ——>-6的原码
//11111111111111111111111111111001
//11111111111111111111111111111010 -6的补码
//00000000000000000000000000000101
//11111111111111111111111111111010
//00000000000000000000000000000000
//
printf("%d\n", c);
return 0;
通过按位与我们可以查看二进制中的任意位置上的数字是1还是0
如下代码:可以判断a向右移位4个整数 按位与为1.
int main()
{
int a = 21;
//a&1
//
//00000000000000000000000000010101
//00000000000000000000000000000001
//00000000000000000000000000000001
//(a>>4)&1 == 1 ————>表示a的二进制向右移动4位 与 1按位与
//
return 0;
}
练习1:编写代码实现:求⼀个整数存储在内存中的⼆进制中1的个数。
首先,如果题目问我们123或者更大的数(十进制)中1的个数,我们会从右向左依次来查1的个数
123%10=3
123/10=12
12%10=2
12/10=1
1%10=0 当%数字时变为0时,结束计算。
那么,二进制与其类似,10进制转换为2进制我们学过,由下往上依次%2得余数就是二进制。
从右向左依次来查1的个数,但是本质上是也是分两步,如下所示:
125%2=1
125/2=62
62%2=0
62/2=31
31%2=1
…………依次类推
同理先%出来的数字对应的同样是最后的数字
方法一:
参考代码:
//⽅法1
#include <stdio.h>
int main()
{
int num = 10;
int count= 0; //计数
while(num)
{
if(num%2 == 1)
count++;
num = num/2;
}
printf("⼆进制中1的个数 = %d\n", count);
return 0;
}
但是此方法也有缺陷,当num=负数时:
当num=-1;
num%2==-1 不等于1;
此时-1/2=0 跳进while(num) 结束循环
此时count=0.
由此可见,方法一只适合正整数。
方法二:
那么有什么办法能让-1也适用呢?
#include <stdio.h>
int main()
{
int num = -1;
int i = 0;
int count = 0; //计数
for(i=0; i<32; i++)
{
if( num & (1 << i) ) //如果按位与=1就count++
count++;
}
printf("⼆进制中1的个数 = %d\n",count);
return 0;
}
好,-1这个问题解决了,能不能再优化一下?怎么改进呢?
#include <stdio.h>
int main()
{
int num = -1;
int i = 0;
int count = 0; //计数
while(num)
{
count++;
num = num&(num-1);
}
printf("⼆进制中1的个数 = %d\n",count);
return 0;
}
这样我们的问题就解决了,其实就是最有边数字借位的道理。
2.按位或 |
按位或 - 对应的二进制位上有1则为1,两个同时为0才为0。
例子如图:
int main()
{
int a = 5;
int b = -6;
//int c = a | b;//按位或 - 对应的二进制位上有1则为1,两个同时为0才为0
//00000000000000000000000000000101 5的原码
//10000000000000000000000000000110
//11111111111111111111111111111001
//11111111111111111111111111111010 -6的补码
//
//00000000000000000000000000000101
//11111111111111111111111111111010
//11111111111111111111111111111111
//
printf("%d\n", c);
return 0;
}
3.按位异或 ^
按位异或 - 对应的二进制位上相同则为0,相异则为1。
例子如图:
int main()
{
int a = 5;
int b = -6;
int c = a ^ b;//按位异或 - 对应的二进制位上相同则为0,相异则为1
//00000000000000000000000000000101 5的原码
//10000000000000000000000000000110
//11111111111111111111111111111001
//11111111111111111111111111111010 -6的补码
//
//00000000000000000000000000000101
//11111111111111111111111111111010
//11111111111111111111111111111111
//
printf("%d\n", c);
return 0;
}
相信你现在对按位与有一个基本的认识,那么在哪里能用到它呢,早在之前谷歌就曾拿它当作一道“变态”的面试题:
——不能创建临时变量(第三个变量),实现两个数的交换。
对于创建临时变量进行交换的问题,我们再来熟悉一下。
在生活中我们想让一瓶醋和一瓶酱油进行交换,必须用第三个瓶子来进行辅助,如图:第1步,先将醋倒进名为tmp的空瓶子。第2步,再将酱油倒入醋瓶子。第三步,将tmp瓶子内的醋倒进原本的酱油瓶。至此,完成交换。
那么在程序中,程序是如何实现的呢?
int main()
{
int a = 3;
int b = 5;
int tmp = 0;//空瓶
printf("交换前:a=%d b=%d\n", a, b);
tmp = a;
a = b;
b = tmp;
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
那么,抛开这个醋与酱油的例子,就没有其他办法了吗? 答案是:还有其他办法!
int main()
{
int a = 3;
int b = 5;
printf("交换前:a=%d b=%d\n", a, b);
a = a + b;
b = a - b;
a = a - b;
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
废话不多说,我们来分析一下代码,第一步,a+b的值赋给a。第二步,a-b(实际上是a原本a的值)的值赋给b。第三步,a-b(此时,b==a,但a还是原来的a)的值等与b赋给a。
相信,这样的想法会让你震惊,先别慌我们接着往下看。~~
利用按位异或来解决:(如果上边的方法没有让你震惊,那么下边这个方法绝对会震惊三观。)
在此之前我们要知道按位异或是支持交换律的。
int main()
{
int a = 3;
int b = 5;
printf("交换前:a=%d b=%d\n", a, b);
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
a=a^b;
b=a^b; //b=a^b^b ——> =右边等于a
a=a^b; //a=a^b^a ——> =右边等于b
^_^怎么样是不是很震惊!
作为一道面试题,它的价值估计就止步于此了,为什么这样说,因为他也有缺点:
4.按位取反 ~
int main()
{
int n = 0;
int a = ~n;//按位取反
//
//00000000000000000000000000000000
//11111111111111111111111111111111
printf("%d\n", a);//
return 0;
}
练习:⼆进制位置0或者置1。
编写代码将13⼆进制序列的第5位修改为1,然后再改回0。
1. 13的2进制序列: 00000000000000000000000000001101
2. 将第5位置为1后:00000000000000000000000000011101
3. 将第5位再置为0:00000000000000000000000000001101
废话不多说看代码:
#include <stdio.h>
int main()
{
int a = 13;
a = a | (1<<4);
printf("a = %d\n", a);
a = a & ~(1<<4);
printf("a = %d\n", a);
return 0;
}
四、操作数的两个重要属性
C语⾔的操作符有2个重要的属性:优先级、结合性,这两个属性决定了表达式求值的计算顺序。
1.优先级
优先级指的是,如果⼀个表达式包含多个运算符,哪个运算符应该优先执⾏。各种运算符的优先级是不⼀样的。根据运算符顺序执行左结合(从左向右计算),还是右结合(从右向左计算)
1+4*5
上述表达式中,既有加法运算符(+),又有乘法运算符(*),但是因为*运算符高于+法运算符,所以先计算4*5,而不是先计算1+4
2.结合性
如果两个运算符优先级一样,这时候应该看结合性,根据运算符顺序执行,一般来说是左结合(从左向右计算),也有右结合(从右向左计算)比如说“=”
5 * 6 / 2;
上述表达式中,*与/的优先级一样,进行左结合。
那么这么多的操作符我们应该如何记忆呢?下边顺序是部分操作符优先级从高到低进行排序,如果还需要其他操作符的优先级和结合性,请通过下表自行查找!!!
• 圆括号( () )
• ⾃增运算符( ++ ),⾃减运算符( -- )
• 单⽬运算符( + 和 - )
• 乘法( * ),除法( / )
• 加法( + ),减法( - )
• 关系运算符( < 、 > 等)
• 赋值运算符( = )
由于圆括号的优先级最⾼,可以使⽤它改变其他运算符的优先级。
^_^
五、表达式求值
1.整形提升
C语⾔中整型算术运算总是⾄少以缺省整型类型的精度来进⾏的。(缺省的意思就是计算机自定义的一个自动选择)
为了获得这个精度,表达式中的字符和短整型操作数在使⽤之前被转换为普通整型,这种转换称为整型提升。
//实例1
char a,b,c;
...
a = b + c;
b和c的值被提升为普通整型,然后再执⾏加法运算。
加法运算完成之后,结果将被截断,然后再存储于a中。
如何进行整数提升呢?
1.有符号位:按照符号位进行提升的。
2.无符号位:在高位直接补0。
//负数的整形提升
char c1 = -1;
变量c1的⼆进制位(补码)中只有8个⽐特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,⾼位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
变量c2的⼆进制位(补码)中只有8个⽐特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,⾼位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
//⽆符号整形提升,⾼位补0
2.算术转换
(在不用整型提升的基础上)如果某个操作符的各个操作数属于不同的类型,那么除⾮其中⼀个操作数的转换为另⼀个操作数的类型,否则操作就⽆法进⾏。下⾯的层次体系称为算术转换。
如果某个操作数的类型在上面这个列表中排名靠后,那么首先要转换为另外⼀个操作数的类型后执行运算。
总结:
即使有了操作符的优先级和结合性,我们写出的表达式依然有可能不能通过操作符的属性确定唯⼀的计算路径,那这个表达式就是存在潜在⻛险的,建议不要写出特别负责的表达式。