继承方式:public、private 与 protected 继承
在C++面向对象编程中,继承是实现代码复用、构建层次化类结构的核心机制,而继承方式则是继承体系中“权限控制”的关键——它决定了父类的public、private、protected成员,在子类中会拥有怎样的访问权限,以及子类对象、类外代码能否访问这些继承而来的成员。
上一篇博客我们讲解了继承的核心概念:父类与子类的“is-a”关系、构造/析构函数的执行顺序,以及单继承、多继承的基本用法。其中我们留下了一个关键疑问:为什么父类的成员有时子类能直接访问,有时却不能?为什么子类对象能调用父类的某些成员函数,有时又会编译报错?答案就藏在三种继承方式中。
C++提供了三种核心继承方式:public(公有继承)、private(私有继承)、protected(保护继承)。它们的核心区别不在于“是否继承父类成员”——实际上,无论哪种继承方式,子类都会继承父类的所有成员(包括public、private、protected成员),区别在于继承后,父类成员在子类中的访问权限会被重新限定。
很多初学者在学习继承方式时,最容易陷入两个核心误区:一是混淆“继承成员”和“访问权限”(误以为private继承不继承父类成员),二是不清楚三种继承方式的适用场景,滥用继承方式导致代码冗余、权限混乱。本文将从“访问权限基础”切入,逐一拆解三种继承方式的规则、实战用法,对比三者的核心差异,结合案例规避高频误区,同时衔接前序知识点(父类子类关系、构造析构顺序),帮你彻底搞懂三种继承方式的本质,学会在实际开发中灵活选择合适的继承方式,完善C++类与对象的知识体系,为后续学习多态、虚函数打下坚实基础。
核心前提回顾:1. 类的访问权限优先级:private < protected < public(private仅类内可访问,protected类内、子类可访问,public类内、类外、子类均可访问);2. 子类会继承父类的所有成员,但父类的private成员无论哪种继承方式,子类都无法直接访问(仅能通过父类public/protected接口间接访问);3. 继承的核心是“is-a”关系,继承方式仅控制权限,不改变继承本质。
一、先明确:继承方式的核心作用(为什么需要三种继承方式?)
在讲解具体的继承方式之前,我们先通过一个简单场景,搞清楚继承方式的核心价值——如果只有一种继承方式,会遇到什么问题?三种继承方式能帮我们解决什么?
场景:定义父类Person(封装name、age、showInfo成员),子类Student继承Person。需求分为三种:
希望子类对象能直接调用父类的showInfo方法(类外可访问),子类内部也能直接访问name、age;
不希望子类对象调用父类的showInfo方法(类外不可访问),但允许子类内部访问name、age;
仅允许子类内部访问父类的所有可访问成员,不允许子类的子类(孙子类)、类外访问。
如果只有一种继承方式,显然无法满足这三种差异化的权限需求——三种继承方式的核心作用,就是通过限定父类成员在子类中的访问权限,实现“精准的权限控制”,既保证代码复用,又避免权限泄露,兼顾代码的安全性和可维护性。
举个通俗的例子:父类的“家产”(成员)分为三类(public、protected、private),子类继承家产时,三种继承方式相当于“不同的继承协议”:
-
public继承:父类的“公开家产”(public)依然公开,“家族内部家产”(protected)依然保留家族内部权限,“私有家产”(private)不可直接动用;
-
protected继承:父类的“公开家产”(public)变为“家族内部家产”,“家族内部家产”(protected)保持不变,“私有家产”(private)不可直接动用;
-
private继承:父类的“公开家产”(public)、“家族内部家产”(protected)均变为子类的“私有家产”,“私有家产”(private)不可直接动用。
接下来,我们逐一拆解每种继承方式的具体规则、实战用法和适用场景。
二、核心详解一:public 继承(最常用、推荐优先使用)
public继承是C++开发中最常用的继承方式,核心特点是“保持父类成员的访问权限不变”——父类的public成员继承后依然是子类的public成员,父类的protected成员继承后依然是子类的protected成员,父类的private成员继承后不可访问(继承但隐藏)。
核心原则:父类的访问权限在子类中完全保留,子类对象可访问父类继承而来的public成员,子类内部可访问父类继承而来的public、protected成员,类外可通过子类对象访问父类的public成员。
1. public 继承的访问控制规则(必记)
| public(公有成员) | 子类的public成员 | 能 | 能 | 能 |
| protected(保护成员) | 子类的protected成员 | 能 | 不能 | 不能 |
| private(私有成员) | 不可访问(继承但隐藏) | 不能(需通过父类接口间接访问) | 不能 | 不能 |
2. public 继承实战演示(结合场景)
场景:实现Person父类(public成员showInfo,protected成员name、age,private成员gender),Student子类public继承Person,实现子类独有成员studentId、showStudentInfo,验证访问权限规则。
#include <iostream>
#include <string>
using namespace std;
// 父类:Person
class Person {
public:
// 父类public成员函数:显示基础信息
void showInfo() {
cout << "姓名:" << name << ",年龄:" << age << endl;
}
protected:
// 父类protected成员:姓名、年龄(类内、子类可访问)
string name;
int age;
private:
// 父类private成员:性别(仅父类内可访问)
string gender;
};
// 子类:Student,public继承Person(核心:保持父类成员权限不变)
class Student : public Person {
public:
// 子类构造函数:调用父类无参构造,初始化自身成员
Student(string name, int age, string studentId) {
// 正确:子类内部可访问父类protected成员name、age
this->name = name;
this->age = age;
// this->gender = "男"; // 错误:父类private成员,子类不可直接访问
this->studentId = studentId;
}
// 子类独有public成员函数:显示学生完整信息
void showStudentInfo() {
// 正确:子类内部可访问父类public成员showInfo
showInfo();
// 正确:子类内部可访问父类protected成员name
cout << "学号:" << studentId << ",学生姓名:" << name << endl;
}
private:
// 子类独有private成员:学号
string studentId;
};
int main() {
// 创建子类对象
Student s("张三", 18, "2024001");
// 1. 类外通过子类对象访问父类继承而来的public成员showInfo(正确)
s.showInfo();
// 2. 类外通过子类对象访问子类独有public成员showStudentInfo(正确)
s.showStudentInfo();
// 3. 类外通过子类对象访问父类protected成员name(错误:protected成员类外不可访问)
// cout << s.name << endl;
// 4. 类外通过子类对象访问父类private成员gender(错误:不可访问)
// cout << s.gender << endl;
return 0;
}
运行结果(验证权限规则):
姓名:张三,年龄:18
姓名:张三,年龄:18
学号:2024001,学生姓名:张三
3. public 继承的适用场景(必记)
public继承是最符合“is-a”关系的继承方式,适用场景:
希望子类复用父类的公开接口(public成员函数),且允许类外通过子类对象调用这些接口(比如Student复用Person的showInfo方法);
希望子类内部能直接访问父类的保护成员(protected),无需通过父类接口间接访问,简化代码;
希望子类的子类(孙子类)能继承父类的protected成员(public继承后,父类protected成员在子类中依然是protected,孙子类可访问);
大多数“父类-子类”的常规场景(如Person→Student、Person→Teacher、Animal→Dog),均推荐使用public继承。
三、核心详解二:private 继承(最少用、谨慎使用)
private继承是三种继承方式中使用频率最低的一种,核心特点是“压缩父类成员的访问权限”——父类的public、protected成员继承后,均变为子类的private成员,父类的private成员继承后不可访问(继承但隐藏)。
核心原则:父类的所有可访问成员(public、protected),均变为子类的私有成员,仅子类内部可访问这些继承而来的成员;子类对象、类外代码,以及子类的子类(孙子类),均无法访问父类继承而来的任何成员。
1. private 继承的访问控制规则(必记)
| public(公有成员) | 子类的private成员 | 能 | 不能 | 不能 |
| protected(保护成员) | 子类的private成员 | 能 | 不能 | 不能 |
| private(私有成员) | 不可访问(继承但隐藏) | 不能(需通过父类接口间接访问) | 不能 | 不能 |
2. private 继承实战演示(结合场景)
场景:沿用Person父类,Student子类改为private继承Person,验证“父类可访问成员变为子类private成员”的规则,以及孙子类无法访问父类成员的特点。
#include <iostream>
#include <string>
using namespace std;
// 父类:Person(与上一节一致)
class Person {
public:
void showInfo() {
cout << "姓名:" << name << ",年龄:" << age << endl;
}
protected:
string name;
int age;
private:
string gender;
};
// 子类:Student,private继承Person(核心:父类public/protected→子类private)
class Student : private Person {
public:
Student(string name, int age, string studentId) {
// 正确:子类内部可访问父类public/protected成员(已变为子类private)
this->name = name;
this->age = age;
this->studentId = studentId;
}
// 子类独有public成员函数:显示学生信息(间接暴露父类成员)
void showStudentInfo() {
// 正确:子类内部可访问父类继承而来的showInfo(已变为子类private)
showInfo();
cout << "学号:" << studentId << endl;
}
private:
string studentId;
};
// 孙子类:GraduateStudent,public继承Student
class GraduateStudent : public Student {
public:
GraduateStudent(string name, int age, string studentId, string major)
: Student(name, age, studentId) {
this->major = major;
}
void showGraduateInfo() {
// 错误:父类Student的showInfo是private成员(继承自Person),孙子类不可访问
// showInfo();
// 错误:父类Student的name是private成员(继承自Person),孙子类不可访问
// cout << name << endl;
cout << "专业:" << major << endl;
}
private:
string major;
};
int main() {
Student s("张三", 18, "2024001");
// 1. 类外通过子类对象访问父类继承而来的showInfo(错误:已变为子类private成员)
// s.showInfo();
// 2. 类外通过子类对象访问子类独有public成员showStudentInfo(正确)
s.showStudentInfo();
// 3. 类外通过子类对象访问父类protected成员name(错误:不可访问)
// cout << s.name << endl;
GraduateStudent gs("李四", 22, "2024002", "计算机科学");
gs.showGraduateInfo();
return 0;
}
运行结果(验证权限规则):
姓名:张三,年龄:18
学号:2024001
专业:计算机科学
3. private 继承的适用场景(必记,谨慎使用)
private继承的核心是“仅子类内部复用父类成员,不对外暴露任何父类接口”,适用场景极少,仅在以下情况考虑使用:
仅需要子类内部复用父类的成员(变量/函数),但不希望子类对象、类外代码访问这些父类成员;
不希望子类的子类(孙子类)继承父类的任何成员(彻底隔离父类与孙子类的关系);
注意:大多数情况下,private继承的需求可以通过“组合”(子类包含父类对象作为成员)替代,组合比private继承更清晰、更灵活,推荐优先使用组合而非private继承。
示例(组合替代private继承):
// 组合替代private继承:Student包含Person对象,而非继承Person
class Student {
private:
Person p; // 组合:Student有一个Person对象,复用Person的成员
string studentId;
public:
void showStudentInfo() {
p.showInfo(); // 间接复用Person的showInfo方法
cout << "学号:" << studentId << endl;
}
};
四、核心详解三:protected 继承(中等频率,场景有限)
protected继承的权限介于public和private之间,核心特点是“适度压缩父类成员的访问权限”——父类的public成员继承后变为子类的protected成员,父类的protected成员继承后依然是子类的protected成员,父类的private成员继承后不可访问(继承但隐藏)。
核心原则:父类的public成员降级为子类的protected成员,protected成员权限不变;子类内部可访问所有继承而来的可访问成员,子类对象、类外代码无法访问;子类的子类(孙子类)可访问这些继承而来的protected成员。
1. protected 继承的访问控制规则(必记)
| public(公有成员) | 子类的protected成员 | 能 | 不能 | 能 |
| protected(保护成员) | 子类的protected成员 | 能 | 不能 | 能 |
| private(私有成员) | 不可访问(继承但隐藏) | 不能(需通过父类接口间接访问) | 不能 | 不能 |
2. protected 继承实战演示(结合场景)
场景:沿用Person父类,Student子类改为protected继承Person,验证“父类public成员降级为子类protected成员”的规则,以及孙子类可访问这些成员的特点。
#include <iostream>
#include <string>
using namespace std;
// 父类:Person(与前文一致)
class Person {
public:
void showInfo() {
cout << "姓名:" << name << ",年龄:" << age << endl;
}
protected:
string name;
int age;
private:
string gender;
};
// 子类:Student,protected继承Person(核心:父类public→子类protected)
class Student : protected Person {
public:
Student(string name, int age, string studentId) {
// 正确:子类内部可访问父类public/protected成员(已变为子类protected)
this->name = name;
this->age = age;
this->studentId = studentId;
}
void showStudentInfo() {
// 正确:子类内部可访问父类继承而来的showInfo(已变为子类protected)
showInfo();
cout << "学号:" << studentId << endl;
}
private:
string studentId;
};
// 孙子类:GraduateStudent,public继承Student
class GraduateStudent : public Student {
public:
GraduateStudent(string name, int age, string studentId, string major)
: Student(name, age, studentId) {
this->major = major;
}
void showGraduateInfo() {
// 正确:父类Student的showInfo是protected成员,孙子类可访问
showInfo();
// 正确:父类Student的name是protected成员,孙子类可访问
cout << "姓名:" << name << ",专业:" << major << endl;
}
private:
string major;
};
int main() {
Student s("张三", 18, "2024001");
// 1. 类外通过子类对象访问父类继承而来的showInfo(错误:已变为子类protected成员)
// s.showInfo();
// 2. 类外通过子类对象访问子类独有public成员showStudentInfo(正确)
s.showStudentInfo();
GraduateStudent gs("李四", 22, "2024002", "计算机科学");
gs.showGraduateInfo();
return 0;
}
运行结果(验证权限规则):
姓名:张三,年龄:18
学号:2024001
姓名:李四,年龄:22
姓名:李四,专业:计算机科学
3. protected 继承的适用场景(必记)
protected继承的核心是“不对外暴露父类接口,但允许子类的子类继承父类成员”,适用场景有限,仅在以下情况考虑使用:
希望子类内部、子类的子类(孙子类)能访问父类的成员,但不希望类外代码、子类对象访问父类的public接口;
希望父类的public接口“仅对家族内部(子类、孙子类)开放”,不对外部开放(比如一些工具类的继承,仅允许子类扩展,不允许外部直接调用父类接口);
注意:protected继承的场景容易与public继承+private成员混淆,需谨慎区分,避免权限设计混乱。
五、三种继承方式核心对比(必记、笔试重点)
为了方便大家记忆和区分三种继承方式,我们用一个综合表格,对比三者的核心差异(重点关注“父类成员在子类中的权限”和“关键访问能力”),同时补充核心总结,帮你快速掌握重点。
1. 综合对比表格
| public 继承 | public | protected | 不可访问 | 能(仅public成员) | 能(仅public、protected成员) | 极高(推荐优先使用) |
| protected 继承 | protected | protected | 不可访问 | 不能 | 能(仅public、protected成员) | 中等(场景有限) |
| private 继承 | private | private | 不可访问 | 不能 | 不能 | 极低(谨慎使用,优先用组合替代) |
2. 核心总结(必记)
继承的本质不变:无论哪种继承方式,子类都会继承父类的所有成员(包括private成员),区别仅在于“继承后成员的访问权限”;
父类private成员的特殊性:无论哪种继承方式,父类的private成员子类都无法直接访问,仅能通过父类提供的public/protected接口间接访问;
权限变化规律:public继承“权限不变”,protected继承“public→protected,protected不变”,private继承“public、protected→private”;
访问能力核心:子类内部均可访问父类的public、protected成员(无论哪种继承方式);子类对象、类外仅能访问public继承中父类的public成员;
开发优先选择:public继承 > 组合 > protected继承 > private继承(大多数场景用public继承,private继承尽量用组合替代)。
六、三种继承方式的高频误区(必避,笔试重点)
结合初学者的常见错误,聚焦三种继承方式的“权限混淆、继承本质误解”,总结5个高频坑,每个坑对应错误示例和正确写法,帮你少走弯路,规避编译错误和逻辑问题。
误区1:private继承不继承父类成员(认知错误)
// 错误认知:Student private继承Person,就不继承Person的showInfo成员
class Person {
public:
void showInfo() { cout << "Person" << endl; }
};
class Student : private Person {
public:
void test() {
// 错误:误以为private继承不继承父类成员,不敢调用showInfo
// showInfo();
}
};
// 正确认知:private继承会继承父类成员,只是父类public/protected成员变为子类private
class Student : private Person {
public:
void test() {
showInfo(); // 正确:子类内部可访问,已变为子类private成员
}
};
关键提醒:继承方式仅改变“访问权限”,不改变“继承本质”——所有继承方式都会继承父类的所有成员。
误区2:子类对象能访问父类的protected成员(编译错误)
class Person {
protected:
string name = "张三";
};
class Student : public Person {};
int main() {
Student s;
// 错误:无论哪种继承方式,protected成员类外(子类对象)都不可访问
cout << s.name << endl;
return 0;
}
关键提醒:protected成员的核心权限是“类内、子类可访问”,无论哪种继承方式,类外(包括子类对象)都无法直接访问。
误区3:protected继承中,父类public成员子类对象能访问(编译错误)
class Person {
public:
void showInfo() { cout << "Person" << endl; }
};
class Student : protected Person {};
int main() {
Student s;
// 错误:protected继承中,父类public成员变为子类protected成员,子类对象不可访问
s.showInfo();
return 0;
}
误区4:滥用private继承,不用组合(设计错误)
// 错误示例:用private继承复用Person成员,不如用组合清晰
class Student : private Person {
public:
void show() { showInfo(); }
};
// 正确示例:用组合替代private继承,代码更清晰、更灵活
class Student {
private:
Person p; // 组合:Student有一个Person对象
public:
void show() { p.showInfo(); } // 复用Person的showInfo方法
};
关键提醒:private继承的核心需求(子类内部复用父类成员),几乎都可以通过组合实现,组合比private继承更符合“has-a”关系,代码可读性更高。
误区5:继承方式不影响构造/析构顺序(认知错误)
错误认知:不同的继承方式,父类与子类的构造/析构顺序不同;
正确认知:继承方式不影响构造/析构顺序——无论哪种继承方式,都是“先构造父类,再构造子类;先析构子类,再析构父类”(与上一篇博客知识点一致)。
class Person {
public:
Person() { cout << "Person构造" << endl; }
~Person() { cout << "Person析构" << endl; }
};
// 无论哪种继承方式,构造/析构顺序都一样
class Student : private Person {
public:
Student() { cout << "Student构造" << endl; }
~Student() { cout << "Student析构" << endl; }
};
int main() {
Student s;
return 0;
}
运行结果:
Person构造
Student构造
Student析构
Person析构
七、综合实战:三种继承方式对比演示(完整可运行)
结合前文知识点,设计一个完整案例,同时演示三种继承方式的权限差异,对比它们的访问效果,帮助大家巩固所学内容,代码可直接复制运行,直观感受三种继承方式的区别。
#include <iostream>
#include <string>
using namespace std;
// 父类:Base(基础类,封装三种权限的成员)
class Base {
public:
string public_mem = "父类public成员";
void public_func() {
cout << public_mem << endl;
}
protected:
string protected_mem = "父类protected成员";
void protected_func() {
cout << protected_mem << endl;
}
private:
string private_mem = "父类private成员";
void private_func() {
cout << private_mem << endl;
}
};
// 1. public继承
class PublicDerived : public Base {
public:
void derived_func() {
// 正确:子类内部可访问父类public/protected成员
public_func();
protected_func();
// private_func(); // 错误:父类private成员不可访问
cout << "public继承:子类内部访问父类public成员:" << public_mem << endl;
cout << "public继承:子类内部访问父类protected成员:" << protected_mem << endl;
}
};
// 2. protected继承
class ProtectedDerived : protected Base {
public:
void derived_func() {
// 正确:子类内部可访问父类public/protected成员(已变为protected)
public_func();
protected_func();
// private_func(); // 错误:父类private成员不可访问
cout << "protected继承:子类内部访问父类public成员:" << public_mem << endl;
cout << "protected继承:子类内部访问父类protected成员:" << protected_mem << endl;
}
};
// 3. private继承
class PrivateDerived : private Base {
public:
void derived_func() {
// 正确:子类内部可访问父类public/protected成员(已变为private)
public_func();
protected_func();
// private_func(); // 错误:父类private成员不可访问
cout << "private继承:子类内部访问父类public成员:" << public_mem << endl;
cout << "private继承:子类内部访问父类protected成员:" << protected_mem << endl;
}
};
int main() {
// 测试public继承
cout << "==================== public继承测试 ====================" << endl;
PublicDerived pd;
pd.derived_func(); // 正确:子类public成员
pd.public_func(); // 正确:子类public成员(继承自父类)
// pd.protected_func(); // 错误:子类protected成员,类外不可访问
// 测试protected继承
cout << "\\n==================== protected继承测试 ====================" << endl;
ProtectedDerived pd2;
pd2.derived_func(); // 正确:子类public成员
// pd2.public_func(); // 错误:子类protected成员,类外不可访问
// 测试private继承
cout << "\\n==================== private继承测试 ====================" << endl;
PrivateDerived pd3;
pd3.derived_func(); // 正确:子类public成员
// pd3.public_func(); // 错误:子类private成员,类外不可访问
return 0;
}
运行结果(清晰对比三种继承方式的权限差异):
==================== public继承测试 ====================
父类public成员
父类protected成员
public继承:子类内部访问父类public成员:父类public成员
public继承:子类内部访问父类protected成员:父类protected成员
父类public成员
==================== protected继承测试 ====================
父类public成员
父类protected成员
protected继承:子类内部访问父类public成员:父类public成员
protected继承:子类内部访问父类protected成员:父类protected成员
==================== private继承测试 ====================
父类public成员
父类protected成员
private继承:子类内部访问父类public成员:父类public成员
private继承:子类内部访问父类protected成员:父类protected成员
八、总结:三种继承方式的选择原则(实战必备)
学习三种继承方式的核心,不仅是掌握它们的访问规则,更重要的是学会在实际开发中“按需选择”——根据权限需求、代码复用场景,选择最合适的继承方式,既保证代码复用,又保证权限安全、结构清晰。


