C++经典面试题

持续收集中...

 

1 基础(9)

1)C++中有malloc/free,为什么还需要new/delete?

new/delete会调用构造/析构函数,适合于类类型对象的创建与销毁。

2)C++中explicit关键字的作用?

关闭函数的类型自动转换(防止隐式转换)。

3)C++中static关键字的作用?

控制变量的存储方式和可见性,可修饰局部变量、全局变量、函数、类、类成员/方法。
注1:static 对局部变量进⾏修饰之后,其⽣命周期以及存储空间发⽣了变化,但其作⽤域并没有改变,作用域还是限制在其语句块。
注2:静态⾮常量数据成员,其只能在类外定义和初始化,在类内仅是声明⽽已。(原因:static类对象必须要在类外进⾏初始化,static修饰的变量先于对象存在,所以static修饰的变量要在类外初始化;)
注3:在类中的static成员函数属于整个类所拥有,这个函数不接收 this 指针,因⽽只能访问类的static成员变量。
4)C++中const修饰函数返回值时的作用是什么?
如果给以“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const修饰的同类型指针。
如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const修饰没有任何实际作用。
5)说⼀下C++里是怎么定义常量的?常量存放在内存的哪个位置?
对于局部常量,存放在栈区;
对于全局常量,编译期⼀般不分配内存,放在符号表中以提⾼访问效率;
字⾯值常量,⽐如字符串,放在常量区。
6)说⼀下++ii++的区别。
++i(前置加加)先自增1再返回,i++(后置加加)先返回i再⾃增1
前置加加不会产生临时对象,后置加加必须产生临时对象,临时对象会导致效率降低。

7)++ii++的实现?
++i实现:
1 int& int::operator++ (){
2   *this +=13   return *this4 }

i++实现:

1 const int int::operatorint) {
2   int oldValue = *this3   ++(*this);
4   return oldValue;
5 }
8)举例说明深拷贝的安全性。
深拷贝与浅拷贝之间的区别就在于深拷贝会在堆内存中另外申请空间来存储数据,从而也就解决了野指针的问题。简而⾔之,当数据成员中有指针时,必须重载拷贝构造函数,使用深拷贝,更加安全。
9)C++是不是类型安全的?

不是,两个不同类型的指针之间可以强制转换(reinterpret_cast)。

2 面向对象(15)

1)C++空类默认有哪些成员函数?

默认构造函数、析构函数、复制构造函数、赋值函数。

2)重载(overload)和重写(override)的区别

重载:在相同作用域中存在多个同名的函数,这些函数的参数表不同;

重写:派生类重新定义基类虚函数。

3)main函数执行之前会执行什么?

全局对象的构造函数。

4)有哪几种情况初始化方式只能用initialization list而不能用assignment?

当类中含有const、reference成员变量;基类的构造函数。

5)举例说明函数返回值采用引用传递的作用。

函数返回值采用引用传递的场合并不多,一般只出现在类的赋值函数中,目的是为了实现链式表达。例如:

1 class A {
2     A& operator = (const A& other); // 赋值函数
3 };
4 A a, b, c;
5 a = b = c; // 合法
6 (a = b) = c; // 非法

6)为什么拷贝构造函数必需是引用传递,不能是值传递?

为了防止递归调⽤。当⼀个对象需要以值⽅式进⾏传递时,编译器会生成代码调⽤它的拷贝构造函数生成⼀个副本,如果类A的拷贝构造函数的参数不是引⽤传递,⽽是采⽤值传递,那么就又需要为了创建传递给拷⻉构造函数的参数的临时对象,而又⼀次调⽤类A的拷贝构造函数,这就是⼀个无限递归。

7)析构函数⼀般写成虚函数的原因?

为了降低内存泄漏的可能性。例如,⼀个基类的指针指向⼀个派⽣类的对象,在使⽤完毕准备销毁时,如果基类的析构函数没有定义成虚函数,那么编译器根据指针类型就会认为当前对象的类型是基类,调⽤基类的析构函数(该对象的析构函数的函数地址早就被绑定为基类的析构函数),仅执⾏基类的析构,派⽣类的⾃身内容将⽆法被析构,造成内存泄漏;如果基类的析构函数定义成虚函数,那么编译器就可以根据实际对象,执⾏派⽣类的析构函数,再执⾏基类的析构函数,成功释放内存。

8)面向过程与面向对象的区别?

面向过程:分析出解决问题所需要的步骤,然后用函数依次实现这些步骤,使用的时候依次调用;性能比面向对象高,但维护成本高。

面向对象:将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据;借助于封装、继承、多态性等特性,后期维护成本更低,更方便复用和扩展,但性能较差。

9)new operator 和operator new 的区别?

new operator实际上先后调用了operator new和constrctor两个函数。

10)重写函数的访问修饰符是可以不同的,例如尽管virtual中是private的,派⽣类中重写可以改为public。

11)空类是否占用内存空间?占用多少?

空类占用内存空间:1字节

12)构造函数调用顺序?

基类构造函数。如果有多个基类,则构造函数的调⽤顺序是某类在类派⽣表中出现的顺序,⽽不是它们在成员初始化表中的顺序。
成员类对象构造函数。如果有多个成员类对象则构造函数的调⽤顺序是对象在类中被声明的顺序,⽽不是它们出现在成员初始化表中的顺序。
派⽣类构造函数。
13)纯虚函数可以实现吗?如果实现,有什么实际意义或者说如何调用?
对于纯虚函数来说,我们其实是可以给它提供实现代码的,但是由于抽象类不能实例化,调⽤这个实现的唯⼀⽅式是在派⽣类对象中指出其class名称来调⽤。
14)说说public、protected、private区别。
A 访问权限:
public: 可以被该类中的函数、子类的函数、其友元函数访问,也可以由该类的对象访问
protected: 可以被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问
private: 只能由该类中的函数、其友元函数访问,不能以任何其他方法访问,该类的对象也不能访问
B 继承方式:
public: 父类中的方法属性不发生改变;
protected: 父类的public方法在子类中变为protected,其他方法不变;
private: 父类的所有方法在子类中变为private。
15)构造函数析构函数可否抛出异常?
不可。在构造函数中发⽣异常,控制权将转出构造函数之外。例如,在对象b的构造函数中发⽣异常,对象b的析构函数不会被调⽤,因此会造成内存泄漏;如果异常从析构函数抛出,而没有在当地进⾏捕捉,那个析构函数便是执行不全的。
3 标准库(1)

1)C++的四种强制转换?

C++的四种强制转换包括:static_cast, dynamic_cast, const_cast, reinterpret_cast
static_cast:明确指出类型转换,⼀般建议将隐式转换都替换成显式转换,因为没有动态类型检查,上行转换(派⽣类->基类)安全,下行转换(基类->派⽣类)不安全,所以主要执行非多态的转换操作;
dynamic_cast:专门用于派生类之间的转换,type-id必须是类指针、类引⽤或void*,对于下行转换是安全的,当类型不⼀致时,转换过来的是空指针,而static_cast,当类型不⼀致时,转换过来的是错误意义的指针,可能造成⾮法访问等问题。
const_cast:专门⽤于const属性的转换,去除const性质,或增加const性质,是四个转换符中唯⼀⼀个可以操作常量的转换符。
reinterpret_cast:不到万不得已,不要使用这个转换符,高危操作。使⽤特点:从底层对数据进⾏重新解释,依赖具体的平台,可移植性差;例如可以将整型转换为指针,也可以把指针转换为数组;可以在指针和引⽤之间进⾏肆无忌惮的转换。
 4 STL(3)

1) vector与deque的区别?

1)deque访问容器元素的效率比vector容器慢很多;
 原因在于deque要跨内存块来访问,而vector只是使用一个内存块,不用跨内存的操作。vector容器相当于一个可以动态分配的数组。
2)扩充容器容量的方面deque比vector的效率高很多;
 deque是申请一个小的内存块,然后把新申请的内存块与其它的deque内存块整合起来。
 vector是申请一个比原先更大的新内存块,把原vector里面的数据复制到新的大内存块里面,然后销毁原先的内存块。所以,在容量扩充方面,deque比vector高效,而且内存利用率也高。
3)vector不可以收缩,deque自动收缩;
 vector不支持把容器的容量调小的操作,即容量需求小于实际容量的情况不会有任何操作。而deque因为它是有很多小内存块的组成的,所以有空闲的内存块时,会销毁空闲的内存块,但不会影响deque里面已有的数据。

2) vector方法push_back与emplace_back的区别?

push_back()向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而emplace_back()在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。

3) 有指针为什么还要迭代器(STL迭代器的作用)?

迭代器把不同集合类的访问逻辑抽象出来,使得不用暴露集合内部的结构也可以达到循环遍历集合的效果。

迭代器不是指针,是类模板,只是表现的像指针。它只是模拟了指针的⼀些功能,通过重载指针的⼀些操作符,如->*++--等。迭代器封装了指针,是⼀个“可遍历STLStandard Template Library)容器内全部或部分元素的对象,本质是封装了原⽣指针,是指针概念的⼀种提升(lift),提供了⽐指针更高级的⾏为,相当于⼀种智能指针,他可以根据不同类型的数据结构来实现不同的++--等操作。迭代器返回的是对象引⽤⽽不是对象的值,所以std::cout只能输出迭代器使⽤*取值后的值而不能直接输出其自身。




posted @ 2022-09-14 11:37  Pepetang  阅读(5516)  评论(0编辑  收藏  举报