C语言深处

    返回首页    发表留言
本文作者:李德强
          第二节 整型变量
 
 

一、字节

        C语言的整形变量有如下几个类型:

  • char        字符变量         1byte
  • short       短整型变量      2byte
  • int           整型变量         4byte(在16位架构下的int型变量占2个byte)
  • long        长整型变量      4byte
  • long long 超长整型变量   8byte

        先来看char类型的变量,char在内存中占用1个byte,也就是说它拥有8个bit位,最多可以表示256个数字,但在C语言中有符号数的最高位为符号位,0代表正数,1代表负数,所以可以使用的bit位数实际上只有7位,加上正负符号char类型变量的数字表示范围为-128 ~ 127,这也就是为什么ASCII码只有128个(-128 ~ -1保留,为其它编码使用,例如汉字等)。

        注意,本教程中明确写出来的内存地址,例如0x100、0x200、0x2000等等,这些地址是为了便于读者理解而假设的地址,程序实际运行时i的地址并不会是0x100、0x200、0x2000。本教程中其它例子也是一样。

        short类型变量在内存中占用2个byte,也就是16个bit位,但是在不同操作系统编译器下所编译出来的程序这两个byte的顺序是不一样的,如下图:


 

        假设在内存地址0x200处定义了一个short型变量,地址0x200处byte的值为0x11,地址0x201处byte的值为0x22,如果在大尾机中这个short变量的值为0x1122,而在小尾机中这个short变量的值为0x2211。大尾机是将数据的高字节存放在低地址,而小尾机则是将数据的高字节放在高地址。同样的道理,int类型、long类型和long long类型也都与short是一样的。例如我们定义一个int型变量并为其初始化:

int i = 0x11223344;

        假设变量i的内存地址为0x200则在小尾机中的数据存储方式为:

        在大尾机中的数据存储方式为:


 

二、赋值

        下面我们来看讨论两个变量赋值的问题:

  1. 如果将一个小范围的变量赋值给一个大范围变量会产生什么样的结果?
  2. 如果将一个大范围的变量赋值给一个小范围变量会产生什么样的结果?

        编写一段代码,编译并运行它,看看结果是什么:

#include <stdio.h>

int main(int argc, char **args)
{
        char i = 0x11;
        short j = 0x2233;
        j = i;
        printf("0x%x\n", j);

        return 0;
}

        运行结果为:

0x11

        也就是说一个小范围变量赋值给一个大范围变量时,将小范围变量的有效内存数据复制到大范围变量的低字节区域,并将高字节区域清零,具体的过程如下:

 

        再来看第二个问题,大范围的变量赋值给一个小范围变量:

#include <stdio.h>

int main(int argc, char **args)
{
        char i = 0x11;
        short j = 0x2233;
        i = j;
        printf("0x%x\n", i);

        return 0;
}

       运行结果为:

0x33

       把一个short类型的变量赋值给一个char类型的变量结果为什么是0x33呢?这是因为当一个赋值操作的目标变量范围小于源变量范围时,编译器只会把低字节的数据复制到小范围变量中,而将无法容纳的字节(高字节)自动抛弃。

        可以看到short j的低字节为0x33,高字节为0x22,赋值时只将低字节的0x33复制到变量char i中,而将高字节的0x22抛弃。

        下面再来看一个非常有趣的赋值问题:

#include <stdio.h>

int main(int argc, char **args)
{
        char i = 34;
        short j = 129;
        i = j;
        printf("%d\n", i);

        return 0;
}

        运行结果为:

-127

        结果出乎我们的意料,但却是正确的运行结果。来看一下这个赋值运算的原理:

       可以看到i被j赋值之后值变为了129,它的2进制表示为1000 0001,因为i是一个有符号数,所以它的最高位为符号位,所以1000 0001的值正好为-127。

 

三、补码

        下面就来了解一下负数的表示方式,在计算机中数通常采用补码的形式来表示,正数和0的补码为其本身,负数的补码为其绝对值取反再加1。

10进制数 绝对值 原码(不包括符号位) 反码(不包括符号位) 补码(不包括符号位) 实际补码(包括符号位)
-3 3 000 0011 111 1100 111 1101 1111 1101
-23 23 001 0111 110 1000 110 1001 1110 1001
-127 127 111 1111 000 0000 000 0001 1000 0001

        上面例子中char i的值为-127,其补码为1000 0001正好是short j的129的原码。所以char i = 129;的结果是-127。

        负数在计算机中通常以补码的形式表示,那为什么要采用补码来表示负数呢?我们来看一下9的补码为0000 1001。在计算机中为了能让CPU对所有的数,无论是正数还是负数都能够快速的进行正确的加减运算,例如要让CPU能够快速的识别并运行9 + (-9) = 0这样的运算法则。也就是说让0000 1001加上一个数(-9)能让结果快速的变为0,这个过程分为两步:

        1.让9加上其本身的反码,即得到一个全1的结果:

                0000 1001
             + 1111 0110 
                1111 1111

        2.在全1的结果基础上再加1,即出现多米诺骨牌效应,所有位均为0,第9个bit位的1溢出,所以结果为0:

                1111 1111
             + 0000 0001 
                0000 0000

        把上面两步的运算合并成一步运算,即一个正数加上其反码再加1结果为0,为了能让CPU能够快速的计算正负数的加减法,所以负数通常采用“补码”的形式来表示。

        再来看看两个负的加法:

        (-9) + (-7) = -16

        -9的补码为1111 0111,-7的补码为1111 1001,两个负数的加法结果为:

                1111 0111
             + 1111 1001​ 
                1111 0000

        1111 0000取其补码(符号位不变)的结果为1001 0000 = -16

 

四、位移运算

        无符号整数的位移运算非常简单,每向左位移1位相当于将其乘2,每向右位移1位相当于将其除2。例如,将无符号整数7向左位移3次再向右位移4次:

#include <stdio.h>

int main(int argc, char **args)
{
        unsigned int i = 7;
        i <<= 1;
        printf("%u\n", i);
        i <<= 1;
        printf("%u\n", i);
        i <<= 1;
        printf("%u\n", i);

        i >>= 1;
        printf("%u\n", i);
        i >>= 1;
        printf("%u\n", i);
        i >>= 1;
        printf("%u\n", i);
        i >>= 1;
        printf("%u\n", i);

        return 0;
}

        运行结果为:

14
28
56
28
14
7
3

         需要注意的是,当向右位移时如果这个数是奇数,它的最低位将会被抛弃,也就是说7向右位移1位时0000 0111变为0000 0011。上面所说的移位运算只限于无符号整数,对于有符号型整数的运算法则不适用。因为当位移运算时可能会影响到其符号位,使其结果并不一定是乘2或除2。

        再来看一个关于有符号数位运算的例子:

#include <stdio.h>

int main(int argc, char **args)
{
        char i = 11;
        i <<= 4;
        printf("%d\n", i);

        i = -7;
        i <<= 5;
        printf("%d\n", i);

        i = -7;
        i >>= 1;
        printf("%d\n", i);

        return 0;
}  

        运行结果为:

-80
32
-4  

        结果很奇怪,但这是正确的运算结果,来分析一下这3个位移运算:

  • 11的补码为0000 1011,向左移动4位结果为1011 0000(左移时总是将最低位补0),它的最高位为1所以它表示的是一个负数,将这个数取反再加1,得到它的数值为-80。
  • -7的补码为1111 1001,向左移动5位结果为0010 0000。它的最高位为0所以它表示的是一个正数,值为32。
  • -7的补码为1111 1001,向右移动1位1111 1100(右移时如果最高位为1,右移结束后再将最高位用1补齐),它的最高位为1所以它表示的是一个负数,将这个数取反再加1,得到它的数值为-4。

 

    返回首页    返回顶部
#1楼  匿名  于 2018年08月29日20:50:08 发表
 
不错!
#2楼  Ye  于 2018年12月24日16:51:29 发表
 
Nice!
#3楼  匿名  于 2019年10月22日17:33:42 发表
 
这个小尾机和大尾机是什么啊,没搜到,求教
#4楼  匿名  于 2023年05月04日11:37:29 发表
 
小尾机和大尾机就当是大端和小端。
#5楼  匿名  于 2023年09月10日09:30:52 发表
 
错字有点多,例如“11的补码为0000 1011”,这里不是“补码”吧?这种关键字出错,太影响阅读了
#6楼  匿名  于 2023年09月10日09:35:53 发表
 
啊,惭愧,上面说错了,正数的补码是其本身,被绕晕了
  看不清?点击刷新

 

  Copyright © 2015-2023 问渠网 辽ICP备15013245号