本文源自于个人github仓库:https://github.com/forthespada/InterviewGuide
github仓库内有PDF版本下载方式,欢迎各位star、fork~
立志收录计算机校招、社招面试最全面试八股文,无内鬼来点八股文~
81、构造函数一般不定义为虚函数的原因
(1)创建一个对象时需要确定对象的类型,而虚函数是在运行时动态确定其类型的。在构造一个对象时,由于对象还未创建成功,编译器无法知道对象的实际类型
(2)虚函数的调用需要虚函数表指针vptr,而该指针存放在对象的内存空间中,若构造函数声明为虚函数,那么由于对象还未创建,还没有内存空间,更没有虚函数表vtable地址用来调用虚构造函数了
(3)虚函数的作用在于通过父类的指针或者引用调用它的时候能够变成调用子类的那个成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类或者引用去调用,因此就规定构造函数不能是虚函数
(4)析构函数一般都要声明为虚函数,这个应该是老生常谈了,这里不再赘述
《为什么C++不能有虚构造函数,却可以有虚析构函数》:https://dwz.cn/lnfW9H6m
82、类什么时候会析构?
1) 对象生命周期结束,被销毁时;
2) delete指向对象的指针时,或delete指向对象的基类类型指针,而其基类虚构函数是虚函数时;
3) 对象i是对象o的成员,o的析构函数被调用时,对象i的析构函数也被调用。
83、构造函数或者析构函数中可以调用虚函数吗
简要结论:
- 从语法上讲,调用完全没有问题。
- 但是从效果上看,往往不能达到需要的目的。
《Effective C++》的解释是:
派生类对象构造期间进入基类的构造函数时,对象类型变成了基类类型,而不是派生类类型。 同样,进入基类析构函数时,对象也是基类类型。
举个例子:
#include<iostream> using namespace std; class Base { public: Base() { Function(); } virtual void Function() { cout << "Base::Fuction" << endl; } ~Base() { Function(); } }; class A : public Base { public: A() { Function(); } virtual void Function() { cout << "A::Function" << endl; } ~A() { Function(); } }; int main() { Base* a = new Base; delete a; cout << "-------------------------" <<endl; Base* b = new A;//语句1 delete b; } //输出结果 //Base::Fuction //Base::Fuction //------------------------- //Base::Fuction //A::Function //Base::Fuction
语句1讲道理应该体现多态性,执行类A中的构造和析构函数,从实验结果来看,语句1并没有体现,执行流程是先构造基类,所以先调用基类的构造函数,构造完成再执行A自己的构造函数,析构时也是调用基类的析构函数,也就是说构造和析构中调用虚函数并不能达到目的,应该避免
《构造函数或者析构函数中调用虚函数会怎么样?》:https://dwz.cn/TaJTJONX
84、智能指针的原理、常用的智能指针及实现
原理
智能指针是一个类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏。动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源
常用的智能指针
(1) shared_ptr
实现原理:采用引用计数器的方法,允许多个智能指针指向同一个对象,每当多一个指针指向该对象时,指向该对象的所有智能指针内部的引用计数加1,每当减少一个智能指针指向对象时,引用计数会减1,当计数为0的时候会自动的释放动态分配的资源。
- 智能指针将一个计数器与类指向的对象相关联,引用计数器跟踪共有多少个类对象共享同一指针
- 每次创建类的新对象时,初始化指针并将引用计数置为1
- 当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数
- 对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数
- 调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)
(2) unique_ptr
unique_ptr采用的是独享所有权语义,一个非空的unique_ptr总是拥有它所指向的资源。转移一个unique_ptr将会把所有权全部从源指针转移给目标指针,源指针被置空;所以unique_ptr不支持普通的拷贝和赋值操作,不能用在STL标准容器中;局部变量的返回值除外(因为编译器知道要返回的对象将要被销毁);如果你拷贝一个unique_ptr,那么拷贝结束后,这两个unique_ptr都会指向相同的资源,造成在结束时对同一内存指针多次释放而导致程序崩溃。
(3) weak_ptr
weak_ptr:弱引用。 引用计数有一个问题就是互相引用形成环(环形引用),这样两个指针指向的内存都无法释放。需要使用weak_ptr打破环形引用。weak_ptr是一个弱引用,它是为了配合shared_ptr而引入的一种智能指针,它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,也就是说,它只引用,不计数。如果一块内存被shared_ptr和weak_ptr同时引用,当所有shared_ptr析构了之后,不管还有没有weak_ptr引用该内存,内存也会被释放。所以weak_ptr不保证它指向的内存一定是有效的,在使用之前使用函数lock()检查weak_ptr是否为空指针。
(4) auto_ptr
主要是为了解决“有异常抛出时发生内存泄漏”的问题 。因为发生异常而无法正常释放内存。
auto_ptr有拷贝语义,拷贝后源对象变得无效,这可能引发很严重的问题;而unique_ptr则无拷贝语义,但提供了移动语义,这样的错误不再可能发生,因为很明显必须使用std::move()进行转移。
auto_ptr不支持拷贝和赋值操作,不能用在STL标准容器中。STL容器中的元素经常要支持拷贝、赋值操作,在这过程中auto_ptr会传递所有权,所以不能在STL中使用。
智能指针shared_ptr代码实现:
template<typename T> class SharedPtr { public: SharedPtr(T* ptr = NULL):_ptr(ptr), _pcount(new int(1)) {} SharedPtr(const SharedPtr& s):_ptr(s._ptr), _pcount(s._pcount){ *(_pcount)++; } SharedPtr<T>& operator=(const SharedPtr& s){ if (this != &s) { if (--(*(this->_pcount)) == 0) { delete this->_ptr; delete this->_pcount; } _ptr = s._ptr; _pcount = s._pcount; *(_pcount)++; } return *this; } T& operator*() { return *(this->_ptr); } T* operator->() { return this->_ptr; } ~SharedPtr() { --(*(this->_pcount)); if (this->_pcount == 0) { delete _ptr; _ptr = NULL; delete _pcount; _pcount = NULL; } } private: T* _ptr; int* _pcount;//指向引用计数的指针 };
《智能指针的原理及实现》:https://blog.csdn.net/lizhentao0707/article/details/81156384
85、构造函数的几种关键字
default
default关键字可以显式要求编译器生成合成构造函数,防止在调用时相关构造函数类型没有定义而报错
#include <iostream> using namespace std; class CString { public: CString() = default; //语句1 //构造函数 CString(const char* pstr) : _str(pstr){} void* operator new() = delete;//这样不允许使用new关键字 //析构函数 ~CString(){} public: string _str; }; int main() { auto a = new CString(); //语句2 cout << "Hello World" <<endl; return 0; } //运行结果 //Hello World
如