• 【C语言】符号的深度理解


    篮球哥温馨提示:编程的同时不要忘记锻炼哦!

    You wangt somenthing, go get it!


    目录

    1、按位运算符

    1.1 按位或 ( | ) 和 按位与 ( & )

    1.2 按位异或 ( ^ )  

    1.3 一个关于整型提升的问题 

    2、移位操作符 

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

    2.2 习题练习 

    3、++ 和 -- 的操作

    3.1 基本操作 

    3.2 从汇编角度深入理解 a++


    1、按位运算符

    1.1 按位或 ( | ) 和 按位与 ( & )

    上期我们讲到过逻辑或和逻辑与,他们得到的结果是真假值,但我们一定要区分清楚,按位运算符 "|" 和 "&" 与逻辑运算符 "||" "&&" 是完全两个概念。

    按位,简明之意,按数值二进制位来进行运算,都是在数据补码的基础上进行。

    按位或 "|" : 两个数值的二进制补码对应位进行运算,对应位有 1 则为 1 ,否则为 0

    按位与 "&":两个数值的二进制补码对应位进行运算,对应位都为 1 则为 1, 否则为 0

    这里我们举例说明:

    1 | 2 :

    1 的二进制补码:0000 0000 ... 0000 0001

    2 的二进制补码:0000 0000 ... 0000 0010 

    ------按位或结果:  0000 0000 ... 0000 0011   -> 对应十进制:3      

    1 & 2:

    1 的二进制补码:0000 0000 ... 0000 0001

    2 的二进制补码:0000 0000 ... 0000 0010 

    ------按位与结果:  0000 0000 ... 0000 0000   -> 对应十进制:0      

    其实有很多大学老师或者是书上都有可能把按位或,按位与,以及后面我们要讲的按位异或,他们会把每位二进制运算后的结果称为或者,其实这样的说法是不够严谨的真假是逻辑判断,而按位运算得到的结果是数值,而且在C语言中0表示假,非0为真,所以我是不推荐这种说法。

    1.2 按位异或 ( ^ )  

    按位或 "^" : 两个数值的二进制补码对应位进行运算,相同为 0 , 不同为 1。 

    这里我们举例说明:

    1 ^ 3 :

    1 的二进制补码:0000 0000 ... 0000 0001

    3 的二进制补码:0000 0000 ... 0000 0011 

    ---按位异或结果:  0000 0000 ... 0000 0010   -> 对应十进制:2     

    5 ^ 0 :

    5 的二进制补码:0000 0000 ... 0000 0101

    0 的二进制补码:0000 0000 ... 0000 0000 

    ---按位异或结果:  0000 0000 ... 0000 0101   -> 对应十进制:5     

    结论:任何数异或0都等于它本身

    这里有一道笔试题:不创建临时变量,实现两个数的交换。

    1. //很多小伙伴直接想出来的做法:
    2. int main()
    3. {
    4. int a = 10;
    5. int b = 20;
    6. printf("a = %d, b = %d\n", a, b);
    7. a = a + b;
    8. b = a - b;
    9. a = a - b;
    10. printf("a = %d, b = %d\n", a, b);
    11. return 0;
    12. }

    但是我们仔细研究下这段代码,他有没有什么隐藏的问题呢?

    一个整型,占四个字节,也就是 32 个比特位,这里进行加法运算,就会产生进位,万一我们是两个很大的数相加呢?他们的和超过了整型最大存储范围,那么在计算机里面就会发生截断!为了避免发生这种现象,我们可以采取异或的方法来实现这道题:

    最后还有一个很简单的按位取反操作符:~

    用途:对一个数的二进制按位取反(包括它的符号位)

    注意:以上的位运算符, 他们的操作数必须是整数!

    1.3 一个关于整型提升的问题 

    有这样一串代码,问:为什么一个char类型大小可以求出来是4字节?

    无论任何位运算符,都是要计算机进行计算的,而计算机中CPU具有运算能力,但计算的数据都是放在内存中的。所以,做任何运算,都必须将数据从内存拿到CPU的寄存器中。而寄存器默认的操作数宽度是32位,可是,char类型数据只有1个字节,也就是8位,不满足32位怎么办,这就需要整型提升了!(详细整型提升大家可以查阅资料哦)

    如果是一个有符号数的话:高位补符号位

    如果是一个无符号数的话:高位补0


    2、移位操作符 

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

    << 左移运算符是一个双目运算符,功能是把左边的运算数的各个二进制位向左移动指定位数。

    >> 右移运算符是一个双目运算符,功能是把右边的运算数的各个二进制位向右移动指定位数。 

    注意:

    << 左移:最低位丢弃,最高位补零

    >> 右移:

    1. 无符号数:最低位丢弃,最高位补零 [逻辑右移]
    2. 有符号数:最低位丢弃,最高位补符号位 [算数右移] 

    以上在补码中进行运算

    警告:移位运算符,请不要移动负数位,这是标准未定义的!

    左移我们好说,主要是右移我们需要细讲一下:

    明显看到,这是在无符号数下进行右移,第一个小伙伴都不会感到惊讶, 可是第二个就有点不理解了,我们来解释下:

    这里有一个问题,当 -1 准备放入变量 b 的时候我们需要看-1的类型吗?

    答案是不需要!内存中放的都是二进制补码,本质上是把 -1 的补码放入变量 b 当中,第二,右移操作符属于计算,需要在CPU中进行,所以需要先把内存中 -1 的补码拿到CPU寄存器中运算,按照我们的规则,右移中,无符号数低位丢弃高位补零,所以 -1 右移完成之后就变成了 0111 1111 ... 1111 1111,接着我们以 %d 有符号整型打印,就会把他当作有符号数看待,最高位是 0 所以被认为是正数,转化成十进制也就是如上打印的值。

    第二个我们来看下有符号数右移:

    这个相信大家就很好理解了,第一个高位补符号位也就是补 0,低位丢弃,所以结果是 0,第二个高位补符号位也就是补 1,低位丢弃,值仍然不变,还是 -1。 

    注意:a>>1 并不会改变 a 变量的值,就好比如 a + 1。这样写才会改变:a = a >> 1; 

    2.2 习题练习 

    学完了上期的逻辑操作符,和本期的移位操作符,我们来练练手:

    请你设计一个宏可以指定数据第几个比特位更改为 1 ,并设计一个函数将各个比特位打印出来。 

    1. //参考
    2. #define SETBIT(a, num) ((a) |= (1 << (num - 1)) )
    3. void PrintBit(int a)
    4. {
    5. int num = 31;
    6. while (num >= 0)
    7. {
    8. if ((a & (1 << num)))
    9. printf("1");
    10. else
    11. printf("0");
    12. --num;
    13. }
    14. printf("\n");
    15. }
    16. int main()
    17. {
    18. int a = 0;
    19. SETBIT(a, 5);
    20. PrintBit(a);
    21. return 0;
    22. }

    3、++ 和 -- 的操作

    3.1 基本操作 

    其实这节知识点理解起来是很简单的,只不过总有些学校喜欢出一些很拉跨的题目:

    int i = 3; 问:(++i) + (++i) + (++i) 的值是多少? 

    我的建议是,看到这类题,直接空着,你也可以在下面添一句,“ 你礼貌吗?”

    这种表达式,在任何编译器下算出来的结果是不一样的!

    对于这种问题没必要去争论谁对谁错, 如果有人想跟你杠的话,那么你直接告诉他,你真的超级高水平。

    好了,言归正传,我们来说一下 ++-- 的基本理解:

    1. 前置++ -- : 先自增(减),再使用
    2. 后置++ -- : 先使用,再自增(减) 如果没有变量接收,那么直接自增。

    例子:

    基本使用就是这么多,接下来我们从汇编角度来深度理解一下:

    3.2 从汇编角度深入理解 a++

    既然我们知道,后置++ 是先使用后++,如果我们单纯的就 ++ 一下呢,他这个值被使用到了哪里去了呢?

    1. int main()
    2. {
    3. int a = 0xDD;
    4. int b = a++; //有b接收,那么a的先使用是将a的值(内容),放到b中
    5. int c = 0xEE;
    6. c++; //没有接收方,那么"先使用",如何理解?
    7. return 0;
    8. }

     vs2019编译器反汇编:

    结论:后置++ 完整的含义是先使用,在自增,如果没有变量接收,那么直接自增。

    注意:在不同的编译器可能处理过程不同,不过这是一个基本的研究过程,比单纯的理论学习更严谨。 

    Calm, mature and precipitated

  • 相关阅读:
    Js中常见的数据结构(Data Structure)
    一些Java相关的知识点
    9.29 校招 实习 内推 面经
    区块链和IPFS安全测试工具;ECC+AES混合加密机制:秘钥管理
    Shell开发实践:服务器的磁盘、CPU、内存的占用监控
    微信小程序开发14 复杂数据库设计:如何用好云开发提供的文档型数据库?
    基于Redis分布式BitMap的应用
    Android 系统的分区
    [附源码]计算机毕业设计springboot贷款申请审核管理系统论文
    【docker系列】使用docker-compose安装私有镜像仓库Harbor
  • 原文地址:https://blog.csdn.net/m0_61784621/article/details/125469242