类继承
(1)定义
从已有的类派生出新的类,而派生类继承了原有类的特征,包括方法。原始类称为基类,继承类称为派生类
(2)派生类具有的特征
存储了基类的数据成员
可以使用基类的方法(前提:方法是公有或受保护)
派生类需要自己的构造函数
派生类可以根据需要添加额外的数据成员和成员函数
派生类
(1)构造函数
#include <iostream>
#include <string>
using namespace std;
//基类
class Person {
protected:string name;int age;
public:Person(string _name = "", int _age = 0) :name(_name), age(_age) {};string getName() { return name; };int getAge() { return age; };
};
//派生类
class Teacher:public Person {
private:string job;
public:Teacher(string _name = "", int _age = 0, string _job = "") :Person(_name, _age), job(_job) {};Teacher(string _job, const Person& p) :job(_job), Person(p) {};
};
(2)派生类和基类之间的关系
派生类的对象可以使用基类的方法,前提是方法不是私有的
基类的指针或引用可以不用显式类型转换的情况下指向派生类的对象,但只能调用派生类中基类的方法
可以将基类的对象初始化为派生类的对象或将派生类对象赋给基类对象,那么派生类的基类部分的数据就会拷贝到基类对象中
(3)要点
派生类的构造函数中,将基类信息传递给基类的构造函数,也初始化自身的新增数据成员
程序先调用基类构造函数,再调用派生类的构造函数
程序先执行派生类的析构函数,再执行基类的析构函数
虚函数
(1)多态
同一个方法在派生类和基类的行为是不同的,即方法的行为应取决于调用该方法的对象,该行为称为多态
(2)若基类和派生类都定义了,但是没有声明为虚方法
使用对象来调用
Brass dom("Dominic Banker", 11224, 4183.45);
BrassPlus dot("Dorothy Banker", 12118, 2592.00);
dom.ViewAcct();//调用基类的
dot.ViewAcct();//调用派生类的
使用指针或引用来调用
Brass dom("Dominic Banker", 11224, 4183.45);
BrassPlus dot("Dorothy Banker", 12118, 2592.00);
Brass& b1 = dom;
Brass& b2 = dot;
b1.ViewAcct();//调用基类的
b2.ViewAcct();//调用基类的
(3)若基类和派生类都定义了,但是没有声明为虚方法
Brass dom("Dominic Banker", 11224, 4183.45);
BrassPlus dot("Dorothy Banker", 12118, 2592.00);
Brass& b1 = dom;
Brass& b2 = dot;
b1.ViewAcct();//调用基类的
b2.ViewAcct();//调用派生类的
(4)派生类的方法调用公有的基类方法
要使用作用域解析运算符(若派生类没有定义则可以不用)来调用基类的方法,否则会调用自己类中的方法
void BrassPlus::ViewAcct()const
{format initialState = setFormat();precis prec = cout.precision(2);Brass::ViewAcct();//调用基类的方法//ViewAcct();将调用派生类的方法,进入死循环cout << "透支限额: $" << maxLoan << endl;cout << "当前的透支总额: $" << owesBank << endl;cout.precision(3);cout << "透支贷款利率: " << 100 * rate << "%" << endl;restore(initialState, prec);
}
(5)虚函数表和虚表指针
每个对象有隐藏成员--虚表指针,它指向虚函数表,而虚函数表存储了类对象声明的虚函数的地址
(6)工作原理
基类有虚表指针,该指针指向基类中所有虚函数的地址表(虚函数表);派生类有独立的虚表指针,其也指向虚函数表。但是派生类的虚函数表与基类不同,其保存了
基类的虚函数的地址
若派生类重写了基类的虚函数,则更新原有虚函数的地址
若派生类定义了新的虚函数,则添加新的虚函数的地址
(7)实现
调用虚函数时,程序将查看存储在对象中的虚表指针的地址,然后转向相应的虚函数表,若使用类声明中定义的第一个虚函数,则程序将使用表中的相对应的函数地址,并执行具有该地址的函数
(8)成本
使用虚函数时,在内存和执行速度方面有一定的成本:
每个对象都将增大,增大量为存储地址的空间
对于每个类,编译器都创建一个虚函数地址表(数组)
对于每个函数调用,都需要执行一项额外的操作,即到表中查找地址
(9)注意
①构造函数不能是虚函数
创建派生类对象时,将调用派生类的构造函数,而不是基类的构造函数,然后派生类的构造函数将使用基类的构造函数初始化基类信息
②析构函数应当是虚函数,除非类不用做基类
派生类可能有自身添加的指针成员,若析构函数若不是虚函数,则调用基类的析构函数,将无法释放派生类添加的指针成员所占的内存
③友元不能是虚函数
因为友元不是类成员,而只有成员才能是虚函数。可以让友元函数使用虚成员函数。
④派生类隐藏基类的方法
派生类和基类中定义了函数名相同(返回类型可以相同和不相同),但出现下列两种情况:
参数不同时,无论基类函数是否为虚函数,都会被派生类函数隐藏
参数相同,但是不是虚函数,则基类函数被派生类函数隐藏
class Brass {...virtual void show(int a)const;...
}
class BrassPlus :public Brass {...virtual void show()const;...
}
BrassPlus bp;
bp.show();//允许
bp.show(5);//不允许
⑤协变
派生类重写基类的虚函数时,若返回类型是基类的引用或指针,可修改为指向派生类的引用或指针,该特性为返回类型协变