十一:《初学C语言》— 操作符详解(上)

简介: 【8月更文挑战第12天】本篇文章讲解了二进制与非二进制的转换;原码反码和补码;移位操作符及位操作符,并附上多个教学代码及代码练习示例

1.了解二进制

其实二进制;八进制;十进制和十六进制都是数值的不同表示形式而已

  • 二进制:基数为2,由0和1两个数字组成,逢2进1。
  • 八进制:基数为8,由0~7八个数字组成,逢8进1。
  • 十进制:基数为10,由0~9十个数字组成,逢10进1。
  • 十六进制:基数为16,由0~9十个数字和A/a(10);B/b(11);C/c(12);D/d(13);E/e(14);F/f(15)六个数字共同组成,逢16进1。

(1)二进制转十进制

位权:以十进制为例

百位 十位 个位
十进制的位 1 2 3
10^2 10^1 10^0
按权展开 100 10 1
求值 1*100+ 2*10+ 3*1

注意:

  • 基数的多少次幂,这样的数被称为位权
  • 任何数的1次方都等于其本身;任何数的0次方都等于1

因此,二进制转十进制的做法如下:以二进制的111为例

二进制的位 1 1 1
2^2 2^1 2^0
按权展开 4 2 1
求值 1*4+ 1*2+ 1*1

(2)十进制转二进制

口诀:转2除2,倒取余,从下往上依次排序

如:将十进制的10转为二进制

十转二.png

注意: 当里面的数小于外面的基数时,也就是除不开的时候,里面的数就是最后的余数

(3)二进制转八进制

方法:将3个二进制数划为一组,按421这个上标的顺序标上标(分组是从后往前分,标上标是从前往后标),不足3位的在最前边补0(0标上标后依旧是0,不参与运算)其余的每组上标相加得出结果,最后每个组的结果合并就是转换完成的八进制数

如:将二进制数1101111转换为八进制的数

二进制数: 1101111
分组后 001 101 111
上标 421 421 421
八进制数结果 1 + 5 + 7 = 157

注意: 以0开头的数字会被当做八进制

(4)二进制转十六进制

方法:将4个二进制数划为一组,按8421这个上标的顺序标上标(分组是从后往前分,标上标是从前往后标),不足4位的在最前边补0(0标上标后依旧是0,不参与运算)其余的每组上标相加得出结果,最后每个组的结果合并就是转换完成的十六进制数

如:将二进制数110110111转换为十六进制的数

二进制数: 0001 1011 0111
分组后 0001 1011 0111
上标 8421 8421 8421
十六进制数结果 1 + b + 7 = 0x1b7

注意:

  • 转换成十六进制时,10~15这些数字必须用字母表示
  • 表示十六进制的时候前面要加0x

2.原码、反码和补码

整数的二进制表示方法有3种:原码、反码和补码

这三种表示方法均有符号位和数值位两部分,符号位都是用0表示正、用1表示负;而数值位最高位的一位是被当做符号位,剩余的都是数值位。

正整数的原码、反码和补码都相同,但负整数的三种表示方法各不相同:

  • 原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码
  • 反码:将原码的符号位(从左往右数第一位就是符号位)不变,数值位(除去符号位的其它位)依次按位取反就可以得到反码
  • 补码:反码+1就得到补码

如:int类型 5 的原码;反码和补码

正5.png

如:int类型 -5 的原码;反码和补码

负5.png

为什么表示5的是32位二进制数?

因为1字节等于8比特,而1个int类型占4个字节,也就是32比特位,所以用32个二进制数来表示

对于整型来说,数据存放内存中其实存放的是补码。 因为在计算机系统中,数值一律用补码来表示和存储。原因在于使用补码可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器);此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路

3.移位操作符

  • << 左移操作符
  • >> 右移操作符

(1)左移操作符

移位规则:左边丢弃,右边补上0

代码示例:将num左移一位

#include <stdio.h>
int main()
{
   
    int num = 10;
    // num的二进制表示:0000000000 0000000000 0000001010
    int ret = num << 1;
    // num << 1的移位结果:000000000 0000000000 00000010100
    printf("num = %d\n",num);
    printf("ret = %d\n",ret);
    return 0;
}

注意:

  • 整个过程num本身的值是不会发生改变的,只有将移位过程赋值给num本身的时候值才会发生改变
  • 无论是正数还是负数,在内存中移动运算的都是补码

(2)右移操作符

  1. 逻辑右移:左边用0填充,右边丢弃
  2. 算术右移:左边用原该值的符号位填充,右边丢弃

注意:

右移所采用的是逻辑右移,还是算术右移是不确定的,取决于编译器。但是大部分的编译器是采用算术右移的

代码示例:将num右移一位(逻辑右移)

#include <stdio.h>
int main()
{
   
    int num = -1;
    // num的二进制表示:1111111111 1111111111 1111111111
    int ret = num >> 1;
    // num >> 1逻辑右移的移位结果:01111111111 1111111111 111111111
    printf("num = %d\n", num);
    printf("ret = %d\n", ret);
    return 0;
}

代码示例:将num右移一位(算术右移)

#include <stdio.h>
int main()
{
   
    int num = -1;
    // num的二进制表示:1111111111 1111111111 1111111111
    int ret = num >> 1;
    // num >> 1算术右移的移位结果:1111111111 1111111111 1111111111
    printf("num = %d\n", num);
    printf("ret = %d\n", ret);
    return 0;
}

注意:

  • 移位操作符的操作数只能是整数,不能是浮点数
  • 对于移位操作符,不要移动负数位,这个标准是未定义的
//如:
int num = 10;
num >> -1; //移动负数位是错误的

4.位操作符

位操作符有:

&  // 按位与
|  // 按位或
^  // 按位异或

注意:

  • 以上位操作符的操作数只能是整数
  • 位指的是二进制位

多说无益,代码示例:

(1)按位与的运算规则:对应的二进制位上,有0则为0,两个同时为1才为1

#include <stdio.h>
int main()
{
   
    // &
    int a = 5;
    int b = -6;
    int c = a & b; // 按位与
    // a的二进制表示:0000000000 0000000000 0000000101
    // b的二进制表示:1111111111 1111111111 1111111010
    printf("%d\n",c);
    // a & b:0000000000 0000000000 0000000000
    return 0;
}

(2)按位或的运算规则:对应的二进制位上,有1则为1,两个同时为0才为0

#include <stdio.h>
int main()
{
   
    // |
    int a = 5;
    int b = -6;
    int c = a | b; // 按位或
    // a的二进制表示:0000000000 0000000000 0000000101
    // b的二进制表示:1111111111 1111111111 1111111010
    printf("%d\n",c);
    // a | b:1111111111 1111111111 1111111111
    return 0;
}

(3)按位异或的运算规则:对应的二进制位上,相同则为0,相异则为1

#include <stdio.h>
int main()
{
   
    // ^
    int a = 5;
    int b = -6;
    int c = a ^ b; // 按位异或
    // a的二进制表示:0000000000 0000000000 0000000101
    // b的二进制表示:1111111111 1111111111 1111111010
    printf("%d\n",c);
    // a ^ b:1111111111 1111111111 1111111111
    return 0;
}

练习1:实现两个数的交换

#include <stdio.h>
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;
}

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

// 第一种方法
#include <stdio.h>
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的数特别大的时候会导致范围越界

// 第二种方法
#include <stdio.h>
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;
}

1. 异或运算符的特点:

  • a ^ a = 0
  • 0 ^ a = a
  • a ^ a ^ b = b
  • a ^ b ^ a = b

通过以上的特点分析得出一个结论:异或是支持交换律的

2. 异或运算的局限性:

  • 异或只能用于整数交换
  • 代码的可读性较差
  • 代码的执行效率也是低于使用第3变量的方法的

练习3:求一个整数存储在内存中的二进制中1的个数

// 第一种方法
#include <stdio.h>
int main()
{
   
    int a = 15;
    int count = 0;
    while(a)
    {
   
        if(a % 2 == 1)
        {
   
            count++;
        }
        a = a / 2;
    }
    printf("count = %d\n",count);
    return 0;
}
// 第二种方法
#include <stdio.h>
int main()
{
   
    int a = 0;
    scanf("%d",&a);
    int i = 0;
    int count = 0;
    for(i<0; i<32; i++)
    {
   
        if(((a>>i) & 1) == 1)
        {
   
            count++;
        }
    }
    printf("count = %d\n",count);
    return 0;
}
// 第三种方法
#include <stdio.h>
int main()
{
   
    int n = 0;
    int count = 0;
    scanf("%d",&n);
    while(n)
    {
   
        n = n & (n-1);
        count++;
    }
    printf("count = %d\n",count);
    return 0;
}

练习4:判断一个数是否是2^n次方

#include <stdio.h>
int main()
{
   
    int n = 0;
    scanf("%d",&n);

    if(n & (n - 1) == 0)
    {
   
        printf("yes\n");
    }
    else
    {
   
        printf("no\n");
    }
    return 0;
}
目录
相关文章
|
3月前
|
存储 C语言 索引
【C语言篇】操作符详解(下篇)
如果某个操作数的类型在上⾯这个列表中排名靠后,那么⾸先要转换为另外⼀个操作数的类型后执⾏运算。
74 0
|
3月前
|
程序员 编译器 C语言
【C语言篇】操作符详解(上篇)
这是合法表达式,不会报错,但是通常达不到想要的结果, 即不是保证变量 j 的值在 i 和 k 之间。因为关系运算符是从左到右计算,所以实际执⾏的是下⾯的表达式。
257 0
|
27天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
34 10
|
1月前
|
存储 编译器 C语言
【C语言】简单介绍进制和操作符
【C语言】简单介绍进制和操作符
160 1
|
1月前
|
存储 编译器 C语言
初识C语言5——操作符详解
初识C语言5——操作符详解
173 0
|
3月前
|
C语言
C语言操作符(补充+面试)
C语言操作符(补充+面试)
45 6
|
4月前
|
C语言
五:《初学C语言》— 操作符
本篇文章主要讲解了关系操作符和逻辑操作符并附上了多个代码示例
44 1
五:《初学C语言》—  操作符
|
5月前
|
C语言
C语言逻辑操作符的短路问题
C语言逻辑操作符的短路问题
|
5月前
|
编译器 C语言
【C语言】:中移位操作符,位操作符详运算规则详解
【C语言】:中移位操作符,位操作符详运算规则详解
44 1
|
5月前
|
存储 编译器 C语言