类内成员的权限
public | private | protected |
---|---|---|
类内、类外、子类都可以访问 | 类内可以访问,类外不能访问,子类不能访问 | 类内可以访问,类外不能访问,子类可以访问 |
类内部的成员权限决定了子类能否被继承
只有public 和protected 权限的成员才能被子类继承
C++ 最重要的特征是代码重用,通过继承机制可以利用已有的数据类型来定义新的数据类型,新的类不仅拥有旧类的成员,还拥有新定义的成员
继承的作用
- 复用已有类的数据、函数,减少冗余代码。
将公共的函数写在基类中,通过继承来实现复用
- 修改已有类的功能,而不修改原类。
如果类内部的函数不能满足需求时,需要修改该函数的功能。
修改源代码的代价很大,所以应该使用重定义该函数来实现我们自己的需求
(当子类重定义父类的函数时,父类的函数会被隐藏
) - 扩展已有类的功能,而不修改原类。
- 为实现强大的多态机制做支撑。
继承方式
- 继承的方式决定了被继承下来的成员在子类中的权限
public | private | protected |
---|---|---|
基类什么权限,继承下来就是什么权限 | 继承下来的成员在子类中全部为private权限 | 继承下来的成员在子类中全部为protected权限 |
继承中的构造和析构顺序
class Base
{
public:
Base()
{
cout<<"base构造函数"<<endl;
}
~Base()
{
cout<<"base析构函数"<<endl;
}
}
class Derived : public Base
{
public:
Derived()
{
cout<<"Derived构造函数"<<endl;
}
~Base()
{
cout<<"Derived析构函数"<<endl;
}
}
void test()
{
Derived der;
}
创建对象时,首先调用父类的构造,在调用子类的构造
销毁对象时,首先调用子类的析构,在调用父类的析构
初始化列表在继承中的作用
子类初始化时,编译器默认调用父类的无参构造,当父类没有提供无参构造时,
需要使用初始化列表指定调用父类的哪一个构造函数
class Base
{
public:
Base(int a)
{
cout<<"base构造函数"<<endl;
}
~Base(){}
}
class Derived : public Base
{
public:
Derived(int num) :Base(num)
{
cout<<"Derived构造函数"<<endl;
}
~Base(){}
}
void test()
{
Derived der(10);
}
继承中同名成员处理
- 当子类成员和父类成员同名,父类同名成员会被隐藏。
- 子类默认访问子类的成员。
- 子类内部通过 类名::成员 的方式访问父类同名成员。
- 任何时候重新定义基类中的一个重载函数,在新类中所有的其他版本将被自动隐藏。
继承和组合情况下构造和析构调用顺序
当其他类作为当前类的对象成员,这种关系为
组合关系
初始化顺序
- 先调用父类构造函数。
- 在调用对象成员构造函数,按照定义顺序。
- 最后调用子类构造函数。
- 析构函数调用与构造函数调用相反。
不能继承的函数
- 私有权限的函数。
- 构造函数。
- 析构函数。
- 赋值运算符函数。
构造函数和析构函数用来处理对象的创建和析构操作,构造和析构函数只知道对它们的特定层次的对象做什么,也就是说构造函数和析构函数不能被继承,必须为每一个特定的派生类分别创建。
operator=也不能被继承,因为它完成类似构造函数的行为。也就是说尽管我们知道如何由=右边的对象如何初始化=左边的对象的所有成员,但是这个并不意味着对其派生类依然有效
多继承
- 多继承可以复用多个类的代码
- 多继承容易出现二义性的问题
- 二义性只能通过类名访问来解决(应该避免二义性)
菱形继承的问题
二义性问题。
子类中存在多份基类数据。
使用虚继承可以解决菱形继承的问题
虚继承
当子类使用虚继承后,父类就变成了虚基类
如果一个类时虚基类,该类的数据在所有的子类中只会出现一份
虚继承主要是解决菱形继承的问题
语法
class Animal
{
public:
Animal()
{
cout << "Animal 构造函数" << endl;
}
~Animal()
{
}
public:
int m_animal;
};
//虚继承
class Demo : virtual public Animal
{
public:
int m_demo;
}
虚基类初始化问题
当使用虚继承时,虚基类是被共享的,也就是在继承体系中无论被继承多少次,对象内存模型中均只会出现一个虚基类的子对象。
C++ 标准中要求在每一次继承子类中都必须书写初始化语句(因为每一次继承子类可能都会用来定义对象),但是虚基类的初始化是由最后的子类完成,其他的初始化语句都不会调用。
虚继承总结
当发生虚继承时,子类没有将基类的数据继承到本类中,编译器会在子类中安插vbptr指针
vbptr 指针指向保存着基类数据偏移量的表,通过该表找到基类数据。