【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 -------------------------------------