目录
- 数据类型的介绍
- 类型的意义
- 类型的基本归类
- 整形家族
- 浮点型家族
- 构造类型--自定义类型
- 指针类型
- 空类型
- 整形在内存中的存储
- 大小端
- 大小端如何区分
- 为什么会有大小端
- 判断机器字节序
从本章开始,我们将正式进入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;
}
我们知道数据在内存中以2进制的形式存储。
而对于整数来说:
整数二进制有三种表示形式:原码,反码,补码
而对于正整数来说:原码,反码,补码相同。
对于负整数来说:原码,反码,补码是需要计算的。
就以a=-10为例,
先写原码:
-10为负数,符号位就应该为1(正数就为0,负数就为1),10用二进制表示为1010
//原码
100000000000000000000000000000001010
反码就是符号位不变,将原码按位取反
//反码
111111111111111111111111111111110101
而补码就是反码+1
111111111111111111111111111111110110
那么我们在内存中储存的究竟是-10的原码还是反码,还是补码呢?我们用监视的方法来看一下,如图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
图中第一和第二种就是大端字节序和小端字节序,第三种和第四种方法虽然在理论上来讲也是可行的,但是存放的过程就会变得过于复杂,所以不采用。
大小端如何区分
如图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
所以我的电脑采用的是小端存储模式。
以上就是本章的全部内容了,如有出入,欢迎指正。