一、多态
1、先看一下例子
class CShape
{
private:
int m_x;
int m_y;
public:
CShape(int x = 0 , int y = 0 ):m_x(x),m_y(y){}
virtual void Draw() //虚函数
{
cout<<"CShape 中的Draw函数"<<endl;
}
};
class CCircle : public CShape
{
private:
int m_r;
public:
CCircle(int x,int y ,int r):CShape(x,y),m_r(r){}
void Draw()<span style="white-space:pre"> </span>//实现了多态
{
cout<<"CCircle 中的Draw函数"<<endl;
}
};
class CRectangle : public CShape
{
private:
int m_i;
int m_j;
public:
CRectangle(int x = 0,int y = 0 ,int i = 0 ,int j = 0):CShape(x,y),m_i(i),m_j(j){}
void Draw()<span style="white-space:pre"> </span>//实现了多态
{
cout<<"CRectangle 中的Draw函数"<<endl;
}
};
void bigDraw(CShape* arr[],int num)
{
for(int i = 0; i < num ; i++)
{
arr[i]->Draw();
}
}
int main(void)
{
//多态
CShape* shape[5];
shape[0] = new CCircle(5,6,8);
shape[1] = new CRectangle(2,2,5,5);
shape[2] = new CRectangle(4,4,6,6);
shape[3] = new CCircle(10,10,10);
shape[4] = new CRectangle(40,40,60,60);
bigDraw(shape,5);
delete shape[0];
delete shape[1];
delete shape[2];
delete shape[3];
delete shape[4];
return 0;
}
上面程序的输出结果:
CCircle 中的Draw函数
CRectangle 中的Draw函数
CRectangle 中的Draw函数
CCircle 中的Draw函数
CRectangle 中的Draw函数
上就是多态技术的使用场合一个很好例子的说明,下面我们来探讨这个多态技术
2.多态
多态: 使用相同类型的引用\指针调用相同的函数,却表现出不同的效果.
(1) 通过指向子类的基类指针/引用,只能调用基类中的成员函数
(2) 通过指向子类的基类指针/引用去调用父类的虚函数,实际上调用的是子类的同版本的函数.
(3) 虚函数: 使用virtual关键字修饰的函数我们称之为虚函数.
(4) 如果在子类中定义一个与基类中具有相同函数原型的成员函数就构成虚函数覆盖.父类中的函数我们使用virtual修饰.
该虚函数的覆盖版本也是虚函数,尽管没有使用virtual修饰.
我们来看一个特殊的例子:
我们来看一个特殊的例子:
假如我定义了两个类是
class A
class A
{
public:
virtual A* foo(void){}
};
class B:public A
class B:public A
{
public:
virtual B* foo(void){}
};
基类A中有一个成员虚函数:virtual A* foo(void){}
子类B也有一个成员虚函数:virtual B* foo(void){}
上述例子构成覆盖. B子指针可以当作A类指针使用…….,实现了多态。
同此我们可以得出结论:如果虚函数返回类型是一个类类型A的指针/引用 ,子类的覆盖版本可以返回该类型A的子类类型.
3.虚析构(虚析构使用的场合)
实际上为了保证delete一个指向子类对象的父类指针时,去调用子类的析构函数而设计的,子类的析构函数会自动调用父类的析构函数,从而避免内存泄漏.
建议:一般而言,如果一个类中有虚函数,那么该类就应该提供一个虚析构函数。
例如如下代码:
class AEx
{
public:
AEx(void)
{
cout<<"A构造"<<endl;
}
virtual ~AEx()
{
cout<<"析构A"<<endl;
}
};
class BEx : public AEx
{
public:
BEx(void)
{
cout<<"B构造"<<endl;
}
~BEx()
{
cout<<"析构B"<<endl;
}
};
4.多态的底层实现机制.
首先用一段代码去替换函数调用语句,这段代码的执行步骤:
(1)弄清指针pa所指向的对象的真实身份
(2)然后通过对象的虚函数表指针去访问虚函数表,并且找到与foo函数标识符对应的地址
(3)根据虚函数中保存地址,找到函数的代码进行调用.
class AAEx
{
private:
// int m_num;
public:
virtual void foo(void)
{
cout<<"这是A类中的foo函数"<<endl;
}
virtual void print(void)
{
cout<<"这是A类中的print函数"<<endl;
}
};
class CCEx
{
public:
virtual fooex()
{
cout<<"这是B类中的fooex函数"<<endl;
}
};
class BBEx : public AAEx,public CCEx
{
public:
void foo(void)
{
cout<<"这是B类中的foo函数"<<endl;
}
};
typedef void(*Fun)(void); //定义一个函数指针的别名.
typedef Fun* pFun; //定义一个指向虚函数表的指针的类型别名.
5 多态的必要条件
(1)虚函数
(2)通过基类指针/引用去指向子类的对象,使用基类指针/引用 去调用覆盖的同名函数
(3)面试题:
构造函数,析构函数,运算符函数(成员函数),静态成员函数,哪些函数不能为虚函数?为什么?
答:构造函数不能为虚函数, 静态成员不能为虚函数.
6.纯虚函数和抽象类
(1)纯虚函数:virtual 返回值类型 函数名(参数列表……) = 0 ;
(2)抽象类::一个类中只要有一个纯虚函数,那这个类就是抽象类。抽象类是不能创建对象的。
(3)纯抽象类:完全由纯虚函数组成的抽象类称作纯抽象类.
注意:一个抽象类的子类如果没有为基类中的全部纯虚函数提供覆盖版本,那么这个子类也成为抽象类。
class E
{
public:
virtual void foo(void) = 0; //纯虚函数
//virtual void show(void) = 0; //纯虚函数
};
class F : public E
{
public:
virtual void foo(void)
{
cout<<"F类中的foo"<<endl;
}
};
class G : public F
{
public:
void foo(void)
{
cout<<"G 类中的foo"<<endl;
}
};