Chapter15:派生类

在C++语言中,基类将类型相关的函数与派生类不做改变直接继承的函数区别对待,对于某些函数,基类希望它的派生类各自定义适合自身的版本,此时基类就将这些函数声明为虚函数。

 

派生类必须将其继承而来的成员函数中需要覆盖的那些重新声明。不同于之间的成员函数,虚函数必须得定义。

 

因为在派生类对象中含有与其基类对应的组成部分,所以我们能把派生类的对象当成基类对象来使用,即:可以将基类的指针或引用绑定到派生类对象中的基类部分。

 

Best Practice:遵循基类的接口

每个类负责定义各自的接口。要想与类的对象交互必须使用该类的接口,即使这个对象是派生类的基类部分也是如此。

因此,派生类最好遵循基类的接口,并且通过调用基类的构造函数来初始化那些从基类中继承而来的成员。

 

继承与静态成员

如果基类定义了一个静态成员,则在整个继承体系中只存在该成员的唯一定义。无论从基类中派生出来多少个派生类,对于每个静态成员来说都只存在唯一的实例。

 

派生类的声明:只包含类名,但是不包含它的派生列表。

一条声明语句的目的是令程序知晓某个名字的存在以及该名字表示一个什么样的实体,如一个类、一个函数、或一个变量等。派生列表以及与定义有关的其他细节必须与类的主体一起出现。

 

如果我们想将某个类用作基类,则该类必须已经定义而非仅仅声明。原因是:派生类中包含并且可以使用它从基类继承而来的成员,为了使用这些成员,派生类当然要知道它们是什么。->一个类不能派生它本身。

C++11新标准提供了一种防止继承发生的方法,即在类名中跟一个关键字final。

 

  • 类型转换:

不存在从基类到派生类的隐式类型转换,在对象之间不存在类型转换。

如果在基类中含有一个或多个虚函数,我们可以使用dynamic_cast请求一个类型转换,该转换的安全检查将在运行期执行。同样,如果我们已知某个基类向派生类的转换时安全的,则我们可以使用static_cast来强制覆盖编译器的检查工作。

1 Bulk_quote bulk; //派生类对象
2 Quote item(bulk); //使用Quote::Quote(const Quote&)构造函数
3 item = bulk; //调用Quote::operator=(const Quote&)

 总结来说,存在继承关系的类型之间的转换规则:

  1. 从派生类向基类的类型转换只对指针或引用有效;
  2. 基类向派生类不存在隐式类型转换;
  3. 派生类向基类的类型转换也可能会由于访问受限而变得不可行;
  4. 由于大多数类仍然定义了拷贝控制成员,因此,我们通常能够将一个派生类对象拷贝、移动或赋值给一个基类对象。不过需要注意的是,这种操作只处理派生类对象的基类部分。

 

 

  • 虚函数

 

因为我们直到运行时才能知道到底调用了哪个版本的虚函数,所以所有的虚函数都必须有定义。(通常情况下,如果我们不使用某个函数,则无须为该函数提供定义)

一个派生类的函数如果覆盖了某个继承而来的虚函数,则它的形参类型必须与被它覆盖的基类函数完全一致。同样,派生类中虚函数的返回类型也必须与基类函数匹配。该规则存在一个例外,当类的虚函数返回类型是类本身的指针或引用时,上述规则无效。(要求:从D到B的类型转换时可访问的。)

Best Practice:派生类重定义的虚函数之后加override,让编译器来检查参数是否正确的问题。

 

虚函数有默认实参,该实参值由本次调用的静态类型决定;

Best Practice:如果虚函数使用默认实参,则基类和派生类中定义的默认实参最好一致。

 

有些时候,我们希望对虚函数的调用不要进行动态绑定。通常是当一个派生类的虚函数调用它覆盖的基类的虚函数版本时;

通常情况下,只有成员函数(或友元)中的代码才需要使用作用域运算符来回避虚函数机制。

 

  • 访问控制:

 

protected成员:

和私有成员类似,受保护的成员对于类的用户来说不可访问;

和公有成员类似,受保护的成员对于派生类的成员和友元来说是可访问的;

派生类的成员和友元只能通过派生类对象来访问基类的受保护成员。派生类对于一个基类对象中的受保护成员没有任何访问权限

//正确
void clobber(Sneaky &s){s.j = s.prot_mem = 0;}
//错误,clobber是友元不能访问base中的protected成员
void clobber(Base &s){s.prot_mem = 0;}

 

派生类列表的访问说明符

派生访问说明符对于派生类的成员(及友元)能否访问其直接基类成员没什么影响。对基类成员的访问权限只与基类中的访问说明符有关。

那么,派生类访问说明符的作用是什么呢?

是:控制派生类用户(包括派生类的派生类)对于基类成员的访问权限。

如果是public继承,那么成员遵循原来的访问符;如果是private继承,那么所有成员都是私有的;如果是protected继承,那么基类中的所有公有成员在新定义的类中都是受保护的。

 

由此,我们之前提到的“派生类像基类转换的可访问性”:

如果D public继承B,用户代码才能使用派生类向基类的转换;否则,不能;

不论D以什么方式继承B,D的成员函数和友元都能使用派生类向基类的转换;

如果D以public或protected方式继承B,那么D的派生类的成员函数和友元可以使用;否则,不能。

 

 

  • 友元与继承

友元关系不能继承。基类的友元在访问派生类成员时不具有特殊性;但是可以访问派生类的基类成员。这一点可以用:派生类的作用域嵌套在其基类的作用域之内

 

 

  • 正因为上面所提到的“派生类嵌套在基类的作用域中”,所以我们可以理解其名字查找的规律:

对于p->mem()(obj.mem()):

  1. 首先确定p的静态类型;
  2. 在p的静态类型对应的类中查找mem,如果找不到,则依次在直接基类中不断查找知道到达继承链的顶端。如果找不到,报错;
  3. 一旦找到mem,则进行类型检查,以确定本次调用是否合法;
  4. 如果合法,根据mem是否是虚函数,确定是动态绑定还是常规函数调用。

 

如前所述,声明在内层作用域的函数不会重载声明在外层作用域的函数,所以派生类中的函数不会重载其基类成员。所以,如果派生类和基类成员的形参不一致,基类成员会被隐藏。

同时又由于名字查找过程终止之后,再进行类型检查,所以.......

 

从而,

派生类如何覆盖基类已经重载的函数?

要么每个重载版本都重写一遍;要么一个也不覆盖;

但是我们可能只想覆盖其中的一部分函数;那么可以使用using +函数名,将全部函数添加到派生类作用域;然后再定义其特有的函数即可。

 

posted @ 2016-09-08 17:22  wangyanphp  阅读(464)  评论(1编辑  收藏  举报