【网易】C++岗-超详细校招面经
10.26 收到网易邮件了,结果还是蛮令人高兴的,毕竟懒狗秋招就投了四五家,差不多算是all in了
面试开始前半小时手撕一道字符串加法,lc415,easy题…做好边界条件就行
1、自我介绍
2、很熟C++?C++四种cast讲一下吧
经典八股,送分题
static_cast:
转换格式:static_cast < type-id> (expression)
将expression转换为type-id类型,主要用于非多态类型之间的转换,不提供运行时的检查来确保转换的安全性。主要在以下几种场合中使用:
- 用于类层次结构中,基类和子类之间指针和引用的转换;
当进行上行转换,也就是把子类的指针或引用转换成父类表示,这种转换是安全的;
当进行下行转换,也就是把父类的指针或引用转换成子类表示,这种转换是不安全的,也需要程序员来保证;
- 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum等等,这种转换的安全性需要程序员来保证;
-
把void指针转换成目标类型的指针,是极其不安全的;
dynamic_cast:
转换格式:dynamic_cast < type-id> (expression)
相比static_cast,dynamic_cast会在运行时检查类型转换是否合法,具有一定的安全性。由于运行时的检查,所以会额外消耗一些性能。dynamic_cast使用场景与static相似,在类层次结构中使用时,上行转换和static_cast没有区别,都是安全的;下行转换时,dynamic_cast会检查转换的类型,相比static_cast更安全。
这里还顺便讲了一下dynamic_cast的原理:根据继承关系的不同(单继承、多继承、虚继承)比较继承体系里每个类对应的TypeDescriptor的信息,TypeDescriptor里以char[]的形式记录了类型的名字,可能是做了一个比较字符串的操作,效率可能是比较低的。
const_cast
用于移除类型的const、volatile和__unaligned属性。上述二者不可以。注意**移除**二字,**添加**const、volatile和__unaligned属性不是const_cast的职责。
常量指针被转换成非常量指针,并且仍然指向原来的对象;常量引用被转换成非常量引用,并且仍然引用原来的对象。
reinterpret_cast
非常激进的指针类型转换,在编译期完成,可以转换任何类型的指针,所以极不安全。非极端情况不要使用。[转换原理为重解释。]←当时回答的时候忘了说这个。
基本上不用全部答完,毕竟我自己也就答个七七八八。
然后面试官开始追着问了。
3、dynamic_cast转换失败时会返回什么?
这个愣了一下,当初没想过这个问题,不过想想也知道一般就是返回一个空指针
不过其实这样还不完全,如果要转换的是引用,那么是抛出std::bad_cast异常。
4、dynamic_cast 什么情况下不能转换?
有点没get,就回答了待转换的两个指针指向的类不在同一继承体系内。
如果这两个类都在同一继承体系内呢?
这里有点懵。
试探地说了下是不是至少其中一个类有纯虚函数。
那假如这两个类都没有纯虚函数呢?
更懵了。
继续试探,是不是至少其中一个类的构造函数是私有函数?
面试官思考了一下,还是继续问:
假如也没有这种情况呢?
举白旗了,说不知道。
这里的正确答案应该是两种情况:
1、非public不可
2、类没有虚函数(这个接下来面试官追问提示了下)
你刚才说dynamic_cast类型检查的原理是比较类型名,那类型名如何取得?
RTTI
那RTTI在哪?
RTTI应该在虚表指针指向位置的前四个字节。
然后我又自己嘟囔:如果这个类没有虚函数呢?那是没有RTTI吗?不然存在哪里?这怎么转换?好像有点麻,不知道了。
面试官听到以后笑了笑就说你自己回去看吧,这种奇怪的问题都是从effective三剑客里出来的。
其实这里没啥好纠结的…没有虚函数自然就没有虚表指针了,那RTTI自然无从谈起,所以直接是转换不了的。
这里dynamic_cast讲得差不多了,因为刚才reinterpret_cast没讲到重解释所以问了个:
5、你刚才说reinterpret_cast是任意类型间转换,是什么意思?
重解释。
如果一个double类型、值为100.1的变量赋值给一个long long类型的变量,那结果是多少?
这里不知道是面试官疏忽了还是估计挖坑,这里说的是赋值而非reinterpret_cast,所以直接截断就好。如果说是reinterpret_cast,那一定不是100,具体是多少需要计算,需要明白double类型的格式。
到这里cast就问的差不多了。
6、说说右值引用。
八股
先讲了一下左右值的定义:
左值:可以出现在赋值符号左边或右边的值。
右值:只能出现在赋值符号右边的值。
顺便说了下邪道定义:能被取引用的就是左值,反之为右值,这一定义的纰漏在于,C++中无法对一个bit取引用,比如位域中的某一位,vector
移动语义
解决了各种情形下对象的资源所有权的转移问题。
需要知道的是,多数情况下,右值所包含的对象都是可以安全的被移动的。这一特性是的右值被用来表达移动语义。移动语义具体实现是进行浅拷贝然后打断原指针。另:std::move()实际上是static_cast<T&&>()的简单封装。
完美转发
void PrintT(T && t)
{
cout << "rvalue" << endl;
}
void PrintT(T& t)
{
cout << "lvaue" << endl;
}
void Test(T && v)
{
PrintT(v);
}
对于上述代码,很容易让人误以为调用Test函数将会打印出”rvalue”,但实际上结果为打印出”lvalue”,因为在Test函数中,将变量v传递为PrintT函数时,已经为变量v开辟了空间,则此时v为左值,故打印出”lvalue”,要打印”rvalue”则需要调用forward()。
说一下怎么实现移动构造函数比较有效率
希腊奶,哇嘎乃,一带哟
面试官吐槽了句,看来不太清楚具体细节
6、说说智能指针吧
八股环节,签到题
shared_ptr 引用计数
unique_ptr 建立所有权
weak_ptr 解决shared_ptr的环形引用问题
基本上围绕上面列出的点展开就行,有能力的话可以结合RAII讲一下,我不太擅长讲这种比较抽象的思想所以没讲;还可以讲一个auto_ptr铺垫一下,(我自己是讲了下这个被弃用的auto_ptr),也可以聊聊boost里的scoped_ptr(我没看过boost库,所以没讲)
7、写一下shared_ptr的框架和析构函数的具体实现
本来说是实现一下shared_ptr,但是时间有点紧,就说列一下函数成员和数据成员,然后让实现一下析构函数就行了
这里写一下析构函数就行了
template<class T>
~my_shared_ptr(void)
{
--use_count;
if(0 == use_count){
delete ptr;
ptr = nullptr;
}
}
写的比较简陋(
写完问了下要不要保证线程安全,面试官本来说不用,说毕竟本来shared_ptr本来就不保证线程安全。我就说shared_ptr应该只是不保证它指向的对象的线程安全,它本身的引用计数还是线程安全的,因为我记得当初看到的实现是使用了atomic级别的操作
那你实现一下吧
就加了几对pv操作就完了,也不难,记得条件变量那里也要锁上就行。
8、会网络编程不?
不会。
哈哈。
9、那聊聊操作系统吧,地址空间知道不?
这里估摸着是想问虚拟内存的事,就答了虚拟内存的东西。
先讲了下啥是虚拟内存:
虚拟内存使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。
然后说了下这个虚拟内存是依赖于页表的,页表放在PCB(process controll block,进程控制块)里,PCB又放在内存里。
还说了下有了虚拟内存就可以以共享内存的方式实现进程间通信,即将两个不同进程的某一块虚拟内存映射到同一块物理内存上。
你是不是写过操作系统啊
算是吧
10、说一下进程调度算法
八股 白给题
列一下我讲的几个
鸵鸟算法
太简单了
FIFO
太简单了
SJF
太简单了
优先权调度算法
太简单了,顺嘴提了一下非抢占式下优先级反转和进程饿死的问题
RR
所有的就绪进程按先来先服务的原则排成一个队列,每次调度时,把CPU 分配给队首进程,并令其执行一段时间。当执行的时间用完而未完成任务时,将它送往就绪队列的末尾。当时我用的就是RR来调度进程
多级反馈队列调度算法
(1) 应设置多个就绪队列,并为各个队列赋予不同的优先级。
(2) 当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个进程从第一队列依次降到第n队列后,在第n队列便采取按时间片轮转的方式运行。
(3) 仅当第n-1队列空闲时,调度程序才调度第n队列中的进程运行
ps:这里记不得这个调度算法的名字了,说是不是叫什么老化之类的,面试官说没事他get到我的意思了,说好像这算法名字叫什么什么失败的,kora你这家伙明明也不知道叫什么名字嘛!
11、操作系统分配给进程的时间的最小单位是?
时间片。
12、时间片的单位是?
大概是…ms吧,内心吐槽这都要问??
11、分配给进程的资源一般是指什么资源?
这个没怎么准备过…就随口说了什么IO设备、寄存器、打开的文件、地址空间,然后为了掩盖自己太菜的事实还提了下一个进程里线程间不共享的资源:寄存器、栈还有程序计数器(其实还有个状态字,但当时的我不知道)
12、你刚才提到线程是CPU调度的最小单位,那如果操作系统进行CPU调度的时候以进程为单位可以吗?
我当时回答是:
应该不可以吧…但是如果你这个操作系统里写了一个进程接收到CPU资源以后他要将这个资源如何分配给他拥有的线程的话,也不是不行啦
这不说白了还是要落实到线程嘛?
是啊
估计操作系统也问不出什么花来,这一部分就结束了
13、排序算法知道哪几个?
猴子排序。面试官被逗笑了
冒泡、插入、选择、shell、快排、堆排、桶排、归并、基数
14、对一个单向链表排序,那种方式比较好?
当时说了可能是归并,面试官也点头了
后来想想其实堆排也蛮ok的
个人认为应该去掉那些频繁进行前后交换的排序就行了
15、手撕二叉树最近公共祖先
lc.236 medium
不难
总结
总体上题目比较简单,对科班同学难度不大,答案都能在《C Primer Plus》《深入理解C++对象内存模型》《effective》三剑客和黑皮书里找到
手撕也蛮简单,lc刷个两三百应该没问题,虽然我就刷了100(
注意不要刷太多easy,easy题基本没用,刷来放松一下就行,尽量以medium为主,个人easy:medium:hard=2:6:2
二面
二面其实没太多具有普适性的东西,毕竟都是谈个人的项目和情景题。
项目谈了下难点,然后说了下几种解决方案还有这些解决方案的优劣以及个人的取舍,然后顺便用可变参数模板(variadic template)写了一个tuple的变体,因为是当初项目难点考虑过的解决方案之一,而且当时想整点花的加加分(
然后因为项目其实不是完全原创的(因为太懒),是用C++11/14把别人一个原来用C的项目完全重写,所以被问了下跟原来项目有什么优势,当时完全没考虑过这个问题…太菜了。然后只好打开项目边看代码边想着怎么编…瞎瘠薄说了下因为用了override关键字可能更安全了但是可能编译那边效率更低了,然后学着STL里的做法用模板+tag struct的方式优化掉了原项目一个标识用的变量,又谈了下宏函数。当时真的蛮慌的,所以说得比较乱,记不太清除了…
然后自我介绍的时候说比较了解stl,面试官就问什么部分比较懂,为了成为不一样的烟火说了个allocator,而且主要是当初刚好照着侯捷老师的《STL源码剖析》实现了一下SGI版本STL的allocator,然后又开始照着代码说,基本上都是在讲原理,虽然面试官问的比较详细,但是毕竟视频过了两遍、书过了一遍又写了一遍,所以基本上没有太大的问题,只是最后面试官问了一句:那你这个跟原版有什么改进吗?人直接裂开了,何德何能去改进啊!只好说没有…
接着是谈了图形学,我先讲了下图形学学了什么、了解到什么程度、学校里教了啥、自己又额外自学了啥(其实也就GAMES101和虎书,有能力的建议啃一啃Real–time Rendering),暗示了一下问我PBR,别问我渲染管线,然后面试官抬手就是:讲一讲渲染管线吧,当时听到人都裂成八瓣了,只好硬着头皮讲了讲大致的流程,然后面试官一深入问就举手投降了。
然后估计看渲染管线没答好,就问了个送分题安慰一下:说一说你知道的光照模型。简单说了下Lambert、half Lambert、Phong、BlinnPhong,忘了讲cook-torrance,因为当时慌了脑袋卡壳了…
最后问了下快排的最差情况和原因…当时我都怀疑是不是我表现太菜了所以问点简单的来安慰我…
总结
感觉是一次不太一般的二面经历…又没手撕又没情景题,跟其他同学的二面的画风完全不一样,参考价值大概不大,不知道是不是因为组不同…感觉自己面得很麻,麻得像老麻抄手。不过最后总归还是过了,这是好事,希望大家都能拿到心仪的offer吧。