3.3 按位异或
按位异或的规则是:对应的二进制位上相同为0,相异为1
我们来看一段代码:
#include <stdio.h> int main() { int a = 3; int b = -5; int c = a ^ b;//按位异或:对应的二进制位上相同为0,相异为1 //00000000 00000000 00000000 00000011 -- 3的补码 //10000000 00000000 00000000 00000101 -- -5的原码 //11111111 11111111 11111111 11111010 -- -5的反码 //11111111 11111111 11111111 11111011 -- -5的补码 //00000000 00000000 00000000 00000011 -- 3的补码 //11111111 11111111 11111111 11111000 -- ^的结果 printf("%d\n", c); return 0; }
按照这个规则我们来分析一下 c 的补码是:
11111111 11111111 11111111 11111011 -- -5的补码
00000000 00000000 00000000 00000011 -- 3的补码
11111111 11111111 11111111 11111000 -- ^的结果
这里的符号位为 1 (代表是负数),因此这需要将补码转为原码。
原码:10000000 00000000 00000000 00001000
c 的原码转化为 10 进制就是 -8。
效果展示:
3.4 练习
Q:不创建临时变量怎么交换两个数?
法一:
#include <stdio.h> //a^a = 0; 相同为 0 //a^0 = a; 相异为 1 //3^5^3 = 5 二进制码来做 //3^5^3 = 5 //异或支持交换律 int main() { int a = 3; int b = 5; a = a ^ b;//1式 b = a ^ b;// 将1式代入此式当中,得出b = a a = a ^ b;// 将1式带入此式当中,得出a = b printf("a = %d, b = %d\n", a, b); return 0; }
效果展示:
法二:
#include <stdio.h> int main() { int a = 3; int b = 5; a = a + b;//8 b = a - b;//8 - 5 a = a - b;//8 - 3 printf("a = %d, b = %d\n", a, b); return 0; }
效果展示:
但是这种方法存在弊端,如果 a 和 b 过大 a+b 就会存在栈溢出的问题,存在缺陷。因此法一是符合要求的方法,但是只是针对不创建临时变量来讲更合理。^ 交换两个数存在 3 个弊端:
1.是 ^ 只针对整形可以交换,浮点型不可以;
2.它的可读性比较差:
3.如果可以创建临时变量,效率会高点。
Q:编写代码实现:求一个整数存储在内存中二进制中 1 的个数
#include <stdio.h> int main() { int n = 0; scanf("%d", &n); int count = 0;//计数器 int i = 0; while (i<32) { if(1 == (n&1)) count++; n = n >> 1; i++; } printf("%d\n", count); return 0; }
我们来分析一下此代码是如何实现问题的:
1的补码与任何数与只会得到两个数:0 / 1(因为按位与的规则是:有 0 得 0 ,同为 1 得 1。1 的二进制序列除了最后一个数字是 1 之外全是 0 ,因此任何数与 1 按位与只会得到 0 / 1)。按照这个规律我们展开想,假设我们输入了一个 3 ,3 的二进制序列是00000000 00000000 00000000 00000011,我们让它与 1 的二进制序列00000000 00000000 00000000 00000001,按位与,如果得到的数是 1 ,我们就让计数器+1,然后对 3 进行右移,得到下一个二进制数再进行按位与操作,得到的是 1 就再让计数器+1,循环 32 次,这样就可以得出一个整数的二进制序列到底有多少个 1。
4、赋值操作符
赋值操作符是一个很棒的操作符,它可以让你得到一个你满意的值。也就是你可以对自己重新赋值。
初始化的 = 与赋值的 = 是不一样的,创建变量的时候的 = 是初始化,后面对变量的值进行更改用到的 = 才是赋值操作符。
int weight = 120;//体重,这里的=不是赋值操作符,这是初始化 weight = 90;//不满意就重新赋值 //赋值操作符可以连续使用,比如: int a = 10; int x = 0; int y = 20; a = x = y+1;//连续赋值,先把y+1赋给x,再把x赋给a //这样的可读性并不好,因此下面的代码可读性会更好一点 x = y + 1; a = x; //这样的写法更加清晰明了,而且易于调试
4.1 复合赋值符
+= -= *= /= %= >>= <<= &= |= ^=
这些运算符都可以写成符合的效果。
例如:
#include <stdio.h> int main() { int a = 3; a = a + 3; a += 3; a = a >> 3; a >>= 3; a = a * 3; a *= 3; a = a / 3; a /= 3; return 0; }
5、单目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
我们这里只讲一部分:
5.1 ! -- 逻辑反操作
一般是在判断句中做判断调价的调整。
例如:
int main() { int flag = 1; if(flag)//flag为真就进入if语句里面 { } if(!flag)//这里就是flag为假的时候进到if语句里面去 //(flag为假,!flag就变成了真,这样就实现了为假进入) { } return 0; }
5.2 & -- 取地址操作符
一般用于指针,取某个地址。
例如:
#include <stdio.h> int main() { int a = 3; &a;//取出变量a的地址 int arr[10] = { 0 }; &arr;//取出数组arr的地址(这里是拿到整个数组的地址) return 0; }
5.3 * -- 解引用操作符(间接访问操作符)
#include <stdio.h> int main() { int a = 3; int* p = &a;//p里面存放变量a的地址,*p是通过p里面存放的地址找到a,因此*p也就是a *p = 5;//承接上一句,这里看到的是改*p,实则通过地址改了a printf("%d\n", a); return 0; }
效果展示:
因此&和*是一对,一般都是在一起用的。
5.4 sizeof
sizeof是计算所占内存空间大小或者类型的长度的(单位:字节)。
例如:
#include <stdio.h> int main() { int a = 3; printf("%d\n", sizeof(a));//我们这里打印的就是a所占空间的大小 //a两端的()可以不写 printf("%d\n", sizeof a); printf("%d\n", sizeof(int));//我们这打印a的类型是一样的,因为a是整形 int arr[10] = { 0 }; printf("%d\n", sizeof(arr));//sizeof可以计算数组总大小 //引申 printf("%d\n", sizeof(arr[0]));//还可以计算数组首元素大小 printf("%d\n", sizeof(arr)/sizeof(arr[0]));//也可以计算数组长度 return 0; }
效果展示:
总结:sizeof在计算变量的时候可以省略( ),但是计算类型的时候不可以省略。
5.4.1 sizeof练习
我们看一段代码给出答案:
#include <stdio.h> void test1(int arr[]) { printf("%d\n", sizeof(arr)); } void test2(char arr[]) { printf("%d\n", sizeof(arr)); } int main() { int arr1[10] = { 0 }; char arr2[10] = { 0 }; printf("%d\n", sizeof(arr1)); printf("%d\n", sizeof(arr2)); test1(arr1); test2(arr2); return 0; }
我们分析一下:
sizeof(数组名)求的是整个数组的大小,因此打印的一二行分别是40,10。
数组传参传的是首元素地址,是一个指针,test1,test2函数计算的都是指针的大小,在X32的环境下就是4,X64的环境下就是8。我们来看看结果:
效果展示:
5.5 ~ -- 二进制按位取反
我们以一段代码来看看~ 操作符:
#include <stdio.h> int main() { int a = 0; //二进制 //a的补码: 00000000 00000000 00000000 00000000 //~a的补码:11111111 11111111 11111111 11111111 //~a的反码:10000000 00000000 00000000 00000000 //~a的原码:10000000 00000000 00000000 00000001 //原码转化为10进制数为-1 printf("%d\n", ~a); return 0; }
分析:
a的二进制补码:00000000 00000000 00000000 00000000
~a的二进制补码:11111111 11111111 11111111 11111111
~a的二进制反码:10000000 00000000 00000000 00000000
~a的二进制原码:10000000 00000000 00000000 00000001
~a的二进制原码转化为10进制数是 -1。
效果展示:
Q:如何将3的二进制序列00000000 00000000 00000000 00000011中标记的0改为1,最后再改回来?
我们来分析一下这个问题:
我们按照此分析来写代码:
#include <stdio.h> int main() { int a = 3; //处理 a |= (1 << 3); printf("%d\n", a); //回到原来的a a &= (~(1 << 3)); printf("%d\n", a); return 0; }
效果展示: