类的派生与继承:
复用开发好的功能,这使得我们可以站在巨人的肩膀上,节省时间,提高效率。
比如,之前定义的CStudent学生类,含有姓名,性别,年龄,学号,年龄等基本信息,当需要特定使用时,例如小学生,中学生,大学生,有需要定义三个类,代码冗余,而且麻烦,所以使用C++中的继承机制是不二的选择。
-
举个(继承)栗子:
class CStudent{ public: char* p_name; char sex; int no; int age; }; /* class <新类名> : <继承方式> <继承源> { <新的成员变量> } */ class /*新类名*/CPrimeStudent : /*继承方式*/public /*继承源*/CStudent { public: int Chinese_score; int Math_score; int English_score; }; class CMiddleStudent : public CPrimeStudent { public: int Physical_score; int Chemistry_score; }; void test() { CMiddleStudent M01; M01.Physical_score = 90; //调用本类的成员变量 M01.Chinese_score = 100; //调用父类的成员变量 M01.age = 15; //调用爷爷类的成员变量 }
其中 CPrimeStudent、CMiddleStudent 是新定义的小学生、中学生的类名字,: public 之后跟着的是他的父类,也就是从哪个类继承过来的,咱们之前的类定义都是留空,这里面应该设计到继承关系,所以要加上,不然不知道从哪里继承过来的。这里面的 public 是指的继承方式,即:父类中的成员在子类中的继承方式,一般也包含三种:public公有继承、private私有继承、protedted受保护继承。
-
继承方式:
①. public公有继承:
父类的公有成员和受保护成员在子类中保持原有的访问属性,其私有成员仍为父类私有,在子类中是访问不了的,即使通过子类的共有成员函数也访问不了;
②. private私有继承:
父类的公有成员和受保护的成员在子类中变成了私有成员,其私有成员仍为父类私有, 在子类中是访问不了的,即使通过子类的共有成员函数也访问不了;
③. protected受保护继承:
父类的公有成员和受保护的成员在子类中变成了受保护成员,其私有成员仍为父类私有, 在子类中是访问不了的,即使通过子类的共有成员函数也访问不了;来个套图:
-
举个栗子:(基类:CStudent)
class CPrimeStudent : public CStudent { public: int Chinese_score; int Math_score; int English_score; private: int flag_private; protected: int flag_protected; }; class CMiddleStudent : public CPrimeStudent { public: int Physical_score; int Chemistry_score; public: int get_flag_1() { //return flag_private; //私有不可被继承 return flag_protected; } };
子类的构造函数与析构函数:
-
子类的构造函数概述: 在子类中的构造函数,初始化子类对象。
-
构造函数的继承: 子类可以继承父类所有的成员变量和成员函数,但不能继承父类的构造函数。因此,在创建子类对象的时候,为了初始化从父类继承来的数据成员,系统需要调用其父类的构造函数。
-
父类构造函数的调用规则:
①. 如果子类没有定义构造函数,则调用父类的无参数的构造函数;
②. 如果子类定义了构造函数,不论是无参数还是带参数,在创建子类的对象的时候,首先执行父类无参数的构造函数,然后执行自己的构造函数;
③. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数,则会调用父类的默认无参构造函数;
④. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类自己提供了无参构造函数,则会调用父类自己的无参构造函数;
⑤. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类只定义了自己的有参构造函数,则会出错(如果父类只有有参数的构造函数,则子类必须显示调用此带参构造函数);
总而言之:
如果子类没有显示的调用父类的构造函数,那么默认会调用父类无参的构造函数!!! 如果父类只提供了有参数的构造函数,那么子类在默认情况下调用父类的无参构造函数时就会报错(没有东西可以被调用)。
⑥. 如果子类调用父类带参数的构造函数,需要用初始化父类成员对象的方式,栗子如下:
class CPrimeStudent : public CStudent { public: int Chinese_score; int Math_score; int English_score; CPrimeStudent() : CStudent("LiSi", 'M', 1101, 21) //调用父类构造函数初始化成员对象 { Chinese_score = 80; Math_score = 90; English_score = 85; flag_private = 0; flag_protected = 0; } private: int flag_private; protected: int flag_protected; };
-
析构函数的继承: 跟父类的构造函数一样,子类也一样不能继承父类的析构函数,也需要通过派生子类的析构函数去调用父类的析构函数。在执行子类的析构函数时,系统会自动调用父类的析构函数和子对象的析构函数,对父类和子对象进行清理工作。 调用的顺序跟构造函数正好相反:先执行子类自己的析构函数,对派生类新增加的成员进行清理,之后调用子对象的析构函数,对子对象进行清理,最后调用父类的的析构函数,对基类进行清理。
-
多重继承:
以上的继承都是由一个基类继承,而多重继承是从两个或多个类继承,用法如下:
class D: public A, private B, protected C{ //类D新增加的成员 } //A,B,C均为声明好的类
#include <iostream> using namespace std; //基类 class BaseA{ public: BaseA(int a, int b); ~BaseA(); protected: int m_a; int m_b; }; BaseA::BaseA(int a, int b): m_a(a), m_b(b){ cout<<"BaseA constructor"<<endl; } BaseA::~BaseA(){ cout<<"BaseA destructor"<<endl; } //基类 class BaseB{ public: BaseB(int c, int d); ~BaseB(); protected: int m_c; int m_d; }; BaseB::BaseB(int c, int d): m_c(c), m_d(d){ cout<<"BaseB constructor"<<endl; } BaseB::~BaseB(){ cout<<"BaseB destructor"<<endl; } //派生类 class Derived: public BaseA, public BaseB{ public: Derived(int a, int b, int c, int d, int e); ~Derived(); public: void show(); private: int m_e; }; Derived::Derived(int a, int b, int c, int d, int e): BaseA(a, b), BaseB(c, d), m_e(e){ cout<<"Derived constructor"<<endl; } Derived::~Derived(){ cout<<"Derived destructor"<<endl; } void Derived::show(){ cout<<m_a<<", "<<m_b<<", "<<m_c<<", "<<m_d<<", "<<m_e<<endl; } int main(){ Derived obj(1, 2, 3, 4, 5); obj.show(); return 0; }
部分图片与话术引自VC驿站:https://www.cctry.com/