类继承
类继承这种方式使我们的代码更为简洁和高效
先回顾一下三种访问控制关键字
- public: public所修饰的成员变量可以在类内(包括子类)和类外进行访问。
- protected: protected所修饰的成员变量只能在类内(包括子类)进行访问。
- private: private所修饰的成员变量只能在类内访问(不包括子类)。
继承的方式
- 公有继承
class 派生类(子类)名 :public 基类(父类)名{
派生类(子类)新增成员;
};
公有继承对基类成员访问权限的控制:
基类成员 | 派生类成员 |
---|---|
public成员 | public成员 |
protected成员 | protected成员 |
private成员 | 不可访问成员 |
不可访问成员 | 不可访问成员 |
- 私有继承
class 派生类(子类)名 : private 基类(父类)名{
派生类(子类)新增成员;
};
私有继承对基类成员访问权限的控制:
基类成员 | 派生类成员 |
---|---|
public成员 | private成员 |
protected成员 | private成员 |
private成员 | 不可访问成员 |
不可访问成员 | 不可访问成员 |
- 保护继承
class 派生类(子类)名 : protected 基类(父类)名{
派生类(子类)新增成员;
};
保护继承对基类成员访问权限的控制:
基类成员 | 派生类成员 |
---|---|
public成员 | protected成员 |
protected成员 | protected成员 |
private成员 | 不可访问成员 |
不可访问成员 | 不可访问成员 |
类继承中的一些细节
- 派生类(子类)构造函数传参的时候会包含基类的成员和派生类的成员
- 并且基类的成员一定要使用初始化表的形式进行初始化,子类成员没有要求。
如:
class A{
private:
int x,y;
public:
A(int ax,int ay){
x = ax;
y = ay;
}
};
class B : public A{
private:
int z;
public:
B(int bx,int by,int bz):A(bx,by),z(bz){}
};
- 如果基类中有带参数的构造函数,即使派生类中不需要初始化新的数据成员,派生类中也必须定义构造函数。
- 如果基类中定义了多个构造函数,那么派生类中也要相应地定义多个构造函数。
如下:
基类:
class Student{
public:
Student(const string& name):strName(name),cSex('M'),nAge(18){}
Student(const string& name,char sex):strName(name),cSex(sex),nAge(18){}
Student(const string& name,char sex,int age):strName(name),cSex(sex),nAge(age){}
private:
string strName;
char cSex;
int nAge;
};
派生类:
class Athlete : public Student {
public:
Athlete(const string& name):Student(name){}
Athlete(const string& name,char sex):Student(name,sex){}
Athlete(const string& name,char sex,int age):Student(name,sex,age){}
void ShowInfo(){cout<<"Hello"<<endl;}
};
可见,在派生类中也有和基类相对应的构造函数,但是,派生类中并没有新增的数据成员!!这样看来,派生类中的构造函数是不是很复杂?
所以,在C++11中,提供了继承基类构造函数的方法,使得派生类中可以直接使用基类的构造函数。(不经常使用,了解即可)
在这种方法中,只需要在派生类中用using进行声明,把基类的构造函数声明到派生类里面即可:
修改派生类Athlete
class Athlete : public Student {
public:
using Student::Student;
void ShowInfo(){cout<<"Hello"<<endl;}
};
using Student::Student这句可以理解为引入了Student类中的Student函数。
但是,如果派生类中有新增的数据成员,并且需要初始化,那么就必须给派生类定义构造函数,并且显式调用基类的构造函数,初始化基类数据成员。
派生类的析构函数
- 当回收派生类对象新增成员所占用的内存空间时,必须为派生类定义析构函数,如指针 就需要用到析构函数。
创建派生类对象时先调用基类的构造函数,后调用派生类的构造函数。
当派生类对象生命期结束时,先调用派生类的析构函数,后调用基类的析构函数。
隐藏基类成员
- 在派生类中重新定义定义与基类同名成员的时候,派生类中的成员隐藏基类的同名成员,派生类中对这些同名成员赋予了新的意义和功能。
- 如果我们要调用基类中的函数而不是使用派生类中的同名函数的时候,就要写成下面这种形式:
下面例子中,派生类中Test函数将基类中的Test函数隐藏。
class Student{
public:
void Test(int);//基类中有一个Test,里面参数是int类型
};
class Doctor : public Student{
public:
void Test();//派生类中也有一个Test函数,但没有参数。
};
int main(){
Doctor d;//创建一个Doctor类的成员,这里没有写构造函数。
d.Test();//调用派生类中的Test函数
这里派生类中的Test把基类中的Test隐藏,要通过下面这种方法调用!
不能理解为函数重载,不一样!!!!
d.Student::Test(3);//调用基类中的Test函数
};
多重继承
如果派生类有两个或多个直接基类,这种继承就成为多重继承(多继承)。
class 派生类名: 继承方式 基类1,继承方式 基类2,.... 继承方式 基类 n{
派生类新增成员;
};
其中,数据成员的排列规则要按照派生类定义中基类的顺序将基类成员依次排列,然后存放派生类中的新增成员,这里成员的存储位置相邻。
多重继承引起的二义性
有两种方式消除二义性:
- 用更多的属性区别他们。可以用类名和域运算符“::”进行限定
cout<< B::x <<endl;
- 在派生类成员中改写有歧义的同名成员
class A{
protected:
int x;
public:
A(int x);
};
class B{
protected:
int x;
public:
B(int x);
};
class C : public A, public B{
protected:
int x;
public:
void showInfo(){
cout<<x<<endl;//这里的x容易产生二义性,所以我们在类C中定义了新的int类型的 x;
}
};
虚基类
在派生类中保留了间接共同基类的多份同名成员,会占用较多存储空间,增加访问难度,容易出现二义性错误,对此,C++提供了虚基类的方法,使得派生类在继承间接共同基类的时候只保留一份成员。
什么是间接共同基类呢?
举个例子:
B、C都继承了A,D继承了B和C,那么A就是D的间接共同基类。
**虚基类的定义格式 **
class 派生类名 : virtual 继承方式 基类名{
派生类新增成员;
};
那么,继上面的例子 ,虚基类定义在类B 和 类C上即可,类D上写不写都可以。
注意:最后定义的派生类,即类的层次结构中最底层的派生类完成虚基类构造函数的调用,该派生类的其他基类对虚基类的构造函数的调用将被忽略。