在C++中,继承是面向对象编程(OOP)的一个重要特性,它允许一个类(派生类)基于另一个类(基类)来构建。继承的主要目的是实现代码的重用和扩展。以下是C++继承的一些关键概念和用法:
1. 基本语法
class BaseClass {// 基类成员
};class DerivedClass : access-specifier BaseClass {// 派生类成员
};
-
access-specifier
可以是public
、protected
或private
,用于指定基类成员在派生类中的访问权限。
2. 访问控制
-
public继承:基类的
public
成员在派生类中仍然是public
,protected
成员仍然是protected
。 -
protected继承:基类的
public
和protected
成员在派生类中都变为protected
。 -
private继承:基类的
public
和protected
成员在派生类中都变为private
。
3. 构造函数和析构函数
-
派生类的构造函数会首先调用基类的构造函数,然后再执行派生类自己的构造函数。
-
析构函数的调用顺序相反,先调用派生类的析构函数,再调用基类的析构函数。
class Base {
public:Base() { cout << "Base Constructor" << endl; }~Base() { cout << "Base Destructor" << endl; }
};class Derived : public Base {
public:Derived() { cout << "Derived Constructor" << endl; }~Derived() { cout << "Derived Destructor" << endl; }
};int main() {Derived d;return 0;
}
输出:
Base Constructor Derived Constructor Derived Destructor Base Destructor
4. 函数重写(Override)
-
派生类可以重写基类的虚函数,以实现多态。
-
使用
override
关键字可以显式地表示重写基类的虚函数。
class Base {
public:virtual void show() { cout << "Base show" << endl; }
};class Derived : public Base {
public:void show() override { cout << "Derived show" << endl; }
};int main() {Base* b = new Derived();b->show(); // 输出 "Derived show"delete b;return 0;
}
5. 多重继承
-
C++支持多重继承,即一个派生类可以从多个基类继承。
-
多重继承可能会导致菱形继承问题(Diamond Problem),可以通过虚继承来解决。
class A {
public:void show() { cout << "A show" << endl; }
};class B : public A {};
class C : public A {};
class D : public B, public C {};int main() {D d;d.show(); // 错误:show() 不明确return 0;
}
6. 虚继承
-
虚继承用于解决多重继承中的菱形继承问题。
class A {
public:void show() { cout << "A show" << endl; }
};class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};int main() {D d;d.show(); // 输出 "A show"return 0;
}
7. 访问基类的成员
-
可以使用作用域解析运算符
::
来访问基类的成员。
class Base {
public:void show() { cout << "Base show" << endl; }
};class Derived : public Base {
public:void show() {Base::show(); // 调用基类的 show() 函数cout << "Derived show" << endl;}
};int main() {Derived d;d.show();return 0;
}
8. 纯虚函数和抽象类
-
纯虚函数是在基类中声明但不定义的虚函数,派生类必须重写纯虚函数。
-
包含纯虚函数的类称为抽象类,不能实例化。
class Base {
public:virtual void show() = 0; // 纯虚函数
};class Derived : public Base {
public:void show() override { cout << "Derived show" << endl; }
};int main() {// Base b; // 错误:不能实例化抽象类Derived d;d.show(); // 输出 "Derived show"return 0;
}
9. 继承中的类型转换
-
派生类对象可以隐式转换为基类对象(向上转型)。
-
基类对象不能隐式转换为派生类对象(向下转型),但可以通过
dynamic_cast
进行安全的向下转型。
class Base {
public:virtual void show() { cout << "Base show" << endl; }
};class Derived : public Base {
public:void show() override { cout << "Derived show" << endl; }
};int main() {Base* b = new Derived();b->show(); // 输出 "Derived show"Derived* d = dynamic_cast<Derived*>(b);if (d) {d->show(); // 输出 "Derived show"}delete b;return 0;
}
10. 继承与组合
-
继承表示“是一个”(is-a)关系,组合表示“有一个”(has-a)关系。
-
在设计类时,应根据实际情况选择使用继承还是组合。
class Engine {
public:void start() { cout << "Engine started" << endl; }
};class Car {
private:Engine engine;
public:void start() {engine.start();cout << "Car started" << endl;}
};int main() {Car car;car.start();return 0;
}
总结
继承是C++中实现代码重用和多态的重要机制。通过继承,派生类可以复用基类的代码,并且可以通过重写虚函数来实现多态。理解继承的访问控制、构造函数和析构函数的调用顺序、多重继承和虚继承等概念,对于编写高质量的C++代码至关重要。
菱形继承问题(Diamond Problem)是C++多重继承中的一个经典问题。它发生在派生类通过多个路径继承同一个基类时,导致基类的成员在派生类中存在多个副本,从而引发二义性和资源浪费的问题。
菱形继承问题的结构
菱形继承问题的典型结构如下:
复制
A/ \B C\ /D
-
类
B
和类C
都继承自类A
。 -
类
D
同时继承自类B
和类C
。
在这种情况下,类 D
会包含两份类 A
的成员,从而导致二义性和资源浪费。
示例代码
#include <iostream>
using namespace std;class A {
public:int value;A() : value(10) {}
};class B : public A {};
class C : public A {};
class D : public B, public C {};int main() {D d;// d.value = 20; // 错误:对成员 'value' 的访问不明确cout << "Size of D: " << sizeof(d) << endl; // 输出 8(两个 int)return 0;
}
问题分析
-
二义性:
-
类
D
从类B
和类C
分别继承了一份A
的成员value
。 -
当尝试访问
d.value
时,编译器无法确定应该使用B
中的value
还是C
中的value
,因此会报错。
-
-
资源浪费:
-
类
D
中包含两份A
的成员,导致内存浪费。
-
解决方法:虚继承(Virtual Inheritance)
C++ 提供了虚继承机制来解决菱形继承问题。通过虚继承,可以确保派生类中只保留一份基类的成员。
修改后的代码
#include <iostream>
using namespace std;class A {
public:int value;A() : value(10) {}
};class B : virtual public A {}; // 虚继承
class C : virtual public A {}; // 虚继承
class D : public B, public C {};int main() {D d;d.value = 20; // 正确:value 现在只有一份cout << "Value in D: " << d.value << endl; // 输出 20cout << "Size of D: " << sizeof(d) << endl; // 输出 16(包含虚基类指针)return 0;
}
关键点
-
虚继承:
-
使用
virtual
关键字声明继承关系(如class B : virtual public A
)。 -
虚继承确保派生类
D
中只保留一份基类A
的成员。
-
-
内存布局:
-
虚继承会引入额外的开销(如虚基类指针),因此类
D
的大小可能会增加。
-
-
访问成员:
-
现在可以直接访问
d.value
,因为value
只有一份,不存在二义性。
-
虚继承的工作原理
虚继承通过引入虚基类表(Virtual Base Table)来实现:
-
每个虚继承的类会包含一个指向虚基类表的指针。
-
虚基类表记录了虚基类成员的偏移量,从而确保派生类中只保留一份虚基类的成员。
总结
-
菱形继承问题是由于多重继承导致基类成员在派生类中存在多个副本,从而引发二义性和资源浪费。
-
虚继承是解决菱形继承问题的标准方法,它确保派生类中只保留一份基类的成员。
-
虚继承会引入额外的开销(如虚基类指针),因此应谨慎使用。
在实际开发中,尽量避免复杂的多重继承结构,优先使用组合(Composition)或其他设计模式来替代多重继承