C语言--数据的存储1


从本章开始,我们将正式进入C语言的进阶学习中。
本篇内容我们将学习 数据的存储

数据类型的介绍

在以往的学习当中,我们已经接触过许多的基本数据类型了,如下:

char         //字符数据类型
short        //短整型
int          //整形
long         //长整型
long long    //更长的整形
float        //单精度浮点数
double       //双精度浮点数

那么C语言中有没有字符串类型呢?
其实在C语言的环境中是没有的,很多高级语言java #c就有字符串类型,有string来表示字符串,用法和int很类似,但在C语言中字符串是通过字符指针来间接实现的。

类型的意义

我们使用这个类型可以开辟不同大小的空间(大小决定了使用范围)(例如int类型就会开辟4个字节大小的空间),同时也为程序员提供了一个观察内存空间的视角

类型的基本归类

整形家族

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

我们发现,char类型也被归在了整形家族中,这是因为在存储字符的时候,实际上是储存的ascll码值,而ascll码值实际上也是一串数字,所以char类型实际上也隶属于整形家族。

浮点型家族

double
float

构造类型–自定义类型

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

指针类型

int* pi;
char* pc;
float* pf;
double* pb;

空类型

void

void 表示空类型
空类型的作用在于:
1,可作为函数的返回类型: void test ()
2,可作为函数的参数: void test ();
3,指针: void* p;

整形在内存中的存储

我们之前讲过一个变量的创建是要在内存中开辟空间的。空间的大小是根据不同的类型而决定的。
那接下来我们谈谈数据在所开辟内存中到底是如何存储的
举个例子:

int main()
{
    
    
	int a = -10;
	return 0;
}

图1

我们知道数据在内存中以2进制的形式存储。
而对于整数来说:
整数二进制有三种表示形式:原码,反码,补码
而对于正整数来说:原码,反码,补码相同。
对于负整数来说:原码,反码,补码是需要计算的。

就以a=-10为例,
先写原码
-10为负数,符号位就应该为1(正数就为0,负数就为1),10用二进制表示为1010

//原码
100000000000000000000000000000001010

反码就是符号位不变,将原码按位取反

//反码
111111111111111111111111111111110101

补码就是反码+1

111111111111111111111111111111110110

那么我们在内存中储存的究竟是-10的原码还是反码,还是补码呢?我们用监视的方法来看一下,如图2
图2
我们把数据提出来:FFFFFFF6→转换为2进制就为111111111111111111111111111111110110(比如最后四位对应6,6转换为2进制就为0110).

所以,整数在内存中的储存是以补码的形式储存的

那么为什么是存的补码而不是其他形式呢?我们举个例子:
我们首先要知道,编译器里是没有减法计算的,所谓的减法实际上还是加法运算,比如:1-1其实是1+(-1)

假设储存的是原码
 1的原码:
 00000000000000000000000000000000001
-1的原码:
 10000000000000000000000000000000001
 1+(-1)=10000000000000000000000000000000010
 这是-2的原码,与我们想得到的0不相符

所以原码储存是行不通的,我们再来试试补码

假设储存的是补码
1的补码:
 00000000000000000000000000000000001
 -1的补码:
 11111111111111111111111111111111111
 1+(-1)=:
 100000000000000000000000000000000000
 //多出的一位1丢掉变为
 00000000000000000000000000000000000
 这就是0的补码

所以只有用补码来计算才是正确的。
除此之外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

到这里我们发现有一个问题没有被解决,那就是监视器中显示的明明是F6 FF FF FF,但我们在读的时候却是反着读的,这里就要涉及新的概念:大端和小端

大小端

我们来看一个地址的存放,如图3
图3
图中第一和第二种就是大端字节序和小端字节序,第三种和第四种方法虽然在理论上来讲也是可行的,但是存放的过程就会变得过于复杂,所以不采用。

大小端如何区分

图4

如图4,我们假设地址11 22 33 44为一个数字,那么越靠近右边,代表着位数越低,而越靠近左边,代表着位数越高,所以右边为低位字节,左边为高位字节
根据我们定义的左边为低地址,右边为高地址,
大端(存储)模式定义为:数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中
小端(存储)模式定义为:数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中

速记的口诀是:小同大异
那么通过我们所学的知识,上述图片中的F6 FF FF FF就为小端存储模式

为什么会有大小端

为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为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处理器还可以由硬件来选择是大端模式还是小端模式。

判断机器字节序

我们通过设计一个小程序来判断当前机器的字节序
假设我们要存放的地址为 00 00 00 01

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

因为我们只想访问一个字节来判断是否为1,所以我们需要将&a强制转换为char类型,这样char就指向整形a的四个字节中的首个字节*。

我们同样可以通过函数的方法实现:

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

优化一下

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

三种方法运行结果如图4
图4
所以我的电脑采用的是小端存储模式

以上就是本章的全部内容了,如有出入,欢迎指正。

猜你喜欢

转载自blog.csdn.net/m0_75233943/article/details/128932669