C语言的基础知识我们已经经过了简单的了解,现在我们开启C语言中进阶的部分。
????????在之前的文章中我们简单的提及过Debug和Release两种编译方式在编译时的区别。
现在我们在使用上次的一个例子来进行说明:
#include <stdio.h>
int main(void)
{
int i = 0;
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
for ( i = 0; i <= 12; i++)
{
arr[i] = 0;
printf("hehe\n");
}
return 0;
}
????????这段代码在VS2017编译器下在Debug版本下执行的结果是死循环,而在Release版本下执行的结果是有限值,这种现象我们同样可以通过打印地址的方式来发现其中的原因。
int i = 0;
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%p\n", &i);
printf("%p\n", &arr[0]);
//在Debug版本下运行的结果
//00EFF760
//00EFF730
//在Release版本下运行的结果
//0056F730
//0056F734
从上面的运行结果结果中我们就可以发现:在Debug版本下数组的首地址比变量i的地址小,而在Release版本下数组的首地址比变量的地址大。这就说明了Release版本会对我们的代码进行优化
数据在内存中的存储
数据类型的详细介绍
基本的内置类型:
char? (1)? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//字符数据类型
short? (2)? ? ? ? ? ? ? ? ? ? ? ? ? ? //短整型
int? (4)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //整型
long? (4/8)? ? ? ? ? ? ? ? ? ? ? ? ? //长整型
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]
long long ????????unsigned long long[int] ????????signed long long[int]
注:
? ? ? ? 1.字符的本质是ASCII码,是整型划分到整型家族;char是unsigned char还是signed char的标准未定义,取决于编译器的实现
? ? ? ? 2.int a;——>signed int a;
浮点型家族
float
double
只要是表示小数就可以使用浮点类型,float的精度低,存储的数值范围较小,double类型的精度高,存储的数据的范围更大。
构造类型????????自定义类型 - 我们可以自己创建出新的类型
数组类型 int[ ] 结构体类型 struct 枚举类型 enum 联合类型 union
指针类型
int *pi; char *pc; float* pf; void* pv;
空类型
void 表示空类型(无类型) 通常应用于函数的返回类型、函数的参数、指针类型
//第一个void 表示函数不会返回值
//第二个void 表示函数不需要传入任何参数
void test(void)
{
printf("hhh");
}
int main(void)
{
test();
return 0;
}
整型在内存中的存储
一个变量的创建是要在内存中开辟空间的,空间的大小根据不同的类型而决定。
例如:int a = 20;????????int b = -10;????????a就是占据了四个字节的空间。
原码、反码、补码
计算机中的整数有三种表示方法,原码、反码、补码。
这三种表示方法都有符号位与数值位,符号位使用0表示正,使用1代表负。
对于正数而言,原码、反码、补码都相同。
对于负数:
原码:直接将二进制按照正负数的形式翻译成二进制
反码:源码的符号位不变,其它位依次取反
补码:反码的二进制数 + 1
int a = 20;
//00000000 00000000 00000000 00010100
//00000000 00000000 00000000 00010100
//00000000 00000000 00000000 00010100 //0x 00 00 00 14
int b = -10;
//10000000 00000000 00000000 00001010 - 原码
//11111111 11111111 11111111 11110101 - 反码
//11111111 11111111 11111111 11110110 - 补码 //0x ff ff ff f6
?
?????????通过代码的实现我们可以得到在计算机的内存中存放的补码。
对于整型来说,数据在内存中存放的是补码。
原因:
????????在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
在计算机中计算1-1时,是看做1+(-1)的运算
00000000 00000000 00000000 00000001
10000000 00000000 00000000 00000001 - 原码
11111111 11111111 11111111 11111110 - 反码
11111111 11111111 11111111 11111111 - 补码
结果为:
1 00000000 00000000 00000000 00000000
由于只有32位,高位丢失,因此结果为:
00000000 00000000 00000000 00000000
补码 = 原码取反 + 1??;原码? = 补码取反 +1? ? ? ? ?补码和原码可以互相转化
在分析调试结果后,得到了在内存中存储的是补码,但是同样可以看到补码以16进制存储的顺序感觉怪怪的,下面来介绍一下其中的原因。
大小端字节序的介绍及判断
什么是大小端:
大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中; 小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中。
为什么会有大小端:
为什么会有大小端模式之分呢?
????????这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就 导致了大端存储模式和小端存储模式。 ????????例如:一个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为高字节, 0x22 为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中,0x22 放在高地址中,即 0x0011 中。小端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。
分享一道有关的题目:请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。
//这是我自己编写的简单的方法,通过判断内存中地址前进的步长来判断是哪一种存储模式
int main(void)
{
int a = 1;
char* p = &a;
printf("%p\n", p);
printf("%p\n", p + 1);
printf("%d\n", *p);
printf("%d\n", *(p + 1));
return 0;
}
//这个是优化完的代码
#include <stdio.h>
int check_sys()
{
int i = 1;
return (*(char *)&i);
}
int main()
{
int ret = check_sys();
if (ret == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
?一些跟数据存储相关的练习题
1.
#include <stdio.h>
int main()
{
char a= -1;
//10000000 00000000 00000000 00000001 - 原码
//11111111 11111111 11111111 11111110 - 反码
//11111111 11111111 11111111 11111111 - 补码
//11111111 截断 - a
//printf("a=%d",a);
//有符号数
//11111111 11111111 11111111 11111111 整形提升 - 补码
//10000000 00000000 00000000 00000000 - 反码
//10000000 00000000 00000000 00000001 - 原码
//-1
signed char b=-1;
//10000000 00000000 00000000 00000001 - 原码
//11111111 11111111 11111111 11111110 - 反码
//11111111 11111111 11111111 11111111 - 补码
//11111111 截断 - b
//printf("b=%d",b);
//有符号数
//11111111 11111111 11111111 11111111 整形提升 - 补码
//10000000 00000000 00000000 00000000 - 反码
//10000000 00000000 00000000 00000001 - 原码
//-1
unsigned char c=-1;
//10000000 00000000 00000000 00000001 - 原码
//11111111 11111111 11111111 11111110 - 反码
//11111111 11111111 11111111 11111111 - 补码
//11111111 截断 - c
//无符号数
//00000000 00000000 00000000 11111111 整形提升 - 补码
// %d 打印有符号的整形 打印的是原码
printf("a=%d,b=%d,c=%d",a,b,c);//a = -1;b = -1;c = 255
return 0;
}
2.
#include <stdio.h>
int main()
{
char a = -128;
//一个有符号的char类型的范围时-128~127
//1000000 00000000 00000000 10000000 - 原码
//1111111 11111111 11111111 01111111 - 反码
//1111111 11111111 11111111 10000000 - 补码
//1000000 - 截断
//1111111 11111111 11111111 10000000 - 整形提升 - 补码
printf("%u\n",a);//打印的结果为无符号数 - 4294967168
return 0;
}
3.
//结果与2一致
#include <stdio.h>
int main()
{
char a = 128;
printf("%u\n",a);
return 0;
}
4.
int i= -20;
//10000000 00000000 00000000 00010100 - 原码
//11111111 11111111 11111111 11101011
//11111111 11111111 11111111 11101100 - 补码
unsigned int j = 10;
//00000000 00000000 00000000 00001010
//11111111 11111111 11111111 11101100
//00000000 00000000 00000000 00001010
//11111111 11111111 11111111 11110110 i + j - 补码
//11111111 11111111 11111111 11110110 i + j - 补码
//10000000 00000000 00000000 00001001
//10000000 00000000 00000000 00001010 - 输出结果 - 原码
printf("%d\n", i+j);
5.
int main(void)
{
unsigned int i;
for (i = 9; i >= 0; i--)
{
printf("%u\n", i);
}
return 0;
}
//程序当i的值到0之前的数值都是正常打印9 8 7 6 5 4 3 2 1 0
//当达到0时,进行下一步操作 0-1 ,我们将其看做 0 + (-1)
//结果为:
//00000000 00000000 00000000 00000000 - 0
//10000000 00000000 00000000 00000001 - -1 - 原码
//11111111 11111111 11111111 11111110
//11111111 11111111 11111111 11111111 - 补码
//00000000 00000000 00000000 00000000 - 0
//11111111 11111111 11111111 11111111 - 补码
//11111111 11111111 11111111 11111111 - 结果 - 打印无符号数
//4294967295
int main()
{
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d", strlen(a));
return 0;
}
//stelen函数当遇到'\0'时会停止统计字符串的长度
//char类型的数据的范围是[-128~127]
//因此我们可以猜测a的长度应该为255
-128 + (-1)
//10000000 00000000 00000000 10000000 - 原码
//11111111 11111111 11111111 01111111
//11111111 11111111 11111111 10000000 - 补码
//10000000 截断 - a[i]
//11111111 11111111 11111111 10000000 整形提升
//11111111 11111111 11111111 11111111 - -1 - 补码
//11111111 11111111 11111111 10000000 - 补码 - -128
//11111111 11111111 11111111 11111111 - 补码 - -1
//00000000 00000000 00000000 01111111 - 补码 - 计算结果
//01111111 - a[i] - 截断
//00000000 00000000 00000000 01111111 - 补码 - 整形提升
//01111111 11111111 11111111 10000000
//00000000 00000000 00000000 01111111 - 原码 - 127
7.
#include <stdio.h>
unsigned char i = 0;
int main()
{
for(i = 0;i<=255;i++)
{
printf("hello world\n");
}
return 0;
}
//255 + 1
//00000000 00000000 00000000 11111111 - 255
//00000000 00000000 00000000 00000001 - 1
//00000000 00000000 00000001 00000000
//00000000 - 截断
//00000000 00000000 00000000 00000000 - 整形提升
关于浮点型在内存中的存储相关的内容,将在最近一段时间内继续补充完成。
|