上篇内容:C++中的重载
继承
继承是什么
在类和对象(一)->封装中说了,封装是将对应的属性和行为封装到一个类中。
那什么是继承呢?
比如一个学校有老师和同学还有领导,那么我们最开始的想法就是每个职位都去封装一个类,同学类,老师类和领导类,为什么要不同的去封装他们,比如因为老师有老师独特的行为和属性,比如老师有工号属性和讲课行为,学生有学生独特的行为和属性,学生有学生ID和听课行为。然后他们应该都有名字、年龄、升高这些属性吧,那我就可以封装一个人类,来进行对这些属性的封装,然后如何将这些属性赋给同学类,老师类和领导类呢,那就是类和对象中的继承。
学习C++重点:
学习C++,不是主要学习C++的知识点,是通过学习C++程序设计的过程中去学习C++知识点。这个过程就是说设计这块C++代码为什么要通过这样的方式去书写代码,这样学习C++才不会觉得难,因为你会明白为什么C++代码逻辑要去这样设计,所以带着问题去学习。如果只学习C++中的知识点,你也无法进行代码设计,你会对实现对应功能时卡住,你会不知道如何进行去设计代码,这就是为什么很多人学习了C++但是他用C++写代码的能力很弱。
打个比方,在看不懂我的代码时,先思考我的代码是逻辑具体需要干什么的,然后再去分析对应的语法知识点去搜索去查找为什么是这样写的。
通过代码来看如何进行继承
子类(派生类),父类(基类),父类派生出了子类,子类基础于父类。
Animal动物类,在动物类中有一个属性是__name名字,有个方法获取名字。
Cat(猫类)通过来定义类后面:加上了public Animal字段就继承了动物类,通过了继承那么猫类就有了动物类中属性和行为,也就是__name名字和name()方法获取名字。
#include <iostream> #include <string> using namespace std; class Animal { public : void say_name() { cout << name << endl; } void set_name(string name) { this->name = name; return ; } private : string name; }; class Cat : public Animal { public : Cat() { set_name("Cat"); } }; class Dog : public Animal { public : Dog() { set_name("Dog"); } }; class Snake : public Animal { public : Snake() { set_name("Snake"); } }; int main() { Cat c1; Dog d1; Snake s1; //继承了父类中的函数和属性 //那么就可以调用父类中的函数 c1.say_name(); d1.say_name(); s1.say_name(); return 0; }
继承权限
下面是父类中的权限和继承权限结合后对外的访问权限。
注意:在继承权限为private和父类权限中为protected处是为private,这里的作用是,为了子类派生出的子类无法进行对该属性或方法进行访问。
如何去理解这个过程:
外部访问权限就相当于继承权限和父类中对应的属性或方法的权限的结合,谁更低权限就是那个更低的权限。
当子类派生出的子类,如果他需要访问当前子类的父类,就需要结合当前子类继承父类的权限和需要访问父类中属性或方法的权限,然后最终当前子类的子类得到最终的访问权限,通过权限去判定是否能访问当前需要访问的属性或方法。
最后父类中private的属性和方法,他也继承给了子类,只是子类是无法进行访问的,但是他也继承给了子类。
子类与父类的构造函数
子类与父类构造函数需要注意细节:
构造顺序:第一原则先构造的后析构。因为先构造的对象可能会被后构造的对象依赖。比如a比b先构造,那么b的构造可能会依赖a对象,所以b的析构要先于a的析构。所以构造的对象后析构,这样符合栈的特性。
那么派生类和基类的构造顺序:
B继承于A,那么先构造A,在构造B。
代码演示:
#include<iostream> using namespace std; class A { public : A() : x(99) { cout << "A default constructor" << endl; } A(int x) : x(x) { cout << "A parameter constructor" << endl; } int get_x() { return this->x; } private : int x; }; class B : public A{ public : //在创建子类对象时,如果明确不调用父类的默认构造函数 //那就需要在初始化列表中明确调用需要调用的构造函数 B() : A(98), y(100) { cout << "B default constructor" << endl; } //那么这里就会默认调用父类的默认构造 B(int y) : y(y) { cout << "B parameter constructor" << endl; } //在调用子类的拷贝构造时,一定要显示的去调用父类的拷贝构造 //这里调用父类的拷贝构造时,传入的是子类的对象 //因为子类对象可以当作父类的对象引用 B(const B &b) : A(b), y(b.y){ cout << "B copy constructor" << endl; } B &operator=(const B &obj) { //对于子类的拷贝行为时,需要调用父类的拷贝,才能正确的进行拷贝 //这里调用的是默认的赋值运算符 this->A::operator=(obj); this->y = obj.y; return *this; } void output() { cout << get_x() << " " << y << endl; } private : int y; }; int main() { B b1; B b2(20); b1.output(); b2.output(); return 0; }
菱形继承
在这个过程中,A中如果有一个属性,BC都继承于A,那么他们都有A的这个属性,而D继承于BC,那么D继承BC中的A的属性时,他们都有A的属性,这时就会造成歧义,是继承B的还是C的,所以尽量不使用多继承。
然后如果非要使用,BC继承A时,加上virtual关键字虚继承。
class A {}; class B : virtual public A{}; class C : virtual public A{}; class D : public B, public C{};
这样D多继承B和C,就不会造成歧义,因为有了virtual关键字,在D类继承时他会进行对B类和C类中存储的A类中的属性合并成一份给D类,这样就不会造成歧义。