继承与派生
(一)类的三种访问(控制)方式
- private(class默认访问方式):仅定义该成员的类内部可以访问
- protected:派生类可以访问,类外部不可以访问
- public(struct默认访问方式):均可访问
(二)派生类的继承方式与访问控制:
派生类可以通过访问方式对基类成员的访问方式进行改造:
1)private(私有继承):
只有定义该成员的类内部可以访问,不允许派生类访问(不论是什么继承方式)。
2)protected(保护继承):
基类除了private成员,在派生类中的访问方式均为protected。
3)public(公有继承):
基类除了private成员,在派生类中的访问控制与基类一致。
(三)派生类生成的过程:
1)吸收基类成员
派生类会除了基类的构造函数、析构函数以外的所有数据成员和函数成员。
2)改造基类成员
通过访问方式,数据和函数成员的覆盖对基类成员进行改造。
3)添加新成员
尤其是添加构造函数和析构函数
(四)派生类的构造和析构函数
- 派生类没有继承基类的构造和析构函数
- 派生类必须对新增的成员初始化,还需要初始化基类的成员
1)派生类的构造函数定义格式:
派生类名(参数表):需要初始化的基类名(参数表),新增成员初始化列表 { 构造函数体 }
【PS】:若没有在派生类构造函数中显示初始化基类,则默认调用基类的无参构造函数(此时需基类有无参构造函数)
2)派生类构造函数的执行顺序:
1、调用基类的构造函数(按继承时说明从左到右的顺序,与初始化列表无关)
2、(如果有的话)调用子对象的构造函数(按类中说明的顺序)
3、执行派生类构造函数的函数体
执行析构函数的顺序与此正好相反
代码示例:
#include <iostream>
using namespace std;
class Base {
private:
int bi; //私有成员
public:
Base() {
bi=0;
cout<<"执行基类Base的构造函数\n";
}
~Base() {
cout<<"执行基类Base["<<bi<<"]的析构函数\n";
}
};
class Base1 {
private:
int bi1; //私有成员
public:
Base1(int x) {
bi1=x;
cout<<"执行基类Base1的构造函数\n";
}
~Base1() {
cout<<"执行基类Base1["<<bi1<<"]的析构函数\n";
}
};
class Base2 {
private:
int bi2; //私有成员
public:
Base2(int x) {
bi2=x;
cout<<"执行基类Base2的构造函数\n";
}
~Base2() {
cout<<"执行基类Base2["<<bi2<<"]的析构函数\n";
}
};
class Myclass {
private:
int mi; //私有成员
public:
Myclass(int x) {
mi=x;
cout<<"执行普通类Myclass的构造函数\n";
}
~Myclass() {
cout<<"执行普通类Myclass["<<mi<<"]的析构函数\n";
}
};
class Derived:public Base2,Base1,protected Base
{//注意:Base1(缺省)为私有继承,而不是公有继承!
private:
int di; //普通私有成员
Myclass obj; //私有(对象)成员
public:
Derived(int a,int b,int c,int d)
:obj(b),Base1(c),Base2(d) {
//Base没有在初始化表列出,所以自动调用Base的无参构造函数
//如果没有,Base2(d)将导致错误(因为类Base2没有无参构造函数)
di=a;
cout<<"执行派生类Derived的构造函数\n";
}
~Derived() {
cout<<"执行派生类Derived["<<di<<"]的析构函数\n";
}
};
int main()
{
Derived dObj(1,2,3,4);
cout<<endl;
}
/*
该程序的输出为:
执行基类Base2的构造函数
执行基类Base1的构造函数
执行基类Base的构造函数
执行普通类Myclass的构造函数
执行派生类Derived的构造函数
执行派生类Derived[1]的析构函数
执行普通类Myclass[2]的析构函数
执行基类Base[0]的析构函数
执行基类Base1[3]的析构函数
执行基类Base2[4]的析构函数
*/
(五)派生类的拷贝构造函数
//派生类的拷贝构造函数的一般格式如下:
Derived(const Derived &d):Base(d)
{
//用派生类的引用去初始化基类的引用合法! //正常的派生类的拷贝构造函数体
}//Base:基类,Derived:派生类
(六)基类和派生类指针
-
派生类的成员函数将覆盖基类的所有同名成员函数(含重载的同名函数)。
【注意1】:覆盖不是重载,可以通过**Base::show()**来限定。
【注意2】:若是通过基类指针指向派生类,调用同名函数,则执行的基类的函数。(基类指针不知道派生类新增成员的存在)。
-
这也引出了一个问题——【为什么要有虚函数?】:
正是由于【注意点2】所说,基类指针只能调用基类成员函数,而不能调用派生类中的新增成员函数,如果使用虚函数,就能够实现用基类指针访问派生类的成员函数,基于这一点,派生类中的这个成员函数和基类的虚函数的形式要完全相同,并且在派生类中实现重写(一个接口多种方法)。
-
-
不能通过基类的对象(或指针)访问派生类新增的(共有)成员。
【例1】:
#include <iostream> using namespace std; class Base { public: void print(int a=0) { cout<<"Print in Base"<<endl; } void showBase() { cout<<"Show in Base"<<endl; } }; class Derived:public Base { public: void print() { cout<<"Print in Derived"<<endl; } void showDerived() { cout<<"Show in Derived"<<endl; } }; int main() { Base *bp,bObj; Derived *dp,dObj; //分别用基类和派生类的对象调用各自的成员函数 bObj.print(); //基类的print被派生类的覆盖 dObj.print(); //dObj.print(1); 会报错(No Matching Function),派生类的同名函数会覆盖基类的函数(包括重载的函数) dObj.showBase(); //在派生类中找不到,则调用基类的函数show //用基类指针指向派生类 bp=&dObj; bp->print(); //执行基类的print,基类指针并不知道派生类中的新增函数 //bp->showDerived(); 报错,理由同上、 } //输出结果如下: /* Print in Base Print in Derived Show in Base Print in Base */
-
派生类指针不能直接指向基类对象(编译报错),否则可能会使用派生类中的新增成员(在基类中是未定义的),即使是强制转化成基类指针也是调用派生类的函数。
总之,派生类指针无法访问基类成员。
-
基类指针指向派生类是安全的,但是只能用来调用基类的成员。
若要访问派生类的新增成员,需要进行强制转化。
((Derived *)bp)->print(); //调用的是派生的print(强制转化后,相当于是一个派生类指针,可以访问派生类成员函数) //外面的括号不能省略!!(优先级问题!!)
【例2】派生类和基类指针的相互转化
//派生类和基类指针的相互转化
#include <iostream>
using namespace std;
class Base
{
public:
void print()
{
cout<<"Print in Base"<<endl;
}
};
class Derived:public Base
{
public:
void print()
{
cout<<"Print in Derived"<<endl;
}
void show()
{
cout<<"Show in Derived"<<endl;
}
};
int main()
{
Base *bp,bObj;
Derived *dp,dObj;
//基类指针指向派生类:
bp=&dObj;
dp=(Derived *)bp; //将基类指针强制转化为派生类指针
dp->print();
//派生类指针指向基类---报错
// dp=&bObj;
//将基类地址赋给派生类需要进行强制转化!!
dp=(Derived *)&bObj;
dp->print();
dp->show();
}
/*
Print in Derived
Print in Derived
Show in Derived
*/
(七)虚基类
1)概念与声明方式
虚基类(在访问方式前加virtual)
在多层次继承中,若直接基类派生自同一个间接基类,会有两份的间接基类成员。
在虚基类机制下,虽然被一个派生类间接地多次继承,但派生类只继承一份该基类的成员。
2)虚基类下构造函数的执行顺序:
1、直接基类中的虚基类的构造函数在非虚基类之前调用
2、多个虚基类按继承时说明从左到右的顺序执行
3、若虚基类由非虚基类派生而来,则仍先调用基类构造函数,再按派生类中的构造函数的执行顺序调用
4、执行派生类构造函数的函数体
执行析构函数的顺序与此正好相反
3)初始化派生类
- 对于非虚基类,在派生类的构造函数中初始化间接基类是不允许的;
- 而对于虚基类,则必须在派生类中对虚基类初始化。
例如person是teacher和student的虚基类,则在派生类assistant的构造函数一定要初始化虚基类。
若person不是teacher和student的虚基类,则不允许派生类assistant初始化间接基类person。
注意:虚基类并不是在声明基类时声明的,而是在声明派生类是,指定继承方式时声明的。因为一个基类可以在生成一个派生类作为虚基类,而在生成另一个派生类时不作为虚基类。