- 数据类型介绍
- 内置数据类型的归类
- 整型在内存中的存储:①原码、反码、补码 ②大小端字节序 ③char的存储内容
- 浮点型在内存中的存储
自学b站“鹏哥C语言”笔记。
一、数据类型介绍
1.C语言类型
- 内置类型
- 构造类型(自定义类型)
2.内置类型
char | 字符数据类型 | 1字节 |
short | 短整型 | 2字节 |
int | 整型 | 4字节 |
long | 长整型 | 4/8字节 |
long long | 更长的整型 | 8字节 |
float | 单精度浮点数 | 4字节 |
double | 双精度浮点数 | 8字节 |
3.构造类型(自定义类型)
详见文章【数组】 | 数组类型 |
struct | 结构体类型 |
enum | 枚举类型 |
union | 联合类型 |
4.类型的意义
- 类型决定了开辟内存空间的大小
- 类型决定了看待内存空间的视角(不同存储的内容不同)
二、内置数据类型的归类
1.整型
| unsigned char signed char |
| unsigned short (int) signed short (int) |
| unsigned int signed int |
| unsigned long (int) signed long (int) |
unsigned:无符号
signed:有符号,首位是符号位,“正0负1”
无符号比有符号存储的范围大。
2.浮点型
- float
- double
3.指针类型
- int*
- char*
- float*
- void*
4.空类型
- void
通常运用于函数的返回类型(无返回)、函数的参数(无返回)、指针类型。
三、整型在内存中的存储
1.原码、反码、补码
整型有三种表示方式:原码、反码、补码。
(1)有符号整型
三种表示方式均由符号位和数值位组成。符号位是最高位。
正数
原码:符号位“正0负1”,数值位直接将数值转换为二进制
反码:和原码完全相同
补码:和原码完全相同
int main()
{
int a = 20;//4个字节 - 32bit
//原码:00000000000000000000000000010100
//反码:00000000000000000000000000010100
//补码:00000000000000000000000000010100
//十六进制表示(每四位换成一位):0x00000014
return 0;
}
负数
原码:符号位“正0负1”,数值位直接将数值转换为二进制
反码:符号位“正0负1”,其他位是原码按位取反
补码:符号位“正0负1”,其他位是反码+1
原码 → 符号位不变,其它位取反 → 反码 → +1 → 补码
补码 → -1 → 反码 → 符号位不变,其它位取反 → 原码
int main()
{
int b = -10;//4个字节 - 32bit
//原码:10000000000000000000000000001010
//反码:11111111111111111111111111110101
//补码:11111111111111111111111111110110
return 0;
}
(2)无符号整型
无符号位,即所有位都是数值位。
原码:直接将数值转换为二进制
反码:和原码完全相同
补码:和原码完全相同
(3)整型的存储:补码
只要是整型,内存中存储的都是二进制补码。
原因:
- 使用补码可以将符号位和数值位统一处理
- 加法和减法也可以统一处理(CPU只有加法器)
int main()
{
1 - 1;
//转换为1 + (-1)
// 1 原码:00000000000000000000000000000001
//-1 原码:10000000000000000000000000000001
//原码相加:10000000000000000000000000000010,是错误的
// 1 补码:00000000000000000000000000000001
//-1 反码:11111111111111111111111111111110
//-1 补码:11111111111111111111111111111111
//补码相加:100000000000000000000000000000000,整型只有32位,故最高位的1被舍去,答案正确
return 0;
}
2.大小端字节序
- 大端字节序(存储)模式:指数据的低位保存在内存的高地址中。
- 小端字节序(存储)模式:指数据的低位保存在内存的低地址中。
注意:描述的是字节存放的顺序,而不是二进制位存放的顺序。(8个二进制位为一组的意)
例1:
int a = 20;
//0x00 00 00 14
小端:14 00 00 00 |
低地址 ——————————————> 高地址 |
大端:00 00 00 14 |
例2:用代码实现判断本机器的字节序式是大端还是小端
int main()
{
int a = 1;
char* p = (char*)&a;
if(*p == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
例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 -1 255
解析:
char a=-1;
char默认有符号位。
虽然是给char赋值,但-1自身是整型,所以应先找出-1的补码,再取最低8位给char a,即
-1原码10000000000000000000000000000001
-1反码11111111111111111111111111111110
-1补码11111111111111111111111111111111
a补码11111111
第7行代码打印整型(%d),会发生整型提升,高位补符号位(1),则
a补码111111111111111111111111111111111
a反码111111111111111111111111111111110
a原码100000000000000000000000000000001,即-1
signed char b=-1;
与a的分析相同,也是-1
unsigned char c=-1;
与a的分析前半部分相同
a补码也是11111111,
第7行代码打印整型(%d),会发生整型提升,无符号则高位补0,则
a补码000000000000000000000000011111111,又由于无符号位,则a的原码、补码、反码相同
a原码000000000000000000000000011111111,即255
例4(易错):
#include <stdio.h>
int main()
{
char a = -128;
printf("%u\n", a);
return 0;
}
输出结果:4294967168
解析:
char默认有符号位。
虽然是给char赋值,但-128自身是整型,所以应先找出-128的补码,再取最低8位给char a,即
-128原码10000000000000000000000010000000
-128反码11111111111111111111111101111111
-128补码11111111111111111111111110000000
a补码100000000
第5行代码打印十进制无符号整型(%u),会发生整型提升,再以无符号形式输出。
提升时看的是“自身”。a自身是有符号数,则整型提升时补符号位
a补码11111111111111111111111111110000000
打印时是以打印的类型角度看补码。%u是无符号,则认为a补码是无符号,那么原码、反码、补码相同,直接打印出a补码的十进制值,即4294967168
例5:
#include <stdio.h>
int main()
{
int i = -20;
unsigned int j = 10;
printf("%d\n", i+j);
return 0;
}
输出结果:-10
解析:
int i = -20;
-20原码10000000000000000000000000010100
-20反码11111111111111111111111111101011
-20补码11111111111111111111111111101100
unsigned int j = 10;
10原码00000000000000000000000000001010
10补码00000000000000000000000000001010
i+j
i+j补码11111111111111111111111111110110
打印输出十进制有符号整型(%d)
i+j反码11111111111111111111111111110101
i+j原码10000000000000000000000000001010,即-10
例6(易错):
#include <stdio.h>
int main()
{
unsigned int i;
for(i=9; i>=0;i--)
{
printf("%u\n", i);
}
return 0;
}
输出结果:9 8 7 6 5 4 3 2 1 0 4294967295 4294967294 4294967293(一直-1死循环)
解析:
当i=-1时,-1原码10000000 00000000 00000000 00000001
-1反码11111111 11111111 11111111 11111110
-1补码11111111 11111111 11111111 11111111
由于i是无符号整数型,则i原码和补码相同11111111 11111111 11111111 11111111
输出时看成无符号数,则值为2^32-1,即4294967295
3.char的存储内容
- char
- signed char
- unsigned char
都是占1个字节,即8个bit位。易知,可以存储256(2^8)种补码。
有符号数(原码) | 补码 | 无符号数(原码) |
0 1 2 3 …… 127 | 00000000 00000001 00000010 00000011 …… 01111111 | 0 1 2 3 …… 127 |
-128(不计算直接赋值) -127 …… -3 -2 -1 | 10000000 10000001 …… 11111101 11111110 11111111 | 128 129 …… 253 254 255 |
总结:
- 有符号char:-128至127
- 无符号char:0至255
注意:char的数值可以理解为一个圈,
- 如有符号char中,128=127+1等价于-128,-129=-128-1等价于127。
- 无符号char中,256=255+1等价于0。
例1(易错):
#include <stdio.h>
int main()
{
char a = -128;
printf("%u\n", a);
return 0;
}
#include <stdio.h>
int main()
{
char a = 128;
printf("%u\n", a);
return 0;
}
两段代码的结果是一样的,因为char不能存储128,其实等价于127+1,即-128。
例2(难题):
#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;
}
输出结果:255
解析:
strlen函数计算的是参数的长度,在'\0'前停止累计,'\0'的ASCII码值为0。
问题转换为何时出现0。
表面上经过for循环后,a[0]=-1,a[1]=-2,a[2]=-3,……,a[999]=-1000
实际上,数组a[1000]内的元素是char类型的,数据范围只能是-128至127
那么,a[128]=-129=-128-1等价于127,以此类推,a[255]=-256=-128-128等价于0
例3:
#include <stdio.h>
unsigned char i = 0;
int main()
{
for(i=0; i<=255; i++)
{
printf("hello world\n");
}
return 0;
}
输出结果:死循环
解析:
因为i是无符号char类型,i<=255恒成立。
四、浮点型在内存中的存储
注:科学计数法
#include <stdio.h>
int main()
{
double d = 1E10;//1.0^10
printf("%lf\n", d);
return 0;
}
//输出结果:10000000000.000000
国际标准IEEE754规定
二进制浮点数V可以表示成:(-1)^S * M * 2^E
-
(-1)^S
表示符号位。S=0时,V为正数;S=1时,V为负数。 -
M
表示有效数字。1<=M<2。也就是说M=1.xxxxxx,因此M存储的时候只保存xxxxxx部分,右端用0补齐。 -
2^E
表示指数位。
对于32位浮点数,最高1位是符号位S,接着8位是指数位E,剩下23位是有效数字M。
对于64位浮点数,最高1位是符号位S,接着11位是指数位E,剩下52位是有效数字M。
指数位E详解
存储到内存中
E是一个无符号整数,这意味着8位E取值范围是0至255,11位E取值范围是0至2047。
但是,在科学计数法中E是可能出现负数的,所以IEE754规定,存入内存时E的真实值必须再加上一个中间数。
对于8位E,中间数是127。
对于11位E,中间数是1023。
从内存中取出
情况一(一般):E不全为0/1
- E的真实值:对于8位E,减去127。对于11位E,减去1023。
- M的真实值:将有效数字M前加上第一位1。
情况二:E全为0
- E的真实值:规定对于8位E,是1-127=-126。对于11位E,是1-1023=-1022。
- M的真实值:不变,为0.xxxxxx。
情况三:E全为1
- 如果M全为零,则表示±无穷大,不做讨论。
例1:浮点型存储
#include <stdio.h>
int main()
{
int n = 9;
float* pfloat = (float*)&n;
printf("%d\n", n);
printf("%f\n", *pfloat);
*pfloat = 9.0;
printf("%d\n", n);
printf("%f\n", *pfloat);
return 0;
}
输出结果:
9
0.000000
1091567616
9.000000
解析:
第6行:略
第7行:9原码00000000 00000000 00000000 000010019补码和原码相同00000000 00000000 00000000 00001001
由于pfloat是浮点数,*pfloat解引用时认为被解的对象是浮点数。
用浮点数存储的视角看9补码:0 00000000 00000000000000000001001
E全为0,则E的真实值是1-127=-126
M的真实值是0.00000000000000000001001
则浮点数为(-1)^0 * 0.00000000000000000001001 *2^(-126)
而%f默认打印6位小数,因此输出结果为0.000000
第10行:9.0本身是浮点数,存储方式按照浮点型9.0 → 1001.0 → (-1)^0 * 1.001 *2^3
按照标准公式,此时S=0,M=1.001,E=3+127=130
内存中存储为0 10000010 10010000000000000000000
由于n是整型,应该用n的视角看9.0的内存
n补码:01000001 01001000 00000000 00000000
正数原码和补码相同:01000001 01001000 00000000 00000000,即1091567616
第11行:浮点数默认输出6位小数