11 多态
11.1 多态与虚函数
多态性指同一操作作用于不同对象产生不同响应。
11.1.1 binding
编译器根据函数的参数和函数名决定使用那个函数
11.1.3 虚函数
通过指针调用虚函数时,具体调用那个虚函数只取决于指针指向的对象类型。
- 一种接口,多种方法
- 通过基类指针能处理所有派生类的所有情况。
#include<iostream>
using namespace std;
class base
{
public:
virtual void disp()
{
cout<<"base"<<endl;
}
};
class child1:public base
{
public:
void disp()
{
cout<<"child1"<<endl;
}
};
class child2:public base
{
public:
void disp()
{
cout<<"child2"<<endl;
}
};
void disp(base &p)
{
p.disp();
}
int main()
{
base b1,*pb;
child1 c1;
child2 c2;
pb=&c1;
pb->disp();
pb=&c2;
pb->disp();
pb=&b1;
pb->disp();
disp(c1);
return 0;
}
11.1.4 虚函数的声明和定义
只要在成员函数前加一个关键字virtual即可。==如果一个基类的成员函数定义为虚函数,即便是在派生类中省略了virtual关键字,其所有派生类中也将保持为虚函数。==一个虚函数属于其所在的类的层次的,不光属于其所在的类中。
派生类可根据需要对虚函数重定义
格式要求
- 参数和参数类型与基类虚函数相同
- 其返回类可以与基类虚函数相同,也可是基类被替换的虚函数所返回的指针(引用)的派生类型
特殊说明
- private修饰的虚函数没有意义
- 派生类中没有对虚函数重定义则继承基类的虚函数
- 友元不能是虚函数,友元可以调用虚函数解决问题。虚函数可以是另一个类的友元函数。
11.2 虚函数的访问
11.2.1 对象名的访问
通过对象名调用虚函数,采用的是静态联编,和对象名调用非虚函数没有区别。(可能造成虚函数失效)
11.2.2 指针访问
主要形式
11.2.3 引用访问
与指针类似,不同的是一经声明无法修改,提高代码的安全性。
11.2.4 类内访问
类内访问该层次的虚函数,使用this指针。
11.2.5 构造函数或析构函数中访问
编译器解释为静态联编。不推荐使用。
11.3 纯虚函数与抽象类
11.3.1 纯虚函数的声明与定义
提供一个与派生类的接口
class base
{
public:
//纯虚函数,base作为抽象类
virtual void disp()=0;
};
class child1:public base
{
public:
void disp()
{
cout<<"child1"<<endl;
}
};
class child2:public base
{
public:
void disp()
{
cout<<"child2"<<endl;
}
};
11.3.2 抽象类
一个类可以包含许多纯虚函数,只要类中有一个纯虚函数,该类即为抽象类,不能创建抽象类对象,只能作为基类派生新类。但可声明一个指向抽象类的指针
纯虚函数不能被继承,在派生类中只能重定义或者再次将该虚函数声明为纯虚函数。抽象类的派生类给出所有基类的纯虚函数的实现时该派生类不再是抽象类。
在构造函数和析构函数中调用一个纯虚函数会导致运行错误。
11.3.3 另一种抽象类:只定义了protected的构造函数
11.3.4 构造函数为private型
在外部或者派生类中不能使用 "类名+对象名"的形式创建该类对象,但可通过类的static函数成员来创建类的实例(protected型也可)
11.3.5 虚析构函数
构造函数不能为虚函数。类中定义了虚函数,析构函数也应定义为虚函数,尤其是存在动态内存申请和释放
#include<iostream>
using namespace std;
class Base
{
private:
char* data;
public:
Base()
{
data =new char[64];
cout<<"Base类的构造函数被调用"<<endl;
}
~Base()
{
delete [] data;
cout<<"Base类的析构函数被调用"<<endl;
}
};
class child :public Base
{
private:
char* m_data;
public:
child()
{
m_data=new char[64];
cout<<"child类的构造函数被调用"<<endl;
}
~child()
{
delete [] m_data;
cout<<"child类的析构函数被调用"<<endl;
}
};
class grandchild :public child
{
private:
char* mm_data;
public:
grandchild()
{
mm_data=new char[64];
cout<<"grandchild类的构造函数被调用"<<endl;
}
~grandchild()
{
delete [] mm_data;
cout<<"grandchild类的析构函数被调用"<<endl;
}
};
int main()
{
grandchild *pGc=new grandchild;
delete pGc;
cout<<"-------------------------"<<endl;
child *pc=new grandchild;
delete pc;
return 0;
}
析构函数是由指针类型选择调用的
#include<iostream>
using namespace std;
class Base
{
private:
char* data;
public:
Base()
{
data =new char[64];
cout<<"Base类的构造函数被调用"<<endl;
}
//基类虚构函数定义为虚函数
virtual ~Base()
{
delete [] data;
cout<<"Base类的析构函数被调用"<<endl;
}
};
class child :public Base
{
private:
char* m_data;
public:
child()
{
m_data=new char[64];
cout<<"child类的构造函数被调用"<<endl;
}
~child()
{
delete [] m_data;
cout<<"child类的析构函数被调用"<<endl;
}
};
class grandchild :public child
{
private:
char* mm_data;
public:
grandchild()
{
mm_data=new char[64];
cout<<"grandchild类的构造函数被调用"<<endl;
}
~grandchild()
{
delete [] mm_data;
cout<<"grandchild类的析构函数被调用"<<endl;
}
};
int main()
{
grandchild *pGc=new grandchild;
delete pGc;
cout<<"-------------------------"<<endl;
child *pc=new grandchild;
delete pc;
return 0;
}
基类虚构函数定义为虚函数,析构函数根据对象类型调用,很好的完成了内存清理任务
11.5 重载、覆盖与隐藏
重载和函数是否为虚函数无关
覆盖:派生类的成员函数覆盖基类中的成员函数,要求两个函数的参数个数和类型相同,且基类函数为虚函数
隐藏:派生类屏蔽基类中的同名函数。参数相同,同覆盖的区别在于基类函数不是虚函数;参数不同,同重载的区别在于不在一个类中。