前言:首先,我的博客主要记录的是部分容易遗忘,但是比较重要或是值得了解的知识点。很多基本概念我并不会阐述,所以它更适合用来复习;其次,这些知识点是从我自己的角度出发整理的,因此我认为的重点可能和你不同,有的内容可能不完整或有错误,希望大家多多包涵,如果能指出我的错误,我会非常感激!
必要知识点储备:
(1)二进制、八进制、十进制、十六进制之间的转换技巧
十进制转二进制:
对于比较大的数字,有一个通用的求解方法
如1000,将它先除以2得到500,余数0,记录这个0
将500除以2得到250,余数0,记录这个0
将250除以2得到125,余数0,记录这个0
将125除以2得到62,余数1,记录这个1
将62除以2得到31,余数0,记录这个0
将31除以2得到15,余数1,记录这个1
将15除以2得到7,余数1,记录这个1
将7除以2得到3,余数1,记录这个1
将3除以2得到1,余数1,记录这个1
将1除以2得到0,余数1,记录这个1,停止计算
最后从下到上按顺序写出记录的数字1111 1010 00,这个数就是二进制的1000
二进制转八进制、十六进制:
我们知道2的3次方是8,2的4次方是16,所以3个二进制位就可以表示成一个八进制位
如01 101 011从右向左每三个分一组,换算成十进制数字,这个数字对应八进制的数字
011表示3,101表示5,01表示1,所以这个数字的八进制表示是0135,最前面的0是八进制标志
十六进制同理,每四位一组
如0110 1011换算后是0x6b
(2)原码、补码的相互转换
原码转为补码将原码除符号位取反后+1(负数)
补码转为原码将补码除符号位取反后+1(负数),这种方法更方便
补码存在的意义是使加法减法统一,CPU只能进行加法运算
操作符易忘知识点和易错法则
其中简单的我不会阐述,我会抓出易忘点和易错点
1.移位操作符(左移操作符和右移操作符细则区分)
左移操作符执行规则:左边抛弃,右边补0。
因此对于负数来说,左移后就变成了正数(有且只有这一种可能)
右移操作符的执行规则就比左移操作符的要复杂,有两种操作方式;
(1)逻辑右移:左边用0填充,右边丢弃(类似于左移操作符)
(2)算数右移:左边用该值的原符号位填充,右边丢弃。这意味着在算数右移的规则下,负数右移后还是负数,而逻辑右移下,移后的数一定是正数。
对于绝大部分编译器来说,都是采用算数右移,仍有少部分编译器采用逻辑右移。
但是对于所有编译器来说,左移规则相同。这里一定要注意区分!
注意:a >> -1这种操作未被定义,不要用!
2.位操作符
操作的对象都是二进制的补码,且只能是整数,意思是你输入的17会被转换成二进制再操作
如果你输入-1,则实际操作数是-1的补码
& 按位与:只要有0就是0,全为1才是1
| 按位或:只要有1就是1,全为0才是0
^ 按位异或:相同为0,相异为1(易混)
~ 按位取反:将所有位按位取反,不管它是不是符号位,取反后得到的还是补码,因此如果要打印取反后的数字,还要进行一次补码到原码的转换
相关操作技巧
(1)^ 按位异或:
a ^ a == 0 ; a ^ 0 == a
接下来通过这个代码仔细体会异或操作
#include <stdio.h>
int main()
{
int a = 100;
int b = 200;
printf("a=%d b=%d\n", a, b);
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("a=%d b=%d\n", a, b);
return 0;
}
结果
重要思考方式:a ^ b相当于一个密码,储存了a和b的信息,如果我拿到b的信息,我就能破解a(将a ^ b这个密码与b异或);如果我拿到a的信息,我就能破解b(将a ^ b这个密码与a异或)
(2)& 按位与
a & 1 == 1说明a的二进制位中最低位是1
a & 1 == 0说明a的二进制位中最低位是0
注意比较的依然是补码
有一个表达式值得琢磨:
n = n & (n - 1);
11111 - n;
11110 - n - 1;
11110 - n;//第一次操作后
11101 - n - 1;
11100 - n;//第二次操作后
11011 - n - 1;
11000 - n;//第三次操作后
10111 - n - 1;
10000 - n;//第四次操作后
01111 - n - 1;
00000 - n;//第五次操作后
每执行一次,最右边的1就会被去掉,其余位不受影响
(3)& 和 | 修改二进制数操作
合理利用& 和 |可以调整二进制数
让0变成1用|,使用时保证你要变的那位是1,其余都是0,用1 << n来实现
让1变成0用&,使用时保证你要变得那位是0,其余都是1,用 ~( 1 << n )来实现
(4)自定义类型、结构成员访问
注意:int [10]是一个自定义类型,int arr[10]中arr是数组名
struct也是自定义类型,包含成员列表。它相当于一个模具,在实际使用时创建结构体变量。
struct在访问成员时,可用.name表示,点操作符有两个操作数,使用方式是“结构体名.成员名”。第一个不需要时可以不写。
初始化结构体变量时,要么按成员列表顺序创建,要么使用.name初始化。
(5)操作符的优先级、结合性
优先级:一个表达式中运算符优先级高的先执行
一定是相邻操作符才有所谓的优先级之分
如果相邻两个操作符优先级相同,则由结合性决定先后,一般是从左到右结合,但少数从右往左结合,比如赋值操作符=,具体的可查表,这里不再阐述
记住括号优先级最高,其次有自增自减操作符++和--,单目操作符+和-(决定数字正负)。
逗号表达式优先级最低,其次是赋值操作符。
注意:表达式的执行顺序可以被确定,但值的调用顺序不确定,所以当一个表达式中调用的值多次变化时会导致计算结果不确定。
如(i++)+(i++)+(i++)这个表达式的值在不同编译器下有不同解,因为i的调用可能是在++前,也可能是在++后
(6)整型提升(类型小于整型)和算术转换(类型大于整型且类型不同)
缺省整型指的是默认整型int
char中也可以存储数字,字符也是属于整型家族的,可以进行整型运算,只是能存储的大小很有限,你可以把它理解成更短的整型,取值是-128~127
有符号数的整型提升是按符号位提升,因此负数的整型提升补的全是1而不是0,char也有有无符号之分,但默认是不是有符号char得看系统,这个标准没有被定义。
注意整型提升以及之后参与运算的是补码,使用printf打印%d也会发生整型提升,是先提升补码,再转换为原码打印。
算术转换
当int和float相加时,int向float转换(向上转换)后再相加,加后存储的值仍为float类型
一般来说,算术转换的趋势是整型向浮点型转换,由字节少的类型向字节多的类型转换