第18课 - 多态与继承 - 下
一. 重载和重写
Source Example 1: #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ class Parent { public: virtual void func() { printf("Parent->"); printf("func()!\n"); } virtual void func(int i) { printf("Parent->"); printf("func(int i)!\n"); } virtual void func(int i, int j) { printf("Parent->"); printf("func(int i, int j)!\n"); } }; class Child : public Parent { public: virtual void func(int i) { printf("Child->"); printf("func(int i)!\n"); } virtual void func(int i, int j) { printf("Child->"); printf("func(int i, int j)!\n"); } void func(int i, int j, int k) { printf("Child->"); printf("func(int i, int j, int k)!\n"); } }; void run(Parent* p) { p->func(1,2); } int main(int argc, char** argv) { Parent p; Child c; p.func(); p.func(1); p.func(1,2); /* 编译报错,因为void func(int i, int j, int k)函数将继承的func函数名称覆盖了 */ /* 并没有发生函数重载,函数重载发生在同一作用域之间 */ //c.func(); /* 这样才能调用父类中的func函数 */ c.Parent::func(); /* Child里面定义的两个func函数处于同一作用域,可以发生重载 */ c.func(1,2); printf("\n"); /* 会发生多态 */ run(&p); run(&c); return 0; }
输出结果如下:
1.1 重载与重写区别
函数重载:
a.必须在同一个类(同一个作用域)中进行
b.子类无法重载父类的函数,父类同名函数将被覆盖
c.重载是函数在编译期间根据参数类型和个数决定调用的函数
函数重写:
a.必须发生于子类和父类之间
b.并且子类和父类中的函数必须有完全相同的原型
c.使用virtual声明之后能够产生多态
d.多态是在运行期间根据具体对象的类型决定调用函数(编译时不知道)
二.虚函数深入理解
2.1 问题1: 是否可以将类的每个成员都声明为虚函数?
2.2 C++中多态的实现原理
2.2.1 当类中声明为虚函数时,编译器会在类中产生一个虚函数表(存储虚函数的地址)
2.2.2 虚函数表是一个存储类成员函数指针的数据结构
2.2.3 虚函数表是由编译器自动生成与维护的
2.2.4 virtual成员函数会被编译器放入虚函数表中
2.2.5 存在虚函数时,每个对象都有一个指向虚函数表的指针(VPTR指针,一般作为对象的第一个成员)
void run(Parent* p) 编译器确定func()是否为虚函数?
{ 1.Yes, 编译器在对象VPTR所指向的虚函数表中查找func(),并调用,查找,在运行时完成
p->func(); 2.No, 编译器直接可以确定并找到被调用成员的函数
}
问题一答案:通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要寻址操作才能真正的确定应该调用的函数。
而普通成员函数在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。
出于效率的考虑,没必要将所有的成员函数都声明为虚函数。
2.2 问题2: 对象中VPTR指针什么时候被初始化?
2.2.1 对象在创建的时候由编译器对VPTR指针进行初始化
2.2.2 只有当对象的构造完全结束后VPTR的指向才最终确定
完全结束->(父类和子类的构造都结束,每个构造函数执行之前VPTR指针都会变化)
2.2.3 父类对象的VPTR指向父类虚函数表
2.2.4 子类对象的VPTR指向子类虚函数表
结论:构造函数中调用虚函数无法实现多态。
Source Example2.2(构造函数中的"多态") #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ class Parent { public: Parent() { this->func(); } virtual void func() { printf("Parent->"); printf("func()!\n"); } }; class Child : public Parent { public: virtual void func(int i) { printf("Child->"); printf("func(int i)!\n"); } }; int main(int argc, char** argv) { /* 会调用父类的func函数 */ Parent p; /* * 1. 首先会执行父类函数的构造函数,此时VPTR指针指向父类的虚函数表 * 2. 调用虚函数表的func函数,即父类中的func函数 * 3. 其次会执行子类函数的构造函数,此时VPTR指针指向子类的虚函数表 * 4. 因此会调用父类的func函数,多态并没有发生 * 5. 结论,构造函数中无法实现多态 */ Child c; return 0; }
输出结果如下:
三.纯虚函数
3.1 面向对象中的抽象概念
在进行面向对象分析时,会有一些抽象的概念
->矩形
图像 ->三角形 考虑如何给图形求面积?
->圆形
在现实中需要知道图像的具体类型才能球面积,所以对概念上的"图形"求面积是没有意义的!
class Shape
{
public:
double area()
{
return 0
}
}
这个类的设计完全脱离实际,没有任何意义。
3.2 用Shape作为基类进行继承
Source Example 3.2: #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ class Shape{ public: virtual double area() { return 0; } }; class Rectangle : public Shape{ protected: int a; int b; public: Rectangle(int a, int b) { this->a = a; this->b = b; } virtual double area() { return a * b; } }; class Circle : public Shape{ protected: int r; public: Circle(int r) { this->r = r; } virtual double area() { return 3.14 * r * r; } }; void area(Shape* s) { printf("area = %lf\n", s->area()); } int main(int argc, char** argv) { Rectangle r(1,2); Circle c(3); Shape s; area(&r); area(&c); area(&s); return 0; }输出结果如下:
3.3 面向对象中的抽象类
3.3.1 抽象类可用于表示现实世界中的抽象概念
3.3.2 抽象类是一种只能定义类型,而不能产生对象的类
3.3.3 抽象类只能被继承并且重写相关函数
3.3.4 抽象类的直接特征是纯虚函数
注意:纯虚函数是只声明函数原型,而故意不定义函数体的虚函数
3.4 抽象类与纯虚函数
3.4.1 抽象类不能用于定义纯虚对象
3.4.2 抽象类只能用于定义指针和引用
3.4.3 抽象类中的纯虚函数必须被子类重写
class Shape
{
public:
virtual double area() = 0;
};
area是纯虚函数,=0 告诉编译器,这个函数故意只声明不定义
Source Example3.4: #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ class Shape{ public: virtual double area() = 0; }; class Rectangle : public Shape{ protected: int a; int b; public: Rectangle(int a, int b) { this->a = a; this->b = b; } /* 子类中必须要有重写Shape类中的area函数,不然会报错 */ virtual double area() { return a * b; } }; class Circle : public Shape{ protected: int r; public: Circle(int r) { this->r = r; } /* 子类中必须要有重写Shape类中的area函数,不然会报错 */ virtual double area() { return 3.14 * r * r; } }; void area(Shape* s) { printf("area = %lf\n", s->area()); } int main(int argc, char** argv) { Rectangle r(1,2); Circle c(3); /* 编译会报错,因为不能定义抽象类的对象 */ Shape s; area(&r); area(&c); return 0; }