████公司 C++ 笔试+面试回顾

原创
2020/06/29 18:58
阅读数 449

投了半个月的简历,在51终于联系到一个cpp/lua的面试,这段时间能找到愿意给机会面试的公司还蛮难得的。今天刚面完,公司名就先码了。

这次面试对我来说算比较新鲜,之前接过的面试都没有发几张试卷做题的环节。最早面过一次也只是口头提了要求在白纸上写代码。

而面试环节中,讨论的求职理念、愿景、短期目标这些话题,很值得深思。

笔试部分

先说笔试部分。笔试分五个部分,简答、叙述、逻辑、算法、分析。这里挑几个例子。

简答题

简单介绍你的游戏经历。

玩过哪些网络游戏?游戏时长?充值金额?

这类题目的主要目的都是考察是否对申请的职位有基本的了解。如果从未玩过某一类型的游戏,而公司的产品正好是你没玩过的这类游戏,那么你在工作中就可能会遇到沟通上的问题。无论是面对面问答还是笔试,这类问题的回答策略实事求是即可。

基础题

定义数组int a[100],求sizeof(a)strlen(a)的值分别是什么。

sizeof(a)返回的是数组大小,即400。我没注意数据类型是int,答成了100。实际上int的大小也不一定是4字节,我没记错的话按照标准文档的定义是不小于2字节,例如Win16 平台。现代32-bits/64-bits的体系结构基本能保证32-bits以上。

strlen(a)则是一个坑,按照题干中描述的int a[100]的写法,数组a是未初始化的,数组内容依照标准规定是undefined behavior。strlen函数在遇到NUL的时候返回,这是典型的数组越界访问情形。而越界访问也是典型的undefined behavior。所以这题的正确答案应该是undefined behavior。

简述 new,delete,new[],delete[] 的联系和区别。

说到new可能是C++里最重要的内容之一了,new表达式有两种重要的形式

  1. new (placement) type initializer
  2. new type initializer

第一种形式称之为placement new,在一片指定的内存上构造对象。第二种形式就是大家熟知的常规用法了。举个栗子。

// 使用placement new必须引入这个标准头文件
#include <new>
#include <cassert>

struct Student {
    int id;
   	char name[16];
};

int main() {
    // example 1
    // 常规 new 表达式
    auto student = new Student;
    // example 2
    // placement new,在一片已经分配的内存上构造对象
    auto mem = new char[sizeof(Student)];
    auto student = new(mem) Student;
    assert(static_cast<void*>(mem) == static_cast<void*>(student));
    return 0;
}

placement new有很多细节可以在文档查阅到,放个链接在这里。cpp reference - new expression

题目问 newnew[]deletedelete[]之间的关系,其实是摆明车马考newdelete配对、new[]delete[]配对这个知识点了。这里要记住newoperator new(),而new[]operator new[](),这是两个不同的操作符。这里引用cppreference上对new数组的说明来解释下为什么new[]出来的内容不能用delete去删除。

数组的分配中可能带有一个未指明的开销(overhead),且两次调用 new 的这个开销可能不同,除非选择的分配函数是标准非分配形式。new 表达式所返回的指针等于分配函数所返回的指针加上该值。许多实现使用数组开销存储数组中的对象数量,它为 [delete] 表达式所用,以进行正确数量的析构函数调用。

(原链接)[https://zh.cppreference.com/w/cpp/language/new]

此外诸如重载new运算符之类的技巧这里不作赘述,了解了placement new基本上重载new运算符就不是什么大问题了。上面的链接可以查到如何调用自定义的new运算符。

逻辑题

有A、B、C、D四个人,在夜里过一座桥,分别耗时1、2、5、8分钟,只有一支手电,桥同时只能走两个人。如何安排使四个人过桥时间最短?

我不确定这题做对了没。我的答案是让A走三个来回,把B、C、D都带过去。合计就是2+1+5+1+8一共17分钟。

分析题

下面的代码输出是?

#include <iostream>

using namespace std;

class A {
public:
    A() {
        cout << "A::A()" << endl;
        init();
    }
    
    virtual ~A() {
        cout << "A::~A()" << endl;
    }
    
    virtual void init() {
        cout << "A::init()" << endl;
    }
    
    virtual void method() {
        cout << "A::method()" << endl;
    }
};

class B: public A {
public:
    B(): A() {
        cout << "B::B()" << endl;
    }
    
    ~B() {
        cout << "B::~B()" << endl;
    }
    
    void init() {
        cout << "B::init()" << endl;
    }
    
    void method() {
        cout << "B::method()" << endl;
    }
};

int main() {
    A* a = new B();
    a->method();
    delete a;
    return 0;
}

一行一行开始分析,先看构造的过程。

B():A()显然调用了父类构造函数,按顺序应该是父类构造完毕再构造子类,在父类A::A()里调用了init();init是一个虚函数......所以调用的还是A::init。注意,在构造和析构函数中调用虚函数,调用的并不是派生类的覆盖函数,而是当前类中的最终覆盖函数。

这里我答题的时候做错了。

当从构造函数或从析构函数中直接或间接调用虚函数(包括在类的非静态数据成员的构造或析构期间,例如在成员初始化器列表中),且对其实施调用的对象是正在构造或析构中的对象时,所调用的函数是构造函数或析构函数的类中的最终覆盖函数,而非进一步的派生类中的覆盖函数。 换言之,在构造和析构期间,进一步的派生类并不存在。

(原链接)[https://zh.cppreference.com/w/cpp/language/virtual]

所以A* a = new B();的输出是:

A::A()
A::init()
B::B()

再看a->method();A::method是一个虚函数,在B类重写了,所以这里调用的是B::method

输出是:

B::method()

最后是delete a;

析构函数从子类开始按继承链往回析构,先析构B再析构A。输出是:

B::~B()
A::~A()

考虑情境,用C++写出实现代码。

情境:猫大叫一声,所有老鼠开始逃跑,主人被惊醒。

要求:

  1. 具有联动性,老鼠和主人的行为是被动的
  2. 考虑扩展性,猫的叫声可能引发其他联动效应

这题实际写的代码会很长,就不贴了。实际面试中也只是写了一点pseudo code大概说一下思路。

算法题

已知前序遍历结果abcdefgh,中序遍历cbdaegfh,画图表示这棵树并写出后序次序。

经典数据结构题,根据遍历还原二叉树,虽然说基础但我没答上来。这里复习下。

前序遍历的顺序是根左右,中序遍历的顺序是左根右,后序遍历是左右根。注意这个前中后是指根在遍历中的次序,最先遍历根的是前序,最后遍历根的是后序。人肉还原二叉树的过程基本是这样。

从前序遍历结果分析出根节点是a,中序判断出左子树三个节点cbd,b是左子树的根。然后看右子树的前序是efgh,右子树根是e,中序看到e前面没有节点,所以e的左子树是空的,右子树是gfh。前序是fgh,f是e的右子树的根,左节点g,右节点h。这样就人肉重建完成了。

写一个函数,找出整数数组中第二大的数。

我的代码如下。

#include <iostream>
#include <vector>

using namespace std;

int find_second(const vector<int>& container) {
        int max=0;
        int second=0;
        for(const auto& i: container) {
                if(i>max) {
                        second=max;
                        max=i;
                }
                if(i<max && i>second) {
                        second = i;
                }
        }

        return second;
}

int main() {
        cout << "second is " << find_second(vector<int> {1,2,3,4,5,6,7,9,10}) << endl;
}

当然你要刷leetcode这样是不行的,因为是笔试,拿笔写代码很蛋疼,所以尽可能简略了一下。边上写明了假设输入数字都大于0,长度不足时返回0。这类边界情况注意说明下你注意到了即可。大概。

面试部分

要不然你们等我下一篇博客补充吧?

展开阅读全文
加载中
点击加入讨论🔥(2) 发布并加入讨论🔥
2 评论
1 收藏
0
分享
返回顶部
顶部