嵌入式校招_面试经验大全【C语言】【3_位运算】
目录
【专栏一】嵌入式校招指南
作者机械硕士,从零开始自学嵌入式软件,21届秋招进入国内芯片大厂。
从自身转行经历来看,网上嵌入式学习路线的资料少之又少,大多千篇一律且复制粘贴。
而嵌入式入行门槛高,技能树要求多,学习难度非常大,没有有效的方法指导,很容易迷失方向,错过校招。
在此专栏分享我的校招从零开始转行经验,听我给你娓娓道来~
专栏链接 https://www.nowcoder.com/creation/manager/columnDetail/MWZkkj
1.专栏大纲&写在前面
2.转行概述
3.前期准备
4.自学教材推荐_基础知识
5.自学教材推荐_笔试准备
6.开发板&项目
7.简历
8.行业&公司
9.城市&岗位
10.消费&工业电子类公司
未完待续……
【专栏二】嵌入式校招_面试经验大全
嵌入式软件校招的常见问题,应付校招面试的速效救心丸,你值得拥有!
嵌入式的知识太多太杂,不知道面试经常问哪些? 书上说的知识点太抽象,没有一定的基础很难理解?
别怕,本专栏用通俗的语言和比喻,为你讲清楚!
包含C语言、计算机组成原理、操作系统、数据结构与算法及计算机网络等,详见大纲。
1.【C语言】【1_变量】https://www.nowcoder.com/discuss/491773863525679104
2.【C语言】【2_关键字】https://www.nowcoder.com/discuss/497562309628276736
3.【C语言】【3_位运算】https://www.nowcoder.com/discuss/505894349847224320
————————————————————
【问】介绍一下enum的用法
【答】enum是一种枚举数据类型,描述的是一组常数的集合
【解析】
- 背景知识
我们经常会在代码中使用各种各样的数字来参与计算/赋值,那么如何更好地统一使用这些数字呢?
假设小明同学参加秋招,某大厂发offer时给出的基本工资是根据个人情况而不同的,但是年终奖都是按照较差、普通、优秀、杰出来给予0、2、4、6个月来的。
此时小明想要计算自己能够拿到年包的各种可能性,应该怎么办呢?
如果按照以下的方法计算,也不是不行,但是总感觉哪里怪怪的。。
我们会发现
1.case后面表示绩效等级的数字,无法直观地体现它代表的含义
2.年终奖的数额(多少个月),无法表示所代表的绩效等级
那么,怎么样才能更直观地表示出这些呢?
对的!就是枚举~
我们将绩效等级和其所对应的年终奖数额都用枚举来表示,这样就非常直观明了~
#include <stdio.h> #include <stdint.h> enum { WORSE = 0, NORMAL, GOOD, OUTSTANDING }Bonus_Level; enum { WORSE_BONUS = 0, NORMAL_BONUS = 1, GOOD_BONUS = 3, OUTSTANDING_BONUS = 5 }Bonus; /* 小羽的嵌入式校招指南 */ uint32_t CalSalary(uint32_t SalaryBase, uint8_t PerformLevel) { uint32_t AnnualSalary = 0; switch (PerformLevel) { case WORSE: AnnualSalary = SalaryBase * (12 + WORSE_BONUS); printf("\nYour SalaryBase is:[%dk], the AnnualSalary with WORSE is:[%dk]!\n" , SalaryBase, AnnualSalary); break; case NORMAL: AnnualSalary = SalaryBase * (12 + NORMAL_BONUS); printf("\nYour SalaryBase is:[%dk], the AnnualSalary with NORMAL is:[%dk]!\n" , SalaryBase, AnnualSalary); break; case GOOD: AnnualSalary = SalaryBase * (12 + GOOD_BONUS); printf("\nYour SalaryBase is:[%dk], the AnnualSalary with GOOD is:[%dk]!\n" , SalaryBase, AnnualSalary); break; case OUTSTANDING: AnnualSalary = SalaryBase * (12 + OUTSTANDING_BONUS); printf("\nYour SalaryBase is:[%dk], the AnnualSalary with OUTSTANDING is:[%dk]!\n\n" , SalaryBase, AnnualSalary); break; default: printf("Error Perform Level!\n"); break; } return AnnualSalary; } int main(void) { uint32_t BaseSalary; //k uint32_t AnnualSalary; printf("\nPlease input your BaseSalary:"); scanf("%d",&BaseSalary); //WORSE CalSalary(BaseSalary, WORSE); //NORMAL CalSalary(BaseSalary, NORMAL); //GOOD CalSalary(BaseSalary, GOOD); //OUTSTANDING CalSalary(BaseSalary, OUTSTANDING); return 0; }
- 总结
根据C语言编程规范可知,代码的可读性是优先级极高的属性。
因为代码首先是要服务于人,能够让人读懂,其次才是它的性能。
因此在处理这类纯数字的情况时,使用枚举类型来代替纯数字,能够大大提高代码可读性~
【问】位或操作
【答】两个数据,按位进行“或|”运算。
运算规则:参加运算的两个对象只要有一个为1,其值为1。0与0位或等于0。
即 :0|0=0; 0|1=1; 1|0=1; 1|1=1;
负数按补码形式参加按位或运算。
【解析】
位操作的绝大部分场景下,数据都是无符号的。即unsigned。此处仅考虑无符号场景。
- 背景知识
在某些场景下(尤其是驱动岗位的寄存器中),我们仅需要表示某个状态的开或关,用0或者1表示即可。也就是说,仅需1bit即可。
然而我们知道,就算是变量类型bool,也是需要占据1Byte空间(即使它只能用来表示0和1)。
那么在某些需要大量表示状态位的场景下(比如表示8盏灯的开关状态),每个状态位都使用1Byte来表示,那么需要8Byte。
这对于资源极度紧张的嵌入式设备来说,会造成严重的资源浪费!
所以,聪明的先行者们就想到了使用变量中的每个bit来表示一个状态开关。
比如uint8_t Status = 21;那么6即是0001 0101。
如果现在需要表示8盏灯的状态,那么21就表示第1(bit0)、第3(bit2)和第5(bit4)盏灯是亮着的。
那么此时,仅用一个Byte,就可以表示8盏灯的状态,真的是方便呀~
- 移位
左移:将变量向左移位操作,高位溢出丢弃,低位补0。
int main(void) { uint8_t BulbStatus = 21; printf("\nThe original num:"); PrintToBin(8, &BulbStatus); /* 小羽的嵌入式校招指南 */ //左移2位 BulbStatus = BulbStatus << 2; printf("After Move 2 bits to the left:"); PrintToBin(8, &BulbStatus); //左移5位 BulbStatus = 21; BulbStatus = BulbStatus << 5; printf("After Move 5 bits to the left:"); PrintToBin(8, &BulbStatus); return 0; }
21的二进制是0001 0101。左移2位后等于84,二进制为0101 0100。左移5位后为160,二进制为1010 0000.
大家发现一个现象没有?
2^2=4,21*4=84。
2^5=32,21*32=672(超出char类型的[-127,128]),最终只留下672-(128*4)=160.(具体原理查看前面变量的文章——【问】*char型变量最大能表示的值?如果超出最大值怎么处理?https://www.nowcoder.com/discuss/491773863525679104)
我们可以发现,对一个数左移1位就是乘以2,左移n位就是乘以2^n。
在CPU中运行位移运算比乘除法快得多,所以这也是优化程序运行速度的一个技巧哦~
右移:将变量向右移位操作,低位溢出丢弃,高位补0。
21的二进制是0001 0101。右移2位后等于5,二进制为0000 0101。右移4位后为1,二进制为0000 0001.
2^2=4,21/4后取整等于5。2^4=16,21/16后取整等于1。
所以,对一个数右移1位就是除以2后取整,左移n位就是除以2^n后取整~
- 对数据的指定位设置为1
因为位或运算的特性,0与任何数与的结果都是该数本身。
因此我们只需将我们想要设置的位跟1做与运算,即可将该位设置为1.
现在有个uchar型变量,用来表示8盏灯的开关状态。
如果现在灯全是暗着的,即0B0000 0000,我想要表示第2(bit1)和第3(bit2)盏灯为打开状态,应该怎么做呢?
int main(void) { uint8_t BulbStatus = 0; /* 小羽的嵌入式校招指南 */ //设置第2(bit1)盏灯状态为打开 BulbStatus |= 1 << 1; //设置第3(bit2)盏灯状态为打开 BulbStatus |= 1 << 2; printf("\nAfter Open The 2nd&3rd Bulb:"); PrintToBin(8, &BulbStatus); return 0; }
我们通过位或操作,就达到了给指定bit置1的目的。
当然我们可以不需要每次只给一盏灯赋予状态,我们能不能一次性给两盏灯的状态都打开呢?
答案是当然可以~
我们可以将我们想要打开的灯的位置信息,先用位或计算出来,然后再位或操作写入BulbStatus。
- 羽你俗说
位或操作就是能够精准地将我们想要的一个或多个位置1,屏蔽掉无关位。
举个栗子,如果我想要在我的羽毛球线上,DIY一个特别点的logo,最好的方法是什么呢?
手动一点点地去画不太现实,因为那个对画画技术要求太高,很容易走形。
那么最好的方法是什么呢?用一块板子蒙住,只留下我们希望画的图案的部分。
见下图:
用记号笔,对着中间的空白地方直接画,最后就会在羽毛球线上面留下一个可爱的只因(手动滑稽)~
只有我们选中的(中间空白处)部分才会被涂画,其余部分不受影响,这就是位或操作与现实生活的联系~
————————————————
【问】位与操作
【答】两个数据,按位进行“与&”运算。
运算规则:两个位的数据同时为“1”,结果才为“1”,否则为0。
即:0&0=0; 0&1=0; 1&0=0; 1&1=1;
负数按补码形式参加按位与运算。
【解析】
- 常见应用
(1)判断奇偶性
a & 1 = 1;则a为奇数
b & 1 = 0;则a为偶数
这个很好理解,用位与运算来判断bit0的数值。
从bit1开始往后,都是代表了2^N。只有bit0需要判断奇偶性。
(2) 对数据的指定位设置为0
因为位与运算的特性,0与任何数与的结果都是0。
因此我们只需将我们想要设置的位跟0做与运算,即可将该位设置为0。
还是以上面的uchar型变量表示8盏灯的开关状态为例。
第2(bit1)、第3(bit2)盏灯为打开,现在将第3(bit2)盏灯关闭。
具体如下:
将1左移2位,其值为0B0000 0100。再对其取反,得到0B1111 1011。
此时我们就得到了bit2为0,其余bit为1的数(1和任何数做与&操作,结果是该值本身,所以我们只改变了值为0的那一位的数值)。
关闭bit2后,就只剩下bit1的灯为打开状态啦~
(3)取指定位的数值
可以获取某个数指定1个或多个位的数值。
还是以前面的例子,假设现在第2(bit1)、第3(bit2)、第6(bit5)盏灯状态为打开,即BulbStatus=38,0B0010 0110。
如果想要获取到底那几盏灯是打开的,应该怎么做呢?
int main(void) { uint8_t BulbStatus = 0; uint8_t GetStatus = 0; int i; /* 小羽的嵌入式校招指南 */ //设置第2(bit1)、第3(bit2)盏灯状态为打开 BulbStatus |= (1 << 1) | (1 << 2); printf("\nAfter Open The 2nd&3rd Bulb:"); PrintToBin(8, &BulbStatus); //依次获取每盏灯的开关状态 for (i = 0; i < 8; i++) { if (0 != (BulbStatus & (1 << i)) ) { printf("The No.%d status is On\n\n",i + 1); } } return 0; }
上面依次获取0-7位的数值,从而判断灯的开关状态。
这就是位与的用法~
在驱动岗位中,经常会使用这种方法来获取寄存器的值~当然,也会用到位或来设置寄存器的值~
- 羽你俗说
①指定位置0
同上述位或操作,只擦除指定位置的涂画
②获取指定位的数值
大家都用过答题卡吧?如下图这种:
在没有读卡器的时候,老师们是怎么快速批改试卷的呢?
答案是:把正确答案全部凿洞,然后直接放在学生的答题卡上面,看看洞内是否有涂,就可以快速批改试卷啦~
咱们的位与操作也是如此,提前选定指定位,查看这些位的数值~
【问】异或操作
【答】两个数据,按位进行“异或^”运算。
运算规则:两个位的数据相同(异)时为“0”,不同时为“1”。
即:0^0=0; 0^1=1; 1^0=1; 1^1=0;
【解析】
- 与0异或,保留原值;与1异或,翻转数值
还以前面的灯为例,如果我想做出跑马灯的效果。
第一步,将第1、3、5、7盏灯翻转;
第二步,循环将每一盏灯在量灭之间转换。
这个灯光秀有点抽象哈~但是大概的意思已经表达清楚了~
- 如何不适用第三个变量,交换两个变量的值
这个问题经常出现在校招面试中,常见的有三种解法,咱们这里只讲通过位运算的解法。
首先,由异或的特性,咱们推出一个结论:
1.B^B=0
因为自己和自己异或,必然每一位都是相同的,结果自然是0
2.A^0=A
与0异或,保持原值。
那么开始我们的计算。
先做一下分析:A^0=A^(B^B)=(A^B)^B
int main(void) { /* 小羽的嵌入式校招指南 */ int A = 10;//B = 1010 int B = 12; //B = 1100; printf("\nIn the beginning\nOA=%d,OB=%d\n\n",A,B); //最初的A用OA表示,最初的B用OB表示 //A = OA^OB A = A ^ B; //B = (OA^OB)^OB = OA^0 = OA B = A ^ B; //A = (OA^OB)^OA = OB^0 = OB A = A ^ B; //完成互换值 printf("\nAt the end\nA=%d,B=%d\n\n",A,B); }
切记当前的A和B代表的是原始的A和B怎样组合起来的,就不容易搞混啦~
- 找出唯一重复出现的数
题:有N个数,其中包含1个仅重复1次的数值和从1~N-1个不重复的数。找出该数值。
例:[1,2,3,4,5,5,6,7,8,9],10个数的数组中,包含重复1次的5和不重复的12346789。
答:使用该数组中的每个数与两个包含1~N-1的不重复数组,所有数值进行异或运算,结果就是重复的数值。
因为A^A=0,两个数组中重复的数值异或后得到0。
而重复的数字跟0异或,得到的还是该数值。
怎么样,神奇不?如果不用异或运算,你要怎么算呢?
————————————————————
更多内容,持续更新中!!!
【觉得有用的小伙伴们可以订阅一下专栏,后续还有更多文章哦~ 😀 】
作者其他专栏
【嵌入式校招指南_完整学习路线】https://www.nowcoder.com/creation/manager/columnDetail/MWZkkj
请帮忙点赞、评论+收藏,是对我最大的支持~感谢!!!
#校招##嵌入式##offer##秋招##面经#