一、C语言类型
类型的基本归类
整型家族:
char:
unsigned char (0~255)
signed char (-128~127)
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*
char*
float*
void*
空类型:
void 表示空类型(无类型),通常应用于函数的返回类型、函数的参数、指针类型。
二、整型在内存中的存储
1.原码、反码、补码
提到整型在内存中的存储就不得不再次提到原码、反码、补码的概念。
正数和无符号数的原码、反码、补码相同;负数的原码是把数字按二进制直接翻译,反码是原码除符号位按位取反,补码是反码+1 。
int a = 20;
// 0000 0000 0000 0000 0000 0000 0001 0100 —— 原码
// 0000 0000 0000 0000 0000 0000 0001 0100 —— 反码
// 0000 0000 0000 0000 0000 0000 0001 0100 —— 补码
// 0x00 00 00 14
int b = -10;
// 1000 0000 0000 0000 0000 0000 0000 1010 —— 原码
// 1111 1111 1111 1111 1111 1111 1111 0101 —— 反码
// 1111 1111 1111 1111 1111 1111 1111 0110 —— 补码
// 0x ff ff ff f6
因为CPU只有加法器,所以对于整型来说,数据在内存中的存放形式都是补码。
2.大小端介绍
计算机系统中,我们以字节为单位,所以涉及到多个字节安排的问题,也就有了大端存储模式和小端存储模式两种方案。我们常用的x86结构采用的是小端模式。
大端(存储)模式:数据的低位保存在内存的高地址中,数据的高位保存在内存的低地址中。
小端(存储)模式:数据的低位保存在内存的低地址中,数据的高位保存在内存的高地址中。
int a = 20;
// 0x00 00 00 14
对于大端存储模式来说,为(低地址)00 00 00 14(高地址)
对于小端存储模式来说,为(低地址)14 00 00 00(高地址)
//写一段代码,告诉我们机器当前的字节序是什么
int check_sys()
{
int a = 1;
return *(char*)&a;//强制类型转换,只取出a的首字节(低字节)并进行解引用。
}
int main()
{
int ret = check_sys();
if(ret == 1)
{
printf("小端\n");
}
else
{
PRINTF("大端\n");
}
return 0;
}
3.深入理解整型存储
#include <stdio.h>
int main()
{
char a = -128;
//1000 0000 0000 0000 0000 0000 1000 0000
//1111 1111 1111 1111 1111 1111 0111 1111
//1111 1111 1111 1111 1111 1111 1000 0000
//1000 0000 ——补码
printf("%u\n",a); // 4,294,967,168
//整型提升: 1111 1111 1111 1111 1111 1111 1000 0000
// %d -- 打印十进制有符号数字
// %u -- 打印十进制无符号数字
return 0;
}
对于有符号的char,范围是:-128~127 。无符号的char,范围是:0~255 。
以0000 0000(0)开始加一,到0111 1111(127)为最大正数,再加一即改变符号位,1000 0000(-128)符号位为一其余为0是最小整数,再加一到1111 1111(-1),之后再加一又变回0000 0000(0)。 可以看到该循环变化过程有两套逻辑,二进制位由0000 0000变到1111 1111,实际数字由0变到最大整数(127)后,再由最小整数(-128)变到-1为止。
int main()
{
int i = -20;
unsigned int j = 10;
printf("%d\n",i+j); // -10
//i(原码) :1000 0000 0000 0000 0000 0000 0001 0100
//i(反码) :1111 1111 1111 1111 1111 1111 1110 1011
//i(补码) :1111 1111 1111 1111 1111 1111 1110 1100
//j(补码) :0000 0000 0000 0000 0000 0000 0000 1010
//i+j(结果):1111 1111 1111 1111 1111 1111 1111 0110
//i+j(反码):1111 1111 1111 1111 1111 1111 1111 0101
//i+j(原码):1000 0000 0000 0000 0000 0000 0000 1010 -- -10
return 0;
}
int main()
{
char a[1000]
int i;
for(i=0;i<1000;i++)
{
a[i] = -1-i;
}
printf("%d",strlen(a)); //255
//a[1000] = {-1,-2,-3,...,-128,127,126,...,3,2,1,0,...}
return 0;
}
三、浮点型在内存中的储存
任意一个二进制浮点数可以表示为该形式: (-1)^S * M * 2^E
(-1)^S 表示符号位,S=0为正数,S=1为负数;
M表示有效数字,
;
2^E表示指数位。
例如,5.0二进制是101.0,相当于1.01 * 2^2。得到S=0,M=1.01,E=2 。
对于32位的浮点数(float),最高的1位是符号位S,紧接着8位是指数E,剩下的23位是有效数字M。
对于64位的浮点数(double),最高的1位是符号位S,紧接着11位是指数E,剩下的52位是有效数字M。
注意:
1)M一定为1.xxxxxx,所以默认舍去1,只保留xxxxxx这一部分。如1.01只保存01;
2)E有可能为负数,所以存入E时会加上一个中间数,E为8位时中间数为127,E为11位时中间数位1023。如2^10,保存为32为浮点数时,保存为10+127=137,即1000 1001。
3)E从内存中取出分为三种情况:
①E不全为0或不全为1:E减去127(或1023),在M前加上第一位的1。
②E全为0:此时E的真实值为-127(-1023),有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数,以表示+-0或接近于0很小的数字。
③E全为1:此时如果有效数字M全为0,表示+-无穷大。
int main()
{
int n = 9;
//0000 0000 0000 0000 0000 0000 0000 1001 -- 补码
float* pFloat = (float*)&n;
printf("%d\n",n); // 9
printf("%f\n",*pFloat); // 0.000000
//0 00000000 00000000000000000001001
//(-1)^0 * 0.00000000000000000001001 * 2^-126
*pFloat = 9.0;
//1001.0
//1.001 * 2^3
//(-1)^0 * 1.001 * 2^3
// 0 10000001 0010000000000000000000
// 0100 0000 1001 0000 0000 0000 0000 0000
printf("%d\n",n); // 1091567616
printf("%f\n",*pFloat); // 9.000000
return 0;
}
本文为学习C语言心得与笔记记录,部分举例来源于B站C语言教学up主鹏哥