第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子类>去调用,一定会发生内存泄露。