目录
什么是继承
继承的定义
继承的声明格式
继承的简单实现
继承中的构造
构造的规则
友元与静态成员
继承的分类
单继承与多继承
菱形继承
虚拟继承
什么是继承
继承的定义
继承是代码复用的重要手段,是“代码复用”与“功能扩展”的结合。
-
代码复用:无需重复编写基类中已有的成员,派生类直接继承并使用。
-
功能扩展:派生类可以在继承的基础上添加新的成员,实现基类不具备的功能。
-
行为重定义:派生类可以修改基类中成员函数的实现,使同一函数在不同类中有不同的表现(为多态奠定基础)。
其价值是非常明确的,在大型项目开发中减少代码冗余:多个具有共性的类可以抽象出基类,将共性成员集中在基类中,派生类仅需要关注自身的特性。当共性逻辑需要修改时,只需修改基类代码,所有派生类都会同步受益,避免了多处修改可能引发的错误。
通过继承可以形成清晰的类结构,便于代码的组织和理解。同时支持多态特性,继承是多态的前提,通过基类指针或引用指向派生类对象,可以实现“同一接口,不同实现”的灵活编程。
继承的声明格式
// 单继承(仅一个基类)
class 派生类名 : 继承方式 基类名 {
// 派生类的成员(新增成员或重定义成员) };
// 多继承(多个基类,用逗号分隔)
class 派生类名 : 继承方式1 基类名1, 继承方式2 基类名2, … {
// 派生类的成员 };
这个多继承在后边还有一些连锁反应,不妨思考一下会引起什么问题
接下来放一张大家学C++时都背过的表深入理解一下继承后的关系,当然因为权限只能缩小不能放大,取两者的min就可以了

继承的简单实现
小狗是人类的好朋友,我们用小狗来演示一下继承的用法

#include <iostream>
#include <string>
using namespace std;
class Animal {
private:
int age;
protected:
string name;
public:
Animal(int a, string n) : age(a), name(n) {}
void eat() {
cout << name << " is eating." << endl;
}
int getAge() {
return age;
}
void setAge(int a) {
if (a > 0) age = a;
}
};
class Dog : public Animal {
private:
string breed;
public:
Dog(int a, string n, string b) : Animal(a, n), breed(b) {}
void bark() {
cout << name << " (breed: " << breed << ") is barking:woof woof!" << endl;
}
void eat() {
cout << name << " (dog) is eating bones." << endl;
}
};
int main() {
Dog dog1(3, "dzl", "BLZJMJQ");
dog1.eat();
dog1.setAge(21);
cout << dog1.getAge() << endl;
dog1.bark();
return 0;
}
简单运行一下

派生类Dog通过public继承基类Animal,获得了Animal的protected成员name和public成员函数eat()、getAge()、setAge()
这里要注意一下,派生类构造函数必须先初始化基类部分,在初始化列表中完成,不能在函数体内部初始化基类成员
继承中的构造
构造的规则
当创建派生类对象时,编译器会先自动调用基类的构造函数,初始化基类的成员;再调用派生类的构造函数,初始化派生类的新增成员。
如果基类没有默认构造函数(无参构造函数),派生类的构造函数必须在初始化列表中显式调用基类的带参构造函数,否则会编译报错。
如果有多个基类(多继承),构造函数的调用顺序与派生类声明中基类的顺序一致,与初始化列表中基类的顺序无关。
派生类的构造函数只能初始化自身的新增成员,无法直接初始化基类成员(需通过基类构造函数)。
友元与静态成员
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name;
};
class Student : public Person
{
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);
}
由于子类没有display函数的声明,所用在调用时,就会报错,因为找不到这个函数,所以如果要使用的话,我们需要把display同样声明到子类中,在这时要注意,因为person中的display也使用了student类,需要在person类之前声明student类
基类定义了
static
静态成员,则整个继承体系里面只有一个这样的成员
。无论派生出多少个子
类,都只有一个
static
成员实例
。就是继承了一份使用权
继承的分类
单继承与多继承
单继承是指派生类仅从一个基类继承,这是最常见的继承方式。类层次结构为树形结构,不易出现冲突。前文的示例为单继承。
多继承是指派生类从多个基类继承,同时获得多个基类的成员。多继承能提升代码复用的灵活性,但也会带来歧义,数据冗余的问题
#include <iostream>
using namespace std;
class Base1 {
public:
void show() { cout << "Base1 show()" << endl; }
};
class Base2 {
public:
void show() { cout << "Base2 show()" << endl; }
};
// 多继承:Derived从Base1和Base2继承
class Derived : public Base1, public Base2 {
public:
void display() {
// 调用show()?歧义:Base1::show()还是Base2::show()?
// show(); // 编译报错
Base1::show(); // 显式指定基类,解决歧义
Base2::show();
}
};
int main() {
Derived d;
d.display();
// d.show(); // 编译报错:歧义
d.Base1::show(); // 外部访问时也需显式指定基类
return 0;
}
当两个的父类有一个相同名字发函数,派生类在调用时,就会不知道调用哪个,从而引发一系列问题,所以此时必须显式指出使用的哪个基类的show
菱形继承

也正是这个多继承引发了菱形继承时的严重问题,比方说上边是爷爷类,父1类,父2类,儿子类,两个父类中都有相同的爷类成员,就造成了数据冗余
虚拟继承
使用这个办法就可以解决上述问题,加上virtual,上述B,C中不在生成A,而是一个指针指向虚基表,表中存了偏移量,可以通过偏移量分别找到B,C公共的A
最后简单演示一下
#include <iostream>
using namespace std;
class Base {
protected:
int data;
public:
Base(int d) : data(d) {}
void showData() { cout << "Data: " << data << endl; }
};
class Derived1 : virtual public Base {
public:
Derived1(int d) : Base(d) {}
};
class Derived2 : virtual public Base {
public:
Derived2(int d) : Base(d) {}
};
class FinalDerived : public Derived1, public Derived2 {
public:
// 虚继承下,最终派生类必须显式初始化虚基类Base
FinalDerived(int d) : Base(d), Derived1(d), Derived2(d) {}
};
int main() {
FinalDerived fd(10);
fd.showData();
fd.data = 20;
fd.showData();
return 0;
}
共享虚基类对象:Derived1和Derived2不再各自拥有Base类的对象,而是共享同一个Base对象,消除了数据冗余
虚继承下,虚基类的初始化责任由最终派生类承担,无论继承层级多少,都需在最终派生类的初始化列表中显式调用虚基类的构造函数
虚继承会增加一定的内存开销和性能损耗,因此仅在需要解决菱形继承问题时使用,避免滥用。


