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++的探索路16多态与虚函数之练习篇

C++之多态性与虚函数

C++的探索路11继承与派生之拓展篇--多形式派生以及派生类指针转换

C++的探索路12继承与派生之高级篇--派生类与赋值运算符及多重继承

C++多态性与虚函数

探索C++对象模型