众所周知,C++最核心的三个部分分别是封装,继承和多态。封装的思想在前面类和对象部分已经体现了,接下来介绍的就是继承。
概念与定义
继承,顾名思义,就是从长辈那边获得一些东西。cpp中的继承指的是子类从父类继承函数,变量的手段,并在这个的基础上进行扩展,产生新的类。继承设计的核心就是减少代码的编写,提高编码效率。例如胡萝卜和上海青都是蔬菜,那就可以继承蔬菜这个父类的一些变量,比如维生素含量啥啥的,然后可能胡萝卜还有自己的胡萝卜素的含量,再另外处理。
继承的定义
继承最本质就是父类和子类还有继承方式,代码中Student就是子类,Person是父类,protected是继承方式。继承方式有三种,分别对应了类的三个域,public,protected和private。继承只能继承public,protected的数据,具体情况如下图所示。
class Student :protected Person

父类private成员无法再派生类中被访问,但是可以通过形如Person:x的方式来访问private成员x.
如果类的定义时使用class,且没有指定继承方式,则默认为private继承。如果是使用struct,则默认为public继承。一般来说,都使用public继承。
父类和子类的转换
如果使用public进行继承,那么子类的对象可以直接赋值给父类的指针或者引用。这种过程类似于切片,子类把自己体内关于父类的部分切了出来,本质上指针或者引用指向的是子类中切出来的父类部分,通过以下代码我们就可以得知了。并且,通过代码我们还可以知道,继承在子类的声明中占了一个比较高的优先级,基本处于内存的顶部位置。虽然子类能够赋值给父类的指针或者引用,但是不能反过来,把父类赋值给子类。
#include "iostream"
#include "string"
using namespace std;
class Person
{
public:
Person(string name)
:_name(name)
{
age = 0;
cout << "Person" << endl;
}
string _name;
size_t age;
string _addr;
};
class Student :public Person
{
public:
Student(string name, int num)
:Person(name),
_num(num)
{
cout << "Student" << endl;
}
private:
int _num;
};
int main()
{
Student s1("张三",3);
cout << &s1 << endl;
Person* p2 = &s1;
cout << p2 << endl;
Person &p3 = s1;
cout << &p3 << endl;
Person p1(s1);
cout << &p1 << endl;
return 0;
}
Person
Student
000000C02F5DF840
000000C02F5DF840
000000C02F5DF840
000000C02F5DF900
继承中的作用域
在继承体系中父类和子类都有自己的作用域,两个域互不相同,当父类和子类都有同名的成员时,子类成员将会将会屏蔽父类的成员,叫做隐蔽。如果实在想访问父类的成员,可以用父类:父类成员的方法。所以在实际中,最好不要定义同名成员。
在这其中还有一个有意思的,那就是函数的隐藏。在以下的代码中,初一看,可能会觉得A中的fun和B中的fun是重载,但并非如此。根据重载的定义,重载函数必须在同一个域中才能构成重载。
class A
{ p
ublic:
void fun()
{
cout << "func()" << endl;
}
};
class B : public A
{ p
ublic:
void fun(int i)
{
cout << "func(int i)" <<i<<endl;
}
};
int main()
{
B b;
b.fun(10);
b.fun();
return 0;
}
子类的默认成员函数
类中包含6个默认的成员函数,其中有四个在继承系统中需要再提一下。
**1.构造函数:**子类的构造函数必须调用父类的构造函数来初始化子类中父类的部分,如果父类没有一个合适的默认构造函数,则必须在子类构造函数中的初始化列表显示调用。如下图的代码,流程为先调用Person的构造函数,然后在初始化num。
public:
Student(string name, int num)
:Person(name),
_num(num)
{}
2**.拷⻉构造函数:**派⽣类的拷⻉构造函数必须调⽤基类的拷⻉构造完成基类的拷⻉初始化。
3.析构函数: 析构函数会在被调⽤完成后⾃动调⽤基类的析构函数清理基类成员.派⽣类对象析构清理先调⽤派⽣类析构再调基类的析构。
**4.operator=:**必须要调⽤基类的operator=完成基类的复制。
这张图就很形象的描绘出基类和派生类中构造函数和析构函数的逻辑顺序。

实现⼀个不能被继承的类
方法有二,都很简单。⽅法1:基类的构造函数私有,派⽣类的构成必须调⽤基类的构造函数,但是基类的构成函数私有化以后,派⽣类看不⻅就不能调⽤了,那么派⽣类就⽆法实例化出对象。
⽅法2:C++11新增了⼀个final关键字,final修改基类,派⽣类就不能继承了。
// C++11的⽅法
class Base final
{ p
ublic:
void func5() { cout << "Base::func5" << endl; }
protected:
int a = 1;
private:
// C++98的⽅法
/*Base()
{}*/
}
继承与友元
友元关系不能继承,也就是说基类友元不能访问派生类private和protected的成员。爸爸的朋友不是我的朋友,就这个意思。
继承与静态成员
基类中定义了一个static静态成员,则整个继承体系中都会只有这一个成员,对他的操作将是共享的。如下图代码所示,当我初始化x为0,然后++s1的x,再++s2的x,进行打印以后看到的将是0,1,2,符合同一变量原则。
class Person
{
public:
Person(string name)
:_name(name)
{
age = 0;
cout << "Person" << endl;
}
string _name;
size_t age;
string _addr;
static int x;
};
class Teacher :public Person
{
public:
Teacher(string name, int num)
:Person(name),
_id(num)
{
cout << "Student" << endl;
}
private:
int _id;
};
class Student :public Person
{
public:
Student(string name, int num)
:Person(name),
_num(num)
{
cout << "Student" << endl;
}
private:
int _num;
};
int Person::x = 0;
int main()
{
Student s1("张三",3);
Teacher s2("李四", 4);
cout << Person::x << endl;
s2.x++;
cout << s1.x++ << endl;
cout <<s2.x << endl;
return 0;
}
继承及其菱形继承问题
**单继承:**一个派生类只有一个基类与之对应,简称只有一个爹。
**多继承:**一个派生类有多个基类与之对应,有好多爹。
**菱形继承:**一个派生类有多个基类与之对应,这几个基类还都有同一个爹。
菱形继承少用,其本身效率低易出错,想要用好菱形继承的话就得用虚继承的技术
class Person
{ p
ublic:
string _name; // 姓名
/*int _tel;
int _age;
string _gender;
string _address;*/
// ...
};
// 使⽤虚继承Person类
class Student : virtual public Person
{ p
rotected:
int _num; //学号
};
// 使⽤虚继承Person类
class Teacher : virtual public Person
{ p
rotected:
int _id; // 职⼯编号
};
// 教授助理
class Assistant : public Student, public Teacher
{ p
rotected:
string _majorCourse; // 主修课程
}