目录
2.3.练习(※)
3.1.一个例子(※)
1. 数据类型介绍
char // 字符数据类型short // 短整型int // 整形long // 长整型long long // 更长的整形float // 单精度浮点数double // 双精度浮点数
c99中引入了布尔类型: _Bool,其作用是存储真假值,如下图所示(图中true就是1,false就是0)(其实基本上用不到布尔类型,因为int类型完全能够解决,并且布尔类型本质上就是对int类型进行了重命名)
需要引用stdbool.h头文件
1.1.类型的基本归类
1.整形家族:
charunsigned char char到底是signed char还是unsigned char是取决于编译器实现的signed char vs编译器中char与signed char等价shortunsigned short [int] 无符号短整型signed short [int] 有符号短整型 short与signed short等价intunsigned int 无符号整型signed int 有符号整型 int与signed int等价longunsigned long [int] 无符号长整型signed long [int] 有符号长整型 long与signed long等价longlong
floatdouble
3.构造类型 /自定义类型
> 数组类型: 数组名去掉,其余的就是数组的类型( int a[10]类型是int [10] )> 结构体类型 struct> 枚举类型 enum> 联合类型 union
4.指针类型
int* pi ;char* pc ;float* pf ;void* pv ;
void 表示空类型(无类型)通常应用于函数的返回类型、函数的参数、指针类型
![](https://i-blog.csdnimg.cn/blog_migrate/83f31d977f64035995b21d091cabc089.png)
2.整形在内存中的存储
2.1.原码、反码、补码
2.1.1.原码、反码、补码介绍
整数的二进制表示有三种表示形式:原码、反码、补码内存中存储的是二进制的补码
整数分为正整数和负整数正整数:原码、反码、补码相同15的原码: 00000000 00000000 00000000 0000111115的反码: 00000000 00000000 00000000 0000111115的补码: 00000000 00000000 00000000 00001111负整数:原码、反码、补码需要计算,如下原码:按照一个数的正负直接写出来的二进制就是原码-15的原码:10000000 00000000 00000000 00001111反码:符号位不变,其他位按位取反就是反码-15的反码:11111111 11111111 11111111 11110000补码:反码的二进制序列加一就是补码-15的补码:11111111 11111111 11111111 11110001
2.1.2.内存中为什么存的是补码
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
计算机计算1-1,因为计算机中只有加法器,所以是1+(-1),下面我们尝试用原码和补码计算
2.2.大小端介绍
![](https://i-blog.csdnimg.cn/blog_migrate/26ff9826e304492cfbf9e29ec8cb25d6.png)
总结:我们发现内存中的数字总是倒着存的
2.2.1.大端和小端
int a = 0x11223344;
大端字节序存储:当一个数据的低字节数据存放在高地址处,高字节序的内容放在了低地址处,这种存储方式就是大端字节序存储 内存: 11 22 33 44
小端字节序存储:当一个数据的低字节数据存放在低地址处,高字节序的内容放在了高地址处,这种存储方式就是小端字节序存储 内存:44 33 22 11
2.2.2.为什么有大端和小端
为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在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处理器还可以由硬件来选择是大端模式还是小端模式。
2.2.3.设计一个小程序来判断当前机器的字节序
代码1:
![](https://i-blog.csdnimg.cn/blog_migrate/e1d40ad704a365ab44ffdb08fd84a080.png)
![](https://i-blog.csdnimg.cn/blog_migrate/8064f873280f00c570bd2b3a1959bd9f.png)
![](https://i-blog.csdnimg.cn/blog_migrate/1d792c9f656cfbce457b8b17796ed153.png)
2.3.练习
计算下面代码输出的结果
#include <stdio.h>
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;
}
答案:
注:
1. char a=-1 和 signed char b=-1 和 unsigned char c=-1
-1的原码:10000000000000000000000000000001
计算下面代码输出的结果
#include <stdio.h>
int main()
{
char a = -128;
printf("%u\n",a);
return 0;
}
答案
注:
1.char a = -128
-128的原码:10000000000000000000000010000000
计算下面代码输出的结果
#include <stdio.h>
int main()
{
char a = 128;
printf("%u\n",a);
return 0;
}
答案:
注:
1.char a = 128
128的原码:00000000000000000000000010000000
128的补码:00000000000000000000000010000000
计算下面代码输出的结果
#include <stdio.h>
int main()
{
int i = -20;
unsigned int j = 10;
printf("%d\n", i + j);
return 0;
}
答案:
注:
1.int i = -20
-20的原码:10000000000000000000000000010100
计算下面代码输出的结果
#include <stdio.h>
int main()
{
unsigned int i;
for (i = 9; i >= 0; i--) {
printf("%u\n", i);
}
return 0;
}
答案:死循环
注:
1.当i为0的时候,i--为-1
-1的补码:11111111111111111111111111111111
计算下面代码输出的结果
#include <stdio.h>
int main()
{
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d", strlen(a));
return 0;
}
答案:
![](https://i-blog.csdnimg.cn/blog_migrate/589e6cf9019dffe3909e83ee8598cba0.png)
注:
1.一个char类型的变量中到底能放什么数值
char类型是一个字节八个比特位,放的数据(都是补码)有下面的可能
![](https://i-blog.csdnimg.cn/blog_migrate/8bcffa5ad6399d4984119d3dcca46dcb.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d39728a7a7a4f012ff5ee9582618064e.png)
对于有符号char类型来说,补码转换为原码,数据范围如下所示(规定其中10000000系统默认解析为-128)
![](https://i-blog.csdnimg.cn/blog_migrate/481b7116269b5c357458ac5e1a5d40da.png)
总结:
![](https://i-blog.csdnimg.cn/blog_migrate/f532eae4591cfaa298e28f24f6df95be.png)
(2)与char同理,推演出其他类型数值范围
对于有符号char,其取值范围为 -128~127
对于有符号short,其取值范围为 -32768~32767 (-32768为1000000000000000直接解析得到)
![](https://i-blog.csdnimg.cn/blog_migrate/1df5d80cfd7c94b7e99f3a794c0a12e2.png)
2.因此char a[1000]里面存的数据应该是
-1 | -2 | -3 | -4 | -5 | ... | -128 | 127 | 126 | ... | 3 | 2 | 1 | 0 | -1 | -2 | ... |
0 | 1 | 2 | 3 | 4 | ... | 127 | 128 | 129 | ... | 252 | 253 | 254 | 255 | 256 | 257 | ... |
3.求字符串的时候到'\0'截至,统计'\0'之前数值的个数,其实'\0'的ASCII码值为0,所以到0截止,由上表知0的前面有255个数。
7.练习七
计算下面代码输出的结果
#include <stdio.h>
unsigned char i = 0;
int main()
{
for (i = 0; i <= 255; i++)
{
printf("hello world\n");
}
return 0;
}
答案:死循环
注:无符号char,其取值范围为 0~255,到255时下一个值为0,永远无法达到循环截止条件,因此死循环
3. 浮点型在内存中的存储
常见的浮点数:3.141591E10(意思是1.0)
浮点数家族包括: float、double、long double 类型浮点数表示的范围:float.h中定义,如下图所示
3.1.一个例子(※)
int main()
{
int n = 9;
float *pFloat = (float *)&n;
printf("n的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
*pFloat = 9.0;
printf("num的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
return 0;
}
运行结果:
注:
1.%f和%lf都会默认小数点后有6位
2.我们发现用浮点型指针进行存储n的地址,解引用出来的值不是9或9.0,我们可以推断出浮点型和整型在内存中的存储方式是有区别的。
3.上面代码中*pFloat是站在浮点数的视角进行解引用的
当n=9时
n的原码:00000000000000000000000000001001
n的补码:00000000000000000000000000001001
0 | 00000000 | 00000000000000000001001 |
S | E | M |
E为全零,取出来的值是一个非常接近0的数字,因此打印出来是0.000000
当n=9.0时
十进制9.0转换为二进制为1001.0转换为科学计数为:*1.001*
,S=1,M=1.001,E=3
0 | 3+127=130的二进制:10000010 | 001的二进制:00100000000000000000000 |
S | E | M |
因此此时的n为:0100 0001 0001 0000 0000 0000 0000 0000
n的十六进制为 4 1 1 0 0 0 0 0 即0x41100000
由上图知,浮点数存储也符合小段存储
将内存中n的值01000001000100000000000000000000用%d取出来,此时原反补相同,取出来的值为1091567616
3.2.浮点数存储规则
根据国际标准 IEEE (电气和电子工程协会) 754 ,任意一个二进制浮点数 V 可以表示成下面的形式:(-1)^S * M * 2^E
(-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。
M表示有效数字,大于等于1,小于2。
2^E表示指数位。
数值 | 1 | 1 | 1 | . | 1 | 1 | 1 |
权重 | 2^2 | 2^1 | 2^0 | . | 2^(-1)=0.5 | 2^(-2)=0.25 | 2^(-3)=0.125 |
例如:十进制的5.5转换成二进制为101.1,101.1可以写成*1.011*
(S=0,M=1.011,E=2)
![](https://i-blog.csdnimg.cn/blog_migrate/3e3888fb470235bbeda64bc3d34b603f.png)
最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。
![](https://i-blog.csdnimg.cn/blog_migrate/25f5de2d0e5ec47133791117c462ef71.png)
最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
前面说过, 1≤M<2 ,也就是说, M 可以写成 1.xxxxxx 的形式,其中 xxxxxx 表示小数部分。IEEE 754 规定,在计算机内部保存 M 时,默认这个数的第一位总是 1 ,因此可以被舍去,只保存后面的xxxxxx部分。比如保存 1.01 的时候,只保存01 ,等到读取的时候,再把第一位的 1 加上去。这样做的目的,是节省 1 位有效数字。以 32 位浮点数为例,留给M 只有 23 位,将第一位的1 舍去以后,等于可以保存 24 位有效数字。
首先, E 为一个无符号整数( unsigned int )这意味着,如果 E 为 8 位,它的取值范围为 0~255 ;如果 E 为 11 位,它的取值范围为 0~2047 。但是,我们知道,科学计数法中的E 是可以出现负数的比如:十进制0.5转换成二进制为0.1,0.1转换成科学计数为:*
![]()
所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。
比如:十进制0.5的E的真实值为-1,而存入E中时要存入的是真实值+中间数(float:-1+127=126 double:-1+1023=1022)
浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1
浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)