继承与多态
一、继承
1、继承概念
在原有类的基础上派生出新的类,新类继承原有类的属性和方法,称原有的类为基类, 又称为父类。由已存在的类派生出的新类称为派生类,又称为子类。从一个基类派生的继承称为单继承、从多个基类派生的继承称为多继承
继承是包含的一种特列 C类继承B类,且有一个A类对象,其C类对象的首地址为B类对象的首地址
2、继承使用方法
单继承派生类的声明语法为:
class 派生类名 : 继承方式 基类名
{
派生类新增成员的声明;
}
class CForm{
public:
CForm();
~CForm();
//其他省略……
};
class CFormMainMenu:public CForm{
public:
CFormMainMenu();
~CFormMainMenu();
//其他省略……
};
3、三种继承方式:
公有继承(public):特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的。
受保护继承(protected):特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。
私有继承(private):默认继承方式,特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问。
基类中的私有成员
①、不能被派生类继承
②、不能被派生类的成员函数直接访问
③、可通过定义于基类的公有和受保护成员函数访问
4、继承中构造函数和析构函数关系
继承中构造函数调用顺序:基类–>派生类
继承中析构函数调用顺序:派生类–>基类
当基类的构造函数需要参数时,在派生类里使用初始化列表来进行初始化。
派生类名::派生类名(派生类构造参数表):基类(基类构造函数参数表)
Teacher::Teacher(char *str, char *sch) : People(str) //调用基类的构造函数
{
-----
}
5、派生类的重定义(隐藏的一种)
在派生类中重定义基类的函数
①、派生类自动继承基类的所有成员
②、重定义函数的函数原型和基类中被重定义函数的函数原型必须完全相同
不同则导致基类的成员函数被隐藏
③、重定义之后两个函数共存,但调用方法不同
调用基类函数:基类名 :: + 函数名 CForm::Load();
调用派生类函数:直接调用 Load();
重载、重定义(隐藏的一种)、多态关系:
共同点:函数名相同
①、重载:是在同一个类里函数名相同,即不是上下级的函数名相同
②、隐藏:在基类和派生类之间,函数名相同,参数不同
重定义:在基类和派生类之间,函数名相同,参数相同
③、多态: 在重定义的前提下,其基类的重定义函数是虚函数
6、类指针
基类指针(引用&) 可指向 派生类或基类 ----> 安全
派生类指针(引用&) 指向 基类不安全可能会访问越界---->不安全
一般使用基类指针或引用来指向派生类。
但在编译器位置指向的是基类还是派生类,调用其函数都是基类的函数,此为静态绑定。要实现动态绑定,即不知对象是基类还是派生类都能调用到该对象下的函数,这个过程就叫做多态,需要虚函数来实现。
7、虚继承与虚基类
在一个A类被B、C继承后,然后D继承B、C, 在D中就会纯在二义性,有相同继承来自A类的成员或方法。D在使用这些时会出现二义性。
解决方法:使用虚继承来解决对继承的问题。
虚继承的基类就是虚基类。
使用方法:
class A: virtual 继承方式 B
①、需要使用初始化列表来初始基类需要的参数
②、虚基类的构造函数只会调用一次, 即被最底层的派生类的构造函数调用,
而最底派生类的其他基类对虚基类的构造函数的调用被自动忽略.
二、多态
#include <iostream>
#include <stdio.h>
using namespace std;
#define PureVirtualFunc 0
class CBase
{
public:
// 重定义
void func()
{
cout << "CBase::func" << endl;
}
// 虚函数
virtual void virtualFunc()
{
cout << "CBase::virtualFunc" << endl;
}
#if PureVirtualFunc
virtual void PurevirtualFunc() = 0;
#endif
};
class CSub1 : public CBase
{
public:
void func()
{
cout << "CSub1::func" << endl;
}
void virtualFunc()
{
cout << "CSub1::virtualFunc" << endl;
}
virtual void PurevirtualFunc()
{
cout << "CSub1::PurevirtualFunc" << endl;
}
};
class CSub2 : public CSub1
{
public:
void virtualFunc()
{
cout << "CSub2::virtualFunc" << endl;
}
virtual void PurevirtualFunc()
{
cout << "CSub2::PurevirtualFunc" << endl;
}
};
int main()
{
#if PureVirtualFunc
CBase *pb;
CSub1 sObj;
pb = &sObj;
pb->func();
pb->virtualFunc();
pb->PurevirtualFunc();
#else
CBase bObj, *pb;
CSub1 sObj, *ps;
// 常规用法
pb = &bObj;
ps = &sObj;
bObj.func();
sObj.func();
// 统一用基类指针管理
pb = &bObj;
pb->func();
pb->virtualFunc();
// 引用引发多态
pb = &sObj;
pb->func();
pb->virtualFunc();
// 引用引发多态
CBase &b_alias = sObj;
b_alias.virtualFunc();
// 基类对象无法引发多态
CBase bObj2 = sObj;
bObj2.virtualFunc();
#endif
return 0;
}
虚函数与纯虚函数
1.虚函数
虚函数的概念:在基类中冠以关键字 virtual 的成员函数
虚函数的定义:
virtual 函数类型 函数名称(参数列表);
如果一个函数在基类中被声明为虚函数,则他在所有派生类中都是虚函数
(包括重定义函数)
只有通过基类指针或引用调用虚函数才能引发动态绑定
2.多态
// 引用引发多态
pb = &sObj;
pb->func();
pb->virtualFunc();
// 引用引发多态
CBase &b_alias = sObj;
b_alias.virtualFunc();
通过基类指针或引用,使得可以调用到派生类的同名函数
3、虚析构函数
①、析构函数可以声明为虚函数
delete 基类指针;
程序会根据基类指针指向的对象的类型确定要调用的析构函数
基类的析构函数为虚函数,所有派生类的析构函数都是虚函数
②、构造函数不得是虚函数
如果要操作具有继承关系的类的动态对象,最好使用虚析构函数。特别是在析构
函数需要完成一些有意义的操作——比如释放内存时
4 、纯虚函数、抽象类
①、纯虚函数
在基类中不能给出有意义的虚函数定义,这时可以把它说明成纯虚函数,把它的
定义留给派生类来做
定义纯虚函数:
class 类名{
virtual 返回值类型 函数名(参数表) = 0;
};
纯虚函数不需要实现
eg:
virtual void PurevirtualFunc() = 0;
②、抽象类(包含纯虚函数)
a、如果一个类中至少有一个纯虚函数,那么这个类被成为抽象类(abstract class)
b、抽象类存在的意义是作为其它类的基类,也叫抽象基类
c、抽象类不能用于直接创建对象实例,可以声明抽象类的指针和引用
d、可使用指向抽象类的指针支持运行时多态性
e、派生类中必须重载基类中的纯虚函数,否则它仍将被看作一个抽象类