2019独角兽企业重金招聘Python工程师标准>>>

面试

Intro

简单介绍下面试的前置情况。

面试的公司是鲸鱼游戏,职位是后端开发工程师,开发语言C++。

这篇博文主要是为了记录面试中发现的自身不足。

这次面试里,因为面试约得比较匆忙,所以基本没做任何准备。讲道理的说我是有点盲目自信了,毕竟C/C++是我的第一语言来着,本来以为考察语言的部分不会有什么问题,但没想到因为紧张而错漏百出。

那么接下来就直接进入正题,以下是对面试中遇到的问题重新思考后的回答和想法。

下面面试官的提问并非原话,有经过脑补润色。

起手式:面向对象

面试官:讲讲面向对象,继承,还有多态。我们都知道程序设计有两种常见的范式,面向过程和面向对象,讲讲面向对象给我们带来了什么好处?

实话说第一问就已经有点出乎意料,但想想其实还是在意料之中。初级职位更注重于基础概念和技能,中高级职位可能会在数据结构和并发一类的问题上更深入。

答:抽象,归类blabla...易于维护blabla...

全错。

现在回忆起来,面试官想问的其实只有一点,就是那句封装

封装是面向对象的核心概念之一

封装使代码成为一个黑箱,让我们不必关注它的实现,而是关注它的行为接口

这产生了面向接口编程的概念,我们不再关注封装后的对象内部的逻辑,我们给封装后的对象以输入,然后从封装后的对象里取出数据。

封装并不只是一系列接口的集合,更包含了数据状态,它就是一个微型化的服务,调用者告诉它去做什么事,而不关心它怎么做。

第二招:继承

面试官:讲讲继承。

我:代码复用,blabla......

代码复用,这是核心。

代码复用是继承最主要的作用,大家都知道。面试官并没有在这方面继续深入,所以能答出代码复用其实已经差不多了。

除非再抠上语言相关的语法细节:多继承单继承

多继承

C++ 采用了多继承模型,即一个子类可以有多个父类。

Father ------||====> child
Mother ------|

多继承可以允许一些特殊的编程范式。比如说mixin模式。但是多继承也存在其固有的复杂性,主要表现在运行时多态上。

举几个多继承上常见的问题。

  1. 父类成员冲突

典型场景如下

class ParentA {
public:void func(){}
};class ParentB {
public:void func(){}
};class Child: public ParentA,ParentB {};int main() {Child c;c.func(); // errorreturn 0;
}

解决办法也很简单

int main() {Child c;c.ParentA::func();return 0;
}

之所以如果不调用 func 就不会出错,是因为 func 在编译后的ABI导出的名字并没有产生冲突。但如果主动调用了func,编译器则需要插入一个函数调用,但这里的func语义却是不明确的,所以编译阶段就会报告错误。

  1. dynamic_cast会改变指针

dynamic_cast是基于RTTI的运行时类型安全的标准类型转换,dynamic_cast本身是一个关键字,这里就说一说dynamic_cast的行为和多继承。

多继承下的dynamic_cast会修改指针绝非危言耸听。事实上只要稍作思考就能得出这样的结论:多继承下的内存布局应该是什么样子的?

v Pointer to Childv Pointer to ParentB
v Pointer to ParentA
|  ParentA  | ParentB           | Child          |
[-----------====================>>>>>>>>>>>>>>>>>]

C++ 鼓吹Zero cost abstraction也不是一天两天的事情了,成果如何不予置评,但显然,专门为多继承下的指针附加类型信息,以允许ParentB*类型的指针指向的地址和Child*相同是不可能的。

遑论C++标准里根本没地址这回事儿了,指针指向的是啥玩意儿都有可能。

单继承

单继承就简单得多,只允许一个父类存在,根据语言设计也可能允许实现多个接口。比如说JavaC#。以我比较熟悉的 Rust 为例(暂不提继承,因为Rust就没继承这码事儿,全是Trait),一个struct可以实现多个Trait,然后以Trait object来实现对象多态。

单继承更多是在多态、重载、接口等方面的取舍,就不细谈了。

第三招:多态

多态和面向接口编程

面试官:知道多态吗?多态有什么好处?

答:多态就是...blabla...不去关注子类细节,归类成xxx......blabla

多态算是面向对象基本概念之一了。

多态最基本的解释就是同一个接口的不同实现,但我理解中的多态解释则更趋向于类型擦除,即我不在乎你是什么黑人、白人、黄种人、香蕉人,我只要你能做到某件事。本质上来说,多态的主要作用就是擦除细节

举个例子,我打算去面试一家公司,面试官想要的是什么呢?他想要的是能干活的人

class Worker {
public:const int declarePay;const int declareEfficiency;BOOL testWorkEfficiency(SomeShit);virtual ~Worker()=0;
};class Company {
public:BOOL hire(Worker) {...}
}

面试者可能是HardWorkerFxxkWorker都是Worker实例,但他们也同时是Human,可能是Wife,可能是Husband,也可能是FatherMother,但是这些我们都不关心。

我们不可能为每个People某某某各自定义一个BOOL hirePeople某某某() {},我们关注的是工作能力,所以我们要在类型里擦除掉这些无关的细节,保留关注的部分。

多态做的就是这样的一件事:我不在乎你是谁,我在乎你是不是能干好这件事的人。

这么说其实有些脱离主题了,因为这是面向接口编程的思想,而不是对多态的学术解释,但这确实就是我对多态的理解,它的主要作用就是隐藏差异,进而发展为擦除细节

我的回答其实根本没到点上,也没Get到面试官的point,所以面试官很快就换了下一个问题。

谈谈虚函数

面试官:虚函数的作用是什么?

答:啊?实现多态啊?...

可以说是最差的回答。

面试中没有反应过来问的啥,知道被拒绝了才突然明白。

o( ̄ヘ ̄o#)

这已经问到语言细节了,所以咱们就从语言出发来讲。

多态

首先虚函数是什么?虚函数是C++实现多态的手段,这么答没错,学过C++都知道。不过虚函数不仅仅是这一点。

咱先从这一点讲起。

虚函数通过一个叫虚函数表的东西来实现多态,这个虚函数表是实现定义的,标准没有对vtable做什么规定,比如说必须放在类指针的前后几个字节处啊什么的......不存在的。所以也不谈虚表是怎么实现的,这已经是具体到平台和编译器上的差别了,要抠这个的话必须去读编译器和平台相关的各种文档了,PE格式啊DLL啊SharedObject啊什么的。

如果问起来的话......嗯......这个职位应该很厉害。

所以我就跳过了。

直接给个虚函数的实例,真的没什么好说的。

#include <iostream>class ParentA {
public:virtual vFunc() {std::cout << "ParentA" << std::endl;}
};class Child: public ParentA {
public:virtual vFunc() override {std::cout << "Child" << std::endl;// 顺便写调用父类的ParentA::vFunc();}
};

虚析构函数

C++虚函数的另一个重要用途就是虚析构函数。

因为......C++对象模型中,析构函数的位置十分尴尬。

构造函数也就算了,无论如何也要显式调用一次。

析构函数则因为多态的存在而十分尴尬:给你一个父类指针列表,你显然不能一个一个检查这些指针指向是什么对象,然后再转回去,最后才 delete 它。

光是听起来就麻烦得要死,更别提有时候根本做不到。C++脆弱的RTTI和基本不存在的Reflection可是出了名的。

C++对这个问题的解决办法就是虚析构函数。

和一般的虚函数不同,一般的虚函数一旦被override,除非你主动调用指定父类的虚方法,否则调用的必然是继承链最后一个override了这个虚方法的类的虚方法实现。

析构函数的话就稳了,它会链式的调用继承链上每个类的析构方法,多继承的情况下则是按照继承的顺序调用析构方法。

不用主动写ParentA::~ParentA(),是不是特别爽?

还行,这就是个语法糖。

纯虚函数和抽象类

最后是纯虚函数。

其实这玩意儿我更愿意称他为接口

本质上来说,纯虚函数规定了一个方法,这个方法接收固定的输入,并保证提供一个输出,相应的可能还有异常声明,来说明这个方法可能抛出的异常。

怎么样,看起来眼熟不?

还没完,纯虚方法没有实现(你开心的话也可以写个实现),强制要求子类必须实现,而定义了纯虚方法的类被称之为抽象类

我想就算是叫它接口类它也不会反对的吧。

纯虚函数可以类比于C#interface,或者typescriptinterface,总之就是各种语言的interface。这些interface在具体的规定上可能有所差异,比如说不允许写数据成员啦,数据成员写了不算在实现interface的类上还要再声明一次啦,interface的方法可不可以有个默认实现啦,这些都是细节。

还记得上面我说多态吗?多态的目的是擦除类型细节,所以这些长得各不相同百花齐放的interface做的事情其实都是一回事:你能做啥,那么你是啥。

这里再说个细节,纯虚函数作为析构函数的时候,析构函数应该有个实现......

听起来挺奇怪的?不写纯虚析构函数实现的话,会报个链接错误...至于为什么要这么做,其中的取舍就不得而知了。

C++的纯虚函数和抽象类很灵活,没有其他语言interface种种限制,如果要追问纯虚函数

when? where? why?

那就要看到具体场景了,C++这些灵活的特性一不小心就会变成滥用,反正这么问我应该也就答interfacemixin以及其他具体需求的场景这样子了。

Mixin 模式

Mixin模式在Python里比较常见,不过C++也并不是没有。通过定义纯虚析构函数,来给一个对象混入特定功能而又不允许自己被独立构建,算是个常见的范式。

举个例子,引用计数,如果发现自己引用归零了就释放资源,线程安全之类的问题先不管,仅仅是展示这个范式。

#include <iostream>class RcMixin {
private:using deleter = ()->void;int *_rc = nullptr;deleter resDeleter;
public:RcMixin(deleter resDeleter):resDeleter(resDeleter) {*_rc+=1; // 线程安全就先放一边}RcMixin(const RcMixin& other) {resDeleter = other.resDeleter;*_rc+=1;}virtual ~RcMixin() = 0 {*_rc-=1;if(*_rc <= 0) {resDeleter();}}
};// 虽然是个RcMixin但是外界并不需要知道它是RcMixin
class SomeShit: private RcMixin {
private:int* res = nullptr;
public:SomeShit(): RcMixin([&this]() {std::cout << "" << std::endl;delete this.res;}) {res=new int(10);}virtual ~SomeShit() {}
};int main() {SomeShit a;auto b = a;auto c = b;
}

代码没测过,反正大概就是这种感觉,将某些功能混入一个现存的类,而不需要做太多的工作。在C++里没那么方便,强类型下的Mixin需要很多变通技巧才能愉快地混入新功能,而鸭子类型Duck typing的语言则舒爽很多,当然,最好的还是具有完善 ReflectionAttribute 支持的语言,完全避免了对Mixin类型的构造和需要利用的数据的绑定一类的不必要的关注。

扩展:虚继承

同样是 virtual 关键字,虚继承和虚函数关系就不怎么大了。

虚继承面对的问题是多继承时,多个父类继承自同一个基类这一问题。

听起来是不是有点奇怪?这些父类继承自同一个基类会有什么问题?

事实上,这个问题取决于写出多继承代码的人,也取决于这多个父类是否有对多继承方面做过考虑。

举个简单的例子,ParentAParentB都继承自DataAParentA修改了DataA的数据,但ParentB不知道。如果ParentB需要根据DataA的某些数据进行操作——很遗憾,这个行为可能与预期的不同。

之所以引入虚继承,是为了解决要不要共享同一个基类实例的问题,选择虚继承,则选择共享基类实例。

共享基类实例的优势是,多个父类的功能可以无缝结合。ParentAParentB可以共享基类定义的Mutex等状态资源——当然,前提是设计父类的人有过这方面的考虑。

不然的话,不共享基类实例是个保守但更安全,不易出现歧义的选择。

第四招:数组和链表

面试官:我们聊一下数据结构方面吧.....讲一下数组和链表?可以从访问和删除两方面来说。

答:数组允许随机访问,只需要一步就能找到对应元素,而链表需要......blabla,数组删除元素如果需要移动后续元素的话,会产生复制操作性能损失,链表只需要修改几个指针...blabla。

实际上答到这里我已经不知道自己在说啥了。

数组和链表的区别还是挺大的,我应该算是Get到了几个点?下面是重新整理了语言后的回答。

数组和链表的内存布局

数组和链表两者都是线性数据结构,表现上都是一条有头有尾的有序序列,但是储存方式上有区别。

数组的储存方式是一端连续的内存空间,索引只需要进行一次指针运算即可获得目标元素的位置,也可以理解为访问时间始终是O(1)

PS: 还能写出 0[array] 这样的骚写法,不怕被打死的话。

链表的内存布局则是分散的,通常的链表实现往往是插入元素时动态分配一个元素的空间,而删除的时候再释放,长此以往对内存是不友好的,容易产生内存碎片,导致分配较大空间时无法寻得足够长的连续内存片段而造成分配失败。

......当然,是长期才会产生的问题,而且是切实存在的问题。

索引

对于数组来说的话,可以理解成标准库的 std::array,也可以理解成原始数组,但不变的是索引方式始终是O(1)复杂度,而且支持随机访问迭代器。

对于链表来说,不考虑优化后的变体,索引方式在本质上都是顺序访问迭代器——指针也算是概念上的迭代器。所以对于链表,访问时间的复杂度最坏情况应该是O(n)n是链表长度。不用说,索引性能自然是不如数组的。

删除

数组删除元素其实是比较烦的,复杂度应该是O(n)n是数组长度减去删除元素在数组中的位置。最麻烦的是万一数组很长,那么复制元素到上一个位置将会是噩梦。

当然也不是不能优化......把移动的操作推迟到插入新元素的时候就好了,用一个占位符表示这里已经被删除,同时记录前面有多少个元素被删除。这样一来索引性能会下降(因为要找到上一个被删除的元素,然后更新索引位置,直到找到正确的元素),删除性能提高(只要找到上一个被删除的元素然后记录自己作为被删除元素的位置就好),整体实现的复杂度提升,索引删除插入都要另外编写实现,感觉得不偿失。

链表删除元素很简单,索引到需要删除的元素的时间复杂度是O(n),删除操作的时间复杂度是O(1),而且实现简单。

扩展:结合两者?

好吧,这个问题面试官没问到。

链表和数组结合一下能解决一部分内存碎片的问题,基本思路的话......咱预先分配100个元素,如果插入的元素超过了100个,咱再分配100个元素的空间,然后索引的时候再去找第二个池?

这个思路术语叫什么记不起来了。

哦不!他到底想问什么?

猜一猜面试官到底想问些什么?

  1. 动态内存分配:数组定长,而链表变长。我感觉这个特征基本没什么好说的,工作中基本没有机会自己重新实现一个线性容器,除非要定制一些特殊的结构,环形链表之类的东西。其他像是链表,数组,队列,标准库都有相应的实现。也许是考虑自行编写线程安全版本的STL?
  2. std::arraystd::list。所以问的是啥呢...?提供的保证和implement specified还有undefined behavior吗?STL现在还没有concept,但是早早就有了SFINAEenable_if之类的东西,constexpr if 更是极大地强化了编译期元编程方面的能力。如果是问标准模板库方面的东西的话,我觉得问标准库线程安全啊,迭代器算法之类的东西要合适得多。所以......大概也不是想问这个。
  3. 迭代器。如果是这个的话我真的希望面试官大人能直接说出迭代器三个字......不过好歹回答出随机访问了,应该不至于吧。

第四招:数据库索引

面试官:讲一下数据库的索引有什么作用。

我:懵逼......

还行,直接懵了。

因为完全没搞明白面试官的意图:索引指的是啥?面试官是想问数据库索引的方式吗?B+树该怎么实现?

回来路上我考虑了一下,这几方面可能可以作为回答的方向。

索引的实现

数据库索引的常见实现方式是 B+ 树,我数据结构学的不好,只知道 B+ 树是个很厉害的数据结构.....所以博文写到这里,不得不开始查资料了。

B+ 树是一种树数据结构,通常用于数据库和操作系统的文件系统中。B+ 树的特点是能够保持数据稳定有序,其插入与修改拥有较稳定的对数时间复杂度。B+ 树元素自底向上插入,这与二叉树恰好相反。

如果问起B+树实现,或者让手写个B+树的话,我也只能望而兴叹了。

postgres 数据库的索引属性

对于数据库的实现我了解不多。

大概就是建立个独立的 B+ 树索引......吧?

emmmmmm

真想不出了...

第五招:Primary key

面试官:说下主键的作用。

我:emmmmmm.....

到这里我基本已经萌的不行了。(无错字)

内心OS:我是谁?我在哪?我要干什么?

甚至连zhujian都听成了zujian

被面试官提醒了一下

面试官B:就是那个 key

我也没反应过来......

有啥用啊(天真脸)

主键的话,具有唯一性的索引?

emmmmm,不然还有什么作用呢......

看来数据库必须下功夫学一学才行啊......

叮叮叮——You fxxk up

面试官:十动然拒。

我:理解理解,谢谢谢谢。

还行,回顾完整个面试流程,除了C++部分可能是因为发挥失常之外,数据库方面的确是没有下够功夫,以至于连索引和PrimaryKey这两问都在持续懵逼。

而且实话说面试,确实有技巧这回事......

面试官提的问题也存在着范式——网络上面试真题什么的,看起来像是玩笑,但面试官提出这些问题的时候却是认真的。

尽管......这种

聊聊xxxx(某技术/概念/工具),xxx的作用是什么

的提问确实让人不容易抓住重点......

考察基础的角度来说,现场白板写一个程序,然后再深入聊聊这么写的用意,有没有优化方案,考察对语言的理解和api设计、代码架构能力,比单纯的说说xxx,问xxx作用要实际的多。当然并不是说这么问不好,这些概念的掌握也是非常重要的基础,而且能有效考察面试者语言组织能力和对这方面知识的掌握程度。

唯一不好的就是,面试者和面试官聊的过程就像是用黑话交流一样......

不说了,学这黑话去......

转载于:https://my.oschina.net/u/3888259/blog/1833040

鲸鱼游戏后端开发工程师职位面试过程回顾相关推荐

  1. python开发工程师面试题-一名python web后端开发工程师的面试总结

    背景介绍 工作一年多不到两年.之前一直在做C++的MFC软件界面开发工作.公司为某不景气的国企研究所.(喏,我的工作经验很水:1是方向不对:2是行业有偏差).然后目前是在寻找python后端开发这一块 ...

  2. python后端工程师_Python 招聘:[字节跳动] 后端开发工程师 | Python 技术论坛

    大家好,字节跳动后端研发工程师正在招聘. 职位为内推职位,社招.实习均可,欢迎大家了解投递. 后端开发工程师 职位描述: 1.对标业内顶级 PaaS 和  SaaS 平台产品,按照业务领域需求,打造优 ...

  3. java后端开发工程师笔试_后端开发工程师面试参考

    为什么要写这篇文章 后端开发工程师的知识体系大而杂,从Java基础到算法.数据库.操作系统.网络.中间件.系统设计等都可以理解为后端的射程.下定决心开始准备面试时,可谓是千头万绪无从下手.我的开始源于 ...

  4. php架构师之路 知乎,知乎后端开发工程师 交叉面(第一次面试遇到架构师)

    知乎 架构师面 知乎一面面经: 一面 2021 秋招面经汇总 : 面经汇总 我的其他文章: 分享汇总 职位: 后端开发工程师 结果:通过 面试我的据说是知乎架构师,感觉经验很丰富,面试体验相当不错 面 ...

  5. 光宇游戏软件测试面试,[腾讯][急招][缺人][光速面试] IEG 天美游戏测试开发工程师...

    腾讯天美游戏测试开发工程师 岗位描述: 负责移动游戏产品测试工作,包括功能.自动化.性能,兼容性等维度,保障项目顺利发布: 能够根据项目中遇到的问题进行技术研究,工具开发都手段针对性的解决问题,并提升 ...

  6. 0814-网易游戏雷火2022秋招Web后端开发工程师

    0814-网易游戏雷火2022秋招Web后端开发工程师 编程题1 这道题恶心的是输入的处理,需要将这个各种方括号的玩意儿读出来,解析为ArrayList,想了一下还是有办法可以解决的,直接根据],分割 ...

  7. [笔试题目] 美团2015年9月后端开发工程师笔试题

    由于题目是我通过草稿回顾,可能表述不清,但是内容大致一样.希望该博客内容对你有所帮助,题目所有权归美团公司所有,我只是想分享给大家学习,还望贵公司海涵~ 面试职位 应聘职位:后端开发工程师 岗位描述: ...

  8. 【计算机就业-后端开发工程师】校招想去互联网公司担任后端开发工程师该怎么准备

    前言 hello大家好,我本次分享的主题是计算机专业校招想去互联网公司担任后端开发工程师该怎么准备,比如说计算机专业投什么岗,笔试面试需要准备什么.我将结合自己以及身边小伙伴们的秋招经历来进行简要的分 ...

  9. IT开发工程师经典面试

    IT开发工程师经典面试 1.请你自我介绍一下你自己? 回答提示:一般人回答这个问题过于平常,只说姓名.年龄.爱好.工作经验,这些在简历上都有.其实,企业最希望知道的是求职者能否胜任工作,包括:最强的技 ...

最新文章

  1. JQUERY的size()与length
  2. 在unity2d同屏显示9千人
  3. [react] react多个setState调用的原理是什么?
  4. 问题:pom文件有删除线,不能识别maven模块
  5. 【从蛋壳到满天飞】JS 数据结构解析和算法实现-堆和优先队列(一)
  6. 知方可补不足~sqlserver中使用sp_who查看sql的进程
  7. C#自定义控件七水波纹
  8. shiro配置文件shiro.ini简介说明
  9. wps画流程图交叉弧形_wps画的流程图打印不清晰|wps怎么绘制出立体流程图?wps绘制出立体流程图的方法...
  10. 时间序列信号处理系列-基于Python的同步压缩变换
  11. vbs文件放在java工程中如何调用_VBS教程:在 VBScript 中使用对象
  12. 2017 技术大检阅
  13. python自我介绍_面试技巧:一分钟自我介绍怎么说才精彩
  14. @Autowired浅析
  15. python中的os.chdir()定义及作用
  16. AI人工智能机器人教室方案(含全套配置)
  17. python是一种语言吗-python语言是非开源语言吗
  18. android开发 之 Paint
  19. php pdf只能查看不能下载,对于pdf文件不支持浏览器下载如何处理?
  20. c语言用星号循环打出爱心,学过C语言的同学,是否还记得当年各种打印各种星星。今天,我就改一版使用Shell脚本输出各种星星。打印单个星号         这个没啥可说的,直接上代码:#...

热门文章

  1. VBA中冗长的多条件判断如何破
  2. 一句话说清OpenShift的核心价值!
  3. redis分布式锁工具类
  4. 2021年度全球最宜居的城市排行榜出炉,你中意的城市上榜了吗?
  5. Java 基础之实例方法,实例变量,类方法,类变量
  6. Testin我的测试法宝
  7. win7当前页面的脚本发生错误怎么办?
  8. 构造函数和析构函数的顺序问题
  9. 【基本功】Litho的使用及原理剖析
  10. linux安装qq qq for linux