13.1 继承的基本概念
13.1.1 为什么需要继承?
继承允许从已有类(基类)派生出新类(派生类),派生类自动拥有基类的属性和方法,并可以添加新功能或修改已有功能。
继承关系示意:
┌─────────────────┐
│ Animal(基类) │
│ - name │
│ - age │
│ + eat() │
│ + sleep() │
└────────┬────────┘
│ 继承
┌────────┴────────┐
│ │
┌───────┴──────┐ ┌───────┴──────┐
│ Dog(派生类)│ │ Cat(派生类)│
│ + bark() │ │ + meow() │
│ + fetch() │ │ + purr() │
└──────────────┘ └──────────────┘
派生类拥有:基类的所有成员 + 自己新增的成员
继承的好处:
- 代码复用:不重复编写相同的代码
- 扩展性:在已有功能基础上添加新功能
- 多态性:统一接口,不同实现
13.1.2 基本继承语法
cpp
// 语法格式:
class 派生类名 : 访问说明符 基类名
{
// 派生类新增的成员
};
// 访问说明符:public(最常用)、protected、private
13.2 公有继承(public inheritance)
13.2.1 基类与派生类的基本示例
cpp
// inheritance_basic.cpp -- 公有继承基础
#include <iostream>
#include <string>
// ===== 基类 =====
class TableTennisPlayer
{
private:
std::string firstname;
std::string lastname;
bool hasTable;
public:
TableTennisPlayer(const std::string& fn = "none",
const std::string& ln = "none",
bool ht = false)
: firstname(fn), lastname(ln), hasTable(ht)
{
std::cout << "[基类构造] " << fn << " " << ln << std::endl;
}
~TableTennisPlayer()
{
std::cout << "[基类析构] " << firstname << std::endl;
}
void name() const
{
std::cout << lastname << ", " << firstname << std::endl;
}
bool hasATable() const { return hasTable; }
void resetTable(bool v) { hasTable = v; }
};
// ===== 派生类 =====
class RatedPlayer : public TableTennisPlayer
{
private:
unsigned int rating; // 派生类新增的成员
public:
// 派生类构造函数:必须调用基类构造函数
RatedPlayer(unsigned int r,
const std::string& fn,
const std::string& ln,
bool ht = false)
: TableTennisPlayer(fn, ln, ht), // 调用基类构造函数
rating(r)
{
std::cout << "[派生类构造] rating=" << r << std::endl;
}
// 也可以用基类对象初始化
RatedPlayer(unsigned int r, const TableTennisPlayer& tp)
: TableTennisPlayer(tp), // 调用基类拷贝构造
rating(r)
{
std::cout << "[派生类构造2] rating=" << r << std::endl;
}
~RatedPlayer()
{
std::cout << "[派生类析构] rating=" << rating << std::endl;
}
unsigned int getRating() const { return rating; }
void setRating(unsigned int r) { rating = r; }
};
int main()
{
using namespace std;
cout << "===== 创建基类对象 =====" << endl;
TableTennisPlayer player1("张", "三", true);
player1.name();
cout << "有球桌:" << boolalpha << player1.hasATable() << endl;
cout << "\n===== 创建派生类对象 =====" << endl;
RatedPlayer rplayer1(1140, "李", "四", true);
rplayer1.name(); // 继承自基类
rplayer1.hasATable(); // 继承自基类
cout << "评级:" << rplayer1.getRating() << endl; // 派生类新增
cout << "\n===== 用基类对象初始化派生类 =====" << endl;
RatedPlayer rplayer2(1212, player1);
rplayer2.name();
cout << "评级:" << rplayer2.getRating() << endl;
cout << "\n===== 派生类赋值给基类(向上转型)=====" << endl;
TableTennisPlayer& ref = rplayer1; // 基类引用指向派生类对象
ref.name(); // 调用基类方法
cout << "\n===== 程序结束 =====" << endl;
return 0;
}
输出(构造/析构顺序):
===== 创建派生类对象 =====
[基类构造] 李 四 ← 先构造基类部分
[派生类构造] rating=1140 ← 再构造派生类部分
===== 程序结束 =====
[派生类析构] rating=1140 ← 先析构派生类部分
[基类析构] 李 ← 再析构基类部分
💡 构造/析构顺序:
- 构造:先基类,后派生类
- 析构:先派生类,后基类(与构造相反)
13.3 派生类与基类的关系
13.3.1 访问控制在继承中的体现
cpp
// access_in_inheritance.cpp -- 继承中的访问控制
#include <iostream>
class Base
{
public:
int pub; // 公有:任何地方可访问
protected:
int prot; // 保护:类内和派生类可访问
private:
int priv; // 私有:只有类内可访问
public:
Base(int a, int b, int c) : pub(a), prot(b), priv(c) {}
void showBase() const
{
std::cout << "pub=" << pub
<< " prot=" << prot
<< " priv=" << priv << std::endl;
}
};
class Derived : public Base
{
public:
int own;
Derived(int a, int b, int c, int d)
: Base(a, b, c), own(d) {}
void showDerived() const
{
std::cout << "pub=" << pub << std::endl; // ✅ 可访问
std::cout << "prot=" << prot << std::endl; // ✅ 可访问
// std::cout << priv; // ❌ 不可访问
std::cout << "own=" << own << std::endl; // ✅ 自己的成员
}
};
int main()
{
Derived d(1, 2, 3, 4);
d.pub = 10; // ✅ public成员,外部可访问
// d.prot = 20; // ❌ protected成员,外部不可访问
// d.priv = 30; // ❌ private成员,外部不可访问
d.showBase();
d.showDerived();
return 0;
}
三种继承方式对比:
| 基类成员 | public继承 | protected继承 | private继承 |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | 不可访问 | 不可访问 | 不可访问 |
13.3.2 派生类不能继承的内容
cpp
// 派生类不能继承:
// 1. 构造函数(但可以调用)
// 2. 析构函数(但会自动调用)
// 3. 赋值运算符(但可以调用)
// 4. 友元函数(友元关系不继承)
13.4 虚函数与多态
13.4.1 为什么需要虚函数?
cpp
// without_virtual.cpp -- 没有虚函数的问题
#include <iostream>
class Animal
{
public:
void speak() const // 非虚函数
{
std::cout << "动物发出声音" << std::endl;
}
};
class Dog : public Animal
{
public:
void speak() const // 隐藏基类函数
{
std::cout << "汪汪汪!" << std::endl;
}
};
class Cat : public Animal
{
public:
void speak() const
{
std::cout << "喵喵喵!" << std::endl;
}
};
int main()
{
Dog dog;
Cat cat;
dog.speak(); // 汪汪汪!(正确)
cat.speak(); // 喵喵喵!(正确)
// 问题:通过基类指针调用时,无法实现多态!
Animal* p1 = &dog;
Animal* p2 = &cat;
p1->speak(); // 动物发出声音(错误!应该是汪汪汪)
p2->speak(); // 动物发出声音(错误!应该是喵喵喵)
return 0;
}
13.4.2 虚函数实现多态
cpp
// virtual_demo.cpp -- 虚函数实现多态
#include <iostream>
#include <string>
class Animal
{
protected:
std::string name;
public:
Animal(const std::string& n) : name(n) {}
// virtual 关键字:声明虚函数
virtual void speak() const
{
std::cout << name << " 发出声音" << std::endl;
}
virtual std::string getType() const
{
return "动物";
}
// 虚析构函数(含虚函数的类必须有!)
virtual ~Animal()
{
std::cout << "[析构] " << name << std::endl;
}
void showInfo() const
{
std::cout << "类型:" << getType()
<< " 名字:" << name << std::endl;
}
};
class Dog : public Animal
{
public:
Dog(const std::string& n) : Animal(n) {}
// override 关键字(C++11):明确表示重写虚函数
void speak() const override
{
std::cout << name << " 说:汪汪汪!" << std::endl;
}
std::string getType() const override
{
return "狗";
}
};
class Cat : public Animal
{
public:
Cat(const std::string& n) : Animal(n) {}
void speak() const override
{
std::cout << name << " 说:喵喵喵!" << std::endl;
}
std::string getType() const override
{
return "猫";
}
};
class Duck : public Animal
{
public:
Duck(const std::string& n) : Animal(n) {}
void speak() const override
{
std::cout << name << " 说:嘎嘎嘎!" << std::endl;
}
std::string getType() const override { return "鸭子"; }
};
// 多态函数:接受基类指针,调用正确的派生类方法
void makeSpeak(const Animal* animal)
{
animal->speak(); // 根据实际类型调用正确的speak()
}
int main()
{
using namespace std;
Dog dog("旺财");
Cat cat("咪咪");
Duck duck("唐老鸭");
cout << "===== 直接调用 =====" << endl;
dog.speak();
cat.speak();
duck.speak();
cout << "\n===== 通过基类指针(多态)=====" << endl;
Animal* animals[] = {&dog, &cat, &duck};
for (int i = 0; i < 3; i++)
{
animals[i]->showInfo(); // 调用虚函数getType()
animals[i]->speak(); // 多态调用
}
cout << "\n===== 通过函数参数(多态)=====" << endl;
makeSpeak(&dog);
makeSpeak(&cat);
makeSpeak(&duck);
cout << "\n===== 动态分配(多态)=====" << endl;
Animal* p = new Dog("小黑");
p->speak(); // 汪汪汪!(虚函数正确调用)
delete p; // 调用Dog的析构函数(因为虚析构函数)
return 0;
}
输出:
===== 通过基类指针(多态)=====
类型:狗 名字:旺财
旺财 说:汪汪汪!
类型:猫 名字:咪咪
咪咪 说:喵喵喵!
类型:鸭子 名字:唐老鸭
唐老鸭 说:嘎嘎嘎!
13.4.3 虚函数的工作原理(虚函数表)
虚函数表(vtable)机制:
Animal对象:
┌──────────────┐
│ vptr ────────┼──→ Animal的vtable
│ name │ ┌─────────────────┐
└──────────────┘ │ speak → Animal::speak │
│ getType → Animal::getType│
└─────────────────┘
Dog对象:
┌──────────────┐
│ vptr ────────┼──→ Dog的vtable
│ name │ ┌─────────────────┐
└──────────────┘ │ speak → Dog::speak │
│ getType → Dog::getType │
└─────────────────┘
通过基类指针调用虚函数时:
1. 找到对象的vptr
2. 通过vptr找到vtable
3. 在vtable中找到正确的函数地址
4. 调用该函数
→ 这就是运行时多态!
13.5 纯虚函数与抽象类
13.5.1 纯虚函数
cpp
// abstract_class.cpp -- 纯虚函数与抽象类
#include <iostream>
#include <string>
#include <vector>
#include <cmath>
// 抽象类:含有纯虚函数的类
class Shape
{
protected:
std::string color;
public:
Shape(const std::string& c = "黑色") : color(c) {}
// 纯虚函数:= 0,派生类必须实现
virtual double area() const = 0;
virtual double perimeter() const = 0;
virtual std::string name() const = 0;
// 普通虚函数:有默认实现,派生类可以重写
virtual void show() const
{
std::cout << name() << "(" << color << ")"
<< " 面积=" << area()
<< " 周长=" << perimeter() << std::endl;
}
virtual ~Shape() {}
};
// 不能实例化抽象类:
// Shape s; // ❌ 错误!
class Circle : public Shape
{
private:
double radius;
static constexpr double PI = 3.14159265358979;
public:
Circle(double r, const std::string& c = "红色")
: Shape(c), radius(r) {}
double area() const override { return PI * radius * radius; }
double perimeter() const override { return 2 * PI * radius; }
std::string name() const override { return "圆形"; }
};
class Rectangle : public Shape
{
private:
double width, height;
public:
Rectangle(double w, double h, const std::string& c = "蓝色")
: Shape(c), width(w), height(h) {}
double area() const override { return width * height; }
double perimeter() const override { return 2 * (width + height); }
std::string name() const override { return "矩形"; }
};
class Triangle : public Shape
{
private:
double a, b, c; // 三边长
public:
Triangle(double a, double b, double c,
const std::string& col = "绿色")
: Shape(col), a(a), b(b), c(c) {}
double area() const override
{
double s = (a + b + c) / 2;
return sqrt(s * (s-a) * (s-b) * (s-c)); // 海伦公式
}
double perimeter() const override { return a + b + c; }
std::string name() const override { return "三角形"; }
};
int main()
{
using namespace std;
// 用基类指针管理不同形状(多态)
vector<Shape*> shapes;
shapes.push_back(new Circle(5.0));
shapes.push_back(new Rectangle(4.0, 6.0));
shapes.push_back(new Triangle(3.0, 4.0, 5.0));
shapes.push_back(new Circle(3.0, "黄色"));
cout << "===== 所有形状 =====" << endl;
double totalArea = 0;
for (const auto& s : shapes)
{
s->show();
totalArea += s->area();
}
cout << "\n总面积:" << totalArea << endl;
// 释放内存
for (auto& s : shapes)
delete s;
return 0;
}
13.6 虚析构函数
cpp
// virtual_destructor.cpp -- 虚析构函数的重要性
#include <iostream>
class Base
{
public:
Base() { std::cout << "Base 构造" << std::endl; }
// ❌ 非虚析构函数(危险!)
// ~Base() { std::cout << "Base 析构" << std::endl; }
// ✅ 虚析构函数(正确!)
virtual ~Base()
{
std::cout << "Base 析构" << std::endl;
}
};
class Derived : public Base
{
private:
int* data;
public:
Derived()
{
data = new int[100];
std::cout << "Derived 构造(分配内存)" << std::endl;
}
~Derived()
{
delete[] data; // 释放内存
std::cout << "Derived 析构(释放内存)" << std::endl;
}
};
int main()
{
std::cout << "--- 通过派生类指针删除(正确)---" << std::endl;
Derived* dp = new Derived();
delete dp; // 调用 Derived::~Derived() 然后 Base::~Base()
std::cout << "\n--- 通过基类指针删除 ---" << std::endl;
Base* bp = new Derived();
delete bp;
// 有虚析构函数:先调用Derived::~Derived(),再调用Base::~Base() ✅
// 无虚析构函数:只调用Base::~Base(),内存泄漏!❌
return 0;
}
⚠️ 规则 :只要类中有虚函数,就必须将析构函数声明为虚函数!
否则通过基类指针
delete派生类对象时,派生类的析构函数不会被调用,导致资源泄漏。
13.7 override 和 final(C++11)
cpp
// override_final.cpp -- override和final关键字
#include <iostream>
class Base
{
public:
virtual void func1() const { std::cout << "Base::func1" << std::endl; }
virtual void func2() { std::cout << "Base::func2" << std::endl; }
virtual void func3() const { std::cout << "Base::func3" << std::endl; }
virtual ~Base() {}
};
class Derived : public Base
{
public:
// ✅ override:明确表示重写虚函数,编译器会检查
void func1() const override
{
std::cout << "Derived::func1" << std::endl;
}
// ❌ 没有override时,签名不匹配会静默创建新函数(常见bug)
// void func2() const { ... } // 这是新函数,不是重写!
// ✅ 正确重写
void func2() override
{
std::cout << "Derived::func2" << std::endl;
}
// final:禁止进一步重写
void func3() const override final
{
std::cout << "Derived::func3(不可再重写)" << std::endl;
}
};
// final类:禁止继承
class FinalClass final : public Base
{
public:
void func1() const override
{
std::cout << "FinalClass::func1" << std::endl;
}
};
// class CannotInherit : public FinalClass {}; // ❌ 错误!
int main()
{
Derived d;
Base* p = &d;
p->func1(); // Derived::func1
p->func2(); // Derived::func2
p->func3(); // Derived::func3
return 0;
}
13.8 动态类型转换
cpp
// dynamic_cast.cpp -- 动态类型转换
#include <iostream>
#include <string>
class Animal
{
public:
virtual std::string type() const { return "Animal"; }
virtual ~Animal() {}
};
class Dog : public Animal
{
public:
std::string type() const override { return "Dog"; }
void fetch() const { std::cout << "去捡球!" << std::endl; }
};
class Cat : public Animal
{
public:
std::string type() const override { return "Cat"; }
void purr() const { std::cout << "呼噜呼噜..." << std::endl; }
};
int main()
{
using namespace std;
Animal* animals[] = {new Dog(), new Cat(), new Dog()};
for (int i = 0; i < 3; i++)
{
cout << "类型:" << animals[i]->type() << endl;
// dynamic_cast:安全的向下转型
// 成功返回有效指针,失败返回nullptr
if (Dog* dog = dynamic_cast<Dog*>(animals[i]))
{
cout << " 这是狗:";
dog->fetch();
}
else if (Cat* cat = dynamic_cast<Cat*>(animals[i]))
{
cout << " 这是猫:";
cat->purr();
}
}
for (auto& a : animals)
delete a;
return 0;
}
13.9 多层继承
cpp
// multi_level.cpp -- 多层继承示例
#include <iostream>
#include <string>
// 第一层
class Vehicle
{
protected:
std::string brand;
int year;
public:
Vehicle(const std::string& b, int y)
: brand(b), year(y) {}
virtual void show() const
{
std::cout << "品牌:" << brand << " 年份:" << year;
}
virtual ~Vehicle() {}
};
// 第二层
class Car : public Vehicle
{
protected:
int doors;
public:
Car(const std::string& b, int y, int d)
: Vehicle(b, y), doors(d) {}
void show() const override
{
Vehicle::show(); // 调用基类方法
std::cout << " 车门:" << doors;
}
};
// 第三层
class ElectricCar : public Car
{
private:
int batteryCapacity; // 电池容量(kWh)
public:
ElectricCar(const std::string& b, int y,
int d, int battery)
: Car(b, y, d), batteryCapacity(battery) {}
void show() const override
{
Car::show(); // 调用父类方法
std::cout << " 电池:" << batteryCapacity << "kWh" << std::endl;
}
int getRange() const { return batteryCapacity * 6; } // 估算续航
};
int main()
{
using namespace std;
ElectricCar tesla("Tesla", 2024, 4, 100);
tesla.show();
cout << "估算续航:" << tesla.getRange() << " km" << endl;
// 多态
Vehicle* v = &tesla;
v->show(); // 调用ElectricCar::show()
return 0;
}
13.10 综合示例:员工管理系统
cpp
// employee_system.cpp -- 综合示例:员工管理系统
#include <iostream>
#include <string>
#include <vector>
#include <iomanip>
// ===== 基类:员工 =====
class Employee
{
protected:
std::string name;
int id;
double baseSalary;
static int nextId;
public:
Employee(const std::string& n, double salary)
: name(n), baseSalary(salary)
{
id = nextId++;
}
virtual ~Employee() {}
// 纯虚函数:每种员工计算薪资的方式不同
virtual double calcSalary() const = 0;
virtual std::string getType() const = 0;
// 普通虚函数
virtual void show() const
{
std::cout << std::setw(4) << id
<< " | " << std::setw(8) << getType()
<< " | " << std::setw(10) << name
<< " | 薪资:" << std::fixed
<< std::setprecision(2) << calcSalary();
}
std::string getName() const { return name; }
int getId() const { return id; }
};
int Employee::nextId = 1001;
// ===== 派生类1:全职员工 =====
class FullTimeEmployee : public Employee
{
private:
double bonus; // 奖金
public:
FullTimeEmployee(const std::string& n,
double salary, double b = 0)
: Employee(n, salary), bonus(b) {}
double calcSalary() const override
{
return baseSalary + bonus;
}
std::string getType() const override { return "全职"; }
void setBonus(double b) { bonus = b; }
};
// ===== 派生类2:兼职员工 =====
class PartTimeEmployee : public Employee
{
private:
double hourlyRate; // 时薪
int hoursWorked; // 工作小时数
public:
PartTimeEmployee(const std::string& n,
double rate, int hours)
: Employee(n, rate), hourlyRate(rate), hoursWorked(hours) {}
double calcSalary() const override
{
return hourlyRate * hoursWorked;
}
std::string getType() const override { return "兼职"; }
};
// ===== 派生类3:销售员工 =====
class SalesEmployee : public Employee
{
private:
double salesAmount; // 销售额
double commissionRate; // 提成比例
public:
SalesEmployee(const std::string& n,
double base, double rate)
: Employee(n, base), salesAmount(0), commissionRate(rate) {}
void setSales(double amount) { salesAmount = amount; }
double calcSalary() const override
{
return baseSalary + salesAmount * commissionRate;
}
std::string getType() const override { return "销售"; }
void show() const override
{
Employee::show();
std::cout << " 销售额:" << salesAmount;
}
};
// ===== 管理类 =====
class Department
{
private:
std::string name;
std::vector<Employee*> employees;
public:
Department(const std::string& n) : name(n) {}
~Department()
{
for (auto& e : employees)
delete e;
}
void addEmployee(Employee* e)
{
employees.push_back(e);
}
void showAll() const
{
using namespace std;
cout << "\n===== " << name << " 部门员工 =====" << endl;
cout << string(60, '-') << endl;
double total = 0;
for (const auto& e : employees)
{
e->show();
cout << endl;
total += e->calcSalary();
}
cout << string(60, '-') << endl;
cout << "部门总薪资:" << fixed << setprecision(2)
<< total << endl;
}
// 找最高薪资员工
Employee* getHighestPaid() const
{
if (employees.empty()) return nullptr;
Employee* best = employees[0];
for (const auto& e : employees)
if (e->calcSalary() > best->calcSalary())
best = e;
return best;
}
};
int main()
{
using namespace std;
Department dept("技术");
// 添加不同类型的员工
auto* ft1 = new FullTimeEmployee("张三", 15000, 3000);
auto* ft2 = new FullTimeEmployee("李四", 18000, 5000);
auto* pt1 = new PartTimeEmployee("王五", 80, 120);
auto* sl1 = new SalesEmployee("赵六", 8000, 0.05);
sl1->setSales(200000);
dept.addEmployee(ft1);
dept.addEmployee(ft2);
dept.addEmployee(pt1);
dept.addEmployee(sl1);
dept.showAll();
Employee* best = dept.getHighestPaid();
if (best)
cout << "\n最高薪资员工:" << best->getName()
<< "(" << best->getType() << ")"
<< " 薪资:" << best->calcSalary() << endl;
return 0;
}
输出:
===== 技术 部门员工 =====
------------------------------------------------------------
1001 | 全职 | 张三 | 薪资:18000.00
1002 | 全职 | 李四 | 薪资:23000.00
1003 | 兼职 | 王五 | 薪资:9600.00
1004 | 销售 | 赵六 | 薪资:18000.00 销售额:200000
------------------------------------------------------------
部门总薪资:68600.00
最高薪资员工:李四(全职) 薪资:23000
📝 第13章知识点总结
| 知识点 | 核心要点 |
|---|---|
| 继承语法 | class 派生类 : public 基类,派生类拥有基类所有成员 |
| 构造/析构顺序 | 构造:先基类后派生类;析构:先派生类后基类 |
| 访问控制 | public继承:public→public,protected→protected,private不可访问 |
| 虚函数 | virtual 关键字,通过基类指针/引用实现运行时多态 |
| 虚函数表 | 每个含虚函数的类有vtable,对象通过vptr找到正确函数 |
| 虚析构函数 | 含虚函数的类必须有虚析构函数,否则delete基类指针会内存泄漏 |
| 纯虚函数 | = 0,派生类必须实现,含纯虚函数的类是抽象类,不能实例化 |
| override | C++11,明确表示重写虚函数,编译器检查签名是否匹配 |
| final | C++11,禁止虚函数被进一步重写,或禁止类被继承 |
| dynamic_cast | 安全的向下转型,失败返回nullptr(指针)或抛出异常(引用) |
| 多态条件 | 基类指针/引用 + 虚函数 + 派生类重写 |
| 抽象类 | 含纯虚函数,不能实例化,定义接口规范 |