公有继承、多态公有继承、静态联编和动态联编、抽象基类、继承和动态内存分配

1.公有继承 p392

1)格式

Class RatedPlayer:public TableTennisPlayer
{
...
};

2)派生类构造函数中使用成员初始化列表将参数赋值给基类构造函数,来初始化基类成员 p395

class RatedPlayer:public TableTennisPlayer
{
private:
    unsigned int rating;
public:
    RatedPlayer(unsigned int r = 0, const string & fn = "none", const string & ln = "none", bool ht = false); //构造函数
    RatedPlayer(unsigned int r, const TableTennisPlayer & tp); //构造函数
    unsigned int Rating() const {return rating;}
    void ResetRating(unsigned int r){rating = r;}
};

公有派生类不能直接访问基类的私有成员,而必须通过基类方法进行访问。p395

例如,RatedPalyer构造函数不能直接设置继承的基类私有成员(first name,lastname,hasTable),而必须使用基类的公有方法来访问基类的私有成员。因此,派生类构造函数必须使用基类构造函数。p395

创建派生类对象时,程序首先创建基类对象,即基类对象应当在程序进入派生类构造函数之前被创建。c++使用成员初始化列表语法来实现。p395

以RatedPlayer的第一个构造函数为例:

RatedPlayer::RatedPalyer(unsigned int r, const string & fn, const string & ln, bool ht):TableTennisPlayer(fn, ln, ht)//TableTennisPlayer(fn, ln, ht) 为成员初始化列表,用来在RatedPlayer的构造函数中初始化基类TabletennisPlayer的私有成员
{
    rating = r;
}

如果派生类构造函数中省略了成员初始化列表,则程序将使用默认的基类构造函数。p396

RatedPlayer的第二个构造函数:

RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp):TableTennisPlayer(tp)//TableTennisPlayer(tp) 为成员初始化列表
{
    ratring = r;
}

tp的类型为 TableTennisPlayer &,因此将调用基类的复制构造函数。p396

也可以对派生类成员使用成员初始化列表语法。

3)有关派生类构造函数的要点:p396

  • 首先创建基类对象
  • 派生类构造函数应通过成员初始化列表将基类信息(即基类的数据成员,一般是私有的)传递给基类构造函数
  • 派生类构造函数应初始化派生类新增的数据成员

注意:创建派生类对象时,程序首先调用基类的构造函数,再调用派生类的构造函数;基类构造函数负责初始化继承的数据成员,派生类的构造函数用于初始化新增的数据成员。派生类的构造函数总是调用一个基类构造函数。可以使用初始化列表语法执行要使用的基类的构造函数,否则将使用默认的基类构造函数。(注意,成员初始化列表只适用于构造函数)

这个例子中没有提供显示的构造函数,因此使用隐式构造函数;释放对象的顺序与创建对象的顺序相反,当派生类对象过期时,首先执行派生类的析构函数,再自动调用基类的构造函数。p397

 

2.c++要求引用和指针类型与赋给的类型匹配,但这一规则对继承来说是例外。

基类指针可以在不进行显示类型转换的情况下指向派生类对象;基类引用可以在不进行显示类型转换的情况下引用派生类对象。但基类指针或引用只能调用基类方法。p399

不可以将基类对象和指针(地址)赋给派生类引用和指针。

引用的兼容性允许将基类的对象初始化为派生类对象:p399

RatedPlayer olaf1(1840,"Olaf","Loaf",true); //派生类对象
TableTennisPlayer olaf2(olaf1); //用派生类对象初始化基类对象

要初始化olaf2,匹配的构造函数原型如下:

TableTennisPlayer(const RatedPlayer &); //不存在

TalbeTennisPlayer类定义中没有这样的复制构造函数,但存在隐式复制构造函数:

TableTennisPlayer(const TableTennisPlayer &); //复制构造函数

因为基类引用可以指向派生类对象,因此 TableTennisPlayer olaf2(olaf1); 将调用该复制构造函数,将派生类对象 olaf1 作为实参赋给形参 TableTennisPlayer &。使用TableTennisPlayer的默认复制构造函数后,它将根据函数内容,复制基类的数据成员。

同样的,也可以将派生类对象赋给基类对象:

RatedPlayer olaf1(1840, "Olaf", "Loaf", true); //派生类对象
TableTennisPlayer winner; //基类对象
winner = olaf1;

这种情况下,将调用基类的隐式重载复制运算符函数:

TableTennisPlayer & operator=(const TableTennisPlayer &)const;

 

3.多态公有继承 p400

多态:同一个方法随上下文而异,即同一个方法在派生类和基类中的行为是不同的。

多态公有继承的两种重要机制:

  • 在派生类中重新定义基类的方法。
  • 使用虚方法

如果一个类方法是通过对象的引用或指针而不是对象调用的,如果没有使用关键字virtual,程序将根据引用类型或指针类型选择方法;如果使用了virtual,程序将根据引用或指针指向的对象的类型来选择方法。 p403,407

方法在基类中被声明为虚的后,它在派生类中自动称为虚方法。也可以在派生类中使用关键字virtual来指出那些函数是虚函数。

一般将基类的析构函数声明为虚的。p403,p408

关键字virtual只用于类声明的方法原型中,不用于方法定义中。p403

在多态公有继承中,派生类方法访问基类的 private 成员的方式:p405

  • 派生类的构造函数在初始化基类私有数据时,采用成员初始化列表语法。
  • 非构造函数不能使用成员初始化列表语法,但派生类方法可以调用公有的基类方法;标准技术是使用作用域解析运算符来调用基类方法;若派生类中没有重定义该方法,可以不使用作用域解析运算符,直接调用基类的公有方法。

一般,如果要在派生类中重新定义基类的方法,则将它设置为虚方法;否则,设置为非虚方法。p411

虚函数的工作原理:p411

给每个对象添加也给隐藏成员,隐藏成员中保存了一个指向由函数地址构成的数组的指针,这种数组称为虚函数表。虚函数表中存储了为类对象进行声明的虚函数的地址。

 

4.静态联编和动态联编 p86,p409

1)概念:将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编(binding)。在编译过程中进行联编被称为静态联编(早期联编);由于使用虚函数时,使用哪一个函数是不能在编译时确定的(要根据基类指针或引用指向的对象是基类对象或派生类对象来确定),因此编译器必须在程序运行时选正确的虚函数的代码,这被称为动态联编(晚期联编)。

2)向上强制转换,向下强制转换

将派生类引用或指针转换为基类引用或指针被称为向上强制转换(upcasting),这使公有继承不需要进行显式类型转换;(可以对基类对象执行的任何操作,都适用于派生类对象)

向上转换是可传递的,即若从基类A派生出派生类B,从派生类B派生出派生类C,则基类指针和引用也可以指向C类的对象。

将基类指针或引用转换为派生类指针或引用被称为向下强制转换;如果不适用显式转换,向下强制转化是不允许的。

3)编译器在编译时对非虚方法使用静态联编;对虚方法使用动态联编。

4)友元不能是虚函数,因为友元不是类成员。p413

5)

  • 如果派生类中重新定义继承的方法,应确保与原来的原型完全相同;但如果继承的方法返回类型是基类引用、指针,则可以修改为派生类的引用或指针。这种特性被称为返回类型协变,注意,这种例外只适用于返回值,不适用于参数。p413
  • 如果基类的函数声明被重载了,且需要在派生类中重新定义基类的该函数,则应该在派生类中重新定义所有的基类版本;如果只重新定义一个版本,另外的重载版本将被隐藏,派生类对象无法使用它们。

 

5.访问控制:protected p414

关键字protected与private相似,在类外只能用公有类成员函数来访问protected部分中的类成员;

private和protected的区别只有在基类派生的类中才会体现出来:派生类的成员可以直接访问基类的protected成员,但不能直接访问基类的private成员。

 

6.抽象基类ABC(Abstract Base Class)p415

1)纯虚函数声明的结尾处为 =0 ,如:

Class BaseEllipse
{
private:
    double x;
    double y;
public:
    BaseEllipse(double x0 = 0, double y0 = 0):x(x0),y(y0){}
    virtual ~BaseEllipse(){};
    void Move(int nx, int ny){x = nx; y = ny;}
    virtual double Area() const = 0; //pure virtual function
    ...
};

2)

  • c++通过使用纯虚函数来提供类中未实现的函数;p416
  • 抽象基类至少包含一个纯虚函数;
  • 当类声明中包含纯虚函数时,不能创建该类的对象;因此包含纯虚函数的类只能用作派生类的基类
  • 在抽象基类中,纯虚函数可以有定义,也可以无定义;但无定义的函数必须声明为纯虚函数。
  • 抽象基类要求具体派生类必须覆盖其纯虚函数。p421

 

7.继承和动态内存分配 p421

当基类使用动态内存分配(new),派生类是否应该对:复制构造函数、复制运算符重载函数、析构函数 进行重新定义?

要注意:

  • 派生类的默认析构函数总是在执行自身代码后自动调用基类构造函数。
  • 在类的默认的复制构造函数中,成员复制将根据数据类型采用相应的复制方式;当复制类成员、类对象或继承的类组件时,是自动使用该类的复制构造函数完成的。
  • 在重载赋值运算符函数时,类的默认赋值运算符函数将自动使用基类的重载赋值运算符函数来对基类组件进行赋值。
  • 派生类访问基类的友元:友元函数非类成员,因此不能继承;可以使用强制类型转换将派生类引用或指针转换为基类引用或指针,然后使用转换后的指针或引用来调用基类的友元函数。 p423,p432

8.派生类不能从基类那里继承什么 p430,432

  • 构造函数(复制构造函数等)
  • 析构函数
  • 赋值运算符函数
  • 友元

9.派生类不继承基类重载运算符函数,定义派生类的重载运算符函数时,可以使用作用域解析解析运算符调用基类重载运算符函数。p423

10.派生类的友元函数如何使用基类友元函数?(如派生类的opeartor<<使用基类的operator<<)不能使用作用域解析运算符来调用基类的operator<<,因为该函数不是基类的成员函数;可以使用强制类型转换将派生类引用或指针转换为基类引用或指针。p425,p426

 

posted @ 2022-04-11 11:52  SanFranciscoo  阅读(54)  评论(0编辑  收藏  举报