C语言-入门-必备基础知识(九)

x33g5p2x  于2022-07-11 转载在 其他  
字(5.9k)|赞(0)|评价(0)|浏览(905)

进制基本概念

对于进制,我们都很熟悉,从小学开始就知道 1、2…9、10、11… 这就是十进制。 几进制就是逢几进1。

为什么全世界各地都从一开始就习惯使用 10 进制,很简单,因为我们有10个手指头,哈哈哈哈哈 直接数指头就行了!

那么对于计算机来说,它并没有手指,它处理数据是根据2进制来处理的! 二进制:010101…0101 。 那么为什么是 2进制 呢? 因为机器处理指令的硬件都是双态的,只要是涉及到数据的,那么就是 电位的 “高” 或 “低”,即二进制的 “1” 或 “0”。

在程序猿使用机器语言来编写程序的时候,二进制太麻烦,为了方便会使用 八进制 或 十六进制。谈到机器语言的话,跟十进制就没啥关系了!

  • 二进制是计算技术中广泛采用的一种数制。二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是逢二进一,借位规则是借一当二。
  • 八进制就是逢8进1,都是0~7的数字,到8就进1位, 以8为基数的计数法,采用0,1,2,3,4,5,6,7八个数字,逢八进1。计算题中常常以字母O表明该数字是八进制。八进制的数和二进制数可以按位对应(八进制一位对应二进制三位)。
  • 十进制数是组成以10为基础的数字系统,有0,1,2,3, 4, 5, 6, 7, 8, 9十个基本数字组成。十进制基于位进制和十进位两条原则,即所有的数字都用10个基本的符号表示,满十进一,同时同一个符号在不同位置上所表示的数值不同,符号的位置非常重要。基本符号是0到9十个数字。
  • 十六进制十六进制(简写为hex或下标16)是一种基数为16的计数系统,是一种逢16进1的进位制。通常用数字0、1、2、3、4、5、6、7、8、9和字母A、B、C、D、E、F(a、b、c、d、e、f)表示,其中:AF表示1015,这些称作十六进制数字

进制转换

10 进制转 2 进制

除2取余, 余数倒序; 得到的序列就是二进制表示形式

2 进制转 10 进制

将每个数值进行标记,从右往左,从0开始进行标记,依据之前进行的标记,以2为底(有值就是2没值就是0),并以标记数进行开次方 ,最后,将所得的数相加

2 进制转 8 进制

三个二进制位代表一个八进制位, 因为3个二进制位的最大值是7,而八进制是逢8进1

列: 每三个数字为一组,若不足三位,则在前用“0”补足(若是小数,则在后面补足3位),将分过组的二进制数转化成八进制数

八进制转二进制

将八进制数变更成二进制数,第一位若是“0”,请舍去(小数的话,最后一位,如果是“0”,请舍去)将所有数字进行组合,得出最终数字

2 进制转 16 进制

四个二进制位代表一个十六进制位,因为4个二进制位的最大值是15,而十六进制是逢16进1

列: 每四个数字为一组,若不足四位,则在前用“0”补足(若是小数,则在后面补足4位), 将分过组的二进制数转化成十六进制数

16进制转2进制

将十六进制数变更成二进制数,第一位若是“0”,请舍去(小数的话,最后一位,如果是“0”,请舍去)将所有数字进行组合,得出最终数字

进制表

十进制小数转换为二进制小数

整数部分,直接转换为二进制即可, 小数部分,使用"乘2取整,顺序排列" 用2乘十进制小数,可以得到积,将积的整数部分取出,再用2乘余下的小数部分,直到积中的小数部分为 零,或者达到所要求的精度为止然后把取出的整数部分按顺序排列起来, 即是小数部分二进制最后将整数部分的二进制和小数部分的二进制合并起来

以0.65为例

0.65x2=1.3,取1;
0.3x2=0.6,取0;
0.6x2=1.2,取1;
0.2x2=0.4,取0;
0.4x2=0.8,取0;
0.8x2=1.6,取1;
0.6x2=1.2,取1;

此时已经陷入了循环,不必再计算,0.65的二进制就是01010011……,有的计算中并不要求有符号位,可省略,为1010011……

二进制小数转换为十进制小数

二进制的小数转换为十进制主要是乘以2的负次方,从小数点后开始,依次乘以2的负一次方,2的负二次方,2的负三次方等。''例如二进制数0.001转换为十进制。

第一位为0,则0*1/2,即0乘以2负 一次方。
第二位为0,则0*1/4,即0乘以2的负二次方。
第三位为1,则1*1/8,即1乘以2的负三次方。
各个位上乘完之后,相加,0*1/2+0*1/4+1*1/8得十进制的0.125

原码反码补码

计算机只能识别0和1, 所以计算机中存储的数据都是以0和1的形式存储的,数据在计算机内部是以补码的形式储存的, 所有数据的运算都是以补码进行的,正数的原码

原码 : 最高位是符号位,0代表正数,1代表负数,非符号位为该数字绝对值的二进制。

x=1100110,则[X]原=01100110,

x=-1100111,则[X]原=11100111,

反码: 正数的反码与原码一致,负数的反码是对原码按位取反,只是最高位(符号位)不变。

x=1100110,则[X]反=01100110,

x=-1100111,则[X]反=10011000,

补码: 正数的补码与原码一致,负数的补码是对原码按位取反加1,符号位不变。

x=1100110,则[X]补=01100110,

x=-1100111,则[X]补=10011001,

为何要使用原码, 反码和补码
首先, 因为人脑可以知道第一位是符号位, 在计算的时候我们会根据符号位进行加减。 但是对于计算机, 加减乘数已经是最基础的运算,,设计得尽量简单。计算机辨别"符号位"显然会让计算机的基础电路设计变得十分复杂! 于是人们想出了将符号位也参与运算的方法.。我们知道,根据运算法则减去一个正数等于加上一个负数,即: 1-1 = 1 + (-1) = 0 ,所以机器可以只有加法而没有减法, 这样计算机运算的设计就更简单了。

我们来看原码的相加减,如下:

计算十进制的表达式: 1-1=0

二进制的表达式:1 - 1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2

如果用原码表示,让符号位也参与计算,显然对于减法来说,结果是不正确的。这也就是为何计算机内部不使用原码表示一个数。为了解决原码做减法的问题,

出现了反码,如下所示:

计算十进制的表达式:1-1=0,
二进制的表达式:1 - 1 = 1 + (-1) =[0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0

发现如果用反码计算减法,结果是正确的。而唯一的问题其实就出现在"0"这个特殊的数值上。虽然人们理解上+0和-0是一样的,但是0带符号是没有任何意义的。而且会有[0000 0000]原和[1000 0000]原两个编码表示0。于是补码的出现, 解决了0的符号以及两个编码的问题。

1-1 = 1 + (-1) = [0000 0001]补 + [1111 1111]补 = [0000 0000]补=[0000 0000]原

这样0用[0000 0000]表示, 而以前出现问题的-0则不存在了

位运算符

效率高,内存消耗少, 在某些情况中,位操作可以避免或者减少在一个数据结构上需要进行循环的次数,并且可以成倍的效率提升,因为位操作是并行处理的。但是位操作的代码比较难以编写和维护。

程序中的所有数据在计算机内存中都是以二进制的形式储存的。位运算就是直接对整数在内存中的二进制位进行操作,C语言提供了6个位操作运算符, 这些运算符只能用于整型操作数

按位与

使用规则:两个二进制操作数对应位同为1 结果位才为1,其余情况为0;

按位或

使用规则:两个二进制操作数对应位只要有一个为1结果位就为1,其余情况为0;

按位异或

使用规则:两个二进制操作数对应位相同为0,不同为1;

按位取反

使用规则:一个二进制操作数,对应位为0,结果位为1;对应位为1,结果位为0;

这里稍微有些麻烦,做下解释: 十进制 1 的二进制表示为0000 0001 每位都取反为1111 1110 这是内存中的保存形式。我们读取的十进制是根据原码来读取,而在内存中,数值都是以二进制补码形式存储的。正数的补码和原码一样,
负数的补码得到过程:原码转反码再转补码,负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1

位运算应用场景

判断奇偶(按位与)

偶数: 的二进制是以0结尾 8 -> 1000 10 -> 1010
奇数: 的二进制是以1结尾 9 -> 1001 11 -> 1011

任何数和1进行&操作,得到这个数的最低位
1000 &0001 ----- 0000 // 结果为0, 代表是偶数
1011 &0001 ----- 0001 // 结果为1, 代表是奇数

权限系统(按位或)
enum Unix { 
	S_IRUSR = 256,// 100000000 用户可读 
	S_IWUSR = 128,// 10000000 用户可写 
	S_IXUSR = 64,// 1000000 用户可执行 
	S_IRGRP = 32,// 100000 组可读 
	S_IWGRP = 16,// 10000 组可写 
	S_IXGRP = 8,// 1000 组可执行 
	S_IROTH = 4,// 100 其它可读 
	S_IWOTH = 2,// 10 其它可写
	S_IXOTH = 1 // 1 其它可执行 
 };

假设设置用户权限为可读 可写printf("%d\n", S_IRUSR | S_IWUSR); // 384 // 110000000

交换两个数的值(按位异或)
a = a^b; b = b^a; a = a^b;
乘法和除非(按位左移,按位右移)

按位左移: 把整数a的各二进位全部左移n位,高位丢弃,低位补0 由于左移是丢弃最高位,0补最低位,所以符号位也会被丢弃,左移出来的结果值可能会改变正负性
规律: 左移n位其实就是乘以2的n次方

2<<1; //相当于 2 *= 2 // 4 
0010<<01002<<2; //相当于 2 *= 2^2; // 8

按位右移: 把整数a的各二进位全部右移n位,保持符号位不变为正数时, 符号位为0,最高位补0为负数时,符号位为1,最高位是补0或是补1(取决于编译系统的规定)
规律: 快速计算一个数除以2的n次方

2>>1; //相当于 2 /= 2 // 1 
0010>>00014>>2; //相当于 4 /= 2^2 // 1

变量内存分析

内存模型是线性的(有序的)
对于 32 机而言,最大的内存地址是2^32次方bit(4294967296)(4GB)
对于 64 机而言,最大的内存地址是2^64次方bit(18446744073709552000)(171亿GB)

CPU 读写内存:

CPU 在运作时要明确三件事:

  • 存储单元的地址(地址信息)
  • 器件的选择,读 or 写 (控制信息)
  • 读写的数据 (数据信息)

如何明确这三件事情:

  • 通过地址总线找到存储单元的地址
  • 通过控制总线发送内存读写指令
  • 通过数据总线传输需要读写的数据

地址总线: 地址总线宽度决定了CPU可以访问的物理地址空间(寻址能力)

  • 例如: 地址总线的宽度是1位, 那么表示可以访问 0 和 1的内存
  • 例如: 地址总线的位数是2位, 那么表示可以访问 00、01、10、11的内存

数据总线: 数据总线的位数决定CPU单次通信能交换的信息数量

  • 例如: 数据总线:的宽度是1位, 那么一次可以传输1位二进制数据
  • 例如: 地址总线的位数是2位,那么一次可以传输2位二进制数据

控制总线: 用来传送各种控制信号

写入流程

  • CPU 通过地址线将找到地址为 FFFFFFFC 的内存
  • CPU 通过控制线发出内存写入命令,选中存储器芯片,并通知它,要其写入数据
  • CPU 通过数据线将数据 12 送入内存 FFFFFFFC 单元中

读取流程

  • CPU 通过地址线将找到地址为 FFFFFFFC 的内存
  • CPU 通过控制线发出内存读取命令,选中存储器芯片,并通知它,将要从中读取数据
  • 存储器将 FFFFFFFC 号单元中的数据 12 通过数据线送入 CPU寄存器中

变量的存储原则

  • 先分配字节地址大内存,然后分配字节地址小的内存(内存寻址是由大到小)
  • 变量的首地址,是变量所占存储空间字节地址(最小的那个地址 )
  • 低位保存在低地址字节上,高位保存在高地址字节上

char类型内存存储细节

char是C语言中比较灵活的一种数据类型,称为“字符型” , char类型变量占1个字节存储空间,共8位除单个字符以外, C语言的的转义字符也可以利用char类型存储

char型数据存储原理: 计算机只能识别0和1, 所以char类型存储数据并不是存储一个字符, 而是将字符转换为0和1之后再
存储正是因为存储字符类型时需要将字符转换为0和1, 所以为了统一, 老美就定义了一个叫做ASCII表的东东ASCII表中定义了每一个字符对应的整数

char ch1 = 'a';
printf("%i\n", ch1); // 97
char ch2 = 97; 
printf("%c\n", ch2); // a

char类型注意点

  1. char类型占一个字节, 一个中文字符占3字节(unicode表),所有char不可以存储中文 char c = '我'; // 错误写法
  2. 除转义字符以外, 不支持多个字符 char ch = 'ab'; // 错误写法
  3. char类型存储字符时会先查找对应的ASCII码值, 存储的是ASCII值, 所以字符6和数字6存储的内容不同
char ch1 = '6'; // 存储的是ASCII码 64
char ch2 = 6; // 存储的是数字 6

signed和unsigned

首先要明确的:signed int等价于signed,unsigned int等价于unsigned, signed和unsigned的区别就是它们的最高位是否要当做符号位,并不会像short和long那样改变数据的长度,即所占的字节数。

signed:表示有符号,也就是说最高位要当做符号位。但是int的最高位本来就是符号位,因此signed和int是一样的,signed等价于signed int,也等价于int。signed的取值范围是-2^31 ~ 2^31- 1

unsigned:表示无符号,也就是说最高位并不当做符号位,所以不包括负数。因此unsigned的取值范围是:0000 0000 0000 0000 0000 0000 0000 0000 ~ 1111 1111 1111 1111 1111 1111 1111 1111,也就是0 ~ 2^32 - 1

修饰符号的说明符可以和修饰长度的说明符混合使用, 相同类型的说明符不能混合使用

signed short int num1 = 666;  //正常
signed unsigned int num2 = 666; // 报错

点赞 -收藏-关注-便于以后复习和收到最新内容有其他问题在评论区讨论-或者私信我-收到会在第一时间回复感谢,配合,希望我的努力对你有帮助^_^

免责声明:本文部分素材来源于网络,版权归原创者所有,如存在文章/图片/音视频等使用不当的情况,请随时私信联系我。

相关文章