C++中所谓“继承”就是在一个已存在的类的基础上建立一个新类,从已有的类那里获得已有特性,叫做类的继承。从另一角度说,从已有的类(父类)产生一个新的子类,称为类的派生。一个派生类只从一个基类派生,这称为单继承;一个派生类有两个或多个基类,称为多继承。派生类是基类的具体化,而基类则是派生类的抽象。
该篇将通过一些习题进一步了解继承与派生。
一、私有、保护、公有成员的公有继承的访问权限
在C++中,对于派生类对象,不能直接通过派生类对象来引用基类的私有(private)成员,因为私有成员只能在类自身内部访问,而不能从外部(包括派生类)访问;保护(protected)成员虽然在类外部不可见,但是它们在派生类内部是可见的。此时我们将通过公有继承案例来了解下它们之间关系。
在以下程序结构中,分析问题,代码如下:
class A{
public:
void f1(){}
int i;
protected:
void f2(){}
int j;
private:
int k;
};
class B: public A{
public:
void f3(){}
protected:
int m;
private:
int n;
};
class C: public B{
public:
void f4(){}
private:
int p;
};
int main(){
A a1; // a1 是基类A的对象
B b1; // b1 是派生类B的对象
C c1; // c1 是派生类C的对象
return 0;
}
1.1 派生类访问基类中数据成员
问题:在main函数中能否用b1.i,b1.j和b1.k引用派生类B对象b1中基类A的成员?
回答:
- b1.i是可以的,因为i是公有的。
- b1.j是不可以的,因为j为保护成员,只能在派生类或基类中访问,无法在类外部直接访问
- b1.k是不可以的,因为k为私有成员,只能在基类中访问。
1.2 派生类访问基类中成员函数
问题:派生类B中的成员函数能否调用基类A中的成员函数f1和f2?
回答:可以,因为基类A中f1()为公有(public)成员函数,派生类B的成员函数可以直接调用它;f2()为保护(protected)成员函数,派生类B的成员函数也可以调用它。
1.3 派生类成员函数访问基类中数据成员
问题:派生类B中的成员函数能否引用基类A中的数据成员i,j,k?
回答:对于公有(public)数据成员i 和 保护(protected)数据成员j,派生类B的成员函数是可以直接引用它,但是对于基类A中私有(private)数据成员k,只能在基类A内部访问,派生类B的成员函数不能直接引用它,否则会报错【[Error] 'int A::k' is private within this context】- 在这个上下文中,'int A::k是私有的'。
1.4 多层派生类访问数据成员
问题:能否在main函数中用c1.i,c1.j,c1.k,c1.m,c1.n,c1.p引用基类A的成员i,j,k,派生类B的成员m,n,以及派生类C的成员p?
回答:
- c1.i是可以的,因为i是基类A的公有数据成员。
- c1.j是不可以的,因为j是基类A的保护数据成员,只能在基类A和派生类B、C内部访问。
- c1.k是不可以的,因为k是基类A的私有数据成员,只能基类A内部访问
- c1.m是不可以的,因为m是基类B的保护数据成员,只能在基类B和派生类C内部访问。
- c1.n是不可以的,因为n是基类B的私有数据成员,只能在基类B内部访问。
- c1.p是不可以的,因为p是派生类C的私有数据成员,只能在派生类C内部访问。
1.5 多层派生类访问成员函数
问题:能否在main函数中用c1.f1(),c1.f2(),c1.f3()和c1.f4(),调用f1,f2,f3,f4成员函数?
回答:
- c1.f1()是可以的,因为函数f1()是基类A的公有成员函数。
- c1.f2()是不可以的,因为函数f2()是基类A的保护成员函数,只能在基类A内部或派生类B、C内部访问。
- c1.f3()是可以的,因为函数f2()是派生类C的基类B的公有成员函数。
- c1.f4()是可以的,因为函数f4()是派生类C的公有成员函数。
1.6 多层派生类内部访问成员函数
问题:派生类C的成员函数f4能否调用基类A中的成员函数f1,f2和派生类中的成员函数f3?
回答:可以的,因为函数f1(),f2()在基类A中为公有和保护成员函数,可以在派生类C的内部直接调用。
二、私有、保护、公有继承的成员访问权限
问题:分析以下程序的所有成员在各类的范围的访问权限,代码如下:
class A{
public:
void f1(){}
protected:
void f2(){}
private:
int i;
};
class B: public A{
public:
void f3(){}
int k;
private:
int m;
};
class C: protected B{
public:
void f4(){}
protected:
int m;
private:
int n;
};
class D: private C{
public:
void f5(){}
protected:
int p;
private:
int q;
};
int main(){
A a1; // a1 是基类A的对象
B b1; // b2 是派生类B的对象
C c1; // c3 是派生类C的对象
D d1; // d1 是派生类D的对象
return 0;
}
回答:
2.1 基类和派生类内部的访问权限
首先分析各类的成员访问权限:
1)A类:
- 公有成员函数:f1()
- 保护成员函数:f2()
- 私有数据成员:i
2)B类:
- 公有成员函数:f3()
- 公有数据成员:k
- 私有数据成员:m
- 继承自类A的公有成员函数:f1()(因公有继承,保持为公有)
- 继承自类A的保护成员函数:f2()(因公有继承,保持为保护)
- 继承自类A的私有数据成员:i(在派生类B中不可见)
3)C类:
- 公有成员函数:f4()
- 保护数据成员:m
- 私有数据成员:n
- 继承自类B的公有成员函数:f3()(因保护继承,B类中继承的公有成员函数变有保护成员)
- 继承自类B的公有数据成员:k(因保护继承,B类中继承的公有数据成员变为保护成员)
- 继承自类B的私有数据成员:m(在派生类C中不可见)
- 继承自类A的公有成员函数:f1()(因保护继承,A类中继承的公有成员函数变有保护成员)
- 继承自类A的保护成员函数:f2()(因保护继承,A类中继承的公有数据成员变为保护成员)
- 继承自类A的私有数据成员:i(在派生类C中不可见)
4)D类:
- 公有成员函数:f5()
- 保护数据成员:p
- 私有数据成员:q
- 继承自类C的公有成员函数:f4()(因私有继承,在类外部不可见)
- 继承自类C的保护数据成员:m(因私有继承,在类外部不可见)
- 继承自类A的保护成员函数:f1()和f2()(因在类C中为保护继承,故类C继承了类A中的公有成员在类C中变为保护成员)
- 继承自类B保护成员:f3()和k(因在类C中为保护继承,故类C继承了类B中的公有成员在类C中变成保护成员)
2.2 基类和派生类外部的访问权限
在main方法中,创建A、B、C、D类的对象a1、b1、c1、d1。
1)对象a1:
- 可以调用公有成员函数:f1()
- 保护成员函数f2()和私有成员i在类外部无法访问。
2)对象b1:
- 可以调用公用成员函数:f2()
- 可以调用公有数据成员:k
- 可以调用继承类A中公有成员函数:f1()
3)对象c1:
- 可以调用公有成员函数:f4()
- 保护数据成员m和私有数据成员n在类外无法调用
- 由于类C保护继承类B,故类A和类B中公有成员和保护成员被类C继承后变为保护成员,所以在类外无法访问。
4)对象d1:
- 可以调用公有成员函数:f5()
- 保护数据成员p和私有数据成员q在类外无法调用
- 由于类D为私有继承,侧被继承成员类外无法调用
三、基类和派生类的构造函数
问题:分析以下代码,查看运行后输出结果,是否运行正确,以及执行过程中调用构造函数执行过程。
#include <iostream>
using namespace std;
class A{
public:
A(){
a = 0;
b = 0;
}
A(int i){
a = i;
b = 0;
}
A(int i, int j){
a = i;
b = j;
}
void display(){
cout <<"a=" <<a <<" b=" <<b;
}
private:
int a;
int b;
};
class B: public A{
public:
B(){
c = 0;
}
B(int i): A(i){
c = 0;
}
B(int i, int j): A(i, j){
c = 0;
}
B(int i, int j, int k): A(i, j), c(k){
}
void display1(){
display();
cout <<" c=" <<c <<endl;
}
private:
int c;
};
int main(){
B b1;
B b2(2);
B b3(2, 3);
B b4(2, 3, 5);
b1.display1();
b2.display1();
b3.display1();
b4.display1();
return 0;
}
回答:
1)运行后程序正确,结果如下图:
2)调用构造函数执行过程
- 创建b1对象时,首先调用类B的默认构造函数,先执行继承类A中的默认构造函数初始化数据成员,再初始化自己的数据成员。
- 创建b2对象时,先执行继承类A中构造函数A(int i)进行初始化,再初始化自己的数据成员。
- 创建b3对象时,先执行继承类A中构造函数A(int i, int j)进行初始化,再初始化自己的数据成员。
- 创建b4对象时,先执行继承类A中构造函数A(int i, int j)进行初始化,再初始化自己的数据成员。
综上所述,可见被继承的基类构造函数先执行初始化,再初始化派生类自己的成员。可以将上述代码添加输出,更直观了解其执行过程。代码如下:
#include <iostream>
using namespace std;
class A{
public:
A(){
a = 0;
b = 0;
cout <<"A default" <<endl;
}
A(int i){
a = i;
b = 0;
cout <<"A i" <<endl;
}
A(int i, int j){
a = i;
b = j;
cout <<"A i,j" <<endl;
}
void display(){
cout <<"a=" <<a <<" b=" <<b;
}
private:
int a;
int b;
};
class B: public A{
public:
B(){
c = 0;
cout <<"B default" <<endl;
}
B(int i): A(i){
c = 0;
cout <<"B i" <<endl;
}
B(int i, int j): A(i, j){
c = 0;
cout <<"B i, j" <<endl;
}
B(int i, int j, int k): A(i, j), c(k){
cout <<"B i,j,k" <<endl;
}
void display1(){
display();
cout <<" c=" <<c <<endl;
}
private:
int c;
};
int main(){
B b1;
B b2(2);
B b3(2, 3);
B b4(2, 3, 5);
b1.display1();
b2.display1();
b3.display1();
b4.display1();
return 0;
}
运行结果如下图:
四、基类和派生类的析构函数
问题:阅读以下程序,写出运行时输出的结果,并分析程序执行过程中,调用的构造函数和析构函数的过程。
#include <iostream>
using namespace std;
class A{
public:
A(){
cout <<"constructing A" <<endl;
}
~A(){
cout <<"destructing A" <<endl;
}
};
class B: public A{
public:
B(){
cout <<"constructing B" <<endl;
}
~B(){
cout <<"destructing B" <<endl;
}
};
class C: public B{
public:
C(){
cout <<"constructing C" <<endl;
}
~C(){
cout <<"destructing C" <<endl;
}
};
int main(){
C c1;
return 0;
}
回答:
1)运行程序正确,结果如下图:
2)调用构造函数和析构函数的过程
构造函数执行过程:当创建C c1对象时,由于C类继承B类,B类继承A类,因此会先调用基类A的构造函数进行初始化,再调用B类的构造函数进行初始化,最后再调用自身进行初始化成员。所以构造函数输出结果为:constructing A,constructing B,constructing C。
析构函数执行过程:析构函数的销毁过程刚好与构造函数的过程相反,所以会先调用类C的析函数,再调用类B的析构函数,最后再调用类A的析构函数。所以析构函数输出结果为:destructing C,destructing B,destructing A。
五、多继承
问题:分别定义Teacher(教师)类和Cadre(干部)类,采用多重继承方式由这两个类派生出新类Teacher_Cadre(教师兼干部)。
- 在两个基类中都包含姓名、年龄、性别、地址、电话等数据成员。
- 在Teacher类中还包含数据成员title(职称)、在Cadre类中还包含数据成员post(职务),在Teacher_Cadre类中还包含数据成员wages(工资)。
- 对两个基类中的姓名、年龄、性别、地址、电话等数据成员用的名字,在引用这些数据成员时,指定作用域。
- 在类体中声明成员函数,在类外定义成员函数。
- 在派生类Teacher_Cadre类的成员函数show中调用Teacher类中的display函数,输出姓名、年龄、性别、职称、地址、电话,然后再用cout语句输出职务与工资。
回答:代码如下:
#include <iostream>
class Teacher{
private:
std::string name;
int age;
char gender;
std::string address;
std::string phone;
std::string title; //职称
public:
Teacher(std::string name, int age, char gender, std::string address, std::string phone, std::string title):
name(name), age(age), gender(gender), address(address), phone(phone), title(title){}
void display();
};
class Gadre{
private:
std::string name;
int age;
char gender;
std::string address;
std::string phone;
std::string post; //职务
public:
Gadre(std::string name, int age, char gender, std::string address, std::string phone, std::string post):
name(name), age(age), gender(gender), address(address), phone(phone), post(post){}
std::string get_post();
};
class Teacher_Gadre: public Teacher, public Gadre{
private:
double wages; //工资
public:
Teacher_Gadre(std::string name, int age, char gender, std::string address, std::string phone,
std::string title, std::string post, double wages):
Teacher(name, age, gender, address, phone, title), Gadre(name, age, gender, address, phone, post), wages(wages){}
void show();
};
// 类体外定义Teacher类的display成员函数
void Teacher::display(){
std::cout <<"name:" <<name <<", age:" <<age <<", gender:" <<gender <<", address:" <<address
<<", phone:" <<phone <<", title:" <<title <<std::endl;
}
// 类体外定义Gadre类的get_post成员函数
std::string Gadre::get_post(){
return post;
}
// 类体外定义show函数
void Teacher_Gadre::show(){
display();
std::cout <<"Post:" <<get_post() <<", Wage:" <<wages <<std::endl;
}
int main(){
// 创建对象tc并初始化数据
Teacher_Gadre tc("John", 30, 'M', "345 Main St", "13288889999", "Professor", "History", 5000);
// 调用show()函数显示结果
tc.show();
return 0;
}
运行结果如下图:
六、继承与组合
这里再复习下在上一篇中讲到的“五、继承与组合”,地址:C++面向对象程序设计 - 多继承,以及基类与派生类转换-CSDN博客
问题:将其中代码再完善一下,通过定义Student对象s给出所有数据的初值,再修改对象s的生日数据,最后输出对象s的全部最新数据。
回答:代码如下:
#include <iostream>
#include <string>
using namespace std;
class Person{
protected:
string name;
int age;
public:
Person(string name, int age): name(name), age(age){}
};
// 定义生日类
class Birthday{
private:
int year;
int month;
int day;
public:
Birthday(int year, int month, int day): year(year), month(month), day(day){}
// 声明修改数据成员函数
void reset(int, int, int);
// 显示日期
void show(){
cout <<year <<'/' <<month <<'/' <<day <<endl;
}
};
// 类体外定义重置生日函数
void Birthday::reset(int year, int month, int day){
this->year = year;
this->month = month;
this->day = day;
}
// Person作为Student基类
class Student: public Person{
protected:
Birthday birth;
public:
// 构造函数
Student(string name, int age, Birthday birth): Person(name, age), birth(birth){}
// 声明reset()成员函数修改生日(由于birth对象作用数据成员,故公有成员函数reset()是无法继承的)
void reset(int, int, int);
// 显示结果
void display(){
cout <<"name:" <<name <<endl;
cout <<"age:" <<age <<endl;
cout <<"birth:"; birth.show();
}
};
// 类体外定义重置生日函数
void Student::reset(int year, int month, int day){
birth.reset(year, month, day);
}
int main(){
Student s("Tom", 20, Birthday(2000, 1, 15));
// 调用公有函数reset()修改生日
s.reset(2024, 5, 6);
// 显示信息
s.display();
return 0;
}
运行结果如下:
由于Birthday类为Student类的保护成员,类Student并未继承类Birthday,故类Birthday中公有成员函数reset()是无法通过对象s访问的;对象birth是对象s的保护成员,故在s对象内部可以访问birth.reset()的,所以需要在类Student中再定义一个成员函数用于修改生日的功能。
由于对象birth为保护成员,只能在类内部访问,那是否还有其他方法可以修改生日呢?当然可以的,前面大家学习到的友函数,是可以访问类中的私有和保护成员。修改后代码如下:
#include <iostream>
#include <string>
using namespace std;
class Person{
protected:
string name;
int age;
public:
Person(string name, int age): name(name), age(age){}
};
// 定义生日类
class Birthday{
private:
int year;
int month;
int day;
public:
Birthday(int year, int month, int day): year(year), month(month), day(day){}
// 声明修改数据成员函数
void reset(int, int, int);
// 显示日期
void show(){
cout <<year <<'/' <<month <<'/' <<day <<endl;
}
};
// 类体外定义重置生日函数
void Birthday::reset(int year, int month, int day){
this->year = year;
this->month = month;
this->day = day;
}
// Person作为Student基类
class Student: public Person{
protected:
Birthday birth;
public:
// 构造函数
Student(string name, int age, Birthday birth): Person(name, age), birth(birth){}
// 声明Student类的友函数
friend void reset_birth(Student&, int, int, int);
// 显示结果
void display(){
cout <<"name:" <<name <<endl;
cout <<"age:" <<age <<endl;
cout <<"birth:"; birth.show();
}
};
// 定义Student类的友函数
void reset_birth(Student& s, int year, int month, int day){
s.birth.reset(year, month, day);
}
int main(){
Student s("Tom", 20, Birthday(2000, 1, 15));
// 调用Student的友函数修改生日
reset_birth(s, 2024, 5, 6);
// 显示信息
s.display();
return 0;
}
运行结果还是一样的,结果如下图: