whywhy 发表于 2007-11-4 12:07

给师弟师妹的一些面试经验和面试资料

本帖最后由 whywhy 于 2010-2-20 11:58 编辑

我是2007届的毕业生,是计算机学院的。从去年12月初停止找工后就想在后院留下点东西,但是因为懒所以一直没有写。今天周末刚好有空上来后院看看,发觉师弟师妹又在忙着找工,希望下面这些能给正在找工的校友一下帮助。我是从2006年暑假就开始准备简历的,从2006年10月份开始找工,先后参加多场招聘会和宣讲会,投出简历估计有100多份,获得面试机会10多次(没有包括招聘会的),到12月初一共拿了三个offer。
经历篇
处女面——周立功单片机
经过了三个多星期的海投简历和多次的宣讲会之后,终于获得了一次笔试机会和面试机会。
广州周立功单片机发展有限公司
是在2006年10月28日(星期六)上午9:30在我们学校(大学城校区)教学4号楼101室开的宣讲会,(因为是处女面所以我的记得很清楚)周立功今年来的比较早,去听宣讲会的人不多,开始时公司的李公害怕去的人不多,叫我们快点发短信叫同学来听。在之过程中我抓住了机会和李公聊了一会(因为有师兄指点我开宣讲会的时候能做前点就坐前点最好是第二排,这样可以抓住机会和公司的人接近),可以说把自己推荐给他了,我跟他说我很向往他们公司还把我会的和我的优势(我本身是学软件的但是我的兴趣是在嵌入式这一块)都说给他听了,最后他跟我说你等下笔试完把试卷直接交到他那里,怕我笔试过不了,我跟他说笔试应该没有问题(吹水的,其实心理没有底的,毕竟是第一次)。因为我从三的时候就开始有了解周公这个公司,也对这个公司比较有兴趣。李公大概说了半个钟就开始笔试了。
我是笔试软件那份试卷的,笔试的内容主要是C和C++方面的知识。笔完试故意等到人走到差不多的时候把试卷交给了李功,然后跟他说我还有点东西想让他了解的,之后就大概跟他吹了十多分钟左右。
过后就一直在等他的面试通知,觉得自己机会挺大的,李公说下周一(10月30日)之前会通知的,可是我等到星期三还没有等到通知,以为没有希望了,心理有点被打击的感觉,心想周立功招人的要求真的这么高,之后也没有多想了。星期五哪天刚好女朋友来我这里,晚上五点多的车,哪天就五点起来了。没想到10多的时候接到周立功的面试通知电话,下午两点钟面试,人力资源小姐说漏通知我了。接完电话,正想不去,因为战衣什么都没有准备,吃完饭坐上33路车的去周立功公司,然后丢下女朋友在下面就上去面试了,面试分二面,
第一面主要是面基础知识,他除了问笔试那份试卷的题目以外,几乎把我大学学的专业方面的知识都问了还问了一些硬件方面的(因为我说我喜欢做嵌入式方面的)。他一下子叫我写程序一下子叫我写类,在我写程序时他还会边问我些操作系统的语言的基础知识啊,搞得我很乱。
第二面主要是面项目的,是一个项目经理面我,面得很细,细到项目中的重要代码的编写而且他还出了一些和我项目中相近的例子让我当场解决。由于我根本没有准备,做完项目没有做详细的总结,因此在此过程有几个致命性的问题都答不上来。面完第二面后hr小姐叫我等一下,等几位面试官讨论一下结果就出来。等了大约十多分钟,hr小姐进来跟我说不好意思,我不是很适合他们公司。听后我没什么感觉反而轻松很多,我跟他说第一次面试没有关系,然后就离开公司。

总结:周立功的要求实在是比较高,面试时也面得比较细、广、深,没有充分准备和实力很难拿到 offer。而且感觉他要的就是纯粹的技术人员,其他因素好象考虑不多。

高科通信
高科也是在我们学校开宣讲会的,研发只招5人而且要研究生,服务类的比较多。我分析了自己一下投了技术支持这个责位。宣讲会开完后,非研发的都有一分多钟的面试机。我是比较先进去面试的。进去后,面试官第一个问题就问我现在如果叫我去西藏工作你要不。我楞了一下,说去,然后就问些家庭的学校的情况。感觉很不好。很快就收到高科的二面通知,有点意外,
二面是这样进行的,我门组一共是9个人进去面试。进去面试后首先是叫我们9人站起来大合唱,有点奇怪,到现在我还不知道他的用意是什么。我还记得歌名是《真心英雄》,我不是很会唱歌,幸好好歌词看,就跟着唱起来了。合唱完后就叫我们抽题目演讲,没人5分钟准备5分钟演讲。第一次参加这样的面试真有点不知所处,幸好我的题目比较容易,大概意思是毕业3年后的目标是什么?准备的5分钟很快就过去,我也不知道怎样准备。演讲时我就一个劲头的说,没想到还说超时了,而且自己感觉还比较有条理性。二面也就是这样结束的。自己也感觉比较有机会进3面。不出我所料,2天后收到3面通知。
三面主要是无领导小组面试,所谓无领导小组面试就是在招聘过程中往往用到无领导小组交谈的方式,就是让几位参与应聘的人员在互相不认识的情境下,就某一问题进行交流沟通,而面试人员借机观察和考察几位应聘者的沟通能力,分析和解决问题能力,组织能力和领导艺术.题目是假如你是一名飞行员在非洲丛林中遇难,然后有14样东西让你排序。先是个人排序然后就是小组讨论排序,最后拿出专家的排序让我们重新排序。每次排序后都有30秒的发言时间。过程大概就是这样。

总结:做技术支持和销售的,确实需要比较强的口才和反应能力。

广东威创日新电子有限公司
威创我是在51job上搜社会招聘搜到的,投后很快就有回复。广东威创日新电子有限公司坐落在科学城,主要是做大液晶屏幕的。我是下午去面试的,到了威创首先是给我份题目做一下,笔试内容不外乎我下面这些东西,笔试完后就是技术面试,主要是面试我简历中的东西的笔试的东西,面了一个多钟头。最好是hr面试。hr面也没有面什么,我记得比较清楚的是他叫我能不能下个星期来上班,我跟他说我还没有毕业,结果才知道他以为我是出来工作的人了,然后问我最快什么时候能来上班,我好像跟他说我最快也要过年后再来,待遇记得是只给了3k,这个算是第一个offer吧。说这个只是想说明一下如果你有项目经验的话不凡投社会招聘的机会会大一下,因为可能这个竞争没有校园招聘那么大。

总结:投社会招聘的机会会大一点,竞争少一点。
还有别的公司留在以后有空慢慢写


知识结构篇
下面这个图片是我自己觉得如果你在找工作的时候具有这个的知识结构可能能够找到一份很不错的工作(适合计算机的学生),但是前提可能你要先过了四级,要不然有些公司连给你机会都没有。如果大一大二的师弟师妹们想这个结构去学习可能会好一点,个人的建议,如果有那位觉得我说的不对请指正。


面试技巧篇
不知道你在面试时会不会住于被动地位呢,面试官问什么你就答什么。或者乱说自己什么什么多厉害啊,当被面试官一问到就变哑巴了。或者碰到不怎么想说话的面试官,两人在那里不知道说什么呢。如果你有过这样的情况我认为你需要看看我下面说的方法。
我觉得在面试中我们不应该处于被动地位,面试官问什么我们答什么,这样我们很吃亏,除非你是很强的人,不然在遇到你不会答的题目会大大增加的。网上有很多的面试的技巧都教我们在面试的的时候最好是80%的时间是我们在说话,20%的时间是面试官在问。但是我们要怎样才能处于主动地位呢,在面试之前你必须认真的分析自己,哪方面是自己的强项,哪方面是自己的弱项,强项则要突出来,弱项则要避免面试官问到。在强项方面你必须做好准备,你应该站在面试官的角度去想他会问你什么问题,准备好这方面的问题,而且要学会引导面试官去问你这方面的问题,这时你就可以说你什么什么学得可以了。如果被问到弱项时,要小心的回答尽量要把话题引开引向你的强项方面去,如果确实被追问了也要冷静或者跟面试官说你确实对这方面没有深入研究,他也会谅解你,毕竟刚毕业的人不可能样样都懂。然后在他问你问题的也要善于引导,他问你一方面的问题你如果能把其他和他进行对比那么效果会更好的。这样你也会慢慢的在面试中处于主导地位。
1.例如面试官问你函数重载又什么用,你可以在你答完这个问题以后说你还知道继承,多态方面的知识和他们和重载的区别,自然而然他们就会问你继承,多态方面的问题。
2.例如在问你51单片机方面的知识的时候,你可以拿他和PIC单片机对比。说出51是冯诺曼结构的,PIC是哈佛结构的
3.例如问你静态变量放在内存那个地方,你可以把函数啊,类啊,局部变量啊,全局变量啊在内存怎么安排的都说出来
还有一点要强调的平时要多把自己放在面试官的角度去想问题,如果你想招人,你要怎样的人,你要怎么面试别人等等,然后去解决这些问题。
还有啊当你遇到不会的东西后回来一定要上百度!!!
就这样,不知道说得对不对。

面试和笔试应注意的一些问题
1.在笔试中要注意代码的格式和注析(记得立信集团在招人的时候很看重这一点)
2.在面试中不是不可以吹水,是要吹得有水平。主要是不要被面试官看出破绽。比如有些师弟师妹说没有项目经验,其实如果有做过项目的同学能把他做过的东西和你分享,你能把他消化了也未见得是件坏事,主要是你要抓住面试官的心里,引导他去问你会的东西。但是一旦被他知道你说的不真实那么这次面试可以说是失败的了。这个要你自己去分析能不能那样做了,或者有没有能力这样做。
3.简历写的东西你一定要很清楚。
4.在面试中要有自信,但是不要太拽
5.很多时候面试官在面试你时主要还是看重你解决问题的能力,遇到问题时的处理方法,而不是看重你的技术有多强。

[ 本帖最后由 whywhy 于 2008-9-20 00:22 编辑 ]

whywhy 发表于 2007-11-4 12:11

先写这些吧,改天有空再写还有一下面试资料,
下面上传些技术的东西,如果把这些都掌握了,我不相信找不到3k以上的工作

几天没看这个贴不知沉到那里去了,再传点东西,见58楼

[ 本帖最后由 whywhy 于 2007-11-11 11:43 编辑 ]

whywhy 发表于 2007-11-4 12:12

下面发一下面试和笔试中常见的题目去年总结的

排序算法小结
    排序算法是一种基本并且常用的算法。由于实际工作中处理的数量巨大,所以排序算法对算法本身的速度要求很高。
    而一般我们所谓的算法的性能主要是指算法的复杂度,一般用O方法来表示。在后面我将给出详细的说明。
    对于排序的算法我想先做一点简单的介绍,也是给这篇文章理一个提纲。
    我将按照算法的复杂度,从简单到难来分析算法。
    第一部分是简单排序算法,后面你将看到他们的共同点是算法复杂度为O(N*N)(因为没有使用word,所以无法打出上标和下标)。
    第二部分是高级排序算法,复杂度为O(Log2(N))。这里我们只介绍一种算法。另外还有几种算法因为涉及树与堆的概念,所以这里不于讨论。
    第三部分类似动脑筋。这里的两种算法并不是最好的(甚至有最慢的),但是算法本身比较奇特,值得参考(编程的角度)。同时也可以让我们从另外的角度来认识这个问题。
    第四部分是我送给大家的一个餐后的甜点——一个基于模板的通用快速排序。由于是模板函数可以对任何数据类型排序(抱歉,里面使用了一些论坛专家的呢称)。
   
    现在,让我们开始吧:
   
一、简单排序算法
由于程序比较简单,所以没有加什么注释。所有的程序都给出了完整的运行代码,并在我的VC环境
下运行通过。因为没有涉及MFC和WINDOWS的内容,所以在BORLAND C++的平台上应该也不会有什么
问题的。在代码的后面给出了运行过程示意,希望对理解有帮助。

1.冒泡法:
这是最原始,也是众所周知的最慢的算法了。他的名字的由来因为它的工作看来象是冒泡:
#include <iostream.h>

void BubbleSort(int* pData,int Count)
{
    int iTemp;
    for(int i=1;i<Count;i++)
    {
      for(int j=Count-1;j>=i;j--)
      {
            if(pData<pData)
            {
                iTemp = pData;
                pData = pData;
                pData = iTemp;
            }
      }
    }
}

void main()
{
    int data[] = {10,9,8,7,6,5,4};
    BubbleSort(data,7);
    for (int i=0;i<7;i++)
      cout<<data<<" ";
    cout<<"\n";
}

倒序(最糟情况)
第一轮:10,9,8,7->10,9,7,8->10,7,9,8->7,10,9,8(交换3次)
第二轮:7,10,9,8->7,10,8,9->7,8,10,9(交换2次)
第一轮:7,8,10,9->7,8,9,10(交换1次)
循环次数:6次
交换次数:6次

其他:
第一轮:8,10,7,9->8,10,7,9->8,7,10,9->7,8,10,9(交换2次)
第二轮:7,8,10,9->7,8,10,9->7,8,10,9(交换0次)
第一轮:7,8,10,9->7,8,9,10(交换1次)
循环次数:6次
交换次数:3次

上面我们给出了程序段,现在我们分析它:这里,影响我们算法性能的主要部分是循环和交换,
显然,次数越多,性能就越差。从上面的程序我们可以看出循环的次数是固定的,为1+2+...+n-1。
写成公式就是1/2*(n-1)*n。
现在注意,我们给出O方法的定义:

    若存在一常量K和起点n0,使当n>=n0时,有f(n)<=K*g(n),则f(n) = O(g(n))。(呵呵,不要说没
学好数学呀,对于编程数学是非常重要的!!!)

现在我们来看1/2*(n-1)*n,当K=1/2,n0=1,g(n)=n*n时,1/2*(n-1)*n<=1/2*n*n=K*g(n)。所以f(n)
=O(g(n))=O(n*n)。所以我们程序循环的复杂度为O(n*n)。
    再看交换。从程序后面所跟的表可以看到,两种情况的循环相同,交换不同。其实交换本身同数据源的有序程度有极大的关系,当数据处于倒序的情况时,交换次数同循环一样(每次循环判断都会交换),复杂度为O(n*n)。当数据为正序,将不会有交换。复杂度为O(0)。乱序时处于中间状态。正是由于这样的原因,我们通常都是通过循环次数来对比算法。


2.交换法:
交换法的程序最清晰简单,每次用当前的元素一一的同其后的元素比较并交换。
#include <iostream.h>
void ExchangeSort(int* pData,int Count)
{
    int iTemp;
    for(int i=0;i<Count-1;i++)
    {
      for(int j=i+1;j<Count;j++)
      {
            if(pData<pData)
            {
                iTemp = pData;
                pData = pData;
                pData = iTemp;
            }
      }
    }
}

void main()
{
    int data[] = {10,9,8,7,6,5,4};
    ExchangeSort(data,7);
    for (int i=0;i<7;i++)
      cout<<data<<" ";
    cout<<"\n";
}
倒序(最糟情况)
第一轮:10,9,8,7->9,10,8,7->8,10,9,7->7,10,9,8(交换3次)
第二轮:7,10,9,8->7,9,10,8->7,8,10,9(交换2次)
第一轮:7,8,10,9->7,8,9,10(交换1次)
循环次数:6次
交换次数:6次

其他:
第一轮:8,10,7,9->8,10,7,9->7,10,8,9->7,10,8,9(交换1次)
第二轮:7,10,8,9->7,8,10,9->7,8,10,9(交换1次)
第一轮:7,8,10,9->7,8,9,10(交换1次)
循环次数:6次
交换次数:3次

从运行的表格来看,交换几乎和冒泡一样糟。事实确实如此。循环次数和冒泡一样也是1/2*(n-1)*n,所以算法的复杂度仍然是O(n*n)。由于我们无法给出所有的情况,所以只能直接告诉大家他们在交换上面也是一样的糟糕(在某些情况下稍好,在某些情况下稍差)。

3.选择法:
现在我们终于可以看到一点希望:选择法,这种方法提高了一点性能(某些情况下)这种方法类似我们人为的排序习惯:从数据中选择最小的同第一个值交换,在从省下的部分中选择最小的与第二个交换,这样往复下去。
#include <iostream.h>
void SelectSort(int* pData,int Count)
{
    int iTemp;
    int iPos;
    for(int i=0;i<Count-1;i++)
    {
      iTemp = pData;
      iPos = i;
      for(int j=i+1;j<Count;j++)
      {
            if(pData<iTemp)
            {
                iTemp = pData;
                iPos = j;
            }
      }
      pData = pData;
      pData = iTemp;
    }
}

void main()
{
    int data[] = {10,9,8,7,6,5,4};
    SelectSort(data,7);
    for (int i=0;i<7;i++)
      cout<<data<<" ";
    cout<<"\n";
}
倒序(最糟情况)
第一轮:10,9,8,7->(iTemp=9)10,9,8,7->(iTemp=8)10,9,8,7->(iTemp=7)7,9,8,10(交换1次)
第二轮:7,9,8,10->7,9,8,10(iTemp=8)->(iTemp=8)7,8,9,10(交换1次)
第一轮:7,8,9,10->(iTemp=9)7,8,9,10(交换0次)
循环次数:6次
交换次数:2次

其他:
第一轮:8,10,7,9->(iTemp=8)8,10,7,9->(iTemp=7)8,10,7,9->(iTemp=7)7,10,8,9(交换1次)
第二轮:7,10,8,9->(iTemp=8)7,10,8,9->(iTemp=8)7,8,10,9(交换1次)
第一轮:7,8,10,9->(iTemp=9)7,8,9,10(交换1次)
循环次数:6次
交换次数:3次
遗憾的是算法需要的循环次数依然是1/2*(n-1)*n。所以算法复杂度为O(n*n)。
我们来看他的交换。由于每次外层循环只产生一次交换(只有一个最小值)。所以f(n)<=n
所以我们有f(n)=O(n)。所以,在数据较乱的时候,可以减少一定的交换次数。


4.插入法:
插入法较为复杂,它的基本工作原理是抽出牌,在前面的牌中寻找相应的位置插入,然后继续下一张
#include <iostream.h>
void InsertSort(int* pData,int Count)
{
    int iTemp;
    int iPos;
    for(int i=1;i<Count;i++)
    {
      iTemp = pData;
      iPos = i-1;
      while((iPos>=0) && (iTemp<pData))
      {
            pData = pData;
            iPos--;
      }
      pData = iTemp;
    }
}


void main()
{
    int data[] = {10,9,8,7,6,5,4};
    InsertSort(data,7);
    for (int i=0;i<7;i++)
      cout<<data<<" ";
    cout<<"\n";
}

倒序(最糟情况)
第一轮:10,9,8,7->9,10,8,7(交换1次)(循环1次)
第二轮:9,10,8,7->8,9,10,7(交换1次)(循环2次)
第一轮:8,9,10,7->7,8,9,10(交换1次)(循环3次)
循环次数:6次
交换次数:3次

其他:
第一轮:8,10,7,9->8,10,7,9(交换0次)(循环1次)
第二轮:8,10,7,9->7,8,10,9(交换1次)(循环2次)
第一轮:7,8,10,9->7,8,9,10(交换1次)(循环1次)
循环次数:4次
交换次数:2次

上面结尾的行为分析事实上造成了一种假象,让我们认为这种算法是简单算法中最好的,其实不是,
因为其循环次数虽然并不固定,我们仍可以使用O方法。从上面的结果可以看出,循环的次数f(n)<=
1/2*n*(n-1)<=1/2*n*n。所以其复杂度仍为O(n*n)(这里说明一下,其实如果不是为了展示这些简单
排序的不同,交换次数仍然可以这样推导)。现在看交换,从外观上看,交换次数是O(n)(推导类似
选择法),但我们每次要进行与内层循环相同次数的‘=’操作。正常的一次交换我们需要三次‘=’
而这里显然多了一些,所以我们浪费了时间。

最终,我个人认为,在简单排序算法中,选择法是最好的。


二、高级排序算法:
高级排序算法中我们将只介绍这一种,同时也是目前我所知道(我看过的资料中)的最快的。
它的工作看起来仍然象一个二叉树。首先我们选择一个中间值middle程序中我们使用数组中间值,然后
把比它小的放在左边,大的放在右边(具体的实现是从两边找,找到一对后交换)。然后对两边分别使
用这个过程(最容易的方法——递归)。

1.快速排序:
#include <iostream.h>

void run(int* pData,int left,int right)
{
    int i,j;
    int middle,iTemp;
    i = left;
    j = right;
    middle = pData[(left+right)/2];//求中间值
    do{
      while((pData<middle) && (i<right))//从左扫描大于中值的数
            i++;         
      while((pData>middle) && (j>left))//从右扫描大于中值的数
            j--;
      if(i<=j)//找到了一对值
      {
            //交换
            iTemp = pData;
            pData = pData;
            pData = iTemp;
            i++;
            j--;
      }
    }while(i<=j);//如果两边扫描的下标交错,就停止(完成一次)

    //当左边部分有值(left<j),递归左半边
    if(left<j)
      run(pData,left,j);
    //当右边部分有值(right>i),递归右半边
    if(right>i)
      run(pData,i,right);
}

void QuickSort(int* pData,int Count)
{
    run(pData,0,Count-1);
}

void main()
{
    int data[] = {10,9,8,7,6,5,4};
    QuickSort(data,7);
    for (int i=0;i<7;i++)
      cout<<data<<" ";
    cout<<"\n";
}

这里我没有给出行为的分析,因为这个很简单,我们直接来分析算法:首先我们考虑最理想的情况
1.数组的大小是2的幂,这样分下去始终可以被2整除。假设为2的k次方,即k=log2(n)。
2.每次我们选择的值刚好是中间值,这样,数组才可以被等分。
第一层递归,循环n次,第二层循环2*(n/2)......
所以共有n+2(n/2)+4(n/4)+...+n*(n/n) = n+n+n+...+n=k*n=log2(n)*n
所以算法复杂度为O(log2(n)*n)
其他的情况只会比这种情况差,最差的情况是每次选择到的middle都是最小值或最大值,那么他将变
成交换法(由于使用了递归,情况更糟)。但是你认为这种情况发生的几率有多大??呵呵,你完全
不必担心这个问题。实践证明,大多数的情况,快速排序总是最好的。
如果你担心这个问题,你可以使用堆排序,这是一种稳定的O(log2(n)*n)算法,但是通常情况下速度要慢于快速排序(因为要重组堆)。

三、其他排序
1.双向冒泡:
通常的冒泡是单向的,而这里是双向的,也就是说还要进行反向的工作。
代码看起来复杂,仔细理一下就明白了,是一个来回震荡的方式。
写这段代码的作者认为这样可以在冒泡的基础上减少一些交换(我不这么认为,也许我错了)。
反正我认为这是一段有趣的代码,值得一看。
#include <iostream.h>
void Bubble2Sort(int* pData,int Count)
{
    int iTemp;
    int left = 1;
    int right =Count -1;
    int t;
    do
    {
      //正向的部分
      for(int i=right;i>=left;i--)
      {
            if(pData<pData)
            {
                iTemp = pData;
                pData = pData;
                pData = iTemp;
                t = i;
            }
      }
      left = t+1;

      //反向的部分
      for(i=left;i<right+1;i++)
      {
            if(pData<pData)
            {
                iTemp = pData;
                pData = pData;
                pData = iTemp;
                t = i;
            }
      }
      right = t-1;
    }while(left<=right);
}

void main()
{
    int data[] = {10,9,8,7,6,5,4};
    Bubble2Sort(data,7);
    for (int i=0;i<7;i++)
      cout<<data<<" ";
    cout<<"\n";
}

2.SHELL排序
这个排序非常复杂,看了程序就知道了。
首先需要一个递减的步长,这里我们使用的是9、5、3、1(最后的步长必须是1)。
工作原理是首先对相隔9-1个元素的所有内容排序,然后再使用同样的方法对相隔5-1个元素的排序
以次类推。
#include <iostream.h>
void ShellSort(int* pData,int Count)
{
    int step;
    step = 9;
    step = 5;
    step = 3;
    step = 1;

    int iTemp;
    int k,s,w;
    for(int i=0;i<4;i++)
    {
      k = step;
      s = -k;
      for(int j=k;j<Count;j++)
      {
            iTemp = pData;
            w = j-k;//求上step个元素的下标
            if(s ==0)
            {
                s = -k;
                s++;
                pData = iTemp;
            }
            while((iTemp<pData) && (w>=0) && (w<=Count))
            {
                pData = pData;
                w = w-k;
            }
            pData = iTemp;
      }
    }
}

void main()
{
    int data[] = {10,9,8,7,6,5,4,3,2,1,-10,-1};
    ShellSort(data,12);
    for (int i=0;i<12;i++)
      cout<<data<<" ";
    cout<<"\n";
}
呵呵,程序看起来有些头疼。不过也不是很难,把s==0的块去掉就轻松多了,这里是避免使用0
步长造成程序异常而写的代码。这个代码我认为很值得一看。
这个算法的得名是因为其发明者的名字D.L.SHELL。依照参考资料上的说法:“由于复杂的数学原因
避免使用2的幂次步长,它能降低算法效率。”另外算法的复杂度为n的1.2次幂。同样因为非常复杂并
“超出本书讨论范围”的原因(我也不知道过程),我们只有结果了。


四、基于模板的通用排序:
这个程序我想就没有分析的必要了,大家看一下就可以了。不明白可以在论坛上问。
MyData.h文件
///////////////////////////////////////////////////////
class CMyData
{
public:
    CMyData(int Index,char* strData);
    CMyData();
    virtual ~CMyData();

    int m_iIndex;
    int GetDataSize(){ return m_iDataSize; };
    const char* GetData(){ return m_strDatamember; };
    //这里重载了操作符:
    CMyData& operator =(CMyData &SrcData);
    bool operator <(CMyData& data );
    bool operator >(CMyData& data );

private:
    char* m_strDatamember;
    int m_iDataSize;
};
////////////////////////////////////////////////////////

MyData.cpp文件
////////////////////////////////////////////////////////
CMyData::CMyData():
m_iIndex(0),
m_iDataSize(0),
m_strDatamember(NULL)
{
}

CMyData::~CMyData()
{
    if(m_strDatamember != NULL)
      delete[] m_strDatamember;
    m_strDatamember = NULL;
}

CMyData::CMyData(int Index,char* strData):
m_iIndex(Index),
m_iDataSize(0),
m_strDatamember(NULL)
{
    m_iDataSize = strlen(strData);
    m_strDatamember = new char;
    strcpy(m_strDatamember,strData);
}

CMyData& CMyData::operator =(CMyData &SrcData)
{
    m_iIndex = SrcData.m_iIndex;
    m_iDataSize = SrcData.GetDataSize();
    m_strDatamember = new char;
    strcpy(m_strDatamember,SrcData.GetData());
    return *this;
}

bool CMyData::operator <(CMyData& data )
{
    return m_iIndex<data.m_iIndex;
}

bool CMyData::operator >(CMyData& data )
{
    return m_iIndex>data.m_iIndex;
}
///////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////
//主程序部分
#include <iostream.h>
#include "MyData.h"

template <class T>
void run(T* pData,int left,int right)
{
    int i,j;
    T middle,iTemp;
    i = left;
    j = right;
    //下面的比较都调用我们重载的操作符函数
    middle = pData[(left+right)/2];//求中间值
    do{
      while((pData<middle) && (i<right))//从左扫描大于中值的数
            i++;         
      while((pData>middle) && (j>left))//从右扫描大于中值的数
            j--;
      if(i<=j)//找到了一对值
      {
            //交换
            iTemp = pData;
            pData = pData;
            pData = iTemp;
            i++;
            j--;
      }
    }while(i<=j);//如果两边扫描的下标交错,就停止(完成一次)

    //当左边部分有值(left<j),递归左半边
    if(left<j)
      run(pData,left,j);
    //当右边部分有值(right>i),递归右半边
    if(right>i)
      run(pData,i,right);
}

template <class T>
void QuickSort(T* pData,int Count)
{
    run(pData,0,Count-1);
}

void main()
{
    CMyData data[] = {
      CMyData(8,"xulion"),
      CMyData(7,"sanzoo"),
      CMyData(6,"wangjun"),
      CMyData(5,"VCKBASE"),
      CMyData(4,"jacky2000"),
      CMyData(3,"cwally"),
      CMyData(2,"VCUSER"),
      CMyData(1,"isdong")
    };
    QuickSort(data,8);
    for (int i=0;i<8;i++)
      cout<<data.m_iIndex<<""<<data.GetData()<<"\n";
    cout<<"\n";
}

whywhy 发表于 2007-11-4 12:13

深入浅出sizeof



1.0 回答下列问题:[答案在文章末尾]

1. sizeof(char) =                           

2. sizeof 'a'   =                           

3. sizeof "a"   =                        

4. strlen("a")) =

  如果你答对了全部四道题,那么你可以不用细看下面关于sizeof的论述。如果你答错了部分题目,那么就跟着我来一起探讨关于sizeof的用法了。  

  对于前面的题目,我想一般有一定C基础的同志应该不会答错1和4题。至于第2题,我想应该要清楚sizeof是求字符串所占的内存。"a"在内存中的表现为a\0,别忘了末尾的\0也占一个字节呢。至于第2题,可能有些人会惊讶了。C 语言中,字符常数是int 型, 因此 sizeof('a') 是 sizeof(int), 这是另一个与 C++ 不同的地方。既然字符常数是int 型,那么int就可以存放4个字符,我们可以得到sizeof 'abcd'为 4。  

1.1 回答以下题目[答案在文章末尾]

short (*ptr);

1. sizeof(ptr)         =

2. sizeof(ptr)      =

3. sizeof(*ptr)       =

4. sizeof((*ptr))) =   

  是不是又开始晕了。这里我们定义了一个100个指针数组,每个指针均指向有200个元素的数组,其内存占用为200*sizeof(short)字节。那么这100个数组指针的大小sizeof(ptr)为100*sizeof(short*)。接着,指针数组的第一个指针ptr指向第一个数组,所以这个指针ptr的大小实际上就是一个普通指针的大小,即sizeof(short*)。*ptr指向第一个数组的起始地址,所以sizeof(*ptr)实际上求的是第一个组的内存大小200*sizeof(short)。(*ptr))是第一个数组的第一个元素,因为是short型,所以这个元素的大小sizeof((*ptr)))等价于sizeof(short)。

1.2 回答以下题目[答案在文章末尾]

#include <stdio.h>

#pragma pack(push)

#pragma pack(2)

typedef struct _fruit
{
char          apple;
int         banana;
short         orange;   
double      watermelon;
unsigned intplum:5;
unsigned intpeach:28;
char*         tomato;
struct fruit* next;   
} fruit;

#pragma pack(4)

typedef struct _fruit2
{
char         apple;
int            banana;   
short          orange;
double         watermelon;
unsigned int   plum:5;
unsigned int   peach:28;   
char*          tomato;
struct fruit2* next;   
} fruit2;

#pragma pack(pop)

int main(int argc, char *argv[])
{
printf("fruit=%d,fruit2=%d\n",sizeof(fruit),sizeof(fruit2));
}

问题:打印结果为什么呢?

如果你回答错误,那么你对数据结构的对齐还没有吃透。这里#pragma pack(2)强制设置编译器对齐属性为2,所以第一个数据结构以2对齐,sizeof(fruit)=(sizeof(apple)+1)+sizeof(banana)+sizeof(orange)+sizeof(watermelon)+((plum:5bit+peach:28bit+15bit)/8bit)+sizeof(tomato)+sizeof(next)(注意式子中1 和 15bit 表示补齐内存,使其以2对齐,),既sizeof(fruit)=(sizeof(char)+1)+sizeof(int)+sizeof(short)+sizeof(double)+sizeof(char*)+sizeof(struct fruit*)。第一个数据结构声明完了之后,又使用#pragma pack(4)强制设置编译器对齐属性为4,所以同理,可以得到sizeof(fruit2)=(sizeof(char)+3)+sizeof(int)+(sizeof(short)+2)+sizeof(double)+((5bit+28bit+31bit)/8bit)+sizeof(char*)+sizeof(struct fruit2*)。


-----答案:

1.0: 1,4,2,1

1.1: 400,4,400,2

1.2: fruit=30,fruit2=36

"这里我们定义了一个100个指针数组,每个指针均指向有200个元素的数组,其内存占用为200*sizeof(short)字节。那么这100个数组指针的大小sizeof(ptr)为100*sizeof(short*)。"

这里有错误,应该是定义了一个指针数组,该数组有100个元素,其中每一个元素都是一个指针,每个指针指向一个含有200个元素的数组。

豪情 发表于 2007-11-4 12:13

hehe `等你上传好野

whywhy 发表于 2007-11-4 12:15

周立功单片机软件面试方面的部分题目和解答

1、        编写函数从一个包含由逗号分隔的100个16进制数的字符串中提取这100个16进制数,按从大到小的顺序输出其中的偶数。
解:此题按步骤解即可:先解析100个整数;再对其中的偶数排序;最后输出。

#incluede<iostream>
void coma(char *s,int b[],int bl)
{
        int i,j=0,k;
   
    //将字符串s按分隔符’,’解析成100个整数,存放在数组b中
        for(i=0;i<bl;i++)
        {
          while(s!=','&&j<strlen(s))
                {
                        if(s>='0'&&s<='9')
                                k=s-'0';
                        else if(s>='a'&&s<='f')
                                k=s-'a'+10;
            b=b*16+k;
                        j++;
                }
                j++;
        }

        int max;
        int blen=bl;
        int cur,t,even=0;

    //对b数组的偶数按从大到小排列,交换到数组的前端。
        for(i=0;i<blen;i++)
        {
                max=0;
                cur=-1;
      //j从i开始,因为i以前已是有序偶数,且已从大到小排列
          for(j=i;j<blen;j++)
                {
                  if(b>max&&b%2==0)
                        {
                           max=b;
                           cur=j;
                        }
                }

      //找到剩余数组中的最大值,交换到数组前端
      if(cur!=-1)
                {
                  t=b;
                        b=b;
                        b=t;
                        even++;
                }
      //剩余数组找不到偶数时,跳出循环
                else
                        break;
        }

    //输出前even个排序好的偶数
        for(i=0;i<even;i++)
      //十六进制格式输出
                cout<<hex<<b<<"";
                //printf("%x",b);

    cout<<endl;
    return;
}

//测试
int main()
{
char *s="1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,10,11,12,13,14,15,16,17,18,19,1a,1b,1c,1d,1e,1f,21,22,23,24,25,26,27,28,29,2a,2b,2c,2d,2e,2f,30,31,32,33,34,35,36,37,38,39,3a,3b,3c,3d,3e,3f,40,41,42,43,44,45,46,47,48,49,4a,4b,4c,4d,4e,4f,50,51,52,53,54,55,56,57,58,59,5a,5b,5c,5d,5e,5f,60,61,62,63,64,65";
        int sb={0};
        coma(s,sb,100);
return 0;
}

测试输出:
64 62 60 5e 5c 5a 58 56 54 52 50 4e 4c 4a 48 46 44 42 40 3e 3c 3a 38 36 34 32 30 2e 2c 2a 28 26 24 22 20 1e 1c 1a 18 16 14 12 10 e c a 8 6 4 2 0

2、        编写函数数N个BYTE的数据中有多少位是1。
解:此题按步骤解:先定位到某一个BYTE数据;再计算其中有多少个1。叠加得解。

#incluede<iostream>
#define N 10
//定义BYTE类型别名
#ifndef BYTE
typedef unsigned char BYTE;
#endif

int comb(BYTE b[],int n)
{
    int count=0;
        int bi,bj;
BYTE cc=1,tt;

//历遍到第bi个BYTE数据
        for(bi=0;bi<n;bi++)
        {
//计算该BYTE的8个bit中有多少个1
                tt=b;
      for(bj=0;bj<8;bj++)
                {
            //与1相与或模2结果是否是1?测试当前bit是否为1
            //if(tt%2==1)
                  if((tt&cc)==1)
                        {
                count++;
            }
            //右移一位或除以2,效果相同
                        //tt=tt>>1;
                        tt=tt/2;
                }
        }
        return count;
}

//测试
int main()
{
BYTE b={3,3,3,11,1,1,1,1,1,1};
        cout<<comb(b,N)<<endl;
    return 0;
}

测试输出:
15

3、        编写函数比较两个字符串。
解:此题简单,可能只要注意一下下标越界问题。

#incluede<iostream>
int mstrcmp(char *st1,char *st2)
{
    /*注意到当st1,st2任一为空时,*st1==*st2测试为假;当st1,st2同时为空时,*st1==*st2语句为假,故不会越界。*/
        while(*st1==*st2)
        {
                st1++;
                st2++;               
        }
        if(*st1>*st2)
                return 1;
        else if(*st1<*st2)
                return -1;
        else
                return 0;
}

//测试
int main()
{
    char* x1="def2";
        char* x2="def223";
    //c标准库自带字符串比较函数
        cout<<strcmp(x1,x2)<<endl;
    //自己写的
    cout<<mstrcmp(x1,x2)<<endl;
}

测试输出:
-1
-1

4、        计算一个结构占的字节数,及在紧凑模式下占的字节数。
解:char占1个字节;int占4个;short占2个;long占4个。考虑到位对齐:char 占1-4字节;int占4-8字节;short占8-12字节;long占12-16字节。紧凑下不知道如何编写代码,但应该不用位对齐,故估计输出应为:11,00000007。
这里要提醒大家的是:位对齐与类型的排列顺序有关。若类型排列为:int char short long,则int 占1-4字节;char占4-6字节;short占6-8字节;long占8-12字节;此题输出应为:12,00000008。

#incluede<iostream>
typedef struct _st
{
    char a;
        int b;
        short c;
        long d;
}
st;

//测试:
int main()
{
    cout<<sizeof(st)<<endl;
        cout<<&(((st*)0)->d)<<endl;
    return 0;
}

测试输出:
16
0000000C
注:如何定义紧凑模式?求紧凑模式下的代码。

5、        多继承。
解:virtual继承是防止a-b,c-d型继承时维护超基类a的两个版本。此题中virtual继承不影响构造及析构函数的调用顺序。此题另一个需要注意的是:new开辟类空间时需要调用构造函数;只当该类空间被delete释放时,相应类实例才会被析构。

#incluede<iostream>
class a
{
public:
        a(){cout<<"a"<<endl;}
        ~a(){cout<<"~a"<<endl;}
};

class b : virtual a
{
public:
        b(){cout<<"b"<<endl;}
    ~b(){cout<<"~b"<<endl;}
};

class c
{
public:
        c(){cout<<"c"<<endl;}
        ~c(){cout<<"~c"<<endl;}
};

class d : public c
{
public:
        d(){cout<<"d"<<endl;}
        ~d(){cout<<"~d"<<endl;}
};

class e : public b, public d
{
public:
        e(){cout<<"e"<<endl;}
        ~e(){cout<<"~e"<<endl;}
};

//测试
int main()
{
e es;
        e *ep=new e;
        //delete ep;
    return 0;
}

//测试输出
a
b
c
d
e
a
b
c
d
e
~e
~d
~c
~b
~a

6、        编写String类,实现加法和赋值运算符重载。
解:此题名不见经传,实现中需要注意:必须实现拷贝构造函数,在加法重载中返回一个类实例才能成功。

#incluede<iostream>
class String
{
public:
    //构造函数
    String(char *s=0,int len=0):len(len)
        {
                this->s=new char;
          int i;
                for(i=0;i<len;i++)
                        this->s=s;
        }
        //析构函数
        ~String(){delete []s;len=0;}
    void prints();
        String(const String &str);
        String& operator=(const String &str);
        String operator+(const String &str);
protected:
        char *s;
        int len;
};

//打印函数
void String::prints()
{
        int i;
    for(i=0;i<len;i++)
                cout<<s;
        cout<<endl;
}

//赋值运算符重载
String& String::operator=(const String &str)
{
    delete []s;
        len=str.len;
        s=new char;
        int i;
        for(i=0;i<len;i++)
                s=str.s;
        return *this;
}

//加法运算符重载
inline String String::operator+(const String &str)
{
    String st(s,len+str.len);
        int i;
        for(i=len;i<st.len;i++)
                st.s=str.s;
        return st;
}

//拷贝构造函数
String::String(const String &str)
{
        len=str.len;
        s=new char;
        int i;
        for(i=0;i<len;i++)
                s=str.s;
}

//测试
int main()
{
        char *s1="123";
        char *s2="4567";

    //第一个字符串
    String str(s1,3);
        str.prints();

    //第二个字符串
        String str2(s2,4);
        str2.prints();

    //赋值运算符重载测试
        String st;
        st=str;
        st.prints();

    //加法运算符重载测试
        String string;
        string=str+str2;
        string.prints();

return 0;
}

测试输出:
123
4567
123
1234567

whywhy 发表于 2007-11-4 12:18

嵌入式方面的试题

预处理器(Preprocessor)

1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
          #define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在这想看到几件事情:
1) #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)
2)懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
3) 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
4) 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。

2 . 写一个"标准"宏MIN ,这个宏输入两个参数并返回较小的一个。
         #define MIN(A,B) ((A) <= (B) ? (A) : (B))
这个测试是为下面的目的而设的:
1) 标识#define在宏中应用的基本知识。这是很重要的。因为在嵌入(inline)操作符 变为标准C的一部分之前,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。
2)三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码,了解这个用法是很重要的。
3) 懂得在宏中小心地把参数用括号括起来
4) 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?
         least = MIN(*p++, b);

3. 预处理器标识#error的目的是什么?
如果你不知道答案,请看参考文献1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆子才会读C语言课本的附录去找出象这种问题的答案。当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。


死循环(Infinite loops)

4. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?
这个问题用几个解决方案。我首选的方案是:

while(1)
{

}

一些程序员更喜欢如下方案:

for(;;)
{

}

这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的基本原理。如果他们的基本答案是:"我被教着这样做,但从没有想到过为什么。"这会给我留下一个坏印象。

第三个方案是用 goto
Loop:
...
goto Loop;
应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。


数据声明(Data declarations)

5. 用变量a给出下面的定义
a) 一个整型数(An integer)
b)一个指向整型数的指针( A pointer to an integer)
c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a pointer to an intege)r
d)一个有10个整型数的数组( An array of 10 integers)
e) 一个有10个指针的数组,该指针是指向一个整型数的。(An array of 10 pointers to integers)
f) 一个指向有10个整型数数组的指针( A pointer to an array of 10 integers)
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer)
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer )

答案是:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a; // An array of 10 integers
e) int *a; // An array of 10 pointers to integers
f) int (*a); // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
h) int (*a)(int); // An array of 10 pointers to functions that take an integer argument and return an integer

人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。但是当我被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道所有的答案(或至少大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为什么出准备呢?

Static

6. 关键字static的作用是什么?
这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用:
1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2) 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
3) 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。

大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。


Const

7.关键字const有什么含意?
我只要一听到被面试者说:"const意味着常数",我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有用法,因此ESP(译者:Embedded Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.如果你从没有读到那篇文章,只要能说出const意味着"只读"就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知道更详细的答案,仔细读一下Saks的文章吧。)
如果应试者能正确回答这个问题,我将问他一个附加的问题:
下面的声明都是什么意思?

const int a;
int const a;
const int *a;
int * const a;
int const * a const;

/******/
前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你可能会问,即使不用关键字 const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由:
1) 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)
2) 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
3) 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。


Volatile

8. 关键字volatile有什么含意?并给出三个不同的例子。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1) 并行设备的硬件寄存器(如:状态寄存器)
2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3) 多线程应用中被几个任务共享的变量

回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
1)一个参数既可以是const还可以是volatile吗?解释为什么。
2); 一个指针可以是volatile 吗?解释为什么。
3); 下面的函数有什么错误:

int square(volatile int *ptr)
{
         return *ptr * *ptr;
}

下面是答案:
1)是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2); 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3) 这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:

int square(volatile int *ptr)
{
   int a,b;
   a = *ptr;
   b = *ptr;
   return a * b;
}

由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int *ptr)
{
   int a;
   a = *ptr;
   return a * a;
}

位操作(Bit manipulation)

9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个操作中,要保持其它位不变。
对这个问题有三种基本的反应
1)不知道如何下手。该被面者从没做过任何嵌入式系统的工作。
2) 用bit fields。Bit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间是不可移植的,同时也保证了的你的代码是不可重用的。我最近不幸看到 Infineon为其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编译器用其它的方式来实现bit fields的。从道德讲:永远不要让一个非嵌入式的家伙粘实际硬件的边。
3) 用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下:

#define BIT3 (0x1 << 3)
static int a;

void set_bit3(void)
{
   a |= BIT3;
}
void clear_bit3(void)
{
   a &= ~BIT3;
}

一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。我希望看到几个要点:说明常数、|=和&=~操作。


访问固定的内存位置(Accessing fixed memory locations)

10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。
这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:
   int *ptr;
   ptr = (int *)0x67a9;
   *ptr = 0xaa55;

A more obscure approach is:
一个较晦涩的方法是:

   *(int * const)(0x67a9) = 0xaa55;

即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。

中断(Interrupts)

11. 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字 __interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。

__interrupt double compute_area (double radius)
{
   double area = PI * radius * radius;
   printf("\nArea = %f", area);
   return area;
}

这个函数有太多的错误了,以至让人不知从何说起了:
1)ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。
2) ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。
3) 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。
4) 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。不用说,如果你能得到后两点,那么你的被雇用前景越来越光明了。


代码例子(Code examples)

12 . 下面的代码输出是什么,为什么?

void foo(void)
{
   unsigned int a = 6;
   int b = -20;
   (a+b > 6) ? puts("> 6") : puts("<= 6");
}
这个问题测试你是否懂得C语言中的整数自动转换原则,我发现有些开发者懂得极少这些东西。不管如何,这无符号整型问题的答案是输出是 ">6"。原因是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。如果你答错了这个问题,你也就到了得不到这份工作的边缘。

13. 评价下面的代码片断:

unsigned int zero = 0;
unsigned int compzero = 0xFFFF;
/*1's complement of zero */

对于一个int型不是16位的处理器为说,上面的代码是不正确的。应编写如下:

unsigned int compzero = ~0;

这一问题真正能揭露出应试者是否懂得处理器字长的重要性。在我的经验里,好的嵌入式程序员非常准确地明白硬件的细节和它的局限,然而PC机程序往往把硬件作为一个无法避免的烦恼。
到了这个阶段,应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应试者不是很好,那么这个测试就在这里结束了。但如果显然应试者做得不错,那么我就扔出下面的追加问题,这些问题是比较难的,我想仅仅非常优秀的应试者能做得不错。提出这些问题,我希望更多看到应试者应付问题的方法,而不是答案。不管如何,你就当是这个娱乐吧...


动态内存分配(Dynamic memory allocation)

14. 尽管不像非嵌入式计算机那么常见,嵌入式系统还是有从堆(heap)中动态分配内存的过程的。那么嵌入式系统中,动态分配内存可能发生的问题是什么?
这里,我期望应试者能提到内存碎片,碎片收集的问题,变量的持行时间等等。这个主题已经在ESP杂志中被广泛地讨论过了(主要是 P.J. Plauger, 他的解释远远超过我这里能提到的任何解释),所有回过头看一下这些杂志吧!让应试者进入一种虚假的安全感觉后,我拿出这么一个小节目:
下面的代码片段的输出是什么,为什么?

char *ptr;
if ((ptr = (char *)malloc(0)) == NULL)
   puts("Got a null pointer");
else
   puts("Got a valid pointer");

这是一个有趣的问题。最近在我的一个同事不经意把0值传给了函数malloc,得到了一个合法的指针之后,我才想到这个问题。这就是上面的代码,该代码的输出是"Got a valid pointer"。我用这个来开始讨论这样的一问题,看看被面试者是否想到库例程这样做是正确。得到正确的答案固然重要,但解决问题的方法和你做决定的基本原理更重要些。

Typedef

15 Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如,思考一下下面的例子:

#define dPS struct s *
typedef struct s * tPS;

以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么?
这是一个非常微妙的问题,任何人答对这个问题(正当的原因)是应当被恭喜的。答案是:typedef更好。思考下面的例子:

dPS p1,p2;
tPS p3,p4;

第一个扩展为

struct s * p1, p2;
.
上面的代码定义p1为一个指向结构的指,p2为一个实际的结构,这也许不是你想要的。第二个例子正确地定义了p3 和p4 两个指针。



晦涩的语法

16 . C语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么?

int a = 5, b = 7, c;
c = a+++b;

这个问题将做为这个测验的一个愉快的结尾。不管你相不相信,上面的例子是完全合乎语法的。问题是编译器如何处理它?水平不高的编译作者实际上会争论这个问题,根据最处理原则,编译器应当能处理尽可能所有合法的用法。因此,上面的代码被处理成:

c = a++ + b;

因此, 这段代码持行后a = 6, b = 7, c = 12。
如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个问题的最大好处是这是一个关于代码编写风格,代码的可读性,代码的可修改性的好的话题。





1、将一个字符串逆序
2、将一个链表逆序
3、计算一个字节里(byte)里面有多少bit被置1
4、搜索给定的字节(byte)
5、在一个字符串中找到可能的最长的子字符串
6、字符串转换为整数
7、整数转换为字符串

marcus 发表于 2007-11-4 12:18

兄台用心良苦

whywhy 发表于 2007-11-4 12:27

C语言嵌入式系统编程修炼

觉得不错,嵌入式方面的技术面试几乎会问到的。

BMDHP 发表于 2007-11-4 12:29

顶一个,辛苦师兄了!

whywhy 发表于 2007-11-4 12:29

C/C++笔试经典题目

1. 以下三条输出语句分别输出什么?
char str1[] = "abc";
char str2[] = "abc";
const char str3[] = "abc";
const char str4[] = "abc";
const char* str5 = "abc";
const char* str6 = "abc";
cout << boolalpha << ( str1==str2 ) << endl; // 输出什么?
cout << boolalpha << ( str3==str4 ) << endl; // 输出什么?
cout << boolalpha << ( str5==str6 ) << endl; // 输出什么?
答:分别输出false,false,true。str1和str2都是字符数组,每个都有其自己的存储区,它们的值则是各存储区首地址,不等;str3和str4同上,只是按const语义,它们所指向的数据区不能修改。str5和str6并非数组而是字符指针,并不分配存储区,其后的“abc”以常量形式存于静态数据区,而它们自己仅是指向该区首地址的指针,相等。


2. 以下代码中的两个sizeof用法有问题吗?
void UpperCase( char str[] ) // 将 str 中的小写字母转换成大写字母
{
for( size_t i=0; i<sizeof(str)/sizeof(str); ++i )
if( 'a'<=str && str<='z' )
str -= ('a'-'A' );
}
char str[] = "aBcDe";
cout << "str字符长度为: " << sizeof(str)/sizeof(str) << endl;
UpperCase( str );
cout << str << endl;

答:函数内的sizeof有问题。根据语法,sizeof如用于数组,只能测出静态数组的大小,无法检测动态分配的或外部数组大小。函数外的str是一个静态定义的数组,因此其大小为6,函数内的str实际只是一个指向字符串的指针,没有任何额外的与数组相关的信息,因此sizeof作用于上只将其当指针看,一个指针为4个字节,因此返回4。


3. 非C++内建型别 A 和 B,在哪几种情况下B能隐式转化为A?
答:
a. class B : public A { ……} // B公有继承自A,可以是间接继承的
b. class B { operator A( ); } // B实现了隐式转化为A的转化
c. class A { A( const B& ); } // A实现了non-explicit的参数为B(可以有其他带默认值的参数)构造函数
d. A& operator= ( const A& ); // 赋值操作,虽不是正宗的隐式类型转换,但也可以勉强算一个


4. 以下代码有什么问题?
struct Test
{
Test( int ) {}
Test() {}
void fun() {}
};
void main( void )
{
Test a(1);
a.fun();
Test b();
b.fun();
}

答:变量b定义出错。按默认构造函数定义对象,不需要加括号。


5. 以下代码有什么问题?
cout << (true?1:"1") << endl;
答:三元表达式“?:”问号后面的两个操作数必须为同一类型。


6. 以下代码能够编译通过吗,为什么?
unsigned int const size1 = 2;
char str1[ size1 ];
unsigned int temp = 0;
cin >> temp;
unsigned int const size2 = temp;
char str2[ size2 ];
答:str2定义出错,size2非编译器期间常量,而数组定义要求长度必须为编译期常量。


7. 以下反向遍历array数组的方法有什么错误?
vector array;
array.push_back( 1 );
array.push_back( 2 );
array.push_back( 3 );
for( vector::size_type i=array.size()-1; i>=0; --i ) // 反向遍历array数组
{
cout << array << endl;
}

答:首先数组定义有误,应加上类型参数:vector<int> array。其次vector::size_type被定义为unsigned int,即无符号数,这样做为循环变量的i为0时再减1就会变成最大的整数,导致循环失去控制。


8. 以下代码中的输出语句输出0吗,为什么?
struct CLS
{
int m_i;
CLS( int i ) : m_i(i) {}
CLS()
{
CLS(0);
}
};
CLS obj;
cout << obj.m_i << endl;

答:不能。在默认构造函数内部再调用带参的构造函数属用户行为而非编译器行为,亦即仅执行函数调用,而不会执行其后的初始化表达式。只有在生成对象时,初始化表达式才会随相应的构造函数一起调用。


9. C++中的空类,默认产生哪些类成员函数?
答:
class Empty
{
public:
Empty(); // 缺省构造函数
Empty( const Empty& ); // 拷贝构造函数
~Empty(); // 析构函数
Empty& operator=( const Empty& ); // 赋值运算符
Empty* operator&(); // 取址运算符
const Empty* operator&() const; // 取址运算符 const
};



10. 以下两条输出语句分别输出什么?
float a = 1.0f;
cout << (int)a << endl;
cout << (int&)a << endl;
cout << boolalpha << ( (int)a == (int&)a ) << endl; // 输出什么?
float b = 0.0f;
cout << (int)b << endl;
cout << (int&)b << endl;
cout << boolalpha << ( (int)b == (int&)b ) << endl; // 输出什么?

答:分别输出false和true。注意转换的应用。(int)a实际上是以浮点数a为参数构造了一个整型数,该整数的值是1,(int&)a则是告诉编译器将a当作整数看(并没有做任何实质上的转换)。因为1以整数形式存放和以浮点形式存放其内存数据是不一样的,因此两者不等。对b的两种转换意义同上,但是0的整数形式和浮点形式其内存数据是一样的,因此在这种特殊情形下,两者相等(仅仅在数值意义上)。
注意,程序的输出会显示(int&)a=1065353216,这个值是怎么来的呢?前面已经说了,1以浮点数形式存放在内存中,按ieee754规定,其内容为0x0000803F(已考虑字节反序)。这也就是a这个变量所占据的内存单元的值。当(int&)a出现时,它相当于告诉它的上下文:“把这块地址当做整数看待!不要管它原来是什么。”这样,内容0x0000803F按整数解释,其值正好就是1065353216(十进制数)。
通过查看汇编代码可以证实“(int)a相当于重新构造了一个值等于a的整型数”之说,而(int&)的作用则仅仅是表达了一个类型信息,意义在于为cout<<及==选择正确的重载版本。


11. 以下代码有什么问题?
typedef vector IntArray;
IntArray array;
array.push_back( 1 );
array.push_back( 2 );
array.push_back( 2 );
array.push_back( 3 );
// 删除array数组中所有的2
for( IntArray::iterator itor=array.begin(); itor!=array.end(); ++itor )
{
if( 2 == *itor ) array.erase( itor );
}

答:同样有缺少类型参数的问题。另外,每次调用“array.erase( itor );”,被删除元素之后的内容会自动往前移,导致迭代漏项,应在删除一项后使itor--,使之从已经前移的下一个元素起继续遍历。

12. 写一个函数,完成内存之间的拷贝。[考虑问题是否全面]
答:
void* mymemcpy( void *dest, const void *src, size_t count )
{
char* pdest = static_cast<char*>( dest );
const char* psrc = static_cast<const char*>( src );
if( pdest>psrc && pdest<psrc+cout ) 能考虑到这种情况就行了
{
for( size_t i=count-1; i!=-1; --i )
pdest = psrc;
}
else
{
for( size_t i=0; i<count; ++i )
pdest = psrc;
}
return dest;
}

  1 编程:

  用C语言实现一个revert函数,它的功能是将输入的字符串在原串上倒序后返回。

  2 编程:

  用C语言实现函数void * memmove(void *dest,const void *src,size_t n)。memmove

  函数的功能是拷贝src所指的内存内容前n个字节

  到dest所指的地址上。

题目不难,但有些限制和陷阱,如第一题隐含一个条件就是空间复杂度不能太高;第二题就是有个陷阱就是当目的内存块与源内存块相交的时候的情况。相信很多粗心的人都会中这个陷阱的。

我的参考答案如下:

char* revert( char* p, size_t n )
{
char* s = p;
char* e = p+n-1;
while( s < e )
{
*s ^= *e;
*e ^= *s;
*s ^= *e;
++s,--e;
}
return p;
}

void * memmove(void *dest,const void *src,size_t n)
{
if( dest == src )
{
return dest;
}
unsigned int* s = (unsigned int*)src;
unsigned int* d = (unsigned int*)dest;

void* e = (void*)((const char*)src + n);

if( dest > src && dest < e )
{
char* se = (char*)e;
char* de = ((char*)dest)+n;
while( se >= src )
   *de-- = *se--;
}
else
{
while( (void*)(s+1) <= e )
{
   *d++ = *s++;
}
char* cd = (char*)d;
char* cs = (char*)s;
while( cs < e )
{
   *cd++ = *cs++;
}
}

return dest;
}


这两天又看到一个很牛的内存拷贝算法,效率上没有作优化,但想象力惊人,可惜也中了那个源内存与目的内存相交的陷阱,大家来学学吧。

void* mymemcpy( void* dest, const void* src, size_t count )
{
    char* d = (char*)dest;
    const char* s = (const char*)src;
    int n = (count + 7) / 8; // count > 0 assumed

    switch( count & 7 )
    {
    case 0:do {*d++ = *s++;
    case 7:      *d++ = *s++;
    case 6:      *d++ = *s++;
    case 5:      *d++ = *s++;
    case 4:      *d++ = *s++;
    case 3:      *d++ = *s++;
    case 2:      *d++ = *s++;
    case 1:      *d++ = *s++;
               } while (--n > 0);
    }

whywhy 发表于 2007-11-4 12:33

CC++一些面试中经常考到的题目

CC++一些面试中经常考到的题目,没有去细分里面的内容,可能和上面有些资料重复了,当有些问题确实面试和笔试中常常问到的

whywhy 发表于 2007-11-4 12:38

笔试面试迅雷时留下的资料

迅雷在语言和算法方面都要求很高,所以我失败了(也可能是我不是很适合吧,我对嵌入式比较有兴趣)。

whywhy 发表于 2007-11-4 12:43

硬件方面的一些笔试题目

硬件方面的一些笔试题目,这些我只看了个别的,希望对自动化和信工的师弟师妹有帮助

whywhy 发表于 2007-11-4 12:46

复杂定义的解释

复杂定义的解释,这个在笔试和面试中有问到经常都会答错的,如果你答对了,那么你的机会会大很多。
先看一个简单例子:
      void(*funcPtr)();
      当看到这样的一个定义时,最好的处理方法是从中间开始和向外扩展以及挖空括号。
      “从中间开始”的意思是从变量名开始,“向外扩展”的意思是先注意右边最近的项,遇括号变向,如此持续下去。“挖空括号”是指把变量名所在的括号去掉并去掉参数表所在括号,就得到其函数的返回值(仅限于函数声明)。
      以上分析如下: 中间开始----> funcPtr 向右走------> 遇括号 转向--------> 遇* ,funcPtr是一个指针 ,又是括号 转向--------> 一个空参数表(不带参数的函数) 向左走------> void 返回值------> void 最终结果是: funcPtr是一个指针,它指向一个不带参数并返回void的函数。

再看几个更复杂的:
      void *(*(*fp1)(int)); fp1是一个指向函数的指针,它接受一个int参数并返回一个指向含有10个void指针数组的指针(void * )。
      float (*(*fp2)(int,int,float))(int); fp2是一个指向函数的指针,它接受三个参数:int, int, float且返回一个指向函数的指针,该函数有一个int参数并返回一个float。
      typedef double (*(*(*fp3)()))(); //返回值为:double (*(*))(); fp3 a; fp3是一个指向函数的指针,该函数无参数并返回一个指向含有10个指向函数指针数组的指针,这些函数不接受参数并返回double值.
      int (*(*f4()))(); //返回值为 int(*())(); f4是一个返回指针的函数,该指针指向含有10个函数指针的数组,这些函数返回整型值.

whywhy 发表于 2007-11-4 12:51

下面这个记得是为深信服的笔试准备的

一、请填写BOOL , float, 指针变量 与“零值”比较的 if 语句。(10分)
提示:这里“零值”可以是0, 0.0 , FALSE或者“空指针”。例如 int 变量 n 与“零值
”比较的 if 语句为:
if ( n == 0 )
if ( n != 0 )
以此类推。

请写出 BOOL flag 与“零值”比较的 if 语句:
请写出 float x 与“零值”比较的 if 语句:
请写出 char *p 与“零值”比较的 if 语句:

二、以下为Windows NT下的32位C++程序,请计算sizeof的值(10分)

char str[] = “Hello” ; char *p = str ;int n = 10;请计算sizeof (str )
= sizeof ( p ) = sizeof ( n ) = void Func (
char str){请计算 sizeof( str ) = }
void *p = malloc( 100 );请计算sizeof ( p ) =

三、简答题(25分)

1、头文件中的 ifndef/define/endif 干什么用?



2、#include 和 #include “filename.h” 有什么区别?



3、const 有什么用途?(请至少说明两种)



4、在C++ 程序中调用被 C编译器编译后的函数,为什么要加 extern “C”声明?




5、请简述以下两个for循环的优缺点

// 第一个for (i=0; i ing();} // 第二个if (condition){for (i=0; i for (i=0; i 优点:缺点: 优点:缺点:

四、有关内存的思考题(20分)

void GetMemory(char *p){p = (char *)malloc(100);}void Test(void) {char *str =
NULL;GetMemory(str); strcpy(str, "hello world");printf(str);}请问运行Test函数会
有什么样的结果?答: char *GetMemory(void){ char p[] = "hello world";return p;
}void Test(void){char *str = NULL;str = GetMemory(); printf(str);}请问运行Test
函数会有什么样的结果?答:
Void GetMemory2(char **p, int num){*p = (char *)malloc(num);}void Test(void){c
har *str = NULL;GetMemory(&str, 100);strcpy(str, "hello"); printf(str); }请问运
行Test函数会有什么样的结果?答: void Test(void){char *str = (char *) malloc(1
00); strcpy(str, “hello”); free(str); if(str != NULL) { strcpy(str, “
world”); printf(str);}}请问运行Test函数会有什么样的结果?答:


五、编写strcpy函数(10分)
已知strcpy函数的原型是
char *strcpy(char *strDest, const char *strSrc);
其中strDest是目的字符串,strSrc是源字符串。

(1)不调用C++/C的字符串库函数,请编写函数 strcpy







(2)strcpy能把strSrc的内容复制到strDest,为什么还要char * 类型的返回值?




六、编写类String的构造函数、析构函数和赋值函数(25分)
已知类String的原型为:
class String
{
public:
String(const char *str = NULL); // 普通构造函数
String(const String &other); // 拷贝构造函数
~ String(void); // 析构函数
String & operate =(const String &other); // 赋值函数
private:
char *m_data; // 用于保存字符串
};
请编写String的上述4个函数。

jazaone 发表于 2007-11-4 13:11

师兄是广工最牛的人之一。。。。。。

sunkiss 发表于 2007-11-4 13:13

怎一个牛字了得

xeigm 发表于 2007-11-4 14:21

多谢师兄多来后院指导一下师弟呀......

wen_yeah 发表于 2007-11-4 14:58

顶一个~~ 有心了~~
页: [1] 2 3 4 5 6
查看完整版本: 给师弟师妹的一些面试经验和面试资料