380 likes | 600 Views
吴 昊. Visual C++ 与面向对象程序设计教程. 继承与派生. 第八章. 在 C++ 语言中通过 派生 机制来实现类的继承性。可以从已有的类中派生出一个新的类,对原有类的特性进行 修改 或 继承 。 C++ 语言中的 派生类 是实现 继承性 的基础。从任何已有的类,无论是 C++ 语言标准类库提供的类,还是程序员自己定义的类,都可以派生出新的类,这个新的类称为 派生类 或 子类 ,而产生派生类的类则称为 基类 或 父类 。派生类可以在基类的基础上包含 新 的成员;在新的类中 隐藏基类 的任何函数成员;或者为新的类 重新定义 函数成员。
E N D
吴 昊 Visual C++ 与面向对象程序设计教程 • 继承与派生 第八章
在C++语言中通过派生机制来实现类的继承性。可以从已有的类中派生出一个新的类,对原有类的特性进行修改或继承。在C++语言中通过派生机制来实现类的继承性。可以从已有的类中派生出一个新的类,对原有类的特性进行修改或继承。 C++语言中的派生类是实现继承性的基础。从任何已有的类,无论是C++语言标准类库提供的类,还是程序员自己定义的类,都可以派生出新的类,这个新的类称为派生类或子类,而产生派生类的类则称为基类或父类。派生类可以在基类的基础上包含新的成员;在新的类中隐藏基类的任何函数成员;或者为新的类重新定义函数成员。 理解继承是理解面向对象程序设计所有方面的关键。
主要内容 继承与派生 1 类模板 派生类的继承方式 5 2 INHERITANCE AND DERIVE 静态成员 4 派生类的构造函数与析构函数 3
继承与派生 1 • 继承性在客观世界中是一种常见的现象。例如,一个人与他的兄弟、姐妹一样,在血型、肤色、身材、相貌等方面都具有其父母的某些生理特征,同时他又有与其兄弟、姐妹相区别的特征。 • 从分类学的观点来看,要为一个新发现的物种定义一个新的类,首先要确定与它最接近的既有的类是什么。在定义新类的时候,只要将新类定义为最接近既有类的子类,就可继承(无须重复描述)既有类的一切特性,对新类只要给出不同于既有类的那些特性的说明即可。
继承与派生 1 从面向对象程序设计的观点来看,继承所表达的正是这样一种类与类之间的关系,这种关系允许在既有类的基础上创建新类。也就是说,定义新类时可以从一个或多个既有类中继承(即拷贝)所有的数据成员和函数成员,然后加上自己的新成员或重新定义由继承得到的成员。这就形成了一种类的层次关系,既有类称为基类或父类,以它为基础建立的新类称为派生类,导出类或子类。
继承与派生 1
继承与派生 1 通过继承可以在一个一般类的基础上建立新类,如果一个类只有一个基类称为单继承,否则称为多继承。 从编码角度讲,从基类中派生类以较低代价换来了大量的灵活性。一旦产生了可靠的基类,只需要调试派生类中所做的修改即可。 派生类从它的父类中继承性质时,可使派生类对它们进行扩展、限制、改变、删除或不做任何修改。
继承与派生 1 派生类的定义形式与普通类定义形式基本相同,不同的是必须在类声明的头部指明它的基类,其基本形式如下: class派生类名:继承方式标识符 基类名 { 派生类增加的成员声明; }; 多重继承的定义方式在教材p121页已经给出,其有关内容请大家课后自学,课堂上只讲述单继承。
继承与派生 1 class First { int value_1; protected: int value_2; public: int value_3; }; class Second : public First { int value_4; public: void F2(); }; public:基类中所有public成员在派生类中仍为public成员。
继承与派生 1 访问控制用于决定在基类中声明的数据成员和成员函数在多大范围内能被派生类的对象使用。基类成员的访问控制权限有三种:public, protected和private。 派生类的继承方式也有三种:public, protected和 private;在没有给出继承方式字的情况下,缺省为private。 protected: 保护继承,基类成员在派生类中的访问权限是保护的;public: 公有继承,基类成员在派生类中的访问权限不变;private: 私有继承,基类成员在派生类中的访问权限是私有的。 下面构造一个简单的例子来说明派生类的定义方法。 ch0801
派生类的继承方式 2 派生类继承了基类的除构造函数、析构函数外的所有成员,通过不同的继承方式,派生类可以调整自身成员及其派生类的使用者对于基类的访问控制权限。 公有继承 1 私有继承 保护继承 3 2 派生类 继承方式
公有继承 派生类的继承方式 2 基类的public成员为派生类的public成员;即派生类可以访问自身的public成员一样访问基类的public成员。 基类的private成员仍保持private;即派生类内部无法直接访问。派生类的使用者也无法通过派生类直接访问基类的private成员。 基类的protected成员仍为protected;即派生类可以访问自身的protected成员一样访问基类的protected成员。但是,派生类的使用者(main函数)也无法通过派生类直接访问基类的protected成员。 教材实例0801
公有继承 派生类的继承方式 2 显然,通过实例,我们知道派生类通过public派生得到的成员还可以继续被它的子类继承。另外,C++编译器规定派生类只能继承基类的public和protected成员,不能继承基类中的private成员。
私有继承 派生类的继承方式 2 基类的public成员和protected成员为派生类的private成员;即派生类只能通过自身的函数去访问他们。 基类的private成员,无论派生类内部成员或派生类的使用者都无法直接访问。 显然,通过private派生得到的派生类再派生出子类时,该派生类的基类成员不能被其子类所继承,这样大大限制了派生类的作用,所以,在实际应用中,private继承方式很少使用。 教材实例0802
保护继承 派生类的继承方式 2 基类的public成员和protected成员为派生类的protected成员;即派生类或其子类能通过自身的成员函数去访问他们。 基类的private成员,无论派生类内部成员或派生类的使用者都无法直接访问。 protected继承与private继承的差别体现在当前派生类进一步派生的子类中;但是在当前派生类这一层次上,两者没有差别。另外,派生类的使用者(main函数)不能直接访问基类的public成员。 参见实例ch0801
访问权限的调整 派生类的继承方式 2 在private继承的情况下,基类成员在派生类中具有private成员的特性。在派生类中使用“基类名::基类成员名”对访问权限进行调整。将基类中的public成员调整为在派生类中仍为public或protected,基类中的protected成员在派生类中仍为protected或public。且不能写出其成员函数的返回值、参数;同时,若有多个重载函数,只要按格式写出,就调整了多个重载函数的访问控制。 参见实例ch0802
派生类 派生类 基类 基类 小结 派生类的继承方式 2 基类与派生类的关系 C++语言中,基类的public成员缺省情况下的继承方式是private,而private成员在派生类的成员函数中是不能访问的。 在基类与派生类的例子中,派生类可以对基类的属性(特征)作修改;派生类对象的外部(即派生类的使用者,main函数中)不能直接访问protected成员函数。 基类数据成员与派生类数据成员的关系:基类的私有成员不能被继承
小结 派生类的继承方式 2 注意区分派生类的对象和派生类中的成员函数对基类的访问
class First { int value_1; protected: int value_2; public: int value_3; }; class Second:First { int value_4; public: void F2( ) { cout<<value_3; } }; 问:在F2( )函数中可以访问的成员变量有那些?
派生类的构造函数与析构函数 3 类实例化时,需要调用其构造函数。如果该类是派生类,必须调用其父类的构造函数,如果其父类也是派生出来的,重复该过程直至到达非派生的基类。 编译器调用构造函数的次序:基类 成员对象 派生类 编译器调用析构函数的次序:基类 成员对象 派生类 析构函数的调用顺序与构造函数的调用顺序相反。 教材实例0803
派生类的构造函数与析构函数 3 初始化基类成员 由于派生类继承了基类的一些成员,因此在创建派生类对象时,对派生类中的数据成员和基类的数据成员必须进行初始化。从基类中继承的数据成员可以使用基类的构造函数来完成初始化,而派生类中新增的数据成员的初始化要由自身的构造函数来完成,同时在撤销派生类对象时,也要通过基类的析构函数对派生类中继承的数据成员进行处理。
派生类的构造函数与析构函数 3 构造函数的格式 派生类构造函数(参数总表): 基类1构造函数(参数表), …基类n构造函数(参数表),内嵌对象成员1的构造函数(参数表),… 内嵌对象成员n的构造函数(参数表) {… … } 析构函数 在派生类中是否要定义析构函数与它所属的基类无关。基类的析构函数不会因为派生类没有析构函数而得不到执行,它们是各自独立的,因为析构函数是不带参数的,仅负责释放对象。
静态成员 4 当类创建多个对象时,通常将类的所有成员复制一份给这些对象,这些对象中的每一个成员都有各自的内存空间。有时这是一种浪费。在有些情况下,我们需要特定的成员(数据或函数)为类的所有对象共享,声明为static的类成员便能实现这个目的,该成员称为静态成员。静态成员分为静态数据成员和静态成员函数两种。 静态数据成员与静态类型的数据定义方式一样,即在成员数据的定义前加关键词static。同时,静态成员数据也必须有确定的值。但是不能在构造函数中给静态成员数据赋初值。
静态成员 4 void main( ) { file file1; cout<<CFile::filecount; //通过类的名字来访问 ……} class CFile { …… public: staticint filecount; …… }; 只要CFile类被定义了,则filecount就存在,不管有没有定义对象访问静态成员,即创建对象前就已存在并可以使用。 静态数据成员初始化:必须在程序全局区进行,即在所有的函数定义体外进行初始化。 int CFile::filecount=0; 参见实例ch0803
静态成员 4 可以将静态数据成员定义为private、protected、public,对于public,既可以通过类的对象进行访问,也可以通过类名直接访问,格式为: <类名>::<静态成员数据>,这是区别于一般成员数据的特点。private的和protected的静态数据成员则只能被该类的成员函数访问。 参见实例ch0803
静态成员 4 静态成员函数同静态数据成员一样,属于整个类,为该类所有对象共享,定义静态成员函数时只要在成员函数名前加上static即可。 静态成员函数首先是一个成员函数,可以在类的定义体内进行定义为内联函数,也可以在体外定义。另外,静态成员函数是特殊的成员函数,不属于特定的对象,因此不含this指针,所以就不能像一般成员函数那样直接访问对象中的非静态成员(数据成员和成员函数),也就是说,静态的成员函数只能访问该类的静态成员(数据成员和成员函数)、全局变量、常量、外部函数等。 参见实例ch0803
静态成员函数访问非静态成员时,必须通过对象进行,即必须通过参数传递方式得到对象名,然后通过对象名来访问。如果访问控制权限允许的话(即为 public),可以在程序内部不依赖于任何对象按下述格式调用静态成员函数: 一般格式: 类名:: 静态成员函数名(<参数表>) 静态成员 4
小结 静态成员 4 使用静态数据成员,实际上可以消灭全局变量。全局变量给面向对象程序带来的问题就是违背封装原则。要使用静态数据成员必须在main()程序运行之前分配空间和初始化。使用静态数据成员,可以在实际创建任何对象之前初始化专有的静态数据成员。静态成员不与类的任何特定对象相关联。 静态数据成员确实是在程序一开始运行时就已经存在了。因为函数在程序运行中被调用,所以静态数据成员不能在任何函数内分配空间和初始化。这样,它的空间分配有3个可能的地方:一是作为类的外部接口的头文件,那里有类声明;二是类定义的内部实现,那里有类的函数成员定义;三是应用程序的main()函数前的全局数据声明和定义处。
小结 静态成员 4 静态成员的static一词与静态存储的static是两个概念,前者涉及类,后者涉及内存空间的位置以及作用域限定,因此要区分静态成员和静态存储。
类模板 5 类模板代表着一类类,它允许用户为类定义一种模式,使得类中的某些数据成员、某些成员函数的参数和某些成员函数的返回值,能取任意类型(包括系统预定义的和用户自定义的)。 如果一个类中数据成员的类型不能确定,或者是某个函数成员的参数或返回值的类型不能确定,就必须将此类声明为类模板。 1 2
类模板的一般定义形式如下: template < class <类型参数表> > class ClassName { 类声明体; } 类模板定义不是一个实实在在的类,只是对类的描述(称为类模板)。类模板建立后,可以用下面的方式创建类模板的实例。 ClassName <类型实参表>; 类模板 5
template < class T> //声明一个模板 class CVector //CVector 是类模板名 { T* date; int size; public: CVector(int); T& opreator[ ](int); CVector operator[ ](int) }; 类模板 5 例如: // 在类定义中,使用通用数据类型的成员 // 或函数参数时需要在它前面加上模板参数T
在类定义体外定义成员函数时,如果该成员函数中有模板参数存在,则需要在函数体外进行模板声明,并且在函数名前的类名后加上“<T>”。在类定义体外定义成员函数时,如果该成员函数中有模板参数存在,则需要在函数体外进行模板声明,并且在函数名前的类名后加上“<T>”。 类模板 5 例如: template <class T> //函数体外进行模板声明 CVector<T>::operator[ ](int n) { }; template <class T> //函数体外进行模板声明 T& CVector<T>::operator[ ](int i) { }; 函数名前的类名后加上“<T>”
类模板 5 类模板的使用实际上是将类模板实例化成一个具体的类,它的格式为:类名 <实际的类型>; 类模板使我们在声明一个类时,能够将用于实现这个类所需要的数据类型参数化。类模板特别适合定义具有包容性质的类。所谓包容性质的类是指这样一种类,它的对象象一个容器,用于盛放其它类型的对象,容器对象的操作特性与所盛放的对象的类型无关或很少有关系。 例如栈操作。 教材实例vc0804
模板是一种安全的、高效的重用代码的方式。它被用于参数化类型,在创建对象或函数时所传递的类型参数可以改变其行为。每个模板类的实例是一个实际的对象,可以像其它对象一样使用,甚至可以作为函数的参数或返回值。模板是一种安全的、高效的重用代码的方式。它被用于参数化类型,在创建对象或函数时所传递的类型参数可以改变其行为。每个模板类的实例是一个实际的对象,可以像其它对象一样使用,甚至可以作为函数的参数或返回值。 类模板 5
使用模板应注意以下几点: (1) 在每个模板定义之前,不管是类模板还是函数模板,都需要在前面加上模板声明语句template < class T>。 (2) 在使用模板时,必须在名字后面加上模板参数<T>, 如CList<T>和CNode<T>。 (3) 对象是类的特例,类又可以看作类模板的特例。类模板与函数模板有同样的应用目的。 (4) 类模板与函数模板不同的是:①由函数模板生成的模板函数的调用是由编译器自动决定的,而类模板的解释由程序设计者自行指明;②格式定义不同。 类模板 5
继承与派生 • 第八章 全章小结 首先介绍了面向对象程序设计中继承与派生的概念以及具体应用,然后重点介绍了派生类的三种继承方式 (public、private和protected继承方式) ,派生类的构造函数与析构函数,类的静态成员(数据成员和成员函数),最后介绍了类模板。
Visual C++ Assignments: Page 139: 2, 4, 5. Thank you, everybody.