• 第五站:操作符(第一幕)


    操作符相关的知识,在我们初识C语言(第三幕)这篇文章中其实已经讲到过了。但是那一次讲解仅仅只是一些粗略的知识讲解,我们在那里面已经提到过,后续会专门超级详细的讲解操作符的知识,我们现在就来攻下操作符相关的知识吧。

    这是之前讲解操作符的链接初识C语言(第三幕)

    目录

    一、操作符分类

    二、算术操作符

    1./号操作符

     2.%操作符

    三、移位操作符

    1.整数的二进制表示

    1.计算机中的正数和负数的二进制序列

    2.原码,反码和补码

    3.原码反码补码三码的相互转换图解

    2.数据在内存中的存储

    3.左移操作符详解

    (1)正数的左移

    (2)负数的左移

    4.右移操作符详解

    (1)两种右移

    四、位操作符

    1.按位与&

    2.按位或|

    3.按位异或 ^

    (1)详解异或操作符

    (2)一道经典的题目

    五、赋值操作符

    1.赋值操作符(=)

    2.复合赋值符

     六、单目操作符

    1.单目操作符的概念

    2.单目操作符的种类

    3.单目操作符详解

    (1)!逻辑反操作

    (2)+ -操作符

    (3)&取地址 *解引用

    (4)sizeof

    (5)~对一个数的二进制按位取反

     (6)++ --操作符

    (7)强制类型转换

    4.sizeof与数组的一个经典题目

     总结:


    一、操作符分类

    操作符一般分为以下几类:

    算术操作符

    移位操作符

    位操作符

    赋值操作符

    单目操作符

    关系操作符

    逻辑操作符

    条件操作符

    逗号表达式

    下标引用、函数调用和结构成员

    二、算术操作符

    以下是五种算术操作符

    +        -        *        /        %      

    其中前三种相信大家都了解的很清楚。跟数学中的运算一致。需要注意的就是/和%这两个操作符

    1./号操作符

    我们直接看这样一段代码

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include<stdio.h>
    3. int main()
    4. {
    5. int ret = 10 / 3;
    6. printf("%d", ret);
    7. return 0;
    8. }

    他的运行结果是

     这是因为对于除法操作符而言,两边的操作数都是整数,则执行的是整数除法,而如果想要计算出小数,则需要两边的操作数至少有一个是浮点类型的。

    计算出浮点数的案例如下

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include<stdio.h>
    3. int main()
    4. {
    5. //int ret = 10 / 3;
    6. //printf("%d", ret);
    7. double ret = 10.0 / 3;
    8. printf("%lf", ret);
    9. return 0;
    10. }

    运行结果为

     但是我们这块是六位小数,如果只想要一位小数。则将第八行中的lf改为.1lf,这个代表的是打印小数点后一位。如下所示

     2.%操作符

    我们看下面一段代码

    1. #include
    2. int main()
    3. {
    4. int ret = 10 % 3;
    5. printf("%d", ret);
    6. }

    运行结果为

     其实%操作符(取模操作符)可以计算的是两个操作数的余数。并且两个操作数必须为整数,否则报错。

    三、移位操作符

    >>右移操作符

    <<左移操作符

    注意:移位操作符只能作用于整数

    首先我们得知道,这个移位操作符,移的是二进制位,所以说这就要求我们要熟练掌握进制转换,我们在之前出过一篇进制转换的文章,这里附上链接:进制转换

    其次是他们的使用,他们的使用是这样的,并且他们的移位并不会改变自身的大小

    1. #include<stdio.h>
    2. int main()
    3. {
    4. int a = 10;
    5. int b = a << 2;
    6. int c = a >> 1;
    7. return 0;
    8. }

    1.整数的二进制表示

    这是我们了解移位操作符的基础,首先我们必须了解的是,整数的二进制表示形式一共有三种。分别是原码,反码,补码。

    1.计算机中的正数和负数的二进制序列

    比如说我们看这一段很简单的代码

     上图中a是10,b是-10,他们的区别就是一个是整数一个是负数

    a是10的话,我们不难得出他的二进制序列是1010

    而a又是一个整型类型。一个int类型是32个bit位,所以我们得出

    1010的前面需要补0。

    所以二进制序列其实应该为00000000 00000000 00000000 00001010

    但是呢我们如何区分他是+10还是-10呢?

    我们是用最高位来进行区分的

    最高位是1,他就是负数,最高位是0,它就是正数。

    所以a,也就是10,他的二进制序列为00000000 00000000 00000000 00001010

            b,也就是-10,他的二进制序列为10000000 00000000 00000000 00001010

    我们将上面这两种,按照他的正负,直接就能写出来的二进制序列称为原码。

    2.原码,反码和补码

    我们在上面说过,整数的的二进制表示形式一共有三种,分别是原码,反码和补码。而我们在上面也解释了如何求出一个原码。原码其实就是按照一个数的正负,直接就能写出来的二进制序列。

    那么反码和补码又是如何求出来的呢?

    我们这里给出一条结论,正数的原码,反码,补码相同;负数的反码,补码是需要经过计算的,反码是符号位不变,其他位按位取反,得到的就是反码,补码是反码+1得到的。

    我们这里举出一个例子

    对于上面的a而言,他是一个正数10

    他的原码是00000000 00000000 00000000 00001010

    他的反码是00000000 00000000 00000000 00001010

    他的补码是00000000 00000000 00000000 00001010

    也就是原码,反码,补码均相同

    对于上面的b而言,他是一个负数-10

    他的原码是10000000 00000000 00000000 00001010

    他的反码是11111111 11111111 11111111 11110101

    他的补码是11111111 11111111 11111111 11110110

    原码是需要将最高位,也就是符号位改为1得到的

    反码是需要将原码的符号位不变,其他位均按位取反得到的

    补码是反码+1得到的

    3.原码反码补码三码的相互转换图解

    对于正数而言,他的三码统一,所以无需进行转换

    需要注意的是负数的三码转换,下面的负数转化的三码转换图解:

    对于一个已知的原码,符号位不变,其他位按位取反可以得到反码,然后反码+1可以得到补码。(如图中黑色线所示)

    相反的,如果补码想要得到原码可以直接按照上面相反的步骤即可得到(如图中绿色线所示)

    当然还有另外一种方法,也可以从补码转换成原码,那就是和原码转换成补码的方法一样。补码的符号位不变,其他位按位取反,然后+1.这样也可以得到原码 (如图中橙色线条所示)

     为了方便我们理解,我们举一个例子

    对于上面的b而言,他是一个负数-10

    他的原码是10000000 00000000 00000000 00001010

    他的反码是11111111 11111111 11111111 11110101

    他的补码是11111111 11111111 11111111 11110110

    这是我们刚刚走过一个流程,是-10的原码如何得出补码的

    那么从补码到原码的方法,按照逆运算,也就是绿色线条的方法,肯定是可以得到原码的

    我们来看一下橙色线条的方法

    -10的补码是11111111 11111111 11111111 11110110

    符号位不变,其余按位取反得:

                       10000000 00000000 00000000 00001001

    +1得           10000000 00000000 00000000 00001010

    最终的结果恰好就是原码。

    2.数据在内存中的存储

    为什么我们突然要讲一下原码,反码和补码呢?他们有什么用呢?其实这是因为内存中存储的其实是补码。所以在参与移位操作符运算的时候均需要按照补码来移位。而绝不是对原码进行操作,这一点也是很多人在移位操作符上经常犯得经典的错误,标准的零分。

    3.左移操作符详解

    (1)正数的左移

    我们看这样一段代码

    1. #include
    2. int main()
    3. {
    4. int a = 10;
    5. int b = a << 1;
    6. printf("%d\n", a);
    7. printf("%d\n", b);
    8. return 0;
    9. }

    这段代码中使用了一个左移操作符。我们先来分析一下,左移操作符是如何操作的?

    我们在前文中说过,数据在内存中存储的是补码,所以移位操作符应该作用于补码

    如下图所示,假设这是a在内存中的存放

     我们让其进行左移一位。就变成下图所示

     左边有一位被多出去了,右边少了一位。此时计算机就会将左边多出去的扔掉,不要了,然后和右边自动补0,如下图所示,紫色的0被扔掉,补充绿色的0

    此时我们的二进制序列就变为了

    00000000000000000000000000010100,当然这是补码,以上所有的操作都是对于补码的操作

    我们打印出来的时候,我们用的是原码,但是由于正数三码统一,所以上述补码转换为原码的结果仍然是它

    而它转换成10进制就是20

    这就是左移操作符的图解了

    我们按照上面的代码往下走,这个a<<1以后变为了20赋值给了b,所以b其实就是20了,但是这个过程中a并没有发生改变

    最终运行结果就是

     事实上,我们会发现,左移操作符,他使得二进制序列左移一位,他会使得我们每一位的权重都变为了原来的二倍。所以说左移操作符会使得原来的数扩大十倍。

    所以左移操作符的运算规则是左边丢弃,右边补零

    (2)负数的左移

    我们继续来看一下负数的案例,我们看这个代码

    1. #include
    2. int main()
    3. {
    4. int a = -10;
    5. int b = a << 1;
    6. printf("%d\n", a);
    7. printf("%d\n", b);
    8. return 0;
    9. }

    对于此时的这个a而言

    他的原码是10000000 00000000 00000000 00001010

    他的反码是11111111 11111111 11111111 11110101

    他的补码是11111111 11111111 11111111 11110110

    假设它在内存中是这样放的

     我们对其进行左移,而左移的规则是左边丢弃,右边补零

     所以最终的补码结果为

    11111111 11111111 11111111 11101100

    转化为反码为(补码-1)

    11111111 11111111 11111111 11101011

    转化为原码为(符号位不变,按位取反)

    10000000 00000000 00000000 00010100

    而此时正好就是-20

    我们运行一下

    而此时我们会发现仍然符合我们上面的规则,左移一位,扩大2倍。

    4.右移操作符详解

    (1)两种右移

    与左移不同的是,右移要比左移复杂一点,因为它有两种右移方式,而左移只有一种

    右移有如下两种方式。具体采用哪种方式是取决于编译器的

    1.算术右移(绝大多数编译器,平常见到的)

    2.逻辑右移

     我们看这样一段代码

    1. #include
    2. int main()
    3. {
    4. int a = -1;
    5. int b = a >> 1;
    6. printf("%d\n", a);
    7. printf("%d\n", b);
    8. return 0;
    9. }

    我们先写出-1的原码,进而推导出补码

    原码:10000000 00000000 00000000 00000001
    反码:11111111 11111111 11111111 11111110(符号位不变,其他按位取反)
    补码:11111111 11111111 11111111 11111111(反码+1)

    下图是-1在内存中的存储

     我们右移一位,得到下图

     可是这里出现一个一个问题,是补0还是1呢?

    这块就回到了我们一开始所说的右移的两种方式了

    算术右移:右边丢弃,左边补原来的符号位

    逻辑右移:右边丢弃,左边直接补0

    而我们目前的visual studio2022编译器采用的是算术右移

    所以我们补1

    补了1以后呢,我们发现,这和我们之前的一模一样

    所以这个补码转换成原码是

    10000000 00000000 00000000 00000001

    转换成十进制数就是-1

    所以我们编译器应该的结果就是两个-1

    注意:对于移位运算,不要移动负数位,也不要超出数据的大小。这是c语言标准未定义的。是很危险的操作。

    四、位操作符

    位操作符有

    &        //按位与

    |        //按位或

    ^        //按位异或

    注:他们的操作数必须都是整数,同时也是针对二进制位进行计算的

    1.按位与&

    我们看这样一段代码

    1. #include
    2. int main()
    3. {
    4. int a = 3;
    5. int b = -5;
    6. int c = a & b;
    7. printf("%d", c);
    8. return 0;
    9. }

    这段代码中

    由于在内存中数据都是补码,所以&两端的数据都应该先转化成补码

    a是3

    所以它的原码是00000000 00000000 00000000 00000011

    而正数三码统一,所以补码就是上面的二进制序列

    b是-5

    所以它的原码是10000000 00000000 00000000 00000101

                  反码为11111111 11111111 11111111 11111010

                  补码为11111111 11111111 11111111 11111011

    而按位与(&)的运算是,每一个对应的二进制位依次进行计算,只有对应的二进制位都为1,结果才为1,否则为0

    所以上述两个反码的运算过程见下图

    所以也就是说计算出来的c的补码为00000000 00000000 00000000 00000011

    而这个c的符号位为0,也就是正数,因此三码统一,所以它的原码等于补码

    所以得出c的值为3

    我们在总结一下&的运算规则:

    对应的二进制有0,则为0,都为1,才为1

    2.按位或|

    我们看这样一段代码

    1. #include
    2. int main()
    3. {
    4. int a = 3;
    5. int b = -5;
    6. int c = a | b;
    7. printf("%d", c);
    8. return 0;
    9. }

    对于这段代码而言

    我们仍然是将a和b的二进制补码形式计算出来

    a的补码为00000000 00000000 00000000 00000011

    b的补码为11111111 11111111 11111111 11111011

    而按位或( | )运算是,每一个对应二进制位依次进行计算,只要出现1,则为1,两个都为0,才为0

    所以计算过程为

     也就是c的补码为11111111 11111111 11111111 11111011

    有了c的补码就可以计算出它的反码,进而推导出原码

     

    所以c的原码为10000000 00000000 00000000 00000101, 也就是-5

    我们在总结一下|的运算规则:

    对应的二进制有1,则为1,都为0,才为0

    3.按位异或 ^

    (1)详解异或操作符

    我们来看这样一段代码

    1. #include
    2. int main()
    3. {
    4. int a = 3;
    5. int b = -5;
    6. int c = a ^ b;
    7. printf("%d", c);
    8. return 0;
    9. }

    对于^这个操作,异或的计算是这样的,将每一个二进制数依次进行计算,相同为0,相异为1

    所以计算过程为

    也就是c的原码为10000000 00000000 00000000 00001000 ,而这个正好是-8

    所以运行结果为

    我们总结一下^的运算规律

    对应的二进制序列,相同为0,相异为1

    (2)一道经典的题目

    题目描述:

    不创建第三个变量,实现两个整数的交换

    我们最容易想到的就是创建一个临时变量来进行交换,这样是可以交换,但是违背了题意,那么我们应该怎么做呢?

    请看下面的代码

    1. #include<stdio.h>
    2. int main()
    3. {
    4. int a = 3;
    5. int b = 5;
    6. printf("%d %d\n", a, b);
    7. a = a + b;
    8. b = a - b;
    9. a = a - b;
    10. printf("%d %d\n", a, b);
    11. return 0;
    12. }

    运行结果为

    我们发现这段代码居然可以实现交换,那这又是为什么呢?

    我们仔细分析一下这段代码,如下图所示

     但是这种写法存在一种问题,如果a和b的值太大了,导致他们的和溢出了,但是他们本身是不溢出的。这样就会出现问题。所以这样写是存在局限性的。那么我们还有更好的办法吗?我们说当然由。别忘记我们正在讲解异或的知识,所以我们考虑用异或去解决这个问题

    请看这段代码

    1. #include
    2. int main()
    3. {
    4. int a = 3;
    5. int b = 5;
    6. printf("%d %d\n", a, b);
    7. a = a ^ b;
    8. b = a ^ b;
    9. a = a ^ b;
    10. printf("%d %d\n", a, b);
    11. return 0;
    12. }

    运行结果为

     可见可以实现交换,那么为什么可以进行交换呢?我们来详细分析一下

    第一步操作中,a^b赋给了a

    第二步操作中,用现在的a^b,我们将第一步的a代入得:(a^b)^b,也就是a^b^b,而异或是满足交换律,结合律的,式子中正好由两个b,所以两个b异或的结果正好就是0,而0^a的结果是什么呢?

    异或的运算规则是依次比较对应二进制(补码),相异为1,相同为0。而二进制只有0和1这两个,0和0异或结果为0,0和1异或,结果为1。所以我们得出结论:0与任意数异或结果为该任意数,这一点似乎与小学数学中所学的1乘以任意数结果为该任意数有着异曲同工之妙。其实,看到这块,我们也可以试着推测一下1异或任意数的值是多少呢?我们知道1异或0等于1,1异或1等于0。这样我们变可以想到任意一个数异或1以后。它的二进制补码中的1变为0,0变为1。而这些计算出来的是补码,最终我们还要-1,然后符号位不变,其他按位取反。所以我们得出一个结论:1异或任意整数,若该任意数大于等于0且不等于1,则结果为该任意数+1;若该任意数等于1,则结果为0;若该任意数小于0,则结果为该任意数-1。这个结论读者可自行验证

     因此有了上面的分析,我们可以得到a^b^b的结果就是a,所以第二步的操作实际上就是把b赋给了a

    第三步的操作就与第二步的操作分析过程类似,结果就是将原来的a赋给了b。

     当然我们上面讲了两种方式,但是要注意,在实际的应用中,以上两种方式交换都不如直接使用第三个变量合适。因为对于异或而言,它的可读性差,并且效率也不如使用第三个变量高,而且它只能针对整数进行交换。这里仅仅只是作为一种思维题考查。在实际应用中很少见。

    五、赋值操作符

    1.赋值操作符(=)

    赋值操作符,是一个很常见的操作符,它可以修改我们以前不想要的一个值。

    int weight=12;

    weight=120;//直接进行赋值修改即可

    同时赋值操作符也可以连续使用

    int x=10;

    int y=20;

    int a=0;

    a=x=y+1;

    这个代码的意思就是,先将y+1的值赋给x,然后将x赋给a,这样写的话其实不利于我们进行调试,而且可读性差,所以我们一般都将其拆开

    x=y+1;

    a=x;

    这样写就更加清晰了

    2.复合赋值符

    复合赋值符顾名思义,就是将赋值操作符和一些其他的操作符结合起来。由如下几种

    +=        -=        *=        /=        %=        >>=        <<=        &=        |=        ^=

    举个例子,如下图所示,这些复合赋值符都是可以进行展开的,这样就可以实现改变我们原来的值,所有的操作符都是同理的

     六、单目操作符

    1.单目操作符的概念

    什么是单目操作符呢?要想了解这个,我们先看下面的讲解

    a + b 这个操作中,a和b是操作数,+是操作符。这个操作符其实是一个双目操作符,所谓双目操作符,就是有两个操作数。

    那么根据这个我们可以得到,所谓单目操作符,就是只有一个操作数。

    2.单目操作符的种类

    有如下几种单目操作符

    !           逻辑反操作     

    -           负值                

    +          正值

    &          取地址

    sizeof      操作数的类型长度(以字节为单位)

    ~           对一个数的二进制按位取反

    --          前置、后置--

    ++          前置、后置++

    *           间接访问操作符(解引用操作符)

    (类型)       强制类型转换

    3.单目操作符详解

    (1)!逻辑反操作

    我们直接看这个代码

    1. #include
    2. int main()
    3. {
    4. int flag = 5;
    5. if (flag)
    6. {
    7. printf("1\n");
    8. }
    9. if (!flag)
    10. {
    11. printf("2\n");
    12. }
    13. printf("%d\n", flag);
    14. printf("%d\n", !flag);
    15. printf("%d\n", !!flag);
    16. return 0;
    17. }

    运行结果为

     第一次打印出来1是因为,只有0为假,非零为真

    第二次打印出来5,是因为flag就是5

    第三次打印出来0,是因为使用了!,导致5变成了假,也就是0

    第四次打印出来1,是因为使用了两次!,使用一次变为0,第二次使用变为真,而这个真默认为1

    除此之外,我们还有一点需要说明一下

    就是布尔类型,在c语言中也有布尔类型,当然这个是在c99中引入的,所以很多编译器不支持,布尔类型其实是判断真假的类型

    我们看这个代码

    1. #include
    2. #include
    3. int main()
    4. {
    5. _Bool flag = true;
    6. if (flag)
    7. {
    8. printf("1");
    9. }
    10. return 0;
    11. }

    运行结果为

     或者说这段代码,可以用来判断闰年

    1. #include
    2. #include
    3. _Bool is_leap_year(int y)
    4. {
    5. if ((y % 4 == 0) && (y % 100 != 0) || (y % 400==0))
    6. {
    7. return true;
    8. }
    9. else
    10. {
    11. return false;
    12. }
    13. }
    14. int main()
    15. {
    16. int y = 0;
    17. scanf("%d", &y);
    18. int ret=is_leap_year(y);
    19. printf("%d", ret);
    20. }

    当然,这个这个布尔类型除了可以使用_Bool以外,还可以使用bool来说明类型,两者是等价的

    (2)+ -操作符

    这两个操作符就很简单了,就是我们初中数学所学的运算。完全一致,这里给出一个例子

    1. #include
    2. int main()
    3. {
    4. int a = 10;
    5. printf("%d\n", a);
    6. printf("%d\n", +a);
    7. printf("%d\n", -a);
    8. return 0;
    9. }

    运行结果为

     其实在这里,有一部分人会跟unsigned的用法混淆!这两个本应该是没有关系的两个东西,不过既然提到,在这里也提及一下

    unsigned 是无符号整型

    我们一般的类型前面其实应该加一个signed 意思是有符号类型,但是省略了

    我们知道,在我们计算一个数的原码时候,第一位是符号位,这就是有符号类型的意思

    而无符号类型,第一位是不作为符号位的。

    也就是说,对于一个(unsigned int)10而言

    它的原码是00000000 00000000 00000000 00001010,所以补码也是这个,而补码的第一位的零是不作为符号位,直接按补码转换成二进制来进行打印。

    对于(unsigned int )-10而言

    我们还是先写出它的原码

    10000000 00000000 00000000 00001010

    反码为

    11111111 11111111 11111111 11110101

    补码为

    11111111 11111111 11111111 11110110

    -10在内存中存储的是补码,由于第一位不是符号位,所以,直接按补码的大小直接转化为十进制就是它真正的值

    代码为

    1. #include
    2. int main()
    3. {
    4. unsigned int a = 10;
    5. unsigned int b = -10;
    6. printf("%u\n%u", a, b);
    7. return 0;
    8. }

    注意:unsigned类型需要以%u来打印

    结果为

    (3)&取地址 *解引用

    对于取地址操作符而言,一般都是对一个变量取地址的,这里给出代码,详细说明取地址的各种情形

    1. #include
    2. int main()
    3. {
    4. //对于变量
    5. int a = 10;
    6. int* p = &a;//取出a的地址,放入p这个指针中
    7. char ch = 'a';
    8. char* pc = &ch;//取出ch的地址
    9. char arr[10] = { 0 };
    10. char* p2 = arr;//取出数组首元素的地址
    11. char* p3 = &arr[0];//取出数组首元素的地址
    12. //对于常量而言
    13. char* p4 = "abcdef";//取出来的是a的地址
    14. printf("%c", *p4);
    15. }

    当然还有解引用操作符,如下代码所示,可以通过*去访问a,进而修改它的值

    1. #include
    2. int main()
    3. {
    4. int a = 10;
    5. int* p = &a;
    6. *p = 20;
    7. printf("%d", a);
    8. return 0;
    9. }

    (4)sizeof

    首先sizeof是关键词也是操作符,这两个是不冲突的,不是必须选择一个的。是用来计算一个类型,一个变量的大小的

    我们看一段代码

    1. #include
    2. int main()
    3. {
    4. int a = 0;
    5. printf("%d\n", sizeof(a));
    6. printf("%d\n", sizeof a );
    7. printf("%d\n", sizeof(int));
    8. return 0;
    9. }

    运行结果为

     需要注意的是,sizeof计算一个变量的大小时,括号可以省略,但是计算类型的大小时,括号不可以省略。sizeof int 这种行为就是报错的,但是sizeof(int)不会

    而且根据这一点我们还可以证明sizeof不是一个函数,因为我们sizeof一个变量时候,括号是可以省略的,而函数调用的时候,是不能省略括号的。

    类似的,sizeof也可以对数组进行操作

    1. #include
    2. int main()
    3. {
    4. int arr[10] = {0};
    5. printf("%d\n", sizeof(arr));
    6. printf("%d\n", sizeof arr );
    7. printf("%d\n", sizeof(int [10]));
    8. return 0;
    9. }

    运行结果为

     需要注意的是,int [10]是arr数组的类型,要知道,数组其实也是有类型的。这一点在后面还会提到

    然后还需要注意的就是这一段代码

    1. #include
    2. int main()
    3. {
    4. int a = 5;
    5. short s = 3;
    6. printf("%d\n", sizeof(s = a + 3));
    7. printf("%d\n", s);
    8. }

     这段代码的运行结果为

     第一个打印出2是因为假设里面的发生了,则a+3放到了s里面,s的大小是2

    第二个打印出来的是3的原因是sizeof内部的表达式是不进行计算的!!!

    而它不进行计算的原因是我们的程序由.c文件变成.exe可执行程序文件时候需要经过几个阶段

     而sizeof是在编译阶段就已经计算出来了,所以内部的表达式不会进行计算

    (5)~对一个数的二进制按位取反

    与计算反码不同的是,这个波浪号是使全部位都按位取反。而计算反码时符号位不会发生改变,当然这个也只能作用于整数。我们举一个例子,如下代码所示

    1. #include
    2. int main()
    3. {
    4. int a = 0;
    5. printf("%d", ~a);
    6. return 0;
    7. }

    运行计算过程为

     运行结果为

     当然在我们说到这块的时候,我们可以试着应用一下我们的位操作符做一个经典题目

    如下图所示,这段代码中,想要使得a的二进制序列中,从右往左数第五个二进制数改为1,该如何实现呢?

    这道题有很多人连思考都没有,就想要使用指针,这是一个经典的错误,标准呢的零分,因为指针操作的内存最小单位都是字节,而这个是一个比特位,要知道一个比特位是四个字节。所以使用指针是肯定不可以的。那么该如何解决呢?其实这是一个针对改变比特位的问题,我们自然而然就想到使用位操作符。而位操作符我们截至到目前总共学习了>> 右移<< 左移,&按位与,|按位或,^按位异或,以及~,二进制取反。这几种,而此时我们想要解决我们可以这样想,任何一个数或上1都会变成1,或上0还是原来的数,所以我们可以给这个二进制数或上一个只有第五个比特位是1的一个数,那个数又该如何获得呢?其实这里我们就可以使用左移操作符了,我们只要使用左移操作符,问题就迎刃而解了

    1. #include<stdio.h>
    2. int main()
    3. {
    4. int a = 9;
    5. //00000000 00000000 00000000 00001001
    6. //00000000 00000000 00000000 00010000(可由1<<4得到)
    7. //00000000 00000000 00000000 00011001(按位或得到了目标)
    8. a |= (1 << 4);
    9. printf("%d", a);
    10. return 0;
    11. }

    运行结果为

     那么问题来了,如果我们又想要吧第五位改回去该如何做呢?

    其实我们思考一下,这个问题应该与前一个问题是对称的。所以我们推测,应该是需要使用&,而我们知道,任何一个数&0,都为0,按位与1,仍为原来的数,所以我们得到,这个数应该按位与一个只有第五位是0,其余全为1的二进制序列。那么这个该如何得到呢?其实我们不难想象,在上面中,我们是按位或一个第五位为1,其余全为0的数,而这个数不正好是我们现在需要的那个数的二进制刚好反了过来吗,由此我们得到,使用一个~操作符即可

    1. #include<stdio.h>
    2. int main()
    3. {
    4. int a = 9;
    5. //00000000 00000000 00000000 00001001
    6. //00000000 00000000 00000000 00010000(可由1<<4得到)
    7. //00000000 00000000 00000000 00011001(按位或得到了目标)
    8. a |= (1 << 4);
    9. printf("%d\n", a);
    10. //00000000 00000000 00000000 00011001
    11. //11111111 11111111 11111111 11101111(由~(1<<4)得到)
    12. //00000000 00000000 00000000 00001001(按位与可以得到目标)
    13. a &= (~(1 << 4));
    14. printf("%d\n", a);
    15. return 0;
    16. }

    运行结果为

     (6)++ --操作符

    我们看这样一段代码

    1. #include<stdio.h>
    2. int main()
    3. {
    4. int a = 0;
    5. int b = a++;//后置++,先使用后++
    6. printf("%d", a);
    7. printf("%d", b);
    8. return 0;
    9. }

    这个是一个后置++操作符,先使用后++,所以结果应该为11,10

     而前置++,也是同理的,只不过他是先++,后使用

    1. #include<stdio.h>
    2. int main()
    3. {
    4. int a = 0;
    5. int b = ++a;//后置++,先引用后使用
    6. //等价于 a = a + 1,b=a;
    7. printf("%d", a);
    8. printf("%d", b);
    9. return 0;
    10. }

    运行结果为两个11

    还有一种场景是这样的

    1. #include
    2. int main()
    3. {
    4. int a = 10;
    5. printf("%d\n", a++);
    6. printf("%d\n", a);
    7. return 0;
    8. }

    此时的第一个打印是先使用后++。所以结果应该为10 11

     如果换成前置++

    1. #include
    2. int main()
    3. {
    4. int a = 10;
    5. printf("%d\n", ++a);
    6. printf("%d\n", a);
    7. return 0;
    8. }

    则结果为 11  11

     而如果在函数中使用的话,也是会有前置和后置的区别的

    1. #include<stdio.h>
    2. void test(int b)
    3. {
    4. printf("%d\n", b);
    5. }
    6. int main()
    7. {
    8. int a = 10;
    9. test(a++);
    10. test(++a);
    11. return 0;
    12. }

     结果为

    以上就是++操作符的使用场景的总结,而--操作符和++操作符是完全类似的。我们可以看出++,--这些操作符是带有副作用的,使用之后会改变它自身的值

    (7)强制类型转换

    这个也是属于一种操作符,我们看一下使用场景

    1. #include
    2. int main()
    3. {
    4. int a = (int3.14;
    5. return 0;
    6. }

     在这段代码中,我们知道a是一个整型,3.14是一个double类型,我们非要将3.14放到a中,那么就会发生强制类型转换

    还有我们之前在三子棋小游戏,扫雷,猜数字小游戏中都使用到了一个srand和time函数

    1. #include
    2. int main()
    3. {
    4. srand((unsigned int)time(NULL));
    5. return 0;
    6. }

    这是因为,time 返回的是一个time_t类型的数据,而srand需要的是unsigned int类型的数据,所以需要强制类型转换

    4.sizeof与数组的一个经典题目

    1. #include <stdio.h>
    2. void test1(int arr[])
    3. {
    4. printf("%d\n", sizeof(arr));//(2)
    5. }
    6. void test2(char ch[])
    7. {
    8. printf("%d\n", sizeof(ch));//(4)
    9. }
    10. int main()
    11. {
    12. int arr[10] = { 0 };
    13. char ch[10] = { 0 };
    14. printf("%d\n", sizeof(arr));//(1)
    15. printf("%d\n", sizeof(ch));//(3)
    16. test1(arr);
    17. test2(ch);
    18. return 0;
    19. }

    我们在第四站:数组时候就讲过,数组名一般情况都代表首元素地址,只有两种情况不是,一种是&数组名,这是整个数组的地址,另外一种就是sizeof(数组名),这计算出来的是整个数组的大小。

    所以说我们(1)处打印的是4*10=40   (2)处打印的是10*1=10   

    由于函数传一个数组名传的其实是地址,虽然它形参写的看起来是一个数组,但实际上是一个指针,所以(3)(4)计算的结果都是4/8,32位平台位4,64位平台为8

    运行结果为


     总结:

    好了本节内容就先暂时讲解到这块,本篇篇幅已经较长了,已经讲解了1.3w字了。所以剩余内容放在后面的文章中继续讲解。本节主要讲解了算术操作符,计算机中二进制的表示,移位操作符,位操作符,赋值操作符,以及单目操作符,希望对大家有所帮助!

    本站未完,欲知后事,请看下节

  • 相关阅读:
    振动在线监测系统
    【Verilog】Verilog设计进阶
    编程技巧,Python缩进规则(包含快捷键)
    11. 盛最多水的容器
    SPA项目开发之首页导航+左侧菜单
    数据库、表备份命令
    pip install 下载速度太慢
    C++的一些基础语法
    MacBook Pro开发环境配置指南
    11、将数组中的 0 移动到末尾
  • 原文地址:https://blog.csdn.net/jhdhdhehej/article/details/127951261