天天看点

C++面向对象(5)

作者:Abin随心录
C++面向对象(5)

简述一下虚析构函数,什么作用

  • 虚析构函数,是将基类的析构函数声明为virtual
  • 虚析构函数的主要作用是防止内存泄露。

定义一个基类的指针p,在delete p时,如果基类的析构函数是虚函数,这时只会看p所赋值的对象,如果p赋值的对象是派生类的对象,就会调用派生类的析构函数。如果p赋值的对象是基类的对象,就会调用基类的析构函数,这样就不会造成内存泄露。

如果基类的析构函数不是虚函数,在delete p时,调用析构函数时,只会看指针的数据类型,而不会去看赋值的对象,这样就会造成内存泄露。

示例:

  • 创建一个TimeKeeper基类和一些及其它的派生类作为不同的计时方法
class TimeKeeper
{
public:
    TimeKeeper() {}
    ~TimeKeeper() {}  //非virtual的
};

//都继承与TimeKeeper
class AtomicClock :public TimeKeeper{};
class WaterClock :public TimeKeeper {};
class WristWatch :public TimeKeeper {};           
  • 该函数返回一个基类指针,这个基类指针是指向于派生类对象的。
TimeKeeper* getTimeKeeper()
{
    //返回一个指针,指向一个TimeKeeper派生类的动态分配对象
}           
  • 因为函数返回的对象存在于堆中,因此为了在不使用时我们需要使用释放该对象(delete)
TimeKeeper* ptk = getTimeKeeper();
delete ptk;           
  • 此处基类的析构函数是非virtual的,因此通过一个基类指针删除派生类对象是错误的
  • 解决办法: 将基类的析构函数改为virtual
  • 声明为virtual之后,通过基类指针删除派生类对象就会释放整个对象(基类+派生类)

什么是虚基类,可否被实例化?

  • 在被继承的类前面加上virtual关键字,这时被继承的类称为虚基类
class A
class B1:public virtual A;
class B2:public virtual A;
class D:public B1,public B2;           
  • 虚继承的类可以被实例化
class Animal {/* ... */ };
class Tiger : virtual public Animal { /* ... */ };
class Lion : virtual public Animal { /* ... */ }           
int main( )
{
    Liger lg;

    /*既然我们已经在Tiger和Lion类的定义中声明了"virtual"关键字,于是下面的代码编译OK */
    int weight = lg.getWeight();
}           

简述一下拷贝赋值和移动赋值?

  • 拷贝赋值是通过拷贝构造函数来赋值,在创建对象时,使用同一类中之前创建的对象来初始化新创建的对象。
  • 移动赋值是通过移动构造函数来赋值,二者的主要区别在于
  1. 拷贝构造函数的形参是一个左值引用,而移动构造函数的形参是一个右值引用。
  2. 拷贝构造函数完成的是整个对象或变量的拷贝,而移动构造函数是生成一个指针指向源对象或变量的地址,接管源对象的内存,相对于大量数据的拷贝节省时间和内存空间。

仿函数了解吗?有什么作用

  • 仿函数(functor)又称为函数对象(function object)是一个能行使函数功能的类。仿函数的语法几乎和我们普通的函数调用一样,不过作为仿函数的类,都必须重载operator()运算符。
class Func{
public:
    void operator() (const string& str) const {
        cout<<str<<endl;
    }
};

Func myFunc;
myFunc("helloworld!");

>>>helloworld!           
bool LengthIsLessThanFive(const string& str) {
     return str.length()<5;    
 }
 int res=count_if(vec.begin(), vec.end(), LengthIsLessThanFive);
           
bool LenthIsLessThan(const string& str, int len) {
     return str.length()<len;
 }           
class ShorterThan {
 public:
     explicit ShorterThan(int maxLength) : length(maxLength) {}
     bool operator() (const string& str) const {
         return str.length() < length;
     }
 private:
     const int length;
 };           

C++ 中哪些函数不能被声明为虚函数?

常见的不能声明为虚函数的有:普通函数(非成员函数),静态成员函数,内联成员函数,构造函数,友元函数。

  • 为什么C++不支持普通函数为虚函数?

普通函数(非成员函数)只能被overload,不能被override,声明为虚函数也没有什么意思,因此编译器会在编译时绑定函数。

  • 为什么C++不支持构造函数为虚函数?

主要是从语义上考虑,所以不支持。因为构造函数本来就是为了明确初始化对象成员才产生的,然而virtual function主要是为了再不完全了解细节的情况下也能正确处理对象。另外,virtual函数是在不同类型的对象产生不同的动作,现在对象还没有产生,如何使用virtual函数来完成你想完成的动作。(这不就是典型的悖论)

构造函数用来创建一个新的对象,而虚函数的运行是建立在对象的基础上,在构造函数执行时,对象尚未形成,所以不能将构造函数定义为虚函数

  • 为什么C++不支持内联成员函数为虚函数?

内联函数就是为了在代码中直接展开,减少函数调用花费的代价。虚函数是为了在继承后对象能够准确的执行自己的动作,这是不可能统一的。(inline函数在编译时被展开,虚函数在运行时才能动态的绑定函数)。

  • 为什么C++不支持静态成员函数为虚函数?

静态成员函数对于每个类来说只有一份代码,所有的对象都共享这一份代码,他也没有要动态绑定的必要性。

静态成员函数属于一个类而非某一对象,没有this指针,它无法进行对象的判别

  • 为什么C++不支持友元函数为虚函数?

因为C++不支持友元函数的继承,对于没有继承特性的函数没有虚函数的说法。

C++ 中类模板和模板类的区别

  • 类模板是模板定义,不是实实在在的一个类,定义中用到通用类型参数。
  • 模板类是是实在在的类定义,是类模板的示例化。类定义中的参数被实际类型所代替。

虚函数表里存放的内容是什么时候写进去的?

  • 虚函数表是一个存储虚函数地址的数组,以NULL结尾。虚表(vftable)在编译阶段生成,对象内存空间开辟以后,写入对象中的 vfptr,然后调用构造函数。即:虚表在构造函数之前写入。
  • 除了在构造函数之前写入之外,我们还需要考虑到虚表的二次写入机制,通过此机制让每个对象的虚表指针都能准确的指向到自己类的虚表,为实现动多态提供支持。
C++面向对象(5)