)
首先声明此博客无照搬抄袭融入个人理解我所使用的编译器是VS2022目录1.操作符的分类2.二进制和进制之间的转换3.原码、反码、补码4.移位操作符5.位操作符、|、^、~6.单目操作符7.逗号表达式一、操作符的分类算术操作符-*/%移位操作符位操作符|^~赋值操作符、、-、*、/、%、、、、|、^单目操作符一元操作符!、-、、、*、sizeof、、--、~关系操作符、、、、、!逻辑操作符||条件操作符三目运算符exp1 ? exp2 : exp3逗号操作符 下标引用操作符[]函数调用操作符()结构成员访问操作符 .-二、二进制和进制之间的转换2.1 二进制转为十进制二进制转化为十进制是有权重存在的从右往左分别是2⁰、2¹、2²、2³、2⁴、2⁵、2⁶、2⁷......其它进制转化为十进制时二进制转权重就是二的n次方八进制转权重就是八的n次方十六进制转权重就是十六的n次方n从0开始逐个递增比如我们计算1101从右往左加以权重1×2⁰ 0×2¹ 1×2² 1×2³ 1 0 4 8 13再比如计算八进制555从右往左加以权重5×8⁰ 5×8¹ 5×8² 5 40 320 365所以当我们会了二进制转为十进制后八和十六转十进制也就会了总结其它进制转十进制时乘权相加2.2十进制转化为二进制转二进制就模二取余如下图计算方法模二一直模到商为0将得出的余数从下往上列出就是结果总结十进制转二进制就模二取余转八进制就模八取余转十六进制就模十六取余2.3二进制转八进制首先每个二进制位只能存0或者1而2*2*2等于8也就是2³8所以三个二进制位就可以表达八种结果分别是000、001、010、011、100、101、110、111也就对应着八进制的八个数字字符0~7所以三个二进制位就可以表达所有的八进制的数字符号了最后我们在将二进制转为八进制位时我们需要从右往左三个三个分隔出来转权重为2的n次方例如二进制110101分成110和101分别乘权相加110:1*2²1*2¹0*2º42061011*2²0*2¹1*2º4015所以此八进制结果是65又例如二进制10110分成010最左边不足三位时高位补零和 110 分别乘权相加010:0*2²1*2¹0*2º02021101*2²1*2¹0*2º4206所以此八进制结果是262.4二进制转十六进制知道了二进制转八进制的原理那我们也就相当知道了二进制转十六进制的原理因为2⁴16所以可知四个二进制位就可以组合16种结果刚好对应十六进制的16个数字符号0~F计算时我们就需要从右往左每四个四个分隔开来计算权重为2的n次方例如二进制11011010分为1101和1010分别乘权相加11011*2³1*2²0*2¹1*2º1310101*2³0*2²1*2¹0*2º10所以此十六进制的结果是DA当二进制分隔开最左边不够四位需高位补0再进行转换和上面二进制转八进制例子一样在此不再演示2.5八进制转二进制八进制转化为二进制一位八进制数转化二进制对应三位二进制数也就是一个八进制数转换成二进制数会变成三个二进制数可以将八进制的每个数看作十进制数再利用十进制转二进制的方法分别模二取余模到0后将余数从下往上排列得到每一位数对应的二进制数当然你也可以通过查表查询一位八进制位和三位二进制位的对应关系来得出逐个排列好后再逐个放入原先八进制时的字符位置就可以了例如八进制53转化成二进制八进制 5 → 101 八进制 3 → 011所以53(O) 101 011(B)2.6十六进制转二进制十六进制转二进制将十六进制的每一位分别模二取余结果将余数从下往上排列其它的进制转换十六进制转八进制可以先将十六进制转化为十进制或者二进制再将十进制或者二进制转化为八进制八进制转十六进制也可以先将八进制转化为十进制或者二进制再将十进制或者二进制转化为十六进制到此进制转换就讲完了三、原码、反码、补码整数在内存中的存储都是由二进制形式来存储的而二进制的表现方式有三种分别是原码、反码、补码。一个整型整数对应的内存为4个字节的大小也就是32个bit位可以存32的二进制数其中最高位是符号位0代表正数1代表负数二进制数中最高位是指最左边所存的数最右边是最低位也就是从左往右分别是从高位到低位原码直接将整数按照正负数翻译成二进制得到的数反码原码符号位不变其余数全部取反补码反码1不过注意正数的正反补码都是相同的负数的正反补码表示方式都不一样计算机当中整数的存储都是由二进制形式来存储并且是以补码的形式存在整数的运算和存储都是以二进制补码的形式进行的四、移位操作符注意移位操作符的操作数只能是整数而且移动的位数只能是大于等于零的整数不能是负数或者小数4.1左移操作符乘2移位规则左边抛弃、右边补0#include stdio.h int main() { int num 10; int n num 1; printf(n %d\n, n); return 0; }左移操作符演示可以看见一共32个格子整体向左移一位后就把最左边多出来的数丢掉符号位也丢本来是00001010左移后变成了00010100右边补了一个0运行截图可以看到num本来是10变成了20由此可以看出左移操作符具有将整数乘2的效果这是因为二进制转化为十进制是有权重的从低位到高位分别是2⁰、2¹、2²、2³、2⁴、2⁵、2⁶、2⁷.....例如上面10二进制是00001010为1*2³1*2¹8210我们向左移1位之后变成了00010100为(1*2³)*2(1*2¹)*28*22*282*2,对比移位之前的每个数都乘了2根据结合律那就是总体乘24.2右移操作符除2右移操作符移位规则分两种逻辑位移:左边补0右边抛弃算术位移左边补符号位右边抛弃#include stdio.h int main() { int num 10; printf(%d\n, num1); return 0; }运行截图一般情况下都是算数位移如果是unsigned intunsigned char类型的整数就是逻辑位移可以看到num本来是10变成了5由此可以看出右移操作符具有除2的效果五、位操作符、|、^、~5.1位操作符详解用于整数二进制补码的运算 按位与 | 按位或 ^ 按位异或 ~ 按位取反按位与操作符两边都是1才为1有0则为0|按位或操作符有1则为1两边都是0才为0^:按位异或操作符相同为0相异为1~按位取反操作符全部取反包括符号位注他们的操作数必须是整数。#include stdio.h int main() { int num1 -3; int num2 5; printf(%d\n, num1 num2); printf(%d\n, num1 | num2); printf(%d\n, num1 ^ num2); printf(%d\n, ~0); return 0; }运行截图我们来分析一下为什么会有这串结果我们把-3和5用八位数的二进制来做演示5原码、反码、补码:00000101-3原码10000011-3反码11111100-3补码111111010原码、反码、补码:00000000-35计算方式如下有0则为0两边都是1才为1结果00000101换算成十进制为5-3|5计算方式如下有1则为1两边都是0才为0结果11111101换算成反码为先取反10000010再加1:10000011换算成十进制为-3-3^5计算方式如下相同为0相异为1结果11111000换算成反码为先取反10000111再加1:10001000换算成十进制为-8~0计算方式如下包括符号位全部取反结果为11111111换算成反码为10000000再加1:10000001换算成十进制为-15.2位操作符编程题5.2.1题一1不能创建临时变量第三个变量实现两个整数的交换#include stdio.h int main() { int a 10, b 20; printf(交换前a%d b%d\n, a, b); a新a a原a ^ b原b; //内的内容为批注请读者自行删除 b a新a ^ b原b; a a新a ^ b新b; printf(交换后a%d b%d\n, a, b); return 0; }运行截图首先我们要知道一个数a如果异或自身结果会为0如果异或0结果还是aa^a0a^0a我们来分析这个代码首先aa^b没有问题接着第二步ba^b我们已知aa^b把a代入第二步的式子得ba^b^ba^(b^b)a^0a所以第二步式子就能把a的值赋给了b接着第三步a a新a ^ b新b,由于b已经改变新ba原a所以式子也可以写为aa新a^a原a所以我们再次将第一步中的a新a a原a ^ b原b式子代入第三步式子得aa原a值^b原b^a原a值a原a值^a原a值^b原bb所以第三步转化为ab原b至此完成了整数的交换5.2.2题二2编写代码实现求⼀个整数存储在内存中的二进制中1的个数方法一#include stdio.h int main() { int num 10; int count 0;//计数 while (num) { if (num % 2 1) count; num num / 2; } printf(%d\n, count); return 0; }这个方法看似没问题逻辑也正确算10的二进制中有几个1count用于计数通过while循环如果num%21就代表这个二进制的最右边那位数是1我们通过联想上我上述写的十进制转二进制的计算过程来理解这句话然后我们再用numnum/2相当于右移操作符把二进制数整体往右移然后再重新进入循环直到num0就说明里面的1全部挪到了最右方挪完了每判断一次如果是二进制位数1count就自增最后打印count但是这个代码有个致命的弱点他只能判断正数负数进来没法正确判断我们来分析一下导致其输入负数会错误的地方首先假如输入的是负数比如-5那么ifif (num % 2 1)这句话会直接失效因为-5%2一个负数%2结果必然是负数不会是1从这就错了再而我们看循环条件num是每次numnum/2来达到这个循环条件但是我们发现-5/2-2.5,除法规则向0取整为-2num此时等于-2然后-2/2-1,可-1/2-0.5,我们向0取整结果是0结束循环循环都结束了count仍然是0总结上述两点错误带来的后果负数进去if判断条件失效程序结束count不会自增count永远为0所以不管输入什么负数结果都打印0方法二#include stdio.h int Find1(int n) { int count 0; for (int i 0; i 32; i) { if (((n i)1)1) { count; } } return count; } int main() { int n -1; int ret Find1(n); printf(%d\n, ret); return 0; }运行截图这个代码的原理是已知整型数占4个字节32个bit位有32个二进制位所以我们把整数n不断右移右移32次每次都上1如果结果为1那就说明这个位置二进制数为1最终用count计数并且打印出来整数在内存中用二进制形式存储的1的个数方法三最优方法#include stdio.h int main() { int num -1; int count 0; while (num) { count; num num (num - 1); // 每次消去最右边的1 } printf(二进制中1的个数 %d\n, count); return 0; }上面的方法2执行了32次把二进制的每一位都判断了一遍而此代码只判断了有1的部分大大提升了程序执行的效率。num num (num - 1)的作用是每次消去一个1二进制减 1会把最右边的 1 变成 0把最右边1后面的 0 全变成 1比如101110101010把最右边的1变为0把1011最右边的1消去了然后101010011000把最右边的1变为0把最右边1后面的 0 变成了 1把第二个1消去了然后100001110000把最右边的1变为0把最右边1后面的 0 全变成 1所有1消去退出循环此时count刚好计数到1的个数所以我们使用num num (num - 1)这串代码效率就会很高5.2.3题三3将二进制位的某一指定位置置为0或113的2进制序列 00000000000000000000000000001101将第5位置为1后00000000000000000000000000011101将第5位再置为000000000000000000000000000001101#include stdio.h void Mod1(int* n,int k) { *n| (1 (k - 1)); //置1 } void Mod0(int* n, int k) { *n (~(1 (k - 1))); //置0 } int main() { int n 13; int k 0; scanf(%d, k); Mod1(n,k); printf(第%d位改成1后的数%d\n, k, n); Mod0(n,k); printf(第%d位改成0后的数%d\n, k, n); return 0; }运行截图关键操作置 1*n | (1 (k-1))置 0*n ~(1 (k-1))六、单目操作符取地址操作符取出某个变量在内存中的地址*解引用操作符搭配地址使用通过地址找到该地址所指向的内存单元里所存放的数据七、逗号表达式逗号表达式就是用逗号隔开的多个表达式逗号表达式从左到右依次计算整个表达式的最终结果是最后一个表达式算出来的结果逗号表达式 exp1,exp2,exp3,......expN我们来看几串代码1代码一int a 1; int b 2; int c (ab, ab10, a, ba1);//逗号表达式 c是多少我们来分析从左到右计算ab也就是12,为假表达式结果为0接着ab10,b2,所以a12第三个a12第四个表达式ba1b13逗号表达式的结果是最后一个表达式算出来的结果所以最终c等于132代码二if (a b 1, c a / 2, d 0)这个if语句中的判断条件用了一个逗号表达式其中逗号表达式的执行流程是从左到右依次执行ab1会执行ca/2会执行但最终决定if判断条件的就只有逗号表达式的最终结果d0来决定3代码三// 第一步先获取一个值 a get_val(); // 第二步对这个值做统计/记录 count_val(a); // 只要 a 0 就进循环 while (a 0) { //业务处理 //... // 再重新获取新值 a get_val(); // 再统计新值 count_val(a); }其实就是我们先调用get_val()获取a的值然后调用了一遍count_val(a)对a进行统计操作其实这两个具体是什么操作不重要重要的是我们在开头就调用了这两个函数接着如果a0我们就进入循环进行一些列代码处理后又再此执行a get_val()count_val(a)这两个函数这样写可以但是代码就显得有些冗余了因为这两个函数在循环外调用了一次在循环内又继续调用为了让代码更清晰简明我们可以这样改进4代码四while (a get_val(), count_val(a), a 0) { //业务处理 }用逗号表达式来改进这串代码的意思和代码三的意思一模一样都会先执行a get_val()和 count_val(a)然后最终这个逗号表达式的结果是a0,也就是会最终还是由a0这个表达式来决定是否进入循环也就对应着代码三while循环的判断条件。这样写代码不冗余逻辑更集中可读性更强