C语言——深度剖析数据在内存中的存储——第1篇——(第24篇)

坚持就是胜利


一、数据类型详细介绍

前面我们学习了基本的内置类型,以及它们所占存储空间的大小。

char        //字符数据类型  1 字节
short       //短整型        2 字节 
int         //整型          4 字节
long        //长整型        4/8 字节  sizeof(long) >= sizeof(int)
long long   //更长的整型    8 字节
float       //单精度浮点数  4 字节
double      //双精度浮点数  8 字节

1、什么是 内置类型?

答:C语言本身自带的类型

2、为什么整型分为 short int long?

答:比如存储年龄 age,用 int 太大了,用 short 就可以。short 取值范围:-32768 ~ 32767

#include <limits.h>   //注意头文件
                      //在此头文件下,可以查询内置类型的最大值,最小值

int main()
{
	INT_MAX;   //转到定义

	return 0;
}

在这里插入图片描述

类型的意义:

1、使用这个类型开辟内存空间的大小(大小决定了使用范围)。
2、如何看待内存空间的视觉。

1、类型的基本归类

有符号数、无符号数,只针对 整型。
浮点数 没有 有符号数和无符号数 的说法和区分。

(1)整型家族

//字符 char 在存储的时候,存储的是 ASCII值,是整型所以归类的时候,放在整型家族。
char         
	unsigned char 
	signed char
short
	unsigned short
	signed short
int 
	unsigned int
	signed int
long 
	unsigned long [int]
	signed long [int]
对于整型家族的类型来说,有:“有符号”和“无符号”的区分

1char 到底是 signed char 还是 unsigned char 不确定
  (C语言没有给出明确的规定)
  (char 在 VS 上是 signed char2short == signed short
   unsigned short
3int  ==  signed int
   unsigned int
4long  ==  signed long
   unsigned long

在这里插入图片描述
在这里插入图片描述

(2)浮点数家族

float
double

(3)构造类型 (自定义类型)

1、数组类型
2、结构体类型 struct
3、枚举类型 enum
4、联合类型 union

//int arr1[10] ——> int [10]
//int arr2[20] ——> int [20]
//char arr3[10] ——> char [10]
//数组 arr1 和 arr2 和 arr3 类型各不相同

(4)指针类型

1int* pi;
2char* pc;
3float* pf;
4void* pv;
5、结构体指针类型;

(5)空类型

void 表示 空类型(无类型)
通常应用于 函数的返回类型、函数的参数、指针类型。
//void* ps;

二、整型在内存中的存储解析

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

1、原码、反码、补码

计算机中的整数有 3 种 2进制 表示方法,即原码、反码、补码。
三种表示方法均有 符号位 和 数值位 两部分,符号位都是用 0 表示 “正”,用 1 表示 “负”,
而数值位:
正整数的原码、反码、补码都相同。
负整数的三种表示方法各不相同。

原码
直接将数值按照正负数的形式翻译成二进制就可以得到原码

反码
将原码的符号位不变,其他位依次按位取反就可以得到反码

补码
反码 + 1 就得到补码

对于整型来说:数据存放内存中,其实存放的是补码。
为什么呢?

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

在这里插入图片描述

为什么用 补码 计算,而不用 原码 计算?

答:用 原码 计算,有些运算会出错

计算 1 - 1  (由于CPU只有加法器,所以 1 - 1 就是 1 +  (-1)1+(-1)

原码 计算 
00000000 00000000 00000000 00000001  数值 1
10000000 00000000 00000000 00000001  数值 -1
10000000 00000000 00000000 00000010  相加等于 -2 ,计算错误

补码计算:将 符号位 和 数值域 统一处理
00000000 00000000 00000000 00000001  数值1原码
00000000 00000000 00000000 00000001  数值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补码
1 00000000 00000000 00000000 00000000  只能存3200000000 00000000 00000000 00000000  结果为 0 ,正确

2、大小端字节序介绍

什么是 字节序?
答:以 字节 为单位,讨论存储顺序的。

(1)什么是大端小端?

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

(2)为什么有大端和小端?

为什么会有大小端模式之分呢?
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8 bit。但是在C语言中除了8 bit 的 char 之外,还有 16 bit 的 short型 ,32 bit 的 long型 (要看具体的编译器),另外,对于 位数 大于8位 的处理器;例如 16位 或者 32位 的处理器,由于寄存器宽度大于 一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。

例如:一个 16bit 的 short型 x,在内存中的地址为 0x0010,x 的值为 0x1122,那么 0x11 为高字节,0x22 为低字节。对于大端模式,就将0x11 放在低地址中,即 0x0010 中,0x22 放在高地址中,即 0x0011 中。小端模式,刚好相反。
我们常用的 x86 结构 是 小端模式,而 KEIL C51 则为大端模式。很多的 ARM,DSP 都为 小端模式。有些 ARM 处理器 还可以由 硬件 来选择是大端模式还是小端模式。

在这里插入图片描述

(3)设计一个小程序来判断当前机器的字节序

在这里插入图片描述

由于 int a;
所以 &a 是 int* 类型
需要判断 int a 的 第1个字节
将 int* 转换为 char* 即可!
再 解引用* 
#include <stdio.h>

int main()
{
	int a = 1;

	//char* p = &a;   //这么写是错误的,因为 &a 是 int* 类型

	char* p = (char*)&a;

	if (*p == 1)
	{
		printf("小端存储\n");
	}
	else
	{
		printf("大端存储\n");
	}
	return 0;
}

在这里插入图片描述

#include <stdio.h>

int main()
{
	int a = 1;
	if (*(char*)&a == 1)
	{
		printf("小端存储\n");
	}
	else
	{
		printf("大端存储\n");
	}
	return 0;
}
#include <stdio.h>

int check_sys()
{
	int a = 1;
	if (*(char*)&a == 1)  //再精简为:if(*(char*)&a)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

int main()
{
	int ret = check_sys();
	if (ret == 1)
	{
		printf("小端存储\n");
	}
	else
	{
		printf("大端存储\n");
	}

	return 0;
}
#include <stdio.h>

int check_sys()
{
	int a = 1;  

	return *(char*)&a;  //一步步精简成这样
	
}

int main()
{
	int ret = check_sys();
	if (ret == 1)
	{
		printf("小端存储\n");
	}
	else
	{
		printf("大端存储\n");
	}

	return 0;
}

3、练习

(1)

对于整型家族的类型来说,有:“有符号”和“无符号”的区分

1char 到底是 signed char 还是 unsigned char 不确定
  (C语言没有给出明确的规定)
  (char 在 VS 上是 signed char2short == signed short
   unsigned short
3int  ==  signed int
   unsigned int
4long  ==  signed long
   unsigned long

在这里插入图片描述
在这里插入图片描述

#include <stdio.h>

int main()
{
	char a = -1;
	signed char b = -1;
	unsigned char c = -1;
	printf("a=%d,b=%d,c=%d\n", a, b, c);  // a=-1 b=-1 c=255 
	return 0;
}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

//知识点(之前学习过)
//1、长度大于 int 的,就不需要进行 ”整型提升“
//2、有符号的整型提升:高位补充符号位
//3、无符号的整型提升,高位补 0
//4、%d 十进制的形式打印有符号整型整数

//正确的解答过程
//整数 -1 存入 char a 中
//整数 -1 原码:10000000 00000000 00000000 00000001
//        反码:11111111 11111111 11111111 11111110
//        补码:11111111 11111111 11111111 11111111

//截断,因为只有 1 字节 才能存入 char a 中:11111111

//%d 十进制的形式打印有符号整型整数
//对char a 进行 整型提升
//补码:11111111 11111111 11111111 11111111
//反码:10000000 00000000 00000000 00000000
//补码:10000000 00000000 00000000 00000001
//结果显示:-1


//signed char b 和 char a 是一样的解答过程
//在本电脑中的VS编译器,char 类型是 signed char 

//unsigned char c
//整数 -1 存入 unsigned char c 中
//整数 -1 原码:10000000 00000000 00000000 00000001
//        反码:11111111 11111111 11111111 11111110
//        补码:11111111 11111111 11111111 11111111

//截断,因为只有 1 字节 才能存入 unsigned char c 中:11111111

//无符号整型提升,高位补 0
//补码:00000000 00000000 00000000 11111111
//%d 十进制的形式打印有符号整型整数
//因为此时已经按照 %d 的形式输出,最高位为 0,是 正数
// 正数的原码、反码、补码相同
//原码:00000000 00000000 00000000 11111111
//结果:255

(2)

#include <stdio.h>

int main()
{
	char a = -128;
	printf("%u\n", a);
	return 0;
}

在这里插入图片描述
在这里插入图片描述

//知识点:
//%u 十进制的形式打印无符号的整型整数

//整数-128,存入 char a 中
//-128补码:10000000 00000000 00000000 10000000
//    反码:11111111 11111111 11111111 01111111
//    补码:11111111 11111111 11111111 10000000

//截断:10000000  存入 char a 中

//%u 十进制的形式打印无符号的整型整数
//整型提升,也就是对 char a 进行整型提升
//char a 是有符号的,符号位是 1
//补码:11111111 11111111 11111111 10000000  
//%u 是无符号数,内存中就没有符号位,内存中存入的就是:11111111 11111111 11111111 10000000
//无符号数可以看成 正数,补码,反码,原码都是一样的
//结果就是:11111111 11111111 11111111 10000000
//十进制就是:4294967168

(3)

#include <stdio.h>

int main()
{
	char a = 128;
	printf("%u\n", a);
	return 0;
}

在这里插入图片描述

//知识点:
//%u 十进制的形式打印无符号的整型整数

//整数128,存入 char a 中
//128补码:00000000 00000000 00000000 10000000
//正数的原码、反码、补码相同

//截断:10000000  存入 char a 中

//%u 十进制的形式打印无符号的整型整数
//整型提升,也就是对 char a 进行整型提升,char a 是有符号的,符号位为 1
//char a 是有符号的,符号位是 1
//补码:11111111 11111111 11111111 10000000  
//%u 是无符号数,内存中就没有符号位,内存中存入的就是:11111111 11111111 11111111 10000000
//无符号数可以看成 正数,补码,反码,原码都是一样的
//结果就是:11111111 11111111 11111111 10000000
//十进制就是:4294967168

(4)

在这里插入图片描述

#include <stdio.h>

int main()
{
	int i = -20;
	unsigned int j = 10;
	printf("%d\n", i + j);
	return 0;
}

在这里插入图片描述

//知识点:
//1、补码得到原码有两种方式:
//                          (1)补码-1得到反码,反码取反得到原码
//                          (2)补码先取反再加1,得到原码

//整数-20原码:10000000 00000000 00000000 00010100
//       反码:11111111 11111111 11111111 11101011
//       补码:11111111 11111111 11111111 11101100

//unsigned int j = 10原码:00000000 00000000 00000000 00001010
//无符号数的反码、原码、补码相同
//                   补码:00000000 00000000 00000000 00001010

//i+j  补码相加 11111111 11111111 11111111 11101100
//              00000000 00000000 00000000 00001010
//              11111111 11111111 11111111 11110110 
//截断:11111111 11111111 11111111 11110110 
// (1)补码-1得到反码,反码取反得到原码
//补码:11111111 11111111 11111111 11110110  
//反码:11111111 11111111 11111111 11110101
//原码:10000000 00000000 00000000 00001010
//结果:-10

//(2)补码先取反再加1,得到原码
//补码:11111111 11111111 11111111 11110110 
//取反:10000000 00000000 00000000 00001001
//+1  :10000000 00000000 00000000 00001010
//结果:-10

(5)

#include <stdio.h>
#include <windows.h>

int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--)
	{
		printf("%u\n", i);
		
		Sleep(1000);  //单位:毫秒   5000毫秒
	}
	return 0;
}

在这里插入图片描述
死循环,因为 unsigned int i 是 恒大于等于零 的。
在这里插入图片描述

//9 8 7 6 5 4 3 2 1 0 的原码、反码、补码 相同
//0 原码:00000000 00000000 00000000 00000000

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

//0-1  0+(-1)
//0 补码:00000000 00000000 00000000 00000000
//-1补码:11111111 11111111 11111111 11111111
//相加  :11111111 11111111 11111111 11111111

//以 %u 输出:4294967295

在这里插入图片描述

(6)比较难,好好理解

#include <stdio.h>
#include <string.h>

int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
		printf("%d\n", a[i]);
	}
	printf("%d\n", strlen(a));  //答案是:255

	return 0;
}

结果:255
原因:strlen() 统计的是 ‘\0’,之前出现的数字的个数!!!
在这里插入图片描述
在这里插入图片描述

(7)好题

#include <stdio.h>

unsigned char i = 0;   //unsigned char 的取值范围:0 ~ 255

int main()
{
	for (i = 0; i <= 255; i++)      //此时:i<=255 条件恒成立,所以死循环
	{
		printf("hell0 world\n");    //死循环
	}
	return 0;
}

微软雅黑字体
黑体
3号字
4号字
红色
绿色
蓝色

  • 25
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值