Part7.Derived Classes
数据抽象:现实生物中的共同属性抽取出来,来建立模型。
继承性和封装性是面向对象的真谛,所以在这之前要重点掌握!!!
CLASS relationships:
IS A -- By Inheritance e.g:”a manager is an employee“
HAS A -- By Composition e.g: ”a person has a name“
派生类:由已有的基类基础上派生出来的。
例子:A program dealing with people employed by a company
问题在于一个公司会有中层经理,但也会有更高层经理,而list中数据类型是不能变化的。显然这两类是没法区分的。
所以我们要告诉编译器,经理既是员工同时也是经理,这样才能放入group中。
struct Manager : public Employee
{
list<Employee*> group;
short level;
…
};
Base class – Employee
Derived class – Manager
•A derived object can be used wherever a base class is acceptable.
接下来我们就可以用一个derived的指针去指向一个base的指针,但是不能反向指向,
问题很明显:派生类的成员,基类并不是都有的,就会出现灾难性的访问error!
当进行地址赋值的时候,要注意强制类型转换static_cast。
void g(Manager mm, Employee ee)
{
Employee* pe{&mm}; //coercion
Manager* pm{&ee}; //error
pm->level=2; //disaster
pm=static_cast<Manager*>(pe);
pm->level=2;
}
class Employee;//this is a declaration
class Manager:public Employee{
… // error, Employee must be
// defined before it is used.
};
基类的public属性在被继承的派生类中就会被转化为protected
protected是不能外界访问的。
基类的同名函数是不会被派生类的函数重载的,只会被覆盖,想要调用则需要加上解析运算符。
void Manager::print() const
{
Employee::print(); // print Employee infor mation
cout << level << … ;
}
构造函数怎么写?
还要注意如果基类具有构造函数,则必须调用构造函数。 基类构造函数的参数在派生类构造函数的定义中指定为初始值设定项。 构造函数永远不会被继承。
classname::classname(parameterlist)
: baseclassname(argumentlist),
member1(argumentlist ),
member2(argumentlist)…
{
…
}
实现方法:
class Employee
{ …
Employee(const string& n,Date dd,int d);
…
};
class Manager : public Employee
{ …
Manager(const string& n,Date dd,int d,int lvl);
…
};
Employee::Employee(const string& n,Date dd,int d)
:name(n), hiring_date(dd), department(d) { }
Manager::Manager(const string& n,Date dd,int d,int lvl)
: Employee(n,dd,d), level(lvl){ }
·拷贝构造函数
派生类永远不会继承赋值运算符和复制构造函数。您应该自己定义复制派生类的语义。 如果将派生类对象复制到基对象中,则会出现切片问题。 使用基指针或基引用可以避免切片问题。
void f(const Manager& m)
{
Employee e{m}; // slicing
e = m; // slicing
}
/* level and group members are ignored */
如何避免slicing problem?
Employee* or Employee&
给定一个 Base*,指向的对象真正属于哪个派生类型?
1. limit that only objects of a single type are
pointed to. ( homogeneous objects )
2. place a type tag and set it in every object. (heterogeneous objects)
3. use virtual functions. (heterogeneous objects)
异构对象无法处理,使用类型字段是一种容易出错的技术,会导致维护问题,所以最好的办法是使用虚拟函数
虚拟函数允许程序员在基类中声明函数,这些函数可以在每个派生类中重新定义。 编译器和链接器将保证对象与应用于它们的函数之间的正确对应关系。 实现此任务的技术称为动态绑定或运行时绑定。
实现代码:
class Employee
{ string name;
Date hiring_date;
short department;
…
public:
virtual void print() const;
string get_name() const
{ return name; }
…
};
class Manager:public Employee
{ list<Employee*> group;
short level;
…
public:
void print() const;
/* virtual can be omitted here.*/
…
};
void display_list(const list<Employee*>& s)
{
for (auto p : s) p->print();
}
/* each object will be displayed according to its own type.
*/
这里引出了一个新的概念 Polymorphism(多态性)!
从基类的函数中获取正确的行为,而与实际使用的对象类型无关,这称为多态性。 具有虚函数的类型称为多态类型。 若要获得多态行为,调用的成员函数必须是虚拟的,并且必须通过指针或引用操作对象。
给一个例子:
void f(const Manager& m, const Employee& ee)
{ Employee e{m};
e.print(); // Employee::print
const Employee* pe{&m};
pe->print(); // Manager::print
pe = ⅇ
pe->print(); // Employee::print
const Employee& re = m;
re.print(); // Manager::print
}