1. 整数的进制转换
进制
-
十六进制 0x12f
-
八进制 O127 八进制多用在linux文件的权限
-
二进制 1001
-
十进制 100
-
[x] 进制只是数值的表现形式,好比人穿了衣服
-
[x] 汇编语言的进制表示
- 十六进制: 1aH
- 八进制:17O
- 十进制 12D
- 二进制 110B
十六进制,八进制,二进制 与十进制转换
将二进制、八进制、十六进制转换为十进制
-
[ ] 整数部分
例如,将八进制数字53627转换成十进制:
[53627 = 5×8^4 + 3×8^3 + 6×8^2 + 2×8^1 + 7×8^0 = 22423 ]从右往左看,第1位的位权为 1,第2位的位权为 8,第3位的位权为 64,将各个位的数字乘以位权,然后再相加,就得到了十进制形式。
-
[ ] 小数部分
例如,将八进制数字423.5176转换成十进制:
小数部分和整数部分相反,要从左往右看,第1位的位权为 1/8,第2位的位权为 1/64,第4位的位权为 8-4=1/4096
十进制转换二进制、八进制、十六进制转换为
- [ ] 整数部分
十进制整数转换为N进制整数采用“除N取余,逆序排列”法。具体做法是:
将N作为除数,用十进制整数除以N,可以得到一个商和余数;保留余数,用商继续除以N,又得到一个新的商和余数;
仍然保留余数,用商继续除以N,还会得到一个新的商和余数;
……
如此反复进行,每次都保留余数,用商接着除以N,直到商为0时为止。
把先得到的余数作为N进制数的低位数字,后得到的余数作为N进制数的高位数字,依次排列起来,就得到了N进制数字。
下图演示了将十进制数字36926转换成八进制的过程:
- [ ] 小数部分
十进制小数转换成N进制小数采用“乘N取整,顺序排列”法。具体做法是:
用N乘以十进制小数,可以得到一个积,这个积包含了整数部分和小数部分;
将积的整数部分取出,再用N乘以余下的小数部分,又得到一个新的积;
再将积的整数部分取出,继续用N乘以余下的小数部分;
……
如此反复进行,每次都取出整数部分,用N接着乘以小数部分,直到积中的小数部分为0,或者达到所要求的精度为止
下图演示了将十进制小数0.930908203125转换成八进制小数的过程:
-
[ ] 正整数十进制转换二进制采用表格法
256 128 64 32 16 8 4 2 1 0.5 0.25 0.125 0.0625 0.03125
比如 十进制数 100
1 0 0 0 1 0 0
-
[ ] 正浮点数十进制也可以采用表格法
-
[ ] 二进制变十六进制 四位二进制数变为1位十六进制
-
[ ] 十六进制变二进制 1位十六进制拆成4位二进制数
十六进制特点
- 可以直观的看出 数字在内存中每个字节的内容
0x12 34 56 78 对应的字节的数字为 0x12,0x34,0x56,0x78
其他进制
3进制,5进制,7进制,36进制,这些在面试题中容易考试
八进制的256 转化成七进制是多少?
15*4=112,系统采用的进制是? A:6 B:7 C:8 D:9
char* transferDecimeberTo36(int a){
char c[20]={};
int i=0;
while (a) {
c[i]=a%36<10?a%36+'0'-0 :a%36+'A'-10;
a/=36;
i++;
} ;
//printf("%s
",c);
int j=i;//边界
while (j%4) { //当j=4 不进入循环体了
c[j]='0';
j++;
}
printf("%s
",c);
char*str = malloc(sizeof(char)*j+1) ;
memset(str, 0, sizeof(char)*j+1);
memcpy(str,c,sizeof(char)*j+1);
for (i=0; i<j/2; i++) {
char temp=*(str+i);
*(str+i)=*(str+j-1-i);
*(str+j-1-i)=temp;
}
return str ;
}
int main(int argc, const char * argv[]) {
int a =0x7FFFFFFF;
char*s=transferDecimeberTo36(a);
printf("转换后的字符串:%s
",s);
free(s);
return 0;
}
思考版
char* transferDecimeberTo36(int a){
char c[20]={}; 虽然浪费,但是栈空间用完释放
int i=0;
//以 a=100为例子 进行测试
//边界问题 循环执行1次
/*
while (a/36) {
// 整数0 怎么转换成字符0
c[i]=a%36<10?a%36+48:a%36+55;
a/=36;
i++;
} ;
*/
//边界问题 循环体执行1次 其实也是执行1次 当 执行到while的时候a 变成 2,此时 a/36就为假的
/*
do {
// 整数0 怎么转换成字符0
c[i]=a%36<10?a%36+48:a%36+55;
a/=36;
i++;
}while (a/36);
*/
while (a) {
// 整数0 怎么转换成字符0
//c[i]=a%36<10?a%36+48:a%36+55; 硬编码这样不好
c[i]=a%36<10?a%36+'0'-0 :a%36+'A'-10;
a/=36;
i++;
} ;
//printf("%s
",c);
//根据题目要求,少于四个字符要补 0 “S2”==>"S200"
// 求出 大于 i 且是4的倍数 n4,然后补0
/*
int j=i;//边界
while (1) {
c[j]='0';
if(!(j%4))
break;
j++;
} c[4]又越界了
*/
//既然是要判断j的,何不在while的循环条件里判断呢
int j=i;//边界
while (j%4) { //当j=4 不进入循环体了
c[j]='0';
j++;
}
printf("%s
",c);
//然后就是把字符数组c[10]逆转输出,这里有个问题,分配了10字节的字符数组,其实就用了3个字节
//用一个s字符指针指向了c;其实是给内存换了视角,
//这里值内的指针是不能返回的
//char*str=c;
char*str = malloc(sizeof(char)*j+1) ;//+1是为了存放
memset(str, 0, sizeof(char)*j+1);//每个字节初始化为0
memcpy(str,c,sizeof(char)*j+1); //拷贝栈空间的字符c[10] 到 堆空间的str
//关于这个循环的长度,其实j ,不要写了这个忘了前面
//比如长度是 5 (0,1, 2, 3,4,)
//比如长度是 6 (0,1,2, 3,4,5)
for (i=0; i<j/2; i++) {
char temp=*(str+i);
*(str+i)=*(str+j-1-i);
*(str+j-1-i)=temp;
}
return str ;
}
int main(int argc, const char * argv[]) {
//C语言的整型默认是有符号的 int 最大 0111 1111 111 1111 1111 1111 1111 11111
int a =0x7FFFFFFF; //整数是4个字节 ,一个字节8位 32个字节
//这种情况会造成内存泄漏
//printf("转换后的字符串:%s
",transferDecimeberTo36(1)); 1
char*s=transferDecimeberTo36(a);
printf("转换后的字符串:%s
",s);
free(s);
return 0;
}
2. 整数的编码
计算机存储数据的Bit(位) 与Byte(字节)
-
[ ] Bit:位 ,小b,最小单位
-
[ ] Byte 字节,大B, 1Byte=8bit
-
[ ] 家庭网络带宽使用bit计算的
-
1个bit 只能存储2个信息 0,1
-
1个Byte 能存储 2^8个信息, [-128,127] or [0,255]
-
数字在存储有1个字节 2个字节,4个字节 8个字节,没有3个字节等
-
数据存储位置:寄存器,内存,磁盘
计算机信息存储单位
1 Byte = 8 Bit
1 KB = 1,024 Bytes
1 MB = 1,024 KB = 1,048,576 Bytes
1 GB = 1,024 MB = 1,048,576 KB = 1,073,741,824 Bytes
1 TB = 1,024 GB = 1,048,576 MB = 1,073,741,824 KB = 1,099,511,627,776 Bytes
1 PB = 1,024 TB = 1,048,576 GB =1,125,899,906,842,624 Bytes (13107.2个80G的
1 EB = 1,024 PB = 1,048,576 TB = 1,152,921,504,606,846,976 Bytes
1 ZB = 1,024 EB = 1,180,591,620,717,411,303,424 Bytes
1 YB = 1,024 ZB = 1,208,925,819,614,629,174,706,176 Bytes
硬盘1KB其实就是 1000B,科学计数法就是 2^10,也就是1024个字节
整数的正负数表示
-
[ ] 有符号数:最高位做符号位这种表示的整数,叫做有符号数(有正负之分) ;
最高位做符号位,1表示负数,0表示正数
如果用1个字节来表示整数,那么:
101001 负数
011110 正数
-
[ ] 无符号数无负数,表示0和正整数,最高位无需表示正负,参与数值计算。
1个字节的整数
有符号: [-128,127]
无符号: [0,255]
整数的编码之 原码
-
[ ] 整数是按照二进制存储的,二进制需要进行特定的编码规则存储:
原码,反码,补码 -
[ ] 原码:在数值前面增加了一位符号位(即最高位为符
号真该位为0表示正数,该位为1表示负数,其余
位表尔数值的大小。以1个字节作为整数的存储长度为例子:
1 = 00000001
-1 = 10000000
那么1-1 =1+(-1)=00000001+10000001 =10000010 = -2
错误原因,符号位参与运算
采用原码的方式:符号位无法参加运算。
整数的编码之 反码
反码:正整数的反码就是其自身,而负整数的反码可以通过对其绝对值逐位求反来求得。
以1个整数的存储长度为例子:
1 = 00000001
-1 = 11111110
哪么1+(-1)= 00000001+11111110 = 11111111 = -0
反码的问题出现在(+0)和(-0)上,因为在人们
的计算概念中零是没有正负之分的。
整数的编码之 补码
- [x] 补码:正数的补码为它本
身,负数的补码就是它的绝
对值求反加1,0的补码是0 - [x] 计算机采用补码存储整数
下面的例子是1个字节的补码
- 0 的补码为 0
- 100的补码 01100100
- -100的补码 100的补码 01100100 取反加1 10011100
- -1 的补码 11111111
- 1 的补码 00000001
- 127 的补码 01111111
- -128的补码 10000000
由于1个字节最大数表示就是 127,因为最高位是符号位。
128的补码就是 0000 0000 1000 0000
-128的补码就是就是 128的反码:1111 1111 0111 1111 +1 = 1
就是 1111 1111 1000 0000 最高位是符号位,正好 是 -128
- [ ] 补码的好处符号位可以参与运算
-1 + 1 = 11111111 + 00000001= 00000000
- [x]
3. 整数的存储
整数的补码是如何存储在计算机上呢?
整数的存储方式
-
[x] 什么是整数的低位和高位?
整数的低位和高位是逻辑描述,比如0x12345678
在左边的 0x12 就是高位,0x78就是低位
不管是大端法还是小端法存储,计算机在内存中存放数据的顺序都是从低地址到高地址,所不同的是首先取低字节的数据存放在低地址还是取高字节数据存放在低地址?就诞生了 小端模式和大端模式
- [ ] 低位优先 little-endian
- 低位首先存在低地址
- [ ] 高位优先 big-endian
- 高位首先存在低地址
低位优先和高位优先只是规则的不同,就好比靠右行驶和靠左行驶一样,但没有本质的优劣之分
int i = 1 的存储 0x00000001
内存:低地址 | ——> | ——> | 内存:高地址 | |
---|---|---|---|---|
大端 | 0x00 | 0x00 | 0x00 | 0x01 |
小端 | 0x01 | 0x00 | 0x00 | 0x00 |
网络字节序和主机字节序
比如说:网络主机系统是小端,但是客户机系统是大端,所以要用函数进行转换
在C/C++写网络程序的时候,往往会遇到字节的网络顺序和主机顺序的问题。这是就可能用到htons(), ntohl(), ntohs(),htons()这4个函数。
网络字节顺序与本地字节顺序之间的转换函数:
htonl()--"Host to Network Long"
ntohl()--"Network to Host Long"
htons()--"Host to Network Short"
ntohs()--"Network to Host Short"
由于不同体系结构的机器之间无法通信,所以要转换成一种约定的数序,也就是网络字节顺序
面试
- 编写程序判断大小端的两种方法?
第一种:联合(union)方式判断法
在union中所有的数据成员共用一个空间,同一时间只能储存其中一个数据成员,所有的数据成员具有相同的起始地址。即上述的union虽然定义了两个成员,但其实这个union只占用了4个字节(32位机器中),往a成员赋值,然后读取b就相读取a成员的低位第一个字节的值。
如果机器使用大端模式,则u.a=1那a的最高字节值为1;
如果机器使用小段模式,则u.a=1则a的最低位字节为1。
上述可知b和a有相同的起始位,所以读取b如果等于1,
typedef union {
int i;
char c;
}my_union;
int checkSystem1(void)
{
my_union u;
u.i = 1;
return (u.i == u.c);
}
第2种:直接判断法
int checkSystem2(void)
{
int i = 0x12345678;
char *c = &i;
return ((c[0] == 0x78) && (c[1] == 0x56) && (c[2] == 0x34) && (c[3] == 0x12));
}
int main(void)
{
if(checkSystem1())
printf("little endian
");
else
printf("big endian
");
if(checkSystem2())
printf("little endian
");
else
printf("big endian
");
return 0;
}
借助指针
int i= 1;// 0x00000001
unsigned char* p= (unsigned char*)&i;
if(*p){
//小端
double b = 99.75;
printf("%p",&b);
}else{
}
4.浮点数
浮点数的定义
- [ ] 单精度浮点数: float 占4个字节
- [ ] 双精度浮点数: double 占8个字节
float x = 1.3f ;
float y= 34.43;
浮点数内存存储结构:科学计数法
IEEE规定:
- 表示小数点位置的部分如果全零,那么这个浮点数的值为0,而如果这个时候有效数字部分非零,那么这个浮点数就“不是一个数”(NaN)
- 表示小数点位置的部分如果全一,那么这个浮点数的值为无穷大。
- 浮点数能表示的最大数值:表示小数点位置的部分除了最低位为零其它均为一,有效数字全为1
- 浮点数能表示的最小数值:表示小数点位置的部分除了最低位为一其它均为零,有效数字全为0
面试题目
- -99.75的32位和64位的二进制表示?
64位
- [ ] 先算 99.75的补码
- 99.75 的二进制 1100011.11
- 阶码 6+1027 = 10000001001
- 位数 .10001111 00...00 (44个0)
- 补码 形式 0 10000001001 10001111 44个0
- 40 58 f0 00 00 00 00 00
- 小端(低字节位优先)存放就是 00 00 00 00 00 f0 58 40
- [ ] 再算-99.75
就是在99.75的补码形式 只需符号位变成1
0 100 0000 1001 1000 1111 0000 (44个0)
1 100 0000 1001 1000 1111 0000 (44个0)
32位略
- 1.5625的二进制表示?
1.1001
下列输出为何是0?
int main(int argc, const char * argv[]) {
int i = 15;
float m = (float)i;
printf("i=%p",&i);
printf("m=%p",&m);
printf( "%d
", m);
return 0;
}