在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口(函数的声明就相当于是接口,与别人对接)继承。
纯虚函数一般是只声明,不实现,因为实现没有价值,但是是可以实现的
class Car
{
public:
// 纯虚函数是只声明,不实现
// virtual void Drive() = 0;
virtual void Drive() = 0
{
}
};
int main()
{
// Car c; 不行
Car* pc; // 可以用指针或是引用
return 0;
}
对比下图两种情况


为什么p是空指针/野指针,还可以去调用f()?——因为函数名没有放在对象里面,对象里面只有成员变量;因此p->f();并没有解引用,只是把p传给了this,传递一个空指针是没问题的,那么为什么第一种情况会出错?——虚函数的调用要去虚表中查找
第一种虚函数是放在虚表里,p->Drive调用需要去虚表中找,虚表和虚表的指针在对象里面
第二种在公共的代码区,没在对象里面,就不存在对空指针的解引用
class Car
{
public:
virtual void Drive() = 0
{
cout << "virtual void Drive() = 0" << endl;
}
};
class Benz :public Car
{
public:
virtual void Drive()
{
cout << "Benz-舒适" << endl;
}
};
int main()
{
// Car* p = new Car; // 不能实例化出这个对象
Car* p = new Benz;
p->Drive(); // 重写虚函数后,就可以实例化出对象,调用的就是子类的
// 指向谁就调用谁
return 0;
}
因此,纯虚函数一般只声明,不实现,实现没有价值
抽象一般指的是在现实世界中没有对应的实物,就叫做抽象,一个类型,如果一般在现实世界中没有对应的具体实物,就定义为抽象类比较好
纯虚函数的类,本质上是强制子类去完成虚函数的重写,override只是在语法上检查了是否完成重写
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。
继承体现的是实现继承
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
virtual void Func2()
{
cout << "Func2()" << endl;
}
private:
int _b = 1;
char _ch = 'A';
};
int main()
{
cout << sizeof(Base) << endl; // 这里为多少?12/16
return 0;
}
有了虚函数后,这个对象里面就多了一个成员

这个成员叫虚函数表指针(指向虚函数),之后按对齐数计算大小

现在就可以解释刚刚的问题,p->Drive为什么会报错

class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
void f()
{
cout << "f()" << endl;
}
protected:
int _a = 0;
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
protected:
int _b = 0;
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person Mike;
Func(Mike);
Student Johnson;
Func(Johnson);
return 0;
}
上述代码如何实现出多态的?



因此他们两是调用的不同函数
多态的原理:基类的指针或是引用指向谁,就去谁的虚函数表中找到对应的位置的虚函数进行调用
那为什么要传指针和引用,而不能是对象?

因此其中一个原因是必须要为指针或是引用
int main()
{
Person p1;
Person p2;
Student s1;
Student s2;
return 0;
}
同类型的对象,虚表指针是一样的


普通函数和虚函数存的位置一样吗?
一样,都在公共的代码段,只是虚函数要把地址存到虚表,方便实现多态
void Func(Person& p)
{
p.BuyTicket();
// 运行时,是去p指向的对象的虚表中,找到虚函数的地址
p.f();
}
只有符合多态的条件,才会去虚函数的表中去找,就算是虚函数,但不构成多态也不会去虚函数的表中去找,而是直接确认了函数的地址
多态调用在编译时是不能确定调用的哪个函数,指向谁是不知道的
class Base
{
public:
virtual void Func1()
{
cout << "Base::Func1()" << endl;
}
virtual void Func2()
{
cout << "Base::Func2()" << endl;
}
void Func3()
{
cout << "Base::Func3()" << endl;
}
private:
int _b = 1;
};
class Derive : public Base
{
public:
virtual void Func1()
{
cout << "Derive::Func1()" << endl;
}
private:
int _d = 2;
};
int main()
{
Base b;
Derive d;
return 0;
}


class Base {
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
private:
int a;
};
class Derive :public Base {
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
virtual void func4() { cout << "Derive::func4" << endl; }
private:
int b;
};
int main()
{
Base b;
Derive d;
return 0;
}

可以观察到虚函数表中只有func1和func2,但是func3和4在哪里?

class Base1 {
public:
virtual void func1() { cout << "Base1::func1" << endl; }
virtual void func2() { cout << "Base1::func2" << endl; }
private:
int b1;
};
class Base2 {
public:
virtual void func1() { cout << "Base2::func1" << endl; }
virtual void func2() { cout << "Base2::func2" << endl; }
private:
int b2;
};
class Derive : public Base1, public Base2 { // 只存放第一个继承的虚表base1
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
private:
int d1;
};

可以发现,有两个虚表指针,都存的func1和func2,那么func3在哪里?以及两个func1虽然地址不同,但是最后指向的位置是相同的


又转到这里

又进行jmp,多次jmp是要修正this指针的值以及指针的偏移

最后两者的地址一样,第一个是直接调到00C3122B,第二个jmp多次后才跳到;虽然两个fun1()地址不同,但是虚表中存的地址不是函数真正的地址,最终调用的都是同一个函数
多继承时,子类重写了Base1和Base2虚函数func1,但是虚表中重写的fun1地址的确不一样,但是这没有什么关系,因为最后还是会调到同一个函数

构成多态:运行时决定去调用谁
不构成多态:编译时决定去调用谁