04-C语言进制和位运算

进制基本概念

  • 什么是进制?

    • 进制是一种计数的方式,数值的表示形式
  • 常见的进制

    • 十进制、二进制、八进制、十六进制
  • 进制书写的格式和规律

    • 十进制 0、1、2、3、4、5、6、7、8、9 逢十进一
    • 二进制 0、1 逢二进一
      • 在C语言中, 如果想用二进制表示某个数, 前面需要加上0b;
    • 八进制 0、1、2、3、4、5、6、7 逢八进一
      • 在C语言中, 如果想用八进制表示某个数, 前面需要加上0;
    • 十六进制 0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F 逢十六进一
      • 在C语言中, 如果想用十六进制表示某个数, 前面需要加上0x;
  • 注意点:
    • %i %d代表以十进制输出整数
    • 0x 和 0X写法 一样, 0b和0B写法也一样
// 12的不同进制的表达
// 用十进制来表示
int num1 = 12;
printf("num1 = %i\n", num1);
// 用十六进制来表示
int num2 = = 0xc;
printf("num2 = %i\n", num2);
// 用八进制来表示
int num3 = 014;
printf("num3 = %i\n", num3);
// 用二进制来表示
int num4 = 0b1100;
printf("num4 = %i\n", num4);

// 不同进制的输出
int num = 12;
// 以十进制输出
printf("%i\n", num);
// 以八进制输出
printf("%o\n", num);
// 以十六进制输出
printf("%x\n", num);
//二进制

void printBinary(char value){
    
}

进制转换

  • 十进制转二进制

    • 除2取余, 余数倒序; 得到的序列就是二进制表示形式
    
    /*
     *  求12的二进制数
     *   12
     *    2
     * ----
     *    6   0
     *    2
     * ----
     *    3   0
     *    2
     * ----
     *    1   1
     *    2
     * ----
     *    0   1
     *
     * 12 --> 1100    
    */
    
    
    
    
  • 二进制转十进制

    • 规则: 系数 * 基数^(索引)
      • 系数:就是每一位对应的值就是系数;
      • 基数: 如果是二进制转到十进制,那么2就是基数
        -如果是从八进制转换到十进制, 那么八就是基数
      • 索引:从最低位以0开始,依次递增;
    /*
        1*2^3 + 1*2^2 + 0*2^1 + 0*2^0
        
        1       1       0       0    
        
    ==> 8  +    4    +  0   +  0   = 12
            
    */
    
    
    /*
     * 二进制快速转换十进制
     * 64 32 16 8 4 2 1
     * 1  1  1  1 1 1 1
     */
    
    
  • 十进制转换八进制

    • 规则: 除8取余,余数倒数
    
    /* 十进制24转换为八进制
     *
     * 24
     *  8
     * ---
     *  3   0
     *  8
     * ---
     *  0   3
     *
     * 24 --> 30
     */
     
    
  • 二进制转换为八进制
    • 必须知道:在八进制中最大的数字是7
    • 1.可以使用系数 * 基数^(索引) 将二进制转换为8进制
    • 2.二进制在转换为8进制的时候,把三个二进制数看做一个八进制位即可
    /*从右至左每3位划分为8进制的1位, 不够前面补0 
    001 100 100
    第0位: 100 等于十进制 4
    第1位: 100 等于十进制 4
    第2位: 001 等于十进制 1
    最终结果: 144就是转换为8进制的值
    /*
    
  • 二进制转换为十六进制
    • 二进制在转换为十六进制的时候,把四个二进制位看做一个十六进制为即可
    /*
         3      8
        0011   1000    ==>  0x38 
    */
    

十进制小数转换为二进制小数

  • 整数部分,直接转换为二进制即可
  • 小数部分,使用"乘2取整,顺序排列"
    • 用2乘十进制小数,可以得到积,将积的整数部分取出,再用2乘余下的小数部分,直到积中的小数部分为零,或者达到所要求的精度为止
    • 然后把取出的整数部分按顺序排列起来, 即是小数部分二进制
  • 最后将整数部分的二进制和小数部分的二进制合并起来, 即是一个二进制小数
  • 例如: 将12.125转换为二进制
// 整数部分(除2取余)
  12
/  2
------
   6    // 余0
/  2
------
   3    // 余0
/  2
------
   1   // 余1
/  2
------
  0   // 余1
//12 --> 1100
  
// 小数部分(乘2取整数积)
  0.125
*     2
  ------
   0.25  //0
   0.25
*     2
  ------
    0.5  //0
    0.5
*     2
  ------
    1.0  //1
    0.0
// 0.125 --> 0.001

// 12.8125 --> 1100.001


二进制转换为十进制小数

  • 整数部分按照二进制转十进制即可
  • 小数部分从最高位开始乘以2的负n次方, n从1开始
  • 例如: 将 1100.001转换为十进制
// 整数部分(乘以2的n次方, n从0开始)
0 * 2^0 = 0
0 * 2^1 = 0
1 * 2^2 = 4
1 * 2^3 = 8
 // 1100 == 8 + 4 + 0 + 0 == 12

// 小数部分(乘以2的负n次方, n从0开始)
0 * (1/2) = 0
0 * (1/4) = 0
1 * (1/8) = 0.125
// .100 == 0 + 0 + 0.125 == 0.125

// 1100.001  --> 12.125


  • 将0.8125转换为二进制
0.8125
*      2
--------
   1.625  // 1
   0.625
*      2
--------
    1.25 // 1
    0.25
*      2
--------
     0.5 // 0
*      2
--------
    1.0 // 1
    0.0

// 0. 8125  --> 0.1101


原码 补码 反码之间的关系

  • 正数的原码 = 补码 = 反码 ; (三码合一);

  • int 类型在计算机中占用4个字节

  • 1个字节等于8位,所以int类型总共占用32位;

  • 注意点:

    • 在存储正整数的时候,第一个(最高位)是符号位, 0是代表正数, 1代表是负数;
     0000 0000 0000 0000 0000 0000 0000 1100
    // int num = 12; 
    
  • 负整数的原码, 补码, 反码
// int  num = -12; 
原码 : 二进制,将最高位变为1;
1000 0000 0000 0000 0000 0000 0000 1100

反码 : 除了符号位以外, 0变1, 1变0;
1000 0000 0000 0000 0000 0000 0000 1100  12的反码

补码 : 反码 + 1
    1111 1111 1111 1111 1111 1111 1111 0011  12的反码
  + 0000 0000 0000 0000 0000 0000 0000 0001   +1
    ----------------------------------------
    1111 1111 1111 1111 1111 1111 1111 0100  12的补码
    
    // 注意点: 无论是正整数还是负整数,存储在计算机中都是补码

  • 如果只有原码参与运算会出现什么问题
// 1 - 1; 1 + (-1);
   0000 0000 0000 0000 0000 0000 0000 0001 // 1原码
  +1000 0000 0000 0000 0000 0000 0000 0001 // -1原码
   ---------------------------------------
   1000 0000 0000 0000 0000 0000 0000 0010 == -2

  • 很明显,上面的运算结果不成立

  • 计算机只能做就加法运算,而且是做补码运算;


    
    
    /*
     * 1 - 1  理解为 1 + (-1)
     * 0000 0000 0000 0000 0000 0000 0000 0001    1补码就是原码
     *
     // 求-1的补码
     * 1000 0000 0000 0000 0000 0000 0000 0001    -1源码
     * 1111 1111 1111 1111 1111 1111 1111 1110    -1反码
     * 0000 0000 0000 0000 0000 0000 0000 0001
     * -----------------------------------------
     
     * 1111 1111 1111 1111 1111 1111 1111 1111    -1补码
     *
     *
     *   0000 0000 0000 0000 0000 0000 0000 0001     1补码
     * + 1111 1111 1111 1111 1111 1111 1111 1111    -1补码
     * -------------------------------------------
     *   0000 0000 0000 0000 0000 0000 0000 0000    0
     */


    /*
     * 4 - 6 --> 4 + (-6)
     * 0000 0000 0000 0000 0000 0000 0000 0100  4补码
     *
     * 1000 0000 0000 0000 0000 0000 0000 0110  -6源码
     * 1111 1111 1111 1111 1111 1111 1111 1001  -6反码
     * 0000 0000 0000 0000 0000 0000 0000 0001
     * -------------------------------------------
     * 1111 1111 1111 1111 1111 1111 1111 1010  -6补码
     *
     *
     *
     * 0000 0000 0000 0000 0000 0000 0000 0100  4补码
     * 1111 1111 1111 1111 1111 1111 1111 1010  -6补码
     * 1111 1111 1111 1111 1111 1111 1111 1110  补码的结果
     *
     * 注意点: 当前计算出来的是正确结果的补码, 要想知道正确结果是多少, 需要将补码转换为源码
     // 原码 = (补码 - 1)再求其反码
     *
     * 1111 1111 1111 1111 1111 1111 1111 1110  结果的补码
     * 0000 0000 0000 0000 0000 0000 0000 0001  减1
     * -------------------------------------------
     * 1111 1111 1111 1111 1111 1111 1111 1101  结果的反码
     * 1000 0000 0000 0000 0000 0000 0000 0010  结果的源码  -2
     */

    /*
         0101
       + 0001
         -----
         0102 --> 0110
    
         0110 --> 0102
       - 0001     0001
        ---------------
                  0101
   */

位运算符

符号 名称 运算结果
& 按位与 同1为1
| 按位或 有1为1
^ 按位异或 不同为1
~ 按位取反 0变1,1变0
<< 按位左移 乘以2的n次方
>> 按位右移 除以2的n次方
  • & 按位与

    • 规则: 两个都是真(1)时,结果才是真(1);
    • 规律:任何一位和1相与,结果还是原来的那一位;
    /* 
     * 9 & 3 = ?
     *   0000 0000 0000 0000 0000 0000 0000 1001 // 9的补码
     * & 0000 0000 0000 0000 0000 0000 0000 0011 // 3的补码
     * -----------------------------------------------
     *   0000 0000 0000 0000 0000 0000 0000 0001 // 1
     *
     * 12 & 8
     *  0000 0000 0000 0000 0000 0000 0000 1100  //12的补码
     *& 0000 0000 0000 0000 0000 0000 0000 1000  //8的补码
     * -----------------------------------------------
     *  0000 0000 0000 0000 0000 0000 0000 1000  //4
     */    
    
    • 应用: 判断奇数偶数
    int num = 4;
    //    if(1 == (num & 1)){
    //        printf("%i是奇数\n", num);
    //    }else{
    //        printf("%i是偶数\n", num);
    //    }
    
    if(num & 1){
        printf("%i是奇数\n", num);
    }else{
        printf("%i是偶数\n", num);
    }
    
  • | 按位或

    • 只要对应的二个二进位有一个为1时,结果位就为1,否则为0
    /* 9 | 3 = ?
     *  0000 0000 0000 0000 0000 0000 0000 1001 // 9的补码
     *| 0000 0000 0000 0000 0000 0000 0000 0011 // 3的补码
     *  -----------------------------------------------
     *  0000 0000 0000 0000 0000 0000 0000 1011  // 11
     */
    
  • ~ 按位取反

    • 规则:0变1, 1变0 ;
    /* ~9 = ?
     * 0000 0000 0000 0000 0000 0000 0000 1001 // 9的补码
     *~1111 1111 1111 1111 1111 1111 1111 0110 // 结果的补码
     * 0000 0000 0000 0000 0000 0000 0000 0001
     * -------------------------------------------
     * 1111 1111 1111 1111 1111 1111 1111 0101 // 结果补码的反码
     * 1000 0000 0000 0000 0000 0000 0000 1010 // 结果的源码 -10
     */
    
  • ^ 按位异或

    • 规则: 相同为0,不同为1;
    /* 9 ^ 3 = ?
     * 0000 0000 0000 0000 0000 0000 0000 1001 // 9的补码
     *^0000 0000 0000 0000 0000 0000 0000 0011 // 3的补码
     *-------------------------------------------
     * 0000 0000 0000 0000 0000 0000 0000 1010 // 10
     */
    
    • 规律:

      • 任何两个相同的值异或之后的结果都是0;
      • 任何一个数和0异或之后的结果还是那个数
      • 任何一个数异或另外一个数两次之后,结果还是那个数;
    • 应用场景:简单的加密

    int pwd = 123456789;
    int res = pwd ^ 998;
    printf("加密之前: %i\n", pwd);
    printf("加密之后: %i\n", res);
    int res2 = res ^ 998;
    printf("解密之后: %i\n", res2);
    
    • 交换两个数的值(按位异或)(任何一个数异或另外一个数两次之后,结果还是那个数;)
    /*
     * 需求: 交换两个变量的值
     *
     * int a = 10; int b = 20;
     */
    int a = 10;
    int b = 20;
    printf("交换之前a = %i, b = %i\n", a, b);
    //    a = a ^ b;
    //    b = a ^ b; // b = a ^ (b ^ b) = a;  b = 10;
    //    a = a ^ b; // a = (a ^ a) ^ b = b; a = 20;
    
     b = a ^ b;
     a = a ^ b; // a = a ^ a ^ b; a = b;  a = 20;
     b = a ^ b; // b = a ^ b ^ b; b = a;  a = 10;
    printf("交换之后a = %i, b = %i\n", a, b);
    
    --------------------------------------------------------
    // 之前学的
    // 重点掌握
    //    int temp = a;
    //    a = b;
    //    b = temp;
    //    printf("交换之后a = %i, b = %i\n", a, b);
    
    //    a = a + b; // a = 10 + 20; a = 30;
    //    b = a - b; // b = 30 - 20; b = 10;
    //    a = a - b; // a = 30 - 10; a = 20;
    //    printf("交换之后a = %i, b = %i\n", a, b);
    
    
    
  • 按位左移 <<

    • 移动规则:从符号位开始整体移动,多余的砍掉,缺少的用零补
    • 计算规律: 一个数左移多少位,就是这个数乘以2的多少次幂
    • 应用场景: 在企业开发中一旦想要某个数乘以2的多少次幂, 就要想到左移
      例如: 9 * 2(1) = 18 ; 9 * 2(2) = 36

      9 << 1
      0000 0000 0000 0000 0000 0000 0000 0000 // 位置参考
      000 0000 0000 0000 0000 0000 0000 10010 // 9的补码
  • 按位右移 <<
    • 移动规则: 除符号位以外整体右移,多出来的砍掉,缺少的用符号位填充;
    • 计算规则: 一个正数右移多少位,就是这个数除以2的n次幂;
/*
    // 9 >> 1
     0000 0000 0000 0000 0000 0000 0000 0000 // 位置参考
     00000 0000 0000 0000 0000 0000 0000 100  --> 4
     */
    printf("%i\n", 9 >> 1);
    
    
    //  -9 >> 1
    
    /*
     * 1000 0000 0000 0000 0000 0000 0000 1001 // -9源码
     * 1111 1111 1111 1111 1111 1111 1111 0110 // -9反码
     * 1111 1111 1111 1111 1111 1111 1111 0111 // -9补码
     *
     0000 0000 0000 0000 0000 0000 0000 0000 // 位置参考
     11111 1111 1111 1111 1111 1111 1111 011 // 右移完毕的结果, 补码
     0000 0000 0000 0000 0000 0000 0000 0001
     11111 1111 1111 1111 1111 1111 1111 010 // 右移完毕的结果, 反码
     10000 0000 0000 0000 0000 0000 0000 101 // 右移完毕的结果, 源码
     */
    printf("%i\n", -9 >> 1);

变量的内存分析

  • 内存是连续的;
  • 内存地址从大到小;
  • 计算机会从内存地址大的开始分配内存(内存寻址从大到小);
  • 也就是说先定义的变量内存地址大于后定义的变量;
  • 变量的存储原则:
    • 先分配字节地址大内存,然后分配字节地址小的内存(内存寻址是从大到小),
    • 变量的首地址,是变量所占存储空间字节地址(最小的那个地址)
    • 低位保存在地地址字节上,高位保存在高地址字节上;
image
  • 定义变量

    • 定义变量的目的就是为了在内存中开辟一块存储空间;
    • 定义变量时指定数据类型的目的就是为了告诉计算机需要开辟多大的空间
    • char在内存中占用一个字节,int在内存中占用4个字节,double在内存中占用8字节(一个字节 = 8 位);
    • sizeof(char); sizeof(int); sizeof(double)
  • 由于计算机只能识别0,1,所以会把十进制转换为二进制

  • 给变量分配内存的时候是从内存地址比较大的开始存储;

char类型内存存储细节

  • %p 是专门用于输出变量地址的
  • & 是专门用于取出变量地址;
int num1 = 10;
int num2 = 20;
printf("num1 = %p\n", &num1);
printf("num2 = %i\n", &num2);
  • char类型基本概念
    • char是c语言中比较灵活的一种数据类型,称为"字符型"
    • char类型变量占1个字节存储空间, 共8位;
    • 除单个字符以外,C语言的转移字符也可以利用char类型存储;
字符 意义
\b 退格(BS)当前位置向后回退一个字符
\r 回车(CR),将当前位置移至本行开头
\n 换行(LF),将当前位置移至下一行开头
\t 水平制表(HT),跳到下一个TAB 位置
\0 用于表示字符串的结束标记
\ 代表一个反斜线字符 \
" 代表一个双引号字符 "
' 代表一个单引号字符 '
  • char类型的注意点:

    • char类型占一个字节,所以只有8位可以存储
    • ASCII码表中字符对应的ASCII值是从0~127
    • char类型的取值范围 -2^7 ~ 2^7 // -128 ~ 127
    • 一个中文字符占3字节(unicode表),所有char不可以存储中文
    char c = '我';  // 错误写法
    
    • 除转义字符以外,不支持多个字符
    char ch = 'ab'; // 错误写法
    
    • char 类型存储字符时会先查找对应的ASCII码值,存储的是ASCII值,所以字符6和数字6存储的内容不同;
    char ch1 = '6';  // 存储的是ASCII码 64;
    char ch2 = 6 ;  // 存储的是数字 6;
    

类型说明符

  • 类型说明符易班都是用于修饰int 类型所以在使用时可以省略int ;

  • 说明长度的:

    • short 短整型
    • long 长整型
    • long long
     char ch;
     short int num1;
     int num2;
     long int num3;
     long long int num4;
     printf("ch = %i\n", sizeof(ch));  // 1
     printf("short = %i\n", sizeof(num1)); // 2 
     printf("int = %i\n", sizeof(num2)); // 4
     printf("long = %i\n", sizeof(num3));  // 4
     printf("long long = %i\n", sizeof(num4));  // 8
    
  • C语言不看怎么存,只看怎么取;

    short num5 = 123;
    printf("num5 = %lld\n", num5);
    long num6 = 123;
    printf("num6 = %lld\n", num6);
    long long num7 = 123;
    printf("num7 = %lld\n", num7); // 123
    
    
    
  • short 和long

    • int占用4个字节(32bit),取值范围是-2^31 ~ 2^31-1;
    • short占用2个字节(16bit),取值范围是-2^15 ~ 2^15-1
    • long int 在32位编译器中占用4个字节;
    • 在64位编译器中,long占用8个字节(64bit),取值范围是-2^63 ~ 2^63-1
  • 注意点:

    • 如果存储的数据超出了类型对应的取值范围,那么就会导致存储的数据不对

    • 由于内存非常有限,所以在编写程序的时候,尽量合理的定义变量

    • short的长度不能大于 int , int的长度不能大于long

    • char 一定为8位(1个字节),毕竟char是我们变成能用的最小数据类型;

  • 说明符号位的:

    • signed 有符号型(默认int就是有符号的 默认的,一般用不上);
    • unsigned 无符号型
      • 不把二进制的第一位当做符号位来使用,
      • 所以只能存储零和正数,不能存储负数
      • 注意点: 如果变量被unsigned修饰了,那么取出的时候必须使用%u,%u就代表用无符号;
  • 对于整数而言,在内存中二进制的第一位就是符号位

    • 如果第一位是0,代表正数;第一位是1,代表负数;
    • 默认情况下所有的int 类型都是有符号的,也就是都会将第一位看做符号位;

推荐阅读更多精彩内容

  • 进制基本概念 什么是进制?进制是一种计数的方式,数值的表示形式 常见的进制十进制、二进制、八进制、十六进制 进制书...
    极客江南阅读 1,177评论 0 10
  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 1,199评论 0 2
  • 1.编译程序(1)gcc xx.c,他会默认生成一个a.out的可执行文件,在a.out所在目录,执行./a.o...
    萌面大叔2阅读 186评论 0 1
  • 1.编译程序 (1)gcc xx.c,他会默认生成一个a.out的可执行文件,在a.out所在目录,执行./a....
    萌面大叔2阅读 50评论 0 1
  • 网站乱码问题我们会经常碰到,大多见于非英文的中文字符或其他字符乱码,而且,这类问题常常是因为编码方式问题,主要原因...
    卐鑫卍阅读 1,231评论 1 7