C++新经典笔记

第14章 类

14.7 子类、调用顺序、访问等级与函数遮蔽

14.7.1 子类概念

多数类之间都有一种层次关系,其中上层类叫父类,下层类叫子类。比如卡车和轿车,都是车,都烧油,就可以引入三个类:车(父类)、卡车(子类1)、轿车(子类2)。

这种层次关系就是继承,父类主要负责公共函数实现,故继承的本质是为了复用, 复用基类的数据成员和方法。

14.7.2 子类对象定义时构造函数执行顺序

Human.h:

#ifndef UNTITLED8_HUMAN_H
#define UNTITLED8_HUMAN_H
class Human {
    public:
        Human();
        Human(int);

    public:
        int age;
};
#endif //UNTITLED8_HUMAN_H

Human.cpp:

#include <iostream>
#include "Human.h"
using namespace std;

Human::Human() {
    cout<<"excute Human::Human()"<<endl;
}

Human::Human(int age) {
    cout<<"excute Human::Human(int)"<<endl;
}

Men.h

#ifndef UNTITLED8_MEN_H
#define UNTITLED8_MEN_H

#include "Human.h"
class Men: public Human {
public:
    Men();
};

#endif //UNTITLED8_MEN_H

Men.cpp

#include <iostream>
#include "Men.h"
using namespace std;

Men::Men() {
    cout<<"excute Men::Men()"<<endl;
}

main.cpp

#include <iostream>
#include "Men.h"

int main() {
    Men men;
    return 0;

}

执行结果:

excute Human::Human()

excute Men::Men()

说明子类对象构造过程中先构造基类部分。

14.7.3 访问等级

继承方式分为public/protected/private三种继承方式:

Public继承,父类成员在子类访问权限不变;

Protected继承,父类public成员变为子类的protected成员;

Private继承,父类的public和private成员变为子类的private成员

无论何种继承,父类的private成员子类都无权访问,但如果深入研究会发现实际会被继承下来,占用内存,只是无权访问。

编译时报错:

error: 'int Human::age' is inaccessible within this context
    5 |     men.age = 1;

14.7.4 函数覆盖

只要子类中存在同名函数(不论参数和返回值是否一样),父类中的同名函数均调用不到,见下例:

Human.h

#ifndef UNTITLED8_HUMAN_H
#define UNTITLED8_HUMAN_H
class Human {
    public:
        Human();
        Human(int);

        void SameFunc();
        void SameFunc(int);

    public:
        int age;

};
#endif //UNTITLED8_HUMAN_H

Human.cpp

#include <iostream>
#include "Human.h"
using namespace std;
Human::Human() {
    cout<<"excute Human::Human()"<<endl;
}

Human::Human(int age) {
    cout<<"excute Human::Human(int)"<<endl;
}

void Human::SameFunc() {
    cout<<"excute Human::SameFunc()"<<endl;
}

void Human::SameFunc(int) {
    cout<<"excute Human::SameFunc(int)"<<endl;
}

Men.h

#ifndef UNTITLED8_MEN_H
#define UNTITLED8_MEN_H
#include "Human.h"
class Men: public Human {
public:
    Men();
    void SameFunc(int);
};

#endif //UNTITLED8_MEN_H

Men.cpp

#include <iostream>
#include "Men.h"
using namespace std;

Men::Men() {
    cout<<"excute Men::Men()"<<endl;
}

void Men::SameFunc(int) {
    cout<<"excute Human::SameFunc(int)"<<endl;
}

Main函数调用

Men men;
men.SameFunc(1);
excute Men::SameFunc(int)
Men men;
men.SameFunc();
error: no matching function for call to 'Men::SameFunc()'
5 |     men.SameFunc();

也有办法调到父类同名函数

Men men;
men.Human::SameFunc();
men.Human::SameFunc(1);
excute Human::SameFunc()
excute Human::SameFunc(int)

这样做的意义值得商榷,既然覆盖了父类同名函数,肯定不想调用到!

14.8 父类指针、虚/纯虚函数、多态性与析构函数

14.8.1 父类指针与子类指针

父类指针可以指向父类指针,也可以指向子类指针;

子类指针只能指向父类指针

Human *phuman = new Human();
Men *pMen = new Men();
Human *phuman2 = new Men();

这时如果在父类和子类各加一个函数

Human类加:

void Human::HumanFunc() {
    cout<<"excute Human::HumanFunc()"<<endl;
}

Men类加:

void Men::MenFunc() {
    cout<<"excute Men::SameFunc(int)"<<endl;
}

调用:

Human *phuman2 = new Men();
phuman2->HumanFunc();
// phuman2->MenFunc(); // 找不到

发现只能调用到父类添加的函数,那么父类指针怎么调到子类对象的函数呢?故引入虚函数

14.8.2/3 虚函数与多态

引入一个Women类,每个类都实现同名同参的eat()函数,父类前面加上virtual(子类可加可不加,建议加上)

Human类添加:

void Human::Eat() {
    cout<<"excute Human::Eat()"<<endl;
}

Men类添加:

void Men::Eat() {
    cout<<"excute Men::Eat()"<<endl;
}

Women类实现:

Women.h

#ifndef UNTITLED8_WOMEN_H
#define UNTITLED8_WOMEN_H
#include "Human.h"
class Women: public Human {
public:
    Women();
    virtual void Eat();
};

#endif //UNTITLED8_WOMEN_H

Women.cpp

#ifndef UNTITLED8_WOMEN_H
#define UNTITLED8_WOMEN_H
#include "Human.h"
class Women: public Human {
public:
    Women();
    virtual void Eat();
};

#endif //UNTITLED8_WOMEN_H

调用:

Human *phuman = new Human();
Human *phuman2 = new Men();
Human *phuman3 = new Women();

phuman->Eat();
phuman2->Eat();
phuman3->Eat();

执行结果:

excute Human::Eat()
excute Men::Eat()
excute Women::Eat()

可见虚函数使得可以调用到运行时创建的对象对应的函数。[动态绑定]

也有办法调到父类的Eat():

phuman2->Human::Eat();

执行结果:

excute Human::Eat()

Override关键字:

为了防止在实现子类的虚函数时写错,可以在虚函数末尾加上override关键字,这时当前实现的函数必须在父类可以找到完全一样的。如下例即使返回值不一样也不行

 

报错:

error: conflicting return type specified for 'virtual int Men::Eat()'
   11 |     virtual int Eat() override;

final关键字:

用于禁止子类覆盖此虚函数

 

报错:

error: virtual function 'virtual void Men::Eat()' overriding final function
   11 |     virtual void Eat();

小结:

1//多态就是对虚函数所说的,父类声明成虚函数,子类覆写此虚函数,就可以实现运行时动态调用不同子类的虚函数。

2//程序直到运行时才调用到虚函数,未防止到时候找不到,所以虚函数强制要求实现(不管调不调的到),否则编译报错。

14.8.4 纯虚函数

纯虚函数是指声明时后面加=0,此时这个类也为抽象类,不能创建对象。

例如Human类就行如下修改:

virtual void Eat() = 0;

这时如果创建对象就会报错:

Human human;
Human *phuman = new Human();

报错:

error: cannot declare variable 'human' to be of abstract type 'Human'
8 |     Human human;

抽象类的主要作用是用来作为父类,把需要实现的公共函数作为纯虚函数,这些纯虚函数就相当于规范,所有子类都要实现这些规范。

14.8.5 父类的析构函数一般写为虚函数

动机为解决如下问题:

1.Human类显式指定析构函数:

Human::~Human() {
    cout<<"excute Human::~Human()"<<endl;
}

2.Men类显式指定析构函数

Men::~Men() {
    cout<<"excute Men::~Men()"<<endl;
}

3.测试

1>直接调用子类对象

Men men;

当离开作用域时,自动调析构,先调子类析构,再调父类析构

excute Human::Human()
excute Men::Men()
excute Men::~Men()
excute Human::~Human()

2>new子类,然后delete子类

Men *pmen = new Men();
delete(pmen);

当手动调delele时,调用到析构函数

excute Human::Human()
excute Men::Men()
excute Men::~Men()
excute Human::~Human()

3>父类指针指向子类对象

Human *phuman2 = new Men();
delete(phuman2);

当手动调delele时,会只调用到子类析构函数(内存泄露)

excute Human::Human()
excute Men::Men()
excute Human::~Human()

4.解决

父类析构声明为虚函数,子类直接就是了,不用加virtual关键字

执行结果:

excute Human::Human()
excute Men::Men()
excute Men::~Men()
excute Human::~Human()

编程准则:

普通类可以不写析构函数,但如果是父类,一定要显式写,并且声明为virtual,不然只要以<父类指针 = new子类>去调用,一定会发生内存泄露。

  • 14
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值