一个新类从已有的类那里获得其已有特性,这种现象称为类的继承。从已有的类(父类)产生一个新的子类,称为类的派生
派生类没有选择地接收基类全部成员(除构造函数和析构函数外),并可以对成员作必要的增加或调整。所以派生类的成员包括两部分:
- 从基类继承过来的成员
- 自己增加的成员
单继承: 指一个派生类只从一个基类派生。
多重继承:指一个派生类有两个或多个基类。称为直接基类,间接基类
声明派生类的一般形式为:
class 派生类名:[ 继承方式 ] 基类名
{
派生类新增加的成员
};
通过指定继承方式来调整改变基类成员在派生类中的访问属性,包括:
public(公用的):都不变
private(私有的):全变私有 // 默认的继承方式
protected(受保护的):公有变保护
基类成员在派生类中的访问属性
基类中的成员 | 在公用派生类中的访问属性 | 在私有派生类中的访问属性 | 在保护派生类中的访问属性 |
---|---|---|---|
私有成员 | 不可访问 | 不可访问 | 不可访问 |
公共成员 | 公用 | 私有 | 保护 |
保护成员 | 保护 | 私有 | 保护 |
派生类中的成员 | 在派生类中 | 在派生类外部 | 在下层公用派生类中 |
---|---|---|---|
派生类中访问属性为公用的成员 | 可以 | 可以 | 可以 |
派生类中访问属性为受保护的成员 | 可以 | 不可以 | 可以 |
派生类中访问属性为私有的成员 | 可以 | 不可以 | 不可以 |
在派生类中不可访问的成员(即基类私有成员等) | 不可以 | 不可以 | 不可以 |
派生类的构造函数
基类的构造函数不能被派生类继承。
派生类对象创建时,执行构造函数的顺序
- 调用基类构造函数,初始化 基类 成员;
- 初始化 子对象(即派生类中包含的其他类类型成员);
- 初始化 派生类的成员
在派生类对象释放时,析构函数的执行顺序(反过来了)
1、先执行派生类析构函数
2、再执行其基类的析构函数
派生类构造函数一般形式:
派生类构造函数名(总参数列表):基类构造函数名(参数列表),子对象名(参数列表)
{ 派生类中新增数据成员初始化语句}
#include <iostream>
using namespace std;
// 定义两个基类
class Base1
{
public:
Base1()
{
cout << "+ Constructor of Base1\n";
}
~Base1()
{
cout << "- Destructor of Base1\n";
}
};
class Base2
{
public:
Base2()
{
cout << "+ Constructor of Base2\n";
}
~Base2()
{
cout << "- Destructor of Base2\n";
}
};
// 定义两个子对象类
class Member1
{
public:
Member1()
{
cout << "+ Constructor of Member1\n";
}
~Member1()
{
cout << "- Destructor of Member1\n";
}
};
class Member2
{
public:
Member2()
{
cout << "+ Constructor of Member2\n";
}
~Member2()
{
cout << "- Destructor of Member2\n";
}
};
// 定义派生类
class Derived: public Base2, public Base1
// 先继承Base2,再继承Base1
{
public:
// 先定义一个Member2的对象,再定义一个Member1的对象
Member2 m2;
Member1 m1;
// 定义派生类的构造函数
Derived(): m1(), m2(), Base1(), Base2()
// 与定义时相反,按照Member1、Member2、Base1、Base2初始化对象
{
cout << "+ Constructor of Derived\n";
}
// 定义派生类的析构函数
~Derived()
{
cout << "- Destructor of Derived\n";
}
};
int main()
{
Derived d;
return 0;
}
1. 初始化基类成员,先调用基类的构造函数
+ Constructor of Base2
+ Constructor of Base1
2. 初始化子对象成员,再调用子对象类(成员变量)的构造函数
+ Constructor of Member2
+ Constructor of Member1
3. 初始化派生类成员,最后调用派生类的构造函数
+ Constructor of Derived
- Destructor of Derived
- Destructor of Member1
- Destructor of Member2
- Destructor of Base1
- Destructor of Base2
情况 | 示例 | 用谁? | 说明 |
---|---|---|---|
初始化基类 | Base(参数) | 用类名 | 必须显式调用哪个基类构造函数 |
初始化成员变量 | m(参数) | 用变量名 | 成员对象或变量名,用哪个变量就写哪个 |
初始化基类必须显示调用哪个基类的构造函数,所以用类名(x,x,x);初始化本派生类中的变量和成员对象,用变量名(x,x,x)
多重继承派生类的构造函数的形式
基本同于单继承,只是在初始化时包含多个基类构造函数。如,
派生类构造函数名(总参数表列):
基类1构造函数名(参数表列),
基类2构造函数名(参数表列),
基类3构造函数名(参数表列), ……
{派生类中新增数据成员初始化语句}
声明一个教师Teacher类和一个学生类Student,用多重继承的方式声明一个研究生Graduate派生类。教师类中包括数据成员name(姓名)、age(年龄)、title(职称)。学生类中包括数据成员name1(姓名)、sex(性别)、score(成绩)。在定义派生类对象时给出初始化的数据,然后输出这些数据。
#include <iostream>
#include <string>
using namespace std;
// 教师类
class Teacher {
public:
// 构造函数
Teacher(string nam, int a, string t) {
name = nam;
age = a;
title = t;
}
// 显示教师信息
void display() {
cout << "name: " << name << endl;
cout << "age: " << age << endl;
cout << "title: " << title << endl;
}
protected:
string name;
int age;
string title;
};
// 学生类
class Student {
public:
// 构造函数
Student(string nam, char s, float sco) {
name1 = nam;
sex = s;
score = sco;
}
// 显示学生信息
void display1() {
cout << "name: " << name1 << endl;
cout << "sex: " << sex << endl;
cout << "score: " << score << endl;
}
protected:
string name1;
char sex;
float score;
};
// 研究生类,多重继承自 Teacher 和 Student
class Graduate : public Teacher, public Student {
public:
// 构造函数:调用 Teacher 和 Student 的构造函数,以及初始化私有成员 wage
Graduate(string nam, int a, char s, string t, float sco, float w)
: Teacher(nam, a, t), Student(nam, s, sco), wage(w) { }
// 显示研究生所有信息
void show() {
cout << "name: " << name << endl;
cout << "age: " << age << endl;
...
}
private:
float wage;
};
// 主函数
int main() {
Graduate grad1("Wang-li", 24, 'f', "assistant", 89.5, 1234.5);
grad1.show();
return 0;
}
}
多重继承引起的二义性问题
1.两个基类有同名成员
class A
{ public:
int a; // A类中数据成员a
void display(); // A类中成员函数display()
};
class B
{ public:
int a; // B类中数据成员a
void display(); }; // B类中成员函数display()
class C:public A,public B
{ public:
int b;
void show();
};
如果在main函数中定义C类对象c1,并调用数据成员a和成员函数display( ):
C c1;
c1.a=3;
c1.display( );
则会出错,因为编译系统无法判断a是哪一个基类(类A或类B)的数据成员。可以用基类名来限定
c1.A::a =3; // 引用c1对象中的基类A的数据成员a
c1.A::display( );// 调用c1对象中的基类A的成员函数display( )
如果在派生类C中通过派生类成员函数show( )访问基类A的display( )和a,可以不必写对象名,系统默认为当前对象,写成下列形式即可。
A::a =3; // this->A::a = 3;指当前对象
A::display( );
2.两个基类和派生类三者都有同名成员。
将上面的C类声明改为:
class C:public A,public B
{ public:
int a; // 改b变量为a
void display( ); // 改show()变量为display()
};
则能通过编译,因为此时派生类新增加的同名成员覆盖了基类(类A或类B)中的同名成员。
要在派生类外访问基类A的成员,应指明作用域A。
3.类A和类B是从同一个基类派生的
class N
{ public:
int a;
void display()
{ cout<<a<<endl;}
};
class A:public N
{ public:
int a1;
};
class B:public N
{ public:
int a2;
};
class C:public A,public B
{ public:
int a3;
void show();
};
通过类N的直接派生类名来指出要访问的是类N的哪一个 派生类中的基类成员。
c1. A::a=3; // 要访问的是类N的派生类A中的基类成员
写成下列方法是错误的,因为存在二义性
c1.a=3; c1.display(); // 错误
c1. N::a=3; c1. N::display(); // 错误
派生类的析构函数
调用析构函数的顺序:
- 先执行派生类自己的析构函数,对派生类新增加的成员进行清理
- 再调用子对象的析构函数,对子对象进行清理
- 最后调用基类的析构函数,对基类进行清理
需要通过派生类的析构函数去调用基类的析构函数
虚基类(Virtual Base Class)
作用:使得在继承间接共同基类时只保留一份成员
class 派生类名:virtual 继承方式 基类名
class B:virtual public A // 声明类B是类A的公用派生类,A是B的虚基类
为了保证虚基类在派生类中只继承一次,应当在该虚基类的所有直接派生类中声明为虚基类。虚拟继承只有在每一层都声明 virtual 才生效;否则最下层类仍然会重复继承基类。
用关键字 virtual
把 A
声明为虚基类,让 B
和 C
共享这份 A
,从而 D
只有一份 A
class A { public: int a; };
class B : virtual public A { };
class C : virtual public A { };
class D : public B, public C { };
现在,B
和 C
不再各自复制一份 A
,而是都指向 共享的唯一那一份 A
。非虚拟继承时:D
对象中内存布局是 → A(B)
+ A(C)
+ 自己;虚拟继承时:D
对象中内存布局是 → A
(一份共享) + B
+ C
+ 自己
虚基类的初始化:通过构造函数的初始化表对虚基类进行初始化
以前,在派生类中只需负责对直接基类初始化,再由其直接基类负责对间接基类初始化
现在,由于虚基类 A
只保留 一份实例;所以必须由最底层类派生类负责初始化 A
;在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。
在例5.8的基础上,在Teacher类和Student类之上增加一个共同的基类Person,如图5.25所示。 作为人员的一些基本数据都放在类Person中,在Teacher类和Student类中再增加一些必要的数据。
#include <iostream>
#include <string>
using namespace std;
class Person //定义公共基类Person
{public:
Person(char *nam,char s,int a) //构造函数
{ strcpy(name,nam); sex=s; age=a; }
protected: //保护成员
char name[20];
char sex;
int age;
};
//定义类Teacher
class Teacher: virtual public Person // 声明Person为公用继承的虚基类
{public:
Teacher( char *nam,char s,int a ,char *t):
Person(nam,s,a) // 构造函数
{
strcpy (title, t);
}
protected: // 保护成员
char title[10]; // 职称
};
//定义类Student
class Student: virtual public Person // 声明Person为公用继承的虚基类
{ public:
Student( char *nam,char s,int a ,float sco):
Person(nam,s,a),score(sco){ } // 构造函数初始化表
protected: // 保护成员
float score; // 成绩
};
//定义多重继承的派生类Graduate
class Graduate :public Teacher,public Student
//声明Teacher和Student类为公用继承的直接基类{ public:
Graduate( char *nam,char s,int a, char *t,float sco,float w): Person(nam,s,a) ,Teacher(nam,s,a,t),Student(nam,s,a,sco),wage(w){}
// 构造函数初始化表
void show( ) // 输出研究生的有关数据
{ cout<<"name:"<<name<<endl; cout<<"age:"<<age<<endl;
cout<<“sex:”<<sex<<endl; cout<<"score:"<<score<<endl;
cout<<"title:"<<title<<endl; cout<<"wages:"<<wage<<endl;
}
private:
float wage; // 工资
};
int main( )
{ Graduate grad1("Wang-li",'f',24,"assistant",89.5,1234.5);
grad1.show( );
return 0;
}
在Graduate类中只保留一份基类的成员,因此可以用Graduate类中的show函数引用Graduate类对象中的公共基类Person的数据成员name、sex和age的值,不需要加类名和域运算符(::),不会产生二义性。
注意如果不是虚基类Graduate类的构造函数应该去掉 Person(nam,s,a)
,改由各个基类自己初始化自己的部分。
Graduate(char *nam, char s, int a, char *t, float sco, float w): Teacher(nam, s, a, t), Student(nam, s, a, sco), wage(w){ }
基类与派生类的转换
因为派生类中包含从基类继承的成员,所以
- 可以将派生类的值赋值给基类对象;
- 在用到基类对象的时候,可以用其子类对象代替。
派生类对象可以向基类对象赋值/初始化
A a1; // 定义基类A的对象a1
B b1; // 定义A的公用派生类B的对象b1
a1=b1; // 用派生类B对象b1对基类对象a1赋值
- 赋值时,只是对基类中的数据成员赋值,舍弃派生类自己的成员。
- 对成员函数不存在赋值问题
A a1; // 定义基类A的对象a1
B b1; // 定义A的公用派生类B的对象b1
A& r =a1; // 正确,定义基类A对象的引用变量r,并用a1对其初始化
或 A& r=b1; // 正确,定义基类A对象的引用变量r,
// 并用派生类B对象b1对其初始化
r 只是b1中基类部分的别名,r与b1中基类部分共享同一段存储单元。r 与b1具有相同的起始地址
总结来讲就是基类出现的地方都可以用派生类对象替换,此外还有函数参数、
基类对象的指针变量也可以指向派生类对象…
Student stud1(1001,”Li”,87.5);
Graduate grad1(2001,”Wang”,98.5,563.5);
Student *pt=&stud1;
pt->display();
pt=&grad1;