BAT经典面试题汇总.docx

上传人:b****8 文档编号:9470456 上传时间:2023-02-04 格式:DOCX 页数:30 大小:112.51KB
下载 相关 举报
BAT经典面试题汇总.docx_第1页
第1页 / 共30页
BAT经典面试题汇总.docx_第2页
第2页 / 共30页
BAT经典面试题汇总.docx_第3页
第3页 / 共30页
BAT经典面试题汇总.docx_第4页
第4页 / 共30页
BAT经典面试题汇总.docx_第5页
第5页 / 共30页
点击查看更多>>
下载资源
资源描述

BAT经典面试题汇总.docx

《BAT经典面试题汇总.docx》由会员分享,可在线阅读,更多相关《BAT经典面试题汇总.docx(30页珍藏版)》请在冰豆网上搜索。

BAT经典面试题汇总.docx

BAT经典面试题汇总

STL中vector的实现原理(衍生:

Map,Set等实现原理)

参考答案

vector的数据安排以及操作方式,与array非常相似。

两者的唯一区别在于空间的运用的灵活性。

array是静态空间,一旦配置了就不能改变;要换个大(或小)一点的房子,可以,一切琐细都得由客户端自己来:

首先配置一块新空间,然后将元素从旧址一一搬往新址,再把原来的空间释还给系统。

vector是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新元素。

因此,vector的运用对于内存的合理利用与运用的灵活性有很大的帮助,我们再也不必因为害怕空间不足而一开始要求一个大块头的array了,我们可以安心使用array,吃多少用多少。

 

     vector的实现技术,关键在于其对大小的控制以及重新配置时的数据移动效率。

一旦vector的旧有空间满载,如果客户端每新增一个元素,vector的内部只是扩充一个元素的空间,实为不智。

因为所谓扩充空间(不论多大),一如稍早所说,是”  配置新空间/数据移动/释还旧空间  “的大工程,时间成本很高,应该加入某种未雨绸缪的考虑。

稍后我们便可看到SGIvector的空间配置策略了。

 

     另外,由于  vector维护的是一个连续线性空间,所以vector支持随机存取  。

 

     注意:

vector动态增加大小时,并不是在原空间之后持续新空间(因为无法保证原空间之后尚有可供配置的空间),而是以原大小的两倍另外配置一块较大的空间,然后将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间。

因此,  对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了  。

这是程序员易犯的一个错误,务需小心。

给定N张扑克牌和一个随机函数,设计一个洗牌算法

参考答案

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

voidshuffle(intcards[],intn)

{

    if(cards==NULL)

        return;

 

    srand(time(0));

 

    for(inti=0;i

    {

        //保证每次第i位的值不会涉及到第i位以前

        intindex=i+rand()%(n-i);

        inttemp=cards[i];

        cards[i]=cards[index];

        cards[index]=temp;

    }

}

25匹马,5个跑道,每个跑道最多能有5匹马进行比赛,最少比多少次能比出前3名?

前5名?

参考答案

找出前3名最少需要7场就可以确定。

首先我们将25匹⻢马分成5组,分别为ABCDE,5组分别进行比赛决出各小组名次;接着让各小组第一进行比赛决出冠军,我们假设各小组第一分别是A1,B1,C1,D1,E1,并且速度A1>B1>C1>D1>E1;接着2,3名可以在一场比赛内决出,分别由A2,A3,B1,B2,C1参赛;这样总共进行了5+1+1=7场;找出前5名的思路和这个类似。

100亿个整数,内存足够,如何找到中位数?

内存不足,如何找到中位数?

参考答案

内存足够的情况:

可以使⽤用类似quicksort的思想进行,均摊复杂度为O(n),算法思想如下:

 

•随机选取一个元素,将比它小的元素放在它左边,比它大的元素放在右边 

•如果它恰好在中位数的位置,那么它就是中位数,可以直接返回 

•如果小于它的数超过一半,那么中位数一定在左半边,递归到左边处理 

•否则,中位数一定在右半边,根据左半边的元素个数计算出中位数是右半边的第几大,然后递归到右半边处理 

内存不⾜足的情况:

 

方法⼀:

⼆分法 

思路:

一个重要的线索是,这些数都是整数。

整数就有范围了,32位系统中就是[-2^32,2^32-1],有了范围我们就可以对这个范围进行二分,然后找有多少个数⼩于Mid,多少数大于mid,然后递归,和基于quicksort思想的第k大⽅方法类似 

方法二:

分桶法思路:

化大为小,把所有数划分到各个小区间,把每个数映射到对应的区间⾥里,对每个区间中数的个数进行计数,数一遍各个区间,看看中位数落在哪个区间,若够小,使⽤用基于内存的算法,否则继续划分

请简述智能指针原理,并实现一个简单的智能指针。

参考答案

智能指针是一种资源管理类,通过对原始指针进行封装,在资源管理对象进行析构时对指针指向的内存进行释放;通常使用引用计数方式进行管理,一个基本实现如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

classObject;

classSmartPointer;

 

classCounter

{

 friend classSmartPointer;

public:

 Counter()

 {

  ptr=NULL;

  cnt= 0;

 }

 Counter(Object*p)

 {

  ptr=p;

  cnt= 1;

 }

 ~Counter()

 {

  deleteptr;

 }

private:

 Object*ptr;

 intcnt;

};

 

classSmartPointer

{

public:

 SmartPointer(Object*p)

 {

  ptr_counter= newCounter(p);

 }

 SmartPointer(constSmartPointer&sp)

 {

  ptr_counter=sp.ptr_counter;

  ++ptr_count->cnt;

 }

 SmartPointer&operator=(constSmartPointer&sp)

 {

  ++sp.ptr_counter->cnt;

  --ptr_counter->cnt;

  if(ptr_counter->cnt== 0)

  {

   deleteptr_counter;

  }

  ptr_counter=sp.ptr_counter;

 }

 ~SmartPointer()

 {

  --ptr_counter->cnt;

  if(ptr_counter->cnt== 0)

  {

   deleteptr_counter;

  }

 }

private:

 Counter*ptr_counter;

 

};

如何处理循环引用问题?

参考答案

刚刚研究过这个问题。

何为循环引用

如果有两个或者以上的对象,它们彼此引用,就会造成循环引用。

如下面的例子

class Node { 

    Node next ;

Node a = new Node (); 

Node b = new Node (); 

a . next = b ; 

b . next = a ; 

 

代码中,a对象引用了b对象,b对象也引用了a对象,这种情况下a对象和b对象就形成了循环引用。

引用计数GC处理

什么是引用计数

引用计数是一种垃圾回收的形式,每一个对象都会有一个计数来记录有多少指向它的引用。

其引用计数会变换如下面的场景

∙当对象增加一个引用,比如赋值给变量,属性或者传入一个方法,引用计数执行加1运算。

∙当对象减少一个引用,比如变量离开作用域,属性被赋值为另一个对象引用,属性所在的对象被回收或者之前传入参数的方法返回,引用计数执行减1操作。

∙当引用计数变为0,代表该对象不被引用,可以标记成垃圾进行回收。

如何处理

实际上单纯的基于引用计数实现的计数器无法处理循环引用带来的问题。

CPython的垃圾回收就是采用引用计数,采用引用计数的主垃圾回收器会清理垃圾,对于那些因为循环引用无法清理的对象,CPython会不时启动一个辅助的基于引用遍历的垃圾回收器来清理它们。

引用遍历GC处理

什么是引用对象遍历

垃圾回收器从被称为GCRoots的点开始遍历遍历对象,凡是可以达到的点都会标记为存活,堆中不可到达的对象都会标记成垃圾,然后被清理掉。

GCRoots有哪些

∙类,由系统类加载器加载的类。

这些类从不会被卸载,它们可以通过静态属性的方式持有对象的引用。

注意,一般情况下由自定义的类加载器加载的类不能成为GCRoots

∙线程,存活的线程

∙Java方法栈中的局部变量或者参数

∙JNI方法栈中的局部变量或者参数

∙JNI全局引用

∙用做同步监控的对象

∙被JVM持有的对象,这些对象由于特殊的目的不被GC回收。

这些对象可能是系统的类加载器,一些重要的异常处理类,一些为处理异常预留的对象,以及一些正在执行类加载的自定义的类加载器。

但是具体有哪些前面提到的对象依赖于具体的JVM实现。

如何处理

基于引用对象遍历的垃圾回收器可以处理循环引用,只要是涉及到的对象不能从GCRoots强引用可到达,垃圾回收器都会进行清理来释放内存。

总结

基于引用计数的垃圾回收器无法处理循环引用导致的内存泄露问题,但是其在主流的JVM中很少,几乎所有的JVM都是采用引用对象遍历的方法,垃圾回收器都会处理循环引用潜在的问题。

请实现一个单例模式的类,要求线程安全

参考答案

在C++之后,下面的实现是个线程安全的单例模式实现:

1

2

3

4

5

6

7

8

9

10

11

12

13

classSingleton

{

private:

 Singleton();

 Singleton(constSingleton&s);

 Singleton&operator=(constSingleton&s);

public:

 staticSingleton*GetInstance()

 {

  staticSingletoninstance;

  return&instance;

 }

};

下面的结构体大小分别是多大(假设32位机器)?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

structA{

chara;

charb;

charc;

};

 

structB{

inta;

charb;

shortc;

};

 

structC{

charb;

inta;

shortc;

};

 

#pragmapack

(2)

structD{

charb;

inta;

shortc;

};

参考答案

3,8,12,8

引用和指针有什么区别?

参考答案

本质:

引用是别名,指针是地址,具体的:

 

•指针可以在运行时改变其所指向的值,引用一旦和某个对象绑定就不再改变 

•从内存上看,指针会分配内存区域,而引用不会,它仅仅是一个别名 

•在参数传递时,引⽤用会做类型检查,而指针不会 

•引用不能为空,指针可以为空

const和define有什么区别?

参考答案

本质:

define只是字符串替换,const参与编译运行,具体的:

 

•define不会做类型检查,const拥有类型,会执行相应的类型检查 

•define仅仅是宏替换,不占⽤用内存,⽽而const会占用内存 

•const内存效率更高,编译器通常将const变量保存在符号表中,而不会分配存储空间,这使得它成为一个编译期间的常量,没有存储和读取的操作

define和inline有什么区别?

参考答案

本质:

define只是字符串替换,inline由编译器控制,具体的:

 

•define只是简单的宏替换,通常会产生二义性;而inline会真正地编译到代码中 

•inline函数是否展开由编译器决定,有时候当函数太大时,编译器可能选择不展开相应的函数

malloc和new有什么区别?

参考答案

1,malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。

它们都可用于申请动态内存和释放内存。

2,对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。

对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。

由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。

3,因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以一个能完成清理与释放内存工作的运算符delete。

注意new/delete不是库函数。

4,C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。

5、new可以认为是malloc加构造函数的执行。

new出来的指针是直接带类型信息的。

而malloc返回的都是void指针。

C++中static关键字作用有哪些?

参考答案

1、隐藏:

当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。

static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏.

2、static的第二个作用是保持变量内容的持久:

存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。

共有两种变量存储在静态存储区:

全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,

说到底static还是用来隐藏的。

虽然这种用法不常见

3、static的第三个作用是默认初始化为0(static变量)

4、C++中的作用

1)不能将静态成员函数定义为虚函数。

  

2)静态数据成员是静态存储的,所以必须对它进行初始化。

(程序员手动初始化,否则编译时一般不会报错,但是在Link时会报错误) 

3)静态数据成员在<定义或说明>时前面加关键字static。

  

C++中const关键字作用有哪些?

?

参考答案

•修饰变量 

•修饰成员函数,表示该成员函数不会修改成员变量

C++中成员函数能够同时用static和const进行修饰?

参考答案

否,因为static表⽰示该函数为静态成员函数,为类所有;而const是用于修饰成员函数的,两者相矛盾

下面三个变量分别代表什么含义?

 

constint*ptr; 

intconst*ptr; 

int*constptr;

参考答案

前两个代表指向const变量的指针,即指针所指向的对象是const的,不能使用指针修改;最后一个代表const指针,即指针本身是const的,不能指向其他地址

C++中包含哪几种强制类型转换?

他们有什么区别和联系?

参考答案

•reinterpret_cast:

转换一个指针为其它类型的指针。

它也允许从一个指针转换为整数类型,反之亦然.这个操作符能够在非相关的类型之间转换.操作结果只是简单的从一个指针到别的指针的值的二进制拷贝.在类型之间指向的内容不做任何类型的检查和转换?

 

classA{}; 

classB{}; 

A*a=newA;

B*b=reinterpret_cast(a); 

•static_cast:

允许执行任意的隐式转换和相反转换动作(即使它是不允许隐式的),例如:

应用到类的指针上,意思是说它允许子类类型的指针转换为父类类型的指针(这是一个有效的隐式转换),同时,也能够执行相反动作:

转换父类为它的子类 

classBase{}; 

classDerive:

publicBase{}; 

Base*a=newBase; 

Derive*b=static_cast(a); 

•dynamic_cast:

只用于对象的指针和引用.当用于多态类型时,它允许任意的隐式类型转换以及相反过程.不过,与static_cast不同,在后一种情况里(注:

即隐式转换的相反过程),dynamic_cast会检查操作是否有效.也就是说,它会检查转换是否会返回一个被请求的有效的完整对象。

检测在运行时进行.如果被转换的指针不是一个被请求的有效完整的对象指针,返回值为NULL.对于引用类型,会抛出bad_cast异常 

•const_cast:

这个转换类型操纵传递对象的const属性,或者是设置或者是移除,例如:

 

classC{}; 

constC*a=newC; 

C*b=const_cast(a);

下面两段代码的输出分别是什么?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

classBase{

    public:

      virtual voidPrint() const{

        cout<< "PrintinBase"<

      }

};

classDerive:

:

publicbase

{

    public:

      voidPrint() const{

        cout<< "PrintinDerive"<

      }

};

voidPrint(constBase*base){

    base->Print();

}

intmain(){

    Baseb;

    Derived;

    print(&b);

    print(&d);

    return0;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

classBase{

    public:

      voidPrint() const{

        cout<< "PrintinBase"<

      }

};

classDerive:

:

publicbase

{

    public:

      voidPrint() const{

        cout<< "PrintinDerive"<

      }

};

voidPrint(constBase*base){

    base->Print();

}

intmain(){

    Baseb;

    Derived;

    print(&b);

    print(&d);

    return0;

}

参考答案

考察对虚函数的基本理解 

第一个:

PrintinBase,PrintinDerive 

第二哥:

PrintinBase,PrintinBase

简述C++虚函数作用及底层实现原理

参考答案

要点是要答出虚函数表和虚函数表指针的作用。

C++中虚函数使用虚函数表和虚函数表指针实现,虚函数表是一个类的虚函数的地址表,用于索引类本身以及父类的虚函数的地址,假如子类的虚函数重写了父类的虚函数,则对应在虚函数表中会把对应的虚函数替换为子类的虚函数的地址;虚函数表指针存在于每个对象中(通常出于效率考虑,会放在对象的开始地址处),它指向对象所在类的虚函数表的地址;在多继承环境下,会存在多个虚函数表指针,分别指向对应不同基类的虚函数表。

一个对象访问普通成员函数和虚函数哪个更快?

参考答案

访问普通成员函数更快,因为普通成员函数的地址在编译阶段就已确定,因此在访问时直接调用对应地址的函数,而虚函数在调用时,需要首先在虚函数表中寻找虚函数所在地址,因此相比普通成员函数速度要慢一些

在什么情况下,析构函数需要是虚函数?

参考答案

若存在类继承关系并且析构函数中需要析构某些资源时,析构函数需要是虚函数,否则当使用父类指针指向子类对象,在delete时只会调用父类的析构函数,而不能调用子类的析构函数,造成内存泄露等问题

内联函数、构造函数、静态成员函数可以是虚函数吗?

参考答案

都不可以。

内联函数需要在编译阶段展开,而虚函数是运行时动态绑定的,编译时无法展开;构造函数在进行调用时还不存在父类和子类的概念,父类只会调用父类的构造函数,子类调用子类的,因此不存在动态绑定的概念;静态成员函数是以类为单位的函数,与具体对象无关,虚函数是与对象动态绑定的,因此是两个不冲突的概念;

构造函数中可以调用虚函数吗?

参考答案

可以,但是没有动态绑定的效果,父类构造函数中调用的仍然是父类版本的函数,子类中调用的仍然是子类版本的函数

简述C++中虚继承的作用及底层实现原理?

参考答案

虚继承用于解决多继承条件下的菱形继承问题,底层实现原理与编译器相关,一般通过虚基类指针实现,即各对象中只保存一份父类的对象,多继承时通过虚基类指针引用该公共对象,从而避免菱形继承中的二义性问题。

1000个灯围成一个环,初始状态是熄灭的,按一个灯,它以及它的左右两盏灯的状态会改变,问如何让所有灯都亮?

参考答案

挨个按一遍。

思路是每个灯只会被3个位置改变状态,挨个按一遍恰好每个位置被改变了奇数次状态

n条直线最多能将一个平面分成多少部分?

参考答案

num[n]=num[n-1]+n思路:

第n条直线总能和前⾯面n-1条直线形成n-1个交点,将第n条直线分成n份,每一份会多分出一个平⾯面;num[n]=1+n*(n+1)/2;

n个平面最多能将一个空间切成多少部分?

参考答案

显然,当这n个平面满足以下条件时,所分割的部分数是最多的。

1、这n个平面两两相交;2、没有三个以上的平面交于一点;3、这n个平面的交线任两条都不平行。

对于一般情况一下子不易考虑,我们不妨试着从简单的,特殊的情况入手来寻找规律。

设n个平面分空间的部分数为an,易知当n=1时,an=2;当n=2时,an=4当n=3时,an=8当n=4时,情况有些复杂,我们以一个四面体为模型来观察,可知an=15;从以上几种情况,很难找出一个一般性的规律,而且当n的值继续增大时,情况更复杂,看来这样不行。

那么,我们把问题在进一步简单化,将空间问题退化到平面问题:

n条直线最多可将平面分割成多少个部分?

(这n条直线中,任两条不平行,任三条不交于同一点),设n条直线

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 医学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1