一、进制
1.什么是进制
• 是一种计数的方式,数值的表示形式
2.二进制
• 特点:只有0和1,逢2进1
• 书写格式:0b或者0b开头
o 二进制 以0b 或者 0B开头
o 八进制 以0开头
o 十六进制 以0x或者0X开头
• 使用场合:二进制指令\二进制文件,变量在内存中就是二进制存储
• n为二进制位所能表示的数据范围(不考虑负数):0~2的n次方-1
3.八进制
• 特点:0~7,逢八进一
• 书写格式:0开头
4.十六进制
• 特点:0~F,逢十六进一
• 书写格式:0x或者0X开头
5.总结
1>mac中计算器的使用
2>printf以不同形式进行输出
进制的应用
#include <stdio.h>
int main(int argc, const char * argv[])
{
int a = 13;
printf("%d\n",a);
printf("%o\n",a);
printf("%x\n",a);
int b = 0b00000000000000000000000000001101;
printf("%d\n",b);
int b1 = 015;
printf("%d\n",b1);
int b3 = 0x520A;
printf("%d\n",b3);
return 0;
}
二、常见的进制转换
转换:有得时候转换为人能识别的数制,要操作计算机写计算机能够识别的语言(二进制)
10 -> 2 : 除2取余法,把10进制数除以2,然后取得余数的序列,再倒序
2 -> 10 : 所有位的位权相加 101 = 1*2^0+0*2^1+1*2^2
2 –> 16 : 4合1法, 整数部分从右向左 4位结合成一位,小数部分从左向右4位结合1位, 不足部分补0
16 –>2 : 1拆4法, 16进制的1位拆成二进制的4位
2–>8:3合1
8–>2:1拆3
10–>8 :除8取余
8 –> 10 : 8–> 2 –> 10
16 –> 10: 16 –>2 –>10
进制转换应用
#include <stdio.h>
void changeTo2(int n){
int len = sizeof(n) * 8;
int temp;
for (int i=0; i<len; i++) {
temp = n;
temp = temp>>31-i;
int t = temp & 1;
printf("%d",t);
}
printf("\n");
}
int main(int argc, const char * argv[]) {
changeTo2(13);
return 0;
}
三、机器数和真值的概念
1、机器数和真值
1)机器数
一个数在计算机中的二进制表示形式, 叫做这个数的机器数。机器数是带符号的,在计算机 用一个数的最高位存放符号, 正数为0, 负数为1.
比如,十进制中的数 +3 ,计算机字长为8位,转换成二进制就是00000011。如果是 -3 ,就 是 10000011 。 那么,这里的 00000011 和 10000011 就是机器数。
2)真值
因为第一位是符号位,所以机器数的形式值就不等于真正的数值。
例如上面的有符号数 10000011,其最高位1代表负,其真正数值是 -3 而不是形式值131(10000011转换成十进制等于 131)。
所以,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值。
例:0000 0001的真值 = +000 0001 = +1,
1000 0001的真值 = –000 0001 = –1
四、原码反码补码概念及转换
1、原码、反码、补码的基本概念.
在探求为何机器要使用补码之前, 让我们先了解原码, 反码和补码的概念.对于一个数, 计算机要 使用一定的编码方式进行存储. 原码, 反码, 补码是机器存储一个具体数字的编码方式.
原码, 反码, 补码是计算机原理的术语。说白了就是为了理解计算机2进制用的。对于C/C++来 说,是和数据类型有关的。
数据在计算机中的存储方法:
数据在计算机内部是以补码的形式储存的
数据分为有符号数和无符号数, 无符号数都为正数,由十进制直接转换到二进制直接存储(其实也是该十进制的补码)即可。 有符号数用在计算机内部是以补码的形式储存的。( 正数的最高位是符号位0,负数的最高位是 符号位1。
对于正数:反码==补码==原码。
对于负数:反码==除符号位以外的各位取反。补码=反 码+1)
正数的首位地址为0,其源码是由十进制数转换到的二进制数字 负数的首位地址为1,其源码后面的位也为10进制数转换过去的二进制数字,都是用补码方式表示 有符号数的。
1)原码
原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制:
第一位是符号位. 因为第一位是符号位, 所以8位二进制数的取值范围就是:
即
原码是人脑最容易理解和计算的表示方式.
2)反码
反码的表示方法是:
正数的反码是其本身
负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.
可见如果一个反码表示的是负数, 人脑无法直观的看出来它的数值. 通常要将其转换成原码再计 算.
3)补码
补码的表示方法是:
正数的补码就是其本身
负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)
对于负数, 补码表示方式也是人脑无法直观看出其数值的. 通常也需要转换成原码在计算其数值.
2、 -1在内存中存储细节
在64位计算机中,-1的原码、反码、补码如下
-1原码 1000 0000 0000 0000 0000 0000 0000 0001
-1反码 1111 1111 1111 1111 1111 1111 1111 1110
-1补码 1111 1111 1111 1111 1111 1111 1111 1111
补码的转换
#include <stdio.h>
int main(int argc, const char * argv[]) {
int b = 0b11111111111111111111111111110011;
printf("b = %d\n",b);
return 0;
}
源码补码转换
#include <stdio.h>
int main(int argc, const char * argv[]) {
int b1 =0b000000000000000000000000000000001;
printf("b1 = %d\n",b1);
int b2 = 0b111111111111111111111111111111111;
printf("b2 = %d\n",b2);
return 0;
}
五、为什么要引入反码、补码?
- 为什么要引入反码和补码?
在开始深入学习前, 我的学习建议是先”死记硬背”上面的原码, 反码和补码的表示方式以及计算 方法.
现在我们知道了计算机可以有三种编码方式表示一个数. 对于正数因为三种编码方式的结果都相 同:
所以不需要过多解释. 但是对于负数:
可见原码, 反码和补码是完全不同的. 既然原码才是被人脑直接识别并用于计算表示方式, 为何还会有反码和补码呢?
首先, 因为人脑可以知道第一位是符号位, 在计算的时候我们会根据符号位, 选择对真值区域的 加减. (真值的概念在本文最开头). 但是对于计算机, 加减乘数已经是最基础的运算, 要设计的 尽量简单. 计算机辨别”符号位”显然会让计算机的基础电路设计变得十分复杂! 于是人们想出了 将符号位也参与运算的方法. 我们知道, 根据运算法则减去一个正数等于加上一个负数, 即: 1-1 = 1 + (-1) = 0 , 所以机器可以只有加法而没有减法, 这样计算机运算的设计就更简单了.
2、补码的再深入(不要求掌握)
计算十进制的表达式: 1-1=0
1 -1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2
如果用原码表示, 让符号位也参与计算, 显然对于减法来说, 结果是不正确的.这也就是为何计算 机内部不使用原码表示一个数.
为了解决原码做减法的问题, 出现了反码:
计算十进制的表达式: 1-1=0
= [0000 0001]反 + [1111 1110]反
= [1111 1111]反
= [1000 0000]原 (1111 1111,符号位不变,其他为逐位取反)
= -0
-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原
发现用反码计算减法, 结果的真值部分是正确的. 而唯一的问题其实就出现在”0”这个特殊的数值 上. 虽然人们理解上+0和-0是一样的, 但是0带符号是没有任何意义的. 而且会有[0000 0000]原和 [1000 0000]原两个编码表示0.
于是补码的出现, 解决了0的符号以及两个编码的问题:
= [0000 0001]补 + [1111 1111]补
= [0000 0000]补
= [0000 0000]原
1-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原
这样0用[0000 0000]表示, 而以前出现问题的-0则不存在了.而且可以用[1000 0000]表示-128:
(-1) + (-127) = [1000 0001]原 + [1111 1111]原 = [1111 1111]补 + [1000 0001]补 = [1000 0000]补
-1-127的结果应该是-128, 在用补码运算的结果中, [1000 0000]补 就是-128. 但是注意因为实际 上是使用以前的-0的补码来表示-128, 所以-128并没有原码和反码表示.(对-128的补码
实际 上是使用以前的-0的补码来表示-128, 所以-128并没有原码和反码表示.(对-128的补码表示[1000 0000]补算出来的原码是[0000 0000]原, 这是不正确的)
单字节的表示的最大的负数是多少?
[10000000]补
=[10000000]反+1
=11111111+1
=(1)00000000 =00000000(最高位溢出了,符号位变成了0)
有人会问 10000000这个补码表示的哪个数的补码呢? 其实这是一个规定,这个数表示的是-128 所以n位补码能表示的范围是 -2^(n-1)到2^(n-1)-1 比n位原码能表示的数多一个
六、位运算符介绍
1、位运算符介绍
位运算是指按二进制进行的运算。在系统软件中,常常需要处理二进制位的问题。 C语言提供了6个位操作运算符。这些运算符只能用于整型操作数,即只能用于带符号或无符号的 char,short,int与long类型。
1)& 按位与
只有对应的两个二进位均为1时,结果位才为1,否则为0 口诀: 同1为1
例如:
9&13 = 9
2) | 按位或
只要对应的二个二进位有一个为1时,结果位就为1,否则为0
1001
& 1101
——————— 1101
3) ^ 按位异或
当对应的二进位相异(不相同)时,结果为1,否则为0
1001
^ 1101
———————- 0100
4) ~ 取反
各二进位进行取反(0变1,1变0)
~9 =-10
9的原码:0000 0000 0000 0000 0000 0000 0000 1001
反码:1111 1111 1111 1111 1111 1111 1111 0110 //-10
知道补码求原码:也是符号位不变,其他各位取反+1
1111 1111 1111 1111 1111 0110
取反
1000 0000 0000 0000 0000 1001+11000 0000 0000 0000 0000 1010 //-10
1、<< 左移
1、各二进位全部左移n位,高位丢弃,低位补0
1)左移可能会改变一个数的正负性
2)左移1位想当于*2
x << n 左移 x 的所有二进制位向左移动n位,移出位删掉,移进的位补零 左移注意:
2、用途:快速计算一个数乘以2的n次方(8<<3 等同于8*2^3)
2<<2 == 2*2
左移改变一个数的正负性:
2、>> 右移
各二进位全部右移n位,保持符号位不变
x >> n x的所有二进制位向右移动n位,移出的位删掉,移进的位补符号位
x 右移 n 位就相当于除以2的n次方
1、右移不会改变一个数的符号
2、用途:快速计算一个数除以2的n次方(8>>3 等同于8/2^3)
左移改变一个数的正负性
2、>> 右移
各二进位全部右移n位,保持符号位不变
x >> n x的所有二进制位向右移动n位,移出的位删掉,移进的位补符号位
x 右移 n 位就相当于除以2的n次方
1、右移不会改变一个数的符号
2、用途:快速计算一个数除以2的n次方(8>>3 等同于8/2^3)
3、位运算技巧:
1)任何数和1进行&操作,得到这个数的最低位
1001
& 0001
———— 00012)想把某一位置0
11111111& 11111011
———— 11111011
位运算应用
#include <stdio.h>
int main(int argc, const char * argv[]) {
int result = 9 & 8;
result = 9|8;
result = ~9;
result = 9^8;
printf("result = %d\n",result);
return 0;
}
应用二
#include <stdio.h>
int main(int argc, const char * argv[]) {
int result = 8<<2;
int b = 0b11000000000000000000000000000001;
result = -1073741823 << 2;
result = 9>>2;
printf("b = %d\n",b);
int c = 6;
result = c&1;
printf("result = %d\n",result);
return 0;
}
利用位运算实现两个变量的值得交换
#include <stdio.h>
void jiou(int n){
if (n&1) {
printf("奇数\n");
}else{
printf("偶数\n");
}
}
int main(int argc, const char * argv[]) {
// jiou(6);
int a = 3,b=4;
//a = 4,b = 3;
printf("a = %d,b = %d\n",a,b);
// int temp;
// //3 3
// temp = a;
// //4 4
// a = b;
// //3 3
// b = temp;
a = a^b;
b = a^b;
a = a^b;
/*
0011 3
0100
^
-----
0111 a = 7 b=4
0111
0100
^
-----
0011 b = 3, a = 7
a = a^b;
0111
0011
^
------
0100 a = 4
*/
printf("a = %d,b = %d\n",a,b);
return 0;
}
七、变量地址获取及存储原理
1、获取变量地址的方法
%p 输出一个地址 定义在函数中变量我们称为局部变量, 先分配字节地址大内存,然后分配字节地址小的内存
变量的首地址,是变量所占存储空间字节地址最小的那个地址
2、变量在内存中存储的原则
低位保存在低地址字节上,高位保存在高地址字节上
CPU能够识别存储单位就是字节
存储原理应用
#include <stdio.h>
int main(int argc, const char * argv[]) {
int a = 3;
int b = 4;
char ch = 'a';
printf("&a = %p\n",&a);
printf("&b = %p\n",&b);
printf("&ch = %p\n",&ch);
return 0;
}
学习心得:
通过本章知识点的剖析,了解了在计算机语言中,进制的概念以及转换的过程和原理 ,熟悉各进制之间的转换,一个数的源码,反码,补码的存在便于计算机的识别和计算,利用位运算改变数的符号,实现变量之间值得转换,了解变量的地址分配及占用的内存空间由高到低。