C++(14):OOP之核心——多态(Polymorphsim)

引言


多态,Polymorphsim,多种状态,面向对象程序设计的核心。

据说,这是OOP程序员在应聘的时候,第一大考点!!!!!!

就我目前的认知水平来看,多态的出现,是为了在庞大的软件工程里,降低开发的复杂度。


e.g. 一个软件工程量很大,通过定义一些接口,interface,不同的人按照这些接口所指定的规则(输入、输出),

去实现具体的内容。最后,软件统一整合的时候,才能对接得上,相当于是协作关系。

不过我觉得吧,定义一堆函数,不也就可以吗?找xxx去实现函数a,找yyy去实现函数b。这样看起来,似乎也可以啊……

难道这成了面向过程的了?难道这样不能大家一起合作开发吗?

暂时还是没有太能理解这种设计思想……哎…………【注意】后面一个案例,就可以体现。没有多态,开发起来很麻烦!


按照百度百科的说法,在OOP中,接口的不同实现方式,就叫做多态。

如果某种程序设计语言,有对象,但是不支持多态,那么只能叫基于对象(object based),不能叫面向对象(object oriented)。

现在也没太能理解这种意思。


反正我现在的理解就是,运用多态,似乎可以更好地让一群人分工合作,协同开发。类似于自己制定的一套规范,标准。还有,解耦合?没懂……

就像当时看一些JAVA框架的时候,e.g. struts,就相当于制定了一套规范。e.g. MVC,每一个view(jsp)的下一步一定是调到一个controller(java)去…

这样,好像就是行业标准吧。行业标准似乎就是为了实现大统一,只要我们都符合这个标准,只要都按照这种规范去开发,你能用我的,我也能用你的。

就像用USB线给手机充电一样。普通USB数据线,不管是三星,还是小米,还是华为,只要这些手机的充电口都统一符合“采用某种USB接口研发、生产制造”的标准,那这些手机的充电线是可以通用的。至于手机里面怎么充电的,是什么电池,容量多少,好像并不关心。手机充电头那边是怎么把220V转成5V,1A的输出,也无所谓。只在乎“你有android手机的充电线吗?”这句话。


再举一个“多态”的例子。

我听别人说的。

一位老板,一声令下,“走吧!”

司机,开左前车门,从左前车门上车,发动汽车。

保镖,开右后车门,请老板上车。待老板上车后,自己开右前车门,上车。

老板,从右后车门,上车。

秘书,开左后车门,从左后车门上车。


尽管,同一个“函数”也好、“命令”也好,但是,不同的角色,不同的人,却有不同的动作和响应。这就是多态。

而不是需要老板说,司机,你干嘛干嘛,保镖,你干嘛干嘛,小秘,你又干嘛干嘛。这样累不累?

【经典案例】

清华的某位教授说,什么叫经典例题?

1. 本来就很经典;

2. 每次做,都会有新的收获!

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


class CAnimal
{
public:
	CAnimal();
	virtual ~CAnimal();

	virtual void showName() const{
		cout<<"动物:我不知道我是谁"<<endl;
	}

	virtual void say() const{
		cout<<"动物:我不知道怎么叫"<<endl;
	}

	virtual void eat() const{
		cout<<"动物:我不知道吃什么"<<endl;
	}

protected:
	string name;

private:

};

CAnimal::CAnimal()
{
}

CAnimal::~CAnimal()
{
}


class CCat : public CAnimal
{
public:
	CCat();
	~CCat();

	void showName() const{
		cout<<"动物:我是猫"<<endl;
	}

	void say() const{
		cout<<"动物:喵喵喵"<<endl;
	}

	void eat() const{
		cout<<"动物:我吃鱼"<<endl;
	}


private:

};

CCat::CCat()
{
}

CCat::~CCat()
{
}

class CDog : public CAnimal
{
public:
	CDog();
	~CDog();

	void showName() const{
		cout<<"动物:我是狗"<<endl;
	}

	void say() const{
		cout<<"动物:汪汪汪"<<endl;
	}

	void eat() const{
		cout<<"动物:我啃骨头"<<endl;
	}

private:

};

CDog::CDog()
{
}

CDog::~CDog()
{
}

class CFood
{
public:
	CFood();
	virtual ~CFood();

	virtual void showName() const{
		cout<<"食物:我不知道我是什么食物"<<endl;
	}

private:

};

CFood::CFood()
{
}

CFood::~CFood()
{
}

class CFish : public CFood
{
public:
	CFish();
	~CFish();

	void showName() const{
		cout<<"食物:我是鱼"<<endl;
	}

private:

};

CFish::CFish()
{
}

CFish::~CFish()
{
}

class CBone : public CFood
{
public:
	CBone();
	~CBone();

	void showName() const{
		cout<<"食物:我是骨头"<<endl;
	}

private:

};

CBone::CBone()
{
}

CBone::~CBone()
{
}

class CMaster
{
public:
	CMaster();
	~CMaster();

	void feed(CAnimal & an, CFood & food) const{
		an.showName();
		an.say();
		food.showName();
		an.eat();
	}

private:

};

CMaster::CMaster()
{
}

CMaster::~CMaster()
{
}

int main () {

	CAnimal * an = new CCat;
	CFood * food = new CFish;

	CMaster * master = new CMaster;
	master->feed(*an,*food);


	delete an;
	delete food;
	delete master;


	system("pause");
	return 0;

}
动物:我是猫
动物:喵喵喵
食物:我是鱼
动物:我吃鱼


主人类中,feed函数,本来按理说就应该填一个动物,填一种食物,就好。

多态提供了足够的灵活性!

主人类中,代码可以写得很少啊!!!(有助于开发,特别是有助于减少架构师的工作量…)

如果没有多态,主人类中,要重载至少2个函数嘛。

一个给猫喂鱼,一个给狗喂骨头。

要是有更多的子类,主人类的开发要被写死!而且,维护起来很困难啊!



多态概述


据说,多态的形式分2种。我直接从PPT上摘下来。
Polymorphism is one of three keys in OOP and supports
– function overloading and operator  overloading at  compile time(编译时多态)
– function  overriding at  run-time associate many meanings to one function (运行时多态)
Run-time polymorphism
– enable programmers to design a common interface that can be used on different but related objects
– reduce complexity and development time


Compile-time polymorphism
– apply static binding
– advantage of fast speed
– realized by function overloading and operator overloading

Run-time polymorphism
– apply dynamic binding
– advantage of enhanced flexibility
– realized by inheritance + virtual functions

In C++, redefining a virtual function in a derived class is called overriding a function.

绑定的方式


静态绑定,static binding, early binding,在编译时进行绑定 at compile time.
动态绑定,dynamic binding, late binding,在运行时进行绑定 at run-time.

在JAVA里面,多态是可以通过写一个interface,然后某个class去implement这个接口,再override接口里的函数,实现。
好像也可以通过直接用class A 去 extends class B,并override B中的函数,实现。

在C++里,使用abstract base class和virtual function实现。
如果A是一个class,
A * a = new A;
a.fun1();
属于静态绑定,编译时就知道要调用A的fun1()函数了。

如果A是一个class,A中的fun1()前面用了virtual修饰词,
B又继承了A,如果在B中override了fun1()这个函数,
A * a = new B;
a.fun1();
属于动态绑定,只有到运行时才知道,虽然是a.fun1();,但其实是要调用B的fun1()函数。

我暂时不明白,为什么只有到运行时,才会知道……
难道在编译的时候,编译器看不懂我写的代码? (明明new的就是B啊)?难道只有在运行时,才会发现new的是B?
这是什么情况……

有一种官方的说法。

Casting between the base class and the derived class( 互相转型,3种情况!
– can assign a derived-class object to a  base-class object // 1. 在assign的时候,可以向上转型!
– can copy the address of a derived-class object to a pointer of a base-class object // 2. 可以把派生类的地址assign给指向基类的指针
– a derived-class object can be a reference to base-class object // 3. 可以写 B0 & b0 = D1; 
But! can only access members in the base class, not members in the derived class ( 都只能范围基类中的成员!不能访问派生类中的成员!

虚函数


Virtual functions makes that a pointer or reference of a base-class object can be applied onto a derived-class object(注意,也 只能是虚函数加上pointer or ref才会体现,assign也是不行的!

Virtual functions tell the compiler
– don’t know how function is implemented
– wait until used in program
– get implementation from object instance
– call dynamic (late) binding
(只要写了virtual function,编译器自己就会去等,直到运行时…)

If a function f() in the base class is virtual, then all f()‘s in the derived classes are virtual.
(只要父类中f是虚函数,即用virtual声明了,则之后在它的所有派生类中,f这个函数都是虚函数,即时没有用virtual声明)
A virtual function must be a member function of a class cannot be global, static or friend.
Destructors can be virtual but constructors cannot be virtual.

Major disadvantage: more storage overhead + running slower
#include <iostream>
#include <cstdlib>
#include <cstring>

using namespace std;

class B0 {   
public: 
	void ShowFun() { //not virtual
        cout << "B0::ShowFun()" << endl; } 
};

class C0 : public B0 {   
public: 
	virtual void ShowFun() { //virtual
    	cout << "C0::ShowFun()" << endl; } 
};

class C1 : public C0 {   
public: 
	void ShowFun() { //virtual
        cout << "C1::ShowFun()" << endl; } 
};

class C2 : public C1 {   
public: 
	void ShowFun() { //virtual
        cout << "C2::ShowFun()" << endl; } 
};

void FunPtr(C0 *ptr) {
    ptr->ShowFun();
}


int main() 
{
    B0 w, *p; C0 x, *q;
    C1 y; C2 z;
    p = &w; p->ShowFun();
    p = &y; p->ShowFun(); //Q1: what happen??
    q = &x; FunPtr(q);    //Q2: which to call? 
    q = &y; FunPtr(q);
    q = &z; FunPtr(q); 
	//q = &w; FunPtr(q);    //Q3: what happen??
	//FunPtr(&w); 			//Q4: what happen?? => cannot covert from B0* to C0*; but the reverse can
	//FunPtr(p);			//Q5: what happen?? => cannot covert from B0 to C0* ; but the reverse can
	
	system("pause");
    return 0;	
}
结果。
B0::ShowFun()  // p是指向B0的指针,输出是B0
B0::ShowFun()  // 不是虚函数,不会访问派生类的成员
C0::ShowFun()  // 虚函数。 showFun在B0里已经是虚函数了。在子类中也会是虚函数!尽管override了!
C1::ShowFun()  // 
C2::ShowFun()

【注意】
1. 如果某个类是基类,立即把它的析构函数写成虚函数!
【经典案例】
class A { public:
~A() { cout << “A::~A()\n”;}
};
class C : public A {
int * iary;
public:
C(int i) { iary = new int [i]; }
~C() {
delete [] iary;
cout << “C::~C()\n”; }
};
//in main()
A *pa = new C(10);
delete pa;
派生类对象开辟的内存,并没有回收!!!
所以,一定要把析构函数写成虚函数!!!

2. 尽量用父类去声明,尽管最后new出来的是子类!
和java里的写法一样。
List <Object> list = new ArrayList<Object> ();
如果用子类去声明的话,有可能会发生点不出来父类中写过的一些函数的情况。
e.g.  overloaded members in base classes cannot be accessed directly.
上一篇也提到过。
Review: C++多继承

#include <iostream>
using namespace std;


class CB0
{
public:
	CB0() {};
	~CB0() {};

	void fun() {
		cout<<"CB0::fun()"<<endl;
	}

	void fun(int i) {
		cout<<"CB0::fun(int i) "<<endl;
	}

};

class CD0 : public CB0
{
public:
	CD0() {};
	~CD0() {};

	void fun(int i) {
		cout<<"CD0::fun(int i) "<<endl;
	}
};

int main () {

	CB0 * obj1 = new CD0; // 父类声明,却有两个fun
	obj1->fun();
	obj1->fun(0);

	CD0 obj2;
	obj2.fun(0); // 子类声明,只有这一个了!

	system("pause");
	return 0;
}
结果。
CB0::fun()
CB0::fun(int i)
CD0::fun(int i)
因为没写虚函数,所以指针的值虽然保存的一个CD0对象的地址,但通过这个指针,fun也只能调用CB0的fun了。
如果用了虚函数
#include <iostream>
using namespace std;


class CB0
{
public:
	CB0() {};
	virtual ~CB0() {};

	virtual void fun() {
		cout<<"CB0::fun()"<<endl;
	}

	virtual void fun(int i) {
		cout<<"CB0::fun(int i) "<<endl;
	}
	
private:

};

class CD0 : public CB0
{
public:
	CD0() {};
	~CD0() {};

	virtual void fun(int i) {
		cout<<"CD0::fun(int i) "<<endl;
	}

private:

};

int main () {

	CB0 * obj1 = new CD0; // 父类声明,却有两个fun
	obj1->fun();
	obj1->fun(0); 

	CD0 d0;
	d0.fun(1); // 子类声明,只有一个fun。在子类中,override过什么,同名的就只有什么了。
	
	system("pause");
	return 0;
}
结果。
CB0::fun() // 这确实在调用父类的fun。
CD0::fun(int i) // 由于多态,调用子类的fun。一定要是整个函数一模一样,才算覆写!覆写≠重载!
CD0::fun(int i) // 本来就是用CD0声明的,所以肯定调用子类的。

【经典案例】吐血推荐!
class B { public:
void f() { cout << “Bf ”; }
virtual void g(){ cout << “Bg ”; }
void h() { g(); f(); }
virtual void m(){ g(); f(); }
};
class D : public B { public:
void f() { cout << “Df ”; }
void g() { cout << “Dg ”; }
void h() { f(); g(); }
void m(int i) {f(); g();}
};
//in main()
D d; B *pB = &d;
pB->f(); pB->g(); pB->h(); pB->m();
结果会是:
Bf Dg Dg Bf Dg Bf

pB->h(); h在B里不是虚函数,所以调用B的h()。
B的h里面,有g,而g是虚函数,所以调用D的g,输出Dg,
B的h里面,还有f,f不是虚函数,所以调用B的f,输出Bf。
同理,pB->m(); m()是在B里虚函数,但是D中没有override( override一定要是两个函数一模一样!!!),只是又增加了一个void m(int i)的函数
所以调用B中的m,
B的m里面,有g,g是虚函数,调用D的g,输出Dg,
B的m里面,还有f,f不是虚函数,调用B的f,输出Bf
game over…

纯虚函数


(类似于Java里面的interface)
Pure virtual functions require no definition 
– force each derived classes to define its own version
Class with one or more pure virtual functions is abstract base class(注意,是抽象基类,不是虚基类。虚基类是虚拟继承里的概念!)
– can only be used as base class
– no objects can ever be created from it
because it doesn’t include complete definitions of all its members
抽象基类不能实例化!
If one derived class fails to define all pure virtual functions,
– also an abstract base class
如果抽象基类的派生类,没有重新定义所有的虚函数,那这个派生类依然是抽象基类,还是不能被实例化!
纯虚函数的声明方式。后面等于0,不要写函数体。
virtual void Area()=0; //pure virtual!




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qcyfred

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值