C语言学习记录(九)——操作符

移位操作符
左移
0000 1011  << 1  在右边统一补0   11
0001 0110  << 2                   11*2^1
0010 1100                         11*2^2

右移
0000 1011   >>1  补当前数字的最高位 11
0000 0101  >>2                       11/2^1
0000 0010                            11/2^2

特殊情况,最高位为1时
最高位为符号位,1为负数,0为正数
负数在内存当中存储的是补码
原码 --》反码 --》补码

原码  1000 0000 0000 0000 0000 0000 0000 0001      此为-1的源码
反码  1111 1111 1111 1111 1111 1111 1111 1110    符号位不变,其他位取反
补码  1111 1111 1111 1111 1111 1111 1111 1111     对反码+1得到补码

-1在内存中是存放了32个1
  在定义时使用unsigned int a = -1定义出来的是无符号量,此时当我们以printf("%d\n",a)时,打印出来的为-1,但当使用printf("%u\n",a)时,打印出来将是一个很大的数。

位操作符
& 按位与
    0000 1010
    0100 1001
    0000 1000    对应为上相同,就是那个位上的值

|  按位或
    0000 1010
    0100 1001
    0100 1001    只要对应位上是1,结果就是1

^ 按位异或(不一样的位上或,一样的都是0)
    0000 1010
    0100 1001
    0100 0011                  ————》 0异或任何数字,都是那个数字本身
                                     两个相同的数字,因为对应位2进制一样,所以结果就是0

例题1:对于一个数组[1,2,3,2,1]找到这个数组中只出现一次的数

解析:
只要是运算符,都满足交换律
1^2^3^2^1 = (1^1)^(2^2)^3 = 0^0^3 = 3

对例题1进行升级:在一个数组中,有两个数字是出现一次的,其他数字都是出现两次的,找到出现一次的那两个数字

以[1,2,3,2,1,4]数组为例
对上述数组进行异或,其结果就是3^4的结果
0000 0011    3            0000 0001   1
0000 0100    4            0000 0010    2 
0000 0111
得到3和4在最后三位位置上,对应位不一样
我们设定一个规则,只要数组中从右往左第二个位是1的,放在一类,不是1的放在一类

第二位上为1的                       第二位上不为0的
   2,2,3                             1,1,4
对两类分别进行异或,就可以找到两个数字

例题2:不创建临时变量实现,实现两个数的交换
一般情况下,想要交换两个数,使用的是以下代码

int main(){
    int a = 10;
    int b = 20;
    int tmp = a;
    a = b;
    b = tmp;
    printf("%d,%d",a,b);
}

不添加临时变量

int main(){
    int a = 10;
    int b = 20;
    a = a + b;  // a=30
    b = a - b;  //30-20=10
    a = a - b;  //30-10=20
}

使用异或的方法

int main(){
    int a = 3;
    int b = 2;
    a = a^b;  //   0011 ^ 0010 = 0001
    b = a^b;  //   0001 ^ 0010 = 0011    3
    a = a^b;  //   0001 ^ 0011 = 0010    2
}

例题3:计算一个数字的二进制有多少个1

#include<stdio.h>
int main(){
    int n = -1;
    int count = 0;
    while(n ! = 0){
        count++;
        n = n & (n-1);
    }
    printf("%d\n",count);
}

练习1:关于表达式求值的相关概念:
  ①表达式求值先看是否存在整形提升或算数转换,在进行计算。
  ②表达式真正计算的时候先看相邻操作符的优先级决定先算谁
  ③相邻操作符的优先级相同的情况下,看操作符的结合性决定计算顺序
  ④有了优先级和结核性,表达式也有可能有不同的计算路径,导致计算结果的差异。

练习2:下面代码结果:

#include<stdio.h>
int main(){
    int i = 1;
    int ret = (++i) + (++I) + (++i);
    printf("ret = %d\n",ret);
    return 0;
}

  解析:表达式(++i) + (++I) + (++i)只有操作符的优先级和结合性,无法计算唯一计算路径。在不同的编译器上结果可能有差异。所以此表达式为错误的表达式
练习3:下面代码的结果是:

#include<stdio.h>
int i;
int main(){
    i--;
    if(i > sizeof(i)){
        printf(">\n");
    }
    else{
        printf("<\n");
    }
    return 0;
}

  解析:在C语言中,0为假,非0为真;这里定义了i为全局变量,没有给初始值时,编译会默认将其初始化为0。则i--结果为-1i为整形,sizeof(i)i类型的大小,在32系统下为4;理论上结果应该时输出“<”,但是sizeof的返回值类型实际为无符号整型,因此编译器会自动将左侧的i自动转换为无符号整形数据,因此-1将会被转化为一个很大的数。

练习4:下面代码的运行结果:

#include <stdio.h>
int main()
{
	int a, b, c;
	a = 5;
	c = ++a;    // ++a:加给a+1,结果为6,用加完之后的结果给c赋值,因此:a = 6  c = 6
	b = ++c, c++, ++a, a++;  // ++c:c为7 c++:c值不变 ++a:a的值为7,a++:a值不变,b取a的值:7
	             // 表达式结束时,c++和a++会给a和c分别加1,此时c:8,a:8,b:7
	b += a++ + c; // a先和c加,结果为16,在加上b的值7,比的结果为23,最后给a加1,a的值为9
	printf("a = %d b = %d c = %d\n:", a, b, c); // a:9, b:23, c:8
	return 0;
}

  结果为:a=9,b=23,c=8

练习5:求两个数二进制中不同位的个数

思路:
1. 先将m和n进行按位异或,此时m和n相同的二进制比特位清零,不同的二进制比特位为1
2. 统计异或完成后结果的二进制比特位中有多少个1即可
#include<stdio.h>
int calc_diff_bit(int m, int n){
    int tmp = m^n;
    int count = 0;
    while(tmp){
        tmp = tmp & (tmp - 1);
        count++;
    }
    return count;
}
int main(){
    int m,n;
    while(scanf("%d %d", &m, &n) == 2){
        printf("%d\n", calc_diff_bit(m, n))
    }
}

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

思路
1. 提取所有的奇数位,如果该位是1,输出1,是0则输出0
2. 以同样的方式提取偶数位置
检测num中某一位是0还是1的方式:
   1. 将num向右移动i位
   2. 将移完位之后的结果与1按位与,如果:
      结果是0,则第i个比特位是0
      结果是非0,则第i个比特位是1
#include<stdio.h>
#include<windows.h>
void my_print(int n)
{
	int i = 0;
	printf(" 打印奇数位\n");
	for (i = 30; i >= 0; i = i - 2)
	{
		printf("%d ", (n >> i) & 1);
	}
	printf("\n");
	printf("打印偶数位\n");
	for (i = 31; i >= 1; i = i - 2)
	{
		printf("%d ", (n >> i) & 1);
	}
}
int main()
{
	int num = 0;
	int count = 0;
	scanf("%d", &num);
	my_print(num);
	system("pause");
}

练习7:统计二进制中1的个数

作业内容
写一个函数返回参数二进制中 1 的个数。
比如: 15    0000 1111    4 个 1
方法一;循环进行以下操作,直到n被缩减为0:
   1. 用该数据模2,检测其是否能够被2整除
   2. 可以:则该数据对应二进制比特位的最低位一定是0,否则是1,如果是1给计数加1
   3. 如果n不等于0时,继续1
#include<stdio.h>
#include<windows.h>
int count_one_bit(int n){
	int count = 0;
	while (n){
		if (n % 2 == 1){
			count++;
		}
		n = n / 2;
	}
	return count;
}
int main(){
	int n = 0;
	scanf("%d", &n);
	printf("%d\n",count_one_bit(n));
	system("pause");
}
上述方法缺陷:进行了大量的取模以及除法运算,取模和除法运算的效率本来就比较低。
方法二:
一个int类型的数据,对应的二进制一共有32个比特位,可以采用位运算的方式一位一位的检测。
#include<stdio.h>
#include<windows.h>
int count_one_bit(int n){
	int count = 0;
	for (int i = 0; i < 32; i++){
		if (((n >> i) & 1) == 1){
			count++;
		}
	}
	return count;
}
int main(){
	int n = 25;
	//scanf("%d ", &n);
	//printf("%d\n", count_one_bit(n));
	count_one_bit(n);
	system("pause");
}
//方法二优点:用位操作代替取模和除法运算,效率稍微比较高,缺陷:不论是什么数据,循环都要执行32次
方法三:
思路:采用相邻的两个数据进行按位与运算
举例:
9999:‭10 0111 0000 1111‬
第一次循环:n=9999   n=n&(n-1)=9999&9998= 9998
第二次循环:n=9998   n=n&(n-1)=9998&9997= 9996
第三次循环:n=9996   n=n&(n-1)=9996&9995= 9992
第四次循环:n=9992   n=n&(n-1)=9992&9991= 9984
第五次循环:n=9984   n=n&(n-1)=9984&9983= 9728
第六次循环:n=9728   n=n&(n-1)=9728&9727= 9216
第七次循环:n=9216   n=n&(n-1)=9216&9215= 8192
第八次循环:n=8192   n=n&(n-1)=8192&8191= 0
 

可以观察下:此种方式,数据的二进制比特位中有几个1,循环就循环几次,而且中间采用了位运算,处理起来比较高效
#include<stdio.h>
#include<windows.h>
int count_one_bit(int n)
{
	int count = 0;
	while(n)
	{
		n = n&(n-1);
		count++;
	}
	return count;
}
int main(){
	int n = 0;
	scanf("%d", &n);
	printf("%d\n",count_one_bit(n));
	system("pause");
}

练习8:交换两个变量,不允许创建临时变量,交换两个整数的内容

#include <stdio.h>
int main()
{
	int a = 10;
    int b = 20;
    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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值