一、继承是什么?为什么要用继承?
继承是面向对象代码复用的核心手段。
- 允许在保留原有类的基础上扩展功能,产生新类。
- 原有类叫 基类/父类 ,新类叫 派生类/子类。
- 本质:类设计层面的复用,避免重复写相同成员。
没继承之前(代码冗余)
Student 和 Teacher 都有姓名、地址、电话、age、identity(),重复定义很麻烦。
用继承之后(代码复用)
把公共成员抽到 Person 基类,Student/Teacher 继承它,直接复用。
cpp
class Person {
protected:
string _name = "张三";
string _address;
string _tel;
int _age = 18;
public:
void identity() { cout << "认证:" << _name << endl; }
};
// 子类 : 继承方式 父类
class Student : public Person {
protected:
int _stuid;
public:
void study() {}
};
class Teacher : public Person {
protected:
string _title;
public:
void teaching() {}
};
二、继承的定义格式
cpp
class 派生类 : 继承方式 基类
{
// 新增成员
};
示例:
cpp
class Student : public Person
{
public:
int _stuid;
};
三、三种继承方式(最重要表格)
基类成员在派生类中的访问权限 = 取更小的那个
public > protected > private
| 基类成员 | public 继承 | protected 继承 | private 继承 |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | 不可见 | 不可见 | 不可见 |
关键结论
- 基类 private 成员无论怎么继承,在子类都不可见。
- 想让子类能访问、但类外不能访问 → 用 protected。
- 实际开发 99% 用 public 继承。
- class 默认 private 继承;struct 默认 public 继承。
四、基类 ↔ 派生类对象转换(切片/切割)
只有 public 继承才支持。
1. 合法(可以)
- 派生类对象 → 赋值给 基类对象
- 派生类对象 → 赋值给 基类指针
- 派生类对象 → 赋值给 基类引用
原理:把派生类中"属于基类的那部分"切过来用。
cpp
Student s;
Person p = s; // 可以
Person* pp = &s; // 可以
Person& rp = s; // 可以
2. 不合法(不行)
- 基类对象不能赋值给派生类对象
cpp
Person p;
Student s = p; // 报错
五、继承中的作用域:隐藏(超级高频考点)
隐藏规则
- 基类和子类有独立作用域。
- 子类和基类 同名成员 → 子类屏蔽基类 → 叫 隐藏。
- 成员函数只要函数名相同就构成隐藏,不管参数!
- 想访问基类同名成员:
基类::成员
示例:同名变量隐藏
cpp
class Person {
protected:
int _num = 111; // 身份证
};
class Student : public Person {
protected:
int _num = 999; // 学号 → 隐藏父类
public:
void Print() {
cout << _num << endl; // 子类 999
cout << Person::_num << endl; // 父类 111
}
};
示例:函数名相同即隐藏(不是重载)
cpp
class A {
public:
void func() { cout << "func()\n"; }
};
class B : public A {
public:
void func(int i) { cout << "func(int)\n"; } // 隐藏 A::func()
};
// 调用
B b;
b.func(10); // 走子类
// b.func(); // 报错!被隐藏了
b.A::func(); // 必须加类域才能调
六、派生类的 4 大默认成员函数(必考)
派生类的构造、拷贝构造、赋值、析构,必须配合基类。
1. 构造函数
- 子类构造 必须先调用基类构造。
- 基类无默认构造 → 子类必须在初始化列表显式调用。
cpp
Student(const char* name, int num)
: Person(name) // 先初始化父类
, _num(num)
{}
2. 拷贝构造
- 子类拷贝构造 → 必须调用 基类拷贝构造。
cpp
Student(const Student& s)
: Person(s)
, _num(s._num)
{}
3. 赋值重载 operator=
- 子类赋值会隐藏 父类赋值,必须显式调用。
cpp
Student& operator=(const Student& s) {
if (this != &s) {
Person::operator=(s); // 显式调用父类赋值
_num = s._num;
}
return *this;
}
4. 析构函数
- 子类析构 自动调用父类析构。
- 调用顺序:先析构子类 → 再析构父类。
- 构造顺序:先构造父类 → 再构造子类。
4.2 如何实现一个 不能被继承的类
两种方法:
方法 1:C++98(构造函数私有)
- 子类必须调用父类构造,私有后子类无法调用,无法实例化。
cpp
class NonInherit {
private:
NonInherit() {} // 私有构造
};
方法 2:C++11 final 关键字(最简单)
cpp
class Base final {
// 此类不能被继承
};