C语言的操作符详解(上)超详细~

文章目录

  • 1.二进制介绍
    • 1.1二进制介绍
      • 1.1.1 2进制转10进制
      • 1.1.2 10进制转2进制数字
    • 1.2进制转8进制和16进制
      • 1.2.1 2进制转8进制
      • 1.2.2 2进制转16进制
  • 2.原码、反码、补码
  • 3.移位操作符
    • 3.1 左移操作符
    • 3.2 右移操作符
  • 4.位操作符:&,|、^、~
    • 4.1&操作符
    • 4.2 |操作符
    • 4.3 ^操作符
    • 4.4~操作符
    • 4.5 &、|、^、~的代码题型综合分析:
  • 5.逗号表达式

1.二进制介绍

1.1二进制介绍

在生活中,我们经常能听到2进制,8进制,16进制的这样的讲法,那它们分别是什么意思呢?其实2进制,8进制,16进制只是数值的不同表现形式而已。

比如:数值15的各种进制的表现形式如下图所示:

C语言的操作符详解(上)超详细~_第1张图片

我们重点介绍一下二进制:

首先我们还是得先从10进制开始讲起,众所周知,10进制是我们生活中经常使用的,我们已经形成了很多尝试:

  • 10进制中满10进1
  • 10进制的数字每一位都是0~9的数字组成

其实二进制也是一样的

  • 2进制中满2进1
  • 2进制的数字每一位都是0~1的数字组成

那么1101就是二进制的数字了。

1.1.1 2进制转10进制

其实10进制的123表示的值是一百二十三,为什么是这个值呢?其实10进制的每一位是权重的,10进制的数字从左向右是个位、十位、百位……,分别每一位权重是10的0次方,10的1次方,10的2次方。
具体计算步骤如下图所示:

C语言的操作符详解(上)超详细~_第2张图片

2进制和10进制也是类似的,只不过2进制的每一位的权重,从左向右是:
2的0次方,2的1次方,2的2次方……

如果是2进制的1101,该如何理解呢?

具体计算步骤如下图所示:

C语言的操作符详解(上)超详细~_第3张图片

1.1.2 10进制转2进制数字

下面是10进制转2进制的图:

C语言的操作符详解(上)超详细~_第4张图片

由图可以看出,我们可以通过短除法把二进制中的每一位由下往上给它计算出来。

1.2进制转8进制和16进制

1.2.1 2进制转8进制

8进制的数字每一位是0-7,0-7的数字,各自写成2进制,最多有3个2进制位就足够了,比如7的二进制是111,所以在2进制转8进制数的时候,从2进制序列中右边低位开始向左每3个2进制位会换算一个8进制位,剩余不够3个2进制位的就直接换算。

比如:2进制中01101011,换成8进制:0153,0开头的数字,会被当做8进制。

C语言的操作符详解(上)超详细~_第5张图片

1.2.2 2进制转16进制

16进制的数字每一位是0-9,a-f。0-9,a-f的数字,各自写成2进制,最多有4个2进制位就足够了,比如f的二进制是1111,所以在2进制转16进制数的时候,从2进制序列中右边低位开始向左每4个2进制位会换算成一个16进制位,剩余不够4个二进制位的直接换算。

如下图:2进制的01101011换成16进制:0x6b,16进制表示的时候前面加0x

C语言的操作符详解(上)超详细~_第6张图片

2.原码、反码、补码

整数的2进制表示方法有三种,即原码、反码、和补码

三种表示方法均有符号位数值位两部分,在2进制序列中,最高位的1位是被当做符号位,剩余的都是数值位。

符号位用0表示位“正”,用1表示为“负”。

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

原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码。

反码:将原码的符号不变,其他为一次按位取反就可以得到反码。

补码:反码+1就能得到补码。

举个例子,我们这里要计算-5的原码,这里我们需要注意的是int类型在C语言中是占四个字节,一个字节等于8个bit位,那么4个字节就等于32个比特位了,也就是等于32个二进制位,因此我们可以先用二进制数把-5表示出来,接下来就按位取反,然后再+1,那么我们就能求得出-5的补码出来了。

如下图所示

C语言的操作符详解(上)超详细~_第7张图片

对于整型来说:数据存放内存中其实存放的是补码

为什么呢?

在计算机系统中,数值一律用补码来表示和存储,使用补码,可以将符号位和数值域统一处理;

事实上,我们可以通过调试vs观察出内存的,我们可以把b的地址输进内存,可以看到里面存放的是十六进制数,由于一个十六进制为等于4个二进制位,两个十六进制为等于8个二进制位,8个bit又等于1个字节,所以这个十六进制有32个bit,也就是4个字节。而且细心的话,我们发现这个十六进制是倒着存的,我们可以把4个二进制位分为一组,那么这里有八组,所以存放的是补码。

同时,加法和减法也可以统一处理**(CPU只有加法器)**此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

这又是为什么呢?

具体如下图所示:

C语言的操作符详解(上)超详细~_第8张图片

通过上图,可以发现当我们把1的补码和-1的补码加起来,依然能得到数值0,因为从最低位1+1=2,余0进1,那么最终1会在最高位上,并且变成了33位,==由于呢,这两个数都是int整型的计算,所以最多只能存32位,因此,最高位1会舍去。==那么这两个补码相加最终结果就会变成:

00000000000000000000000000000000

3.移位操作符

《 左移操作符

》 右移操作符

:移位操作符的操作数只能是整数。

3.1 左移操作符

移位规则:左边抛弃、右边补0

具体如下代码所示:

#include 
int main(){

	int num=10;
	int n=num<<1;
	printf("n=%d\n",n);
	printf("num=%d\n",num);

	return 0;
}

C语言的操作符详解(上)超详细~_第9张图片

由图我们可以看出,当我们把10的补码算出来,然后左移一位就能得出左移一位的原码出来,右边补个0,那么我们就能把它左移一位的值给算出来。

需要注意的是,内存里面对应的值是补码,而真正打印出来的是原码的值。

比方说,我们这里要求-15<<1后的值出来,那么我们这里可以通过先求出-15的补码出来,然后左移一位,左边舍弃,右边补0。接下来再按位取反+1,我们就能求得出-15<<1的原码出来,结果也就是-30。

具体操作流程如下图所示:

C语言的操作符详解(上)超详细~_第10张图片

3.2 右移操作符

移位规则:首先友谊运算分两种

1.逻辑右移:右边用0填充,右边舍弃

2.算术右移:左边用原该值的符号位填充,右边舍弃

比方说我们求-1>>1的原码,通常来说,编译器一般都是用算术右移,又因为-1为负整数,因此右移的时候会用1来补上最左边的一位,然后把补码最右边的1给舍弃掉。

下面看一下代码用例和逻辑右移的分析图~

#include 
int main(){

	int num=-1;
	int n=num>>1;
	printf("n=%d\n",n);
	return 0;
}

C语言的操作符详解(上)超详细~_第11张图片

注:如果要使用逻辑右移来计算的话,它对应的图是这样的:

C语言的操作符详解(上)超详细~_第12张图片

由图可以看出,如果num为负整数的时候,逻辑右移会把符号位1替换为0,难免会有点简单粗暴,因此绝大部分的编译器都会采用算术右移来计算出它最终右移后原码的值出来。

需要注意的是:对于移位操作符,不要移动负数位,这个是标准未定义的。

例如

int num=10;
num>>-1;

4.位操作符:&,|、^、~

位操作符有:

1.& //按位与

2.| //按位或

3^ //按位异或

4.~ //按位取反

注:他们的操作数必须是整数

Ok~接下来我分别介绍这几个操作符。
在这里插入图片描述

4.1&操作符

首先看上图~

C语言的操作符详解(上)超详细~_第13张图片

通过运行用例我们可以发现,这里的&操作符是指把变量a和b的补码拿出来作比较,有0则0,两个为1则为1,从图中可以看出,这两个变量的所对应的二进制数都各不相同,因此最终a&b的结果为1。

4.2 |操作符

接下来我们介绍一下| 的用法
C语言的操作符详解(上)超详细~_第14张图片
通过运行用例,不难看出|操作符其实作用是对应的二进制位,有1则1,两个为0才为0,那这两个变量a和b中每位所对的二进制数都不相同,因此最终a|b的结果是全1,需要注意的是,由于真正打印的是原码的值,因此我们要把a|b的结果进行按位取反+1,这样我们就能得到原码的值,也就是打印的值为-1。

4.3 ^操作符

接下来我们介绍一下| 的用法

C语言的操作符详解(上)超详细~_第15张图片

由上图,我们可以看出操作符^的作用是对应的二进制位,相同为0,相异为1,那跟上面|操作符运行结果是一样的,对应的二进制数都不一样,所以最终结果跟上面a|b结果一样,最终变成32个1,同理,这里我们还需要把它转换为原码再把值给打印出来,因此最后打印出来的结果也是-1。
运行结果如下所示:
C语言的操作符详解(上)超详细~_第16张图片

4.4~操作符

另外,我们再介绍一下~操作符

~操作符顾名思义就是按位取反操作符,就是(二进制中是0的变为1,是1的变为0)

int main() {

    int n = 0;
    int x = ~n;
    //00000000000000000000000000000000
    //11111111111111111111111111111111
    printf("%d\n", x);


    return 0;
}

举个例子,我们这里把n的二进制序列按位取反后,把它的值赋给x,结果打印出来的值就为-1

运行结果如下所示:

C语言的操作符详解(上)超详细~_第17张图片

4.5 &、|、^、~的代码题型综合分析:

举个例子,曾经有一道面试题:

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

解题思路分析:这道题让我们实现两个数的交换,并且不能创建临时变量,我们可以怎么做呢?事实上,这道题可以这么想。由于操作符^的作用是相同为0,相异为1,因此我们可以得出这样的结论:

a^a=0;
a^0=a

那么我们可以根据上面那两条结论,进而写出下面这几行代码出来

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

它们的交换逻辑是这样的,具体看如下分析图~

C语言的操作符详解(上)超详细~_第18张图片

^这个符号可以写成交换律的形式,根据上述结论,我们就能推导出这个式子出来,进而地把a的值赋给b,把b的值赋给a,让我们来看一下运行结果吧。
C语言的操作符详解(上)超详细~_第19张图片
C语言的操作符详解(上)超详细~_第20张图片

练习1:编写代码实现:求一个整数存储在内存中的二进制中1的个数。

这个代码我们可以先这么写:


#include 
int main()
{
    int num = 15;
    int count= 0;//计数
    while(num)
    {
    if(num%2 == 1)
        count++;
        num = num/2;
    }
    printf("二进制中1的个数 = %d\n", count);
    return 0;
}

比方说,我们可以用循环的方式,通过**%2和/2**的操作就能把二进制的每一位数给求出来。

运行结果和代码分析可以看下图~

C语言的操作符详解(上)超详细~_第21张图片

C语言的操作符详解(上)超详细~_第22张图片

**但是当我们输入个-1上去的时候,程序打印出来的是0。**这个结果显然是错误的,因为在内存中,存的是补码,而-1的补码恰好是32个1,那么应该打印的结果是32。

那我们应该如何优化这个程序呢?

C语言的操作符详解(上)超详细~_第23张图片

比方说我们可以拿a的二进制序列中补码的值与1进行按位与操作,看看是否等于1,如果是,那么就count++,然后通过>>操作符,通过控制>>移动的力度,进而可以求出变量a的二进制序列中每一位1的个数是多少。
下面我们看看代码示例:

#include 
int main()
{
    int a = 0;
    scanf("%d", &a);

    int count = 0;
    for (int i = 0; i < 32; i++) {
        if (((a >> i) & 1) == 1)
            count++;
    }
   
    printf("二进制中1的个数 = %d\n", count);
    return 0;
}

从该程序中,我们首先让用户输入a的值,接下来我们通过使用for循环,让i的值从0到32之间逐一递增,然后用个if语句,让a的二进制序列>>i位,并与1进行按位与操作,看看是否等于1,,如果等于1的话,则count的值会++,而且在右移期间a的值不会发生任何变化。

比方说:这个代码可以优化成这样:

#include 
int main()
{
    int num = 13;
    int i = 0;
    int count = 0;//计数
    while(num)
    {
        count++;
        num = num&(num-1);
    }
    printf("二进制中1的个数 = %d\n",count);
    return 0;
}

从上述代码,我们可以知道,当num为13的时候2进制序列有3个1,然后每次num都会和(num-1)的二进制序列进行&操作,然后每次循环就能去掉最右边的那个1,直到当num的数变成0的时候,就会退出while循环,进而我们就可以求出count的个数出来。

下面给大家看一下这个代码分析图:
C语言的操作符详解(上)超详细~_第24张图片

运行结果如下所示:

C语言的操作符详解(上)超详细~_第25张图片

另外,我们再介绍一下~操作符

~操作符顾名思义就是按位取反操作符,就是(二进制中是0的变为1,是1的变为0)

int main() {

    int n = 0;
    int x = ~n;
    //00000000000000000000000000000000
    //11111111111111111111111111111111
    printf("%d\n", x);


    return 0;
}

举个例子,我们这里把n的二进制序列按位取反后,把它的值赋给x,结果打印出来的值就为-1

运行结果如下所示:

C语言的操作符详解(上)超详细~_第26张图片

接下来,我们再看一道练习

练习2:二进制位置0或者置1

编写代码将13二进制序列的第5位修改为1,然后再改为0。

13的2进制序列: 00000000000000000000000000001101

将第5位置为1后:00000000000000000000000000011101

将第5位再置为0:00000000000000000000000000001101

由于要把第五个位置变为1,并且二进制的其他位是不能变的,那么我们可以这么想,先定义一个变量a,把13赋给a,由于|操作符是对应的二进制位,有1则1,两个为0才为0,所以我们可以将1的二进制序列向左移动四位并与a进行按位或的操作,再把它的值赋给a,即a=a|(1<<4),这样我们就可以把13二进制序列的第5位修改为1了

int main() {

    int a = 13;
    //00000000000000000000000000001101
    //00000000000000000000000000000000
    //00000000000000000000000000000010
    //将a的二进制第五位改为1
    a = a | (1<< 4);
    printf("a=%d\n", a);
	//将a的二进制第五位改为0
    a = a &~(1 << 4);
    printf("a=%d\n", a);


    return 0;
}

而现在,我们要将a的二进制第五位由1改为0,那我们可以怎么做,细心的话,我们可以发现,由于&操作符是对应的二进制位,有0则0,两个为1才为1,那我们其实可以让1的二进制序列向左移动四位并对其按位取反后再和a进行按位与的操作,即:a = a &~(1 << 4);这样就可以保证其他二进制位的数字不变,只是把a的二进制第五位由1改为0

我们来看一下代码的运行结果:

C语言的操作符详解(上)超详细~_第27张图片

ok,接下来,我们再看一个练习~

练习3

写一个代码:判断一个数是不是2^n?

思路分析:首先,从题干分析,我们是不是可以看出当这个二进制序列只有1个1时才是2^n。于是我们通过画图来分析:

C语言的操作符详解(上)超详细~_第28张图片

比方说:用户输入了8,8的2进制序列为00001000,7的2进制序列为000001111,那7和8进行按位与操作,最终可以得出7&8=0。

反之,如果用户是输入15,那15的2进制序列为00001111,14的2进制序列为00001110,那15和14进行按位与操作,最终得出的结果是00001110,显然不等于0。

因此,那个if的条件判断我们就可以写成这种形式:

if((n&(n-1))==0)

最终代码可以写成:

int main() {
   
    int n = 0;
    scanf("%d",&n);
    if ((n & (n - 1)) == 0) {
        printf("yes\n");
    }
    else {
        printf("no\n");
    }
    return 0;
}

我们可以测试一下运行用例:

C语言的操作符详解(上)超详细~_第29张图片

C语言的操作符详解(上)超详细~_第30张图片

5.逗号表达式

语法形式:

exp1,  exp2, exp3, …expN

逗号表达式,顾名思义就是逗号隔开的多个表达式。

逗号表达式,从左往右依次执行。整个表达式的结果是最后一个表达式的结果。

比方说:我们举个例子

int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
c是多少

从上述代码,可以看到第三行括号里面是逗号表达式,那么将会从左边一次向右执行,也就是(a>b->1>2,a=b+10=2+10=12,12,b=a+1=12+1=13)

,也就是说最终变量c的值变为13,

我们可以用vs编译器来测试一下:

C语言的操作符详解(上)超详细~_第31张图片

需要注意的是,虽然有些逗号表达式的最后一个表达式可能跟前面的表达式关系不大,例如像下面这行代码:

if (a =b + 1, c=a / 2,d > 0)

但我们还是要老老实实地从左往右依次计算,不能不管前面的表达式。因为有些时候前面表达式的结果可能会影响最后一个表达式的结果。像我们上面求变量c的值,恰恰需要前面表达式a=b+10=2+10=12,如果我们忽略了这个表达式,结果就会出错。

okk~今天博主的分享就到此结束啦!如果觉得博主讲得还不错的话,欢迎大家一键三连支持一下,
谢谢!!!

你可能感兴趣的:(C语言笔记,c语言,算法,笔记)