一、C语言数据类型
1.引入C语言数据类型
- 汇编中的数据类型有byte、word、dword等
- 那么C语言的数据类型除了有一个作用域的概念,其他的都和汇编的数据类型相似
2.学习数据类型的三要素
- 存储数据的宽度:每种数据类型能容纳多大的数据
- 存储数据的格式:虽然底层都是用0、1存储,但是使用不同数据类型时要注意里面存的数据到底是什么样的
- 作用范围(作用域):哪里可以用、哪里不能用什么数据类型
3.C语言中的数据类型
-
如下图所示:
二、整数类型
-
整数类型:char、short、int、long
char 8bit 1字节 short 16bit 2字节 int 32bit 4字节 long 32bit 4字节 -
那么和汇编的byte,word,dword有什么关系呢?我们举一个简单的例子来看一下
void Func(){ char cTemp = 0xFF; short sTemp = 0xFF; int iTemp = 0xFF; } void main(int argc, char* argv[]) { Func(); }
如果你用char类型的变量来存储0xFF这个数据,那么汇编语言则会把0xFF这个数据存入内存中,且会用内存的8bit宽度来存储,所以可以看到是byte;而如果用short类型来存储0xFF,那么会用16bit宽度内存来存储,所以可以看到是word;同理如果使用int类型变量来存储0xFF,那么会用32bit宽度内存来存储,所以是dword
-
所以从数据宽度来说:
- 如果一个数据使用char类型变量存储,翻译成汇编则会用byte数据宽度内存来存储
- 如果一个数据使用short类型变量存储,翻译成汇编则会用word数据宽度内存来存储
- 如果一个数据使用int类型变量存储,翻译成汇编则会用dword数据宽度内存来存储
-
如果存入数据的数据宽度大于数据类型宽度,还是和汇编一样,从低位开始向高位存储,存储规定的数据宽度,多余的高位数据舍弃
void Func(){ char cTemp = 0x12345678; //byte short sTemp = 0x12345678; //word int iTemp = 0x12345678; //dword } void main(int argc, char* argv[]) { Func(); }
第一个0x12345678是用char数据类型存储,即用byte数据宽度的内存存储,那么取低8位0x78存储到[ebp-4]
第二个0x12345678是用short数据类型存储,即用word数据宽度的内存存储,取低16位0x5678存入[ebp-8]内存中,且是以字节为单位倒着存的,在0x0012FF24内存中存入78,在0x0012FF25内存中存入56。
第三个0x12345678用int数据类型变量存储,即用dword数据宽度的内存存储,且数据从低位到高位倒着存储,78存入0x12FF20,56存入0x12FF21,34存入0x12FF22,12存入0x12FF23(这里要注意和OD区分开,OD已经帮我们倒过来了,VC6++没有)
-
整数类型分为有符号(signed)和无符号(unsigned)两种:
-
但是在内存中存储的方式完全一样:计算机不管数据有无符号,它就只把数据化成二进制按照指定的数据宽度存在内存中
-
在做运算的时候需要注意有无符号的区别
void Func(){ char x = 0xFF; unsigned char y = 0xFF; printf("%d",x); printf("%d",y); }
如果你定义0xFF为有符号数,那么计算机会翻译显示为-1;如果你定义0xFF为无符号数,则显示结果为255
-
还有在比较的数据大小时,也要注意有无符号的区别:
-
如果一个有符号和一个无符号数比较,那么最终汇编语言会使用有符号数比较的跳转指令JBE、JG等
void Func(){ char x = 0xFF; unsigned char y = 0xFF; if(x>y){ printf("x>y"); } }
movsx:带符号拓展后的mov指令,高位用符号位扩展
-
如果两个有符号数比较,最终汇编语言会使用比较有符号数的跳转指令
void Func(){ char x = 0xFF; char y = 0xFF; if(x>y){ printf("x>y"); } }
-
如果两个无符号数比较,最终汇编语言会使用比较无符号数的跳转指令
void Func(){ unsigned int x = 0xFFFF; unsigned int y = 0xFF; if(x>y){ printf("x>y"); } }
注意:如果你用低于32位数据宽度的数据类型变量来存储,比较之前,汇编会自动帮你把数据扩展到32位,且是带符号扩展(movsx),所以最后比较的时候还是使用有符号数比较的跳转指令。现在为了防止这种情况的出现,直接用32位的容器存储数据即可。(后面会学习)
-
-
三、浮点类型
1.浮点类型
- float和double
- float数据类型宽度为32位;double数据类型宽度为64位
2.小数/浮点数在内存中存储方式
-
十进制、十六进制等整数在可以转化成对应的二进制最终存入内存,但是小数无法像整数那样对样转化成二进制数,但是小数如果想存入内存,它也要想办法转成二进制,那么小数是如何存储的呢?
-
float和double在存储方式上都是遵从IEEE的规范的
-
float的存储方式:
-
double的存储方式:
-
-
尾数部分宽度越长,表示小数的精确度越高,因为有些小数部分表示为二进制数是无限循环的,比如1.3。小数部分0.3用二进制表示:即不断的乘2:每次乘完2取整数部分作为二进制数,小数部分如果为0,则结束,如果不为0,则取出小数部分继续乘2。那么1.3表示为二进制为1.0100110 0100110 …无限循环下去,如果使用float存储,那么尾数部分精确到了23位;如果使用double存储,那么尾数部分就精确到了52位
3.将float类型转换为内存存储格式
下面海哥的方法个人认为不是很容易记忆,建议去b站看王道计算机组成原理的浮点数课程,讲解比较清晰
-
先将这个实数的绝对值化为二进制格式(不管正负,转换分为小数部分和整数部分)
小数部分转换成二进制有一个精确位数的概念,因为可能出现无限循环的情况,所以要给定一个精确位数
-
将这个二进制格式实数的小数点左移或右移n位,直到小数点移动到第一个有效数字(1)的右边,此时结果为m
-
从小数点右边第一位开始数出二十三位数字放入第22到第0位(不够的用0补)
-
如果实数是正的,则在第31位放入“0”,否则放入“1”
-
如果m是小数点左移得到的,第30位放入“1”。如果m是小数点右移得到的或n=0,则第30位放入“0”
-
如果m是小数点左移得到的,则将n-1后化为二进制,并在左边加“0”补足七位,放入第29到第23位
如果m是小数点右移得到的或n=0,则将-n-1化为二进制(补码)后,取低7位放入第29到第23位
-
接着将得到的32位每4位一组,化成十六进制方便我们表示
4.举例说明
-
8.25转换成浮点数存储:
-
整数部分8转换成二进制为:
1000
8/2 0 ↑ //不断的除以2,取余数 4/2 0 | 2/2 0 | 1/2 1 |
-
小数部分0.25转换成二进制为:
10
0.25*2 0 | //取小数部分不断的乘2,之后再取结果的整数部分 0.5*2 1 ↓ //直到小数部分为0结束
-
所以最终8.25用二进制表示为1000.10
-
-
小数点左移三位,小数点移到第一个有效数字的右边,接着用科学计数法表示:
- 因为十进制数100.1可以表示为1.001*102。所以二进制1000.10移动后可以表示为:1.00010 * 23
-
从小数点右边第一位开始数出二十三位数字放入第22到第0位
-
因为小数点右边开始为00010,所以将00010后面再补0,一共补充到总长度为22位即可
-
-
因为8.25是正数,则在第31位放入“0”
-
因为小数点当时是左移3位得到的1.00010 * 23,则在指数部分的首位即30位放入“1”
-
因为小数点是左移3位得到的1.00010 * 23,所以3-1=2化成二进制为10,接着在左边补0,补充到总长度为7即可
另外一种方法:5和6可以用下面的方法代替:如果是左移n位得到的则指数部分为127+n化成二进制;如果是右移n位得到的则指数部分为127-n化成二进制(这里用到的是移码的知识,浮点数的存储方式可以看看王道计算机组成原理中的课程,在B站)
-
化成十六进制为:0x41080000
0100 0001 0000 1000 0000 0000 0000 0000 0x41080000
四、字符的存储
1.英文字符的存储
-
可以这样存:
char x = 'A'
;也可以这样存:char y = 65
因为A对应的十进制ASCII码为65
-
ASCII编码:
-
ASCII 码使用指定的 7 位或 8 位二进制数组合来表示 128 或 256 种可能的字符
- 标准 ASCII 码使用7 位二进制数来表示所有的大写和小写字母,数字 0 到 9、标点符号,以及在美式英语中使用的特殊控制字符(最高位,即第八位永远都是0)
- 扩展 ASCII 码允许将每个字符的第8 位用于确定附加的128 个特殊符号字符、外来语字母和图形符号
-
常用的字符对应的ASCII码为:
二进制 十进制 十六进制 字符 0011 0000 48 30 0 0011 0001 49 31 1 0011 0010 50 32 2 0011 0011 51 33 3 0011 0100 52 34 4 0011 0101 53 35 5 0011 0110 54 36 6 0011 0111 55 37 7 0011 1000 56 38 8 0011 1001 57 39 9 0100 0001 65 41 A 0100 0010 66 42 B 0100 0011 67 43 C 0100 0100 68 44 D 0100 0101 69 45 E 0100 0110 70 46 F 0100 0111 71 47 G 0100 1000 72 48 H 0100 1001 73 49 I 0100 1010 74 4A J 0100 1011 75 4B K 0100 1100 76 4C L 0100 1101 77 4D M 0100 1110 78 4E N 0100 1111 79 4F O 0101 0000 80 50 P 0101 0001 81 51 Q 0101 0010 82 52 R 0101 0011 83 53 S 0101 0100 84 54 T 0101 0101 85 55 U 0101 0110 86 56 V 0101 0111 87 57 W 0101 1000 88 58 X 0101 1001 89 59 Y 0101 1010 90 5A Z 0110 0001 97 61 a 0110 0010 98 62 b 0110 0011 99 63 c 0110 0100 100 64 d 0110 0101 101 65 e 0110 0110 102 66 f 0110 0111 103 67 g 0110 1000 104 68 h 0110 1001 105 69 i 0110 1010 106 6A j 0110 1011 107 6B k 0110 1100 108 6C l 0110 1101 109 6D m 0110 1110 110 6E n 0110 1111 111 6F o 0111 0000 112 70 p 0111 0001 113 71 q 0111 0010 114 72 r 0111 0011 115 73 s 0111 0100 116 74 t 0111 0101 117 75 u 0111 0110 118 76 v 0111 0111 119 77 w 0111 1000 120 78 x 0111 1001 121 79 y 0111 1010 122 7A z
-
2.中文字符的存储
-
GB2312编码规则:
- 天朝专家把那些127号之后的奇异符号们(即EASCII)取消掉,规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,还把数学符号、罗马希腊的 字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了
-
**所以截取一个字符串的一段,如何区分是英文还是中文?**就看这个字节数值的范围,如果小于128就表示英文;如果大于则是汉字的两字节截取了一半(即看最高位为0还是1)
-
GB2312表:可以看到如果最终计算机存储的是0xB0A1,那么使用GB编码规则,这两个字节的值就表示
啊
这个汉字。但是注意:是计算机中存储的值为0xB0A1,而计算机内存中是按字节为单位倒着存的,强调过很多次了,所以我们实际上用啊
的时候要写成0xA1B0
五、作业
-
将float类型的12.5转换成16进制
-
将12.5用二进制表示:
整数部分: 12 化成二进制--> 1100 小数部分: 0.5 化成二进制--> 1 因为0.5*2=1.0 1 综上12.5化成二进制为1100.1
-
小数点左移到第一个有效位的右边,此时用科学计数法表示为:1.1001*23。故左移了3位
-
将此时的小数位从21位依次存到0位,如果不够则补0
-
因为12.5是正数,所以31位为0
-
又因为小数部分是左移,所以30位为1
-
且因为小数二进制时左移3位得到的,所以3-1=2化成二进制为10,将10存入指数部分,不够的左边补0(或者因为3化成移码为3+127=130,130化成二进制即为10000010)
-
最后每四位一组化成十六进制为
0100 0001 0100 1000 0000 0000 0000 0000 0x41480000
-
综上12.5转成十六进制为:0x41480000,存储在内存中为(倒着存入)00 00 48 41
-
验证一下:
-