C++ primer plus-- 类继承

13 类继承

13.1 一个简单的基类

(1)有关派生类构造函数的要点如下:

  • 首先创建基类对象;

  • 派生类构造函数应通过成员初始化列表将基类信息传递给基类构造函数;

  • 派生类构造函数应初始化派生类新增的数据成员。

创建派生类对象时,程序首先调用基类构造函数,再调用派生类构造函数。基类构造函数负责初始化继承的数据成员,派生类构造函数主要用于初始化新增的数据成员。

派生类对象过期时,程序先调用派生类的析构函数,再调用基类的析构函数。

(2)C++ 构造函数的初始化列表:

C++的构造函数与其他函数不同,构造函数除了有名字,参数列表和函数体之外,还可以有初始化列表,初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段。

例如:

class Foo
{
    private:
        string name ;
        int id ;
    public:
        Foo(string s, int i):name(s), id(i){} ; // 初始化列表
};

通常情况下,与下面的在函数体内赋值的操作区别不大

class Foo
{
    private:
        string name ;
        int id ;
    public:
        Foo(string s, int i)
        {
            name = s;
            id = i;
        }
};

(3)必须使用初始化列表的情况

  • 该类的成员变量是个引用;

  • 该类的成员变量是const类型;(这是因为引用和常量类型只能初始化,不能赋值,所以需要写在初始化列表);

  • 该类是继承一个基类,并且基类中有构造函数,构造函数里有参数;

  • 该类的成员变量类型是类类型,而该类的构造函数带参数时;

(4)使用列表初始化方法,在派生类的构造函数中调用基类的构造函数:

#include
using namespace std;

//基类People
class People{
protected:
    char *m_name;
    int m_age;
public:
    People(char*, int);
};
People::People(char *name, int age): m_name(name), m_age(age){}

//派生类Student
class Student: public People{
private:
    float m_score;
public:
    Student(char *name, int age, float score);
    void display();
};
//People(name, age)就是调用基类的构造函数
Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }
void Student::display(){
    cout<

这两种方式等价

Student::Student(char *name, int age, float score): People(name, age), m_score(score){}

Student::Student(char* name, int age, float score): m_score(score), People(name, age){}

不管顺序如何,派生类构造函数总是先调用基类构造函数再执行其他代码(包括参数初始化表以及函数体中的代码)。

(4)基类指针和基类引用可以在不进行显式类型转换的情况下指向或引用派生类对象。当然,只能操作派生类中从基类中继承过来的数据和基类自身的数据。

例如

People* pt = student1;
Peole& rt = student2;

pt->m_age;
rt.m_age;
13.2 继承:is-a 关系
13.3 多态公有继承

多态最常见的用法就是声明基类类型的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。

两种方法实现多态公有继承:

  • 在派生类中重新定义基类的方法

  • 使用虚方法

(2)虚函数

定义为虚函数是为了允许用基类的指针来调用子类的这个函数。

参考:https://www.runoob.com/cplusplus/cpp-polymorphism.html
和 http://c.biancheng.net/cpp/biancheng/view/2988.html

通过基类指针可以调用派生类从基类继承过来的函数,但如果想每个派生类中继承来的函数,有不同的定义,就需要将该函数在基类中用 virtual 修饰,这样,基类指针就可以根据实际指向的对象调用各自的函数(相当于将继承来的函数覆盖),从而实现了“一个接口,多种方法”。

如果不加 virtual,则无法覆盖,调用的是基类的方法。如下

#include 
using namespace std;

class Shape{
protected:             //保护成员,公有继承后,仍是派生类的保护成员
    int width, height;

public:
    Shape(int a=0, int b=0)
    {
        width = a;
        height = b;
    }

    int area()
    {
        cout<<"Parent class area"<area();

    Triangle tria(10, 4);
    shape = &tria;
    shape->area();

    return 0;
}

结果调用的仍是基类函数,没有覆盖

Parent class area
Parent class area

给基类 int area() 函数加上 virtual 关键字

virtual int area()
{
    cout<<"Parent class area"<

输出

Rectangle class area: 40
Triangle class area: 20

将基类虚函数覆盖,实现多态。

(3)纯虚函数

纯虚函数的作用是在基类中为其派生类保留一个函数的名字,以便派生类根据需要对他进行定义。纯虚函数没有函数体。

virtual int area() = 0;   //纯虚函数

13.4 静态联编和动态联编

将源代码中的函数调用解释为执行特定的函数代码块称为函数名联编。

在编译过程中进行联编称为静态联编;然而由于虚函数的加入,使得程序无法在编译阶段就确定需要调用的函数,而只能在程序运行阶段才能确定,这称为动态联编或晚期联编。

(1)编译器对非虚函数静态联编;对虚函数动态联编。如上面的例子所示。没有加 virtual 时,在编译阶段即确定需要调用的函数,即Shape::area();加上 virtual 后,会在运行阶段根据实际指向的对象调用函数。

(2)构造函数不能是虚函数;析构函数一般可设为虚函数;友元不能是虚函数,因为友元不是类成员,只有类成员才能是虚函数。

(3)如果派生类没有重新定义函数,则使用该函数的基类版本。

13.5 访问控制:protected

(1)private 成员是完全私有的,只有自己可以访问,类外和派生类都不可以访问;

protected 成员是受保护的,派生类可以直接访问(类似 public),但类外不可访问(类似private)。

(2)最好对类数据成员采用私有访问控制,不要使用保护访问控制;同时通过基类方法使派生类能够访问基类数据。

13.6 抽象基类

当希望设计一个从基类派生出的类,但该类并不需要用到基类的全部数据和方法,这时,可以定义一个抽象类(包含二者共同的数据和方法),使二者都从该类派生出来。

(1)抽象基类只定义接口,而不涉及实现,主要用于定义派生类的通用接口。抽象基类至少包含一个纯虚函数。抽象基类不能用来创建具体对象。

(2)纯虚函数应该在派生类中重写,否则派生类也是抽象类,不能实例化。

13.7 继承和动态内存分配

你可能感兴趣的:(C++,c++)