C++的探索路14多态与虚函数之基础篇
Posted Guerrouj
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++的探索路14多态与虚函数之基础篇相关的知识,希望对你有一定的参考价值。
Introduction
前几课的内容涉及了类,运算符重载,继承与派生几个基础概念,这一部分将对多态与虚函数进行介绍。
面向对象的程序设计语言具备封装,多态和继承三个基本概念,C++利用这三个基本概念可分别有效地提高程序设计的可读性、可扩充性及可重用性。
类为封装的最直接体现,前三章则对继承与多态进行了介绍。
相对陌生的可能就是多态,但前面章节中的运算符重载就是多态的部分体现。
多态--多种形态:同一名字的事物可以完成不同的功能,依据不同运行形态多态可以细分为编译时的多态以及运行时的多态。
编译时的多态指的是函数的重载(包括运算符重载),而运行时的多态(动态联编)与继承、虚函数等概念相关联。
看一下本章的内容:
为了方便学习,划分为基础篇及高级篇,内容如下
本部分将对基础部分进行介绍。
多态的基本概念
由基类指针实现多态
本部分提到的多态是动态联编的(运行时确定指向)
#include<iostream>
using namespace std;
class A
public:
virtual void Print()
cout << "A::Print" << endl;
;
class B:public A
public:
virtual void Print()
cout << "B::Print" << endl;
;
class D :public A
public:
virtual void Print()
cout << "D::Print" << endl;
;
class E :public B
public:
virtual void Print()
cout << "E::Print" << endl;
;
int main()
A a; B b; D d; E e;
A*pa = &a; B*pb = &b;
pa->Print();
pa = pb;
pa->Print();
pa = &d;
pa->Print();
pa = &e;
pa->Print();
return 0;
程序中展示了多态的例子:
A类派生出B和D类,E类继承了B类的东西,同时他们共有虚函数Print();每个Print函数将打印不同的内容
主程序分别定义了A,B,D,E四个类的对象并分别定义指针pa和pb,在主程序的后部分分别进行不同对象地址的指向进行多态的验证。
第一个pa->Print()将打印A类的内容
将B类地址赋值给A类指针后,将打印B类的内容
以此类推,输出结果应该为
A::Print
B::Print
D::Print
E::Print
这个例子很好的说明了运行时的多态(动态联编),而普通函数的调用语句则是静态联编的。
通过基类引用实现多态
通过基类的引用调用虚函数的语句也是多态的,即通过积累的引用调用积累和派生类的同名、同参数表的虚函数时,若其引用的是一个积累的对象,那么被调用的是基类的虚函数。
class A
public:
virtual void Print()
cout << "A::Print" << endl;
;
class B :public A
public:
virtual void Print()
cout << "B::Print" << endl;
;
void PrintInfo(A&r)
r.Print();
int main()
A a; B b;
PrintInfo(a);
PrintInfo(b);
return 0;
该例子与上述区别不大,这部分主要告诉我们,可以通过指针以及引用实现多态。
多态的作用
几何形体程序
编写一个几何形体处理程序,I:几何形体的个数以及形状和参数。
O:面积从小到大依次输出种类及面积,假设几何形体的总个数不超过100个。
例如输入
4
R 3 5
C 9
T 3 4 5
R 2 2
输出
Rectangle:4
Triangle:6
Rectangle:15
Circle:254.34
本题共同需要求不同形体的面积,因此可以定义形体类CShape,以及相应的虚函数输出对应的面积。
由于做的练习不够多,水平太菜;编程过程漏洞百出,先贴出参考代码,明日将首先自己先写一遍,然后进行参考答案的对照书写
#define PI 3.1415
class CShape
public:
virtual double Area()
return 0;
;
virtual void PrintInfo() ;
;
class CRectangle :public CShape
public:
double width, height;
virtual double Area()
return width*height;
virtual void PrintInfo()
cout << "Rectangle: " << Area() << endl;
;
class CTriangle :public CShape
public:
double a, b, c;
double perimeter()
return (a + b + c)/2.0;
virtual double Area()
return sqrt(perimeter()*(perimeter() - a)*(perimeter() - b)*(perimeter() - c));
virtual void PrintInfo()
cout << "Triangle: " << Area() << endl;
;
class CCircle :public CShape
public:
int radius;
virtual void Init()
cin >> radius;
virtual double Area()
return PI*radius*radius;
virtual void PrintInfo()
cout << "Circle: " << Area() << endl;
;
CShape*pShape[100];
int MyCompare(const void*s1, const void*s2)
CShape**p1 = (CShape**)s1;
CShape**p2 = (CShape**)s2;
double a1 = (*p1)->Area();
double a2 = (*p2)->Area();
if (a1 < a2)
return -1;
else if (a2 < a1)
return 1;
else
return 0;
int main()
int i; int n;
CRectangle*pr; CCircle *pc; CTriangle *pt;
cin >> n;
for (i = 0; i < n; ++i)
char c;
cin >> c;
switch (c)
case 'R':
pr = new CRectangle();
cin >> pr -> width>> pr->height;
pShape[i] = pr;
break;
case 'C':
pc = new CCircle();
cin >> pc->radius;
pShape[i] = pc;
break;
case 'T':
pt = new CTriangle();
cin >> pt->a>>pt->b>>pt->c;
pShape[i] = pt;
break;
qsort(pShape, n, sizeof(CShape*), MyCompare);
for (i = 0; i < n; ++i)
pShape[i]->PrintInfo();
delete pShape[i];
return 0;
程序分析:
第一步,定义形体类CShape
并由形体类派生出CCircle,CTriangle,CRectangle三个类
这几个类共有虚函数Area(),PrintInfo()进行相关操作。
第二步,丰富类内部
依据所需要功能进行填充
第三步,完善主函数
首先要提供一个“仓库”给这些派生出来的类中,比如:CShape pShape[100]。
然后依据多态情况,相应调用不同的函数进行初始化赋值。
最后再将多态的类存储在“仓库”中。
定义几个形体类的指针
第四步,进行qsort排序
第五步,顺序打印
多态的实现原理
多态的实现原理是使用了虚函数表,but,我们实际上并没有见过这玩意,但它是事实上存在的
#include<iostream>
using namespace std;
class A
public:
int i;
virtual void func()
virtual void func2()
;
class B:public A
int j;
virtual void func()
;
int main()
cout << sizeof(A) << "," << sizeof(B) << endl;
return 0;
实际上我们不加virtual关键字的时候,将会输出4,8,加了virtual后输出8与12,折4个字节就是是哪多态的关键,位于对象存储空间的最前端,里面存放了虚函数表的地址。
虚函数表虽然提供了编程上的开发效率,但也增加了程序运行的额外开销,一个是四个字节的地址(空间上的开销)另外一个是时间上的额外开销(查表)
多态的注意事项
成员函数中调用虚函数是多态的
class CBase
public:
void func1()
func2();
virtual void func2()
cout << "CBase::func2()" << endl;
;
class CDerived :public CBase
public:
virtual void func2()
cout << "CDerived::func2()" << endl;
;
int main()
CDerived d;
d.func1();
return 0;
由于成员函数调用虚函数为多态,因此,输出内容为CDerived:func2()。func1函数中的func2等价于this->func2(),而this指针为CBase类型;这条调用语句为多态。
在构造函数和析构函数中调用虚函数是非多态的
即编译时可以确定哪个调用哪个。如果本类有该函数,调用本类;如果没有,调用直接基类;如果直接基类没有,调用间接基类。程序如下
class A
public:
virtual void hello()
cout << "A::hello" << endl;
virtual void bye()
cout << "A::bye" << endl;
;
class B :public A
public:
virtual void hello()
cout << "B::hello" << endl;
B()
hello();
~B()
bye();
;
class C :public B
public:
virtual void hello()
cout << "C::hello" << endl;
;
int main()
C obj;
return 0;
本程序包含A,B,C三个类;其中A类包含成员函数void hello()以及void bye(),这两个函数均生命为虚函数,可以对A进行打印操作。
B类由A类派生而来,内含hello()成员函数,以及构造函数与析构函数,构造函数内部调用hello(),析构函数调用bye()。
C类由B类派生而来,内部包含成员函数hello。
主函数中定义了一个C类的对象obj,显然在程序的运行过程中需要经历构造以及析构两个过程。构造过程中,由于C类本身不存在构造函数,回忆调用基类法则,将调用B()进行构造,B()调用的hello为B类对象,所以将打印"B::hello",由于A类不存在构造函数,所以构造的部分到这一步结束。
下面进行析构,析构时将调用bye(),而B类不存在bye,因此将调用A类的bye(),打印输出A::bye。
如果我们改一下,B类内增加bye()则将打印输出
如果只在A类添加构造函数
A()
hello();
程序也将对A()进行构造,并由先父后子的关系,导致A::hello将先输出
以上两个新增的例子更加说明构造和析构是非多态的。
区分多态与非多态的情况
1,成员函数为虚函数则多态
2,基类中某个函数声明为虚函数,派生类中,同名、同参数表自动为多态
class A
public:
void func1()
cout << "A::func1" << endl;
virtual void func2()
cout << "A::func2" << endl;
;
class B :public A
public:
virtual void func1()
cout << "B::func1" << endl;
void func2()
cout << "B::func2" << endl;
;
class C :public B
public:
void func1()
cout << "C::func1" << endl;
void func2()
cout << "C::func2" << endl;
;
int main()
C obj;
A*pa = &obj;
B*pb = &obj;
pa->func1();
pa->func2();
pb->func1();
return 0;
该程序定义了A,B,C三个类,A为B的基类,为C类的间接基类,B为C的直接基类;这三个类中均包含func1(),func2()函数,这两个函数均能很好的对指向哪个对象进行很好的打印释义。不同的是func1()函数在A类中没有virtual关键字,而在B类开始出现,func2函数的virtual关键字只在A 类中出现一次。
主程序中定义了C类对象obj以及A类与B类的指针pa和pb,pa与pb均指向C类对象obj。
pa先后调用func1()和func2(),而func1()作为A类对象时是非多态的,所以将打印A::func1。func2()则是多态的,将打印C::func2
pb只调用了func1(),func1()对B类是多态的,所以将打印C::func1,因此输出结果为
A::func1
C::func2
C::func1
以上是关于C++的探索路14多态与虚函数之基础篇的主要内容,如果未能解决你的问题,请参考以下文章
C++的探索路11继承与派生之拓展篇--多形式派生以及派生类指针转换