跟涛哥一起学嵌入式 第04集:一道面试题,测出你的C语言功底

大家好,我是涛哥,欢迎阅读《跟涛哥一起学嵌入式》第04集,今天聊聊面试题。

嵌入式C语言面试题中,大家经常会看到宏定义的考题。比如:定义一个宏,求两个数中的最大数。别小看这个考题,虽然简单,但是它却陷阱不断,时刻在考验着你的C语言编程功底!根据你的答案,面试官对你的印象肯定不一样。那下面我们看看各个不同版本的答案吧。

合格

对于学过C语言的同学,写出这个宏基本上不是什么难事,使用条件运算符就能完成:

#define  MAX(x,y)  x > y ? x : y

这是最基本的C语言语法,如果连这个也写不出来,估计场面会比较尴尬。面试官为了缓解尴尬,一般会对你说:小伙子,你很棒,回去等消息吧,有消息,我们会通知你!这时候,你应该明白:不用再等了,赶紧把这篇文章看完,接着面下家。这个宏能写出来,也不要觉得你很牛X,因为这只能说明你有了C语言的基础,但还有很大的进步空间。比如,我们写一个程序,验证一下我们定义的宏是否正确:

#define MAX(x,y) x > y ? x : y
int main(void)
{
    printf("max=%d",MAX(1,2));
    printf("max=%d",MAX(2,1));
    printf("max=%d",MAX(2,2));
    printf("max=%d",MAX(1!=1,1!=2));
    return 0;
}

测试程序么,我们肯定要把各种可能出现的情况都测一遍。这不,测试第4行语句,当宏的参数是一个表达式,发现实际运行结果为max=0,跟我们预期结果max=1不一样。这是因为,宏展开后,就变成了这个样子:

printf("max=%d",1!=1>1!=2?1!=1:1!=2);

因为比较运算符 > 的优先级为6,大于 !=(优先级为7),所以展开的表达式,运算顺序发生了改变,结果就跟我们的预期不一样了。为了避免这种展开错误,我们可以给宏的参数加一个小括号()来防止展开后,表达式的运算顺序发生变化。这样的宏才能算一个合格的宏:

#define MAX(x,y) (x) > (y) ? (x) : (y)

中等

上面的宏,只能算合格,但还是存在漏洞。比如,我们使用下面的代码测试:

#define MAX(x,y) (x) > (y) ? (x) : (y)
int main(void)
{
    printf("max=%d",3 + MAX(1,2));
    return 0;
}

在程序中,我们打印表达式 3 + MAX(1, 2) 的值,预期结果应该是5,但实际运行结果却是1。我们展开后,发现同样有问题:

3 + (1) > (2) ? (1) : (2);

因为运算符 + 的优先级大于比较运算符 >,所以这个表达式就变为4>2?1:2,最后结果为1也就见怪不怪了。此时我们应该继续修改这个宏:

#define MAX(x,y) ((x) > (y) ? (x) : (y))

使用小括号将宏定义包起来,这样就避免了当一个表达式同时含有宏定义和其它高优先级运算符时,破坏整个表达式的运算顺序。如果你能写到这一步,说明你比前面那个面试合格的同学强,前面那个同学已经回去等消息了,我们接着面试下一轮。

良好

上面的宏,虽然解决了运算符优先级带来的问题,但是仍存在一定的漏洞。比如,我们使用下面的测试程序来测试我们定义的宏:

#define MAX(x,y) ((x) > (y) ? (x) : (y))
int main(void)
{
    int i = 2;
    int j = 6;
    printf("max=%d",MAX(i++,j++));
    return 0;
}

在程序中,我们定义两个变量 i 和 j,然后比较两个变量的大小,并作自增运算。实际运行结果发现max = 7,而不是预期结果max = 6。这是因为变量 i 和 j 在宏展开后,做了两次自增运算,导致打印出 i 的值为7。

遇到这种情况,那该怎么办呢? 这时候,语句表达式就该上场了。我们可以使用语句表达式来定义这个宏,在语句表达式中定义两个临时变量,分别来暂储 i 和 j 的值,然后进行比较,这样就避免了两次自增、自减问题。

#define MAX(x,y)({         int _x = x;            int _y = y;            _x > _y ? _x : _y; })
int main(void)
{
    int i = 2;
    int j = 6;
    printf("max=%d",MAX(i++,j++));
    return 0;
}

在语句表达式中,我们定义了2个局部变量_x、_y来存储宏参数 x 和 y 的值,然后使用 _x 和 _y 来比较大小,这样就避免了 i 和 j 带来的2次自增运算问题。

你能坚持到了这一关,并写出这样自带BGM的宏,面试官心里可能已经有了给你offer的意愿了。但此时此刻,千万不要骄傲!为了彻底打消面试官的心理顾虑,我们需要对这个宏继续优化。

优秀

在上面这个宏中,我们定义的两个临时变量数据类型是int型,只能比较两个整型的数据。那对于其它类型的数据,就需要重新再定义一个宏了,这样太麻烦了!我们可以基于上面的宏继续修改,让它可以支持任意类型的数据比较大小:

#define MAX(type,x,y)({         type _x = x;            type _y = y;            _x > _y ? _x : _y; })
int main(void)
{
    int i = 2;
    int j = 6;
    printf("max=%d\n",MAX(int,i++,j++));
    printf("max=%f\n",MAX(float,3.14,3.15));
    return 0;
}

在这个宏中,我们添加一个参数:type,用来指定临时变量 _x 和 _y 的类型。这样,我们在比较两个数的大小时,只要将2个数据的类型作为参数传给宏,就可以比较任意类型的数据了。如果你能在面试中,写出这样的宏,面试官肯定会非常高兴,他一般会跟你说:小伙子,稍等,待会HR会跟你谈待遇问题。

还能不能更牛逼?

如果你想薪水拿得高一点,待遇好一点,此时不应该骄傲,你应该大手一挥:且慢,我还可以更牛逼!

上面的宏定义中,我们增加了一个type类型参数,来兼容不同的数据类型,此时此刻,为了薪水,我们应该把这个也省去。如何做到?使用typeof就可以了,typeof是GNU C新增的一个关键字,用来获取数据类型,我们不用传参进去,让typeof直接获取!

#define max(x, y) ({        typeof(x) _x = (x);     typeof(y) _y = (y);     (void) (&_x == &_y);    _x > _y ? _x : _y; })

在这个宏定义中,使用了typeof关键字用来获取宏的两个参数类型。干货在(void) (&x == &y);这句话,简直是天才般的设计!一是用来给用户提示一个警告,对于不同类型的指针比较,编译器会给一个警告,提示两种数据类型不同;二是,当两个值比较,比较的结果没有用到,有些编译器可能会给出一个warning,加个(void)后,就可以消除这个警告!

此刻,面试官看到你的这个宏,估计会倒吸一口气:乖乖,果然是后生可畏,这家伙比我还牛逼!你等着,HR待会过来跟你谈薪水!

恭喜你,拿到offer了!

本文根据《C语言嵌入式Linux高级编程》第5期:C标准及Linux内核中的C语法扩展部分视频改编。《跟涛哥一起学嵌入式》,会持续跟大家分享嵌入式相关技术、学习方法、学习路线、求职面试等,有兴趣可加入嵌入式技术交流群:475504428,或微信公众号:宅学部落(armlinuxfun)。或者关于51CTO学院我的个人主页:http://edu.51cto.com/lecturer/10824150.html

原文地址:http://blog.51cto.com/zhaixue/2135747

时间: 2024-11-06 18:12:49

跟涛哥一起学嵌入式 第04集:一道面试题,测出你的C语言功底的相关文章

跟涛哥一起学嵌入式 -- 第01集:say you say me

大家好,此时此刻,2018俄罗斯世界杯已暂告一段落,16强已经产生,卫冕冠军德国队提早回家,阿根廷起死回生,C罗一个30多高龄的年纪,还在坚持健身,一身腱子肉,在球场上挥洒青春,演绎着帽子戏法,给观众带来一场场精彩的视觉盛宴.世界杯的最大魅力之处也许就在于其不确定性. 在这足球之夜,<跟涛哥一起学嵌入式>专栏也开始了,关于嵌入式学习.路线.就业.面试相关的问题,我会坚持一点一点地写下去.C罗,一个85后,跟我差不多的年龄,还坚持健身,还这么拼命,我们还有什么理由给自己的不坚持开脱呢? 为什么写

跟涛哥一起学嵌入式 第02集:工作还是考研?

大家好,我是涛哥,今天我们聊聊第一个话题:工作还是考研. 工作与考研,是每个大三大四学生都要去纠结一下的问题,也是一个很难做抉择的话题.对自己的现状不满意,蹉跎了岁月,感觉自己学得不够多,没学到什么技能:对自己能否找到工作.自己能否胜任未来的工作没有自信:高考失利,进了一所非211/985大学,在大学浑浑噩噩已经混了一半时间了,每天吃鸡推搭.小玛利亚,不想再这样颓废下去,想通过考研改变自己.这些都会滋生你考研的念头,希望通过研究生能改变自己.提高自己. 不仅在校学生.工作1~2年后的职场新兵,也

跟涛哥一起学嵌入式 第03集:嵌入式要从u-boot移植学起?

大家好,我是涛哥,欢迎阅读<跟涛哥一起学嵌入式>第3集.俄罗斯仲夏夜,世界杯依旧如火如萘.球场上,夕阳下,梅西没落的身影.C罗的黯淡离场,并没有打击大家太多的热情.战火依旧,老兵不死,梅罗时代是否快要终结?然而新一代巨星冉冉升起,风起云涌.在看球的同时,学习充电也不能落下,今天我们聊聊第3个话题:学习嵌入式,一定要从u-boot移植学起吗? 嵌入式真要从U-boot移植学起? 故事缘起嵌入式技术群(475504428)一位嵌入式学员遇到的问题:这位学员想在他的开发板上移植最新版本的U-boot

跟涛哥一起学嵌入式 第05集:一道程序改错题,测出你的嵌入式功底

大家好,欢迎阅读<跟涛哥一起学嵌入式>第05集,我们今天讨论一下中断的基本概念. 中断,是嵌入式开发中经常使用的一个功能,也是嵌入式工程师必须要掌握的一个概念:CPU和外设通信时,一般都采用中断的形式异步通信,可以大大提高CPU资源的利用率.而你对中断的理解,到底有多少呢?不要急,一道程序改错题,就可以测出你的嵌入式系统功底. 比如,我们在嵌入式ARM裸机平台上,要实现一个MP3播放器,要求实现如下功能:当按键按下时,可以播放.暂停.播放下一首.上一首.为此,我们设计一个按键中断服务程序,当有

跟涛哥一起学嵌入式第11集:一个实现锁机制非常有意思的宏

QQ群(宅学部落)有位学员问了一个很奇怪的宏,觉得很有意思,特拿来分享,它的定义如下: 我们知道,宏定义其实就是为了方便,给一串代码字符串定义一个别名.有时候字符串过于复杂,我们可以分多行书写,然后使用逻辑连接符"\"连接起来,表示一个完整的字符串.但是分析上面的宏定义,你会发现它分别定义了2个宏,但是呢,又使用了一对大括号括起来,很有欺骗性:看起来很像语句表达式,但是呢,有没有小括号括起来,是不是很奇怪?调用的时候,使用方法更是奇怪,如果我们单独使用AA()或BB()调用,你会发现编

[转] 嵌入式入门学习法(写给惠州学院电子系学嵌入式的同学们)

我是08届惠州学院电子系的毕业生,现在从事于linux嵌入式研发工作.本人写这一篇所谓的“嵌入式入门学习法”,是因为自己一开始学习嵌入式的时候,电子系里几乎没有人可以带自己入门或者教授相关学习方法,基本上都是自己摸索着学习,可想而知,这过程蛋疼的程度让人想死.所以希望通过这一年来自己的学习,整理出一条学习路线给以后电子系的师弟们作参考. 废话不多说,进入正题.首先大家应该理解两个概念,什么是处理器,什么是控制器.相信很多电子系的学生,一开始是从玩51单片机开始进入电子研发领域的,再者就是AVR单

【涛哥带你看DC】江西测绘双活数据中心

2014年3月10日,江西省地理信息公共服务平台(政务版)正式上线运行.在半年之后的10月15日,涛哥来到江西省测绘地理信息局仔细了解支持此公共服务平台的后台数据中心的情况. 江西省测绘地理信息局今年8月才搬的家,新办公大楼位于南昌一处偏僻的地方(周围都在建商品房,估计很快也会热闹起来).大楼很气派,但看了内部就知道,很多地方还没有最后完工.新机房也建在这幢大楼里,与旧办公楼的机房构成了相距10多公里的"双活数据中心". 据江西省测绘地理信息局网络中心的负责人介绍,当初在数据中心的建设

涛哥的Python脚本工具箱之批量替换目录所有指定扩展名的文件中的指定字符串

今天发布刚完成的涛哥的Python脚本工具箱之批量替换目录所有指定扩展名的文件中的指定字符串,命令行参数处理改用目前比较好用的argparse库,Python代码如下: #!/usr/bin/python2.7 # -*- encoding: UTF-8 -*- # Copyright 2014 [email protected] """replace old string with new string from all files in path 批量替换目录所有指定扩展

学嵌入式开发好吗

学嵌入式开发好吗 嵌入式系统可以说是当前最热门最有发展前途的IT应用领域之一,嵌入式系统通常会用到在一些特定的专用设备上,特别是随着消费家电的智能化,嵌入式更显重要, 对于刚毕业的学生来讲,投身于一个嵌入式开发职业行业里注定是一个漫长的过程,只要你潜心修炼一定可以成为一名优秀的嵌入式工程师,目前华清远见星创客的应届毕业生,月薪都达到了1w+,那么学嵌入式开发哪里好,下面来一一列举嵌入式的好处: 1.目前国内外这方面的人是很稀缺的.一方面,是因为这一领域入门门槛较高,不仅要懂底层软件,而且必须懂得