从0开始学c语言-24- 剖析数据在内存中的存储(整型在内存中的储存和练习)、char的取值范围、大端小端储存

本文从0基础的角度详细介绍了C语言的数据类型,重点解析了整型在内存中的存储方式,包括原码、反码、补码的概念,并探讨了大端和小端存储模式的原因和判断方法。通过一系列的实践练习,帮助读者巩固理解。
摘要由CSDN通过智能技术生成

CSDN话题挑战赛第2期
参赛话题:学习笔记

本人0基础开始学编程,我能学会的,你也一定可以,学会多少写多少。

下载安装请从官网入手,社区版本即可,这里主要使用的软件是VS2019,图标如下。

 上一篇:从0开始学c语言-23-如何写出好(易于调试)的代码、模拟实现库函数:strcpy、strlen 、编程常见错误_阿秋的阿秋不是阿秋的博客-CSDN博客

目录

1. 数据类型详细介绍

数据类型

类型的意义:

类型的基本归类

整形家族:

浮点数家族:

构造类型:

指针类型

2. 整形在内存中的存储

.1 原码、反码、补码

内存中存放什么码?

我们来看看内存中储存的样子

.2 大小端

大端小端是啥

为什么有大端和小端:

判断大小端

3·练习出真知

练习1

练习2

练习3

有符号char的取值范围

无符号char的输出范围是多少呢?

练习4

练习5

 练习6

练习7


1. 数据类型详细介绍

数据类型

从0开始学c语言-02-关于数据类型_阿秋的阿秋不是阿秋的博客-CSDN博客

详细的在上面那个文章里

char        1   // 字符数据类型
short       2   // 短整型
int           4  // 整形
long        4   // 长整型
long long  8  // 更长的整形
float          4  // 单精度浮点数
double      8   // 双精度浮点数
上面的数字代表类型的大小,单位是字节。

类型的意义:

1. 使用这个类型开辟内存空间的大小(大小决定了使用范围)。
2. 如何看待内存空间的视角——整型和浮点面对同一个二进制序列能够读取出不同的数字。

类型的基本归类

整形家族:

整数:有符号的,无符号的

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

浮点数家族:

小数:单精度、双精度

float
double

构造类型:

特点:自由度大、自定义灵活

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

指针类型

int * p;
char * p;
float* p ;
void* p ;
空类型:
void 表示空类型(无类型)
通常应用于函数的返回类型、函数的参数、指针类型
空指针不能被解引用,也不能使用 -> 操作符访问结构体成员。

2. 整形在内存中的存储

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

数据在所开辟内存中到底是如何存储的?

那就需要讲讲各种码了。

.1 原码、反码、补码

计算机中的整数有三种表示方法,即原码、反码和补码。
三种表示方法均有 符号位 数值位 两部分,符号位都是用 0表示“正”,用1表示“负”
而数值位是 负整数的三种表示方法各不相同。
原码
直接将二进制按照正负数的形式翻译成二进制就可以。
反码
将原码的符号位不变,其他位依次按位取反就可以得到了。
补码

反码+1就得到补码。

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

内存中存放什么码?

对于整形来说:数据存放内存中其实存放的是 补码。
在计算机系统中,数值 一律用补码 来表示和存储。原因在于,使用补码, 可以将符号位和数值域统
一处理; 同时,加法和减法也可以统一处理(CPU 只有加法器 )此外, 补码与原码相互转换,其运算过程是相同的, 不需要额外的硬件电路。
我们先解释一下什么叫, 补码与原码相互转换,其运算过程是相同的。
根据上面所学,我们知道了,
原码 ——> 反码 :符号位不变,其他位按位取反
反码 ——> 补码 :+1
其实反过来也一样的,
补码 ——> 反码 :符号位不变,其他位按位取反
反码 ——> 原码 :+1
比如:
-1的补码
11111111 11111111 11111111 11111111

-1的反码(符号位不变,其他位按位取反)
10000000 00000000 00000000 00000000

-1的原码(+1)
10000000 00000000 00000000 00000001

可以看到确实是一样的。

我们来看看内存中储存的样子

int a = 1;
//1的补码
00000000 00000000 00000000 00000001

int b = -10;
//-10的原码
10000000 00000000 00000000 00001010
//反码
11111111 11111111 11111111 11110101
//补码
11111111 11111111 11111111 11110110

这是二进制序列,内存中会转化成16进制进行储存

int a = 1;
//16进制
00000001
int b = -10;
//-10的补码
11111111 11111111 11111111 11110110
//16进制
fffffff6

 我们调出内存窗口,

 然后看一下a和b的储存样子

 再看看我们刚刚写的16进制

int a = 1;
//16进制
00000001
int b = -10;
//16进制
fffffff6
有没有发现它是倒着储存的?
那就需要讲讲大小端的储存了。

.2 大小端

大端小端是啥

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

为什么有大端和小端:

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

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

判断大小端

我们知道的是,在取地址的时候,取的都是低地址的第一个字节。

然后根据上面a=1的大小端内储存的样子,就可以写出来这段代码啦!

int main()
{
	int a = 1;
	char* p = (char*)&a;
	if (*p == 1)
		printf("little");
	else
		printf("big");
	return 0;
}

优化一下,写成函数

int check_sys()
{
	union
	{
		int i;
		char c;
	}un;
	un.i = 1;
	return un.c;
}

3·练习出真知

练习1

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

先看前三个语句

char a= -1;
signed char b=-1;
unsigned char c=-1;
//-1 的补码
11111111 11111111 11111111 11111111
//截断储存
11111111
printf("a=%d,b=%d,c=%d",a,b,c);
//%d按照有符号整型打印
char a=-1;
11111111 - a
//因为char类型不够int类型的大小,所以要进行整型提升
//整形提升是按照变量的数据类型的符号位来提升的 ,无符号则高位补0
//char是有符号位的
11111111 11111111 11111111 11111111 -a整型提升

因为b也是有符号的char,所以和a是一样的。不再分析。

printf("a=%d,b=%d,c=%d",a,b,c);
//%d按照有符号整型打印
unsigned char c=-1;
11111111 - c
//因为unsigned char类型不够int类型的大小,所以要进行整型提升
//整形提升是按照变量的数据类型的符号位来提升的 ,无符号则高位补0
//unsigned char是无符号位的
00000000 00000000 00000000 11111111 -c整型提升

现在a和b、c的补码都知道了,然后按照%d的形式打印出去。

11111111 11111111 11111111 11111111 -a、b的补码
00000000 00000000 00000000 11111111 -c的补码

c按照%d的方式读取是正数,a和b是负数,所以需要进行原码转换。

11111111 11111111 11111111 11111111 -a、b的补码
10000000 00000000 00000000 00000000 -反码
10000000 00000000 00000000 00000001 -原码

现在看看我们的计算过程是否正确。

然后梳理一下整体思路
1·写出每个变量内存中的补码 - 该截断的就截断。
2·如果打印形式(%d、%u)的长度比变量类型(char、short)长,就需要整型提升。
3·整型提升看变量类型有无符号位选择按照变量的数据类型的符号位来提升还是无符号补0。
4·输出的时候按照输出形式来读取二进制序列。(无符号输出不需要进行正负数的区分)

练习2

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

按照我们上面的思路来做一下。

1·写出每个变量内存中的补码 - 该截断的就截断。

char a = -128;
10000000 00000000 00000000 10000000 -原码
11111111 11111111 11111111 01111111 -反码
11111111 11111111 11111111 10000000 -补码
10000000 -截断 -a
2·如果打印形式(%d、%u)的长度比变量类型(char、short)长,就需要整型提升。
3·整型提升看变量类型有无符号位选择按照变量的数据类型的符号位来提升还是无符号补0。
printf("%u\n",a);
char a = -128;
10000000 -截断 -a
%u比char大,进行整型提升
变量a是char类型,有符号,符号位是1补1
11111111 11111111 11111111 10000000 -a整型提升

4·输出的时候按照输出形式来读取二进制序列。(无符号输出不需要进行正负数的区分)

printf("%u\n",a);
char a = -128;
11111111 11111111 11111111 10000000 -a整型提升
因为%u是无符号整型打印,所以直接输出即可

我用计算机算a的二进制序列= 4294967168

这里不放图了,编译器的结果也是如此,那便证明这样的思路没问题。

练习3

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

1·写出每个变量内存中的补码 - 该截断的就截断。

char a = 128;
00000000 00000000 00000000 10000000 -原码
01111111 11111111 11111111 01111111 -反码
01111111 11111111 11111111 10000000 -补码
10000000 -截断 -a
2·如果打印形式(%d、%u)的长度比变量类型(char、short)长,就需要整型提升。
3·整型提升看变量类型有无符号位选择按照变量的数据类型的符号位来提升还是无符号补0。
printf("%u\n",a);
char a = 128;
10000000 -截断 -a
%u比char大,进行整型提升
变量a是char类型,有符号,符号位是1补1
11111111 11111111 11111111 10000000 -a整型提升

4·输出的时候按照输出形式来读取二进制序列。(无符号输出不需要进行正负数的区分)

printf("%u\n",a);
char a = 128;
11111111 11111111 11111111 10000000 -a整型提升
因为%u是无符号整型打印,所以直接输出即可

你会惊奇的发现,这输出结果和上面的结果一样!

再补充个知识点

有符号char的取值范围

图中的二进制序列是 (signed) char类型变量的 补码(内存中的二进制序列)
绿色的数字是按照 有符号整型 输出的结果
也就是说, 有符号的char输出范围是-128~127

 不理解怎么输出的话,我示范一个

无符号char的输出范围是多少呢?

 

图中的二进制序列是 unsigned  char类型变量的 补码(内存中的二进制序列)
绿色的数字是按照 无符号整型输出的结果

 也就是说,有符号的char输出范围是0~255

同样示范一个数字输出的过程

练习4

 int main()
{
	 int i = -20;
	 unsigned  int  j = 10;
	 printf("%d\n", i + j);
return 0; }
按照上面的思路我们继续做一下这道题。
1·写出每个变量内存中的补码 - 该截断的就截断。
int i = -20;
10000000 00000000 00000000 00010100 - i的原码
11111111 11111111 11111111 11101011 - 反码
11111111 11111111 11111111 11101100 - 补码
不需要截断
unsigned  int  j = 10;
00000000 00000000 00000000 00001010 - j的原码、反码、补码
不需要截断也不需要补码转换,因为无符号整型
2·如果打印形式(%d、%u)的长度比变量类型(char、short)长,就需要整型提升。
3·整型提升看变量类型有无符号位选择按照变量的数据类型的符号位来提升还是无符号补0。
printf("%d\n", i + j);
int i = -20;
unsigned  int  j = 10;
都是int,不需要整型提升
11111111 11111111 11111111 11101100 - i
00000000 00000000 00000000 00001010 - j
11111111 11111111 11111111 11110110 - i+j

4·输出的时候按照输出形式来读取二进制序列。(无符号输出不需要进行正负数的区分)

11111111 11111111 11111111 11110110 - i+j的补码
%d输出,有符号整型,需要进行原码转换运算
10000000 00000000 00000000 00001001 - 反码
10000000 00000000 00000000 00001010 - 原码 等于-10

练习5

int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--) {
		printf("%u\n", i);
	}
	return 0;
}

这题不带你做了,你自己试试?

我要开始讲解了哦,你一定要自己思考

首先我们看到了 i 是一个无符号整型变量,在for循环中赋值为9,循环条件是i >=0,满足条件便减一。那么我们就要思考的是,什么时候跳出循环再确定要输出什么。

i 是一个无符号整型变量,循环条件是 i >=0,那就是说如果i想跳出循环,i 跳出循环的时候应该是小于0的,也就是说i 跳出循环的时候应该是负数,但是i 是一个无符号整型变量,是一个没有负数的变量,那就没办法跳出循环了。

截图出来了一些,实际上运行的时候你都看不清是什么数,一直死循环。

包括你也看到了for有个波浪号,我们看看警告,

 练习6

int main()
{
    char a[1000];
    int i;
    for(i=0; i<1000; i++)
   {
        a[i] = -1-i;
   }
    printf("%d",strlen(a));
    return 0; }

先自己思考,我要开始讲了

首先我们看到char a[1000]是一个存放char类型的数组,在for循环中,一直在数组中存放数据,存放好后,strlen函数来求char a[1000]数组的长度,那便是找\0了,而 \0的ASCII码是0,所以遇到0就不再计算长度了。

我们先分析在数组中存放了什么数据,上面已经学到过char有无符号的取值范围,这是一个有符号整型数组, 有符号的char输出范围是-128~127。
也就是说,是这个图中箭头反过来的样子。
当 i =0 ,a[0]= -1
    i =1 , a[1]= -2
一直到a[某个数]= -128
然后再a[某个数]= 127
再到a[某个数]=0
再继续循环储存,直到循环结束
-1 -2 -3……-128 127 126 ……3 2 1 0 -1 -2……128 127 126……

后储存的数据就像上面这样

现在我们找到了0,那么长度是?=127+128=255

练习7

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

应该会了吧?已经做过很多同类型的了。

首先unsigned char i的取值范围是0-255,而跳出循环的条件是i>255,是死循环哦~

下期讲浮点型储存的样子,和整型还是不太一样的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值