C++面试题

语言基础类

 

0.各种类型和0值比较

bool类型和0值比较

假设有bool类型的flag

if (flag)    // 表示flag为真

if (!flag)    // 表示flag为假

 

整型和0值比较

  假设整型变量value

    if (value == 0)  

      if(value != 0)

浮点型和0值比较

  假设浮点型变量x

  if ((x>=-EPSINON) &&(x<=EPSINON)) 或者  if(abs(x) <= EPSINON)

  其中EPSINON是允许的误差(即精度)。 const float EPSINON = 0.000001,至于为什么取0.000001,可以自己按实际情况定义。

指针类型和0值比较

  假设指针变量p

   if (p ==NULL)    // p与NULL显式比较,强调p是指针变量

   if (p != NULL)

https://www.cnblogs.com/xiaokang01/p/12589794.html

1. 指针和引用的区别?

   (1)指针有自己的一块空间,而引用只是一个别名;

 (2)使用 sizeof 看一个指针的大小为4字节(32位,如果要是64位的话指针为8字节),而引用则是被引用对象的大小。

   (3)  引用必须在定义时被初始化,指针不必;

     (4)不存在指向空值的引用,但存在指向空值的指针。

2.static和 const的用法,(能说出越多越好)(重点)

 首先说说const的用法(绝对不能说是常数)

 把变量修饰为只读

  1. const 常量:定义时就初始化,以后不能更改。
  2. const 形参:func(const int a){};该形参在函数里不能改变
  3. const修饰类成员函数:该函数对成员变量只能进行只读操作

下面的声明都是什么意思?
const int a; a是一个常整型数
int const a; a是一个常整型数
const int *a; a是一个指向常整型数的指针,整型数是不可修改的,但指针可以
int * const a; a为指向整型数的常指针,指针指向的整型数可以修改,但指针是不可修改的
int const * a const; a是一个指向常整型数的常指针,指针指向的整型数是不可修改的,同时指针也是不可修改的
通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。

合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。

再说说static的用法

  1. static局部变量 将一个变量声明为函数的局部变量,那么这个局部变量在函数执行完成之后不会被释放,而是继续保留在内存中
  2. static 全局变量 表示一个变量在当前文件的全局内可访问
  3. static 函数 表示一个函数只能在当前文件中被访问
  4. static 类成员变量 表示这个成员为全类所共有
  5. static 类成员函数 表示这个函数为全类所共有,而且只能访问静态成员变量

 

static关键字的作用

(1)函数体内static变量的作用范围为该函数体,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
(2)在模块内的static全局变量和函数可以被模块内的函数访问,但不能被模块外其它函数访问;
(3)在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
(4)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。

 const关键字的作用

(1)阻止一个变量被改变
(2)声明常量指针和指针常量
(3)const修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
(4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量;
(5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为”左值”。


3.extern c 作用

 告诉编译器该段代码以C语言进行编译。

4.堆和栈的区别

(1)堆栈空间分配区别:

栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈;
堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。
(2)堆栈的缓存方式区别

栈:是内存中存储值类型的,大小为2M(window,linux下默认为8M,可以更改),超出则会报错,内存溢出
堆:内存中,存储的是引用数据类型,引用数据类型无法确定大小,堆实际上是一个在内存中使用到内存中零散空间的链表结构的存储空间,堆的大小由引用类型的大小直接决定,引用类型的大小的变化直接影响到堆的变化
(3)堆栈数据结构上的区别

堆(数据结构):堆可以被看成是一棵树,如:堆排序;
栈(数据结构):一种先进后出的数据结构。

5. 关于静态内存分配和动态内存分配的区别及过程

1) 静态内存分配是在编译时完成的,不占用CPU资源;动态分配内存运行时完成,分配与释放需要占用CPU资源;

2)静态内存分配是在栈上分配的,动态内存是堆上分配的;

3)动态内存分配需要指针或引用数据类型的支持,而静态内存分配不需要;

4)静态内存分配是按计划分配,在编译前确定内存块的大小,动态内存分配运行时按需分配。

5)静态分配内存是把内存的控制权交给了编译器,动态内存把内存的控制权交给了程序员;

6)静态分配内存的运行效率要比动态分配内存的效率要高,因为动态内存分配与释放需要额外的开销;动态内存管理水平严重依赖于程序员的水平,处理不当容易造成内存泄漏。

6. 头文件中的ifndef/define/endif 干什么用?

预处理,防止头文件被重复使用

# define  N 10

知识单纯的文本替换,把所有的N换成10

7. 用struct与class的区别

定义类差别:默认情况下,struct成员的访问级别为public,而class成员的为private。语法使用也相同,直接将class改为struct即可。

继承差别:使用class保留字的派生类默认具有private继承,而用struct保留字定义的类某人具有public继承。

主要点就两个:默认的访问级别和默认的继承级别 class都是private

 

8.派生类与虚函数概述

 

(1) 派生类继承的函数不能定义为虚函数。虚函数是希望派生类重新定义。如果派生类没有重新定义某个虚函数,则在调用的时候会使用基类中定义的版本。

(2)派生类中函数的声明必须与基类中定义的方式完全匹配。

(3) 基类中声明为虚函数,则派生类也为虚函数

9. 虚函数与纯虚函数区别

1)虚函数在子类里面也可以不重载的;但纯虚必须在子类去实现

2)带纯虚函数的类叫虚基类也叫抽象类,这种基类不能直接生成对象,只能被继承,重写虚函数后才能使用,运行时动态动态绑定!

10.深拷贝与浅拷贝

浅拷贝:

char ori[]=“hello”;char*copy=ori;

深拷贝:

char ori[]="hello";  char *copy=new char[];  copy=ori;

浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。

浅拷贝可能出现的问题:

1) 浅拷贝只是拷贝了指针,使得两个指针指向同一个地址,这样在对象块结束,调用函数析构的时,会造成同一份资源析构2次,即delete同一块内存2次,造成程序崩溃。

2) 浅拷贝使得两个指针都指向同一块内存,任何一方的变动都会影响到另一方。

3) 同一个空间,第二次释放失败,导致无法操作该空间,造成内存泄漏。

11.什么是重载,重写

重载 重写重定义
重写发生在两个类之间
重载必须在一个类之间

重写分为两类:
  1虚函数重写 将发生多态
   2非虚函数重写 (重定义)

重载:用同一个函数名定义不同的函数,当函数名和不同的参数搭配时函数的含义不同

函数重载发生在同一个类中。
重载发生的条件:
  • 函数名称必须相同。
  • 参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)。
  • 函数的返回类型可以相同也可以不相同。
  • 仅仅返回类型不同不足以成为函数的重载。
Override(覆盖或重写):是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。

多态发生的条件

必须有继承,虚函数重写, virtual关键字,  实参传入指针或者引用(形参必须是父类指针)..搭建舞台

总结来说:

1要有继承
2要有虚函数重写
3用父类指针(父类引用)指向子类对象....(搭建舞台和调用)
https://www.cnblogs.com/xiaokang01/p/9168933.html

12.什么是多态?多态有什么用途,什么是动态绑定和静态绑定

什么是多态:同种形态的不同表现形式

C++ 多态有两种:静态多态(早绑定)、动态多态(晚绑定)。静态多态是通过函数重载实现的;动态多态是通过虚函数实现的

多态性是一个接口,多种实现,是面向对象的核心。编译时多态性:通过重载函数实现。运行时多态性:通过虚函数实现,结合动态绑定。

多态发生的条件:必须有继承,虚函数重写, virtual关键字,  实参传入指针或者引用(形参必须是父类指针)..搭建舞台

  总结来说:

  1要有继承
  2要有虚函数重写
  3用父类指针(父类引用)指向子类对象....(搭建舞台和调用)

注:多态与非多态的实质区别就是函数地址是静态绑定还是动态绑定。
如果函数的调用在编译器编译期间就可以确定函数的调用地址,并产生代码,说明地址是静态绑定的;
如果函数调用的地址是 需要在运行期间才确定,属于动态绑定。

多态的目的:接口重用。封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。

多态的用法:声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。

用一句话概括:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数


13.C++特点是什么,多态实现机制?多态作用?

C++中多态机制主要体现在两个方面,一个是函数的重载,一个是接口的重写。接口多态指的是“一个接口多种形态”。每一个对象内部都有一个虚表指针,该虚表指针被初始化为本类的虚表。所以在程序中,不管你的对象类型如何转换,但该对象内部的虚表指针是固定的,所以呢,才能实现动态的对象函数调用,这就是C++多态性实现的原理。

多态的基础是继承,需要虚函数的支持,简单的多态是很简单的。子类继承父类大部分的资源,不能继承的有构造函数,析构函数,拷贝构造函数,operator=函数,友元函数等等

作用:

  1. 隐藏实现细节,代码能够模块化;2. 接口重用:为了类在继承和派生的时候正确调用。

必要条件:

1. 一个基类的指针或者引用指向派生类的对象;2.虚函数

14.简述面向对象三大特性

1)封装:将客观事物抽象成类,每个类对自身的数据和方法实行2)继承3)多态:允许一个基类的指针或引用指向一个派生类对象

15.虚函数表

多态是由虚函数实现的,而虚函数主要是通过虚函数表(V-Table)来实现的。

https://www.cnblogs.com/xiaokang01/p/12394420.html

16. C++中哪些不能是虚函数?,为什么

1)普通函数只能重载,不能被重写,因此编译器会在编译时绑定函数。
2)构造函数是知道全部信息才能创建对象,然而虚函数允许只知道部分信息。
3)内联函数在编译时被展开,虚函数在运行时才能动态绑定函数。
4)友元函数 因为不可以被继承。
5)静态成员函数 只有一个实体,不能被继承。父类和子类共有。

为什么构造函数不能是虚函数

简单讲就是没有意义。虚函数的作用在于通过子类的指针或引用来调用父类的那个成员函数。

而构造函数是在创建对象时自己主动调用的,不可能通过子类的指针或者引用去调用。

为什么析构函数必须是虚函数?为什么C++默认的析构函数不是虚函数?

将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。

C++默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。因此C++默认的析构函数不是虚函数,而是只有当需要当作父类时,设置为虚函数。

17.C++类的六个默认成员函数:

  • 构造函数:一个特殊的成员函数,名字与类名相同,创建类类型对象的时候,由编译器自动调用,在对象的生命周期内只且调用一次,以保证每个数据成员都有一个合适的初始值。
  • 拷贝构造函数:只有单个形参,而且该形参是对本类类型对象的引用(常用const修饰),这样的构造函数称为拷贝构造函数。拷贝构造函数是特殊的构造函数,创建对象时使用已存在的同类对象来进行初始化,由编译器自动调用。
  • 析构函数:与构造函数功能相反,在对象被销毁时,由编译器自动调用,完成类的一些资源清理和收尾工作。
  • 赋值运算符重载:对于类类型的对象我们需要对‘=’重载,以完成类类型对象之间的赋值。
  • 取址操作符重载:函数返回值为该类型的指针,无参数。
  • const修饰的取址运算符重载。

18.C++中的inline内联函数与普通函数的区别

内联函数直接运行,不用进行入栈,出栈操作

可以用inline来定义内联函数,不过,任何在类的说明部分定义的函数都会被自动的认为是内联函数。内联函数必须是和函数体声明在一起才有效。

内联函数要做参数类型检查,这是内联函数跟宏相比的优势
宏定义是在预编译的时候把所有的宏名用宏体来替换,简单的说就是字符串替换, 内联函数则是在编译的时候进行代码插入,编译器会在每处调用内联函数的地方直接把内联函数的内容展开,这样可以省去函数的调用的压栈出栈的开销,提高效率。
内联函数是指嵌入代码,就是在调用函数的地方不是跳转,而是把代码直接写到那里去。对于短小简单的代码来说,内联函数可以带来一定的效率提升,而且和C时代的宏函数相比,内联函数 更安全可靠。可是这个是以增加空间消耗为代价的
const与#define的区别:宏在预处理阶段替换,const在编译阶段替换;宏没有类型,不做安全检查,const有类型,在编译阶段进行安全检查

C++4中类型转换

https://www.cnblogs.com/xiaokang01/p/12376683.html

 静态类型转换,static_cast,基本类型之间
例子A,double类型转换成int。B,将子类对象转换成基类对象。
常量类型转换,const_cast, 去除指针变量的常量属性。
无法将非指针的常量转换为普通变量。
动态类型转换,dynamic_cast,运行时进行转换分析的,并非在编译时进行。dynamic_cast转换符只能用于含有虚函数的类。dynamic_cast用于类层次间的向上转换和向下转换,还可以用于类间的交叉转换。在类层次间进行向上转换,即子类转换为父类,此时完成的功能和static_cast是相同的,因为编译器默认向上转换总是安全的。向下转换时,dynamic_cast具有类型检查的功能,更加安全。类间的交叉转换指的是子类的多个父类之间指针或引用的转换。该函数只能在继承类对象的指针之间或引用之间进行类型转换,或者有虚函数的类。

 

 

 19.什么叫智能指针?常用的智能指针有哪些?智能指针的实现?

智能指针是一个存储指向动态分配(堆)对象指针的类,构造函数传入普通指针,析构函数释放指针。栈上分配,函数或程序结束自动释放,防止内存泄露。使用引用计数器,类与指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建,增加引用计数;对一个对象进行赋值时,减少引用计数,并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数,当引用计数减至0,则删除基础对象。

std::auto_ptr,不支持复制(拷贝构造函数)和赋值(operator=),编译不会提示出错。

C++11引入的unique_ptr, 也不支持复制和赋值,但比auto_ptr好,直接赋值会编译出错。

C++11或boost的shared_ptr,基于引用计数的智能指针。可随意赋值,直到内存的引用计数为0的时候这个内存会被释放。还有Weak_ptr

 

20,C++中哪些运算符不可以重载?

不能重载的5个运算符:

(1) .

(2) ?:

(3) sizeof

(4) ::

(5) *

注意个别运算符重载是如何写的

<返回类型说明符> operator <运算符符号>(<参数表>){}
重载为类的成员函数和重载为类的非成员函数。参数个数会不同,应为this指针。

https://www.cnblogs.com/xiaokang01/p/9166745.html

21. 内存对齐的原则?

A.结构体的大小为最大成员的整数倍。
B.成员首地址的偏移量为其类型大小整数倍。

https://www.cnblogs.com/xiaokang01/p/12490758.html

22. 动态分配对象和静态分配对象的区别?

动态分配就是用运算符new来创建一个类的对象,在堆上分配内存。
静态分配就是A a;这样来由编译器来创建一个对象,在栈上分配内存。

 

23.new与malloc的区别,delete和free的区别?

1.malloc/free是C/C++语言的标准库函数,new/delete是C++的运算符
2.new能够自动分配空间大小,malloc传入参数。
3. new/delete能进行对对象进行构造和析构函数的调用进而对内存进行更加详细的工作,而malloc/free不能。
既然new/delete的功能完全覆盖了malloc/free,为什么C++还保留malloc/free呢?因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。

24. 模版怎么实现?模版作用?

实现:template void swap(T& a, T& b){}
作用:将算法与具体对象分离,与类型无关,通用,节省精力

 

25. 多重类构造和析构的顺序

记住析构函数的调用顺序与构造函数是相反的

26.说说堆区和栈区

1).分配和管理方式不同 :

1、栈区(stack)
由编译器自动分配释放 ,存放函数的参数值,局部变量的值等,内存的分配是连续的,类似于平时我们所说的栈,如果还不清楚,那么就把它想成数组,它的内存分配是连续分配的,即,所分配的内存是在一块连续的内存区域内.当我们声明变量时,那么编译器会自动接着当前栈区的结尾来分配内存.

2、堆区(heap)
一般由程序员分配释放, 若程序员不释放,程序结束时可能由操作系统回收.类似于链表,在内存中的分布不是连续的,它们是不同区域的内存块通过指针链接起来的.一旦某一节点从链中断开,我们要人为的把所断开的节点从内存中释放.


2).产生碎片不同
        对堆来说,频繁的new/delete或者malloc/free势必会造成内存空间的不连续,造成大量的碎片,使程序效率降低。
        对栈而言,则不存在碎片问题,因为栈是先进后出的队列,永远不可能有一个内存块从栈中间弹出。
3).生长方向不同
      堆是向着内存地址增加的方向增长的,从内存的低地址向高地址方向增长。
     栈是向着内存地址减小的方向增长,由内存的高地址向低地址方向增长

27.C++内存管理

栈: 存放函数参数以及局部变量,在出作用域时,将自动被释放.栈内存分配运算内置于处理器的指令集中,效率很高,但分配的内存容量有限.

堆:new分配的内存块(包括数组,类实例等),需delete手动释放.如果未释放,在整个程序结束后,OS会帮你回收掉.

自由存储区:malloc分配的内存块,需free手动释放.它和堆有些相似.

全局/静态区:保存自动全局变量和static变量(包括static全局和局部变量)。静态区的内容在整个程序的生命周期内都存在,有编译器在编译的时候分配(数据段(存储全局数据和静态数据)和代码段(可执行的代码/只读常量))。

常量存储区:常量(const)存于此处,此存储区不可修改.

 

 28.sizeof一个类求大小(注意成员变量,函数,虚函数,继承等等对大小的影响)

https://blog.csdn.net/jollyhope/article/details/1895357

https://www.cnblogs.com/BeyondTechnology/archive/2010/09/21/1832369.html

29.拷贝构造函数

https://www.cnblogs.com/xiaokang01/p/9163833.html

 

30100万个32位整数,如何最快找到中位数。能保证每个数是唯一的,如何实现O(N)算法?

1).内存足够时:快排

 

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

 

31常见的c的字符串操作函数实现

C实现常见的字符串处理函数

 

31.预处理,编译,汇编,链接的过程

预处理, 展开头文件/宏替换/去掉注释/条件编译                      (test.i main .i)
编译,    检查语法,生成汇编                                                      ( test.s  main .s)
汇编,   汇编代码转换机器码                                                         (test.o main.o)
链接     链接到一起生成可执行程序                                              a.out

1.预处理,生成预编译文件(.文件):

Gcc –E hello.c –o hello.i
2.编译,生成汇编代码(.s文件):

Gcc –S hello.i –o hello.s
3.汇编,生成目标文件(.o文件):
Gcc –c hello.s –o hello.o
4.链接,生成可执行文件:
Gcc hello.o –o hello

未完待续

网络,数据结构等。

 

volatile可以确保每次使用变量的时候,都从内存中重新读取,而不允许编译器对这个变量的读取操作进行优化

 STL方面的

stl面试总结

posted @ 2020-03-08 07:53  Lucky&  阅读(3133)  评论(2编辑  收藏  举报
//返回顶部开始
//返回顶部结束