多态的概念
所谓多态,其含义就是“多种形式”或“多种状态”。我们把具有继承关系的多个类型称为多态类型,因为我们能使用这些类型的“多种形式”而无须在意他们的差异。在面向对象语言中,接口的多种不同的实现方式即为多态,多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。
多态的分类
1)静态多态(早绑定):编译器在编译期间完成,根据函数实参的形式推断出要调用的函数,如果有对应的函数就调用,没有就编译报错;主要表现形式是函数重载和泛型编程。
如函数重载实例:
#include<iostream>
using namespace std;
int Add(int left, int right)
{
return left + right;
}
double Add(double left, double right)
{
return left + right;
}
int main()
{
cout << Add(1, 2) << endl;
cout << Add(1.1, 2.2) << endl;
system("pause");
return 0;
}
根据实参的类型分别调用不同的类型的Add函数,保证了运行结果的正确性,结果如下:
2)动态多态(晚绑定):是在程序执行(非编译)期间完成的,主要是通过虚函数来实现的。
动态多态两个缺一不可的条件:a.基类中必须有虚函数,在派生类中必须对虚函数重写
b.必须通过基类的指针或者引用调用虚函数
在介绍虚函数之前,我们先来了解一下基类与派生类之间的赋值兼容的内容,以便更好的理解虚函数;
赋值兼容的简单理解:我们有时候会把整形类型的数据赋值给双精度类型的变量,这时会先把整形类型的数据先转换成双精度类型,再赋值给双精度类型变量,这种不同类型之间的类型转换和赋值,我们称之为赋值兼容。
同样的,派生类和基类之间也存在赋值兼容规则,并且它要求基类必须是公有继承,因为在公有继承中派生类保留了基类中除了构造和析构之外的所有成员,基类的公有或保护成员的访问权限都按原样保留下来,在派生类外可以调用基类的公有函数来访问基类的私有成员。其主要的实现方式如下: 1. 子类对象可以赋值给父类对象(切割/切片)
2. 父类对象不能赋值给子类对象
3. 父类的指针/引用可以指向子类对象
4. 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)
来看看一个具体的例子:
#include<iostream>
using namespace std;
#include<string>
class StrBase
{
public:
StrBase(string S)
{
s = S;
}
void showS()
{
cout << "字母S=>" << s << endl;
}
private:
string s;
};
class Derived :public StrBase
{
public:
Derived(string d)
:StrBase(d)
{}
};
void funtest(StrBase&base)
{
base.showS();
}
int main()
{
StrBase s1("123");
Derived d("hello");
s1.showS();
d.showS();
StrBase&p = d;
p.showS();
StrBase *str = &d;
str->showS();
StrBase&p1 = s1;
p1.showS();
system("pause");
return 0;
}
虚函数:虚函数必须是基类的非静态成员函数,其访问权限可以是private或protected或public,在基类的类定义中定义虚函数的一般形式。在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数,实现多态性。
格式:
virtual 函数返回类型 函数名 (形参列表)
{
函数体
}
单继承:
class A
{
public:
virtual void funtest1()
{
cout << "A::funtest1()" << endl;
}
virtual void funtest2()
{
cout << "A::funtest2()" << endl;
}
int _a;
};
class B:public A
{
public:
virtual void funtest1()
{
cout << "B::funtest1()" << endl;
}
virtual void funtest3()
{
cout << "B::funtest3()" << endl;
}
int _b;
};
int main()
{
A a;
B b;
a.funtest1();
a.funtest2();
b.funtest1();
b.funtest2();
b.funtest3();
A *p = &b;
p->funtest1();
A&p1 = b;
p1.funtest1();
p1.funtest2();
system("pause");
return 0;
}
我们来分析一下实例:a.funtest1()、a.funtest2()、b.funtest1()、b.funtest2()、b.funtest3()这几个函数都是通过对象直接对相应的函数调用的,*p是一个基类指针,从结果看,他不是直接访问对应函数的,而是通过查询虚函数列表找到对应的函数的地址进而进行访问,而且根据基类指针的指向不同,函数的地址也不同,所以通过指针找到派生类中的funtest1()函数。
如图所示:
多继承:
示例如下
class A
{
public:
virtual void funtest1()
{
cout << "A::funtest1()" << endl;
}
virtual void funtest2()
{
cout << "A::funtest2()" << endl;
}
int _a;
};
class B
{
public:
virtual void funtest3()
{
cout << "B::funtest3()" << endl;
}
virtual void funtest4()
{
cout << "B::funtest4()" << endl;
}
int _b;
};
class C:public A,public B
{
public:
virtual void funtest1()
{
cout << "C::funtest1()" << endl;
}
virtual void funtest3()
{
cout << "C::funtest3()" << endl;
}
int _c1;
};
int main()
{
C c;
c._a = 0;
c._b = 1;
c._c1 = 2;
c.funtest1();
c.funtest2();
c.funtest3();
c.funtest4();
A *p = &c;
p->funtest1();
A&p1 = c;
p1.funtest1();
p1.funtest2();
system("pause");
return 0;
}
我们看看该多继承的内存空间:
多重继承含有两张虚表,对于派生类C,class C 对相应的重写的函数进行替换,没有重写的就调用基类的虚函数
而派生类自己的虚函数放在第一张虚表之后。