马克嵌入式软件
目录【专栏一】嵌入式校招指南作者机械硕士,从零开始自学嵌入式软件,21届秋招进入国内芯片大厂。 从自身转行经历来看,网上嵌入式学习路线的资料少之又少,大多千篇一律且复制粘贴。 而嵌入式入行门槛高,技能树要求多,学习难度非常大,没有有效的方法指导,很容易迷失方向,错过校招。 在此专栏分享我的校招从零开始转行经验,听我给你娓娓道来~专栏链接 https://www.nowcoder.com/creation/manager/columnDetail/MWZkkj1.专栏大纲&写在前面2.转行概述3.前期准备4.自学教材推荐_基础知识5.自学教材推荐_笔试准备6.开发板&项目7.简历8.行业&公司9.城市&岗位10.消费&工业电子类公司未完待续……【专栏二】嵌入式校招_面试经验大全嵌入式软件校招的常见问题,应付校招面试的速效救心丸,你值得拥有! 嵌入式的知识太多太杂,不知道面试经常问哪些? 书上说的知识点太抽象,没有一定的基础很难理解? 别怕,本专栏用通俗的语言和比喻,为你讲清楚!  包含C语言、计算机组成原理、操作系统、数据结构与算法及计算机网络等,详见大纲。1.【C语言】【1_变量】https://www.nowcoder.com/discuss/4917738635256791042.【C语言】【2_关键字】https://www.nowcoder.com/discuss/4975623096282767363.【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因为自己和自己异或,必然每一位都是相同的,结果自然是02.A^0=A与0异或,保持原值。那么开始我们的计算。先做一下分析:A^0=A^(B^B)=(A^B)^Bint 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请帮忙点赞、评论+收藏,是对我最大的支持~感谢!!!
点赞 16
评论 3
全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务