第一份实习面经

​ 2018年11月终于不想再过浑浑噩噩的生活,于是开始投简历,找实习。主要是在实习僧和拉勾网上海投简历,最初还想投算法岗,但无奈算法岗要求硕士学历起步,屡投不中,石沉大海,只能转而投后端开发岗和C++开发岗。最终收获了地平线算法平台后端开发岗的offer,算作人生第一次实习吧,早就该写写面经,以做记录,无奈拖延症太厉害,一直拖到现在,强逼自己动笔写下来,一来复盘,二来以备后用。

​ 以下按脑海中回想到的顺序开始写:

百度一面:

基础问题:

1.指针与引用区别,什么情况下用引用更好,引用是否占用内存(汇编语言中与指针一样,回答错误)

这个问题之前已经复习过了,主要是有几点:

(1)当引用被创建时,它必须被初始化。而指针则可以在任何时候被初始化。

(2)一旦一个引用被初始化为指向一个对象,它就不能被改变为对另一个对象的引用。而指针则可以在任何时候指向另一个对象。

(3)不可能有NULL引用。必须确保引用是和一块合法的存储单元关联。

关于什么情况下用引用更好,这个当时没回答上来,但是后来查阅资料应该是类对象,

虽然说程序为指针变量分配内存区域,而引用不需要分配内存区域;但是其实在汇编层面上,指针与引用是一样的,这个当时回答的时候也没答上来,其实想想也应该知道,在底层至少也应该有一块空间来存储该地址的别名。

2.C++多态的实现和原理,运行时多态,编译时多态(没回答上来):

这些都是老生常谈了,C++的三大特性:封装,继承,多态

C++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。存在虚函数的类都有一个一维的虚函数表叫做虚表,类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。多态性是一个接口多种实现,是面向对象的核心,分为类的多态性和函数的多态性。 多态用虚函数来实现,结合动态绑定。 这些都是运行时多态。

编译时多态即:以不同的模板参数具现化导致调用不同的函数,如函数重载和运算符重载来实现。

3.C++重载与重写区别,重载如果返回类型不同是否报错

这个也回答出来了,重载是指同一可访问区内被声明的几个具有不同参数列(参数的类型,个数,顺序不同)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型,所以如果只有返回类型不同是会报错的。

重写(覆盖):是指派生类中存在重新定义的函数。其函数名,参数列表,返回值类型,所有都必须同基类中被重写的函数一致。只有函数体不同(花括号内),派生类调用时会调用派生类的重写函数,不会调用被重写函数。重写的基类中被重写的函数必须有virtual修饰。

4.排序算法选择需要考虑的因素(问题规模,时空复杂度,稳定)

算法题:

链表排序
链表归并排序
链表快排(复杂度)

个人简历问题:

1.C语言编辑的工作
2.逻辑回归因子分析,正确率是否与业界的水平做比较

总体而言,感觉百度的题目难度适中,自己也回答出了大部分问题吗,但是很奇怪再无后续,可能是写代码的过程中表现出略吃力吧。

头条服务器开发一面:

上来先是简历提问,提问的非常细,因为简历上的项目很多只记得大概,所以问到冷汗直流。也直接影响了后续算法题的心态。

简历提问:

1.C语言教材撰写过程中所承担的具体工作:

我回答主要是一二章的编写和算法的设计,后来又让举一个印象深刻的做出突出贡献的例子,但我竟一时无法举出,非常失态。

2.五子棋的棋型统计

主要是提问自己的机器学习五子棋中的棋型统计是怎么实现的。这个五子棋是通过一个三维数组把所有的赢法统计下来,比如win[0][0][0]到win[0][4][0]全置true这即为第0种赢法,同理win[0][1][1]到win[0][5][1]为第1中赢法,把所有的赢法存储下来,然后每落一子,便扫描所有的赢法若该位置为true则该落子颜色赢法+1,便可统计出棋型。

算法题:

1.树的左视图:这个尚可写出,对树进行层序遍历,外层循环条件为队列不为空,记录队列的大小,内层用i<size来循环,若i为1则为该层第一个节点,输出。
2.数组构造树:输入一个N*3的数组来构造二叉树,此题未写出来。

基础知识问答:

乐观锁与悲观锁

这个主要是java中用的比较多,之前有过了解,但是回答的也不深入。

悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

http中头部节点信息(说五个):

搞过web开发的应该都能说出几个。

头条C++岗一面:

基础题:

进程与线程的区别,栈是否是线程独有,不同线程的栈可否互相访问

也是老生常谈,

根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位

在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)

内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。

包含关系:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

算法题:

  • 1.两个链表交叉节点:两个节点同步走,一个走到头从另一个的起点继续走,最终一定在交叉点相遇。这个思路考官反应了半天才想明白,算作比较巧妙的一个思路。
  • 2.单例:单例模式指的是在应用整个生命周期内只能存在一个实例。单例模式是一种被广泛使用的设计模式。他有很多好处,能够避免实例对象的重复创建,减少创建实例的系统开销,节省内存。分饿汉模式和懒汉模式:以下附代码

  • 饿汉模式:

    1
    2
    3
    4
    5
    6
    7
    class SingletonHungary {
    private static SingletonHungary singletonHungary = new SingletonHungary();
    //将构造器设置为private禁止通过new进行实例化
    private SingletonHungary() {}
    public static SingletonHungary getInstance() {
    return singletonHungary;
    }
  • 懒汉模式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 单例模式的懒汉实现2--线程安全
    // 通过设置同步方法,效率太低,整个方法被加锁
    class SingletonLazy2 {
    private static SingletonLazy2 singletonLazy;
    private SingletonLazy2() {

    }
    public static synchronized SingletonLazy2 getInstance() {
    try {
    if (null == singletonLazy) {
    // 模拟在创建对象之前做一些准备工作
    Thread.sleep(1000);
    singletonLazy = new SingletonLazy2();
    }
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    return singletonLazy;
    }}

奈何对此十分生疏,写的磕磕绊绊。
3.九宫格方案数目

这个就是全开放性题目了,我认为是一个全排列问题,而考官认为是一个图的遍历问题。

总体而言,头条的面试偏算法,偏简历提问(可能与考官有关)。

地平线工程院C++岗一面二面(一起的):

地平线整体的面试风格是非常重视基础,提问了大量的基础知识,但是并不通过牛客网在线写代码,代码题只让提供一下思路就可以。

以下是一二面问到的问题:

1.C/C++联系区别
2.C++中构造函数可否调用虚函数(没回答上来,答案:调用了也只能是为构造函数或析构函数自身类型定义的版本,无法实现多态,析构函数也是)
3.C++析构函数为什么要为虚函数

基类指针可以指向派生类的对象(多态性),如果删除该指针delete []p;就会调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象完全被释放。如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全。所以,将析构函数声明为虚函数是十分必要的。

4.C++编译过程(.c.cpp->预处理->编译优化->生成汇编代码.s->汇编器->目标代码.o(机器代码)->链接器->库文件、其他目标代码、目标代码连接成可执行程序)
5.include包含头文件的语句中,双引号和尖括号的区别

#include <>格式:引用标准库头文件,编译器从标准库目录开始搜索

#incluce “”格式:引用非标准库的头文件,编译器从用户的工作目录开始搜索
6.C++的五个存储区(代码区、全局数据区、堆区、栈区、自由存储区、常量存储区)

堆内存与栈内存的区别:
栈:是一种连续储存的数据结构,具有先进后出的性质。通常的操作有入栈(圧栈)、出栈和栈顶元素。想要读取栈中的某个元素,就要将其之前的所有元素出栈才能完成。类比现实中的箱子一样。
堆:是一种非连续的树形储存数据结构,每个节点有一个值,整棵树是经过排序的。特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。常用来实现优先队列,存取随意。

7.C++迭代器(没回答上来)
8.stl有哪些
9.vector扩容(二倍复制,释放原来的内存)
10.用没用过cmake(答:没用过)
11.linux常用命令
12.C++指针与引用,Java的引用类似于C++的指针还是引用(没回答上来,正确答案:指针)
13.C++多态
14.多态的实例(举了计算工资的例子)

面完之后感觉良好,应该会过,果不其然,第二天打电话约三面并承诺只是和经理聊一聊大概率会过,但因为已经拿到了算法平台的offer所以婉拒。

商汤C++开发岗(一面,二面):

一面:

基础题:

1.进程线程区别,什么线程独有,线程切换上下文所需信息,一个线程读一个线程写可以吗?
2.C++虚函数原理(虚表)
3.继承与多态
4.new与malloc区别,delete与free区别(只回答了返回类型,申请大小还有更深层的)

这个之前看过源码。其实new的底层多用malloc来实现,特地记了下来

new 返回指定类型的指针,并且可以自动计算所需要大小。先计算申请内存(可调用malloc),然后调用构造函数。delete先调用析构函数再free。

malloc 则必须要由我们计算字节数,并且在返回后强行转换为实际类型的指针。new是C++关键字,malloc是库函数。

  • 0.属性

new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持。

  • 1.参数

使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。

  • 2.返回类型

new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void ,需要通过强制类型转换将void指针转换成我们需要的类型。

  • 3.分配失败

new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。

  • 4.自定义类型new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。

  • 5.重载

C++允许重载new/delete操作符,特别的,布局new的就不需要为对象分配内存,而是指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化工作,并返回此地址。而malloc不允许重载。

  • 6.内存区域

new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中。

5.static的作用

以下为所有应用场景:

  • 1.全局变量,变量被称为全局静态变量,存储在静态区目的:限定作用域为当前文件,其他文件不可访问

  • 2.该变量修饰局部变量,即局部静态变量,存储在静态区

    目的:函数结束时不销毁,使得下次调用时不需要再次开辟空间,同时保留原内容。虽然生命周期为整个进程,但仍不能被其他函数、变量访问。局部静态变量不可重入,多线程时要注意线程安全。

  • 3.修饰函数

    使的其他源文件不可访问该函数,达到类似C++ private的效果

  • 4.修饰类成员:表示类的共享数据,与对象无关,所有对象共享,全部类对象是共享一个static类成员的,可以修改,注意:static类对象必须要在类外进行初始化

  • 5.修饰类成员函数:

    由于static修饰的类成员属于类,不属于对象,因此static类成员函数是没有this指针的,this指针是指向本对象的指针。正因为没有this指针,所以static类成员函数

    不能访问非static的类成员,只能访问 static修饰的类成员。

    修饰类内变量起什么作用?(没回答上来):static类数据成员独立于一切类对象存在。即static不会像普通类数据成员一样每一个类对象都有一份,全部类对象是共享一个static类成员的。

    可用 类名::静态成员 或者 对象.静态成员 来访问。

6.生产者和消费者

简要的介绍了一下操作系统中所学。

7.windows中的PV原语实现

这个不熟悉,直接承认没了解过。

算法题:

1.寻找集合交集
2.二分查找,(若找不到找下确界)
2.模板的使用

前两题顺利写出,第三题写的磕磕绊绊,不太顺畅,主要还是C++基础太薄弱。

二面:

二面上来就开始问算法

算法题:

1.while循环调用图像处理算法,为何前几轮轮比较慢(回答了汇编循环展开,多线程优化,其实到现在也不确定自己扯得对不对)

基础知识:

1.单例模式的实现:头条考过,不做赘述。
2.各种熟悉的模式简介及应用(回答了MVC模式和工厂模式)
3.线程池介绍,好处
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。
4.静态全局变量初始化,局部静态变量初始化(系统默认为0)其余非静态初始化(若没初始化会报错)
分别都保存在哪儿?(静态变量保存在全局静态区)

​ 整体上感觉发挥良好,后来面试官问有啥需要问的便谈了谈人才需求,面试主要考察哪些方面的素质?
​ 答:1.代码能力
​ 2.基础知识
​ 3.思维灵活反应快
​ 4.热爱
​ 5.沟通能力
另外介绍了商汤现在分为:研究院与工程院,面试我的部门为工程院。感觉应该会过,但是等了三天都没有消息,后来通过了地平线算法平台的面试,第二个周二接到了二面通过的消息并进行三面邀约,但此时已经决定了去地平线算法平台,便婉拒了。

地平线数据平台后端开发岗:

地平线就是一面到底,一面通过后半个小时内马上安排下一面,这点我非常喜欢,不用等,节省双方时间。

一面:

基础题:

1.hashmap扩容时往里加数据
2.丢失修改,不可重复读,读脏数据。三级锁协议
3.Mysql不可重复读的问题
4.nginx与redis
5.http运行机理
6.cookie与session(cookie存在客户端,session存在服务器端,cookie中可能携带session id)
7.TCP三次握手与四次握手

为什么关闭要四次?因为关闭发起方并不能确定对方无数据传给自己了

为什么要等待一个两个TTL后关闭?因为可能会出现数据包丢失的情况,对方若接收不到ACK会重发数据包。

8.单例模式。应用,多个打印任务用单例模式解决(没回答上来)

算法题:

1.二叉树层序遍历
2.从下往上层序遍历
3.数据中有多个重复数据找出来(回答了排序与哈希表。其实还有一种非常简单的:异或

二面:主要是问项目

1.数据库索引(怎么建?什么规则建?)
2.逻辑回归的理解
3.C语言教材是怎么编的
4.redis用到的数据(只用到了token)
5.求2018年第10个星期日是几号

三面:主要是问项目

1.讲了讲部门里是干什么的(开发分布式数据库PB级别)
2.聚类和最小生成树 介绍
3.涛哥讲述了面试选人的标准:学习能力怎么样,学习热情是否饱满、
4.五子棋为什么叫类BP原理

​ 当天晚上就拿到了offer,并且从HR小姐姐处得到了一个面试官的评价:基础扎实,但是项目经验不足,无法灵活运用,不过作为学生可以理解。感觉评价也算中肯,算法平台非常年轻化,觉得也有机会接触到搞算法的同学,便决定入职算法平台,不再等待商汤的三面。

​ 第一次找实习就告一段落。从11月中旬返校准备找实习,到12月1号拿到offer,大概用了两个周的时间,当时觉得这么快就找到了实习,未免有点儿飘飘然,忘记了最初屡屡受挫,灰心丧气的时候。反过来复盘这几次面试,之所以能拿到offer主要还是靠着海投大量刷经验,各个公司考察的基础知识相差无几,所以才能迅速抓住重点,有针对性的复习好基础,很多问题从底层开始理解才比较深刻,再加上碰到了地平线这种不用当场撸代码的公司,侥幸通过。但是希望自己记住,不是每次都有这种幸运,coding能力的弱的问题依然是大患,需要正视自己的不足,用一句鸡汤语录——戒骄戒躁,砥砺前行。

如果喜欢这篇文章,不如请我喝瓶阔落:)