C++继承基础总结
引言
在编写多个类时,类之间可能会存在多个相同的成员变量,导致代码冗余度过高,C++继承的出现,使得我们可以在已有类的基础上构建新类,从而实现代码复用与结构扩展。
一、继承的基本概念
继承是指子类(派生类)继承父类(基类)的属性和方法,使得子类具有父类的功能,同时也可以扩展新的功能。通过继承,我们可以:
- 重用现有代码
- 建立类之间的层次关系
- 扩展现有类的功能
- 实现"是一种"(is-a)的关系
基本语法如下:
class Base
{
public:void Print(){cout << "class Base" << endl;}
};class Derived :public Base //子类声明所继承的基类
{};void test()
{Derived d;d.Print(); // 可以直接使用基类成员函数
}
二、继承方式
C++ 支持三种继承方式:public
、protected
、private
,它们影响了基类成员在派生类中的访问权限:
总的来说:除了基类的私有成员无论何种方式被继承均无法访问,其余在派生类中的访问权限均取基类和派生类继承方式中访问权限最小者。public>
protected>
private
2.1公有继承
2.2保护继承
2.3保护继承
三、基类与派生类的构造与析构
构造顺序:先构造基类,再构造派生类
析构顺序:先析构派生类,再析构基类
四、派生类中初始化基类
五、派生类重定义基类中的成员函数
派生类可以重新定义(重写)基类中的成员函数,这与多态中的虚函数覆盖不同。当派生类定义了与基类同名的函数时,基类的函数会被"隐藏"。
class Base
{
public:Base(int x = 0) :base_value(x) {}void show(){cout << "base_value : " << base_value << endl;}
private:int base_value;
};class Derived : public Base
{
public:Derived(int x = 0) :Base(x), Derived_value(x) {} void show() //重定义基类中的成员函数{Base::show(); //调用基类的show函数cout << "Derived_value : " << Derived_value << endl;}
private:int Derived_value;
};void test3()
{Derived d(10);d.show(); //调用派生类的show函数
}
重定义基类成员函数时需要注意以下几点:
- 名称隐藏规则:派生类中的同名函数会隐藏基类中的所有同名函数,无论参数列表是否相同
- 作用域解析访问:可以使用作用域解析运算符
::
显式访问被隐藏的基类函数 - 与虚函数覆盖的区别:这种重定义不是多态行为,而是简单的名称隐藏
- 参数不同时的注意事项:即使参数列表不同,派生类的函数仍会隐藏基类的同名函数
六、多重继承
C++支持多重继承,一个基类可以有多个派生类,多个基类可以有一个共同的派生类:
声明一个派生类属于多个基类语法:
class A
{};class B
{};class C : public A, public B //基类之间用逗号隔开
{};
七、菱形继承问题
7.1什么是菱形继承
菱形继承(Diamond Inheritance)是多重继承中的一个常见问题,其结构如下所示:
A 在这个结构中:
/ \ 类A是基类
B C 类B和类C都继承自类A
\ / 类D同时继承自类B和类C
D
7.2菱形继承带来的问题
二义性问题:当D想访问A中成员是,编译器会不知道应该走B这条路径来访问还是走C这条路径来访问
数据重复问题:D中会包含俩份来自A的数据成员,一份来自B,一份来自C,这样不仅会浪费内存,还会导致数据不一致。
7.3解决方案
法一:明确调用路径
class A
{
public: void show() { cout << "Class A" << endl; }
}; class B : public A
{
public: void showB() { cout << "Class B" << endl; }
}; class C : public A
{
public: void showC() { cout << "Class C" << endl; }
}; class D : public B, public C
{
public: void showD() { cout << "Class D" << endl; }
}; void test4()
{ D d; d.showD(); d.B::show(); // 明确走B路径访问A中成员 d.C::show(); // 明确走C路径访问A中成员
}
法二:虚继承解决方案
虚继承的作用是让所有的派生类共享一个基类子对象,通过使得中间类都加上virtual关键字,声明中间类B、C虚继承基类A,让B、C共享一个A的副本,保证最终的派生类D中只包含一份基类的成员:
class A
{
public: void show() { cout << "Class A" << endl; }
}; class B : virtual public A
{
public: void showB() { cout << "Class B" << endl; }
}; class C : virtual public A
{
public: void showC() { cout << "Class C" << endl; }
}; class D : public B, public C
{
public: void showD() { cout << "Class D" << endl; }
}; void test4()
{ D d; d.showD(); d.show();
}
虚继承的特点
- 构造顺序改变:
- 在虚继承中,首先构造虚基类,然后按照常规顺序构造其他类
- 最终派生类需要负责虚基类的初始化,即使它与虚基类之间隔了多层继承
- 虚基类表:
- 编译器通过特殊的"虚基类表"机制实现虚继承
- 这会增加一些运行时开销和对象大小
- 虚基类成员访问:
- 虚基类的成员在整个继承层次中只存在一份
- 可以直接通过最终派生类对象访问虚基类的成员
实际工程中建议尽量避免多重继承,特别是菱形继承结构。