C语言关于进制转换,补码, 整数的位操作

一、进制转换 

//关于进制转换,从网上找了几张经典图片,便于后面查询

1、二进制转十进制、八进制转十进制、十六进制转十进制

2、十进制转二进制, 十进制转八进制,十进制转十六进制

3、二进制转八进制,二进制转十六进制

4、八进制转二进制,十六进制转二进制

// 编程中默认就是十进制
    int num = 188;
    printf("十进制展示: %d\n", num);  //十进制展示: 188
    
    
    // 在编程中想表示二进制,需要在数字前面加上 0b
    int num2 = 0b10111100;
    printf("二进制 =》十进制 : %i\n", num2);  //二进制 =》十进制 : 188
    
    
    // 在编程中想表示八进制,需要在数字前面加上 0
    int num8 = 0274;
    printf("八进制 =》十进制 : %i\n", num8);  //八进制 =》十进制 : 188
    
    
    // 在编程中想表示十六进制,需要在数字前面加上 0x
    int num16 = 0xbc;
    printf("十六进制 =》十进制 : %i\n", num16);  //十六进制 =》十进制 : 188
    
    
    //以八进制形式、十六进制的形式输出十进制
    printf("八进制展示:%o\n", num);   //八进制展示:274
    printf("十六进制展示:%x\n", num); //十六进制展示:bc

二、原码、反码、补码 

/**
     假如一个字节的二进制表示:最高位用来表示符号(正负)
     1、原码:最容易被人脑直接识别并用于计算的表示方式
     2、反码:正数的反码和原码一样,负数的反码除最高位符号位外,其他位都取反
     3、补码:方便计算机进行计算,可以让最高位符号位都能参与计算;
     正数的补码和原码一样,负数的补码是其反码+1
           整数在计算机中以补码的方式存储,不管是正数还是负数。
     
     举例说明:8和-8 (假如都占一个字节,最高位是0表示是正数,是1表示是负数)
     8的原码:0000 1000
     8的反码:0000 1000
     8的补码:0000 1000
     -----------------
     -8的原码:1000 1000
     -8的反码:1111 0111  (除最高位符号位外,其他位取反)
     -8的补码:1111 1000  (负数反码+1进位)
     
     
     
     补码的意义:让计算机运算设计更简单,可以只有加法没有减法,让符号位也参与计算
     举例说明:10 - 8 = ? 和 8 - 10 = ?
     a、10 - 8 = 10 + (-8)  的计算
     -------- start -------
     10原码:      0000 1010
     -8原码:      1000 1000
     -------------------
     10反码:      0000 1010
     -8反码:      1111 0111
     -------------------
     10补码:      0000 1010
     -8补码:      1111 1000
     补码计算结果: 10000 0010   (注意:一个字节八位,最高位符号位相加往前进1,变成最高位符号位为0,表示为正数)
     二进制 0000 0010最高位为0表示是正数,正数的补码、反码、原码一样,所以补码二进制 -> 转成十进制为:2
     -------- end ------
     
     
     
     b、8 - 10 = 8 + (-10)的计算
     --------  start -------
     8原码:         0000 1000
     -10原码:       1000 1010
     --------------------
     8反码:         0000 1000
     -10反码:       1111 0101
     --------------------
     8补码:         0000 1000
     -10补码:       1111 0110
     补码计算结果:    1111 1110   (一个字节八位,最高位为1,表示是负数)
     1111 1110 一个字节八位最高位为1表示是负数,所以此补码二进制转成十进制,需要补码减一变成反码,反码再转成原码,原码转十进制
     补码 1111 1110 
     -->  反码(补码减1):1111 1101  
     --> 原码(符号位外,其他取反):1000 0010  
     --> 十进制:-2
     --------   end -------
     
     */

三、举例证明整数在计算机中是以补码的方式存储(以负数为例)

/*
     4个字节的int类型的负数测试:-10
     1000 0000 0000 0000 0000 0000 0000 1010 原码
     1111 1111 1111 1111 1111 1111 1111 0101 反码
     1111 1111 1111 1111 1111 1111 1111 0110 补码
     */
    int num1 = 0b10000000000000000000000000001010;
    int num1_2 = 0b11111111111111111111111111110110;
    
    printf("int数据类型占用字节个数:%lu\n", sizeof(int));
    printf("num1: %d, %o, %x\n", num1, num1, num1);
    printf("num1_2: %d, %o, %x\n", num1_2, num1_2, num1_2);
    //打印结果:证明了负数在计算机中是以补码的形式存储,不是以原码的方式存储
    /*
     int数据类型占用字节个数:4
     num1: -2147483638, 20000000012, 8000000a
     num1_2: -10, 37777777766, fffffff6
     */
      //num1是把-10的原码存进去,num1_2是把-10的补码存进去,打印结果显示补码转十进制才是-10,而原码转十进制是其他数字了
    
    /**
     4个字节的int类型的负数测试:-1
     1000 0000 0000 0000 0000 0000 0000 0001
     1111 1111 1111 1111 1111 1111 1111 1110 反
     1111 1111 1111 1111 1111 1111 1111 1111 补
     */
    int num2 = 0b10000000000000000000000000000001;
    int num2_1 = 0b11111111111111111111111111111111;
    printf("num2: %d, %o, %x\n", num2, num2, num2);
    printf("num2_1: %d, %o, %x\n", num2_1, num2_1, num2_1);
    //打印结果
    /*
     num2: -2147483647, 20000000001, 80000001
     num2_1: -1, 37777777777, ffffffff
     */
    
    
    /**
     short数据类型负数测试:-1  (2个字节)
     1000 0000 0000 0001 原码
     1111 1111 1111 1110 反码
     1111 1111 1111 1111 补码
     */
    short num3 = 0b1000000000000001;
    short num3_2 = 0b1111111111111111;
    printf("short数据类型占用字节个数:%ld\n", sizeof(short));
    printf("num3: %d, %o, %x\n", num3, num3, num3);
    printf("num3_2: %d, %o, %x\n", num3_2, num3_2, num3_2);
    //打印结果
    /*
     short数据类型占用字节个数:2
     num3: -32767, 37777700001, ffff8001
     num3_2: -1, 37777777777, ffffffff
     */

四、整数的位操作:按位且&、或|、异或^、取反~

/**
     101 1000  --> 88
     110 0100  --> 100
     
     1、 88&100 按位且:一假都假;
     有个小规律:任何位&1位都是该位, 比如位0&1为0位,位1&1位为1位
     101 1000
    &110 0100
     --------
     100 0000 --> 88 & 100 = 64
     另外:88&100 == 88&100&88 == 88&100&100 == 88&100&100&88
     
     2、 88|100 按位或:一真都真
     101 1000
    |110 0100
     --------
     111 1100 --> 88 | 100 = 64 + 32 + 16 + 8 + 4 = 124
     另外:88|100 == 88|100|88 == 100|88|100 == 88|100|88|100
     
     3、88^100 按位异或 : 相同为0,不同为1;
     规律总结:任何数num异或另外一个数num2两次,都等于该数num
     101 1000
    ^110 0100
     --------
     011 1100 --> 88^100 = 32 + 16 + 8 + 4 = 60
     即:88^100^100 == 88, 88^100^88 == 100
     */
    
    printf("0&1 = %d, 1&1 = %d \n", 0&1, 1&1);
    printf("88 & 100 = %d, 88 | 100 = %d, 88 ^ 100 = %d\n", 88 & 100, 88 | 100, 88 ^ 100);
    printf("88&100&88: %d, 100&88&100: %d, 88&100&100&88: %d, 88|100|88: %d, 100|88|100: %d, 88|100|100|88: %d \n",
           88&100&88, 100&88&100, 88&100&100&88, 88|100|88, 100|88|100, 88|100|100|88);
    printf("88^100^88: %d, 100^88^100: %d, -88^100^-88: %d\n", 88^100^88, 100^88^100, -88^100^-88);
    /* 打印结果:
     0&1 = 0, 1&1 = 1
     88 & 100 = 64, 88 | 100 = 124, 88 ^ 100 = 60
     88&100&88: 64, 100&88&100: 64, 88&100&100&88: 64, 88|100|88: 124, 100|88|100: 124, 88|100|100|88: 124
     88^100^88: 100, 100^88^100: 88, -88^100^-88: 100
     */

用short类型来(占用2个字节)演示负数参与按位操作:取反、且、或、异或

/* 先分析,再验证, 以short类型2个字节为例演示
     一、88取反 --> ~88 = -89
     0000 0000 0101 1000
     1111 1111 1010 0111 取反,为负数, 说明这个是该负数的补码
     1111 1111 1010 0110 该负数反码:补码减一得反码
     1000 0000 0101 1001 该负数原码 : 64 + 16 + 8 + 1 = -89
     演示步骤:88原码 --> 取反得负数补码 --> 补码转反码 --> 反码转该负数原码
     
     
     二、-88取反 --> ~-88 = 87
     1000 0000 0101 1000 -88原码
     1111 1111 1010 0111 -88反码
     1111 1111 1010 1000 -88补码
     0000 0000 0101 0111 -88补码取反
     即-88取反的结果为:64+16+4+2+1=87
     这个步骤为:-88原码 --> -88反码 --> -88补码 --> 取反
     
     
     三、-88&100 负数参与按位且,分析步骤
     1111 1111 1010 1000 -88补码
    &0000 0000 0110 0100 100补码
     -------------------
     0000 0000 0010 0000 转成十进制结果为:32, 即-88&100 = 32
     
     
     四、-88&-100 两个负数参与按位且,分析步骤
     1000 0000 0101 1000 -88原码
     1111 1111 1010 0111 -88反码
     1111 1111 1010 1000 -88补码
     1000 0000 0110 0100 -100原码
     1111 1111 1001 1011 -100反码
     1111 1111 1001 1100 -100补码
     ---------------------------
     1111 1111 1010 1000 -88补码
     &1111 1111 1001 1100 -100补码
     -------------------
     1111 1111 1000 1000 结果为负数,这是补码,转成原码
     1111 1111 1000 0111 反码
     1000 0000 0111 1000 原码,转成十进制为:-(64+32+16+8)=-120
     
     
     五、-88|-100 两个负数的按位或,分析步骤
     1111 1111 1010 1000 -88补码
    |1111 1111 1001 1100 -100补码
     --------------------
     1111 1111 1011 1100 结果为负数,这是补码,转成原码为
     1111 1111 1011 1011 反码
     1000 0000 0100 0100 原码,转成十进制为:-(64+4)=-68
     
     
     六、88^-100 有负数的按位异或,分析步骤
     0000 0000 0101 1000 88原码,也是88补码
    ^1111 1111 1001 1100 -100补码
     -------------------
     1111 1111 1100 0100 结果为负数,这是补码,转成原码为:
     1111 1111 1100 0011 反码
     1000 0000 0011 1100 原码,转成十进制为:-(32+16+8+4)=-60
     
     七、-88^-100 两个负数的异或,分析步骤
     1111 1111 1010 1000 -88补码
    ^1111 1111 1001 1100 -100补码
     --------------------
     0000 0000 0011 0100 结果为正数,转成十进制为:32+16+4=52
     */
    short number1 = 0b1111111110100111; //-89的补码
    short number2 = 0b1111111110101000; //-88的补码
    short number3 = 0b1111111110001000; //-120的补码
    printf("number1: %d, number2: %d, number3: %d\n", number1, number2, number3);
    printf("~88:%d, ~-88: %d \n", ~88, ~-88);
    printf("-88&100: %d, -88&-100: %d \n", -88&100, -88&-100);
    printf("-88|-100: %d, 88^-100: %d, -88^-100: %d \n", -88|-100, 88^-100, -88^-100);
    /** 打印结果
     number1: -89, number2: -88, number3: -120
     ~88:-89, ~-88: 87
     -88&100: 32, -88&-100: -120
     -88|-100: -68, 88^-100: -60, -88^-100: 52
     */

五、整数的位移操作:左位移,右位移 (正数、负数)

/**
     以2个字节的short类型为测试数据
     左位移:二进制往左移动一位,最高位左边砍掉,最低位右边补0
     右位移:二进制往右移动,最低位右边砍掉,最高位左边补一个符号位(即正数补0,负数补1)
     正数左位移规律:某个数num左位移n位,等于数num * 2的n次幂,比如9<<1=9*2的一次幂;9<<2=9*2的二次方幂
     正数右位移规律:某数num右位移n位,等于数num/2的n次幂,比如9>>1=9/2的一次幂=4; 9>>2=9/2的2次方幂=9/4=2
     
     
     一、正数的位移:
     9<<1, 9<<2, 9>>1, 9>>2
     
     0000 0000 0000 1001  // 9
     000 0000 0000 1001 0  // 9<<1(9左位移1位,最高位砍掉,最低位补0,即最左边的一位砍掉,最右边补一位0)
     最终为: 0 000 0000 000 1001 0 转成十进制为:16+2=18
     
     00 0000 0000 1001 00 //9<<2 转成十进制:32 + 4= 36
     
     0000 0000 0000 1001   //9
     0 0000 0000 0000 100  // 9>>1 转成十进制:4
     00 0000 0000 0000 10  // 9>>2 转成十进制:2
     
     
     二、负数的位移
     -9<<1, -9<<2, -9>>1, -9>>2
     1000 0000 0000 1001    //-9的原码
     1111 1111 1111 0110    //-9的反码
     1111 1111 1111 0111   //-9的补码
     
     //接下来补码操作进行左位移,右位移
     111 1111 1111 0111 0   //-9<<1, 最新值的补码, 即-9补码往左移动一位,最高位砍掉,最低位补0
     111 1111 1111 0110 1   //最新值的反码
     100 0000 0000 1001 0   //最新值的原码,转成十进制为:-(16+2)=-18, 即-9<<1 = -18
     
     11 1111 1111 0111 00   //-9<<2, 最新值的补码。即-9补码往左移动两位,左边砍掉2位,右边补两个0
     11 1111 1111 0110 11   //最新值的反码
     10 0000 0000 1001 00   //最新值原码,转十进制:-(32+4)=-36
     
     1 1111 1111 1111 011  //-9>>1, 最新值的补码。即-9补码往右移动一位,最高位补符号位1,最低位砍掉
     1 1111 1111 1111 010  //最新值的反码
     1 0000 0000 0000 101  //最新值原码,转十进制:-(4+1)=-5
     
     11 1111 1111 1111 01  //-9>>2, 最新值的补码. 即-9补码往右移动两位,左边补符号两位1,右边砍掉两位
     11 1111 1111 1111 00  //最新值反码
     10 0000 0000 0000 11  //最新值原码,转十进制:-(2+1)=-3
     */
    
    printf("9<<1: %d, 9<<2: %d, 9>>1: %d, 9>>2: %d\n", 9<<1, 9<<2, 9>>1, 9>>2);
    printf("-9<<1: %d, -9<<2: %d, -9>>1: %d, -9>>2: %d\n", -9<<1, -9<<2, -9>>1, -9>>2);
    /** 打印结果:
     9<<1: 18, 9<<2: 36, 9>>1: 4, 9>>2: 2
     -9<<1: -18, -9<<2: -36, -9>>1: -5, -9>>2: -3
     */

六、整数的按位且&、异或^、位移的举例应用

/*
     题目1、输入一个整数num, 打印该整数num的二进制
     该题运用到位移、按位且&
     */
    int num =9;
    for (int i =31; i>=0; i--) {
        if ((i+1)%4==0){
            printf(" ");
        }
        
        printf("%d", (num>>i)&1);
    }
    printf("\n");
    //打印结果为: 0000 0000 0000 0000 0000 0000 0000 1001

/**
其他几个测试记录:
     101 1000  --> 88
     110 0100  --> 100
     
     1、 88&100 按位且:一假都假;
     有个小规律:任何位&1位都是该位, 比如位0&1为0位,位1&1位为1位
     101 1000
     &110 0100
     --------
     100 0000 --> 88 & 100 = 64
     另外:88&100 == 88&100&88 == 88&100&100 == 88&100&100&88
     
     2、 88|100 按位或:一真都真
     101 1000
     |110 0100
     --------
     111 1100 --> 88 | 100 = 64 + 32 + 16 + 8 + 4 = 124
     另外:88|100 == 88|100|88 == 100|88|100 == 88|100|88|100
     即几个变量按位或后,得到的结果再和这几个变量按位或,其新结果不变
     
     3、88^100 按位异或 : 相同为0,不同为1;
     规律总结:任何数num异或另外一个数num2两次,都等于该数num
     101 1000
     ^110 0100
     --------
     011 1100 --> 88^100 = 32 + 16 + 8 + 4 = 60
     即:88^100^100 == 88, 88^100^88 == 100

*/
    
    
    /**
     题目2:输入一个数字,判断该数字的奇偶性
     可以多种方式实现,这里我们使用按位&来实现,
     任何位&1都为该位,即0&1=0, 1&1=1
     分析:整数二进制最后一位为1的是奇数,为0的是偶数
     0001 1
     0010 2
     0011 3
     0100 4
     0101 5
     */
    printf("奇数:%d, %d, %d, %d, %d\n", 1&1, 3&1, 5&1, 7&1, 9&1);
    printf("偶数:%d, %d, %d, %d, %d\n", 0&1, 2&1, 4&1, 6&1, 8&1);
    /** 打印结果
     奇数:1, 1, 1, 1, 1
     偶数:0, 0, 0, 0, 0
     */
    
    
    /**
     题目3:两个整数a和b, 交换两个整数的值
     可以使用按位异或来处理:任何数num异或另外一个数num2两次都为该数num
     */
    int a = 10, b = 8;
    //第一种方式,增加一个中间变量来交换
    int c = a; a=b; b=c;
    printf("第一种方式:a=%d, b=%d \n", a, b);
    
    //第二种方式
    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);
    
    //第三种方式,使用^
    printf("第三种方式交换a: %d, b: %d的值 \n", a, b);
    a = a^b;
    b = a^b; //相当于b = a^b^b = a
    a = a^b; //相当于a = a^a^b = b
    printf("交换后: a = %d, b = %d\n", a, b);
    
    /** 打印结果:
     第一种方式:a=8, b=10
     第二种方式交换a: 8, b: 10的值:
     交换后:a=10, b=8
     第三种方式交换a: 10, b: 8的值
     交换后: a = 8, b = 10
     */
    
    
    
    /** 题目4:整数的简单加密,使用异或^ */
    int pwd = 888888, key = 518518;
    int encodePwd = pwd^key;
    int decodePwd = encodePwd^key;
    printf("原密码:%d, 加密后:%d, 解密后:%d\n", pwd, encodePwd, decodePwd);
    /** 打印结果:
     原密码:888888, 加密后:686414, 解密后:888888
     */

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端迷

前端常见算法的JS实现

11030
来自专栏SeanCheney的专栏

《Pandas Cookbook》第01章 Pandas基础

公司网址,http://www.dunderdata.com(dunder是蒸馏朗姆酒的残留液体,取这个名字是类比数据分析过程) GitHub地址:https...

17020
来自专栏wannshan(javaer,RPC)

dubbo序列化过程源码分析

先看下dubbo在serialize层的类设计方案 序列化方案的入口,是接口Serialization的实现类。 /** * Serialization. ...

90190
来自专栏数据结构与算法

codevs 1213 解的个数

1213 解的个数 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题目描述 Description 已知整数x...

34340
来自专栏赵俊的Java专栏

Java 中 byte 的取值范围为什么是 -128 到 +127

45920
来自专栏从零开始学 Web 前端

unsigned/signed int/char类型表示的数值范围

我们知道,在计算机内部数值一律使用补码存储。正数的补码与原码一致,负数的补码是符号位为1,其余位是该负数的绝对值按位取反后加1得到。

9220
来自专栏Golang语言社区

golang使用sort接口实现排序示例

今天看见群里再讨论排序的sort.Interface的实现,有童鞋一直搞不定,我就上手了一下,哦耶搞定了,代码放在这里. 其实很简单sort.Interface...

40670
来自专栏mathor

原码反码补码

22430
来自专栏一个会写诗的程序员的博客

《Kotlin极简教程》第五章 Kotlin面向对象编程(OOP)一个OOP版本的HelloWorld构造函数传参Data Class定义接口&实现之写pojo bean定一个Rectangle对象封

We frequently create a class to do nothing but hold data. In such a class some s...

25840
来自专栏noteless

[十六]基础类型BigInteger简介

final int[] mag;保存数字的数据 字节序为大端模式,大端模式就是低地址存储高位

43840

扫码关注云+社区

领取腾讯云代金券