C语言每日一练 —— 第20天:位运算_补全函数 xor(x, y) ,该函数的功能是求 0-1 符号串 x 和y 的“异或”运算结果(1)

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Golang全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注go)
img

正文

1

(

2

)

1

(

2

)

=

0

(

2

)

1

(

2

)

0

(

2

)

=

1

(

2

)

1

0

(

2

)

1

(

2

)

=

1

(

2

)

0

(

2

)

0

(

2

)

=

0

(

2

)

1_{(2)} - 1_{(2)} = 0_{(2)} \ 1_{(2)} - 0_{(2)} = 1_{(2)} \ 10_{(2)} - 1_{(2)} = 1_{(2)} \ 0_{(2)} - 0_{(2)} = 0_{(2)}

1(2)​−1(2)​=0(2)​1(2)​−0(2)​=1(2)​10(2)​−1(2)​=1(2)​0(2)​−0(2)​=0(2)​  而我们今天要讲的位运算正是基于二进制展开的。

三、位运算简介

位运算可以理解成对二进制数字上的每一个位进行操作的运算。位运算分为 逻辑(布尔)位运算符 和 移位位运算符。
逻辑位运算符又分为 位与(&)、位或(|)、异或(^)、按位取反(~);移位位运算符分为 左移(<<) 和 右移(>>)。
如图所示:

1、位与的定义

位与运算符是一个二元的位运算符,也就是有两个操作数,表示为x & y
位与运算会对操作数的每一位按照如下表格进行运算,对于每一位只有 0 或 1 两种情况,所以组合出来总共

2

2

=

4

2^2 = 4

22=4 种情况。

左操作数右操作数结果
000
010
100
111

通过这个表,我们得出一些结论:
1)无论是 0 或 1,只要位与上 1,还是它本身;
2)无论是 0 或 1,只要位与上 0,就变成 0;

#include <stdio.h>
int main() {
int a = 0b1010; // (1)
int b = 0b0110; // (2)
printf(“%d\n”, (a & b) ); // (3)
return 0;
}

  • (

1

)

(1)

(1) 在C语言中,以0b作为前缀,表示这是一个二进制数。那么a的实际值就是

(

1010

)

2

(1010)_2

(1010)2​。

  • (

2

)

(2)

(2) 同样的,b的实际值就是

(

0110

)

2

(0110)_2

(0110)2​;

  • (

3

)

(3)

(3) 那么这里a & b就是对

(

1010

)

2

(1010)_2

(1010)2​ 和

(

0110

)

2

(0110)_2

(0110)2​ 的每一位做表格中的&运算。

所以最后输出结果为:2
因为输出的是十进制数,它的二进制表示为:

(

0010

)

2

(0010)_2

(0010)2​。注意:这里的 前导零 可有可无,作者写上前导零只是为了对齐以及让读者更加清楚位与的运算方式。

2、位与运算符的简单应用

1)奇偶性判定

我们判断一个数是奇数还是偶数,往往是通过取模%来判断的,如下:

#include <stdio.h>
int main() {
if(5 % 2 == 1) {
printf(“5是奇数\n”);
}
if(6 % 2 == 0) {
printf(“6是偶数\n”);
}
return 0;
}

然而,我们也可以这么写:

#include <stdio.h>
int main() {
if(5 & 1) {
printf(“5是奇数\n”);
}
if( (6 & 1) == 0 ) {
printf(“6是偶数\n”);
}
return 0;
}

这是利用了奇数和偶数分别的二进制数的特性,如下表所示:

-二进制末尾位
奇数1
偶数0

所以,我们对任何一个数,通过将它和 0b1进行位与,结果为零,则必然这个数的二进制末尾位为0,根据以上表就能得出它是偶数了;否则,就是奇数。

2)取末五位

给定一个数,求它的二进制表示的末五位,以十进制输出即可。

这个问题的核心就是:我们只需要末五位,剩下的位我们是不需要的,所以可以将给定的数 位与上0b11111,这样一来就直接得到末五位的值了。代码实现如下:

#include <stdio.h>
int main() {
int x;
scanf(“%d”, &x);
printf(“%d\n”, (x & 0b11111) );
return 0;
}

3)消除末尾五位

给定一个 32 位整数,要求消除它的末五位。

还是根据位与的性质,消除末五位的含义,有两层:
1)末五位,要全变成零;
2)剩下的位不变;
那么,根据位运算的性质,我们需要数,它的高27位都为1,低五位都为 0,则这个数就是:

(

11111111111111111111111111100000

)

2

(11111111111111111111111111100000)_2

(11111111111111111111111111100000)2​  但是如果要这么写,代码不疯掉,人也会疯掉,所以一般我们把它转成十六进制,每四个二进制位可以转成一个十六进制数,所以得到十六进制数为0xffffffe0。代码实现如下:

#include <stdio.h>
int main() {
int x;
scanf(“%d”, &x);
printf(“%d\n”, (x & 0xffffffe0) );
return 0;
}

4)2的幂判定

请用一句话,判断一个正数是不是2的幂。

如果一个数是 2 的幂,它的二进制表示必然为以下形式:

1

00…00

k

1\underbrace{00…00}_{\rm k}

1k

00…00​​ 这个数的十进制值为

2

k

2^k

2k。那么我们将它减一,即

2

k

1

2^k-1

2k−1 的二进制表示如下(参考二进制减法的借位):

0

11…11

k

0\underbrace{11…11}_{\rm k}

0k

11…11​​于是 这两个数位与的结果为零,于是我们就知道了如果一个数

x

x

x 是 2 的幂,那么x & (x-1)必然为零。而其他情况则不然。
所以本题的答案为:

(x & (x-1)) == 0

3、位或的定义

位或运算符是一个二元的位运算符,也就是有两个操作数,表示为x | y
位或运算会对操作数的每一位按照如下表格进行运算,对于每一位只有 0 或 1 两种情况,所以组合出来总共

2

2

=

4

2^2 = 4

22=4 种情况。

左操作数右操作数结果
000
011
101
111

通过这个表,我们得出一些结论:
1)无论是 0 或 1,只要位或上 1,就变成1;
2)只有当两个操作数都是0的时候,才变成 0;

#include <stdio.h>
int main() {
int a = 0b1010; // (1)
int b = 0b0110; // (2)
printf(“%d\n”, (a | b) ); // (3)
return 0;
}

  • (

1

)

(1)

(1) 在C语言中,以0b作为前缀,表示这是一个二进制数。那么a的实际值就是

(

1010

)

2

(1010)_2

(1010)2​。

  • (

2

)

(2)

(2) 同样的,b的实际值就是

(

0110

)

2

(0110)_2

(0110)2​;

  • (

3

)

(3)

(3) 那么这里a | b就是对

(

1010

)

2

(1010)_2

(1010)2​ 和

(

0110

)

2

(0110)_2

(0110)2​ 的每一位做表格中的|运算。

所以最后输出结果为:14
因为输出的是十进制数,它的二进制表示为:

(

1110

)

2

(1110)_2

(1110)2​。

4、位或运算符的简单应用

1)设置标记位

【例题1】给定一个数,判断它二进制低位的第 5 位,如果为 0,则将它置为 1。

这个问题,我们很容易联想到位或。
我们分析一下题目意思,如果第 5 位为 1,不用进行任何操作;如果第 5 位为 0,则置为 1。言下之意,无论第五位是什么,我们都直接置为 1即可,代码如下:

#include <stdio.h>
int main() {
int x;
scanf(“%d”, &x);
printf(“%d\n”, x | 0b10000);
return 0;
}

2)置空标记位

【例题2】给定一个数,判断它二进制低位的第 5 位,如果为 1,则将它置为 0。

这个问题,我们在学过 《算法零基础100讲》(第42讲) 位运算 (位与) 入门 以后,很容易得出这样一种做法:

#include <stdio.h>
int main() {
int x;
scanf(“%d”, &x);
printf(“%d\n”, x & 0b11111111111111111111111111101111);
return 0;
}

其它位不能变,所以位与上1;第5位要置零,所以位与上0;
这样写有个问题,就是这串数字太长了,一点都不美观,而且容易写错,当然我们也可以转换成 十六进制,转换的过程也有可能出错。
而我们利用位或,只能将第5位设置成1,怎么把它设置成0呢?

我们可以配合减法来用。分成以下两步:
  1)首先,强行将低位的第5位置成1;
  2)然后,强行将低位的第5位去掉;

(

1

)

(1)

(1) 步可以采用位或运算,而第

(

2

)

(2)

(2) 步,我们可以直接用减法即可。代码实现如下:

#include <stdio.h>
int main() {
int x;
int a = 0b10000;
scanf(“%d”, &x);
printf(“%d\n”, (x | a) - a );
return 0;
}

注意:直接减是不行的,因为我们首先要保证那一位为 1,否则贸然减会产生借位,和题意不符。

5、异或运算符的定义

异或运算符是一个二元的位运算符,也就是有两个操作数,表示为x ^ y
异或运算会对操作数的每一位按照如下表格进行运算,对于每一位只有 0 或 1 两种情况,所以组合出来总共

2

2

=

4

2^2 = 4

22=4 种情况。

左操作数右操作数结果
000
011
101
110

通过这个表,我们得出一些结论:
1)两个相同的十进制数异或的结果一定为零。
2)任何一个数和 0 的异或结果一定是它本身。
3)异或运算满足结合律和交换律。

#include <stdio.h>
int main() {
int a = 0b1010; // (1)
int b = 0b0110; // (2)
printf(“%d\n”, (a ^ b) ); // (3)
return 0;
}

  • (

1

)

(1)

(1) 在C语言中,以0b作为前缀,表示这是一个二进制数。那么a的实际值就是

(

1010

)

2

(1010)_2

(1010)2​。

  • (

2

)

(2)

(2) 同样的,b的实际值就是

(

0110

)

2

(0110)_2

(0110)2​;

  • (

3

)

(3)

(3) 那么这里a ^ b就是对

(

1010

)

2

(1010)_2

(1010)2​ 和

(

0110

)

2

(0110)_2

(0110)2​ 的每一位做表格中的^运算。

所以最后输出结果为:12。因为输出的是十进制数,它的二进制表示为:

(

1100

)

2

(1100)_2

(1100)2​。

6、异或运算符的应用

1)标记位取反

【例题1】给定一个数,将它的低位数起的第 4 位取反,0 变 1,1 变 0。

这个问题,我们很容易联想到异或。我们分析一下题目意思,如果第 4 位为 1,则让它异或上 0b1000就能变成 0;如果第 4 位 为 0,则让它异或上 0b1000就能变成 1,也就是无论如何都是异或上 0b1000,代码如下:

#include <stdio.h>
int main() {
int x;
scanf(“%d”, &x);
printf(“%d\n”, x ^ 0b1000);
return 0;
}

2)变量交换

【例题2】给定两个数

a

a

a 和

b

b

b,用异或运算交换它们的值。

这个是比较老的面试题了,直接给出代码:

#include <stdio.h>
int main() {
int a, b;
while (scanf(“%d %d”, &a, &b) != EOF) {
a = a ^ b; // (1)
b = a ^ b; // (2)
a = a ^ b; // (3)
printf(“%d %d\n”, a, b);
}
return 0;
}

我们直接来看

(

1

)

(1)

(1) 和

(

2

)

(2)

(2) 这两句话,相当于b等于a ^ b ^ b,根据异或的几个性质,我们知道,这时候的b的值已经变成原先a的值了。
而再来看第

(

3

)

(3)

(3) 句话,相当于a等于a ^ b ^ a,还是根据异或的几个性质,这时候,a的值已经变成了原先b的值。
从而实现了变量ab的交换。

3)出现奇数次的数

【例题3】输入

n

n

n 个数,其中只有一个数出现了奇数次,其它所有数都出现了偶数次。求这个出现了奇数次的数。

根据异或的性质,两个一样的数异或结果为零。也就是所有出现偶数次的数异或都为零,那么把这

n

n

n 个数都异或一下,得到的数就一定是一个出现奇数次的数了。

#include <stdio.h>
int main() {
int n, x, i, ans;
scanf(“%d”, &n);
ans = 0;
for(i = 0; i < n; ++i) {
scanf(“%d”, &x);
ans = (ans ^ x);
}
printf(“%d\n”, ans);
return 0;
}

光天化日学C语言(14)- 位运算 & 的应用
光天化日学C语言(15)- 位运算 | 的应用
光天化日学C语言(16)- 位运算 ^ 的应用
光天化日学C语言(17)- 位运算 ~ 的应用
光天化日学C语言(18)- 位运算 << 的应用

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

b

b

b,用异或运算交换它们的值。

这个是比较老的面试题了,直接给出代码:

#include <stdio.h>
int main() {
int a, b;
while (scanf(“%d %d”, &a, &b) != EOF) {
a = a ^ b; // (1)
b = a ^ b; // (2)
a = a ^ b; // (3)
printf(“%d %d\n”, a, b);
}
return 0;
}

我们直接来看

(

1

)

(1)

(1) 和

(

2

)

(2)

(2) 这两句话,相当于b等于a ^ b ^ b,根据异或的几个性质,我们知道,这时候的b的值已经变成原先a的值了。
而再来看第

(

3

)

(3)

(3) 句话,相当于a等于a ^ b ^ a,还是根据异或的几个性质,这时候,a的值已经变成了原先b的值。
从而实现了变量ab的交换。

3)出现奇数次的数

【例题3】输入

n

n

n 个数,其中只有一个数出现了奇数次,其它所有数都出现了偶数次。求这个出现了奇数次的数。

根据异或的性质,两个一样的数异或结果为零。也就是所有出现偶数次的数异或都为零,那么把这

n

n

n 个数都异或一下,得到的数就一定是一个出现奇数次的数了。

#include <stdio.h>
int main() {
int n, x, i, ans;
scanf(“%d”, &n);
ans = 0;
for(i = 0; i < n; ++i) {
scanf(“%d”, &x);
ans = (ans ^ x);
}
printf(“%d\n”, ans);
return 0;
}

光天化日学C语言(14)- 位运算 & 的应用
光天化日学C语言(15)- 位运算 | 的应用
光天化日学C语言(16)- 位运算 ^ 的应用
光天化日学C语言(17)- 位运算 ~ 的应用
光天化日学C语言(18)- 位运算 << 的应用

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-XCEfhIga-1713295552536)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 26
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值