[剑指offer] 面试学习

面试小提示

关于项目问答:

在介绍项目经验(包括在简历上介绍和面试时口头介绍)时,应聘者不必详述项目的背景,而要突出介绍自己完成的工作及取得的成绩。

关于代码鲁棒性

面试官除了希望应聘者的代码能够完成基本的功能之外,还会关注应聘者是否考虑了边界条件、特殊输入(比如NULL指针,空字符串等)及错误处理

关于写代码的思路

如果在面试的时候遇到难题,我们有3种办法分析、解决复杂的问题:画图能使抽象问题形象化,举例使抽象问题具体化,分解使复杂问题简单化。

关于面试的时候提到的合作性

在面试过程中,面试官会观察应聘者在介绍项目经验或者算法思路时是否观点明确、逻辑清晰,并以此判断其沟通能力的强弱。另外,面试官也会从应聘者说话的神态和语气来判断他是否有团队合作的意识。通常面试官不会喜欢高傲或者轻视合作者的人。

面试官想从面试中了解到什么内容

很好的学习能力,合作能力,知识迁移能力,抽象建模能力和发散思维能力;

轮到我们提问的时候应该提出什么问题
在结束面试前的5~10分钟,面试官会给应聘者机会问几个问题,应聘者的问题的质量对面试的结果也有一定的影响。有些人的沟通能力很强,马上就能想到有意思的问题。但对于大多数人而言,在经受了面试官将近一小时的拷问之后可能已经精疲力竭,再迅速想出几个问题难度很大。因此建议应聘者不妨在面试之前做些功课,为每一轮面试准备2~3个问题,这样到提问环节的时候就游刃有余了。面试官让应聘者问几个问题,主要是想了解他最关心的问题有哪些,因此应聘者至少要问一两个问题,否则面试官就会觉得你对我们公司、职位等都不感兴趣,那你来面试做什么?但是也不是什么问题都可以在这个时候问。如果问题问得比较合适,对应聘者来说是个加分的好机会;但如果问的问题不太合适,面试官对他的印象就会大打折扣。切莫好高骛远,问些过于不切实际的问题;其次是不要问薪水。技术面试不是谈薪水的时候,要谈工资要等通过面试之后和HR谈。而且让面试官觉得你最关心的问题就是薪水,给面试官留下的印象也不好。最后推荐问的问题是与招聘的职位或者项目相关的问题。如果这种类型的问题问得很到位,那么面试官就会觉得你对应聘的职位很有兴趣。不过要问好这种类型的问题也不容易,因为首先对应聘的职位或者项目的背景要有一定的了解。我们可以从两方面去了解相关的信息:一是面试前做足功课,到网上去收集一些相关的信息,做到对公司成立时间、主要业务、职位要求等都了然于胸;二是面试过程中留心面试官说过的话。有不少面试官在面试之前会简单介绍与招聘职位相关的项目,其中会包含其他渠道无法得到的信息,比如项目进展情况等。应聘者可以从中找出一两个点,然后向面试官提问。

面试题目

Sizeof (C++)

面试官:定义一个空的类型,里面没有任何成员变量和成员函数。对该类型求sizeof,得到的结果是多少?

应聘者:答案是1。

面试官:为什么不是0?

应聘者:空类型的实例中不包含任何信息,本来求sizeof应该是0,但是当我们声明该类型的实例的时候,它必须在内存中占有一定的空间,否则无法使用这些实例。至于占用多少内存,由编译器决定。Visual Studio中每个空类型的实例占用1字节的空间。

面试官:如果在该类型中添加一个构造函数和析构函数,再对该类型求sizeof,得到的结果又是多少?

应聘者:和前面一样,还是1。调用构造函数和析构函数只需要知道函数的地址即可,而这些函数的地址只与类型相关,而与类型的实例无关,编译器也不会因为这两个函数而在实例内添加任何额外的信息。

面试官:那如果把析构函数标记为虚函数呢?

应聘者:++的编译器一旦发现一个类型中有虚拟函数,就会为该类型生成虚函数表,并在该类型的每一个实例中添加一个指向虚函数表的指针。在32位的机器上,一个指针占4字节的空间,因此求sizeof得到4;如果是64位的机器,一个指针占8字节的空间,因此求sizeof则得到8

能否通过编译 (C++)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A{
private:
int value;
public:
A(int num) { value = num; }
A(A other) { value = other.valuel; }

void Print() { std::cout << value << std::endl; }
};

int main()
{
A a = 10;
A b = a;
b.Print();
return 0;
}

在上述代码中,复制构造函数A(A other)传入的参数是A的一个实例。由于是传值参数,我们把形参复制到实参会调用复制构造函数。因此如果允许复制构造函数传值,就会在复制构造函数内调用复制构造函数,就会形成永无休止的递归调用从而导致栈溢出。因此C++的标准不允许复制构造函数传值参数,在Visual Studio和GCC中,都将编译出错。要解决这个问题,我们可以把构造函数修改为A(const A&other),也就是把传值参数改成常量引用。

很多考查C++语法的代码题围绕在构造函数、析构函数及运算符重载。

赋值运算符函数 (C++)

题目:如下为类型CMyString的声明,请为该类型添加赋值运算符函数。

1
2
3
4
5
6
7
8
9
10
class CMyString
{
public:
CMyString(char *pData = NULL);
CMyString(const CMyString &str);
~CMyString();

private:
char *m_pData;
};

面试官想通过这个了解到
● 是否把返回值的类型声明为该类型的引用,并在函数结束前返回实例自身的引用(即this)。只有返回一个引用,才可以允许连续赋值。否则如果函数的返回值是void,应用该赋值运算符将不能做连续赋值。假设有3个CMyString的对象:str1、str2和str3,在程序中语句str1=str2=str3将不能通过编译。
● 是否把传入的参数的类型声明为常量引用。如果传入的参数不是引用而是实例,那么从形参到实参会调用一次复制构造函数。把参数声明为引用可以避免这样的无谓消耗,能提高代码的效率。同时,我们在赋值运算符函数内不会改变传入的实例的状态,因此应该为传入的引用参数加上const关键字。
● 是否释放实例自身已有的内存。如果我们忘记在分配新内存之前释放自身已有的空间,程序将出现内存泄露。
● 是否判断传入的参数和当前的实例(
this ) 是不是同一个实例。如果是同一个,则不进行赋值操作,直接返回。如果事先不判断就进行赋值,那么在释放实例自身的内存的时候就会导致严重的问题:当*this和传入的参数是同一个实例时,那么一旦释放了自身的内存,传入的参数的内存也同时被释放了,因此再也找不到需要赋值的内容了。