c++的继承与派生之从入门到入坟-------集大成者

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

前言

继承与派生的大杂烩

欢迎各位同学华山论剑

学习目标

理解为为什么使用继承
晓得继承工作方式和派生类的初始化及对象的撤销
理解虚基类的使用
掌握继承的使用规则


提示:以下是本篇文章正文内容,下面案例可供参考

一、为什么使用继承

在使用面向对象的程序设计方法解决问题时,一般先建立与实际问题相关的类,然后对类进行实例化,即生成对象,最后利用对象的属性和方法编写解决问题的方案。在许多现实问
题中所涉及的类多数是相关的。这种特殊的类之间的关系,为构建类提供了一种特别的方式,即继承。
C++的继承机制体现了一种更高层次的代码复用性。

二、继承的工作方式

在C++中允许从一个基类或多个基类进行派生,从一个基类派生的继承称为单继承,从多个基类派生的继承称为多继承。

1.从基类中派生新类

声明格式:
class 派生类名:[派生方式] 基类名,[派生方式] 基类名
{
//派生类成员
};
如果没有标明派生方式则默认私有继承(派生方式有private public protected)

【例】
已知一个Person类,使用默认的派生方式派生一个Student类,并加入学号
(No)和班级(Class)以及printInfo()等新成员。
Person类的定义如下:
class Person
{private:
char Name[10];
int Age;
char Sex;
public:
void print()
{cout<<"Base Class Output"<endl;}
};
Student类的定义如下:
class Student:Private Person
{private: 
char Class[20];
char No[10];
public:
void printInfo()
{cout<<"Derived Class Output"<<endl;}
};

引入一个问题* 如果将cout<<“Derived Class Output”<<endl;换成cout<<Name<<endl:则程序能正常编译通过吗

2.继承下的访问控制

在不同的继承方式下,继承的成员的访问权限也不相同。

公有继承:派生类若以此方式继承基类,则继承的基类中的public和protected成员的访问属性不变,即继承的基类成员在派生类中仍然是public和protected的成员,而继承的基类中的private成员不可访问。

私有继承:派生类若以此方式继承基类,则继承的基类中的public和protected成员均转化为派生类中的private成员,而继承的基类中的private的成员不可访问。

保护继承:派生类若以此方式继承基类,则继承的基类中的public和protected成员均转化为派生类中的protected成员,而继承的基类中的private的成员不可访问。

派生类无法继承基类中的构造函数,析构函数,静态成员和友元关系。
不同继承方式的影响主要体现在两方面,即派生类成员函数对基类成员的访问权限和通过派生类对象对基类成员的访问权限

三、派生类对象的初始化和撤销

1.单继承下的构造函数和析构函数

派生类中的构造函数不仅要对自身新增的成员进行初始化,还必须对从基类继承下来的成员进行初始化。由于基类有自己的构造函数,所以派生类只需要向基类的构造函数传递相应的参数即可。如果派生类中存在子对象,则初始化方法与基类相同。
格式如下

派生类构造函数名(参数总表1):基类构造函数名(参数表2),子对象名(参数表3)
{
//派生类构造函数体
}
参数表2和参数表3中的参数必须都是参数总表1中的参数。

在派生类中的析构函数与基类析构函数的功能一样,也是在对象撤乡时进行必要的清理工作,派生类的析构函数也没有数据类型和参数。派生类析构函数,只需对新增成员进行清理和善后继承的基类和对象成员的清理和善后,由各自的析构函数来完成。
因为基类成员的初始化由基类的构造函数完成,而派生类新家成员的初始化由派生类的构造函数完成,所以派生类的构造函数和析构函数的执行顺序为:
(1).先执行基类构造函数再执行子对象构造函数最后执行派生类构造函数。
(2).先执行派生类的析构函数再执行子对象的析构函数,最后执行基类的析构函数。

【例】定义研究生(GraduateStudent)类公有继承学生(Student)类,再定义导师(Suervisor)类作为其子对象。分析GraduateStudent类的构造函数与析构函数。
程序代码如下:
#include<iostream>
using namespace std;
#include<string.h>
class Supervisor
{
	char Name[20];
	int Age;
public:
	Supervisor(const char* pName = "no name") {
		strcpy(Name, pName);
	}
	void direct() {
		cout << Name << "Directed the student" << endl;
	}
};
class Student
{
	char Name[20];
	char No[20];
public:
	Student(const char* pName = "no name", const char* stuNo = "no stuno") {
		strcpy(Name,pName);
		strcpy(No, stuNo);
	}
	void print() {
		cout << "No:" << No << endl;
		cout << "Name:" << Name << endl;
	}
};
class GraduateStudent:public Student
{
	Supervisor supervisor;
	int Grade;
public:
	GraduateStudent( char *stuName, char* stuNo, int stugrade, char* supervisor) :Student(stuName, stuNo), supervisor(supervisor)
	{
		Grade = stugrade;
	}
	void printSupervisor() {
		print;
		cout << "grade:" << Grade << endl;
		supervisor.direct();
	}
};
void main()
{
	Student stu("Li Ming", "2010112501");
	stu.print();
	GraduateStudent Gstu("Wang Hai","2010352401",20,"Advisor");
	Gstu.printSupervisor();
}

2. 多继承下的构造函数和析构函数

多继承时也涉及到基类成员,对象成员和派生类成员的初始化问题。

采用多继承时的一般格式:
派生类名(参数总表):基类名1(参数表1),基类名2(参数表2),……,基类名n(参数表n),对象成员名1(参数表1),…,对象成员名n(参数表n)
{ //派生类新增成员初始化 }

多继承的构造函数和析构函数具有与单继承的构造函数和析构函数相同的性质和特性。当基类中声明有默认形式的构造函数或未声明构造函数时派生类构造函数可以不向基类构造函数传递参数。若基类中未声明构造函数则派生类中也可以不声明,采用默认形式构造函数.当基类声明有带形参的构造函数时派生类也应声明带形参的构造函数并将参数传递给基类构造函数

多继承构造函数和析构函数执行顺序:
(1)调用基类构造函数调用顺序按照他们被继承时声明的顺序(自左向右)。
(2)调用成员对象的构造函数调用顺序按照他们在类中声明的顺序。
(3)派生类的构造函数体中的语句。

注意:若建立派生类对象时调用默认复制构造函数,则编译器将自动调用基类的默认复制构造函数。若编写派生类的复制构造函数,则需要为基类相应的复制构造函数传递参数。
多继承析构函数的声明方法与单继承的相同,析构函数也不能继承。派生类需要声明自己的析构函数。声明方法与普通类的析构函数相同。
提示:不需要显式的调用基类的析构函数。系统会自动隐式调用析构函数的调用次序,与构造函数相反。

三、虚基类的使用

引言
为什么c++要引虚基类的概念?
在多继承中,如果派生类的2个或多个基类具有共同的祖先基类,那么当派生类访问继承下来的公共成员时,就有可能由于同名成员的问题而引发二义性。

如下就会引起二义性
在这里插入图片描述
通过虚基类可以调整继承关系如下所示
在这里插入图片描述

1 虚基类声明一般形式:

class <派生类名 >:virtual <继承方式><基类名>注意:虚基类关键字的作用范围和继承方式与一般派生类相同,即仅对其后的基类起作用(一个基类是否为虚基是由派生类决定的).声明的虚基类以后虚基类的成员在进一步派生过程中和派生类一起维护同一个内存拷贝。**

2虚基类的初始化

虚基类的初始化与多继承下一般派生类的初始化在语法上相同,仅构造函数的执行顺序有差异。一般来说,虚基类的构造函数的执行在非虚基类的构造函数之前。若同一层次中包含多个虚基类,这些虚基类的构造函数按对他们说明的先后次序执行,若虚基类由非虚基类派生而来,则仍然先执行基类的构造函数再执行派生类的构造函数。

【例】建立一个动物类,并派生出狼类和狗类,再构建一个狼狗类,利用虚基类实现,并分别建立各类的构造函数。
#include<iostream>
using namespace std;
/************************************************************************/
class Anmial
{
private:
	int classNo;
public:
	Anmial(int No) {
		classNo = No;
		cout << "constructing,classNo=" << classNo << endl;
	}
};
/************************************************************************/
class Wolf :virtual public Anmial
{
	int WolfNo;
public:
	Wolf(int classNo, int No) :Anmial(classNo)
	{
		WolfNo = No;
		cout << "constructing Wolf,No=" << WolfNo << endl;
	}
};
/**************************************************************************/
class Dog:virtual public Anmial
{
	int DogNo;
public:
	Dog(int classNo, int No) :Anmial(classNo) {
		DogNo = No;
		cout << "constructing Dog,No=" << DogNo << endl;
	}
};
/*************************************************************************/
class WolfDog :public Wolf, public Dog
{
	int WolfDogNo;
public:
	WolfDog(int classNo, int wolfNo, int dogNo, int No) :Anmial(classNo), Wolf(classNo, wolfNo), Dog(classNo, dogNo)
	{
		WolfDogNo = No;
		cout << "constructing WolfDog No=" << WolfDogNo << endl;
	}
};
/************************************************************************/
void main() {
	WolfDog WD(10, 101, 102, 1012);
}

结果如图
在这里插入图片描述

说明
上例中,虚基类Animal的构造函数只执行了一次。这是因为当派生类Wolf Dog调用了虚基类Animal的构造函数之后,类Wolf和类Dog便不再调用虚基类Animal的构造函数了。 cout <<
“constructing,classNo=” << classNo << endl;所以这个语句只执行了一次。
提升:在使用虚基类时应注意以下几个问题
(1)虚基类的关键字virtual与继承方式的关键字provide
protected和。public没有书写顺序的要求,先写虚基类的关键字,或先写继承方式的关键字都可以。
(2一个基类是否是虚基类,由派生类决定,也就是说一个基类在作为某些派生类的虚基类的同时,也可以作为另一些派生类的非虚基类
(3)虚基类构造函数的参数必须由最新派生出来的类负责初始化,即使不是直接继承也应如此。如上例中Anmial的初始化应该由WolfDog类来执行.

总结

继承和组合

继承和组合是面向对象程序设计的重要机制,在什么情况下使用类的组合又是什么情况下使用类继承呢?

类的组合

组合也是构建新类的一种手段,它主要通过包含一个已经存在的类对象的方式构建新类组合,表示类之间的"有"关系,即部分与整体的关系,其反映的是复杂的对象是由其他的对象组合而成。在构建一个类时,如果发现这个类是由其他的类对象组合而成的,且具有部分与整体的关系,这时就可以使用组合的方式构建。例如汽车有一个发动机,发动机和汽车都是独立的对象。然而汽车是一个包含发动机对象的更复杂的对象。当然,汽车不仅包含发动机,还包括车轮车门车身和音响系统。等其他对象。

类的继承

观察继承层次结构,会发现居于顶层的类往往更具有一般性,可以说是其子类共同特征的抽象。反之,沿着继承分支延伸则类变得更加的具体化,也称之为泛化-特化。根据这一特征,在使用继承创建类时,需要考虑以下因素:
(1).新类与已知类,是否具有"是"关系
(2)新类是否需要使用已知类的全部成员
(3)使用组合是否可以替代?如果能建议使用组合

类型兼容 !!!

!!!
!!!
!!!
!!!

类型兼容主要指一个公有派生类的对象,在使用上可以被当作基类的对象来使用,但是反过来使用则不行。 在具体使用过程中主要体现在:


(1)派生类的对象可以被赋值给基类的对象。
(2)派生类的对象可以初始化基类的引用。如一个函数的参数是基类的引用,那么则可以给这个函数传递派生类,但此时编译器不会做具体的区分,及把传进来的派生类当作基类来使用。后面则就引入虚函数的概念以此来解决这一问题。
(3)指向基类的指针也可以指向派生类。 注意:通过基类对象名和指针,仅能使用从基类继承的成员,而不能使用派生类的新成员。******

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

兴趣使然的蓝精灵

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

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

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

打赏作者

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

抵扣说明:

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

余额充值