10.1 C++类继承简介
C++中类作为一种自定义类型的数据结构,在面向对象程序设计思想中有着相当重要的作用。类代表一类事物的集合,与现实社会相似,类类型同样具有继承实现的可能。现实社会中事物的继承性随处可见,如父子的继承关系等。
C++软件开发中,继承机制有着很多的运用。通常在软件编程中,继承与多态性的结合运用可以增加软件的扩展性以及应用程序可重用性。C++语言中将继承分为三种情况,即public、protected以及private继承。不同的继承方式有着不同的限制规则,这点会在后续的继承层次结构中详细讲述。
通常,C++编程中的继承采用public方式居多,不排除特殊情况下会使用另外两种继承方式。但是protected与private继承方式的使用复杂性可能造成不必要的出错。下面主要通过类public继承方式来讲述类继承的基本概念与实际应用。首先通过一个完整的public继承方式的实例直观的了解继承的实现。
1.准备实例
打开UE工具,创建新的空文件并且另存为chapter1001_01.h、chapter1001_01.cpp、chapter1001_02.h、chapter1001_02.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。
/**
* 实例chapter1001
* 源文件chapter1001_01.h chapter1001_01.cpp
* chapter1001_02.h chapter1001_02.cpp
* 水果类单继承方式
*/
//水果类头文件chapter1001_01.h
#include <iostream>
using namespace std;
class Fruit //水果类Fruit定义
{
public: //水果类公开接口成员
Fruit(){} //水果类构造函数定义
~Fruit(){} //水果类析构函数定义
voidshowFruitInfo(); //水果类信息打印函数
};
//水果类源文件chapter1001_01.cpp
#include "chapter1001_01.h"
void Fruit::showFruitInfo() //水果类信息打印方法外部定义
{
cout<<"Thisis Fruit class!"<<endl;
}
//苹果类头文件,chapter1001_02.h继承至水果类
#include "chapter1001_01.h"
class Apple: public Fruit //苹果类Apple定义,以public方式继承至Fruit类
{
public: //苹果类公开接口成员
Apple(){} //苹果类构造函数定义
~Apple(){} //苹果类析构函数定义
voidshowAppleInfo(); //苹果类信息显示函数
};
//苹果类源文件chapter1001_02.cpp
#include "chapter1001_02.h"
void Apple::showAppleInfo() //苹果类信息显示函数外部定义
{
cout<<"Thisis Apple class!"<<endl;
}
int main()
{
Fruitfruit; //主程序中定义水果类对象fruit
Appleapple; //定义苹果类对象apple
fruit.showFruitInfo(); //使用水果类对象调用其打印信息方法
apple.showFruitInfo(); //使用苹果类对象调用继承至水果类方法成员打印类信息
apple.showAppleInfo(); //使用苹果类对象调用自身方法成员打印类信息
return0;
}
本实例主要通过水果类、苹果类演示类单继承方式应用情况。程序主要由4个文件组成,分别为水果类头文件、源文件以及具体苹果类头文件、源文件。具体程序剖析见程序注释与后面讲解。
2.编辑makefile
Linux平台下需要编译源文件为chapter1001_01.cpp、chapter1001_02.cpp,相关makefile工程文件编译命令编辑如下所示。
#makefile
OBJECTS= chapter1001_01.o chapter1001_02.o
CC=g++
#生成可执行程序Apple
Apple: $(OBJECTS)
$(CC)$(OBJECTS) -o Apple
chapter1001_01.o: chapter1001_01.h
chapter1001_02.o: chapter1001_02.h
#清除源文件以外的操作命令
clean:
rm-f Apple core $(OBJECTS)
submit:
cp-f -r Apple ../bin
cp-f -r *.h ../include
上述makefile文件的编写是Linux系统下工程化的编译思想的体现。由于Linux系统下编译器采用的是命令执行形式,makefile文件的组织应用,有利于大型软件工程中代码的编译管理。
以上make命令是执行makefile文件中编译命令。为了使makefile编译文件显得更加的灵活可扩展,此处在makefile文件中增加变量定义,将makefile中多处重复使用的部分提取出来,采用变量定义来表示。这样下次需要增加或者修改对应的文件只需要修改一次即可。上述文件中,主要将程序生成的.o工程文件定义为变量OBJECTS表示,将编译器g++定义为变量CC表示。为此下次需要增加文件或者换用编译命令只需要修改该变量定义即可。
随后将变量运用到执行命令中,此处,采用另一种形式来定义代码文件编译命令。直接通过代码工程文件以及对应的头文件,让make命令自动去推导程序应该执行的编译命令。此处主要涉及两个工程文件的编译,即chapter1001_01.o和chapter1001_02.o,与其对应的代码文件分别为chapter1001_01.h、chapter1001_01.cpp、chapter1001_02.h以及chapter1001_02.cpp,主程序入口定义于chapter1001_02.cpp中。最后,为了清除当前目录下生成的程序以及出错产生的core等垃圾文件定义了clean选项,选项下执行rm –f,表示指定清楚随后的各种文件。
上述实例中不仅采用工程化makefile文件编译方式,同时也采用多代码文件的方式来编写实例程序。本实例共涉及四个代码文件,两个头文件代码,以及两个对应的代码源文件。在软件编程中,代码文件划分应用也是大型软件工程中必不可少的部分,头文件与源代码文件的区分会让软件组织结构更加的合理。
3.编译运行程序
当前shell下执行make命令,生成可执行程序文件。随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。
[developer @localhost src]$ make
g++ -c -o chapter1001_01.o chapter1001_01.cpp
g++ -c -o chapter1001_02.o chapter1001_02.cpp
g++ chapter1001_01.o chapter1001_02.o -o Apple
[developer @localhost src]$ make submit
cp -f -r Apple ../bin
cp -f -r *.h ../include
[developer @localhost src]$ cd ../bin
[developer @localhost bin]$ ./Apple
This is Fruit class!
This is Fruit class!
This is Apple class!
通常在C++面向对象的应用开发中,应用程序的头文件主要包含类体的定义,以及程序需要使用的常量定义等。而源文件则主要是类方法成员的具体实现的场所。使用时只需要#include包含对应的头文件即可。
4.程序剖析
多文件的使用更多的优势之处会在后续更多实例中慢慢体现出来,这里只作简单的介绍。本实例中主要涉及两个类,即水果类Fruit和苹果类Apple。两个类之间为继承关系,由于水果类代表一类广泛的概念类型,而苹果类则代表了具体水果的一种类型,它们之间存在一定的关联。这种关联可以理解为继承关联,即苹果类是从水果类继承而来的。
实例中四个代码文件,其中chapter1001_01.h与chapter1001_01.cpp为水果类定义文件。头文件chapter1001_01.h中包含类Fruit的定义,该类包含自定义空的构造函数与析构函数,以及一个打印自身信息的方法成员showFruitInfo(),暂不包括任何数据成员。chapter1001_01.cpp文件中主要定义实现类Fruit的方法成员,只包含chapter1001_01.h头文件,此处主要定义实现类方法成员showFruitInfo()。
头文件chapter1001_02.h中定义Apple类,采用公开继承方式从水果类派生定义而开。此时需要包含水果类头文件chapter1001_01.h,因为此处需要使用到该类的声明定义。该文件中Apple类公开继承至水果类Fruit,即意味着Apple类拥有了基类的所有成员,同时在此基础上Apple中还定义了显示本信息的方法成员,该成员在chapter1001_02.cpp源文件中定义实现。
主程序中首先定义基类对象fruit,此时必然调用其定义构造函数构造该类对象实例。随后定义其派生类对象apple,该对象拥有基类一切成员的同时还拥有自定义的成员。程序中首先对Fruit对象实例构造完毕后,调用其信息显示函数showFruitInfo(),打印本类基本信息。随后使用苹果类对象实例分别调用自Fruit类继承的该方法,以及该类本身的信息显示方法。
本实例仅仅演示类公开继承的方式的实现,此处仅仅涉及继承中的方法成员情况,下面将会就继承的更多具体操作作详细讲述。
10.2 C++类继承层次结构
C++中继承体系是一种类似数据结构中树状的层次结构,在树状层次的继承体系中,始终脱离不了两个基本概念即基类与派生类。通常C++软件开发中,类可以作为两种方式存在。一种是作为单独存在的自定义类型参与程序处理。另一种则是使用类继承机制,类通常不是作为顶层基类出现就是作为基类的派生类而存在,而该派生类又可以作为其派生类的基类。
10.2.1 C++继承体系简介
前面已经说明过,C++类一般可以作为继承顶层的基类,或者作为基类的派生类存在。基类通常又称为父类,在软件系统中基类应该表示一类事物最通用的基本特性。例如上小节中的水果与苹果类的继承关系,水果类代表一类水果最常见的特性与操作。而苹果类则为水果的一种,具有具体更多的特性描述,通过继承下次需要增加另外一种水果类型会变得很方便。直接继承至水果类即可实现,这种分析方式在面向对象中有助于理解使用继承增加软件可扩展性的概念。
C++中类的继承体系可以是多个基类,派生类继承至该多个基类,通常这种继承方式称为为多继承。另外一种方式即可以是单个基类,扩展至多个派生类并且多个派生类之间是在同一个层次上,互不相干,同时派生类又可以继续派生下去。以下通过分析软件学院人员组织机构继承体系为例,如图10.1所示。
图10.1 软件学院机构简单继承体系图
上述继承分析实例中最上层的基类表示软件学院人员。随后继承体系下出现并行同层次的单继承情况,即软件学院人员可以由行政人员、教师与学生成员组成。类Administration、类Teacher以及类Student直接继承至类SoftwareMember,这种继承方式称为单继承。
而软件学院中通常有一种拥有双重身份的成员。如一名行政人员同时又是教师身份,此时该类需要同时继承行政类与教师类而来,表示该类人的双重身份。类AdministrationTeacher继承至类Administration与类Teacher,这种继承方式称为多继承。当然一个软件学院机构中成员不仅仅会涉及以上这些。本图仅仅突出整个机构中的小部分组织来演示继承体系中可能出现的情况,即单继承与多继承的概念。稍后会在后续小节中详细讲述。
通常类继承机制的优势在于,现有不变的类类型基础之上创建新类,从而通过增加新类操作方法来达到添加新功能的目的。最常见的是库的应用开发中,开发者可以开发出一定范围内可重用的组件类。而这些组件类功能在应用时可以根据具体需要在不修改原类的基础上进行继承扩充。软件中类的继承层次分析方法应用很多,面向对象编程思想中掌握这种分层方法分析事物有助于设计出高可扩展性以及功能强大的软件系统。初学者可以通过分析现实世界中的一些继承实例来培养该方面的思维方式。
10.2.2 单继承中的基类与派生类
C++中单继承有着很广阔的运用,一般软件开发中通过继承现有功能类的方式设计出功能更强大的处理类。但是通常要求基类中封装一类事物中最普遍的属性与操作,供派生类继承并加入新的属性与操作进行扩展。下面依然通过水果类继承实例,加入操作方法与数据成员,了解继承体系中基类与派生类的使用。
1.准备实例
打开UE工具,创建新的空文件并且另存为chapter1002_01.h、chapter1002_01.cpp、chapter1002_02.h、chapter1002_02.cpp与testMain.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。
/**
* 实例chapter1002
* 源文件chapter1002_01.hchapter1002_01.cpp
* chapter1002_02.h chapter1002_02.cpp testMain.cpp
* 水果类单继承方式
*/
//水果基类头文件chapter1002_01.h
#ifndef FRUIT_H
#define FRUIT_H
#include <iostream>
#include <string>
using namespace std;
class Fruit //水果类Fruit
{
public:
Fruit(); //水果类构造函数
~Fruit(); //水果类析构函数
voidsetFruitName(const char* name); //水果类设置水果名称方法成员
voidshowFruitInfo(); //水果类打印输出水果信息方法成员
voideatFruit(); //水果类吃水果方法成员
protected:
charm_name[100]; //水果类私有保护数据成员,表示水果名称
};
#endif
//水果基类源文件chapter1002_01.cpp
#include "chapter1002_01.h"
Fruit::Fruit() //水果类构造函数定义实现
{
cout<<"Fruitclass construction!"<<endl; //打印输出构造函数信息
}
Fruit::~Fruit() //水果类析构函数
{
cout<<"Fruitclass destruction!"<<endl; //打印输出析构函数信息
}
void Fruit::setFruitName(const char* name) //设置水果类名称信息方法定义实现
{
strcpy(m_name,name); //通过拷贝的方式将传入的参数赋值到数据成员中
}
void Fruit::showFruitInfo() //打印输出水果类信息方法定义实现
{
cout<<"showFruit member m_name:"<<m_name<<endl; //打印输出水果类名称成员
}
void Fruit::eatFruit() //水果类吃水果方法成员定义实现
{
cout<<"eatFruit!"<<endl; //打印输出水果类吃水果方法实现信息
}
//苹果类头文件chapter1002_02.h
#include "chapter1002_01.h"
class Apple: public Fruit //苹果类Apple,公开继承至水果基类Fruit
{
public:
Apple(); //苹果类构造函数
~Apple(); //苹果类析构函数
voidsetAppleName(const char* name); //苹果类设置名称方法成员
voidshowAppleInfo(); //苹果类打印苹果信息方法成员
voideatFruit(); //苹果类中吃水果方法成员
};
//苹果类源文件chapter1002_02.cpp
#include "chapter1002_02.h"
Apple::Apple() //苹果类构造函数定义实现
{
cout<<"Appleclass construction!"<<endl;
}
Apple::~Apple() //苹果类析构函数定义实现
{
cout<<"Appleclass destruction!"<<endl;
}
void Apple::setAppleName(const char* name) //苹果类设置名称方法定义实现
{
strcpy(m_name,name);
}
void Apple::showAppleInfo() //苹果类打印输出苹果信息方法成员定义实现
{
cout<<"showApple member m_name"<<m_name<<endl;
}
void Apple::eatFruit() //苹果类中吃苹果方法成员定义实现
{
cout<<"eatApple!"<<endl;
}
//主程序源文件testMain.cpp
#include "chapter1002_02.h"
/*主函数入口*/
int main()
{
Fruitfruit; //定义水果类对象fruit
fruit.setFruitName("Fruit"); //水果类对象fruit调用其设置名称方法
fruit.showFruitInfo(); //水果类对象fruit调用其打印水果信息方法
fruit.eatFruit(); //水果类对象fruit调用其吃水果方法
Appleapple; //定义苹果类对象apple
apple.setAppleName("Apple"); //苹果类对象apple调用其设置名称方法
apple.showAppleInfo(); //苹果类对象apple调用其打印苹果信息方法
apple.eatFruit(); //苹果类对象apple调用其吃水果方法
}
本实例程序主要通过水果、苹果类演示单类继承方式。程序主要由主函数与水果、苹果类组成,相关类中除了相应的构造与析构后,还包含相应输出数据成员信息的方法。程序具体分析见程序注释与后面的讲解。
2.编辑makefile
Linux平台下需要编译源文件为chapter1002_01.cpp、chapter1002_02.cpp、testMain.cpp,相关makefile工程文件编译命令编辑如下所示。
OBJECTS=chapter1002_01.o chapter1002_02.otestMain.o
CC=g++
testMain: $(OBJECTS)
$(CC)$(OBJECTS) -o testMain
testMain.o:
chapter1002_01.o: chapter1002_01.h
chapter1002_02.o: chapter1002_02.h
clean:
rm-f testMain core $(OBJECTS)
submit:
cp-f -r testMain ../bin
cp-f -r *.h ../include
上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。
3.编译运行程序
当前shell下执行make命令,生成可执行程序文件。随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。
[billing@localhost src]$ make
g++ -c -ochapter1002_01.o chapter1002_01.cpp
g++ -c -ochapter1002_02.o chapter1002_02.cpp
g++ -c -otestMain.o testMain.cpp
g++ chapter1002_01.o chapter1002_02.o testMain.o-o testMain
[billing@localhost src]$ make submit
cp -f -r testMain ../bin
cp -f -r *.h ../include
[billing@localhost src]$ cd ../bin
[billing@localhost bin]$ ./testMain
Fruit class construction!
show Fruit member m_name:Fruit
eat Fruit!
Fruit class construction!
Apple class construction!
show Apple member m_nameApple
eat Apple!
Apple class destruction!
Fruit class destruction!
Fruit class destruction!
4.程序剖析
本实例主要演示水果继承体系中单继承的基本实现。主要由两个类源文件组成包含水果基类以及其派生出的苹果类。苹果类采用public方式继承至基类水果类,其中基类中包含构造与析构函数、设置水果类名称方法、打印水果类信息方法以及吃水果动作方法接口的定义。派生出的苹果类除了拥有基类成员外根据需要添加了自身类的构造与析构函数、设置苹果类名称方法、打印苹果类信息方法以及吃水果动作方法接口定义。
实例主程序源文件中,首先定义基类Fruit对象fruit。此时会调用其自定义构造函数构造对应的对象实例。构造函数中主要实现一条提示信息语句。随后通过该对象实例调用其方法成员setFruitName,根据传入的实参设置水果基类的保护数据成员。采用fruit对象调用showFruitInfo打印输出设置的水果基类的数据成员m_name。最后通过fruit对象调用其吃水果动作方法,该方法主要实现为打印执行该方法的提示信息。
主程序中之后定义派生类苹果类对象实例apple,派生类由于继承基类所有成员。所以初始化构造派生类对象之前需要首先调用基类水果类的构造函数,即程序运行结果中首先会打印输出基类构造函数调用的提示信息。随后才调用派生苹果类构造信息。通过apple对象调用其setAppleName方法设置继承至基类水果类的保护数据成员,随后调用showAppleInfo方法打印输出该成员信息。最后则通过apple对象调用eatFruit方法打印输出属于吃苹果具体的信息提示。
从上述实例中总结出单继承的一些特点。首先,单继承中基类对象定义操作与派生类无关,完全可以将基类作为一个单独的类来处理应用。其次派生类中通过public方式继承,包含了基类的所有的成员,其中保护数据成员m_name在派生类中拥有一份拷贝可以供其对象使用。另外派生类中对象实例构造时构造函数执行需要优先执行基类构造,同时对象实例退出时析构函数的调用顺序则正好相反。
最后,派生类中允许定义与基类同样名称的方法成员。此时派生类对象调用同名称方法时,基类该方法成员将会被隐藏。上述实例中允许了同一个方法实现具体不同操作的作法,稍后下章会介绍虚函数在同样情况的应用情况处理。
10.3 继承体系中的访问控制
C++中单独类的访问控制在类章节中已经详细讲述过。但是由于继承拥有public、protected以及private三种方式,针对不同的继承方式,与类中访问控制结合起来,使得派生类的访问控制变得更加的复杂,下面针对三类继承方式详细讲述派生类不同的访问控制策略。
10.3.1 public继承
类采用public方式从基类派生,基类中的public与protected成员在派生类中依然保持public与protected成员访问规则。类继承体系中,基类本身的访问控制方式与单独类访问控制无异,即可以将基类本身看成单独存在的类去应用,因为继承应用其中一个重要的原因就是基类可能被其它程序使用。为了保留基类对象的完整性,通过对基类的继承,来定义新的类类型供使用。这样在保持原有应用程序不变的情况下可以扩充和丰富具体继承类的操作。所以,基类在继承体系中依然保持独立的访问控制。
public继承中,派生类继承至基类,即拥有基类的所有成员基础上增加属于派生类的成员。此时针对派生类的访问控制增加了基类成员部分。public继承方式下,基类的public成员在派生类中依然保持public属性,即派生类中可以保持对public成员的访问权限。派生类对象可以在类体外直接访问基类以及派生类本身的公开成员,包括非静态方法成员、友元函数以及数据成员。
而public继承方式下基类的protected成员在派生类中依然保持protected成员访问控制规则,即派生类中只有非静态方法成员以及友元可以直接访问。public继承方式下,基类的private成员在派生类中依然保持private成员特性,派生类中不允许访问基类的private成员。但是可以通过基类public或者protected成员来访问,为此保证基类的隐藏特性。下面通过一个完整实例演示公开继承访问控制方式。
1.准备实例
打开UE工具,创建新的空文件并且另存为chapter1003_01.h、chapter1003_01.cpp、chapter1003_02.h、chapter1003_02.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。
/**
* 实例chapter1003
* 源文件chapter1003_01.hchapter1003_01.cpp
* chapter1003_02.h chapter1003_02.cpp
* 水果类公开继承访问控制
*/
//水果类头文件chapter1003_01.h
#ifndef FRUIT_H
#define FRUIT_H
#include <iostream>
#include <string>
using namespace std;
class Fruit //水果类Fruit定义
{
public: //水果类公开方法成员
Fruit(){} //水果类构造函数定义
~Fruit(){} //水果类析构函数定义
voidsetFruitId(int id); //水果类设置水果编号方法成员
voidshowFruitId(); //水果类打印水果编号方法成员
public: //水果类公开数据成员
stringm_name; //字符串数据成员表示水果名称
protected: //水果类保护类型数据成员
intm_price; //整型数据成员表示水果价格
private: //水果类私有保护数据成员
intm_id; //整型数据成员表示水果编号
};
#endif
//水果类源文件chapter1003_01.cpp
#include "chapter1003_01.h"
void Fruit::setFruitId(int id) //水果类公开设置水果编号函数定义,传入整型参数表示编号
{
m_id= id;
}
void Fruit::showFruitId() //水果类公开打印水果编号函数定义
{
cout<<"TheFruit id:"<<m_id<<endl;
}
//苹果类头文件chapter1003_02.h
#ifndef APPLE_H
#define APPLW_H
#include "chapter1003_01.h"
class Apple: public Fruit //公开继承至水果类的苹果类定义
{
public: //苹果类公开方法成员
Apple(){} //苹果类构造函数
~Apple(){} //苹果类析构函数
voidsetApplePrice(int price); //苹果类设置苹果价格方法成员
voidshowApplePrice(); //苹果类打印苹果价格的方法成员
};
#endif
//苹果类源文件chapter1003_02.cpp
#include "chapter1003_02.h"
void Apple::showApplePrice() //苹果类打印苹果价格方法成员定义
{
cout<<"Thisis Apple price:"<<m_price<<endl;
}
void Apple::setApplePrice(int price) //苹果类设置苹果价格方法成员定义,传入整型参数表示价格
{
m_price= price;
}
int main()
{
Appleapple; //定义苹果类对象apple
apple.m_name= "apple"; //对象apple访问继承至基类的公开数据成员m_name
cout<<apple.m_name<<endl; //通过对象apple直接打印类公开成员m_name
apple.setApplePrice(3); //apple对象调用设置苹果价格方法
apple.showApplePrice(); //apple对象调用打印苹果价格方法
apple.setFruitId(1); //apple对象调用设置水果编号方法
apple.showFruitId(); //apple对象调用打印水果编号方法
return0;
}
本实例通过水果类单继承方式演示类继承中公共继承访问控制情况。程序由主函数与水果、苹果类组成。具体程序剖析见程序注释与后面的讲解。
2.编辑makefile
Linux平台下其makefile编译文件中涉及两个头文件以及两个源代码文件,makefile文件编辑如下。
OBJECTS= chapter1003_01.o chapter1003_02.o
CC=g++
Apple: $(OBJECTS)
$(CC)$(OBJECTS) -o Apple
chapter1003_01.o: chapter1003_01.h
chapter1003_02.o: chapter1003_0.h
clean:
rm-f Apple core $(OBJECTS)
上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。
3.编译运行程序
当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。
[developer @localhost src]$ make
g++ -c -ochapter1003_01.o chapter1003_01.cpp
g++ -c -ochapter1003_02.o chapter1003_02.cpp
g++ chapter1003_01.o chapter1003_02.o -o Apple
[developer @localhost src]$ make submit
cp -f -r Apple ../bin
cp -f -r *.h ../include
[developer @localhost src]$ cd ../bin
[developer @localhost bin]$ ./Apple
apple
This is Apple price:3
The Fruit id:1
4.程序剖析
上述实例依然采用水果继承实例作为演示。基类水果类中定义三类不同权限控制成员,分别是公开接口成员,以及公开数据成员、保护数据成员与私有数据成员。实例目的是验证在其派生类Apple类中访问基类成员的控制规则。Fruit基类中公开接口分别为setFruitId()与showFruitId()两个方法成员。该方法成员主要访问其私有保护数据成员,设置以及打印该该保护成员表示的水果编号。
派生而来的Apple类中除了继承至Fruit类所有成员之外,同时添加属于Apple类的方法成员setApplePrice()与showApplePrice()。分别用来在Apple类中设置、打印继承到的保护数据成员m_price。主函数中首先定义Apple类对象。由于公有继承原先基类的公开成员,在派生类中非静态函数、友元以及派生类外部函数中可以直接使用,所以随后直接采用Apple类对象实例调用其继承基类的数据成员m_name并直接赋值,最后打印输出。
Apple对象实例apple调用设置苹果价格的方法成员。根据传入的实参来设置继承至基类保护类型数据成员,由于公开继承中保护成员在派生类中依然保持保护成员的访问控制特性。所以在派生类中自然可以通过其public类型方法成员操作。Apple对象实例调用完价格成员设置之后,调用其价格打印方法成员在屏幕上打印显示价格信息。
由于public类型继承方式,私有的成员在派生类中都不可以直接操作。所以只能通过继承至基类的公开接口来操作其私有保护成员。上述实例中Apple类对象实例调用继承至Fruit类的公开方法成员setFruitId以及showFruitId方法,分别设置该基类私有数据成员编号值以及屏幕打印该值。
10.3.2 protected继承
当派生类采用protected方式继承至基类时,基类中的public与protected成员在派生类中都自动转变为protected成员。即此时只能通过派生类的非静态成员函数以及其友元函数可直接访问。而基类的私有保护成员在派生类中则被隐藏,只能通过调用基类的公开成员以及保护成员来访问。
为了演示上述继承方式下成员访问控制,则上一小节实例可修改为protected继承方式。
1.准备实例
打开UE工具,创建新的空文件并且另存为chapter1004_01.h、chapter1004_01.cpp、chapter1004_02.h、chapter1004_02.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。
//水果类Fruit.h
#ifndef FRUIT_H
#define FRUIT_H
#include <iostream>
#include <string>
using namespace std;
class Fruit
{
public:
Fruit(){}
~Fruit(){}
void setFruitId(int id);
void setFruitName(string name);
void showFruitId();
void showFruitName();
public:
string m_name;
protected:
int m_price;
private:
int m_id;
};
#endif
//水果类源文件Fruit.cpp
#include "Fruit.h"
void Fruit::setFruitId(int id)
{
m_id = id;
}
void Fruit::setFruitName(string name)
{
m_name = name;
}
void Fruit::showFruitId()
{
cout<<"The Fruitid:"<<m_id<<endl;
}
void Fruit::showFruitName()
{
cout<<"The Fruitname:"<<m_name<<endl;
}
//苹果类头文件Apple.h
#ifndef APPLE_H
#define APPLW_H
#include "Fruit.h"
class Apple: protected Fruit
{
public:
Apple(){}
~Apple(){}
void setAppleInfo(int id,string name,intprice);
void showAppleInfo();
};
#endif
//苹果类源文件Apple.cpp
#include "Apple.h"
void Apple::showAppleInfo()
{
showFruitId();
showFruitName();
cout<<"This is Appleprice:"<<m_price<<endl;
}
void Apple::setAppleInfo(int id,string name,intprice)
{
m_price = price;
setFruitId(id);
setFruitName(name);
}
int main()
{
Apple apple;
apple.setAppleInfo(1,"Apple",3);
apple.showAppleInfo();
return 0;
}
本实例通过水果类单继承方式演示类继承中保护继承访问控制情况。程序由主函数与水果、苹果类组成。具体程序剖析见程序注释与后面的讲解。
2.编辑makefile
Linux平台下其makefile编译文件中涉及两个头文件以及两个源代码文件,makefile文件编辑如下。
OBJECTS=chapter1004_01.o chapter1004_02.o
CC=g++
Apple: $(OBJECTS)
$(CC)$(OBJECTS) -o Apple
chapter1004_01.o: chapter1004_01.h
chapter1004_02.o: chapter1004_02.h
clean:
rm-f Apple core $(OBJECTS)
submit:
cp-f -r Apple ../bin
cp-f -r *.h ../include
上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。
3.编译运行程序
当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。
[billing@localhost src]$ make
g++ -c -ochapter1004_01.o chapter1004_01.cpp
g++ -c -ochapter1004_02.o chapter1004_02.cpp
g++ chapter1004_01.o chapter1004_02.o -o Apple
[billing@localhost src]$ make submit
cp -f -r Apple ../bin
cp -f -r *.h ../include
[billing@localhost src]$ cd ../bin
[billing@localhost bin]$ ./Apple
The Fruit id:1
The Fruit name:Apple
This is Apple price:3
本实例程序中,采用protected继承方式。由于基类中public与protected成员在派生类中统一演变为protected成员,所以基类中定义的setFruitId、setFruitName、showFruitId、与showFruitName方法成员在派生类中只能通过派生类自定义方法成员访问。所以派生类中增加setAppleInfo与showAppleInfo方法用于调用基类中相关公开接口成员,便于外界通过派生类对象间接调用。
与上述实例不同的是,主程序中,基类public类型的数据成员m_name在派生类中已经变为protected成员,不能在派生类外部函数中直接访问。同时,基类公开的public成员也不能在派生类外部通过对象实例直接调用,而只能通过派生成员间接访问其基类公开成员。另外,基类中的私有数据成员也只能通过基类public与protected成员访问,protected继承方式实际上增加了了继承类中成员的访问控制力度。
10.3.3 private继承
类private继承方式则更加严格控制成员访问限度。基类的public成员在派生类中演变为private类型只能通过其非静态成员函数以及友元访问。另外,基类的protected成员在派生类中也演变为private私有类型,访问规则同上。基类中的private成员则与前面两种继承方式相同,派生类中此类数据被直接隐藏,只能通过基类公开以及保护方法成员去访问。
类中private继承方式实例初学者可以根据前两个继承方式来修改后,执行验证其运行结果。至此类的三种继承方式介绍完毕,初学者可以通过自定义实例来验证不同种情况下类成员的访问控制规则,不断的提高自身对于继承体系概念的认识。
10.4 继承体系中的构造与析构
C++中继承概念前面基本已经有一个大致的轮廓。但是其中还涉及到一个重要的部分,即基类、派生类的构造函数与析构函数定义执行的情况,下面将会就该主题通过完整实例作详细讲述。
10.4.1 派生类构造函数
继承体系中对象实例的构造有着一定的执行顺序,主要针对派生类对象实例构造时,基类构造以及派生类构造函数的执行情况作详细讲述。下面通过一个派生类对象构造实例作为开端。
1.准备实例
打开UE工具,创建新的空文件并且另存为chapter1005_01.h、chapter1005_01.cpp、chapter1005_02.h、chapter1005_02.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。
//水果类头文件chapter1005_01.h
#include <iostream>
using namespace std;
class Fruit
{
public:
Fruit();
voidshowFruitInfo();
};
//水果类源文件chapter1005_01.cpp
#include "chapter1005_01.h"
Fruit::Fruit()
{
cout<<"Fruitclass construction!"<<endl;
}
void Fruit::showFruitInfo()
{
cout<<"Thisis Fruit class!"<<endl;
}
//苹果类头文件chapter1005_02.h
#include "chapter1005_01.h"
class Apple: public Fruit
{
public:
Apple();
~Apple(){}
};
//苹果类源文件chapter1005_02.cpp
#include "chapter1005_02.h"
Apple::Apple()
{
cout<<"Appleclass construction!"<<endl;
}
int main()
{
Fruitfruit;
Appleapple;
apple.showFruitInfo();
return0;
}
本实例主要通过水果类继承方式演示继承体系中构造函数执行顺序情况。程序主要由主函数与水果、苹果类组成。程序具体剖析见程序注释与后面的讲解。
2.编辑makefile
Linux平台下需要编译源文件为chapter1005_01.cpp、chapter1005_02.cpp,相关makefile工程文件编译命令编辑如下所示。
OBJECTS=chapter1005_01.o chapter1005_02.o
CC=g++
Apple: $(OBJECTS)
$(CC)$(OBJECTS) -o Apple
chapter1005_01.o: chapter1005_01.h
chapter1005_02.o: chapter1005_02.h
clean:
rm-f Apple core $(OBJECTS)
submit:
cp-f -r Apple ../bin
cp-f -r *.h ../include
上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。
3.编译运行程序
当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。
[billing@localhost src]$ make
g++ -c -ochapter1005_01.o chapter1005_01.cpp
g++ -c -ochapter1005_02.o chapter1005_02.cpp
g++ chapter1005_01.o chapter1005_02.o -o Apple
[billing@localhost src]$ make submit
cp -f -r Apple ../bin
cp -f -r *.h ../include
[billing@localhost src]$ cd ../bin
[billing@localhost bin]$ ./Apple
Fruit class construction!
Fruit class construction!
Apple class construction!
This is Fruit class!
本实例共涉及两个类,即水果类与其派生类苹果类。其中水果类除了包含有自身的构造函数外还拥有一个打印本身类信息成员方法。其派生类Apple中,只拥有属于自身一个构造函数定义,另外就是继承至水果类的打印方法成员。
主程序中首先定义水果类对象实例,此时根据类构造规则如果没有自定义构造函数,则调用默认构造来构造对象实例。而此处基类自定义了构造函数,则调用自定义构造函数,该函数中执行体只有打印其执行构造的信息“Fruit class construction!”。
随后定义派生类对象,此时注意到,派生类对象实例构造会同时调用基类构造函数与派生类自身的构造函数,并且基类构造函数首先被执行,所以会先调用基类构造函数打印“Fruit class construction!”随后调用派生类构造函数打印信息“Apple class construction!”。最终调用继承至基类的打印类信息方法打印信息“This is Fruit class”从而结束程序运行。
从上述实例表明,基类对象构造时保持类构造特性,而派生类对象实例构造时会优先调用基类构造函数。原因在于派生类中继承有基类的成员,派生类对象构造时需要优先构造初始化基类成员以供派生类使用。派生类对象实例构造时,优先处理基类构造函数,如果基类没有自定义构造函数则直接调用其默认构造即可。另外基类的构造函数可以由派生类单独显式调用,上述实例修改如下所示。
1.准备实例
打开UE工具,创建新的空文件并且另存为chapter1006_01.h、chapter1006_01.cpp、chapter1006_02.h、chapter1006_02.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。
//水果类头文件chapter1006_01.h
#include <iostream>
using namespace std;
class Fruit //水果类Fruit定义
{
public:
Fruit(intid,string name); //水果类构造函数,两个整型参数
protected:
intm_id; //水果类保护数据成员,表示水果编号
stringm_name; //水果类保护数据成员,表示水果名称
};
//水果类源文件chapter1006_01.cpp
#include "chapter1006_01.h"
Fruit::Fruit(int id,string name) //水果类构造函数定义
{
m_id= id;
m_name= name;
cout<<"Fruitclass construction!"<<endl;
}
//苹果类头文件chapter1006_02.h
#include "chapter1006_01.h"
class Apple: public Fruit //继承至水果类的苹果类Apple定义
{
public:
Apple(intid,string name,int price); //苹果类构造函数
~Apple(){} //苹果类析构函数
voidshowAppleInfo(); //打印苹果类信息函数
protected:
intm_price; //苹果类保护数据成员,表示苹果价格
};
//苹果类源文件chapter1006_02.cpp
#include "chapter1006_02.h"
Apple::Apple(int id,string name,int price) //苹果类构造函数定义
:Fruit(id,name)
{
m_price= price;
cout<<"Appleclass construction!"<<endl;
}
void Apple::showAppleInfo() //打印苹果类函数定义
{
cout<<"Fruitid:"<<m_id<<endl;
cout<<"Fruitname:"<<m_name<<endl;
cout<<"Fruitprice:"<<m_price<<endl;
}
int main()
{
Fruitfruit(1,"Fruit"); //定义水果类对象实例,同时传入实参初始化
Appleapple(2,"Apple",3); //定义苹果类对象实例,同时传入实参初始化
apple.showAppleInfo(); //调用apple对象实例打印方法显示信息
return0;
}
本实例主要通过水果类继承方式演示继承体系中构造函数执行顺序情况。程序主要由主函数与水果、苹果类组成。程序具体剖析见程序注释与后面的讲解。
2.编辑makefile
Linux平台下其makefile编译文件中涉及两个头文件以及两个源代码文件,makefile文件编辑如下。
OBJECTS=Fruit.o Apple.o
CC=g++
Apple: $(OBJECTS)
$(CC)$(OBJECTS) -o Apple
Fruit.o: Fruit.h
Apple.o: Apple.h
clean:
rm-f Apple core $(OBJECTS)
上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。
3.编译运行程序
当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。
[developer @localhost src]$ make
g++ -c -ochapter1006_01.o chapter1006_01.cpp
g++ -c -ochapter1006_02.o chapter1006_02.cpp
g++ chapter1006_01.o chapter1006_02.o -o Apple
[developer @localhost src]$ make submit
cp -f -r Apple ../bin
cp -f -r *.h ../include
[developer @localhost src]$ cd ../bin
[developer @localhost bin]$ ./Apple
Fruit class construction!
Fruit class construction!
Apple class construction!
Fruit id:2
Fruit name:Apple
Fruit price:3
经过修改的实例中主要增加了基类的保护型数据成员以及自定义构造函数。其中,构造函数涉及两个基类成员的初始化工作。两个数据成员m_id与m_name分别表示水果类的编号与名称,构造函数定义中主要依据外界传入参数来初始化该数据成员。派生类中则除了继承至基类的保护型数据成员外,增加一个m_price数据成员表示水果的价格,同样其构造函数中实现根据参数传递初始化该值。
所不同的是,在派生类Apple构造函数定义中,增加了显示调用基类构造函数的操作,通过“:”初始化参数列表的方式来调用基类的构造函数。同时将根据调用派生类构造函数传入的实参来作为基类构造函数实参初始化。主程序中首先定义Fruit类对象实例fruit,同时传入编号以及名称值构造并初始化对象实例。随后定义派生类对象apple,此时传入三个实参用来构造初始化,其中,前两个参数用于显式调用基类构造函数作为实参传入并初始化。最后在派生类中增加打印基类以及派生类所有保护数据成员信息,验证构造初始化工作的正确性。
综上所述,继承体系中派生类构造对象实例时。首先调用基类的构造函数用于初始化基类成员。当基类没有自定义构造函数时,调用其默认构造函数。同时基类有多个构造函数重载时也可以在派生类中显式的指定调用基类哪个构造函数来构造对象实例。
10.4.2 派生类析构函数
C++类继承体系中析构函数的操作次序与其构造函数正巧相反。此时派生类的析构函数调用要在基类析构函数调用之前发生。尤其当基类中包含有对象成员时,通常是先调用对象成员构造函数后调用基类构造函数,最后调用派生类构造函数,而析构函数调用顺序刚好相反。修改上一小节实例增加析构函数简单说明继承中析构函数使用情况,修改后的代码如下所示。
1.准备实例
打开UE工具,创建新的空文件并且另存为chapter1007_01.h、chapter1007_01.cpp、chapter1007_02.h、chapter1007_02.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。
//水果类头文件chapter1007_01.h
#include <iostream>
using namespace std;
class Fruit
{
public:
Fruit(intid,string name);
~Fruit();
protected:
intm_id;
stringm_name;
};
//水果类源文件chapter1007_01.cpp
#include "chapter1007_01.h"
Fruit::Fruit(int id,string name)
{
m_id= id;
m_name= name;
cout<<"Fruitclass construction!"<<endl;
}
Fruit::~Fruit()
{
cout<<"Fruitclass destruction!"<<endl;
}
//苹果头文件chapter1007_02.h
#include "chapter1007_01.h"
class Apple: public Fruit
{
public:
Apple(intid,string name,int price);
~Apple();
voidshowAppleInfo();
protected:
intm_price;
};
//苹果类源文件chapter1007_02.cpp
#include "chapter1007_02.h"
Apple::Apple(int id,string name,int price)
:Fruit(id,name)
{
m_price= price;
cout<<"Appleclass construction!"<<endl;
}
Apple::~Apple()
{
cout<<"Appleclass destruction!"<<endl;
}
void Apple::showAppleInfo()
{
cout<<"Fruitid:"<<m_id<<endl;
cout<<"Fruitname:"<<m_name<<endl;
cout<<"Fruitprice:"<<m_price<<endl;
}
int main()
{
Fruit fruit(1,"Fruit");
Apple apple(2,"Apple",3);
apple.showAppleInfo();
return 0;
}
本实例主要通过水果类继承方式演示继承体系中析构函数执行顺序情况。程序主要由主函数与水果、苹果类组成。程序具体剖析见程序注释与后面的讲解。
2.编辑makefile
Linux平台下其makefile编译文件中涉及两个头文件以及两个源代码文件,makefile文件编辑如下。
OBJECTS=chapter1007_01.o chapter1007_02.o
CC=g++
Apple: $(OBJECTS)
$(CC)$(OBJECTS) -o Apple
chapter1007_01.o: chapter1007_01.h
chapter1007_02.o: chapter1007_02.h
clean:
rm-f Apple core $(OBJECTS)
submit:
cp-f -r Apple ../bin
cp-f -r *.h ../include
上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。
3.编译运行程序
当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。
[developer @localhost src]$ make
g++ -c -ochapter1007_01.o chapter1007_01.cpp
g++ -c -ochapter1007_02.o chapter1007_02.cpp
g++ chapter1007_01.o chapter1007_02.o -o Apple
[developer @localhost src]$ make submit
cp -f -r Apple ../bin
cp -f -r *.h ../include
[developer @localhost src]$ cd ../bin
[developer @localhost bin]$ ./Apple
Fruit class construction!
Fruit class construction!
Apple class construction!
Fruit id:2
Fruit name:Apple
Fruit price:3
Apple class destruction!
Fruit class destruction!
Fruit class destruction!
与上小节运行结果不同的是,本实例增加自定义析构函数中提示信息。因此在主程序中对象生命周期结束后,优先调用其派生类析构函数打印信息“Apple class Destruction!”。随后调用基类析构函数析构方法,最后因为基类对象实例最先被定义,所以基类析构函数最后被执行。
继承体系中构造析构函数的操作情况。建议初学者通过多写一些不同情况下处理构造析构的程序来验证总结继承体系中构造与析构执行顺序以及应用处理。
10.5 多重继承
C++中继承除了从单个基类派生以外还可以从多个基类派生继承创建新类。这种继承方式称为多重继承,下面将会详细讲述多重继承的基本应用情况。
所谓多重继承,即派生类继承至多个基类,并且拥有其多个基类的成员。多重继承方式与单继承方式基本语法实现形式相同,仅仅是在继承实现时通过逗号分隔符隔开多个基类名,其一般形式如下。
派生类: public/protected/private 基类1,基类2…
派生类继承至多个基类,即派生类中拥有多个基类成员,当然基类的构造函数与析构函数除外。多重继承除了在定义形式上与单继承稍有不同,其余访问控制等操作运用基本与单继承一致。下面通过一个完整实例演示前面小节分析的软件学院机构人员继承层次,其中行政人员同时又可以是老师的身份部分就包括多重继承的运用,即该类同时具有行政人员类与教师类的特征。
1.准备实例
打开UE工具,创建新的空文件并且另存为chapter1008_01.h、chapter1008_01.cpp、chapter1008_02.h、chapter1008_02.cpp、chapter1008_03.h、chapter1008_03.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。
//行政人员类头文件chapter1008_01.h
#ifndef ADMINISTRATION_H
#define ADMINISTRATION_H
#include <iostream>
#include <string>
using namespace std;
class Administration
{
public:
Administration(){}
~Administration(){}
voidsetAdministrationId(int id);
voidsetAdministrationName(string name);
protected:
intm_AdministrationId;
stringm_AdministrationName;
};
#endif
//行政人员类源文件chapter1008_01.cpp
#include "chapter1008_01.h"
void Administration::setAdministrationId(int id)
{
m_AdministrationId= id;
}
void Administration::setAdministrationName(stringname)
{
m_AdministrationName= name;
}
//教师类头文件chapter1008_02.h
#ifndef TEACHER_H
#define TEACHER_H
#include <iostream>
#include <string>
using namespace std;
class Teacher
{
public:
Teacher(){}
~Teacher(){}
voidsetTeacherId(int id);
voidsetTeacherName(string name);
protected:
intm_teacherId;
stringm_teacherName;
};
#endif
//教师类源文件chapter1008_02.cpp
#include "chapter1008_02.h"
void Teacher::setTeacherId(int id)
{
m_teacherId= id;
}
void Teacher::setTeacherName(string name)
{
m_teacherName= name;
}
//行政管理同时又是教师身份人员类头文件chapter1008_03.h
#ifndef ADMINISTRATIONTEACHER_H
#define ADMINISTRATIONTEACHER_H
#include "chapter1008_02.h"
#include "chapter1008_01.h"
class AdministrationTeacher:publicAdministration,public Teacher
{
public:
AdministrationTeacher(){}
~AdministrationTeacher(){}
voidshowAdministrationTeacherInfo();
};
#endif
//行政人员同时又是教师身份人员类的源文件chapter1008_03.cpp
#include "chapter1008_03.h"
voidAdministrationTeacher::showAdministrationTeacherInfo()
{
cout<<"TheAdministration id:"<<m_AdministrationId<<endl;
cout<<"TheAdministration name:"<<m_AdministrationName<<endl;
cout<<"TheTeacher id:"<<m_teacherId<<endl;
cout<<"TheTeacher name:"<<m_teacherName<<endl;
}
int main()
{
AdministrationTeacheradministrationTeacher;
administrationTeacher.setAdministrationId(1);
administrationTeacher.setAdministrationName("jack");
administrationTeacher.setTeacherId(2);
administrationTeacher.setTeacherName("john");
administrationTeacher.showAdministrationTeacherInfo();
return0;
}
本实例程序主要通过教师类继承体系演示了多重继承的功能。程序主要由主函数与教师继承类组成,教师继承类体系中包含学校行政人员类、教师类以及两者身份兼有的类。程序具体剖析见程序注释与后面讲解。
2.编辑makefile
Linux平台下其makefile编译文件中涉及三个头文件以及三个源代码文件,makefile文件编辑如下。
OBJECTS=chapter1008_01.o chapter1008_02.ochapter1008_03.o
CC=g++
chapter1008_03: $(OBJECTS)
$(CC)$(OBJECTS) -o chapter1008_03
chapter1008_01.o: chapter1008_01.h
chapter1008_02.o: chapter1008_02.h
chapter1008_03.o: chapter1008_03.h
clean:
rm-f chapter1008_03 core $(OBJECTS)
submit:
cp-f -r chapter1008_03 ../bin
cp-f -r *.h ../include
上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。
3.编译运行程序
当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。
[developer @localhost src]$ make
g++ -c -ochapter1008_01.o chapter1008_01.cpp
g++ -c -ochapter1008_02.o chapter1008_02.cpp
g++ -c -o chapter1008_03.o chapter1008_03.cpp
g++ chapter1008_01.o chapter1008_02.ochapter1008_03.o -o chapter1008_03
[developer @localhost src]$ make submit
cp -f -r chapter1008_03 ../bin
cp -f -r *.h ../include
[developer @localhost src]$ cd ../bin
[developer @localhost bin]$ ./chapter1008_03
The Administration id:1
The Administration name:jack
The Teacher id:2
The Teacher name:john
多重继承派生类拥有多个基类的所有成员,即派生类拥有多个基类的特性。上述实例中根据软件学院人员机构分析,有一类人员同时兼顾着行政人员与教师的特性,此时分析继承层次时可以将其定义为多重继承的方式。
本实例中总共涉及三个类,Administrtion、Teacher与AdministrationTeacher分别表示行政人员类、教师类以及兼顾行政管理以及教师人员类。其中AdministrationTeacher类继承至Administrtion与Teacher类。
而Administration类中除了构造与析构函数同时还包含两个公开的接口成员分别为,设置行政人员编号与设置行政人员的姓名的方法,另外还包含两个私有数据成员,分别表示行政人员的编号与姓名。而Teacher类中同样包含两个公开接口成员分别为,设置教师人员编号以及教师人员姓名的方法,同时也包含两个私有数据成员分别表示教师编号与姓名。最终继承至前两个类的AdministrationTeacher类在继承所有的方法成员之外,还包含该类添加的公开成员方法用于打印所设置的编号以及姓名信息。
实例主程序中首先定义AdministrationTeacher类对象实例administrationTeacher,随后使用该对象实例调用其继承至基类的方法成员setAdministrationId、setAdministrationName、setTeacherId以及setTeacherName,并且根据传入实参设置该类中的保护型数据成员。由于基类中数据成员为保护类型,在继承类中依然保持保护类型的特性,那么最后对象实例administrationTeacher调用派生类自定义方法showAdministrationTeacherInfo,该方法中访问了基类中的保护成员,用于在屏幕上显示设置的成员信息。
多重继承在C++应用软件开发中并不常用。因为其继承方式的使用不当会增加软件复杂出错的可能性,同时也可以会影响软件处理的效率。最常见的当多个基类中拥有同个方法成员,而这些基类被派生类继承的方式又不相同时,处理会变得复杂难懂。所以除非必须的处理场景下,一般不建议使用。