一、数据类型介绍
前面我们已经学习了基本的内置类型以及他们所占存储空间的大小。
数据类型分类 | 数据类型分类 |
---|---|
char | 字符数据类型 |
short | 短整型 |
int | 整型 |
long | 长整型 |
long long | 更长的整型 |
float | 单精度浮点型 |
double | 双精度浮点型 |
类型的意义:
- 使用这个类型开辟内存空间的大小(大小决定了使用范围)。
- 如何看待内存空间的视角
二、类型的基本归类
整型家族
整型家族 | 整型家族 |
---|---|
char | unsigned char |
signed char | |
short | unsigned short[int] |
signed short[int] | |
int | unsigned int |
signed int | |
long | unsigned long[int] |
signed long[int] |
浮点型家族
浮点数家族 |
---|
float |
double |
构造类型
也称自定义类型
构造类型 | 构造类型 |
---|---|
数组类型 | |
结构体类型 | struct |
枚举类型 | enum |
联合类型 | union |
int main()
{
int arr[10];//int [10]
int arr2[5];//int [5]
return 0;
}
arr数组的类型是int[10],arr2数组的类型是int[5],同样是整型数组,但两个类型不一样,数组有几个元素可以自己定义,数组类型是什么也可以自己定义。
指针类型
指针类型 |
---|
int *pi |
char *pc |
float *pf |
void *pv |
空类型
void 表示空类型(无类型)
通常应用于函数的返回类型、函数的参数、指针类型。
三、整型在内存中的存储:原码、反码、补码
一个变量的创建是要在内存中开辟空间的。空间的大小是根据不同的类型而决定的。
那接下来我们谈谈数据在所开辟内存中到底是如何存储的?
我们先来了解一下下面的概念:原码、反码和补码。
数据在内存中以二进制的形式存储。
计算机中的有符号数有三种表示方法,即原码、反码和补码。
三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位三种表示方法各不相同。
原码:直接将二进制按照正负数的形式翻译成二进制就可以。
反码:将原码的符号位不变,其他位依次按位取反就可以得到了。
补码:反码+1就得到补码。
正数的原、反、补码都相同。
对于整型来说:数据存放内存中其实存放的是补码。
为什么整数在内存中存储的是补码而不是原码?
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理; 同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
下面我们用一个例子来验证一下。
int main()
{
int a = -10;
return 0;
}
运行结果如下:
我们来分析一下为什么-10在内存中是这样存储的。-10的原码、反码和补码如下。
原码:10000000000000000000000000001010
反码:11111111111111111111111111110101
补码:11111111111111111111111111110110
FFFFFFF6
我们可以发现数据在内存中是以补码的形式存储的。
我们再来看一个例子。以1-1为例。
CPU上没有减法器,只有加法器,用加法来模拟减法。
1-1
1+(-1)
1的原码: 00000000000000000000000000000001
-1的原码:10000000000000000000000000000001
原码相加:10000000000000000000000000000010
原码直接相加是-2
1的补码: 00000000000000000000000000000001
-1的原码:10000000000000000000000000000001
-1的反码:11111111111111111111111111111110
-1的补码:11111111111111111111111111111111
补码相加:100000000000000000000000000000000
多了一位,只能存32位,1被舍弃,最后结果是0
这个例子也可以看出整数在内存中存储的是补码。
四、大小端字节序
大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中。
小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中。
为什么会有大小端模式之分呢?
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为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处理器还可以由硬件来选择是大端模式还是小端模式。
0x11是高位,0x44是低位,地址由低到高增加,0x44存放在低地址,0x11存放在高地址,所以当前编译器是小端字节序存储模式。
百度2015年系统工程师笔试题:
请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。(10分)
最简单的方法就是以1为例。1的大小端存储不同,我们只需要比较第一个字节,就可以判断是大端还是小端。
int main()
{
int a = 1;
char* p = (char*)&a;
if (*p == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
运行结果如下:
可知当前的编译器是小端存储模式。
我们也可以把这个判断程序写成一个函数。
int check_sys()
{
int a = 1;
char* p = (char*)&a;//int*
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;//int*
return *p;//返回1表示小端,返回0表示大端
}
int main()
{
//写代码判断当前机器的字节序
int ret = check_sys();
if (ret == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
五、练习题
1、
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;
}
运行结果如下:
分析:
原码:10000000000000000000000000000001
反码:11111111111111111111111111111110
补码:11111111111111111111111111111111
存到a中的数为11111111
存到b中的数为11111111
存到c中的数为11111111
以整型的形式打印,对内存中的补码要发生整型提升,打印出来的是原码,需要把整型提升后的补码转换成原码。有符号的数整型提升高位补1,无符号数整型提升高位补0。看以什么形式打印,判断高位01是不是符号位,如果是符号位,补码高位是0,证明是正数,原反补相同。补码高位是1,证明是负数,求出原码。
a整型提升后:11111111111111111111111111111111
转换成原码:10000000000000000000000000000001
b整型提升后:11111111111111111111111111111111
转换成原码: 10000000000000000000000000000001
c整型提升后:00000000000000000000000011111111
转换成原码: 00000000000000000000000011111111
注意:
char 到底是signed char 还是unsigned char ,C语言标准并没有规定,取决于编译器
int 是 signed int ,short 是 signed short
2、
int main()
{
char a = -128;
printf("%u\n", a);
return 0;
}
运行结果如下:
分析:
原码:10000000000000000000000010000000
反码:11111111111111111111111101111111
补码:11111111111111111111111110000000
a里面存放的是:10000000
char是有符号的,整型提升为:11111111111111111111111110000000
以无符号整型打印,补码最高位的1不是符号位,所以它是个正数,原码跟补码一样
3、
int main()
{
char a = 128;
printf("%u\n", a);
return 0;
}
运行结果如下:
内存中存放的char类型的变量所有的可能性 。
上面是正数,下面是负数,存在内存中的是补码,正数原反补相同,下面的负数转换成原码如上图所示,10000000不能在减1了,10000000直接被解析成-128,我们也可以推一下,如上图所示。有符号的char的取值范围是:-128-127。
4、
int main()
{
int i = -20;
unsigned int j = 10;
printf("%d\n", i+j);
return 0;
}
运行结果如下:
分析:
-20
原码:10000000000000000000000000010100
反码:11111111111111111111111111101011
补码:11111111111111111111111111101100
10
原码:00000000000000000000000000001010
反码:00000000000000000000000000001010
补码:00000000000000000000000000001010
两补码相加:11111111111111111111111111110110
相加后的反码:11111111111111111111111111110101
相加后的原码:10000000000000000000000000001010
5、
int main()
{
unsigned int i;
for (i = 9; i >= 0; i--)
{
printf("%u\n", i);
}
return 0;
}
运行结果如下:
死循环
i是unsigned int 类型,无符号整型大于等于0,i>=0条件恒成立,所以会死循环。
6、
int main()
{
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d\n", strlen(a));//找到\0 -> 0
return 0;
}
运行结果如下:
-1 -2 -3 …-127 -128 127 126 125…3 2 1 0 -1 -2 …-127 -128 127
求strlen(a)找\0,就是找0
128+127=255
有符号的char范围为-128-127,无符号的char范围为0-255
7、
unsigned char i = 0;
int main()
{
for (i = 0; i <= 255; i++)
{
printf("hello world\n");
}
return 0;
}
运行结果如下:
死循环
无符号的char取值范围是0-255,判断条件恒成立,所以会死循环。