继承的基本概念
为什么使用继承
我们在使用类的时候常常会遇到以下情况
class Buddlesort//冒泡排序 { int num[30]; void sort();//排序函数 void print();//打印函数 }; class Quicksort//快速排序 { int num[30]; void sort();//排序函数 void print();//打印函数 }; class Hillsort//希尔排序 { int num[30]; void sort();//排序函数 void print();//打印函数 };
我们可以发现,除了具体排序的实现方法,其他元素基本趋于一致,我们可以利用继承对代码进行一定·的优化,代码如下
class Sort { public: int num[30]; void print();//打印函数 }; class Buddlesort:public Sort//冒泡排序 { void sort();//排序函数 }; class Quicksort:public Sort//快速排序 { void sort();//排序函数 }; class Hillsort:public Sort//希尔排序 { void sort();//排序函数 };
我们首先定义基类Sort,里面储存着几个类所共有的元素
class Sort { public: int num[30]; void print();//打印函数 };
再通过继承的方式将这些由派生类所继承,以Quicksort为例
class Quicksort:public Sort//快速排序 { void sort();//排序函数 };
我们可以发现使用继承可以大大简化部分重复代码的书写
继承的一些常用操作
类派生列表
class Quicksort:public Sort//快速排序 { void sort();//排序函数 };
当我们定义派生类要通过派生类列表来指出它是从哪个(些)基类继承过来的,写法如上
动态绑定
在开始这部分的内容之前,我们假设有一家书店,不可避免的会有原价出售的书籍和打折出售后的书籍,我们可以先假设所有书都是原价书籍,定义出一个原价出售书籍的类,代码如下
class Qutoe//原价书籍 { public: string isbn() const;//返回书籍编号 virtual double net_price(size_t n); };
在这个类里面我们定义了两个函数
1.string isbn() const用于返回图书的编号
2.net_price(size_t n)用于返回书籍的实际售价,但是值得关注的是,基类里面该函数在返回类型前加上了virtual,这是将基类里面的该函数定义成了虚函数,虚函数将会在后面详细的介绍,在这里仅对它的功能做简单的介绍:
大家可以想一下,虽然基类和后面可能会有的派生类里面这个函数的功能都会是返回书籍的当前价格,但是在每个派生类中函数的实现方式却又各不相同,我们就可以在基类中的该函数返回类型前加上virtual,将它定义为虚函数,便于我们在派生类里面根据我们自己的需求,将其改成我们需要的样子,举个例子:
class Bulk_qutoe:public Qutoe//打折后的书籍 { public: double net_price(size_t n) { return 0; } }; class Last_qutoe:public Qutoe { public: double net_price(size_t n) { n = 100 * 3; return n; } };
虽然类写的简单,但是我们可以发现两个类里面同一个函数实现方法完全不一样,我们可以在不同的派生类对基类的虚函数进行覆盖。
现在我们再将目光放到我们的动态绑定上面,通过动态绑定我们可以通过同一段代码分别处理基类和派生类的对象
double Print(ostream& cout,const Qutoe& q,size_t p) { double res = q.net_price(p); return res; }
这里有一个非常有意思的事情,当我们对函数进行不同参数的输入,如:、
int main() { Qutoe basic; Bulk_qutoe bulk; double n1, n2; Print(cout, basic, n1); Print(cout, bulk, n1); return 0; }
当我们对Print函数里面输入不同类型的对象basi、bulk,所调用的net_price的版本就不相同,这种函数的运行版本由实参决定,在运行时选择函数的版本,叫动态绑定。
定义基类和派生类
定义基类
class Quote { public: Quote() =default; Quote(double size_price,const string& number):size_price(size_price),m_number(number) { } string IseN() { return m_number; } virtual double Total_price(double size_price,int n) { return n*size_price; } virtual ~Quote() =default; private: string m_number; protected: double size_price=0.0; };
对上面的基类我们进行了扩写,得到了上面的代码,对上面的的代码接下来我们作一些讲解:
1.Quote() =default;这个是将构造函数设为默认拷贝构造函数
2.virtual ~Quote() =default;这里是为了使用virtual关键字来声明析构函数,当我们将析构函数声明为虚函数时,它允许通过基类指针或引用来调用派生类对象的析构函数。而这对于使用继承和多态的情况非常重要。当基类指针指向派生类对象时,如果基类的析构函数不声明为虚函数,那么在使用基类指针删除对象时,只会调用基类的析构函数,而不会调用派生类的析构函数,从而导致派生类对象中的资源没有得到正确释放。这可能会导致内存泄漏或其他未定义的行为,为了确保在使用基类指针或引用删除对象时调用正确的派生类析构函数,我们需要在基类中将析构函数声明为虚函数。这样,在删除对象时,会根据指针或引用所指向的对象的实际类型,调用相应的析构函数,确保正确释放资源。
定义派生类
class Bulk_Qutoe:public Quote { public: Bulk_Qutoe()=default; Bulk_Qutoe(double,const string&,int,double); double Total_price(double size_price,int book_number) const overrider; private: double discount; int book_number; string book_name; };
派生类中的虚函数
派生类经常会根据自身需求来覆盖掉基类里面的虚函数,当虚函数没有选择覆盖掉虚函数,该虚函数将会作为一位普通成员被派生类所继承
派生类对象以及派生类到基类的类型转换
在一个派生类中分为两个部分,一个是派生类里面自己的定义的成员(非静态对象)的子对象,而另一部分则是从其他基类所继承对应的子对象,如图所示:
因为派生类里面含有与基类对应的组成部分,所以我们可以将派生对象当作基类对象来使用,同时我们也可以将基类对象的指针或者引用绑定到派生类的基类部分上,如下所示:
Qutoe item //基类对象 Bulk_Qutoe bulk//派生类对象 Quote* p = &item//指向基类的指针p p = &bulk;//p改指向派生类里面从基类里面继承过来的部分 Quote &r=bulk//将r与派生类里面继承的部分绑定在一起
这种转换就是派生类到基类的类型转换,通过它我们可以将派生类对象或派生类对象的引用用在需要基类引用的地方,基类指针也是同样的道理
派生类构造函数
虽然派生类里面含有从基类那边继承过来的成员,但是这部分成员不能使用派生类的构造函数来对该部分成员进行初始化,仍然需要对这一部分成员用基类构造函数进行初始化,以上图的Bulk_Qutoe为例:
class Quote { public: Quote(const string& name,double p,string No):book(name),price(p),book_ISBN(No) { } virtual double net_price(size_t n,double price) { return n*price; } Quote(double p,string No):price(p),book_ISBN(No) {} virtual ~Quote()=default; private: string book; protected: string book_ISBN;//书的编号 double price; }; class Bulk_Quote:protected Quote { private: size_t min_qry; double discount; string book_name; public: Bulk_Quote(const string& name,string book_ISBN,double price,size_t qry,double dis):Quote(price,book_ISBN),book_name(name),min_qry(qry),discount(dis) {} double net_price(size_t n,double price) { if(n>min_qry) price*=discount; return n*price; } ~Bulk_Quote() {} };
如上所示,派生类的从基类继承过来的部分仍然是在基类的构造函数内完成的初始化
继承与静态成员
如果基类定义出了一个静态成员,那在整个继承体系中该静态成员只能有该成员的唯一定义,无论派生出多少派生类,每个静态成员只存在唯一的定义,同时静态成员变量遵循通用的访问控制规则。