本章的重点
1.数据类型详细介绍
2.整形在内存中的存储:原码、反码、补码
3.大小端字节序介绍及判断
4.浮点型在内存中的存储解析
1.数据类型介绍
前面我们已经学习了基本的内置类型(C语言本身就具有的类型):
char //字符数据类型
short //短整型
int //整形
long //长整型
long long //更长的整形(C99中才引入的)
float //单精度浮点数
double //双精度浮点数
//C语言有没有字符串类型
以及他们所存储空间的大小
sizeof(long)>=sizeof(int)
类型的意义:
- 1.使用这个类型开辟内存空间的大小(大小决定了使用范围)
- 2.如何看待内存空间的视角
1.1类型的基本归类:
整形家族:
char
unsigned char
signed char
//字符的本质是在内存中存储的是ASCII码值,是整形,所以划分到整形家族中
//char是unsigned char还是signed char标准是未定义的,这取决于编译器的实现
//多多数编译器都是signed char
short
unsigned short [ int ]
signed short [ int ]
int
unsigned int
signed int
//平时写int a;实际这里int就是signed int
long
unsigned long [ int ]
signed long [ int ]
long long
unsigned long long [ int ]
signed long [ int ]
int a=10;
a是一个整型,有符号的整型,一个整型是4个字节==32bit
00000000 00000000 00000000 00000000
高位是符号位(第一位):符号位是0,表示正数;符号位是1,表示负数
浮点型家族:只要表示小数就可以使用浮点数
float
double
//float的精度低,存储的数值范围较小,double的精度高,存储的数据的范围更大
构造类型:自定义类型-我们可以自己创建出新的类型
数组类型
结构体类型 struct
枚举类型 enum
联合类型 union
指针类型:
int *pi;
char *pc;
float* pf;
void* pv;
空类型:
void表示空类型(无类型)
通常应用于函数的返回类型、函数的参数、指针类型
明确了不需要传参数,但是还是传参数也能正常运行
2.整形在内存中的存储
变量的创建是要在内存中开辟空间的,空间的大小是根据不同的类型而决定的
那么数据在所开辟内存中是如何存储的?
比如:
int a=20;
int =-10;
2.1原码、反码、补码
计算机中的整数有三种二进制表示方法,即原码、反码和补码。
三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位
1.正的整数,原码、反码、补码相同
2.负的整数,原码、反码、补码是需要计算的
原码:直接通过正负的形式写出的二进制序列就是原码
反码:原码的符号位不变,其他位按位取反得到的就是反码
补码:反码+1得到补码
补码到原码-->2种方式
1.-1\取反得到原码
2.补码取反\+1得到原码
数值有不同的表示形式:
2进制
8进制
10进制
16进制
比如,十进制的21用其他进制表示
二进制:0b10101
八进制:025
十进制:21
十六进制:0x15
内存本质上存的还是二进制,通过内存窗口观察不方便,所以用十六进制表示
所以整形在内存中存放的是补码的二进制序列
存放补码的原因:
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域同一处理;同时,加法和减法也可以同一处理(cpu只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
一个十六进制位等于四个二进制位,两个十六进制位就是八个二进制位(一个字节)
- 内存中存放的是补码
- 整形表达式计算使用的是内存中补码计算的
- 打印和我们看到的都是原码
2.2大小端介绍
什么是大端、小端
大端(存储)模式,是指数据的地位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中。
以字节为单位来讨论顺序的,两个十六进制为表示一个字节
大端字节序存储:
把数据的高位字节序的内容存放在低地址处,把低位字节序的内容放在高地址处,就是大端字节序存储
小端字节序存储:
把数据的高位字节序的内容存放在高地址处,把低位字节序的内容放在低地址处,就是小端字节序存储
如何判断当前编译器是大端还是小端?
我们常用的X86结构是小端模式
大小端是以字节位单位的
大小端字节序指的是数据在电鸟上存储的字节顺序
2.3练习
1.
#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;
}
char a=-1;
a是整数,要放到char类型中要发生截断,内存中是放补码的
-1
10000000 0000000 00000000 000000001--原码
111111111 11111111 111111111 1111111110--反码
111111111 11111111 111111111 1111111111--补码
111111111--截断
%d是打印的有符号的整形
所以char a要发生整型提升,char a是有符号的的char,a的最高位看成是符号位,整型提升是按符号位来提升,所以a在提升的过程中,高位补1
111111111 11111111 111111111 1111111111--整型提升之后,这个依然是内存中的补码,%d打印的是原码
10000000 0000000 00000000 000000001--原码
所以打印a是-1
signed char b=-1;
a和b是一样的,打印的也是-1;
unsigned char c=-1;
10000000 0000000 00000000 000000001--原码
111111111 11111111 111111111 1111111110--反码
111111111 11111111 111111111 1111111111--补码
111111111--截断
%d打印整形,所以c要发生整型提升,因为c是unsigned char没有符号位,高位直接补0;
00000000 0000000 00000000 1111111111--补码
%d打印的是有符号数,因为这个补码的符号位是0,所以是正数,而整数的原、反、补相同
00000000 0000000 00000000 1111111111--原码
所以打印c是255
截断后,打印的时候需要整形提升,整型提升的时候需要看这个数类型是有符号数还是无符号数,无符号数整型提升高位补0,有符号数看是负数还是正数,负数高位补1,正数高位补0,并看打印的时候是以%u打印还是以%d的形式打印,如果是以%d的形式打印,还需将补码转换为原码,如果是以%u的形式进行打印,直接打印即可,不需要将补码转换为原码
2.
#include<stdio.h>
int main()
{
char a=-128;
printf("%u\n",a);
return 0;
}
%u-->打印无符号整形,认为内存中存放的补码对应的是一个无符号数
%d-->打印有符号整形,认为内存种存放的补码对应的是一个有符号数
10000000 00000000 00000000 10000000--原码
11111111 11111111 11111111 01111111 --反码
11111111 11111111 11111111 1000000 --补码
10000000--截断
11111111 11111111 11111111 10000000--整形提升
先整型提升然后再看是以无符号数打印
要打印无符号数,对于无符号数没有符号位的概念,所以整型提升之后直接打印
这道题若是printf("%d\n",a);//打印有符号的整形
11111111 11111111 11111111 10000000--整形提升(补码)
//4,294,967,168
3.
#include<stdio.h>
int main()
{
char a=128;
printf("%u\n",a);
return 0;
}
00000000 0000000 00000000 10000000--原码
10000000--截断
11111111 11111111 11111111 10000000-整形提升
//4294967168
4.
#include<stdio.h>
int main()
{
int i=-20;
unsigned int j=10;
printf("%d\n",i+j);
return 0;
//按照补码的形式进行运算,最后格式化成为有符号的整数
}
-20
10000000 00000000 00000000 00010100--原码
11111111 11111111 11111111 11101011--反码
11111111 11111111 11111111 11101100--补码
10
00000000 00000000 00000000 00001010
-20+10
01111111 11111111 11111111 11101100
00000000 00000000 00000000 00001010
11111111 11111111 11111111 11110110(补码)
10000000 00000000 00000000 00001001(反码)
10000000 00000000 00000000 00001010(原码)
//-10
5.
#include<stdio.h>
int main()
{
unsigned int i;
for(i=9;i>=0;i--)
{
printf("%u\n",i);
}
}
后面开始死循环
i是无符号数,当i一直减小到我们认为的负数的时候,第一位不是符号位,由于是无符号数,会被当成无穷大的数
整形家族的类型的取值范围:limits.h
浮点型家族类型的取值范围:float.h