什么是继承
继承是两个类之间的关系,可以实现派生类(子类)对基类(父类)的复用,即派生类在基类的基础上进行扩展,实现更多功能。例如学生和人这两个对象就可以是继承关系,学生具有人的所有特征,而且学生有人这个类之外的其他特征。
例子:
class Person
{
public:string _name; // 姓名
};class Student : public Person
{
public:string _stuNum; // 学号void study(){cout << _name << '(' << _stuNum <<") is studying~~" << endl;}
};void test()
{Student stu;stu._name = "Mike";stu._stuNum = "1234";stu.study();
}
继承的使用
继承的定义
继承方式
简单记忆:派生类中继承过来的成员的访问权限,是基类成员的访问权限和继承方式中权限最小的,例如,基类private成员通过public继承,二者中private的权限最小,继承的成员在派生类中就是private成员
final修饰的类不能作基类
class A final{};//class B :public A {
//
//};
public,protected和private成员的区别
public成员在类内外都可以访问
protected成员可以在类内访问,可以在派生类中(派生类以public/protected继承)访问,不能在类外访问
private成员只能在内内访问
注意:
1.继承后,基类的private成员会继承到派生类,只是派生类没有访问权限
2.class的默认继承方式是private,struct的默认继承方式是public,但是最好显式写出继承方式
派生类和基类的赋值兼容转换
基类可以接收派生类的对象和引用;基类的指针可以接收派生类的指针。
一句话说,就是基类可以接收派生类的值,但是派生类不可以接收基类的值。
可以简单理解一下,派生类赋值给基类时,有一部分成员基类没有,所以派生类只把基类中有的赋值给基类,基类中没有的直接丢掉;但是反过来,基类如果赋值给派生类,派生类中有一部分基类没有的成员无法被赋值,就会出现错误。
class A
{};class B : public A
{};int main()
{A a;B b;B& bb = b;A* pa;//基类赋值派生类a = b;a = bb;pa = &b;}
派生类的指针或者引用赋值给基类时,并不会重开一段空间,而且修改指针指向的数据或者引用的数据也会改变派生类的数据。
成员函数的隐藏/重定义
基类和派生类都有自己独立的作用域,如果基类和派生类中有同名的成员函数,注意只需要同名,那么当我们用派生类的对象调用这个函数时,只会调用派生类的函数,基类的函数就被隐藏/重定义了。
class A
{
public:void fun(){cout << "A_fun()" << endl;}
};class B : public A
{
public:void fun(){cout << "B_fun()" << endl;}
};void _test()
{B b;b.fun();//强制访问基类中被隐藏的函数b.A::fun();
}
派生类的默认成员函数
- 派生类的构造函数必须调用基类的构造函数来初始化基类中的那一部分成员。如果基类没有默认构造函数,需要在派生类的初始化列表中显式调用基类的构造函数。
- 派生类的析构函数会在自身被调用后,调用基类的析构。因为如果派生类的析构函数使用了基类成员,而基类先析构了,就会出现错误。
- 由1,2可知,派生类对象定义和销毁会有以下顺序,先构造基类->构造派生类->析构派生类->析构基类
- 派生类的拷贝构造函数必须调用基类的拷贝构造函数
- 派生类的operator=函数必须调用基类的operator=函数
class Person
{
protected:string _name;
public:Person(const string& s = "xxxxx"):_name(s){cout << "Person()" << endl;}Person(const Person& p):_name(p._name){}Person& operator=(const Person& p){if(this != &p){_name = p._name;}return *this;}~Person(){cout << "~Person" << endl;}
};class Student : public Person
{
protected:string _num;
public:Student(const string& name = "Stuxxx", const string& num = "22335"):Person(name)//参数列表调用基类构造函数,_num(num){cout << "Student()" << endl;}Student(const Student& s):_num(s._num),Person(s)//参数列表调用基类拷贝构造函数{}Student& operator=(const Student& s){if (this != &s){_num = s._num;Person::operator=(s);//调用基类的赋值运算符重载函数}return *this;}~Student(){cout << "~Student()" << endl;}
};
友元的关系不能继承
基类的友元函数不能访问派生类的成员
class Person
{
public:friend void Display(const Person& p, const Student& s);
protected:string _name; // 姓名
};
class Student : public Person
{//friend void Display(const Person& p, const Student& s);
protected:int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{cout << p._name << endl;//cout << s._stuNum << endl; 无法访问
}
void main()
{Person p;Student s;Display(p, s);
}
如果友元函数想要访问派生类成员,需要在派生类中声明友元函数
继承与静态成员
基类中的静态成员,在整个继承体系中只有一个。无论有多少个派生类,都只有一个实例
//统计A构造的次数
class A
{
public:static int count;A() { count++; }
};class B : public A
{};int B::count = 0;void test()
{A a;B b;cout << B::count;//2
}
菱形继承
菱形继承发生在多继承中,如果一个类继承了多个类,而且这些类中有2个或2个以上都继承了同一个基类,这种情况就是菱形继承。
菱形继承会产生两个问题:数据冗余和二义性
D这个类中有两个A的成员a,造成了数据冗余,同时访问a这个基类成员时,编译器不知道要使用哪一个,调用不明确,有二义性问题。
那么如何解决这些问题呢?——虚拟继承
使用方法很简单,在继承最开始的基类的继承方式的前面加上virtual关键字,对应这个例子中就是在B,C两个类继承A时,加上virtual。
class A{};class B : virtual public A{};class C : virtual public A{};class D : public B, public C{};
虚拟继承原理
虚拟继承后,派生类不会直接存储基类成员,而是储存了一个虚基表(用来存储偏移量)的地址,当我们要访问a这个基类成员时,会通过偏移量找到a的地址访问。
继承和组合
继承是一种is-a的关系,派生类is-a基类
组合是一种has-a的关系,比如汽车有一个轮胎
// Car和BMW Car和Benz构成is-a的关系class Car{protected:string _colour = "白色"; // 颜色string _num = "陕ABIT00"; // 车牌号};class BMW : public Car{public:void Drive() {cout << "好开-操控" << endl;}};class Benz : public Car{public:void Drive() {cout << "好坐-舒适" << endl;}};// Tire和Car构成has-a的关系class Tire{protected:string _brand = "Michelin"; // 品牌size_t _size = 17; // 尺寸};class Car{protected:string _colour = "白色"; // 颜色string _num = "陕ABIT00"; // 车牌号Tire _t; // 轮胎};
继承和组合是不同的代码复用方式:
继承是一种白箱复用,基类的实现细节对于派生类是可见的,一定程度上破坏了封装性,对基类的改变会对派生类造成不小的影响,代码维护难
组合是一种黑箱复用,被组合的类的实现对于外部类是不可见的,这种复用未破坏封装性,代码耦合度低,组合类之间没有很强的依赖关系,代码维护相对简单
在实际应用中能用组合就用组合