c++中类的继承
前言
一、继承的基本概念
继承是面向对象编程的三大特性之一(封装、继承、多态),它允许我们根据一个类来定义另一个类,使得代码可以重用,同时也便于创建更抽象的关系模型。
1.1 为什么需要继承
-
代码重用:可以复用基类的代码,减少重复编写
-
层次化分类:可以创建从通用到特殊的类层次结构
-
可扩展性:可以在不修改基类的情况下扩展功能
1.2 基本语法
class 派生类名 : 访问修饰符 基类名 {// 派生类成员
};
二、继承的访问控制
C++中有三种继承方式,它们决定了基类成员在派生类中的访问权限:
2.1 public继承(最常用)
class Base {
public:int publicVar;
protected:int protectedVar;
private:int privateVar;
};class Derived : public Base {// publicVar仍然是public// protectedVar仍然是protected// privateVar不可访问
};
2.2 protected继承
class Derived : protected Base {// publicVar变为protected// protectedVar仍然是protected// privateVar不可访问
};
2.3 private继承(默认继承方式)
class Derived : private Base {// publicVar变为private// protectedVar变为private// privateVar不可访问
};// 等价于
class Derived : Base { ... };
2.4 访问规则总结表
基类成员访问权限 | 继承方式 | 在派生类中的访问权限 |
---|---|---|
public | public | public |
public | protected | protected |
public | private | private |
protected | public | protected |
protected | protected | protected |
protected | private | private |
private | 任何方式 | 不可访问 |
三、派生类的构造与析构
3.1 构造函数调用顺序
-
基类构造函数
-
成员对象构造函数(按声明顺序)
-
派生类构造函数
class Base {
public:Base() { cout << "Base constructor" << endl; }
};class Member {
public:Member() { cout << "Member constructor" << endl; }
};class Derived : public Base {Member m;
public:Derived() { cout << "Derived constructor" << endl; }
};// 输出顺序:
// Base constructor
// Member constructor
// Derived constructor
这里要注意的是要构造derived的话必须先构造base类
3.2 析构函数调用顺序
与构造函数相反:
-
派生类析构函数
-
成员对象析构函数
-
基类析构函数
3.3 显式调用基类构造函数
class Base {int x;
public:Base(int i) : x(i) {}
};class Derived : public Base {int y;
public:Derived(int i, int j) : Base(i), y(j) {}
};
使用初始化列表初始化列表直接调用基类的公有方法给私有成员赋值
四、函数重写与隐藏
4.1 函数重写(覆盖)
发生在虚函数中,是实现多态的基础:
class Base {
public:virtual void show() { cout << "Base show" << endl; }
};class Derived : public Base {
public:void show() override { cout << "Derived show" << endl; }
};Base* b = new Derived();
b->show(); // 输出 "Derived show" - 多态
将base类的成员方法虚写了这里只是虚方法不是纯虚函数所以派生类可以重写也可以不重写
如果重写后调用的时候调用的是派生类的方法
4.2 函数隐藏
如果基类和派生类有同名函数但不是虚函数,会发生隐藏:
class Base {
public:void display() { cout << "Base display" << endl; }
};class Derived : public Base {
public:void display(int x) { cout << "Derived display" << endl; }
};Derived d;
d.display(1); // OK
d.display(); // 错误!基类display被隐藏
d.Base::display(); // 正确,显式调用基类方法
当派生类定义了与基类同名的函数(不论参数是否相同),基类的同名函数在派生类作用域中会被隐藏。
五、多重继承与虚继承
5.1 多重继承
一个类可以继承多个基类:
class A { /*...*/ };
class B { /*...*/ };
class C : public A, public B { /*...*/ };
5.2 菱形继承问题
Base/ \
Der1 Der2\ /Final
这样的情况的话会导致Final类中有两份Base的成员,造成二义性。
class Base {
public:int data;void func() { cout << "Base::func()" << endl; }
};class Der1 : public Base { /* 可能添加特有成员 */ };
class Der2 : public Base { /* 可能添加特有成员 */ };class Final : public Der1, public Der2 { /* ... */ };
Final
类会包含两份Base
的成员(通过Der1
和Der2
各继承一份)对于int data
,Final
对象中将有两份拷贝
六、继承中的类型转换
6.1 向上转型(Upcasting)
派生类指针/引用转换为基类指针/引用,是安全的隐式转换
Derived d;
Base* pb = &d; // 向上转型
Base& rb = d; // 向上转型
6.2 向下转型(Downcasting)
基类指针/引用转换为派生类指针/引用,需要使用dynamic_cast(需要多态类型):
Base* pb = new Derived();
Derived* pd = dynamic_cast<Derived*>(pb);
if (pd) { /* 转换成功 */ }
七、实际应用示例
员工管理系统
class Employee {
protected:string name;double salary;
public:Employee(string n, double s) : name(n), salary(s) {}virtual void printInfo() {cout << "Name: " << name << ", Salary: " << salary << endl;}
};class Manager : public Employee {string department;
public:Manager(string n, double s, string d) : Employee(n, s), department(d) {}void printInfo() override {cout << "Manager - Department: " << department << ", ";Employee::printInfo();}
};
九、常见问题与陷阱
-
切片问题:将派生类对象赋值给基类对象时,派生类特有部分会被"切掉"
Derived d; Base b = d; // 切片发生
-
私有继承误用:私有继承表示"implemented-in-terms-of"关系,不是"is-a"关系
-
默认继承访问权限:class默认private继承,struct默认public继承
-
虚析构函数:当基类指针指向派生类对象时,基类必须有虚析构函数才能正确调用派生类析构函数
总结
继承是C++面向对象编程的核心概念之一,正确使用继承可以创建出灵活、可维护的代码结构。理解各种继承方式的区别、构造/析构顺序、虚函数机制等关键点,对于设计良好的类层次结构至关重要。同时,也要注意继承的滥用问题,在适当的时候考虑使用组合而非继承。