第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;
}