前言
数据存储是编程学习的重要内容
目录
们发现并减少bug具有相当重要的意义。
提示:以下是本篇文章正文内容,下面案例可供参考
一、基本数据类型
在学习C语言的过程中,对数据是如何在计算机中存储的探究与学习是十分必要的。C语言中数据可分为两大类--有符号数和无符号数(),它们的存储类型有以下几类:(这里不考虑结构体类型)
整型 | 浮点型 | 数组 | 指针 |
int(signed int unsigned int) | double | 整型数组 | 整型指针 |
short | float | 浮点型数组 | 浮点型指针 |
long | 指针数组 | 数组指针 | |
long long | |||
char(ASCII码值也为整型) |
这里并未完全列出,我将对类型之间的转换以代码的形式做简要讲解。
二、计算机中数据的运算
我们都知道,数据在计算机中都以二进制补码储存,而CPU能进行加法运算,在我们编写程序时,所有的问题的解决都只是CPU进行着加法运算。像2-1,在计算机中便被理解为2+(-1),2*2,便是两个2相加,这些运算符都早由以前的程序员或科学家们设计好了。在计算机中2-1是这样运算的:
00000000 00000000 00000000 00000010 -- 2的原码、反码、补码(正数三码相同)
10000000 00000000 00000000 00000001 -- -1的原码
11111111 11111111 11111111 11111110 -- -1的反码(最高位为符号位不变)
11111111 11111111 11111111 11111111 -- -1的补码
二者补码相加得:1 00000000 00000000 00000000 00000001
我们知道int型数据由4个字节即32bit位构成,此时最高位的1超出32位被去掉,留下
00000000 00000000 00000000 00000001,这正是1的三码,不得不说这个方法很巧妙。
此外,计算机的存储模式分为大端存储模式和小端存储模式。例如一个16进制的数据:0x12345678,在计算机中大都以78 56 34 12存储,这是小端存储模式,二进制低位在低端,高位在高端,你们可以在自己的编译器上试一下,如果有内存监视窗口。
#include<stdio.h>
int check()
{
int x = 1;
return (*(char*)&x);//先用char类型指针接收整型x的地址,在对其解引用,取得一字节地址的值
}//计算机数据排列模式
int main()
{
if (check() == 1)
printf("小端");
else
printf("大端");
return 0;
}
这里写了一段代码用于判断计算机的数据存储模式。若为0,则高位在低端,为大端存储,反之为小端存储。
三、整型提升
对于下面一段代码你们可以猜猜打印结果是什么(%u是无符号十进制输出)
#include<stdio.h>
int main()
{
char c_1= 128;
char c_2 = -128;
printf("%u\n%u\n", c_1,c_2);
return 0;
}
由下面的图可知char的范围,但char是一字节八位,而%u输出的是四字节32位的十进制整数,那么,在计算机里是怎样操作的呢。
10000000 -- 128的三码,其实就是-128,在signed char类型中无128,由图知进一位为-128,这里就涉及到整型提升这个概念,在计算机将其转化为32位是,取最高位即符号位(无符号数补0)的数往前补,直到将其补满。那么-128便变成下面的二进制码。
11111111 11111111 11111111 10000000 -- 整型提升后的二进制码
由于以无符号数打印,则该数无符号为正数,改码即为其三码,用windows自带的计算器,调至程序员可算出结果为4,294,967,168,一个相当大的数。走程序发现两次打印相同且结果相同都为4,294,967,168,大家可以动手试试。这还有一道题,结果是什么呢?
#include<stdio.h>
#include<string.h>
int main()
{
int i = 0;
char a[1000] = { 0 };
for ( i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d\n", strlen(a));
return 0;
}
strlen函数读取字符串长度,读取到‘\0’时停止,\0 的ASCII码值为0,即当读取到零时停止,由上图知从-1~-128,到-129时即为127,再从127~0停止,中间经过128+127=255个数,则结果为255。
你们可以自己检验一下。
四、数组的存储与越界访问
数组存储可由下面一段代码清楚明了的展示出来:
#include<stdio.h>
int main()
{
char a1[10] = { 0 };
int a2[10] = { 0 };
printf("%d\n%d\n", sizeof(a1), sizeof(a2));
for (int i = 0; i < 10; i++)
{
printf("%p %p\n", &a1[i], &a2[i]);//%p打印地址
}
return 0;
}
char类型逐1增加,int型逐4增加,sizeof计算数据所占空间大小,这里不包含‘\0’,可知数组内元素是连续存储的。
#include<stdio.h>
int main()
{
int i = 0;
int a[10] = { 0,1,2,3,4,5,6,7,8,9};
for (i = 0; i <= 12; i++)
{
a[i] = 0;
printf("ha\n");
}
return 0;
}
这段代码在某些编译器上可能无法通过(我用的VS2017就不行),在VS2013上这是一个死循环。我们知道,局部变量都存在栈区,先创建的放在栈上方,后创建的放在下方,当越界修改数组的值时,就可能修改先前创建的局部变量的值,在此处即a[12]的地址等于i的地址,从而i<=12恒成立陷入死循环。因此,在编写代码时,一定不要越界使用数组和指针。
五、指针类型的意义
这里简单的对指针类型的意义进行说明。
#include<stdio.h>
int main()
{
int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
char*p = (char*)a;
int*pa = a;
int**ppa =&pa;//二级指针,指针类型为int*
int***pppa = &ppa;//三级指针,指针类型为int**
*pa = 999;
printf("%d %d %d %d %d", a[0], *pa, **ppa, ***pppa,*p);
}
像int*pa=a,表明pa是指向a的int型指针,int**ppa=&pa说明ppa是指向pa的int*型指针。
指针的这种访问结构叫做链式结构,由上一个数据找到下一个数据,连成一串。
指针都占4或8个字节,不过指针类型决定了解引用时所访问的步长,如果用char型指针接收int类数据在解引用时就会造成上述整型提升中所遇到的问题,因此,指针类型具有重要意义,避免不同数据在接收时造成混乱,提高对空间的利用率。
六、浮点型数据的存储
IEEE754标准规定了浮点数的存储方式。
#include<stdio.h>
int main()
{
int n = 13;
float*p = (float*)&n;
printf("%d %f\n", n, *p);
*p = 13.0;//(-1)^0*1.101*2^3 ieee754标准,1位表符号为s,8位表指数,为e,余下23位存储m,
printf("%d %f", n, *p);
return 0;
}
像13以float形式存储时要在E位上加127,其M位为13减去E的值,其二进制码为:
0 (符号位S)10000010 (E位+127)10100000 00000000 0000000(M位由高位向低位写,值为原数-E)
当以十进制打印时,由其二进制补码知值为1,095,761,920.
13的补码为:00000000 00000000 00000000 00001101,在float类型下,改二进制码被理解为
:0 01111111 00000000 00000000 00001101,如此M位为很小的一个数,该值无限接近于零。所以打印结果为0.000000
总结
了解数据在计算机中是如何存储的对我们发现问题很有帮助,还请大家一起努力学习。