C语言

9. C语言进阶--数据存储

Posted on 2022-02-01,9 min read

1. 数据存储

1.1 数据类型

1.1.1 整形

char 
    unsigned char
    signed char

short
    unsigned short [int]
    signed short [int]

int
    unsigned int
    signed int

long
    unsigned long [int]
    signed long [int]

1.1.2 浮点型

float
double

1.1.3 构造类型

// 数组
// 结构体 struct
// 枚举类型 enum
// 联合类型 union

1.1.4 指针类型

int* pi;
char* pc;
float* pf;
void* pv;

1.1.5 空类型

void // 空类型(无类型)
// 通常应用于函数的返回类型、函数的参数、指针类型

1.2 整形在内存中的存储

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

int a = 100;
int b = -200;
// a分配四个字节的内存空间
// 无论是在32位还是64位环境下,int类型都是4byte

// 表示范围在limits.h中定义

1.2.1 原码、反码、补码

计算机中有符号数的三种表示方法。整数在计算机中以补码的形式存储。

  • 原码
  • 反码:原码符号位不变,其他位按位取反
  • 补码:反码加一

正数的原码、反码、补码相同。

1.2.2 为什么数据在计算机中以补码形式存储?

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

1.3 大小端字节序

1.3.1 大小端介绍

  • 大端(存储)模式:是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
  • 小端(存储)模式:是指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中。

![](file://C:\Users\Turbo\Desktop\1. 数据存储\image\image.png)

1.3.2 为什么又大端和小端?

在计算机系统中,以字节为单位,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8bit的char之外,还有16bit的short类型,32bit的int类型,以及long类型。另外,对于位数大于8位的处理器,例如16位或32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着如何将多个字节安排的问题。因此就导致了大端和小端两种存储模式。

1.3.3 相关习题

简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。

int main()
{
  int a = 1;
  char* p = (char*) &a;
  if (*p==1)
  {
    printf("小端\n");
  }
  else {
    printf("大端\n");
  }
  return 0;
}

1.4 浮点型在内存中的存储

1.4.1 浮点类型

float // 4byte
double // 8byte
long double // 8byte
// 表示范围再float.h中定义

1.4.2 IEEE754

C/C++中的浮点数采用IEEE754。根据过国际标准IEEE(电气和电子工程协会)754,任意一个二进制浮点数V可以表示成下面的格式:

  • (-1)S*M*2E (E为指数,2为基数,M为尾数)
  • (-1)^S 符号位 当S=0,V为正数;当S=1,V为负数。
  • M 有效数字,2>M≥1。
  • 2^E 指数位。
// 浮点数举例
// 十进制:5.5
// 二进制:101.1 -> 1.011 * 2 ^ 2            1.011为尾数,2(第一个)为基数,2(第二个)为指数
//        -> (-1) ^ 0 * 1.011 * 2 ^ 2
//        -> S=0; E=2; M=1.011

其他规定

  • 对于E,E是一个无符号整数。而科学计数法中E可能出现负值。为避免这种情况,在存储E时,对于float类型,再加上127;对于double类型,再加上1023。

    // 例:
    // 十进制:0.5
    // 二进制:0.1 (float)
    // 科学计数法:(-1)^0 * 1.0 * 2^(-1)
    // S=0; E=-1+127=126
    
  • 对于M,M表示为1.xxxxxx,可见其中的1时固定不变的。所以在存储中可将其省略,将E变成xxxxxx,只将小数部分存储在内存中,节省1bit空间。

    // 例:
    // 同上,十进制0.5的二进制表示为0.1(float)
    // 科学计数法:(-1)^0 * 1.0 * 2^(-1)
    // E=>1.000000=>00000
    

![](file://C:\Users\Turbo\Desktop\1. 数据存储\image\image_1.png)

1.4.3 读取

  • E不全为1,也不全为0

    指数E的计算值减去127(或1023),得真实值,再将M加上第一位的1。

  • E全为0

    浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。为了表示+/-0,以及接近于0的很小的数字。

  • E全为1

    如果有效数字M全为0,表示+/-无穷大(正负取决于符号位S)。

  • 其他:浮点数在内存中也存在大小端问题。

1.4.4 实例1

int main()
{
  int n = 9;
  float* pf = (float*)&n; // 00000000000000000000000000001001
  printf("n的值为:%d\n", n); // 9
  printf("*pf的值为:%f\n", *pf); // 0.0
  // 0 00000000 00000000000000000001001
  // S=0; E=1-127=-126; M=0.00000000000000000001001
  // 0.00000000000000000001001 * 2 ^ (-126)
  // float的精度为6 则打印0.000000

  *pf = 9.0; // 0 01111100 00100000000000000000000
  printf("n的值为:%d\n", n); // 1091567616
  printf("*pf的值为:%f\n", *pf); // 9.0
  return 0;
}

![](file://C:\Users\Turbo\Desktop\1. 数据存储\image\image_2.png)

1.4.5 实例2

int main()
{
  float f = 9.0; // 0 01111100 00100000000000000000000
  printf("%d\n", f); // 以浮点型存储,浮点型读取,整形打印

  int* p = (int*)&f;
  printf("%d\n", *p); // 以浮点型存储,整形读取,整形打印
  return 0;
}

![](file://C:\Users\Turbo\Desktop\1. 数据存储\image\image_3.png)

1.5 习题

1.5.1 一

int main()
{
  char a = -1;
  signed char b = -1;
  unsigned char c = -1;
  // -1的二进制
  // 10000000000000000000000000000001 -> 原码
  // 11111111111111111111111111111110 -> 反码
  // 11111111111111111111111111111111 -> 补码
  // 存放到char类型变量中,高位截断
  // 在a b c中存放的都是11111111
  printf("%d %d %d\n", a, b, c); 
  // %d打印整形,补码整形提升,打印原码
  // a b 整型提升 高位补1
  // c 无符号整型提升 高位补0
  // a b 中的值为 11111111111111111111111111111111
  // c   中的值为 00000000000000000000000011111111
  // 以原码打印 a, b = -1; c = 255;
  return 0;
 }

// 补充
// 1. char到底是signed char 还是 unsigned char?
//    C语言标准并没有规定,取决于编译器(但大多数编译器都是signed char)
// 2. int是signed int 还是 unsigned int?
//    C语言规定int没有unsigned作为前缀时为signed int

2.5.2 二

int main()
{
  char a = -128;
  // 10000000000000000000000010000000
  // a = 10000000 
  printf("%u\n", a); // %u打印无符号整数
  // a是有符号char 整型提升 高位补1
  // 11111111111111111111111110000000
  return 0;
}

2.5.3 三

int main()
{
  char a = 128;
  // 00000000000000000000000010000000
  // a 10000000
  printf("%u\n", a);
  // a是有符号char 整型提升 高位补1
  // 11111111111111111111111110000000
}

// 10000000 在内存中直接被解析为-128
// 有符号char的取值范围为-128~127,如果存放的值大于127,则被解析为负数
// 例如128会被解析为-128

2.5.4 四

int main()
{
  int i = -20;
  // 10000000000000000000000000010100 -20 原码
  // 11111111111111111111111111101100 -20 补码
  unsigned int j = 10;
  // 00000000000000000000000000001010 10
  printf("%d\n", i + j);
  // 相加
  // 11111111111111111111111111110110 补码
  // 10000000000000000000000000001010 原码 -10
  // %d,认为内存中放的是有符号int类型
  return 0;
}
// 站在内存的视角

2.5.5 五

int main()
{
  int a = -20;
  unsigned int b = 10;
  if (a + b > 0) {
    printf("a+b>0");
  }else{
    printf("a+b<=0");
  }
  return 0;
}
// int类型和无符号int类型相加,int类型转换为unsigned int类型

![](file://C:\Users\Turbo\Desktop\1. 数据存储\image\image_4.png)

2.5.6 六

int main()
{
  unsigned int i; // 无符号整数,i>=0恒成立
  for ( i = 9; i >= 0; i--)
  {
    printf("%u\n", i);
  }
  return 0;
}
// 无限循环

![](file://C:\Users\Turbo\Desktop\1. 数据存储\image\image_5.png)

2.5.7 七

#include <string.h>

int main()
{
  char a[1000];
  int i;
  for ( i = 0; i < 1000; i++)
  {
    a[i] = -1 - i;
  }
  printf("%d\n", strlen(a));
  return 0;
}
// -1 - i
// -1 -2 -3 ... -128 127 126 ... 3 2 1 0('\0')
// 128 + 127 = 255

![](file://C:\Users\Turbo\Desktop\1. 数据存储\image\image_6.png)

2.5.8 八

unsigned char i = 0;
int main()
{
  for ( i = 0; i <= 255 ; i++)
  {
    printf("Hello World!\n");
  }
  return 0;
}

下一篇: 8. C语言--调试→