笔记九--继承

基类(base class)
派生类(derived class)

1 C++语言支持多态性的几种方式:
a 通过一个隐式转换,从“派生类指针或引用”转换到“其共有基类类型的指针或引用”
b 通过虚拟函数机制
c 通过dynamic_cast和typeid操作符

2 在派生表中指定的类必须首先被定义好,方可被指定为基类。例如,下面的Query 的前向声明不足以使其被用作基类

// 错误: Query 必须已经被定义
class Query;
class NameQuery : public Query { ... };

3 派生类的前向声明不能包括它的派生表,而只是类名——与非派生类一样。例如,下面
的NameQuery 的前向声明导致编译时刻错误:
// 错误: 前向声明不能包含派生类的派生表
class NameQuery : public Query;
正确的前向声明如下
// 派生类与非派生类的前向声明只列出类名
class Query;
class NameQuery;

4 抽象基类  只能定义指针和引用

class Diffident {
public:
void turn_aside();
// ...
};
class Shy : public Diffident {
public:
// 隐藏了 Diffident::turn_aside() 的可视性
void turn_aside();
// ...
};

class Shy : public Diffident {
public:
// ok: 在标准 C++ 下通过 using 声明
// 创建了基类和派生类成员的重载集合
void mumble( string whatYaSay );
using Diffident::mumble;
// ...
};
实际上,using 声明把基类中每个被命名的成员都引入到派生类的域中。针对一个成员函数的using声明不能指定参数列表,只能制

定成员函数名。

6 派生类不能访问另一个独立的基类对象的protected 成员例如
bool
NameQuery::
compare( const Query *pquery )
{
// ok: 自己的 Query 子对象的 protected 成员
int myMatches = _loc.size();
// 错误: 没有 "直接访问另一个独立的 Query
// 对象的 protected 成员" 的权利
int itsMatches = pquery->_loc.size();
return myMatches == itsMatches;
}


7 考虑下列初始化它用一个派生类NameQuery 对象的地址初始化一个Query 基类指针
Query *pb = new NameQuery( "sprite" );

如果我们调用在Query 基类中定义的虚拟函数如
pb->eval(); // 调用 NameQuery::eval()

则调用派生的NameQuery 类实例。除了在Query 基类中被声明、并且在NameQuery派类中被改写的虚拟函数之外,我们没有办法通过

pb 直接访问NameQuery 的成员:
a 如果Query 和NameQuery 都声明了一个同名的非虚拟成员函数,则通过pq 调用的总是Query 的实例。
b 类似地如果Query 和NameQuery 都声明了一个同名的数据成员,则通过pq 总是访问Query 的实例。
c 如果NameQuery 引入了一个在Query 中不存在的虚拟函数比如suffix(),那么试图通过pq 调用它就会导致一个编译时刻错误:
// 错误: suffix() 不是 Query 的成员
pb->suffix();
d 类似地如果我们试图通过pq 访问NameQuery 的数据成员或非虚拟成员函数,也会产生一个编译时刻错误:
// 错误: _name 不是 Query 的成员
pb->_name;

在这种情况下即使对要访问的成员进行限定修饰也不起作用
// 错误: Query 没有 NameQuery 基类
pb->NameQuery::name();

8 如果一个派生类希望直接访问其基类的私有成员则该基类必须显式地把派生类声明为一个友元friend 例如
class Query {
friend class NameQuery;
public:
// ...
};

二 基类与派生类的构造
1 派生类由一个或多个基类子对象以及派生类部分构成:
例如NameQuery 由一个Query子对象和一个string 成员类对象构成。为了说明派生类构造函数的行为,我们引入一个内置数据成员
class NameQuery : public Query {
public:
// ...
protected:
bool _present;
string _name;
};

2 我们先考虑没有定义NameQuery 类构造函数的情况。在这种情况下,当定义一个NameQuery 对象时:
NameQuery nq;
先调用缺省的Query 类构造函数,然后再调用缺省的string 类构造函数与成员类对象_name 相对应。_present 没有被初始化这是

一个潜在的程序错误根源。
3 构造函数的调用顺序总是如下:
a 基类构造函数。如果有多个基类,则构造函数的调用顺序是某类在类派生表中出现的顺序,而不是他们在成员初始化表中的顺序

b 成员类对象构造函数。如果有多个成员类对象,则构造函数的调用顺序是对象在类中被声明的顺序,而不是他们出现在成员初始

化表中的顺序。
c 派生类构造函数

3 基类构造函数
如果类成员是个对象,这个对象会自动调用自己的默认构造函数。
派生类构造函数只能合法的调用其直接基类的构造函数。

4 派生类的析构函数调用顺序与它的构造函数顺序相反。

三 基类和派生类虚拟函数
1 缺省情况下,类的成员函数是非虚拟的,当一个成员函数为非虚拟的时候,通过一个类对象(指针或引用)而被调用的该成员函

数,就是该类对象中定义的成员函数。

2 当成员函数是虚拟的时候,通过一个类对象(指针或引用)而被调用的该成员函数,是在该类对象的动态类型中被定义的成员函

数。但是,正如所发生的,一个类对象的静态和动态类型是相同的。所以,虚拟函数机制只在使用指针和引用时才会如预期般地起

作用。

3 使用基类对象并不保留派生类的类型身份
例如:
NameQuery nq( "lilacs" );
// ok: 但是 nq 被 "切割" 成一个 Query 子对象
Query qobject = nq;

4 void print( Query object,
const Query *pointer,
const Query &reference )
{
// 直到运行时刻才能确定
// 调用哪个 print() 实例
pointer->print();
reference.print();
// 总是调用 Query::print()
object.print();
}
int main()
{
NameQuery firebird( "firebird" );
print( firebird, &firebird, firebird );
}

通过pointer 和reference 的调用被解析为它们的动态类型。在这个例子中,它们都调用NameQuery::print()。而通过object 的调

用则总是调用Query::print()。
5 第一次引入虚拟函数的基类时,必须在类声明中指定virtual 关键字。如果定义被放在类的外面,则不能再次指定关键字virtual



正确的定义必须不包括关键字virtual

6 virtural函数在基类和派生类中的声明应该完全相同(参数类型、参数个数、返回值类型)。但有一个例外,派生类实例的返回

值可以使基类实例返回类型的共有派生类类型。


7 包含(或继承)一个或多个纯虚函数的类被编译器识别为抽象基类。试图创建一个抽象基类的独立类对象会导致编译时刻报错。

类似的,通过虚拟机制调用纯虚函数也是错误的。

8 当用类域操作符调用虚拟函数时,我们改变了虚拟机制,变得虚拟函数在编译时刻被静态解析。

Query *pquery = new NameQuery( "dumbo" );
// 通过虚拟机制动态调用 isA()
// 调用 NameQuery::isA() 实例
pquery->isA();
// 在编译时刻静态调用 isA
// 调用 Query::isA 实例
pquery->Query::isA();

9 虚拟函数的缺省实参
如果通过基类的指针或者引用调用虚拟函数,那函数是在运行时刻确定的。然而传递给foo()的缺省实参不是在运行时刻决定的,而

是在编译时刻根据被调用函数的对象的类型决定的。当通过pb 调用foo()时,缺省实参中base::foo()的声明决定为1024。当通过pd 

调用foo()时缺省实参由derived::foo()的声明决定,为2048。

#include <iostream>
class base {
public:
virtual int foo( int ival = 1024 ) {
cout < "base::foo() -- ival: " < ival < endl;
return ival;
}
// ...
};
class derived : public base {
public:
virtual int foo( int ival = 2048 ) {
cout << "derived::foo() -- ival: " << ival << endl;
return ival;
}
// ...
};

int main()
{
derived *pd = new derived;
base *pb = pd;
int val = pb->foo();
cout << "main() : val through base: "
<< val << endl;
val = pd->foo();
cout << "main() : val through derived: "
<< val << endl;
}
编译并运行它程序产生下列输出
derived::foo() -- ival: 1024
main() : val through base: 1024
derived::foo() -- ival: 2048
main() : val through derived: 2048

如果通过基类指针或引用调用派生类实例,则传递给它的缺省实参是由基类指定的,那么为什么还要在派生类实例中指定缺省实参

呢?


10 虚拟析构函数
void doit_and_bedone ( vector< Query* > *pvec )
{
// ...
for ( ; it != end_it; ++it )
{
Query *pq = *it;
// ...
delete pq;
}
}

为了使函数能够正确执行,在应用delete 表达式时,必须调用pq 指向的动态类型的析构函数。为此,必须把Query 类析构函数声

明为虚拟的:
class Query {
public:
virtual ~Query() { delete _solution; }
// ...
};

如果pq 指向一个AndQuery 对象
delete pq;
则通过虚拟机制调用AndQuery 析构函数,然后再静态调用BinaryQuery 析构函数,而这之后,Query 析构函数也被静态调用。

基类的析构函数不应该是protected。

11 虚拟new操作符

new操作符不能被声明为虚拟的,因为它是一个静态成员函数,在构造类对象之前被应用到未使用的内存上。

12 构造函数、析构函数使用虚拟函数的问题

如果在基类的构造函数中调用了一个虚拟函数,而基类和派生类都定义了该函数的实例,将会怎么样?应该调用哪一个函数实例?

如果可以调用虚拟函数的派生类实例,并且它访问任意的派生类成员,那么调用的结果在逻辑上是未定义的。而程序可能会崩溃。
为了防止这样的事情发生,在基类构造函数中调用的虚拟实例总是在基类中活动的虚拟实例。实际上,在基类构造函数中,派生类

对象只不过是一个基类类型的对象而已。
对于派生类对象,在基类析构函数中也是如此:派生类部分也是未定义的,但是,这一次不是因为它还没有被构造,而是因为它已

经被销毁。


按成员初始化
1
“用一个类对象初始化该类的另一个对象”发生在下列程序情况下:
a 用一个类对象显示地初始化另一个类对象
b 把一个类对象作为实参传递给一个函数,返回一个类对象
c 非空顺序容器类型的定义,例如:
// 五个 string 拷贝构造函数被调用
vector < string > svec( 5 );
在本例中,用string 缺省构造函数创建一个临时对象,然后通过string 拷贝构造函数,该临时对象被依次拷贝到vector 的五个元

素中。
d 把一个类对象插入到一个容器类型中,例如:
svec.push_back( string( "pooh" ));


2 完全不允许按成员初始化
a 把拷贝构造函数声明为私有的,这可以防止按成员初始化发生在程序的任何一个地方(除了类的成员函数和友元之外)
b 通过有意不提供一个定义(但是,我们仍然需要第1步中的声明),可以防止在类的成员函数和友元中出现按成员初始化。

3 类的对象成员的初始化
a 缺省的按成员初始化依次检查每个成员,如果成员是内置或复合数据类型,则直接执行从成员到成员的初始化。

b 如果成员是对象,则检查该对象是否提供了拷贝构造函数,如果提供则调用,否则进行按成员初始化....递归进行..

4 成员初始化表中初始化所有的成员类对象

转载于:https://www.cnblogs.com/wangtianxj/archive/2009/08/21/1551302.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值