当不同的对象去完成某个行为时会产生出不同的状态。
比如:
买火车票,普通人是全价,学生是半价,儿童是免票。
在了解多态的构成条件之前,我们首先要了解虚函数和重写。
被virtual
修饰的类成员函数称为虚函数。
class person
{
public:
virtual void buyTicket()
{
cout << "买票——全价" << endl;
}
};
派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。
class person
{
public:
virtual void buyTicket()
{
cout << "买票——全价" << endl;
}
};
class student:public person
{
public:
virtual void buyTicket()
{
cout << "买票——半价" << endl;
}
};
派生类重写基类虚函数时,与基类虚函数返回值类型不同。
即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。
class A{};
class B :public A{};
class person
{
public:
virtual A* buyTicket()
{
return new A;
}
};
class student:public person
{
public:
virtual B* buyTicket()
{
return new B;
}
};
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual
关键字,都与基类的析构函数构成重写。
class person
{
public:
virtual ~person()
{
cout << "~person()" << endl;
}
};
class student:public person
{
public:
//只有派生类Student的析构函数重写了Person的析构函数
//delete对象调用析构函数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数
virtual ~student()
{
cout << "~student()" << endl;
}
};
class person
{
public:
virtual void buyTicket()
{
cout << "买票——全价" << endl;
}
};
class student:public person
{
public:
virtual void buyTicket()
{
cout << "买票——半价" << endl;
}
};
void fun1(person& people)
{
people.buyTicket();
}
void fun2(person* people)
{
people->buyTicket();
}
int main()
{
person krystal;
fun1(krystal);
student crystal;
fun2(&crystal);
return 0;
}
override
和final
关键字检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
class car
{
public:
virtual void drive(){}
};
class porsche:public car
{
public:
virtual void drive() override
{
cout << "porsche——fast" << endl;
}
};
修饰虚函数,表示该虚函数不能再被继承。
class car
{
public:
virtual void drive() final {}
};
class porsche:public car
{
public:
virtual void drive()
{
cout << "porsche——fast" << endl;
}
};
在虚函数的后面写上=0
,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。
派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。
class car
{
public:
virtual void drive() = 0;
};
class porsche:public car
{
public:
virtual void drive()
{
cout << "porsche——fast" << endl;
}
};
int main()
{
car c1;
porsche p1;
return 0;
}
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。
虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。
class Base
{
private:
int _b=1;
public:
virtual void fun1()
{
cout << "fun1()" << endl;
}
};
int main()
{
Base b;
return 0;
}
运行代码,使用监视窗口查看对象内容。
发现b对象中除了_b成员还有一个_vfptr放在对象前面,我们把对象中的这个指针叫做虚函数表指针简称虚表指针,指向一个虚表。
一个含有虚函数的类中都至少有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。
派生类的虚表中存放了什么呢?
我们修改代码,进行观察。
fun1()
fun2()
和普通函数fun3()
class Base
{
private:
int _b=1;
public:
virtual void fun1()
{
cout << "BASE::fun1()" << endl;
}
virtual void fun2()
{
cout << "BASE::fun2()" << endl;
}
void fun3()
{
cout << "BASE::fun3()" << endl;
}
};
class Derive :public Base
{
private:
int _d = 2;
public:
virtual void fun1()
{
cout << "Derive::fun1()" << endl;
}
};
int main()
{
Base b;
Derive d;
return 0;
}
fun1()
完成了重写,所以d的虚表中存的是重写的Derive::fun1()
,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。fun2()
继承下来后是虚函数,所以放进了虚表,fun3()
也继承下来了,但是不是虚函数,所以不会放进虚表。总结:
派生类的虚表生成
VS下存放在代码段
class Base
{
private:
int _b=1;
public:
virtual void fun1()
{
cout << "BASE::fun1()" << endl;
}
virtual void fun2()
{
cout << "BASE::fun2()" << endl;
}
void fun3()
{
cout << "BASE::fun3()" << endl;
}
};
class Derive :public Base
{
private:
int _d = 2;
public:
virtual void fun1()
{
cout << "Derive::fun1()" << endl;
}
};
void fun()
{
}
int main()
{
Base b;
Derive d;
static int si = 0;//数据段
int i = 1;//栈
int* pi = new int;//堆
void(*fptr)();
fptr = fun;//代码段
cout << "数据段:" << &si<<endl;
cout << "栈:" << &i<<endl;
cout << "堆:" << &pi<< endl;
cout << "代码段:" <<fptr<< endl;
typedef void(*vfptr)();
vfptr* ptr = (vfptr*)(*((int*)&d));
cout << "虚表:" << ptr << endl;
return 0;
}
class Person
{
public:
virtual void buyTicket()
{
cout << "买票——全价" << endl;
}
};
class Student:public Person
{
public:
virtual void buyTicket()
{
cout << "买票——半价" << endl;
}
};
void fun(Person* p)
{
p->buyTicket();
}
int main()
{
Person zhangsan;
fun(&zhangsan);
Student lisi;
fun(&lisi);
return 0;
}
观察红色箭头我们看到,p是指向zhangsan对象时,p->buyTicket
在zhangsan的虚表中找到虚函数是Person::buyTicket
。
观察蓝色箭头我们看到,p是指向lisi对象时,p->buyTicket
在lisi的虚表中找到虚函数是Student::buyTicket
。
这样就实现了,不同对象完成同一行为时,状态不同。
可以看到,在多态场景下
使用的是动态绑定:
在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数。
class Base
{
private:
int _b;
public:
virtual void fun1()
{
cout << "Base::fun1()" << endl;
}
virtual void fun2()
{
cout << "Base::fun2()" << endl;
}
};
class Derive :public Base
{
private:
int _d;
public:
virtual void fun1()
{
cout << "Derive::fun1()" << endl;
}
virtual void fun3()
{
cout << "Derive::fun3()" << endl;
}
virtual void fun4()
{
cout << "Derive::fun4()" << endl;
}
};
int main()
{
Base b;
Derive d;
return 0;
}
我们发现看不见fun3
和fun4
。这里是编译器的监视窗口故意隐藏了这两个函数。
vfptr
class Base
{
private:
int _b;
public:
virtual void fun1()
{
cout << "Base::fun1()" << endl;
}
virtual void fun2()
{
cout << "Base::fun2()" << endl;
}
};
class Derive :public Base
{
private:
int _d;
public:
virtual void fun1()
{
cout << "Derive::fun1()" << endl;
}
virtual void fun3()
{
cout << "Derive::fun3()" << endl;
}
virtual void fun4()
{
cout << "Derive::fun4()" << endl;
}
};
typedef void(*vfptr)();
void PrintVtable(vfptr Vtable[])
{
cout << "虚表地址:" << Vtable << endl;
vfptr* fptr = Vtable;
while (*fptr != nullptr)
{
printf("虚函数地址:0X%x", fptr);
(*fptr)();
++fptr;
}
}
int main()
{
Base b;
Derive d;
//思路:取出b、d对象的头4bytes,就是虚表的指针
// 1.先取b的地址,强转成一个int*的指针
// 2.再解引用取值,就取到了b对象头4bytes的值,这个值就是指向虚表的指针
// 3.再强转成vfpte*,因为虚表就是一个存vfptr类型(虚函数指针类型)的数组。
// 4.虚表指针传递给PrintVtable进行打印虚表
vfptr* vtablebbb = (vfptr*)(*(int*)&b);
PrintVtable(vtablebbb);
cout << endl;
vfptr* vtableddd = (vfptr*)(*(int*)&d);
PrintVtable(vtableddd);
return 0;
}
派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中。
class Base1
{
private:
int _b1;
public:
virtual void fun1()
{
cout << "Base1::fun1()" << endl;
}
virtual void fun2()
{
cout << "Base1::fun2()" << endl;
}
};
class Base2
{
private:
int _b2;
public:
virtual void fun1()
{
cout << "Base2::fun1()" << endl;
}
virtual void fun2()
{
cout << "Base2::fun2()" << endl;
}
};
class Derive:public Base1, public Base2
{
private:
int _d;
public:
virtual void fun1()
{
cout << "Derive::fun2()" << endl;
}
virtual void fun3()
{
cout << "Derive::fun2()" << endl;
}
};
typedef void(*vfptr)();
void PrintVtable(vfptr Vtable[])
{
cout << "虚表地址:" << Vtable << endl;
vfptr* fptr = Vtable;
while (*fptr != nullptr)
{
printf("虚函数地址:0X%x", fptr);
(*fptr)();
++fptr;
}
}
int main()
{
Derive d;
vfptr* vtable1 = (vfptr*)(*(int*)&d);
PrintVtable(vtable1);
cout << endl;
vfptr* vtable2 = (vfptr*)(*(int*)((char*)&d+sizeof(Base1)));
PrintVtable(vtable2);
return 0;
}
可以看出:
派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中。