公有继承与私有继承的区别
📅 2026/7/12 23:49:58
👁️ 阅读次数
📝 编程学习
公有继承与私有继承的区别
一、核心概念对比
| 特性 | 公有继承(public) | 私有继承(private) |
|---|---|---|
| 关系描述 | is-a(是一个) | has-a(有一个)/ 用…来实现 |
| 接口继承 | 保留基类接口 | 隐藏基类接口 |
| 类型兼容 | 派生类可转换为基类 | 不能转换 |
| 代码复用方式 | 白盒复用(内部可见) | 黑盒复用(内部隐藏) |
| 访问权限 | 基类 public → 派生类 public | 基类所有成员 → 派生类 private |
二、公有继承(is-a 关系)
2.1 定义与特点
classEngine{public:voidStart(){cout<<"引擎启动"<<endl;}};classCar:publicEngine{// 公有继承:Car is-a Engine// Car 拥有 Engine 的所有接口};intmain(){Car car;car.Start();// 可以直接调用基类方法return0;}2.2 类型兼容性
classPerson{};classStudent:publicPerson{};// 公有继承intmain(){Student s;Person*p=&s;// 派生类指针可转换为基类指针Person&r=s;// 派生类引用可转换为基类引用return0;}三、私有继承(has-a 关系)
3.1 定义与特点
classEngine{private:intcylinderNum;public:Engine(intn=4):cylinderNum(n){}voidEnStart(){cout<<"引擎启动"<<endl;}};// 私有继承:Car has-a Engine(用 Engine 来实现 Car)classCar:privateEngine{public:Car(intn=4):Engine(n){}voidStart(){Engine::EnStart();// 内部可以调用基类方法}};intmain(){Car car;car.Start();// 通过 Car 的公有接口调用// car.EnStart(); // 错误!外部不能直接访问基类方法return0;}3.2 私有继承的内存布局
私有继承的内存布局 Car 对象 ----------------------------------------------------------- Engine 部分(基类子对象) cylinderNum (4B) EnStart() 方法 --------------------------- Car 部分(派生类新增) (可能有其他成员) --------------------------- 特点:基类部分存在,但外部不可见四、公有继承 与 私有继承 与 组合
4.1 三种方式对比
| 方式 | 关系 | 接口可见性 | 类型兼容 | 耦合度 |
|---|---|---|---|---|
| 公有继承 | is-a | 完全可见 | 支持 | 较高 |
| 私有继承 | has-a | 完全隐藏 | 不支持 | 较低 |
| 组合 | has-a | 完全隐藏 | 不支持 | 最低 |
4.2 组合示例(推荐替代私有继承)
// 使用组合替代私有继承classEngine{public:voidEnStart(){cout<<"引擎启动"<<endl;}};classCar{private:Engine engine;// 组合:Car has-a Enginepublic:voidStart(){engine.EnStart();// 委托给成员对象}};intmain(){Car car;car.Start();// 通过 Car 接口调用// car.EnStart(); // 不能直接访问return0;}4.3 何时使用私有继承?
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 需要访问基类的 protected 成员 | 私有继承 | 组合无法访问 protected 成员 |
| 需要重写基类的虚函数 | 私有继承 | 可以重写虚函数实现定制 |
| 不需要上述特性 | 组合 | 更简单,耦合度更低 |
// 需要重写虚函数时使用私有继承classBase{public:virtualvoidfunc(){cout<<"Base"<<endl;}};classDerived:privateBase{public:voidfunc()override{cout<<"Derived"<<endl;}// 重写虚函数voidcall(){func();}};五、多重继承与组合的选择
5.1 不推荐的多重继承
classDoor{public:voidOpen(){cout<<"开门"<<endl;}};classEngine{public:voidStart(){cout<<"启动引擎"<<endl;}};// 不推荐:汽车有多个属性,不应该用多重继承classCar:privateEngine,privateDoor{public:voidStart(){Engine::Start();}voidOpen(){Door::Open();}};5.2 推荐的组合方式
// 推荐:使用组合,将多个属性作为成员classCar{private:Engine engine;// 引擎Door door;// 门Wheel wheels[4];// 车轮public:voidStart(){engine.Start();}voidOpenDoor(){door.Open();}};六、继承与静态成员
6.1 场景一:基类和派生类共享同一个静态成员
classPerson{public:staticintnum;// 静态成员,只有一份};intPerson::num=0;classStudent:publicPerson{public:Student(){cout<<"Create Student: "<<++num<<endl;}~Student(){cout<<"Destroy Student: "<<num--<<endl;}};classEmployee:publicPerson{public:Employee(){cout<<"Create Employee: "<<++num<<endl;}~Employee(){cout<<"Destroy Employee: "<<num--<<endl;}};intmain(){Student s1,s2;Employee e1,e2;// 所有对象共享同一个 numcout<<&s1.num<<endl;// 地址相同cout<<&e1.num<<endl;// 地址相同return0;}内存布局:
共享静态成员的内存布局 ------------------------------------------------------ 数据区(.data) Person::num = 4 (所有 Person、Student、Employee 对象共享) ------------------------------------------------------ Student 对象1 Student 对象2 Employee 对象1 Person 部分 Person 部分 Person 部分 (无 num) (无 num) (无 num)6.2 场景二:基类和派生类各自有同名静态成员
classPerson{public:staticintnum;// Person 的静态成员};intPerson::num=0;classStudent:publicPerson{public:staticintnum;// Student 自己的静态成员(隐藏了 Person::num)};intStudent::num=0;classEmployee:publicPerson{public:staticintnum;// Employee 自己的静态成员};intEmployee::num=0;intmain(){Student s1,s2;Employee e1,e2;// 三个静态成员地址不同cout<<&s1.num<<endl;// Student::num 的地址cout<<&e1.num<<endl;// Employee::num 的地址cout<<&Person::num<<endl;// Person::num 的地址// 访问方式Student::num=10;// 访问 Student 的静态成员Person::num=100;// 访问 Person 的静态成员return0;}内存布局:
各自独立静态成员的内存布局 数据区(.data) -------------------------------------- Person::num = 100 -------------------------------------- Student::num = 10 -------------------------------------- Employee::num = 0 -------------------------------------- 注意:三个静态成员相互独立,地址不同七、完整示例代码
#include<iostream>usingnamespacestd;// ========== 公有继承示例 ==========classAnimal{public:voideat(){cout<<"动物进食"<<endl;}};classDog:publicAnimal{// 公有继承public:voidbark(){cout<<"汪汪叫"<<endl;}};// ========== 私有继承示例 ==========classEngine{private:intpower;public:Engine(intp=100):power(p){}voidstart(){cout<<"引擎启动,功率="<<power<<endl;}};classCar:privateEngine{// 私有继承public:Car(intp=100):Engine(p){}voiddrive(){start();// 内部可以调用基类方法cout<<"汽车行驶"<<endl;}};// ========== 组合示例(推荐) ==========classComputer{private:CPU cpu;// 组合Memory mem;// 组合public:voidrun(){cpu.process();mem.load();}};// ========== 静态成员测试 ==========classBase{public:staticintcount;};intBase::count=0;classDerived1:publicBase{public:Derived1(){count++;}};classDerived2:publicBase{public:Derived2(){count++;}};classDerived3:publicBase{public:staticintcount;// 隐藏基类静态成员Derived3(){count++;}};intDerived3::count=0;intmain(){cout<<"=== 公有继承 ==="<<endl;Dog dog;dog.eat();// 继承自 Animaldog.bark();// Dog 自己的方法cout<<"\n=== 私有继承 ==="<<endl;Carcar(150);car.drive();// car.start(); // 外部无法访问cout<<"\n=== 共享静态成员 ==="<<endl;Derived1 d1,d2;Derived2 d3;cout<<"Base::count = "<<Base::count<<endl;// 3cout<<"\n=== 独立静态成员 ==="<<endl;Derived3 d4,d5;cout<<"Derived3::count = "<<Derived3::count<<endl;// 2cout<<"Base::count = "<<Base::count<<endl;// 3(不变)return0;}八、选择
| 需求场景 | 推荐方式 | 原因 |
|---|---|---|
| 表达"is-a"关系 | 公有继承 | 符合面向对象设计 |
| 需要重写虚函数 | 公有/私有继承 | 私有继承也可重写 |
| 只需要使用功能 | 组合 | 更低耦合 |
| 需要访问 protected 成员 | 继承(公有或私有) | 组合无法访问 |
| 表达"has-a"关系 | 组合优先 | 更灵活,更安全 |
| 需要多态 | 公有继承 | 支持基类指针/引用 |
九、建议
- 优先使用组合而非私有继承,除非需要访问 protected 成员或重写虚函数
- 公有继承用于表达真实的"is-a"关系
- 避免多重继承,优先使用组合
- 静态成员在继承中的行为要清楚:同名会隐藏,不同名则共享
- 私有继承是"实现继承",不应暴露给用户
编程学习
技术分享
实战经验