【c++】多态:多态与虚函数、重载、抽象类

【c++】多态:多态与虚函数、重载、抽象类

 1.多态

 2.抽象类

 3.重载


参考:

《c++从入门到精通》 人民邮电出版社

1.多态

        多态是面向对象程序设计的重要特征之一,是扩展性在“继承”之后的又一重大表现。

        多态:是指同一操作作用于不同的类的实例时,将产生不同的执行结果。即:不同的类的对象,收到相同的消息时,得到不同的结果。

        比如,同样是运动,马是奔,鸟是飞,人是走,袋鼠是跳。这就是多态。

        多态性可以分为:

            编译时的多态性

            运行时的多态性

          其中,编译时的多态性又称静态联编,其实现机制为重载;运行时的多态性又称动态联编,其实现机制为虚函数

          所以实现多态有两种方法:虚函数和重载。

那么如何使用多态呢?

        建筑类CBuilding为基类,其派生类为桥类CBridge,二者都有显示函数display(),但功能不同。

先看1:普通的成员函数display(),未实现多态。

分析结果发现桥类的输出没有输出长度length,因此没有实现多态。

//多态性.cpp

#include<iostream>
#include<string.h>
using namespace std;

class CBuilding  //建筑类 
{
	private:
		string name;
	public:
		void set(string str)
		{
			name=str;
		}
		void display()
		{
			cout<<"建筑是:"<<name<<endl;
		}
} ; 

class CBridge:public CBuilding  //桥类 
{
	private:
		double length;
	public:
		void setLength(double i)
		{
			length=i;
		}
		void display()
		{
			CBuilding::display();
			cout<<"桥的长度是:"<<length<<endl;
		}
} ; 

void show(CBuilding *a)
{
	a->display();   //通过指向基类指针进行显示	
}

int main()
{
	CBuilding b1;
	CBridge b2;
	b1.set("古典园林");
	show(&b1);
	
	b2.set("赵州桥");
	b2.setLength(55.6);
	show(&b2);
	return 0;
}

运行结果:

             

再看2虚函数--成员函数display(),可实现多态

                分析结果发现桥类输出了长度length,因此实现多态。

                将上述的CBuilding类改为如下: 就只是将void display改为虚函数,即可!

class CBuilding  //建筑类 
{
	private:
		string name;
	public:
		void set(string str)
		{
			name=str;
		}
		virtual void display()  // 虚函数! 
		{
			cout<<"建筑是:"<<name<<endl;
		}
} ;

运行结果:

虚函数为什么可以实现多态?

        Display()定义为virtual后,编译器将记住这个信息,所以在后边的show()函数中,并不知道display的明确指向,即编译器不知道此时的函数入口是CBuilding 的display()还是CBridge的display()。直到在main()中运行到相应的实例时,才知道具体指向哪一个的display()。这也就是所谓的滞后捆绑技术

void show(CBuilding *a)
{
  a->display();   //通过指向基类指针进行显示  

}

对于虚函数,有以下几点需要注意的问题:

(1)虚函数实际上是利用滞后捆绑处理来实现多态的,因此执行效率低一些,但其实现的多态性相当诱人。所以提倡,成员函数设计成虚函数。

(2)一旦将一个成员函数设置为虚函数,则会在继承结构中自动传承下去。例,父辈的成员函数是虚函数,相应的子辈的成员函数也是虚函数。

(3)静态函数没有虚函数,内联函数不可能是虚函数,构造函数不能是虚函数。

(4)析构函数可以是 而且经常是虚函数。

2.抽象类

        学习了继承之后,发现类不仅有创建对象的功能,还有派生新类(儿子)的功能。那么,有没有专门“生儿子”,而不能被实例化(创建对象)的类呢?

        有!比如,“动物”这一基类,我们是看不到动物的对象的,看到的都是动物的派生类,像哺乳动物、爬行动物等。

而在c++中,通过定义抽象类来实现这一功能。抽象类的特点是:它的成员函数中至少有一个是纯虚函数

格式为:

Class<类名>
{
virtual <类型><函数名>(参数表) = 0; //纯虚函数:令虚函数=0!

}

        virtual <类型><函数名>(参数表) = 0;从形式上看是虚函数,只是没有函数体,用=0来代替了函数体,即:纯虚函数没有具体的方法实现(没有函数体)。

由于纯虚函数没有函数体,因而抽象类不能被实例化,这是在编译层面被限制的。只要有一个成员函数是纯虚函数,这个类就是抽象类,抽象类派生新类后,其子类中一定要对纯虚函数进行覆盖,即重写该方法

注意:只要有一个纯虚函数没有覆盖,则该派生类仍然是抽象类。

 例如:CVehiclevehicle;  // 错误!!!抽象类不能实例化

           CVhicle是一个抽象类,上式将其实例化会报错。

             

抽象类的编程实例:

        CVehicle是抽象类,它含有virtualvoid Motion 这一纯虚函数,CCar是其派生类,CCar重写了Motion()这一方法,对继承来的纯虚函数进行了覆盖,因此CCar(不是抽象类)可以被实例化。而CVehicle不能被实例化

//抽象类.cpp
#include<iostream> 
using namespace std;

class CVehicle 
{
//交通工具类--抽象类 
	private:
		string name;
	public:
		CVehicle(string str="Vehicle") { name=str;} //构造函数 
	
		string GetName(){ return name;	}
		virtual void Motion(string Model="Motion") =0;  //纯虚函数 
	 
} ;

class CCar:public CVehicle 
{
//派生类

	public:
		CCar(string str="Car"): CVehicle(str) { } //构造函数:向基类传递信息 
	
		
		void Motion(string Model="Motion") { //覆盖纯虚函数 
		    cout<<"GetName:"<<GetName()<<endl;
			cout<<"覆盖纯虚函数 " <<endl;
		} 
	 
} ;

int main()
{
	//CVehicle vehicle;  // 错误!!! 抽象类不能实例化
	CCar car("mini_car") ;
	car.Motion();
	return 0;
}

运行结果:

         

 3.重载

实现多态有两种方法:虚函数和重载。重载分为:函数重载运算符重载,二者本质一样。

 函数重载

例1:加法器重载—实现整数、小数、字符串相加

//重载.cpp
#include<iostream> 
#include<string>
using namespace std;

class CAdd
{
//加法器
	
	public:
		int add(int a,int b){ return a+b;} 
		double add(double a,double b){ return a+b;} 
		string add(string a,string b){ return a+b;} 

} ;


int main()
{
	CAdd x;
	cout<<"整数加法:1+2"<<endl;
	cout<<x.add(1,2)<<endl;
	
	cout<<"小数加法:1.1+2.2"<<endl;
	cout<<x.add(1.1, 2.2)<<endl;
	
	cout<<"字符串加法:hello + world "<<endl;
	cout<<x.add("hello","world")<<endl;
	
	return 0;
}

运行结果:

            

 运算符重载

2:复数加法器operator +”

将上述程序中添加新类CComplex()CAdd中添加类成员CComplex add(CComplex a,CComplex b)

class CComplex{
	private:
		int a,b;
	public:
		CComplex(){ a=0;b=0; } //构造函数初始化 
		CComplex operator + (CComplex another)
		{
			CComplex c;
			c.a=a+ another.a;
			c.b=b+ another.b;
return c;
		}
		
		void input() {	cin>>a>>b;	}//输入 
		void show() {   //显示 
		cout<<"a+bi="<<a<<"+"<<b<<"i"<<endl;
		}
};

CAdd中添加:

 CComplexadd(CComplex a,CComplex b){ return a+b;}

main中添加:

cout<<"复数加法输入:"<<endl;
	CComplex c1,c2;
	c1.input();
	c1.show();
	c2.input();
	c2.show(); 
	cout<<"复数加法结果:"<<endl;
	x.add(c1,c2) .show();

运行结果:

            

分析:运算符和普通函数调用时的区别在于:

                对于普通函数,参数出现在圆括号()内;

                而对于运算符“+,参数出现在其左、右侧 A+B

            

注意:并不是所有的运算发都可以被重载,不是理论上不可以,而是没有必要这么做。不能重载的运算符有:

.“::” “.*” “#”  “sizeof”等。

 -------------------------------------------         END      -------------------------------------

猜你喜欢

转载自blog.csdn.net/u012679707/article/details/80237642