本篇主要总结的内容有
继承是代码复用的一种重要手段,它允许程序员在一些原有的基础上进行拓展,由此增加新的类;继承体现了面向对象程序设计的层次结构,体现了由简单到复杂的设计过程
代码复用不仅仅有函数复用,在之前的学习中对于代码复用的认知都是停留在函数进行调用从而进行代码复用,而继承就是类设计层次上的复用
下面代码就是一个基础的继承实例
#include
using namespace std;
class Person
{
public:
Person(int age = 1, int num = 1)
:_age(age)
, _num(num)
{}
void Print()
{
cout << _age << " " << _num << endl;
}
protected:
int _age;
int _num;
};
class Teacher :public Person
{
protected:
int _tele;
};
int main()
{
Person p;
Teacher t;
p.Print();
t.Print();
return 0;
}
从中就印证了前面对于继承的概念,继承后的父类的Person
的成员,都会变成子类中的一部分,体现出了代码的复用
继承的定义方式其实就是前面的写法:
public
继承,protected
继承和private
继承public
访问,protected
访问和private
访问继承基类成员的访问方式的变化
类成员/继承方式 | public 继承 |
protected 继承 |
private 继承 |
---|---|---|---|
基类的public 成员 |
在派生类中public 成员 |
派生类的protected 成员 |
派生类的private 成员 |
基类的protected 成员 |
在派生类中protected 成员 |
派生类的protected 成员 |
派生类的private 成员 |
基类的private 成员 |
在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
private
成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它private
成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected
,可以看出保护成员限定符是因继承才出现的这在继承中是一个比较重要的概念,简单来说就是,派生类对象可以赋值给基类的对象指针引用等,原因是在赋值的过程中,可以把对象进行一定的切割,变成基类的成员,再进行赋值即可
// 派生类赋值给基类
int main()
{
Person p;
Teacher t;
p = t;
return 0;
}
上面演示的就是将派生类赋值给基类,这是行得通的,但是将基类赋值给派生类的操作是不被允许的,因为基类成员并不能包括派生类成员:
// error
// 基类赋值给派生类
int main()
{
Person p;
Teacher t;
t = p;
return 0;
}
举例来解释:
#include
using namespace std;
class Person
{
public:
Person(int age = 1, int num = 1)
:_age(age)
, _num(num)
{}
void Print()
{
cout << "Person::Print" << _age << " " << _num << endl;
}
protected:
int _age;
int _num;
};
class Teacher :public Person
{
public:
Teacher(int age = 10, int tele = 20)
:_age(age)
, _tele(tele)
{}
void Print()
{
cout << "Teacher::Print" << _age << " " << _tele << endl;
}
protected:
int _age;
int _tele;
};
int main()
{
Person p;
Teacher t;
p.Print();
t.Print();
return 0;
}
6个默认成员函数,就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个成员函数是如何生成的
关于此部分的具体实例化表示在后演示
1. 继承和友元关系:
继承是不会继承友元函数的,基类的友元不能访问子类的私有和保护成员
2. 继承和静态成员的关系:
基类中假如定义了一个静态成员,那么在整个继承体系中只有一个这样的成员,不管有多少个派生的子类,都只有一个static
成员的实例
#include
using namespace std;
class Person
{
public:
// 继承是不会继承友元函数的,基类的友元不能访问子类的私有和保护成员
Person(const string& name = "Tom")
:_name("Tom")
{
cout << "Person()" << endl;
}
Person(const Person& p)
:_name(p._name)
{
cout << "Person(const Person& p)" << endl;
}
Person& operator=(const Person& p)
{
cout << "Person& operator=(const Person& p)" << endl;
if (&p != this)
{
_name = p._name;
}
return *this;
}
~Person()
{
cout << "~Person()" << endl;
}
friend void Display(const Person& p)
{
cout << "friend void Display(const Person& p)" << " " << p._name << endl;
}
protected:
// 基类中假如定义了一个静态成员,那么在整个继承体系中只有一个这样的成员
// 不管有多少个派生的子类,都只有一个static成员的实例
static int _static;
string _name;
};
int Person::_static = 10;
class Student :public Person
{
public:
// 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员
// 如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用
Student(const string& name = "Tom", int num = 123)
:Person(name)
, _num(num)
{}
// 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
Student(const Student& s)
:Person(s)
, _num(s._num)
{
cout << "Student(const Student& s)" << endl;
}
// 派生类的operator=必须要调用基类的operator=完成基类的复制
Student& operator=(const Student& s)
{
cout << "Student& operator=(const Student& s)" << endl;
if (&s != this)
{
Person::operator=(s);
_num = s._num;
}
return *this;
}
// 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员
// 因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序
~Student()
{
cout << "~Student()" << endl;
}
protected:
int _num;
};
菱形继承指的是一种多继承,一个子类有两个或以上直接父类时称这个继承关系为多继承
这样会造成的问题有,Student
类和Teacher
类在创建的过程中,都会继承Person
类的内容,也就是说,Student
类和Teacher
类当中都会有一份Person
类的数据,当Assistant
类继承了数据后,在Assistant
类中会存在两份相同的Person
类对象的内容,一份来源于Student
类,一份来源于Teacher
类
这样会带来两个问题,首先是数据冗余,实际上在Assistant
类中只需要一份Person
类的数据即可,但是这里有两份,带来的是数据冗余,其次是二义性问题,当在Assistant
类中调用Person
类的内容数据是,会带来不知道是调用哪一个类中继承下来的Person
类的问题,这是菱形继承带来的两个问题
如何解决?
对于解决二义性的问题,可以通过指定访问哪一个父类来解决二义性问题,但是数据的冗余是无法被彻底解决的,因此就要引入虚拟继承的概念
class Person
{
public:
string _name;
};
class Student : virtual public Person
{
protected:
int _num;
};
class Teacher : virtual public Person
{
protected:
int _id;
};
class Assistant : public Student, public Teacher
{
protected:
string _addr;
};
void Test()
{
Assistant a;
a._name = "Tom";
}
现在定义下面这些类:
class A
{
public:
int _a;
};
class B : virtual public A
{
public:
int _b;
};
class C : virtual public A
{
public:
int _c;
};
class D : public B, public C
{
public:
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
通过内存窗口进行观察:
从中看出,数据冗余的情况是存在的,类B
中有A
的内容,类C
中也有A
的内容,因此就造成了数据冗余的情况出现,针对这种情况,C++
也做出了一定的调整,研发出了一个虚基表的内容,简单来说,就是菱形虚拟继承的内存对象成员模型,就是在D
对象中将A
放到了对象组成的最下面,这个A
同时属于B
和C
,而B
和C
中存储了两个指针,这个指针指向一个表,这两个表就是虚基表,这个指针就是虚基表指针,虚基表中存储的是偏移量,通过偏移量就可以找到A
的位置进而进行访问: