如何将类继承为一个?

摘要:前言 类的继承是面向对象编程(OOP)的核心特性之一,核心价值在于 代码重用 和 逻辑分层,通过抽取不同对象的共性抽象为父类,子类基于父类扩展特有属性和方法,减少重复代码并提升框架稳定性。 类继承的核心概念 定义 类的继承是对对象概念的 纵
前言 类的继承是面向对象编程(OOP)的核心特性之一,核心价值在于 代码重用 和 逻辑分层,通过抽取不同对象的共性抽象为父类,子类基于父类扩展特有属性和方法,减少重复代码并提升框架稳定性。 类继承的核心概念 定义 类的继承是对对象概念的 纵向抽象模拟:将不同对象的共性属性/方法抽离为 父类(基类,Base Class),再通过 派生 生成 子类(派生类,Derived Class),子类自动拥有父类的非私有成员,同时可扩展自身特有成员。 核心优势 代码重用:无需重复编写父类已定义的共性逻辑 逻辑清晰:构建对象间的层级关系,符合现实世界认知 易于维护:修改父类共性逻辑时,所有子类自动受益 经典示例(现实世界抽象) 示例逻辑:猫、狗均属于哺乳动物,共性(体温、体重)抽象到 Mammal 父类,子类仅关注特有行为(喵喵叫、汪汪叫)。 继承的基本语法 语法格式 // 父类定义 class 父类名 { // 成员属性 + 成员方法 }; // 子类继承语法:class 子类名 : 继承方式 父类名 class 子类名 : 继承方式 父类名 { public: // 子类构造函数(需显式调用父类构造函数) 子类名(参数列表) : 父类名(父类参数列表) { // 子类初始化逻辑 } // 子类特有属性 + 方法 }; 实战示例(哺乳动物→猫/狗) #include <iostream> using namespace std; // 父类:哺乳动物 class Mammal { float heat; // 体温(共性属性) float weight; // 体重(共性属性) public: // 父类构造函数(默认参数) Mammal(float h = 0.0f, float w = 0.0f) : heat(h), weight(w) {} // 父类共性方法 void showInfo() const { cout << "体温:" << heat << "℃" << endl; cout << "体重:" << weight << "kg" << endl; } // 【补充】提供访问私有成员的接口(供子类间接访问) float getHeat() const { return heat; } float getWeight() const { return weight; } }; // 子类:狗(公有继承 Mammal) class Dog : public Mammal { public: // 子类构造函数:显式调用父类构造函数 Dog(float h, float w) : Mammal(h, w) {} // 子类特有方法 void bark() const { cout << "汪汪!" << endl; } }; // 子类:猫(公有继承 Mammal) class Cat : public Mammal { public: // 子类构造函数:显式调用父类构造函数 Cat(float h, float w) : Mammal(h, w) {} // 子类特有方法 void miaow() const { cout << "喵喵!" << endl; } }; // 测试代码 int main() { Dog dog(38.5f, 20.0f); cout << "=== 狗的信息 ===" << endl; dog.showInfo(); // 继承自父类的方法 dog.bark(); // 子类特有方法 Cat cat(38.7f, 5.0f); cout << "\n=== 猫的信息 ===" << endl; cat.showInfo(); // 继承自父类的方法 cat.miaow(); // 子类特有方法 return 0; } 三种继承方式与访问控制 继承方式决定了父类成员在子类中的 访问权限,核心是控制子类及子类对象对父类成员的访问范围。 访问权限基础(父类成员本身的权限) 权限修饰符 父类内部 子类内部 类外部(对象) 比喻(便于理解) private ✅ ❌ ❌ 父亲的日记(仅自己看) protected ✅ ✅ ❌ 家族祖传手艺(父子可用,外人不可) public ✅ ✅ ✅ 父亲的代步车(所有人可用) . 公有继承(public inheritance) 核心逻辑 表达 is-a 关系(子类是父类的一种),最常用、最符合现实逻辑 父类成员权限在子类中 保持不变(除 private 成员不可直接访问) 语法规则 父类 private 成员:子类不可直接访问,需通过父类提供的 public 接口间接访问 父类 protected 成员:子类中仍为 protected(子类内可访问,对象不可访问) 父类 public 成员:子类中仍为 public(子类内、对象均可访问) 代码示例 #include <iostream> using namespace std; // 父类 class Base { private: int a; // 私有成员:仅父类内部访问 protected: int b; // 保护成员:父类+子类内部访问 public: int c; // 公有成员:任意位置访问 // 父类构造函数 Base(int a, int b, int c) : a(a), b(b), c(c) {} // 提供访问私有成员a的公有接口 int getA() const { return a; } }; // 子类:公有继承父类 class Derived : public Base { public: // 子类构造函数:显式调用父类构造函数 Derived(int a, int b, int c) : Base(a, b, c) { cout << "子类构造完成" << endl; } // 子类内部访问父类成员 void showMembers() { // cout << a; // 错误:无法直接访问父类private成员 cout << "父类private成员a(通过接口):" << getA() << endl; cout << "父类protected成员b:" << b << endl; // 正确:子类内可访问 cout << "父类public成员c:" << c << endl; // 正确:子类内可访问 } }; // 测试 int main() { Derived d(10, 20, 30); d.showMembers(); // 子类内访问父类成员 // 子类对象访问父类成员 // cout << d.a; // 错误:private成员不可访问 // cout << d.b; // 错误:protected成员不可通过对象访问 cout << "子类对象访问父类public成员c:" << d.c << endl; // 正确 return 0; } 典型应用场景 猫 is-a 哺乳动物、汽车 is-a 交通工具、单选框 is-a 按钮 所有能对父类执行的操作,子类均可执行(如哺乳动物能维持体温,猫也能) 保护继承(protected inheritance) 核心逻辑 语法允许,但 极少使用(不符合现实逻辑) 父类所有非 private 成员,在子类中均变为 protected(最高权限为 protected) 语法规则 父类 private 成员:子类不可直接访问 父类 protected 成员:子类中仍为 protected 父类 public 成员:子类中变为 protected(对象不可访问) 代码示例 #include <iostream> using namespace std; class Base { private: int a; protected: int b; public: int c; }; // 保护继承 class Derived : protected Base { public: void f() { // a = 100; // 错误:无法访问父类private成员 b = 100; // 正确:子类内可访问protected成员 c = 100; // 正确:父类public成员在子类中变为protected,子类内可访问 } }; int main() { Derived d; // d.a = 100; // 错误:private成员不可访问 // d.b = 100; // 错误:protected成员不可通过对象访问 // d.c = 100; // 错误:父类public成员已变为protected,对象不可访问 return 0; } 私有继承(private inheritance) 核心逻辑 表达 has-a 关系(子类包含父类的实现),极少使用(推荐用「类复合」替代) 父类所有非 private 成员,在子类中均变为 private(最高权限为 private) 语法规则 父类 private 成员:子类不可直接访问 父类 protected 成员:子类中变为 private(仅子类内可访问) 父类 public 成员:子类中变为 private(仅子类内可访问) 代码示例 #include <iostream> using namespace std; // 父类:轮子(被包含的组件) class Wheel { private: int radius; // 半径 protected: int width; // 宽度 public: int getRadius() const { return radius; } Wheel(int r, int w) : radius(r), width(w) {} }; // 子类:汽车(私有继承轮子,表示“汽车包含轮子”) class Car : private Wheel { public: Car(int r, int w) : Wheel(r, w) {} void showWheelInfo() { // cout << radius; // 错误:无法直接访问父类private成员 cout << "轮子宽度:" << width << endl; // 正确:子类内可访问(已变为private) cout << "轮子半径:" << getRadius() << endl; // 正确:通过父类接口访问 } }; int main() { Car car(18, 255); car.showWheelInfo(); // 子类提供接口访问父类实现 // cout << car.width; // 错误:子类中已变为private,对象不可访问 // cout << car.getRadius(); // 错误:父类public成员已变为private,对象不可访问 return 0; } 注意事项 私有继承的意义:继承父类的实现,但隐藏父类的接口 替代方案:用「类复合」(子类中定义父类对象作为成员)更清晰,避免逻辑混乱// 类复合(推荐替代私有继承) class Car { private: Wheel wheel; // 汽车包含轮子对象(has-a) public: Car(int r, int w) : wheel(r, w) {} void showWheelInfo() { cout << "轮子半径:" << wheel.getRadius() << endl; } }; 三种继承方式权限总结表 父类成员权限 公有继承(public) 保护继承(protected) 私有继承(private) private 不可直接访问 不可直接访问 不可直接访问 protected 保持 protected 保持 protected 变为 private public 保持 public 变为 protected 变为 private 类继承中的构造与析构 构造函数的调用规则 核心逻辑 构造顺序:先构造父类,再构造子类(先有地基,再盖房子) 若父类无默认构造函数(无参/全缺省),子类必须显式调用父类构造函数 代码示例(基础调用) #include <iostream> using namespace std; class Base { public: // 父类构造函数 Base() { cout << "✅ 父类 Base 构造" << endl; } }; class Derived : public Base { public: // 子类构造函数 Derived() { cout << "✅ 子类 Derived 构造" << endl; } }; int main() { Derived d; // 先调用父类构造,再调用子类构造 // 输出顺序: // ✅ 父类 Base 构造 // ✅ 子类 Derived 构造 return 0; } 代码示例(父类无默认构造函数) #include <iostream> using namespace std; class Base { private: int x; public: // 父类无默认构造函数(必须传参) Base(int x) : x(x) { cout << "✅ 父类 Base 构造(x=" << x << ")" << endl; } }; class Derived : public Base { private: int y; public: // 子类必须显式调用父类构造函数(否则编译错误) Derived(int x, int y) : Base(x), y(y) { cout << "✅ 子类 Derived 构造(y=" << y << ")" << endl; } }; int main() { Derived d(10, 20); // 输出顺序: // ✅ 父类 Base 构造(x=10) // ✅ 子类 Derived 构造(y=20) return 0; } 析构函数的调用规则 核心逻辑 析构顺序:先析构子类,再析构父类(先拆房子,再拆地基) 若子类有动态内存分配,需显式定义析构函数;若涉及多态,父类析构函数需定义为 virtual(避免内存泄漏) 代码示例(基础调用) #include <iostream> using namespace std; class Base { public: Base() { cout << "✅ 父类 Base 构造" << endl; } ~Base() { cout << "❌ 父类 Base 析构" << endl; } // 析构函数 }; class Derived : public Base { public: Derived() { cout << "✅ 子类 Derived 构造" << endl; } ~Derived() { cout << "❌ 子类 Derived 析构" << endl; } // 析构函数 }; int main() { Derived d; // 输出顺序: // ✅ 父类 Base 构造 // ✅ 子类 Derived 构造 // ❌ 子类 Derived 析构 // ❌ 父类 Base 析构 return 0; } 📌 拓展:虚析构函数 当父类指针指向子类对象时,若父类析构函数非虚函数,会导致子类析构函数不被调用,造成内存泄漏: #include <iostream> using namespace std; class Base { public: Base() { cout << "✅ 父类 Base 构造" << endl; } virtual ~Base() { cout << "❌ 父类 Base 析构" << endl; } // 虚析构函数 }; class Derived : public Base { private: int* arr; // 动态内存分配 public: Derived() { arr = new int[10]; cout << "✅ 子类 Derived 构造" << endl; } ~Derived() { delete[] arr; // 释放动态内存 cout << "❌ 子类 Derived 析构" << endl; } }; int main() { Base* ptr = new Derived(); // 父类指针指向子类对象 delete ptr; // 若父类析构非虚函数,仅调用父类析构,子类析构不执行(内存泄漏) // 输出顺序(虚析构情况下): // ✅ 父类 Base 构造 // ✅ 子类 Derived 构造 // ❌ 子类 Derived 析构 // ❌ 父类 Base 析构 return 0; } 拓展知识(进阶要点) 继承中的访问控制总结 访问场景 public继承 protected继承 private继承 子类内部访问父类成员 ✅(除private) ✅(除private) ✅(除private) 子类对象访问父类成员 ✅(仅public) ❌ ❌ 子类的子类访问父类成员 ✅(除private) ✅(除private) ❌ 表达的关系 is-a 无明确关系 has-a(不推荐) is-a 与 has-a 关系深入理解 关系类型 含义 实现方式 示例 is-a 子类是父类的一种 public继承 猫 is-a 哺乳动物 has-a 类包含另一个类的对象 类复合(成员对象) 汽车 has-a 轮子 注意:不要用private继承表达has-a关系,类复合更直观、易维护! 单继承与多继承 单继承(原文档示例) 定义:子类仅继承一个父类 优点:逻辑清晰、无歧义,推荐使用 示例:class Dog : public Mammal { ... } 多继承(拓展) 定义:子类继承多个父类(class 子类 : 继承方式 父类1, 继承方式 父类2 { ... }) 优点:可同时重用多个父类的代码 问题:菱形继承(钻石继承)导致的二义性 [图示位置:菱形继承结构图] 菱形继承问题示例 #include <iostream> using namespace std; // 顶层父类 class Animal { public: void eat() { cout << "动物进食" << endl; } }; // 中间父类1 class Dog : public Animal { ... }; // 中间父类2 class Wolf : public Animal { ... }; // 子类:同时继承Dog和Wolf(菱形继承) class WolfDog : public Dog, public Wolf { ... }; int main() { WolfDog wd; // wd.eat(); // 错误:二义性(Dog::eat 和 Wolf::eat 冲突) wd.Dog::eat(); // 需显式指定父类,解决二义性 return 0; } 解决菱形继承:虚继承(virtual inheritance) 通过 virtual 关键字让中间父类共享顶层父类的实例,避免重复继承: // 中间父类1:虚继承Animal class Dog : virtual public Animal { ... }; // 中间父类2:虚继承Animal class Wolf : virtual public Animal { ... }; int main() { WolfDog wd; wd.eat(); // 正确:无歧义(仅一个Animal实例) return 0; } 继承与虚函数 虚函数:父类中用 virtual 声明的成员函数,子类可重写(override) 作用:实现多态(父类指针指向子类对象时,调用子类的重写方法) 示例: #include <iostream> using namespace std; class Mammal { public: virtual void makeSound() { cout << "哺乳动物发出声音" << endl; } // 虚函数 }; class Dog : public Mammal { public: void makeSound() override { cout << "汪汪!" << endl; } // 重写虚函数 }; class Cat : public Mammal { public: void makeSound() override { cout << "喵喵!" << endl; } // 重写虚函数 }; int main() { Mammal* m1 = new Dog(); Mammal* m2 = new Cat(); m1->makeSound(); // 输出:汪汪!(调用子类重写方法) m2->makeSound(); // 输出:喵喵!(调用子类重写方法) delete m1; delete m2; return 0; } 核心要点总结 继承的核心是 代码重用 和 逻辑分层,优先使用 public 继承(is-a关系) 继承方式决定父类成员在子类的访问权限,private 成员永远不可被子类直接访问 构造顺序:先父后子;析构顺序:先子后父,多态场景下父类析构需为虚函数 避免用 protected/private 继承,has-a关系用「类复合」实现更清晰 多继承易引发菱形继承问题,可通过「虚继承」解决 虚函数是实现多态的基础,子类重写时可加 override 关键字明确意图