C++常见的面试题目(持续更新)

一、C++和C语言的区别

  1. C++是面向对象的语言,C语言是面向函数的语言。
  2. C语言不支持函数重载。
  3. C语言中常见的struct和C++常见的class除了访问默认权限不同,别的功能几乎相同。

二、关键字static、const、extern作用

const和static的作用主要从类外和类内两方面去讲:

static关键字
类外:

  1. static变量的作用范围为函数体,该变量的内存只能被分配一次,下次调用仍然维持上次的值。
  2. 模块内的static变量能在模块内被访问,但是不能在模块外被访问。

类内:

  1. static成员变量为整个类所拥有,对类的所有对象都只有一份拷贝。
  2. 在类中的static成员函数为整个类所拥有的,在类不用实例化便可以访问该函数,但是该函数只能访问类的静态成员变量。

const关键字:

  • 阻止一个变量被改变。
  • 声明常量指针和指针常量。
  • const修饰形参,表明这是一个不能被改变的参数。
  • 对于类里面的函数,说明它是一个常函数,不能改变类的成员变量。
  • 对于类里面的函数,返回值设为const,说明改函数返回值为const类型,不为“左值”;

extern关键词:

  • extern可以放在变量或者函数前面,标识该变量还有函数的定义可以在其他文件中找到。
  • extern “C” 的作用是让C++编译器将extern "C"的代码作为C语言处理。

三、说一下多态吧。

  • 多态是指在不同的条件下表现出不同的状态。 多态包括静态多态还有动态多态
  • 静态多态指的是函数重载,程序在编译的时候就实现了多态。
  • 动态多态指的是通过父类来定义虚函数来实现的,父类定义虚函数,子类继承父类并且重写父类的虚函数,或者时父类指针指向子类来实现多态。

一般构造函数、复制构造函数、运算符重载都不定位为虚函数。
但是如果一个类是要用来继承的话,析构函数最好定义为虚函数。这样使用基类指针指向派生类对象时,进行析构时才会析构子类的对象。
C++默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存,会浪费内存。
因此C++默认的析构函数不是虚函数,而是只有当需要当作父类时,设置为虚函数。
基类指针 指向 派生类对象:若基类析构函数不为虚函数,在delete基类指针时,只会调用基类的析构函数,不会调用派生类的析构函数(资源未释放,基类无法操作派生类中非继承的成员),进而可能导致内存泄漏。这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。

四、请你说一说你理解的虚函数和多态

  • 多态的实现主要分为静态多态和动态多态,静态多态主要是重载,在编译的时候就已经确定;动态多态是用虚函数机制实现的,在运行期间动态绑定。举个例子:一个父类类型的指针指向一个子类对象时候,使用父类的指针去调用子类中重写了的父类中的虚函数的时候,会调用子类重写过后的函数,在父类中声明为加了virtual关键字的函数,在子类中重写时候不需要加virtual也是虚函数。
    虚函数的实现:在有虚函数的类中,类的最开始部分是一个虚函数表的指针,这个指针指向一个虚函数表,表中放了虚函数的地址,实际的虚函数在代码段(.text)中。当子类继承了父类的时候也会继承其虚函数表,当子类重写父类中虚函数时候,会将其继承到的虚函数表中的地址替换为重新写的函数地址。使用了虚函数,会增加访问内存开销,降低效率。

五、为什么有了虚析构函数,就能先调用子类的析构函数?

  • 虚析构函数的地址存在于虚函数表中,和普通虚函数别无二致,同时也会像普通的虚函数一样进行覆盖
    虽然父子的析构函数名字不一样,但是他们占同一个坑(即父子析构函数在虚函数表中的位置是一样的,否则就不存在多态了)
    析构时,到特定的坑中调用该类型的析构函数,其析构函数中又嵌套了很多对父类的析构函数的调用

构造函数可以设为虚函数吗?
不可以,构造函数就承担着虚函数表的建立,如果它本身就是虚函数的话,就无法保证虚函数表的成功构建。

虚函数和纯虚函数
虚函数是用来实现动态多态的,可以被继承和重写。配合虚函数表和虚函数表指针使用。
而定义了纯虚函数的类为抽象类,抽象类不可以被实例化。

六、函数重载

  • 重载函数是函数的一种特殊情况,为方便使用,C++允许在同一范围中声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同,也就是说用同一个函数完成不同的功能。

  • 重载运算符的方式有哪几种?

    (1)令运算符重载函数作为类的成员函数

class complex{
   int x;
   complex(int num)
   {
       this->x = num;
   }
   friend complex opreator+(complex a,complex b)
   {
       return complex(a.x+b.x);
   }
   /*
   complex opreator(complex b)
   {
       return complex(this->x+b.x);
   }
   */
};

(2)令运算符重载函数作为类的友元函数
几乎所有的运算符都可用作重载。具体包含:
算术运算符:+,-,,/,%,++,–;
位操作运算符:&,|,~,^,<<,>>
逻辑运算符:!,&&,||;
比较运算符:<,>,>=,<=,==,!=;
赋值运算符:=,+=,-=,
=,/=,%=,&=,|=,^=,<<=,>>=;
其他运算符:[],(),->,(逗号运算符),new,delete,new[],delete[],->*。
下列运算符不允许重载:. ____ * _______ :: ______ ?:_______ siezof

七、拷贝构造函数为什么要是引用?
如果不是引用的话将会造成无限的循环。

class complex{
   int x;
   complex(complex& c)
   {
       this.x = c.x;
   }
};

如果拷贝构造函数的变量不是引用的话,就会造成调用改函数时,不断地进入到死循环。

八、一个空类有哪些默认的函数?
编译器会自动为你生成一个默认构造函数、一个拷贝默认构造函数、一个默认拷贝赋值操作符和一个默认析构函数、还有一个取址函数。这些函数只有在第一次被调用时,才会别编译器创建。所有这些函数都是inline和public的。

九、new 和 malloc 的区别
new和malloc的区别是C/C++一道经典的面试题,我也遇到过几次,回答的都不是很好,今天特意整理了一下。

  • 属性不同, new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持。
  • 输入参数不同,使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。
  • 返回类型不同,new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
  • 分配失败时处理不同,new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。
  • 自定义类型,new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。
  • 重载,C++允许重载new/delete操作符,特别的,布局new的就不需要为对象分配内存,而是指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化工作,并返回此地址。而malloc不允许重载。
  • 内存区域,new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中。

十、malloc 底层怎么实现的?

malloc基本的实现原理就是维护一个内存空闲链表,当申请内存空间时,搜索内存空闲链表,找到适配的空闲内存空间,然后将空间分割成两个内存块,一个变成分配块,一个变成新的空闲块。如果没有搜索到,那么就会用sbrk()才推进brk指针来申请内存空间。
搜索空闲块最常见的算法有:首次适配,下一次适配,最佳适配。
首次适配:第一次找到足够大的内存块就分配,这种方法会产生很多的内存碎片。
下一次适配:也就是说等第二次找到足够大的内存块就分配,这样会产生比较少的内存碎片。
最佳适配:对堆进行彻底的搜索,从头开始,遍历所有块,使用数据区大小大于size且差值最小的块作为此次分配的块。

合并空闲块
在释放内存块后,如果不进行合并,那么相邻的空闲内存块还是相当于两个内存块,会形成一种假碎片。所以当释放内存后,我们需要将两个相邻的内存块进行合并。

显式空闲链表
还有一种实现方式则是采用显示空闲链表,这个是真正的链表形式。在之前的有效载荷中加入了之前前驱和后驱的指针,也可以称为双向链表。维护空闲链表的的方式第一种是用后进先出(LIFO),将新释放的块放置在链表的开始处。另一种方法是按照地址的顺序来维护。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值