C语言基础09——数据在内存中的存储。整型的存储、大小端讲解、浮点数的存储、杨辉三角、找凶手、猜名次

本文详细探讨了计算机内存中的数据存储方式,包括整型和浮点型的表示,原码、反码、补码的概念,以及大小端字节序。通过实例分析了不同类型数据在内存中的存储规则,解释了不同格式的输出可能会导致的不同打印结果。此外,还介绍了浮点数的科学记数法表示以及IEEE754标准。
摘要由CSDN通过智能技术生成

目录

数据类型

基本内置类型

类型的基本分类

整型在内存中的存储

计算机中整数的三种表示方法:原码、反码、补码

大小端

练习

浮点型在内存中的存储

为什么以下程序输出结果与想象不同?

浮点数存储规则

练习


printf与scanf输入输出格式总结:

%c 用于打印字符

%s 字符串

%d 用于有符号十进制整数

%u 用于无符号十进制整数

%f float浮点型

%lf double浮点型

%x 无符号十六进制整数1122aabb(小写)

%X 无符号十六进制整数1122AABB(大写)

%p 指针地址

%ld 用于long long

%o 无符号八进制

%e 科学计数法输出(小写),一般用于浮点数的打印

%E 科学计数法输出(大写)

输出精度总结:

  • 在%和f之间加上.n可以指定输出小数点后几位,这样的输出是做四舍五入的。

    printf("%.3f\n",-0.0049);//-0.005
    //在计算机内部,无法明确的表明-0.0049。它输出的数是一个离-0.0049很近的一个数,然后用这个数来表达。
    printf("%.15f\n",-0.0049);//-0.00489999999999999984
    printf("%.3f\n",-0.000493);//-0.00
    
  • 八进制和十六进制

    /*
     * 一个字面量,以0开头就是八进制;以0x开头就是十六进制。
     * - 4位二进制可以表示1位十六进制。所以2位十六进制,就可以表示8个二进制位。
     *   也就是2位十六进制,可以表示一个字节(1字节=8bit位=8个二进制位)的数据。
     * - 3位二进制可以表示1位八进制。
     *   因为早起的计算机字长是12的倍数,所以那个时候用八进制来表示会比较合适。
     */
    int main()
    {
        printf("%d\n",0123);//83
        printf("%d\n",0x123);//291
    
        printf("%x",255);//ff
        return 0;
    }
    

数据类型

基本内置类型

变量的创建是要在内存中开辟空间的。空间的大小根据不同的类型而定。

char			//字符数据类型        大小:1个字节
short			//短整型			       2个字节
int				//整型			        4个字节
long			//长整型			       4个字节
long long 		//更长的整型			      8个字节
float			//单精度浮点数		     4个字节
double			//双精度浮点数		     8个字节

类型的基本分类

  • 整型

    char
    	unsigned char
    	signed char
    	
    short 
    	unsigned short [int]
    	signed short [int]
    	
    int
    	unsigned int
    	signed int
    	
    long
    	unsigned long [int]
    	signed long [int]
    

    这么多整型,我们在使用的时候应该如何选择呢?

    为什么有这么多整数呢?

    ​ 为了准确表达内存,做底层程序的需要

    如何选择呢?——没有特殊需要,就选择int类型

    • 现在CPU字长普遍是32/64位,一次内存读写就是一个int,一次计算也是一个int,选择短的类型,不会更快,甚至可能更慢。
    • 现在的编译器一般会设计内存对齐,所以更短的类型实际在内存中也有可能占据一个int类型大小(不是类型本身占据,而是浪费掉的)。虽然sizeof告诉你它很小。但是因为内存对齐的原因,更短的类型与下一个类型之间可能存在浪费的空间。

    unsigned无符号类型,只是输出的将其当作无符号来输出,其在内存中存储并不受影响。内存本身是怎么计算怎么存储的,不会受unsigned的影响。

  • 浮点数

    float
    double
    
  • 构造类型

    - 数组
    - struct 结构体类型
    - enum 枚举类型
    - union 联合类型
    
  • 指针类型

    char * pc;
    int * pi;
    float * pf;c
    void * pv;
    ......
    
  • 空类型

    void表示空类型(无类型),通常用于函数返回类型、函数参数、指针类型

    - 函数返回类型
      void test();
    - 函数参数
      void test(void);
    - 指针
      void * p;
    

整型在内存中的存储

char、short、int 等取值范围在limits.h头文件中定义

常见浮点数:
- 3.141592
- 1E10 ,表示:1.0*10的10次方

为什么最高位是符号位呢? 

/*
 * 如果是十进制计算,那么我们有一个"-"来表示负数。那么在计算机中是如何进行表示的呢?
 * 一个字节可以表达的数:00000000~11111111 ,可以表示0~255,一共256个数字。
 * 三种方案:
 * 1.仿照十进制:有一个特殊的标志表示负数。
 *   这种方式,需要额外的一位来标志正负数。
 * 2.取中间数为0,如10000000表示0(也就是128所在位置表示0),比它小的是负数,比它大的是正数。
 *   这种方式,我们在得到一个数之后,都要对128个数做减法,才能得到这个数的正确结果。
 * 3.补码。
 *   - 考虑-1,我们希望-1+1=0,如何做到?
 *     0->00000000   1->00000001怎么样表示-1,然后让其与1相加就可以得到0呢?
 *   - 我们发现11111111+000000001=1 00000000 ,进位之后,发生截断,只取后八位就得到了0。
 *     或者使用0-1,0向前一位借1,然后做减法:1 00000000 - 000000001 = 11111111
 *   - 11111111被当作二进制解析时,是:255
 *     而如果11111111是一个补码,那么就是-1了。
 *   - 我们发现,使用补码的话,不需要再借位,而是数据本身就可以表达其正负。
 */

/*
 * 有符号数
 * - 二进制的00000000~11111111,转换为十进制就是:0~255,一共有256个数字(包括0)。
 *   如果这256个数字,一半是正数,一半是负数,那么就是:0~127 与-1~-128。
 * - 如果是8位二进制,其范围就是:-2的(8-1)次方~2的(8-1)次方-1, 因为负数与正数之间还个0。
 *   如果是n位二进制,则其范围就是:-2的(n-1)次方~2的(n-1)次方-1
 * - 整数越界。如00000000~11111111这组范围内。其被分为整数和负数。
 *   01111111表示127,127+1本来应该是表示128,但是其超过了正数范围,也就是说10000000表示-128。
 *   也就是从00000000~11111111表示的是:0 1 2 3 ... 126 127 -128 -127 ... -3 -2 -1
 *   正数一直加,超出了正数范围就成为该范围内最小的负数。负数也一直加,超出了其负数范围,就成为0..1..2
 *
 * - 如果是-128,其原码应该是:1 10000000,但实际上使用10000000表示了-128,为什么呢?
 *	 把1000 0000看作原码,应该是表示-0,但已经有0000 0000表示+0了。0的正负不影响其值,所以-0的表示就多余了。
 *   同时:-127原码:1111 1111 ,反码:1000 0000 ,反码+1得到补码:1000 0001
 *         -1 原码:1000 0001 ,反码:1111 1110 ,反码+1得到补码:1111 1111  
 *    -127+(-1) = -128。所以他们的补码进行相加就得到-128  :     1 1000 0000  
 * - 因为char类型只能存8位,所以发生截断,只存储了1000 0000 ,所以就用1000 0000表示-128。
 *   也就是说-128在内存中存储位1000 0000,可以说1000 0000是-128的补码。
 * 
 * 无符号数
 * - 以上这种的是有符号类型的范围。
 *   如果是无符号类型,也就是说:00000000~11111111,转换为十进制就是:0~255。把它当作没有负数来看待。
 *   在定义的时候,就需要在其类型前加上unsigned。这样定义出来的数就被当作没有符号的数来处理
 * - 范围就是:0~2的n次方-1 ,无符号类型的初衷,并不是为了拓展数字的表达范围,因为能表示的范围并没有改变,
 *   只不过是一个只表示了正数,一个不仅表示正数,也表示负数。都表示256个不同的数。而是为了做纯二进制运算,主要是为了位移。
 */

原码、反码、补码详解:C语言——原码, 反码, 补码 详解_蛋翼的博客-CSDN博客目录一. 机器数和真值1、机器数2、真值二. 原码, 反码, 补码的基础概念和计算方法.1. 原码2. 反码3. 补码三. 为何要使用原码, 反码和补码四 原码, 反码, 补码 再深入同余的概念负数取模开始证明一. 机器数和真值在学习原码, 反码和补码之前, 需要先了解机器数和真值的概念.1、机器数一个数在计算机中的二进制表示形式, 叫做这个数的机器数。机器数是带符号的,在计算机用一个数的最高位存放符号, 正数为0, 负数为1.比如,.https://blog.csdn.net/m0_69066786/article/details/124541454

计算机中整数的三种表示方法:原码、反码、补码

/*
 * 数据在内存中以二进制的形式存储。
 * 对于整数来说,其二进制有三种表示方式:
 * - 三种表示方法均有符号位和数值位两部分,符号位都是用0表示”正“,用1表示”负“。
 * - 正整数:原码、反码、补码相同
 * - 负整数:原码、反码、补码不同,需要通过计算得出。
 *   按照数据数值直接写出的二进制序列就是原码,原码最高位符号位不变,其余二进制位按位取反,得到反码;反码+1,得到补码。
 */

int main()
{
    int a = -10;
    printf("%0x",a);//将a用16进制打印出来是:fffffff6
    //原码:10000000 00000000 00000000 00001010  最高位1表示符号位
    //反码:11111111 11111111 11111111 11110101  原码的最高位符号位不变,其余二进制位按位取反,得到反码
    //补码:11111111 11111111 11111111 11110110  反码+1就是补码
    //十六进制: ff      ff         ff     f6
    //结论:整数在内存中存储的是补码

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

    //例如当运算1-1时,因为没有减法器,所以其运算应该是:1+(-1)

    //如果是采用原码进行运算:
    //1的原码: 00000000 00000000 00000000 00000001
    //-1的原码:10000000 00000000 00000000 00000001
    //相加:    10000000 00000000 00000000 00000010
    //最高位符号位表示负数,数值为2,也就是得到-2,但是1-1=0,所以计算机肯定不是采用原码进行计算的

    //采用补码进行运算
    //1是正数,正数的原码、反码、补码相同
    //1的补码: 00000000 00000000 00000000 00000001

    //-1的原码:10000000 00000000 00000000 00000001
    //-1的反码:11111111 11111111 11111111 11111110
    //-1的补码:11111111 11111111 11111111 11111111

    //1的补码:   00000000 00000000 00000000 00000001
    //-1的补码:  11111111 11111111 11111111 11111111
    //相加:    1 00000000 00000000 00000000 00000000
    //进1后有33位,但是只能存储32位,会发生截断,只存储后32位
    //也就是      00000000 00000000 00000000 00000000,得到0。
    return 0;
}

大小端

大小端字节序指的是数据在电脑上存储的字节顺序。注意:是字节顺序,而不是二进制位顺序。

  • 什么是大端,什么是小端

    int main()
    {
        //我们发现在内存中数据都是倒着存的,这是什么原因?
        int a = -10;    //f6 ff ff ff
        int b = 10;     //0a 00 00 00
    
        //本来应该是十六进制的11223344,结果在内存中反了过来,并且我们可以发现,他是按一个字节大小反着存的。
        //这个就叫做小端存储模式
        int c = 0x11223344;// 44 33 22 11
    
        /*
         * 什么是大端小端?
         * - 大端存储模式:是指数据的低位,保存在内存的高地址中;数据的高位,保存在内存的低地址中。
         *   把数据的低位字节序的内容,放到高地址处;高位字节序的内容存放在低地址处。
         * - 小端存储模式:是指数据的低位,保存在内存的低地址中;数据的高位,保存在内存的高地址中。
         *   把数据的低位字节序的内容,放到低地址处;高位字节序的内容存放在高地址处。
         *
         *   例如    int c = 0x11223344;
         *   大端存储:11 22 33 44
         *   小端存储:44 33 22 11
         */
        return 0;
    }
    
  • 为什么会有大端小端?

    /*
    - 因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中,
      除了8bit的char之外,还有16bit(2字节)的short型数据,还有32bit(4字节)的int型数据。另外,对于位数大于8位的处理器,例如16位或32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节排序的问题。
      
    - 因此就导致了大端存储模式和小端存储模式。
      例如:一个16bit的short类型变量x,在内存中的地址为0x0010,x的值为0x1122,那么0x11为高字节,0x22为低字节。  对于大端模式,就将0x11放在低地址中,即放入0x0010中;0x22放在高地址中,即0x0011中。而小端模式,则恰好相反。
      
    - 我们常用的x86结构式小端模式,而KEIL C51则为大端模式。很多的ARM、DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端。
     */
    
  • 设计一个程序来判断当前机器的字节序

    /*
     * 如:1(00 00 00 01)在内存中的存储就是:
     *        低地址——————————————————————————————————>高地址
     * 小端:高字节高地址          01 00 00 00
     * 大端:高字节低地址          00 00 00 01
     */
    #include <stdio.h>
    
    int check_sys()
    {
        int a = 1;
        //使用char类型指针变量,这样就可以只取a变量第一个字节。
        char * p = (char*)&a;
        //取出来然后返回,如果是1,则*p=1,就返回1。如果取出来0,则*p=0,返回0.
        return *p; //返回1表示小端;返回0表示大端。
    }
    
    int main()
    {
        int ret = check_sys();
        if(ret == 1)
        {
            printf("小端");
        }
        else
        {
            printf("大端");
        }
        return 0;
    }
    

练习

- 因为内存中存储的补码,所以计算的时候也是计算补码。
  整型提升之后得到的是其补码。
- 以%d形式打印,则打印的是原码。
- 以%u形式打印,是打印无符号数,如果是要把一个有符号数以%u的形式打印,则是要将这个数的最高位当成数值位,然后打印其原码。
  因为%u会将其符号位当作数值位,没有负数的概念,也就是说每一个以%u形式打印的数都会被当作一个正数,而正数的原码反码补码相同,所以可以理解为是打印其补码。内存中存储的是什么就打印出什么
  • 以下程序打印输出什么

    #include <stdio.h>
    
    /*
     * 补充:
     *  - C语言中并没有规定char是signed char 还是unsignend char。取决于编译器,大部分编译器中,char是signed char
     *  - int类型就是signed int类型
     *  - short类型就是sigend short类型
     */
    int main()
    {
        //-1是整数,占4个字节。-1的补码:11111111 11111111 11111111 11111111
        //-1占4个字节,char类型占1个字节,截取后八位:11111111存入a、b、c中
    
        char a = -1;
        //a中存储的是11111111
        //a是负数,整型提升高位补1。a整型提升的结果:11111111 11111111 11111111 11111111
    
        signed char b = -1;  //signed表示有符号
        //b中存储的是11111111
        //b是负数,整型提升高位补1。b整型提升的结果:11111111 11111111 11111111 11111111
    
    
        unsigned char c = -1;//unsigned是无符号的意思,也就是说c中存储的11111111都是数值位,没有符号位。
        //c中存储的是11111111
        //c是unsigned,整型提升高位补0。c的整型提升结果:00000000 00000000 00000000 11111111
        //c整型提升之后,高位是0,就算是个正数,正数的原码、反码、补码相同。
    
        //%d形式打印,是打印有符号数,是打印原码,这里a、b、c都发生整型提升。
        //a和b整型提升之后都是-1的补码,补码-1得到反码:11111111 11111111 11111111 11111110
        //反码最高位符号位不变,其余位按位取反,得到原码:10000000 00000000 00000000 00000001
        // 所以a、b打印出来是-1
        //c的原码是:00000000 00000000 00000000 11111111,c打印出来就是255
        printf("a=%d,b=%d,c=%d",a,b,c);  //a=-1,b=-1,c=255
        return 0;
    }
    
  • 以下程序打印输出什么

    #include <stdio.h>
    
    int main()
    {
        //-128的原码:10000000 00000000 00000000 10000000
        //原码最高位不变,其余按位取反,得到反码:11111111 11111111 11111111 01111111
        //反码+1就是补码:11111111 11111111 11111111 10000000
        //取后八位存储到a中,就是:10000000。
        char a = -128;
    
        //以%u打印,发生整型提升。a是有符号char,高位补1,整型提升之后的结果:11111111 11111111 11111111 10000000
        //整型提升之后是补码,但是其以%u打印,也就是说将其看作一个没有符号的数据,所有位数都是数值位。
        // 而正数的原码、反码、补码相同,所以将其直接打印出来,是一个很大的数。
        printf("%u",a);//4294967168
        //这里是以%u形式打印,最高位会被看成数值位,而不是符号位,所以是一个很大的数。
        
        return 0;
    }
    
  • 以下程序打印输出什么

    #include <stdio.h>
    
    int main()
    {
        //a的原码:00000000 00000000 00000000 10000000
        //正数的原码、反码、补码相同,取其后八位存储到a中:10000000
        char a = 128;
    
        //以%u形式打印,发生整型提升,a是有符号char,高位补1,整型提升之后的结果:11111111 11111111 11111111 10000000
        //整型提升之后是补码,但是其以%u打印,也就是说将其看作一个没有符号的数据,所有位数都是数值位。
        // 而正数的原码、反码、补码相同,所以将其打印出来,是一个很大的数。
        printf("%u",a);//4294967168
        return 0;
    }
    
  • char类型的取值范围

    /*
     * char类型占一个字节,也就是八位二进制位,其范围:00000000~11111111
     * 因为是有符号的char,正数:00000000 ~01111111 负数:10000000~11111111
     * - 正数范围也就是:0~127,
     * - 10000000~11111111 其最高位是符号位,整形提升之后得到补码,再得到反码原码,
     * - 10000000整型提升得到:11111111 11111111 11111111 10000000 ,这时补码
     *              补码-1得到反码:11111111 11111111 11111111 01111111
     *   反码最高位不变,其余按位取反:10000000 00000000 00000000 10000000
     *   这就是原码,最高位1表示是负数,数值位是128,就是-128。
     * - 11111111整型提升得到:11111111 11111111 11111111 11111111
     *                 补码-1得到: 11111111 11111111 11111111 11111110
     *   反码最高位不变,其余按位取反:10000000 00000000 00000000 00000001
     *   这是原码,最高位1表示是负数,数值位是1,就是-1。
     *
     *  - 所以char的范围就是:-128~127
     */
    
  • 以下程序打印输出什么

    int main()
    {
        //10的原码:10000000 00000000 00000000 00010100
        //10的反码:11111111 11111111 11111111 11101011
        //10的补码:11111111 11111111 11111111 11101100
        int i = -20;
    
        //10的补码:        00000000 00000000 00000000 00001010
        unsigned int j = 10;
    
        //-20的补码:       11111111 11111111 11111111 11101100
        //10的补码:        00000000 00000000 00000000 00001010
        //两补码相加得到补码:11111111 11111111 11111111 11110110
        //+1得到反码:       11111111 11111111 11111111 11110101
        //高位不变,其余取反:10000000 00000000 00000000 00001010
        //最高位1表示负数,数值位是10,所以输出-10
        printf("%d\n",i+j);//-10
        //注意这里不要跟上面搞混,这里是以%d形式打印的,打印的是有符号数的,也就是说i+j的结果其最高位是符号位,然后跟上其数值位。如果这里是%u形式打印,将其最高位看作数值位。则也是一个很大的数。
        
        return 0;
    }
    
  • 以下程序打印输出什么

    /*
     * 该程序会死循环。原因:
     * - i是unsigned int类型,无符号类型,也就是说i最小就是0。会打印9 8 7 6 5 4 3 2 1 0 ...
     * - 0之后应该是-1,但是因为i是无符号类型,所以负数的补码中最高位的1是被当成了数值位的。
     *   所以其0之后的,本因该是负数的,符号位被当成数值位之后,永远是大于0的,所以会造成死循环。。
     *
     * - 如果是以%u形式打印,则-1是4294967295,从这个数每次减1打印。
     *   因为无符号类型,没有负数的概念。所以就会把i当成一个正数,正数的补码反码原码相同,所以直接打印其补码
     *
     * - 如果是以%d形式打印,则是打印变量i的原码。因为%d是打印有符号位的,所以会将i中存储的补码转换为原码之后打印出来
     *   所以-1就打印出-1,根据循环条件每次减1打印。
     *
     * - 以%d形式打印,在0之后,虽然形式上看起来是负数了。但是变量i本质上是无符号类型的,所以i永远大于0
     *   所以即使以%d形式打印,也是死循环
     */
    int main()
    {
        unsigned int i;
        for(i=9 ; i>=0 ; i--)
        {
            printf("%u\n",i);
            //printf("%d\n",i);
        }
        return 0;
    }
    
  • 以下程序输出什么

    #include <stdio.h>
    #include <string.h>
    
    /*
     * 因为char的范围是:-128~127。
     * 在该程序中,a数组中存储了1000个元素。
     * 依次是:-1 -2 -3 -4 .... -128 下一位应该是-129,
     * -129的原码: 10000000 00000000 00000000 10000001
     * 符不变,取反:11111111 11111111 11111111 01111110
     * 反码+1,补码:11111111 11111111 11111111 01111111
     * 截取后八位存储到char中,符号位是0表示正数,数值位是127。所以-129存储到char中实际存储的是127
     * 接着-128存储的应该是:127 126 125 124 ...... 1 0,0在char的
     *
     * char中存储1的时候应该是:00000001,是截取而来。因为是存储负数得到的,补全其原来的补码,应该是:11111111 11111111 11111111 00000001
     * -1得到反码:11111111 11111111 11111111 00000000,符号位不变,其余按位取反,得到10000000 00000000 00000000 11111111
     * 也就是说,-255存储到char中,得到的是1。
     * - 紧接着是-256,原码:10000000 00000000 00000001 00000000,反码:11111111 11111111 11111110 11111111
     *   反码+1,得到补码:11111111 11111111 11111111 00000000 ,截取后八位,所以存储的是0。
     * - 紧接着是-257,原码:10000000 00000000 00000001 00000001,反码:11111111 11111111 11111110 11111110
     *   反码+1,得到补码:11111111 11111111 11111110 11111111 ,截取后八位,所以存储的是-1
     *
     * 所以实际上存储的数就是在循环变化:-1 -2 ... -128 127 ... 1 0 -1 -2 ... -128 127 ... 1 0 -1...
     * 直到循环到最后一次,i=999,a[999]=-1000,存储到数组中就是24。
     *
     * - 最后输出的是strlen(a)的结果,a中有1000个元素,但是strlen()字符串函数遇到字符串结束符'\0'就统计结束
     *   而'\0'的结果就是0,所以也就是求a数组中第一个0之前的元素的个数。
     *   也就是-1 ... -128 127 ... 1 ,128个负数+127个正数,遇到0就体停止了,不计算0。就是128+127=255,所以最后输出255
     */
    int main()
    {
        char a[1000];
        int i;
        for(i=0; i<1000 ; i++)
        {
            a[i] = -1-i;
        }
        printf("%d",strlen(a));//255
        return 0;
    }
    
  • 程序打印输出什么

    #include <stdio.h>
    
    /*
     * 有符号char的范围是-128~127。
     * 无符号char,因为其符号位被当作了数值位,所以其范围是:0~255
     *
     * 以下程序指向,当i=255,循环体执行完之后,++执行,之i=256。
     * 256是正数,其原码补码相同,原码是:11111111 11111111 11111111 00000000
     * 截取后八位存储到char中:00000000,存储为0。
     *
     * 所以i从0~255,然后又从0~255,程序死循环。
     */
    unsigned  char i = 0;
    int main()
    {
    
        for(i=0 ; i<=255 ; i++)
        {
            printf("hello world\n");
        }
        return 0;
    }
    

浮点型在内存中的存储

float、double 浮点型取值范围在float.h头文件中定义。

浮点数:带小数点的数值。
float:单精度浮点数      double:双精度浮点数
在C语言中,两个整数的运算结果只能是整数。如10/3=3。10和10.0是两个不同的数,10是整数,而10.0是浮点数。
当浮点数与整数放到一起运算时,C会将整数转换成浮点数,然后进行浮点数的运算。

为什么以下程序输出结果与想象不同?

#include <stdio.h>

int main()
{
    //n变量式int类型,占4个字节,其中存储了9。(整型存储)
    int n = 9;

    //float类型也是占用四个字节。取n的地址,强制转换成float*指针类型,然后存储到pFloat指针变量中。
    float * pFloat = (float*)&n;
    //以%d的形式打印有符号整型,传入变量n。(整型存储,整型取出)
    printf("n的值为:%d\n",n);//9
    //以%f的形式,是打印浮点类型。*pFloat是float*类型指针。(整型存储,浮点型取出)
    printf("*pFloat的值为:%f\n",*pFloat);//0.000000

    //使用解引用的方式,将浮点数9.0存储到*pFloat指向的地址。(以浮点型存储)
    *pFloat = 9.0;
    //以%d的形式打印有符号整型,传入变量n。(浮点型存储,整型取出)
    printf("n的值为:%d\n",n); //1091567616
    //(浮点型存储,浮点型取出)
    printf("*pFloat的值为:%f\n",*pFloat);//9.000000

    //发现:整存整取、浮存浮取时,以其对应形式打印,都正确输出。但是当整存浮取、浮存整取时,都出现异常。
    //说明:整形与浮点型在内存中的存储大不相同。

    return 0;
}

浮点数存储规则

以上程序中,num和*pFloat在内存中明明是同一个数,为什么浮点数和整数的解读结果会差别这么大?要理解这个结果,就需要知道浮点数在计算机内部是怎么表示的。

  • 科学记数法

    科学记数法是一种记数方法。把一个数表示成a与10的n次幂相乘的形式。(1 ≤ |a| < 10,a不为分数形式,n为整数),这种记数方法叫做科学计数法。例如:
    - 19971400000000=1.99714*10^13 
      计算器或电脑表达10的幂一般是用E或e,也就是1.99714E13=19971400000000
    - 0.00001=1×10^-5,即绝对值小于1的数也可以用科学记数法表示为a乘10 的负n次方的形式。
    - 小数点向前移动几位,就乘10的几次方。
    
  • 浮点数的表示(参考十进制的科学记数法记忆)

    /*
     * 根据国际标准IEEE(电气电子工程师学会)754标准规定,任意一个二进制的浮点数V可以表示为以下形式:
     * - (-1)^S * M * 2^E     
     *   -1的S次方,表示符号位,当s=0时,V为正数;当s=1,V为负数。S又叫数符。
     *   M表示有效数字,大于等于1,小于2。M又叫尾数
     *   2的E次方,表示指数位c。E又叫阶码
     *
     * 例如:
     * - 浮点数5.5,转换为二进制就是:101.1  (5转换为二进制是101,0.5是二分之一,也就是2的-1次方)
     *   101.1的小数点向左移动两位,1.011*2^2。(参考十进制科学计数法,10进制是10的幂次方,二进制就是2的幂次方)
     *   1.011*2^2,也就是4个1.011相加,就可以得到101.1
     * - 用IEEE754标准表示就是:(-1)^0 * 1.011 * 2^2 。 S=0   M=1.011  E=2
     *
     * - 浮点数-5.5就可以表示为:-101.1,相当于-1.011*2^2
     *   用754标准表示就是:(-1)^1 * 1.011 * 2^2 。 S=1   M=1.011  E=2
     */
    

    在这里插入图片描述

  • IEEE754标准对有效数字M和指数E的特别规定

    /*
     * 前面说过 1 ≤ M < 2 ,也就是说M可以写为1.xxxxxx的形式,其中xxxxxx表示小数部分
     * - IEEE标准规定,在计算机内部保存M时,因为这个数肯定是1.x的形式,因此第一位可以被舍去,只存储后面的xxxxxx部分
     * - 例如保存1.01的时候,只保存01。读取时,自动加上第一位的1。这样的目的,是节省1位有效数字。
     *   32位浮点数的M只有23位,将第一位的1舍去之后,就可以保存1之后的23位有效数字,加上舍去的第一位,一共就可以存储24位有效数字了
     *
     * 指数E,E为一个无符号整数(unsigned int)
     * - 意味着,如果E为8位,取值范围就是:0~255;如果E为11位,则取值范围是:0~2047。
     * - 但是在科学记数法中,E是可以为负数的。所以IEEE754规定:存入内存时E的真实值必须再加上一个中间数。
     *   对于32位浮点数,E要加上中间数127;对于64位浮点数,E要加上中间数1023。
     *   比如2^10,E为10,所以保存为32位浮点数时,需要加上127。E就为137,存储到内存中为:1000 1001
     */
    
  • 浮点数5.5在内存中的存储

    int main()
    {
        /*
         * 浮点数5.5,转换为二进制是101.1,可以写为:1.011*2^2。
         * - 其中S=0 , M=1.011 , E=2+127
         * - 所以f以32位浮点数,存储到内存中的应该是:0 10000001 011 0000 0000 0000 0000 0000
         *   第一位为符号位s,为0表示是整数;接下来的8位是指数位,其中存储E
         *   在接下来的23位为有效数字位M,有效数字只有三位011,接着在后面补20个0即可。
         * - 实际存储(二进制)为:0100 0000 1011 0000 0000 0000 0000 0000
         *   转换为十六进制:      4   0    b    0     0    0    0   0
         *   每两个十六进制位为一个字节,float为四个字节:40 b0 00 00
         * - 调试,查看器内存中如何存储
         *   采用小端存储,即低位低址,在内存中显示为:00 00 b0 40
         */
        float f = 5.5f;
        return 0;
    }
    
  • 将浮点数从内存中取出时,还可以分为三种情况

    /*
     * 第一种情况:E不为全0或不为全1
     * - 如0.5的二进制为:0.1,由于规定正数部分必须为1,即小数点向右移一位,就表示为:1.0*2^-1
     *   E在内存中存储为:-1+127=126,二进制表示为:0111 1110。
     *   有效数字M=1.0,舍去1,存储一个0,后面补22个0。
     *   所以浮点数0.5在内存中就存储为0 0111 1110 00000000000000000000000
     * - 这时,从内存中取出时,根据规则,将指数E的计算值减去127(64位浮点数的E减去1023),得到真实值
     *   再将有效数字M前加上第一位的1
     *
     * 第二种情况:E为全0
     * - 存储:如果E为全0,则说明真实E的值真小,只有当真实E+127=0时,存储到内存中时E才为全0。
     *   也就是说E=-127,2^-127,这是一个非常小的数字,无限接近于0。当s=0时就为正,当s=1时就为负
     * - 所以规定:取出时,浮点数的指数E等于1-127(或1-1023)即为真实值。
     *   并且有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示正负0,以及接近于0的很小的数字。
     *
     * 第三种情况:E为全1
     * - 如果E为全1,则说明真实E的值很大,真实E+127=255,存储到内存中的E才为全1,也就是说E=128。
     *   1.xxxxxx * 2^128,这是一个非常大的数字。当s=0时就为正,当s=1时就为负
     * - 规定:这时,如果有效数字M全为0,则表示±无穷大(正负取决于符号位S)
     */
    
  • 此时,再返回刚刚那个程序

    #include <stdio.h>
    
    int main()
    {
        int n = 9;
        //n在内存中存储为:00000000 00000000 00000000 00001001
        float * pFloat = (float*)&n;
        //%d以整型取出,打印原码,最高位0表示正数,数值位值为9,所以输出9。
        printf("n的值为:%d\n",n);//9
        /*
         * %f,以32位浮点数取出:0 00000000 00000000 00000000 0001001
         * s=0表示正数。E=0,则按照规定:真实E=1-127=-126。并且有效数字不再加1,而是直接还原为0.xxxxxx的小数
         * 所以这个数就变成了: 0.00000000000000000001001*2^-126,也就是说这个数无限接近于0
         * 因为%f只打印小数点后六位,所以就打印出:0.000000
         */
        printf("*pFloat的值为:%f\n",*pFloat);//0.000000
    
        *pFloat = 9.0;
        //9.0转换为二进制:1001.0,就是:1.001*2^3。s=0,E=3+127=130,M存储001,后面补20个0
        //所以9.0在内存中就是:0 10000010 00100000000000000000000
    
        //以%d打印,打印原码。01000001 00010000 00000000 00000000,最高位0表示正数,数值位值为1091567616
        //所以n输出为1091567616
        printf("n的值为:%d\n",n); //1091567616
        /*
         * %f,以32位浮点数取出:0 10000010 00100000000000000000000
         * s=0表示正数。E=130,则按照规定:真实E=130-127=3。并且有效数字补1,就成为:1.001
         * 1.001*2^3 ,就是:1001,转换为十进制就是:9
         * 因为%f只打印小数点后六位,所以就打印出:9.000000
         */
        printf("*pFloat的值为:%f\n",*pFloat);//9.000000
        
        return 0;
    }
    

练习

  • 程序执行结果

    #include <stdio.h>
    
    int main()
    {
        //200的二进制:00000000 00000000 00000000 11001000
        //char类型变量a只能存储一个字节,截取后八位存储到a中:1100 1000
        unsigned char a = 200;
        //100的二进制:00000000 00000000 00000000 01100100
        //截取后八位存储到b中:01100100
        unsigned char b = 100;
        unsigned char c = 0;
        //这里发生整型提升,因为a与b都是无符号char,所以高位补零,也就是
        //00000000 00000000 00000000 11001000   200
        //00000000 00000000 00000000 01100100   100
        //00000000 00000000 00000001 00101100   300
        //截取其后八位,存储到c中:00101100
        c = a + b;
    
        //使用%d形式打印,再次发生整型提升。
        // a+b,整型提升之后计算,得到的是00000000 00000000 00000001 00101100,不截断存储,直接打印就是300。
        // c是unsigned char类型,整型提升高位补0:00000000 00000000 00000000 00101100,算出来是44。所以c打印出来是44
        printf("%d %d",a+b,c);//300 44
    
        return 0;
    }
    
  • 在32位大端处理器上,变量b等于

    #include <stdio.h>
    
    int main()
    {
        //int占4个字节,所以a存储的应该是:0x 00 00 12 34
        //因为采用大端存储,也就是:低字节高地址,在内存中存储为:00 00 12 34。
        //因为采用大端存储,也就是:低字节低地址,在内存中存储为:34 12 00 00。
        unsigned int a = 0x1234;
        //这里强制转换为unsigned char类型,只存储了一个字节。所以在32位大端处理器上,变量b等于0x00。
        unsigned char  b = *(unsigned char*)&a;
    
        return 0;
    }
    
  • 打印杨辉三角

    /*
     * 杨辉三角:
     *          1
     *          1 1
     *          1 2 1
     *          1 3 3 1
     *          1 4 6 4 1
     *          1 5 10 10 5 1
     */
    #include <stdio.h>
    
    int main()
    {
        int arr[10][10] = {0};
        int i,j;
        for(i=0 ; i<10 ; i++)
        {
            for(j=0 ; j<10 ; j++)
            {
                //每一行第一个元素是1
                if(j == 0)
                {
                    arr[i][j] = 1;
                }
                //当i=j,也就是第一行的第一个、第二行的第二个、第三行的第三个....第n行的第n个,都是1。
                if(i == j)
                {
                    arr[i][j] = 1;
                }
                //当行数≥2、并且列数≥1的时候,就可以计算当前元素的值
                if(i>=2 && j>=1)
                {
                    arr[i][j] = arr[i-1][j-1] + arr[i-1][j];
                }
            }
        }
    
        for(i=0 ; i<10 ; i++)
        {
            //打印的元素个数:第几行就打印几个。
            for(j=0 ; j<=i ; j++)
            {
                    printf("%d ",arr[i][j]);
            }
            printf("\n");
        }
        return 0;
    }
    
  • 找凶手

    /*
     * 发生了一起谋杀案,警察通过排查,确定杀人凶手必为4个嫌疑犯中的一个。以下是他们的供词:
     * A说:不是我 、 B说:是C  、  C说:是D  、  D说:C在胡说
     * 已知:3个人说了真话,1个人说的假话。现在根据这些信息,写一个程序来确定谁是凶手
     */
    
    #include <stdio.h>
    
    int main()
    {
        char killer = 0;
        //假设凶手是A/B/C/D,然后判断他们四个人说的话是否符合3真1假。
        for(killer='A' ; killer<='D'; killer++)
        {
            //A说:不是我。转换为判断条件:killer != 'A'
            //B说:是C。 转换为判断条件:killer == 'C'
            //C说:是D 。 转换为判断条件:killer == 'D'
            //D说,C在胡说。 C说凶手是D,D说他胡说;意思就是说D说他自己不是凶手,转换为条件:killer != 'D'
            //条件为真则返回1,为假则返回0。3真1假,只有加起来是3的时候,此时killer所存储的就是凶手,打印出来即可。
            if((killer !='A') + (killer == 'C') + (killer == 'D') + (killer != 'D') == 3)
            {
                printf("凶手是:%c",killer);   //C
            }
        }
        return 0;
    }
    
  • 有一根香

    /*
     * 有一根香,材质不均匀。燃烧完一根香需要一个小时。给你两根香,请确认一个15分钟的时间段。
     * - 方法:点燃一根香的两端、另一根香的一端。待到点燃两端的那根香燃烧完的时候,时间过了30分钟。此时点燃一端的那根香也燃烧了30分钟,点燃这跟香的另一端,两端同时燃烧,此时到燃尽这个时间段就是15分钟。
     */
    
  • 坐电梯

    /*
     * 一个人住在30楼。下雨天电梯有人时,做电梯回家。其他时间做电梯到15楼,然后爬楼梯回家,思考为什么?
     * 
     * 官方解释:这个人是侏儒,很矮。
     * - 下雨天的时候,电梯有人,可以帮他按到30楼的按钮。或者他自己用伞点30楼的按钮。
     * - 电梯没人或没带伞时,只能点到15楼的按钮,然后再爬楼梯回家。
     */
    
  • 赛马

    /*
     * 36匹马,6个跑道。不给计数器赛马,比赛多少次,才能确定36匹马中的前三名。
     * 25匹马,5个跑道?
     *
     */
    
  • 猜名次

    /*
     * 5位运动员参加10米跳水比赛,有人让他们预测比赛结果。
     * A说:B第二、我第三。
     * B说:我第二、E第四
     * C说:我第一、D第二
     * D说:C最后,我第三
     * E说:我第四,A第一
     * 比赛结束后,每位选手都说对了一般,请确定比赛的名次。
     */
    #include <stdio.h>
    
    int main()
    {
        int a,b,c,d,e;
        for(a=1 ; a<=5 ; a++)
        {
            for(b=1 ; b<=5 ; b++)
            {
                for(c=1 ; c<=5 ; c++)
                {
                    for(d=1 ; d<=5 ; d++)
                    {
                        for(e=1 ; e<=5 ; e++)
                        {
                            //每个人说了两句话,只有一句是真的。所以我们把每个人说的话都分成两个条件。
                            //真为1,假为0。每个人的两个判断条件加起来应该是1。
                            if(((b==2)+(a==3) == 1)
                            && ((b==2)+(e==4) == 1)
                            && ((c==1)+(d==2) == 1)
                            && ((c==5)+(d==3) == 1)
                            && ((e==4)+(a==1) == 1))
                            {
                                //此时符合5个人所说的他们的话各对一半的情况。
                                //打印时我们发现,会出现名词重复的情况。所以我们加上判断:
                                //名次相乘:5*4*3*2*1=120 ,这样就不会出现重复的情况了。
                                if(a*b*c*d*e == 120)
                                {
                                    printf("a=%d b=%d c=%d d=%d e=%d\n",a,b,c,d,e);
                                }
    
                            }
                        }
                    }
                }
            }
        }
        return 0;
    }
    
  • 去牛客网,刷智力题…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值