继承与派生(二)
目录
1. 派生类成员的访问属性
1.1 公用继承
基类的公有成员和保护成员在派生类中保持原有访问属性;私有成员仍为基类私有,只有基类的成员函数可以引用它,不能被派生类成员函数引用,因此基类私有成员成为派生类中的不可访问的成员。表1给出了公用基类的成员在派生类中的访问属性:
在基类的访问属性 | 继承方式 | 在派生类的访问属性 |
private | public | 不可访问 |
public | public | public |
protected | public | protected |
下面给出一个公用继承的例1:
#include<iostream>
#include<string>
using namespace std;
//访问公有基类的成员
class Student{ //声明基类
public: //基类公用成员
void get_value() { //输入基类数据的成员函数
cin >> num >> name >> sex;
}
void display() { //输出基类数据的成员函数
cout << " num:" << num << endl;
cout << " name:" << name << endl;
cout << " sex:" << sex << endl;
}
private: //基类私有成员
int num;
string name;
char sex;
};
class Student1 :public Student { //以public方式声明派生类Student1
public:
void get_value_1() { cin >> age >> addr; } //输入派生类数据
void display_1() {
cout << " age:" << age << endl; //引用派生类的私有成员,正确
cout << " address:" << addr << endl; //引用派生类的私有成员,正确
}
private:
int age;
string addr;
};
//访问公有基类的成员 分别调用基类的display函数和派生类中的display_1函数 先后输出5个数据
int main11_1() {
Student1 stud; //定义派生类Student1的对象stud
stud.get_value();
stud.get_value_1();
stud.display();
stud.display_1();
return 0;
}
1.2 私有继承
基类的公有成员和保护成员在派生类中成了私有成员,即派生类的成员函数可以访问它们,而在派生类外不能访问它们。基类的私有成员仍为基类私有,在派生类中成为不可访问的成员,只有基类的成员函数可以引用它们。表2给出了私有基类的成员在派生类中的访问属性:
在基类的访问属性 | 继承方式 | 在派生类的访问属性 |
private | private | 不可访问 |
public | private | private |
protected | private | private |
下面给出一个私有继承的例2:
//私有继承方式
class Student{ //声明基类
public: //基类公用成员
void get_value() { //输入基类数据的成员函数
cin >> num >> name >> sex;
}
void display() { //输出基类数据的成员函数
cout << " num:" << num << endl;
cout << " name:" << name << endl;
cout << " sex:" << sex << endl;
}
private: //基类私有成员
int num;
string name;
char sex;
};
class Student2 :private Student { //以private方式声明派生类Student2
public:
void get_value_1()
{
get_value(); //调用基类的公用函数输入基类的3个数据
cin >> age >> addr; } //输入派生类数据
void display_1() {
display(); //调用基类的公用成员函数输出3个数据成员的值
cout << " age:" << age << endl; //引用派生类的私有成员,正确
cout << " address:" << addr << endl; //引用派生类的私有成员,正确
}
private:
int age;
string addr;
};
int main() {
Student2 stud; //定义派生类Student1的对象stud
stud.get_value_1();
stud.display_1();
return 0;
}
1.3 保护成员和保护继承
基类的公有成员和保护成员在派生类中成了保护成员,基类的私有成员仍为基类私有。保护成员的意思是,不能被外界引用,但可以被派生类的成员引用。这样基类原有的公有成员被保护起来,类外不能任意访问。表3给出了保护基类的成员在派生类中的访问属性,表4给出了派生类中的成员的访问属性
在基类的访问属性 | 继承方式 | 在派生类的访问属性 |
private | protected | 不可访问 |
public | protected | protected |
protected | protected | protected |
派生类中访问属性 | 在派生类中 | 在派生外部中 | 在下一层公用派生类中 |
公用 | 可以 | 可以 | 可以 |
保护 | 可以 | 不可以 | 可以 |
私有 | 可以 | 不可以 | 不可以 |
不可访问 | 不可以 | 不可以 | 不可以 |
下面给出一个保护继承的例3:
//在派生类中引用保护成员
class Student3 { //声明基类
public: //基类无公用成员
protected: //基类保护成员
int num;
string name;
char sex;
};
class Student31 :protected Student3 { //用protected方式声明派生类Student31
public:
void get_value1(); //派生类公用成员函数
void display1(); //派生类公用成员函数
private:
int age; //派生类私有数据成员
string addr; //派生类私有数据成员
};
void Student31::get_value1() { //定义派生类公用成员函数
cin >> num >> name >> sex; //输入保护基类数据成员
cin >> age >> addr; //输入派生类数据成员
}
void Student31::display1() { //定义派生类公用成员函数
cout << " num:" << num << endl; //引用基类的保护成员
cout << " name:" << name << endl; //引用基类的保护成员
cout << " sex:" << sex << endl; //引用基类的保护成员
cout << " age:" << age << endl; //引用派生类的私有成员
cout << " address:" << addr << endl; //引用派生类的私有成员
}
//在派生类中引用保护成员
int main() {
Student31 stud1; //stud1是派生类Student31类的对象
stud1.get_value1(); //get_value1是派生类中的公用成员函数,输入数据
stud1.display1(); //display1是派生类中的公用成员函数,输出数据
return 0;
}
1.4 多级派生时的访问属性
如果派生关系为C->B->A,类A为基类,类B是类A的派生类,类C是类B的派生类,那么类C也是类A的派生类。
类B是类A的直接派生类,类C是类A的间接派生类。类A是类B的直接基类,类A是类C的间接基类。
如果在多级派生时都采用公有继承方式,那么直到最后一级派生类都能访问基类的公用成员和保护成员。如果采用私有继承方式,经过若干次派生后,基类的所有的成员已经变成不可访问的了。如果采用保护继承方式,在派生类外是无法访问派生类中的(来自基类的)任何成员的。
下面给出一个多级派生的例子:
class A { //基类
public:
int i;
protected:
void f1();
int j;
private:
int k;
};
class B:public A { //public 派生类
public:
void f2();
protected:
void f3();
private:
int m;
};
class C :protected B{ //protected 派生类
public:
void f4();
private:
int n;
};
2. 派生类的构造函数和析构函数
构造函数的主要作用是对数据成员初始化,在设计派生类的构造函数时,不仅要考虑派生类所增加的数据成员的初始化,还应当考虑基类的数据成员初始化。因此,希望在执行派生类的构造函数时,调用基类的构造函数。
2.1 简单的派生类的构造函数
任何派生类都包含基类的成员,简单的派生类只有一个基类,而且只有一级派生(只有直接派生类,没有间接派生类),在派生类的数据成员中不包含基类的对象(即子对象)。派生类构造函数一般形式为:
派生类构造函数名(总参数表):基类构造函数名(参数表)
{派生类中新增数据成员初始化语句}
Student1(int n, string nam, char s, int a, string ad) :Student(n, nam, s)
//定义派生类构造函数
{
age = a; //在函数体中只对派生类新增的数据成员初始化
addr = ad;
}
冒号“:”前面部分是派生类构造函数的主干,它的总参数表中包括基类构造函数所需的参数和对派生类新增的数据成员初始化所需的参数。冒号“:”后面部分是要调用的基类构造函数及其参数。从上面列出的派生类Student1构造函数首行中可以看到,派生类构造函数名(Student1)后面括号内的参数表中包括参数的类型和参数名(如int n),而基类构造函数名后面括号内的参数表列只有参数名而不包括参数类型(如n, nam, s),因为这里不是定义基类构造函数,而是调用基类构造函数,因此这些参数是实参而不是形参。它们可以是常量、全局变量和派生类构造函数总参数表中的参数。
在main函数中定义对象stud1时指定了5个实参,它们按顺序传递给派生类构造函数Student1的形参(n, nam, s, a, ad)。然后派生类构造函数将前面3个(n,nam,s)传递给基类构造函数的实参。通过Student(n, nam, s)把3个值再传给基类构造函数的形参。
下面给出一个例子,定义简单的派生类构造函数:
class Student5 { //声明基类Student5
public:
Student5(int n, string nam, char s) { //定义基类的构造函数
num = n;
name = nam;
sex = s;
}
~Student5(){} //基类析构函数
protected: //保护部分
int num;
string name;
char sex;
};
class Student51 :public Student5 {
//声明公用派生类Student51
public: //派生类的公用部分
Student51(int n, string nam, char s, int a, string ad) :Student5(n, nam, s) {
//定义派生类构造函数
age = a; //在函数体中只对派生类新增的数据成员初始化
addr = ad;
}
void show() {
cout << "num:" << num << endl;
cout << " name:" << name << endl;
cout << " sex:" << sex << endl;
cout << " age:" << age << endl;
cout << " address:" << addr << endl;
}
~Student51(){} //派生类析构函数
private: //派生类的私有部分
int age;
string addr;
};
int main() {
Student51 stud1(10010, "Wang-li", 'f', 19, "115 Beijing Road,Shanghai");
Student51 stud2(10011, "Zhang-fang", 'm', 21, "213 Shanghai Road,Beijing");
stud1.show(); //输出第一个学生的数据
stud2.show(); //输出第二个学生的数据
return 0;
}
也可以将派生类构造函数在类外面定义,而在类体中只写该函数的声明。
Student1(int n, string nam, char s, int a, string ad); //声明派生类构造函数
//在类的外面定义派生类构造函数:
Student1::Student1(int n, string nam, char s, int a, string ad):Student(n,nam,s)
{
age = a; //在函数体中只对派生类新增的数据成员初始化
addr = ad;
}
初始化表不仅可以对构造函数的数据成员初始化,还可以调用派生类的基类构造函数,实现对基类数据成员的初始化。也可以在同一构造函数的定义中同时实现这两种功能。
Student1(int n, string nam, char s, int a, string ad):Student(n,nam,s),age(a),addr(ad){}
在建立一个对象时,执行构造函数的顺序是:a.派生类构造函数先调用基类构造函数 b.再执行派生类构造函数本身(即派生类构造函数的函数体)。派生类对象释放时,先执行派生类析构函数~Student1(),再执行其基类析构函数~Student()。
2.2 有子对象的派生类的构造函数
类的数据成员可以是标准类型或系统提供的类型,还可以是类对象。如s1就是类对象中的内嵌对象,称为子对象,即对象中的对象。子对象的初始化是在建立派生类时通过调用派生类构造函数来实现的。
Student monitor; //Student是已经声明的类名,monitor是Student类的对象,定义子对象monitor
派生类构造函数一般形式为:
派生类构造函数名(总参数表):基类构造函数名(参数表),子对象名(参数表)
{派生类中新增数据成员初始化语句}
执行派生类构造函数的顺序是:
- 调用基类构造函数,对基类数据成员初始化;
- 调用子对象构造函数,对子对象数据成员初始化;
- 再执行派生类构造函数本身,对派生类数据成员初始化;
例:派生类构造函数的首部如下,构造函数中有6个形参,前两个是作为基类构造函数的参数,第3、4个是作为子对象构造函数的参数,第5、6个是作为派生类数据成员初始化的。由于子对象monitor也是Student类,在建立对象时也是调用基类构造函数。派生类构造函数的总参数表中的参数,应当包括基类构造函数和子对象的参数表中的参数。基类构造函数和子对象的次序可以是任意的,编译系统是根据相同的参数名(而不是参数顺序)来确定它们的传递关系的。
2.3 多层派生时的构造函数
Student是基类,Student1是Student的直接派生类,Student2是Student1的直接派生类,Student2是Student的间接派生类。即Student<-Student1<-Student2。下面给出基类和派生类的构造函数首部的写法:
//基类Student的构造函数首部
Student(int n, string nam)
//派生类Student1的构造函数首部
Student1(int n, string nam, int a):Student(n,nam)
//派生类Student2的构造函数首部
Student2(int n, string nam, int a, int s):Student1(n,nam,a)
在声明Student2类对象时,调用Student2构造函数;在执行Student2构造函数时,先调用Student1构造函数;在执行Student1构造函数时,先调用基类Student构造函数。初始化的顺序是:a.先初始化基类的数据成员 b.再初始化Student1的数据成员 c.最后再初始化Student2的数据成员。
2.4 派生类构造函数的特殊形式
- 当不需要对派生类新增的成员进行任何初始操作时,派生类构造函数的函数体可以为空,即构造函数是空函数。
- 如果在基类中没有定义构造函数,或定义了没有参数的构造函数,那么,在定义派生类构造函数时可以不写基类构造函数。因为此时派生类构造函数没有向基类构造函数传递参数的任务。在调用派生类构造函数时,系统会自动首先调用基类的默认构造函数。子对象构造函数和基类构造函数都没有参数,且无需对派生类数据成员初始化时,可以不必显式定义派生类构造函数,系统会自动调用默认构造函数。
2.5 派生类的析构函数
析构函数没有类型也没有参数,派生类是不能继承基类的析构函数的,也需要通过派生类的析构函数去调用基类的析构函数。在执行派生类的析构函数时,系统会自动调用基类的析构函数和子对象的析构函数,对基类和子对象进行清理。
调用的顺序与构造函数正好相反:先执行派生类自己的析构函数,对派生类新增加的成员进行清理,然后调用子对象的析构函数,对子对象进行清理,最后调用基类的析构函数,对基类进行清理。