C++面向对象程序设计 - 继承与派生进一步讨论

本文围绕C++类的继承与派生展开,介绍了单继承和多继承的概念。通过习题深入探讨了私有、保护、公有成员在公有继承中的访问权限,分析了基类和派生类的构造函数、析构函数执行过程,还涉及多继承的实现以及继承与组合的相关代码完善。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        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(教师兼干部)。

  1. 在两个基类中都包含姓名、年龄、性别、地址、电话等数据成员。
  2. 在Teacher类中还包含数据成员title(职称)、在Cadre类中还包含数据成员post(职务),在Teacher_Cadre类中还包含数据成员wages(工资)。
  3. 对两个基类中的姓名、年龄、性别、地址、电话等数据成员用的名字,在引用这些数据成员时,指定作用域。
  4. 在类体中声明成员函数,在类外定义成员函数。
  5. 在派生类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++面向对象程序设计 - 多继承,以及基类与派生类转换-优快云博客

问题:将其中代码再完善一下,通过定义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;
}

        运行结果还是一样的,结果如下图:

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

觉醒法师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值