与进制有关的操作符

简介: 与进制有关的与进制有关的操作符1.原码,反码,补码2.移位操作符左移操作符 <<右移操作符 >>操作符

与进制有关的操作符

1.原码,反码,补码

2.移位操作符

左移操作符 <<

右移操作符 >>

3.位操作符

按位与&

按位或 |

按位异或 ^

4.几道例题

1.原码,反码,补码


按照一个数的正负,直接写出它的二进制表示形式就是原码

正数的原码、反码、补码是相同的

负数的原码、反码、补码要经过计算的

反码:原码的符号位不变,其他位按位取反

补码:反码+1

内存中存储的形式是:补码的二进制,所以在参与移位时,移动后都是补码

int n = 10,整形占四个字节(32bit)

00000000000000000000000000001010 原码

00000000000000000000000000001010 反码

00000000000000000000000000001010 补码

int n = -10

10000000000000000000000000001010 原码

111111111111111111111111111111110101 反码

111111111111111111111111111111110110 补码


原码、反码、补码相互转换图:




cb9871ed2b5c4900865bbca1c67d93f9.png



这里可以看出:原码到补码与补码到原码的操作实际上是一样的


2.移位操作符


左移操作符 <<

左移操作符:将补码向左移动,在右补0

要注意:b = a<<1左移运算符不是直接将a左移,而是将左移后的值保存到b中,a的值是不变的

如果想让a的·值变:a = a<<1或a<<=1


int main()
{
  //对正数的左移
  int a = 10;
  int b = a << 1;
  printf("%d\n", a);
  printf("%d\n", b);
}


00000000000000000000000000001010 -10的补码,进行左移



8e02428af9ea4805887f3dbebf392ebc.png



左移后的补码:00000000000000000000000000010100

输出结果:



52367cab2f3f4d539e7fd6fe70950774.png



int main()
{
  //对负数的左移
  int a = -10;
  int b = a << 1;
  printf("%d\n", a);
  printf("%d\n", b);
}

1

a的补码:111111111111111111111111111111110110

111111111111111111111111111111101100 -b的补码

10000000000000000000000000010100 -b的原码

输出结果:



f5659b5d4ce44e149507ba99db057f6e.png



从10与-10的左移一位的结果中,可以看出左移一位有扩大2倍的作用


右移操作符 >>

右移操作符分为两种

1.算术右移(常见):右边丢弃,左边补原来的符号位

2.逻辑右移:右边丢弃,左边直接补0

右移的方式取决于编译器,但是常见的右移是算术右移


对于移位运算符,不要移动负数位,这个是标准未定义的。

例如:


int num = 10;
num>>-1;//error
1

2

3.位操作符

位操作符有:


& -按位与

| -按位或

^ -按位异或


它们的操作数必须是正数,并且针对二进制计算


按位与&

对应的二进制有0就为0,只有对应的两个都为1时,才为1


int main()
{
  int a = 3;
  //00000000000000000000000000000011 a的补码
  int b = -5;
  //10000000000000000000000000000101
  //11111111111111111111111111111010
  //11111111111111111111111111111011  b的补码
  int c = a&b;
  //00000000000000000000000000000011 a的补码
  //11111111111111111111111111111011  b的补码
  //00000000000000000000000000000011  c的补码
}



按位或 |

对应的二进制位,有1则位1,两个同时为0则为0


int main()
{
  int a = 3;
  //00000000000000000000000000000011 a的补码
  int b = -5;
  //10000000000000000000000000000101
  //11111111111111111111111111111010
  //11111111111111111111111111111011  b的补码
  int c = a|b;
  //00000000000000000000000000000011 a的补码
  //11111111111111111111111111111011  b的补码
  //11111111111111111111111111111011  c的补码
}


按位异或 ^

对应的二进制位:相同位0,相异为1


int main()
{
  int a = 3;
  //00000000000000000000000000000011 a的补码
  int b = -5;
  //10000000000000000000000000000101
  //11111111111111111111111111111010
  //11111111111111111111111111111011  b的补码
  int c = a^b;
  //00000000000000000000000000000011 a的补码
  //11111111111111111111111111111011  b的补码
  //11111111111111111111111111111000  c的补码
}


异或操作赋一些特点:


1.a^a = 0

2.0^a = a

3.异或支持交换律


与^有关的变态面试题


不能创建临时变量(第三个变量),实现两个数的交换


#include <stdio.h>
int main()
{
  int a = 10;
  int b = 20;
  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 =a ^ b ^b = a,a = a^ b = a^b ^a,也就完成了交换


4.几道例题

这里,我们先想一下怎么得到二进制数字中的最后一个数字?


我们想一下求十进制数字的每一位:以11为例,11%2 = 1得到个位数字,11/2得到取出个位后的数字

其实二进制求每一位上数字一样




7fc0e7449ac34571a1dd391da7730423.png


使用取模运算,除法运算是一种方法,同时也可以使用操作符进行计算

实际上,用1与其他数字做按位与&就可以得到最后以为二进制数字,因为1的二进制最后一位为1,其余为0,&的规则是:有0就为0,都是1才为1,所以得到的数字前面各位都为0,而最后一位由原数字决定,它最后一位为0,结果就为0.为1,结果就为1


4e7ea6ba1650488ab60732940ffc4bad.png



接下来,为了得到去掉最后一位的剩余数字,只需把数字向右移动1即可 11>>1


前面的基础比较容易理解,但是在做题时有的方法我们仍难以想到,所以我们接下来看几道题理解理解


1.求一个数的二进制形式中有多少个1


使用前面的取模除法运算:

//二进制中1的个数
int NumberOf1(int n)
{
  int count = 0;
  while (n)
  {
  if (n % 2 == 1)   //如果最后一位为1.count+1
  {
    count++;
  }
  n = n / 2;   
  }
  return count;
}


这里的循环条件位n>o因为只要不是数字0,其余数字的二进制数字中都会有1,所以n>0时进行循环,如果退出循环来,就说明剩下的数字中一定没有1


使用&,>>

int NumberOf1(int n)
{
  int count = 0;
  while (n>0)
  {
  int a = n & 1;
  if (a==1)
  {
    count++;
  }
  n = n >> 1;
  return count;
}


前面两种算法计算正数都可以,但是负数就计算不了,负数连while循环都进不去,所以我们不能使用n>0作为循环条件

不论正数还是负数,都是4个字节大小,32个比特位,所以我们需将二进制循环遍历32次求出每位上的数字即可


int NumberOf1(int n)
{
  int i = 0;
  int count = 0;
  for (i = 0; i < 32; i++)  //循环32次,求出每位上的数字
  {
  if (((n >> i) & 1) == 1)
  {
    count++;
  }
  }
  return count;
}

4

((n >> i) & 1),将n右移i次,i从0开始,每次加一,所以n从右移1位开始,每次多移一位,因为左右移操作符不会改变原来n的值,所以每次向后移动i位,就巧妙地得到了i位上的数字,在&1就能得到每位上的数字


这里有一个几乎想不到的求法,用n = n&(n-1)求




d9596e55cc464692a0c686c9384c1fb3.png




所以计算n = n&(n-1)使用的次数,就可以得出1的个数


int NumberOd1(int n)
{
  int count = 0;
  while(n)
  {
  n = n&(n-1);
  count++;
  }
  return count++;
}

1

2

2.获取一个整数二进制序列中所有的偶数位和奇数位,分别打印出二进制序列


void print(int n)
{
  int arr[32] = { 0 };
  printf("奇数位:");
  for (int i = 1; i < 32; i+=2)
  {
  printf("%d ", (n>>i)&1);
  arr[i] = (n >> i) & 1;
  }
  printf("\n");
  printf("偶数位:");
  for (int i = 0; i < 32; i += 2)
  {
  printf("%d ", (n >> i) & 1);
  arr[i] = (n >> i) & 1;
  }
  printf("\n");
  for (int i = 31; i >=0; i--)
  {
  printf("%d", arr[i]);
  }
}


这里的原理其实和上一题一样,只是分为奇数和偶数,这里仍是对32位都遍历,只是每次i增加2

i从1开始,每次增加2,这就是在遍历奇数位

i从0开始,每次增加2,这就是在遍历偶数位


3.两个int(32位)整数m和n的二进制表达中,有多少个位(bit)不同?


int compare(int a, int b)
{
  int count = 0;
  for (int i = 0; i < 32; i++)
  {
  if (((a >> i) & 1) != ((b >> i) & 1))
  {
    count++;
  }
  }
  return count;
}

1


12

这里也是,用(n>>i)&1得到两个数字的各个位,然后进行比较,看是否相等


其实这里也可以使用按位异或操作符^,如果对应每位相同位0,相异为1,在去求a^b中1的个数


i

nt compare(int a, int b)
{
  //也可以用异或解决
  int tmp = a ^ b;
  //统计tmp里1的个数
  int count = 0;
  for (int i = 0; i < 32; i++)
  {
  if ((tmp >> i) & 1 == 1)
  {
    count++;
  }
  }
  return count;
}



最后,其实在二进制的一些运算中,十分有趣,不时会让人感觉妙啊·~~


目录
相关文章
|
7月前
|
编译器 C语言
关于合法浮点数、转义字符、八进制、十六进制、字符常量的判断
关于合法浮点数、转义字符、八进制、十六进制、字符常量的判断
123 0
|
C语言
C语言之将十进制整数转换为任意进制整数
C语言之将十进制整数转换为任意进制整数
244 0
|
存储 安全 编译器
(二进制)操作符详解
(二进制)操作符详解
152 0
(二进制)操作符详解
|
7月前
|
存储
进制转换和整型提升
进制转换和整型提升
|
C语言 索引
操作符续(整型提升与算术转换)
操作符续(整型提升与算术转换)
94 0
|
C语言
整数和浮点数的任意进制转!!(包括16进制)确定不进来看看?!
整数和浮点数的任意进制转!!(包括16进制)确定不进来看看?!
160 0
【进制转换】— 包含整数和小数部分转换(二进制、八进制、十进制、十六进制)手写版,超详细
【进制转换】— 包含整数和小数部分转换(二进制、八进制、十进制、十六进制)手写版,超详细
|
存储 程序员 DataX
利用链式栈实现八进制转换成十进制
利用链式栈实现八进制转换成十进制
118 0
|
编译器 C语言
【C操作符详解】之 移位操作符
【C操作符详解】之 移位操作符
107 0
用于字符串的 + 运算符
用于字符串的 + 运算符
77 0