目录
注:条件循环时,注意unsigned类型可能会造成代码的死循环,慎用。
1 数据类型的详细介绍
1.1 基本内置类型
char // 字符数据类型short // 短整型int // 整形long // 长整型long long // 更长的整形float // 单精度浮点数double // 双精度浮点数
1.2 类型基本归类
1.2.1 整形
数值 有正数负数,有些数值,只有正数,没有负数(身高) 无符号位
有正数,有负数(温度) 有符号位
char 只定义为char,编译器的不同,可能会是有符号的char,也可能是无符号的char,char严格上可以分为三类unsigned charsigned charshort(等价于signed short)unsigned short [ int ]signed short [ int ]int(等价于signed int)unsigned intsigned intlong(等价于signed long)unsigned long [ int ]signed long [ int ]
1.2.2 浮点数
float
double
1.2.3 构造类型(自定义类型)
数组类型
结构体类型 struct
枚举类型 enum
联合类型 union
1.2.4 指针类型
int * pi ;char * pc ;float* pf ;void* pv ;
1.2.5 空类型
void 表示空类型(无类型)通常应用于函数的返回类型、函数的参数、指针类型返回类型#include<stdio.h> void test() { } int main() { return 0; }
函数参数#include<stdio.h> void test2(void)//明确不接收参数 { } int main() { return 0; }
指针
#include<stdio.h> int main() { void* p = NULL; int a = 10; void* p1 = &a; return 0; }
void指针可以存储任何数据类型指针,其可以不用强制类型转换,可临时存放地址,用时可以强制类型转换
但是引用时可能会出错,例如++,--,*等操作,其无确定的类型,所以其具体访问及移动的字节数并不确定
2 整型在内存中的存储:原码、反码、补码
2.1 原码,反码,补码
2.1.1 原码
直接将数值按照正负数的形式翻译成二进制就可以得到原码。
2.1.2 反码
将原码的符号位不变,其他位依次按位取反就可以得到反码。
2.1.3补码
反码 +1 就得到补码。
2.1.4 原理补充
计算机中的整数有三种2进制表示方法,即原码、反码和补码。
三种表示方法均有 符号位 和 数值位 两部分,符号位都是用 0 表示 “ 正 ” ,用 1 表示 “ 负 ”而数值位正数的原、反、补码都相同,负整数的三种表示方法各不相同
int main() { int a = 20; //a四个字节,4byte=32bit //00000000 00000000 00000000 00010100 //00000000 00000000 00000000 00010100 //00000000 00000000 00000000 00010100 int b = -10; //10000000 00000000 00000000 00001010 原码 //11111111 11111111 11111111 11110101 反码 //11111111 11111111 11111111 11110110 补码 }
我们F10调试并通过窗口监测&b,列调整为4,可以通过内存窗口看见十六进制的存储内容:
b的补码11111111 11111111 11111111 11110110
对应的十六进制 f f f f f f f 6
对照窗口的 f6 ff ff ff,我们可以知道负数的内存存储是以补码的形式存储
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统 一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程 是相同的,不需要额外的硬件电路。
//例如1-1 // //原码的计算是错误的 //00000000 00000000 00000000 00000001 //10000000 00000000 00000000 00000001 //10000000 00000000 00000000 00000010 ==-2;
//补码的计算 //10000000 00000000 00000000 00000001 (-1的原码) //11111111 11111111 11111111 11111110 //11111111 11111111 11111111 11111111 (-1的补码) //00000000 00000000 00000000 00000001 //11111111 11111111 11111111 11111111 (-1的补码) //100000000 00000000 00000000 00000000(进位丢失) //00000000 00000000 00000000 00000000 ==0;
2.1.5 原码与补码的转换方式
原码到补码——1种方式——原码符号位不变,数值位取反,再加1
//10000000 00000000 00000000 00000001 (-1的原码) //11111111 11111111 11111111 11111110 //11111111 11111111 11111111 11111111 (-1的补码)
补码到原码——2种方式——先减1,数值位取反,得到原码(原路返回,容易理解)
//11111111 11111111 11111111 11111111 (-1的补码) //11111111 11111111 11111111 11111110 (减1) //00000000 00000000 00000000 00000001 (取反)
——补码取反,再加1,得到原码(与原码到补码的方式相同)
//11111111 11111111 11111111 11111111 (-1的补码) //10000000 00000000 00000000 00000000 (取反) //00000000 00000000 00000000 00000001 (+1)==1的原码
3 大小端字节序介绍及判断
3.1 引入
b的补码11111111 11111111 11111111 11110110
对应的十六进制 f f f f f f f 6
0x十六进制(0 1 2 3 4 5 6 7 8 9 a b c d e f )
对照窗口的 f6 ff ff ff,我们已经了解存储的是补码,但是我们仔细观察,其存储的顺序不太一样,其以字节为单位倒着存储,我们不难意识到当1个数值超过1个字节,要存储到内存中,就有顺序问题:
例如0x11223344,我们以字节为单位存储,其实可以有多种方式,例如11 22 33 44;
22 11 33 44;33 11 22 44等;
但是为了方便使用,最终留存了11 22 33 44和44 33 22 11两种方式;
由此我们引出大小端的概念。
3.2 大小端介绍
大端(存储)模式 ,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;小端(存储)模式 ,是指数据的低位保存在内存的低地址中,而数据的高位 ,保存在内存的高地址中。例如0x11 22 33 44 0x十六进制(0 1 2 3 4 5 6 7 8 9 a b c d e f )高 低![]()
小端字节序存储
大端字节序存储
由此可知,当前VS编译器中数据采用小端字节序存储。
我们常用的 X86 结构是小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式 还是小端模式。
例如:一个16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为高字节, 0x22 为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22 放在高地址中,即 0x0011 中。
内存中存放的是补码;
整型表达式计算使用的内存中的补码计算的;
打印的和我们看到的是原码
3.3 使用代码来判断断当前机器的字节序
3.3.1 思路
例如0x11 22 33 44 0x十六进制(0 1 2 3 4 5 6 7 8 9 a b c d e f )高 低![]()
小端字节序存储
大端字节序存储
我们只需判断第一个字节内存储的是11或是44,即可判断是大端字节序或是小端字节序,我们继续简化,我们可以假设a=1,我们只需看二进制1的位置。
int a=1;(0x00000001)
3.3.2 方法一
我们只需访问一个字节,我们可以用char*指针来操作,但是a是int类型,我们对a强制类型转换,再赋值给char*p,所以我们推出:char* p=(char*)&a;再对*p解引用。
//1表示小端 //0表示大端 int main() { int a = 1; char* p = (char*)&a; if (*p == 1) printf("小端\n"); else printf("大端\n"); return 0; }
3.3.3 方法二
我们也可以封装一个函数来实现操作:
//返回1表示小端 //返回0表示大端 int check_sys() { int a = 1; char* p = (char*)&a; if (*p == 1) return 1; else return 0; } int main() { if(check_sys()==1) printf("小端\n"); else printf("大端\n"); return 0; }
3.3.4 方法三
简化初阶:
//返回1表示小端 //返回0表示大端 int check_sys() { int a = 1; //char* p = (char*)&a; //if (*p == 1) if(*(char*)&a==1) return 1; else return 0; } int main() { if(check_sys()==1) printf("小端\n"); else printf("大端\n"); return 0; }
3.3.5 方法四
简化进阶:
//返回1表示小端 //返回0表示大端 int check_sys() { int a = 1; return *(char*)&a; } int main() { if(check_sys()==1) printf("小端\n"); else printf("大端\n"); return 0; }
3.4 权重
10进制:
1 2 3
10^2 10^1 10^0
1*10^2 + 2*10^1 + 3*10^0=123;
十六进制(0x):
1 2 3
16^2 16^1 10^0
1*16^2 + 2*16^1 + 3*16^0=291;
3.5 例题
3.5.1 有符号位和无符号位的数值的整型提升知识补充
//有符号位的按符号位整型提升 //无符号位的高位补0
3.5.2 类型的取值范围详细图解——知识补充
整型类型的取值范围限定在:limits.h
3.5.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; }
我们对其进行详细解释:
int main() { char a = -1;//-1截断后存储到a中 //10000000 00000000 00000000 00000001(-1的原码) //11111111 11111111 11111111 11111110 //11111111 11111111 11111111 11111111(-1的补码) //char两个byte,存放低八个bit位 //11111111-a signed char b = -1; //11111111 11111111 11111111 11111111(-1的补码) //11111111-b //a,b会将最高位作符号位进行处理 unsigned char c = -1;//无符号char //11111111 11111111 11111111 11111111(-1的补码) //11111111-c(八位均作为数值位处理) // printf("a=%d,b=%d,c=%d", a, b, c); //有符号位的按符号位整型提升 //无符号位的高位补0 //a,b打印%d的时候要整型提升,打印原码 //高位全部补1 //11111111 11111111 11111111 11111111(-1的补码) //11111111 11111111 11111111 11111110 //10000000 00000000 00000000 00000001(-1的原码)=-1; //c打印%d的时候要整型提升,无符号位的高位补0 //00000000 00000000 00000000 11111111(整型提升) //当成有符号位来处理,此处高位是0,正数 //正数原码、反码、补码均相同 //00000000 00000000 00000000 11111111(打印的原码)=255; return 0; }
3.5.4 例二
#include <stdio.h> int main() { char a = -128; printf("%u\n",a); return 0; }
%u是打印无符号整形,认为内存中存放的补码对应的是一个无符号数
%d是打印有符号整形,认为内存中存放的补码对应的是一个有符号数
int main() { char a = -128; // //10000000 00000000 00000000 10000000 //11111111 11111111 11111111 01111111 //11111111 11111111 11111111 10000000 //截断 //10000000 -a //整形提升 //char a有符号位,高位置数同符号位 //11111111 11111111 11111111 10000000 //%u形式打印,认为内存中存放的数是无符号数 //原码、反码、补码均相同 printf("%u\n", a); return 0; }
3.5.5 例三
#include <stdio.h> int main() { char a = 128; printf("%u\n",a); return 0; }
int main() { char a = 128; // //00000000 00000000 00000000 10000000 //截断 //10000000 -a //整形提升 //char a有符号位,高位置数同符号位 //11111111 11111111 11111111 10000000 //%u形式打印,认为内存中存放的数是无符号数 //原码、反码、补码均相同 printf("%u\n", a); return 0; }
3.5.6 例四——运算
int i= -20; unsigned int j = 10; printf("%d\n", i+j); //按照补码的形式进行运算,最后格式化成为有符号整数
int main() { int i = -20; //10000000 00000000 00000000 00010100 //11111111 11111111 11111111 11101010 //11111111 11111111 11111111 11101100 // unsigned int j = 10; //00000000 00000000 00000000 00001010 // printf("%d\n", i + j); //按照补码的形式进行运算,最后格式化成为有符号整数 //11111111 11111111 11111111 11101100 //00000000 00000000 00000000 00001010 //相加 //11111111 11111111 11111111 11110110 //打印原码 //11111111 11111111 11111111 11110101 //10000000 00000000 00000000 00001010=-10 return 0; }
3.5.7 例五——无符号变量造成的死循环
#include<stdio.h> int main() { unsigned int i; for (i = 9; i >= 0; i--) { printf("%u\n", i); } return 0; }
无符号i,其永远不会小于0,死循环,。
为了清楚地看清过程,我们使用sleep函数,引用windows.h头文件
#include<stdio.h> #include<windows.h> int main() { unsigned int i; for (i = 9; i >= 0; i--) { printf("%u\n", i); Sleep(100); } return 0; }
3.5.8 例六
int main() { char a[1000]; int i; for(i=0; i<1000; i++) { a[i] = -1-i; } printf("%d",strlen(a)); return 0; }
int main() { char a[1000]; //-1 -2 -3 -4………… -1000 //但是其值放入char类型变量 //char的范围(0 1 2 3 ………… 127 -128 -127 ………… -2 -1)(顺序,加法) // (-1 -2 ………… -127 -128 127 ………… 3 2 1 0 -1 -2…………)(逆序,减法) // int i; for (i = 0; i < 1000; i++) { a[i] = -1 - i; } printf("%d", strlen(a)); //strlen 找到ASCII码为0即可; //( - 1 - 2 ………… - 127 - 128 127 ………… 3 2 1 0)(逆序,减法) //0之前出现255个非\0的数字 return 0; }
3.5.9 例七
#include <stdio.h> unsigned char i = 0; int main() { for(i = 0;i<=255;i++) { printf("hello world\n"); } return 0; }
unsigned char范围0~255,条件恒成立,代码死循环。
注:条件循环时,注意unsigned类型可能会造成代码的死循环,慎用。
4 浮点型在内存中的存储解析
4.1 常见浮点数类型
3.141591E10浮点数家族包括: float、double、long double 类型。浮点数表示的范围:float.h中定义
4.2 浮点型存储规则
4.2.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; }
下面我们来详细分析:
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; }
由此推测,浮点数的存与取均与整数对应的存与取有所不同。
4.2.2 浮点数的存储规则
根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:(-1)^S * M * 2^E(-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。M表示有效数字,大于等于1,小于2。2^E表示指数位。
例如3.3,3.14等,内存对此类数字无法精确保存,浮点数在某种程度上是会丢失一定精度的。
所以我们只需存储S、M、E三部分存储,需要的时候再还原即可。
IEEE 754规定:对于32 位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。
对于64 位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。![]()
IEEE 754对有效数字M和指数E,还有一些特别规定。前面说过, 1≤M<2 ,也就是说, M 可以写成 1.xxxxxx 的形式,其中 xxxxxx 表示小数部分。 IEEE 754规定,在计算机内部保存 M 时,默认这个数的第一位总是 1 ,因此可以被舍去,只保存后面的 xxxxxx部分。比如保存 1.01 的时 候,只保存01 ,等到读取的时候,再把第一位的 1 加上去。这样做的目的,是节省 1 位有效数字。以 32 位浮点数为例,留给M 只有 23 位, 将第一位的1 舍去以后,等于可以保存 24 位有效数字。
E的保存:E 为一个无符号整数( unsigned int) 这意味着,如果E 为 8 位,它的取值范围为 0~255 ;如果 E 为 11 位,它的取值范围为 0~2047 。但是,我们知道,科学计数法中的E 是可以出现负数的,所以IEEE 754 规定,存入内存时 E 的真实值必须再加上一个中间数,对于 8 位的 E ,这个中间数 是 127 ;对于 11 位的 E ,这个中间 数是 1023 。比如, 2^10 的 E 是 10 ,所以保存成 32 位浮点数时,必须保存成 10+127=137 ,即10001001。举个例子:int main() { float f = 5.5f; //(−1)^0x1.011x2^2 //S=0 //M=1.011保留1后面的小数 //E=2加上中间值127==129 //0 10000001 011补齐 //0 10000001 01100000000000000000000 //0100 0000 1011 0000 0000 0000 0000 0000 // 4 0 b 0 0 0 0 0(十六进制) //并且考虑到VS小端存储 return 0; }
![]()
E的取出(三种情况):E不全为0或不全为1这时,浮点数就采用下面的规则表示,即指数 E 的计算值减去 127 (或 1023 ),得到真实值,再将 有效数字M 前加上第一位的 1 。比如:0.5 ( 1/2 )的二进制形式为 0.1 ,由于规定正数部分必须为 1 ,即将小数点右移 1 位,则为1.0*2^(-1) ,其阶码为 -1+127=126 ,表示为 01111110,而尾数 1.0 去掉整数部分为 0 ,补齐 0 到 23 位 00000000000000000000000,则其二进制表示形式为:0 01111110 00000000000000000000000E全为0这时,浮点数的指数 E 等于 1-127 (或者 1-1023 )即为真实值,有效数字 M 不再加上第一位的 1 ,而是还原为 0.xxxxxx 的小数。这样做是为了表示 ±0 ,以及接近于0 的很小的数字。0 01111110 00000000000000000000000E全为1这时,如果有效数字 M 全为 0 ,表示 ± 无穷大(正负取决于符号位 s );
4.2.3 使用存储规则解释4.2.1的栗子
解释前面的题目:为什么 0x00000009 还原成浮点数,就成了 0.000000 ?首先,将 0x00000009 拆分,得到第一位符号位 s=0 ,后面 8 位的指数 E=00000000 ,最后 23 位的有效数字 M=000 0000 0000 0000 0000 1001 。9 -> 0000 0000 0000 0000 0000 0000 0000 1001由于指数E 全为 0 ,所以符合上一节的第二种情况。 因此,浮点数V 就写成:V=( - 1)^0 × 0.00000000000000000001001×2^( - 126)=1.001×2^( - 146)显然, V 是一个很小的接近于 0 的正数,所以用十进制小数表示就是 0.000000 。int n = 9; //00000000 00000000 00000000 00001001(9的补码) // float* pFloat = (float*)&n; printf("n的值为:%d\n", n);//(存整数,取整数) printf("*pFloat的值为:%f\n", *pFloat);//(存整数,取浮点数) //0 00000000000 00000000000000001001 //E全为0 //(-1)^0*0.00000000000000001001*2^(1-127=-126) //数值太小 //打印0.000000
再看例题的第二部分。请问浮点数9.0,如何用二进制表示?还原成十进制又是多少?首先,浮点数9.0等于二进制的 1001.0 ,即 1.001×2^3 。9.0 -> 1001.0 -> ( - 1 ) ^01 . 0012 ^3 -> s = 0 , M = 1.001 , E = 3 + 127 = 130那么,第一位的符号位 s=0 ,有效数字 M 等于 001 后面再加 20 个 0 ,凑满 23 位,指数 E 等于 3+127=130 , 即10000010 。所以,写成二进制形式,应该是s+E+M,即 0 10000010 001 0000 0000 0000 0000 0000 这个32 位的二进制数,还原成十进制,正是 1091567616 。*pFloat = 9.0; //9.0 //1001.0 //1.001*2^3 //(-1)^0*1.001*2^3 3+127 //0 10000010 00100000000000000000000 printf("num的值为:%d\n", n);//(存浮点数,取整数) //0 10000010 00100000000000000000000我们认为其为该数的补码 //符号位为0,为正数,其原反补相同 //01000001000100000000000000000000 printf("*pFloat的值为:%f\n", *pFloat);//(存浮点数,取浮点数) //浮点数形式放入,再以浮点数形式拿出; //不难理解其如何存再反过来将其还原 //其取出依旧为9.0
![]()
int main() { int n = 9; //00000000 00000000 00000000 00001001(9的补码) // float* pFloat = (float*)&n; printf("n的值为:%d\n", n);//(存整数,取整数) printf("*pFloat的值为:%f\n", *pFloat);//(存整数,取浮点数) //0 00000000000 00000000000000001001 //E全为0 //(-1)^0*0.00000000000000001001*2^(1-127=-126) //数值太小 //打印0.000000 *pFloat = 9.0; //9.0 //1001.0 //1.001*2^3 //(-1)^0*1.001*2^3 3+127 //0 10000010 00100000000000000000000 printf("num的值为:%d\n", n);//(存浮点数,取整数) //0 10000010 00100000000000000000000我们认为其为该数的补码 //符号位为0,为正数,其原反补相同 //01000001000100000000000000000000 printf("*pFloat的值为:%f\n", *pFloat);//(存浮点数,取浮点数) //浮点数形式放入,再以浮点数形式拿出; //不难理解其如何存再反过来将其还原 //其取出依旧为9.0 return 0; }