嵌入式校招_面试经验大全【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##秋招##面经#
全部评论
大哥写的真好,一键三连了
1 回复 分享
发布于 2023-07-06 11:52 河北
顶 深受大哥的启迪
1 回复 分享
发布于 2023-07-06 20:05 天津
来啦来啦😛,前排沙发🛋️
点赞 回复 分享
发布于 2023-07-06 12:10 上海

相关推荐

评论
17
56
分享
牛客网
牛客企业服务