220 likes | 401 Views
第十四章. 继承和多态 -2. 回顾. 继承和派生的基本概念 继承定义 派生类重定义基类函数 继承关系中构造函数、析构函数之间的关系. 目标. 基类和派生类对象的转换 类指针的使用 多重继承 复合与继承的关系 虚基类概念 虚基类构造函数 虚继承. 静态成员与继承. 静态成员解决数据共享的问题 实现共享:在类外设置全局变量,但是,全局变量都有局限性 , 破坏类的完整性 。 类的静态数据成员可以在类的范围内存储数据,这种数据独立于类类型中的任何对象,但可以由这些对象访问。 静态成员函数有一种独立于单个类对象的计算能力。
E N D
第十四章 继承和多态-2
回顾 • 继承和派生的基本概念 • 继承定义 • 派生类重定义基类函数 • 继承关系中构造函数、析构函数之间的关系
目标 • 基类和派生类对象的转换 • 类指针的使用 • 多重继承 • 复合与继承的关系 • 虚基类概念 • 虚基类构造函数 • 虚继承
静态成员与继承 • 静态成员解决数据共享的问题 实现共享:在类外设置全局变量,但是,全局变量都有局限性,破坏类的完整性 。 • 类的静态数据成员可以在类的范围内存储数据,这种数据独立于类类型中的任何对象,但可以由这些对象访问。 • 静态成员函数有一种独立于单个类对象的计算能力。 • 静态成员函数的优点在于: 即使不存在类的对象,它们也存在,并且可以调用 • 基类的静态成员没有所谓的继承 • 基类和其派生类共同拥有静态成员 • 静态成员的存取能通过基类、派生类基类::静态数据成员基类::静态成员函数(参数表) 派生类::静态数据成员派生类::静态成员函数(参数表)
friend与继承 • 基类的friend类/函数在继承后不能存取派生类的成员,但是可以存取派生类对象中基类部分的成员.
类指针 • 类名也可以用来声明指针 • 类的指针可以操作类的对象,也可以操作派生类的对象(派生类对象也是基类对象) • 派生类对象和基类对象可以通过指针统一操作和管理
类指针的使用 • 类指针操作类对象的几种可能 • 基类指针操作基类对象(自然) • 派生类指针操作派生类对象(自然) • 基类指针操作派生类对象——把派生类对象作为基类对象看(安全) • 派生类指针操作基类对象——把基类对象作为派生类对象看(危险)
派生类对象基类对象的转换 • 当派生类以Public方式继承基类时,编译器可自动执行的转换(向上转型 upcasting 安全转换) • 派生类对象自动转换为基类对象(特有的成员消失) • 派生类对象指针自动转化为基类对象指针 • 派生类对象引用自动转化为基类对象引用 • 当派生类以private/protected方式继承基类时 • 派生类对象指针(引用)转化为基类对象指针(引用)需用强制类型转化(基类&)派生类对象 (基类*)派生类对象指针 • 不能把派生类对象强制转换为基类对象
基类对象派生类对象的转换 • 基类对象指针(引用)可用强制类型转换为派生类对象指针(引用), 而基类对象无法执行这类转换. • 向下转型不安全,没有自动转换的机制 • 可以使用static_cast运算符进行强制类型转换,但仅限派生类以public方式继承基类.
继承中的类型转换6-1 //文件people.h #if !defined __PEOPLE__H__ #define __PEOPLE__H__ class People { public: People(char *str, int s); //构造函数中参数s大于0表示男性,否则为女性 ~People(); int getSex(); char* getName(); private: char *name; int sex; }; #endif
//文件people.cpp #include <string.h> #include <iostream.h> #include "people.h" People::People(char *str, int s){ name = new char[strlen(str)+1]; strcpy(name, str); if (s>0) sex = 1; //男性 else sex = 0; //女性 } People::~People(){ delete []name; } int People::getSex(){ return sex; } char * People::getName(){ static char str[128]; strcpy(str,name); return str; } 继承中的类型转换6-2
继承中的类型转换6-3 //文件teacher.h #include "people.h" #if !defined TEACHER_H #define TEACHER_H class Teacher : public People { public: Teacher(char *str, int s, char *sch, int y); ~Teacher(); int getWorkYears(); char *getSchool(); private: char *school; int years; }; #endif
#include <string.h> //文件 teacher.cpp #include <iostream.h> #include "teacher.h" Teacher::Teacher(char *str,int s, char *sch,int y):People(str, s) { //调用基类的构造函数 school = new char[strlen(sch)+1]; strcpy(school,sch); years = (y>0) ? y : 0; } Teacher::~Teacher(){ delete []school; } int Teacher::getWorkYears(){ return years; } char * Teacher::getSchool(){ static char str[1024]; strcpy(str,school); return str; } 继承中的类型转换6-4
#include <iostream.h> #include "teacher.h" int main() { People p("Zhang San", 1), *pptr; Teacher t("Li Si",0,"Wuhan University",4), *tptr; //用基类指针操作对象 pptr = &p;//用基类指针指向基类对象 cout<< "People p: " << pptr-> getName() << ", " << (pptr->getSex()?"male":"female") <<endl<<endl; pptr=&t;//用基类指针指向派生类对象 cout<<"Teacher t: “ <<pptr->getName() << ", “ <<(pptr>getSex()?"male":"female") <<endl; //要调用定义于派生类中的函数必须进行类型的强制转换 cout<<"\tin "<< ((Teacher*)pptr)->getSchool() <<"for "<< ((Teacher*)pptr)->getWorkYears() <<" years." << endl << endl; 继承中的类型转换6-5
继承中的类型转换6-6 //用派生类指针操作对象 tptr = (Teacher*)&p; //用派生类指针指向基类对象 cout<<"People p: " << tptr->getName() << ", " <<(tptr->getSex()?"male":"female")<<endl; //危险,访问了不存在的属性 cout<<"\tin " << tptr->getSchool() << " for " << tptr->getWorkYears()<<" years."<< endl; tptr = &t; //用派生类指针指向派生类对象 //调用基类中定义的函数 cout<<"People p: " << tptr->getName() << ", " <<(tptr->getSex()?"male":"female")<<endl; cout<< "\tin " << tptr->getSchool() << " for " <<tptr->getWorkYears()<<" years."<<endl; return 0;}
多重继承与二义性 • 单重继承——一个派生类最多只能有一个基类 • 多重继承——一个派生类可以有多个基类 • Class 类名: 继承方式 基类1,继承方式 基类2,….{….}; • 派生类同时继承多个基类的成员,更好的软件重用 • 可能会有大量的二义性,多个基类中可能包含同名变量或函数 • 多重继承中解决访问歧义的方法 基类名::数据成员名(或成员函数(参数表)) • 明确指明要访问定义于哪个基类中的成员 • 多重继承时派生类构造时,基类构造函数的调用顺序与派生类声明的继承顺序相同,而与成员初始化序列的顺序无关。而析构函数的调用顺序和构造函数的调用顺序相反。
复合的意义 • 复合描述对象之间相互连接的结构关系 • 复合关系聚合:描述对象间的整体和部分的关系组合:特殊的聚合,聚合表现一般概念,而组合更进一步表达当整体消失时部分也一并消失。 • 对于复合的应用当前还没有定论, 因为现实世界中对象间的连接关系在不同角度或者不同情况下存在不同解读, 除非小组成员对于复合关系有相当的共识应当避免使用复合关系。 • 含复合对象的派生类对象的构造函数的调用顺序基类 复合对象(按照类内声明顺序) 派生类对象本身的构造函数。
复合与继承的关系 • 复合与继承 • 代码重用的两种方式 • 都可以使新类重用已有类的数据和函数 • 有助于逐步开发功能越来越强大的程序 • 使用复合还是继承? • 依据是实体之间的关系和程序设计的需要 复合关系的UML描述
类的虚拟继承 • 定义一个类A以虚拟继承的方式继承另一个类B, 只需在继承方式前或后加上virtual修饰词即可class A: virtual 继承方式 Bclass A: 继承方式 virtual B • 虚拟继承主要解决多重继承时某个类别可能被重复继承的问题. • 虚拟继承下的两个父类如果对某个祖父类级的成员权限不同时,派生类采用保护级别较低(较公开的)为准. • 虚拟继承自动获取最近的基类的重写的成员方法.
虚基类 • 当某类的部分或全部直接基类是从另一个共同基类中派生而来时. 可以考虑把共同基类设置为虚基类. • 虚基类是在其派生类的声明过程中声明的.即虚拟继承的定义.
虚基类构造函数 • 整个继承结构中, 直接或者间接继承虚基类的所有派生类中都必须在构造函数的成员初始化列表中列出对虚基类的初始化(除虚基类的默认构造方法外) • 虚基类的构造函数只会调用一次, 即被最底层的派生类的构造函数调用,而最底派生类的其他基类对虚基类的构造函数的调用被自动忽略.
总结 • 基类和派生类对象的转换 • 类指针的使用 • 多重继承 • 复合与继承的关系 • 虚基类概念 • 虚基类构造函数 • 虚继承