基础知识点
在C++中,如果发现有了继承关系,那么注定会有,父类指针指向子类对象。父类指针调用子类方法。这里重点说下虚函数和非虚函数:
虚函数 | 非虚函数 | |
定义和声明 | 使用 virtual 关键字声明,在基类中定义,允许在派生类中重写。 | 没有 virtual 关键字,不能被重写,派生类中同名的函数只是隐藏了基类的同名函数。 |
调用机制 | 使用动态绑定(或晚绑定)。函数调用在运行时根据对象的实际类型确定。 | 使用静态绑定(或早绑定)。函数调用在编译时根据指针或引用的类型确定。 |
适用场景 | 用于需要多态性时,例如想要实现不同类之间的统一接口时。 | 用于仅在基类中需要固定行为,且不需要在派生类中改变的情况。 |
性能 | 由于需要使用虚表(vtable)和指针,有一定的性能开销。 | 由于直接调用,没有额外的开销,通常效率更高。 |
析构函数 | 类的设计中,如果基类有虚函数,通常将析构函数声明为虚的,以确保派生类的析构函数能够被正确调用。 | 如果一个类的析构函数是非虚的,而通过基类指针删除派生类对象,可能导致资源泄漏和未定义行为。 |
补充知识点
上面是基础知识,下面来一些值得思考的知识点(编译器用的是MSVC2015):
如下类定义代码:
class Base {
public:void print() { cout << "Base print" << endl; }
};class A : public Base {
public:void print() { cout << "A print" << endl; }
};
如下调用:
Base *base = new Base;base->print();((A*)base)->print();delete base;
运行结果如下:
这里就有点看开始奇怪了,这里的base变量是用父类new的,第二个打印只是强转成了A*,然后成功打印了A类的print,我感觉应该会运行时报错或者打印Base的print也能理解。因为A的print并没有被构造。再看下下面
如果把类定义改成:
class Base {
public:virtual void print() { cout << "Base print" << endl; }
};class A : public Base {
public:void print() { cout << "A print" << endl; }
};
运行截图如下:
Base *base = new Base;base->print();((A*)base)->print();delete base;
加了virtual就好理解了,新加了个虚表的概念,调用print,就直接从虚表中拿函数地址,因为没有new A,所以虚表没有被刷掉,调用的还是Base类的print。
而上面那个例子,可以看出,在计算机底层中,还有个内存块,专门存储了成员函数。估计在定义时就有了。
当然不推荐大家这么使用。因为例子中给的函数很简单,如果比较复杂,比如这样的:
定义如下:
class Base {
public:void print() { cout << "Base print" << endl; }
};class A : public Base {
public:A() { value1 = 1; }void print() { cout << "A print : " << value1 << endl; }private:int value1;
};
调用如下:
Base *base = new Base;base->print();((A*)base)->print();delete base;
运行截图如下:
就开始有问题了,打印的就不知道是哪块内存的数据。