当前位置: 首页 > news >正文

C++ 继承详解:从入门到深入

继承是面向对象中实现类层次代码复用的机制,在保留父类特性的基础上扩展出新功能,由此产生的新类称为派生类。

一、为什么需要继承?

1.1 问题引入

假设我们要设计学生和老师两个类,你会发现它们有很多共同的属性:姓名、年龄、地址、电话等。如果没有继承,代码会大量重复

#include <iostream> #include <string> using namespace std; // 没有继承的写法 - 代码冗余严重 class Student { public: void identity() { cout << "身份认证:" << _name << endl; } void study() { cout << _name << "正在学习" << endl; } protected: string _name = "张三"; int _age = 18; string _address; string _tel; int _stuId; // 学号(学生特有) }; class Teacher { public: void identity() { cout << "身份认证:" << _name << endl; } void teach() { cout << _name << "正在授课" << endl; } protected: string _name = "李老师"; int _age = 35; string _address; string _tel; string _title; // 职称(老师特有) }; int main() { Student s; Teacher t; s.identity(); t.identity(); return 0; }

1.2 继承解决方案

使用继承,将公共部分提取到基类(Person)中

#include <iostream> #include <string> using namespace std; // 基类(父类)- 存放公共部分 class Person { public: Person(const string& name = "无名", int age = 0) : _name(name), _age(age) { //cout << "Person构造函数: " << _name << endl; } void identity() { cout << "身份认证:" << _name << endl; } void showInfo() { cout << "姓名:" << _name << ",年龄:" << _age << endl; } protected: string _name; int _age; string _address; string _tel; }; // 派生类(子类)- 继承Person class Student : public Person { public: Student(const string& name, int age, int stuId) : Person(name, age), _stuId(stuId) { cout << "Student构造函数: " << _name << endl; } void study() { cout << _name << "(学号:" << _stuId << ")正在学习" << endl; } protected: int _stuId; // 学号(学生特有) }; class Teacher : public Person { public: Teacher(const string& name, int age, const string& title) : Person(name, age), // 直接一步到位 _title(title) { cout<< "Teacher构造函数: " << _name << endl; } void teach() { cout << _name << "(职称:" << _title << ")正在授课" << endl; } protected: string _title; // 职称(老师特有) }; int main() { cout << "=== 创建学生 ===" << endl; Student s("小明", 18, 2024001); s.identity(); // 继承自Person s.study(); // Student自己的函数 s.showInfo(); // 继承自Person cout << "\n=== 创建老师 ===" << endl; Teacher t("王老师", 35, "教授"); t.identity(); // 继承自Person t.teach(); // Teacher自己的函数 t.showInfo(); // 继承自Person return 0; }

注意:

必须调用父类构造函数,让父类自己初始化自己的成员

// ✅ 正确!唯一写法! Student(const string& name, int age, int stuId) : Person(name, age), // 调用父类构造,让父类初始化 _name _age _stuId(stuId) // 子类初始化自己的成员 {}

我自己写的错误示例:

// ❌ 错误!绝对不能这么写! Student(const string& name, int age, int stuId) :_name(name), _age(age), _stuId(stuId)

子类不能在初始化列表里直接给父类的成员变量赋值!

  • _name_age是父类 Person 的成员
  • 父类成员,必须由父类的构造函数来初始化
  • 子类无权在初始化列表里直接初始化父类成员!

二、继承的三种方式

访问权限和继承权限是不同的概念:

1.访问权限(成员本身的权限)

作用:控制 “外面能不能直接用”

  • public:谁都能访问
  • protected:自己 + 子类能访问
  • private:只有自己能访问

这是成员自己的属性,跟继承没关系。

2.继承权限(继承方式)

作用:控制 “继承过来后,成员变成什么权限”三种继承方式:

  • public继承
  • protected继承
  • private继承

它只改变成员在子类里的最终权限,不改变基类本身。

在类的继承中,有以下几点特性:

1 基类的构造函数与析构函数不能被继承

2 派生类对基类成员的继承没有选择权,不能选则继承或不继承某些成员

3 派生类中可以添加新成员,用于实现新功能,让派生类的功能在基类上有所扩展。

2.1 访问限定符与继承方式的关系

#include <iostream> #include <string> using namespace std; class Base { public: int pub = 1; // 公有成员 protected: int pro = 2; // 保护成员 private: int pri = 3; // 私有成员 }; // 1. public继承 class PubDerived : public Base { public: void test() { cout << pub << endl; // ✅ 可以访问:pub → public cout << pro << endl; // ✅ 可以访问:pro → protected // cout << pri << endl; // ❌ 错误:基类private成员在派生类不可见 } }; // 2. protected继承 class ProDerived : protected Base { public: void test() { cout << pub << endl; // ✅ 可以访问:pub → protected cout << pro << endl; // ✅ 可以访问:pro → protected // cout << pri << endl; // ❌ 错误:不可见 } }; // 3. private继承 class PriDerived : private Base { public: void test() { cout << pub << endl; // ✅ 可以访问:pub → private cout << pro << endl; // ✅ 可以访问:pro → private // cout << pri << endl; // ❌ 错误:不可见 } }; int main() { PubDerived pubObj; pubObj.pub = 10; // ✅ public成员在类外可访问 // pubObj.pro = 20; // ❌ protected成员类外不可访问 ProDerived proObj; // proObj.pub = 10; // ❌ 变成protected后,类外不可访问 PriDerived priObj; // priObj.pub = 10; // ❌ 变成private后,类外不可访问 cout << "=== 结论 ===" << endl; cout << "实际开发中,99%的情况使用public继承" << endl; return 0; }

总结:

1. private 成员

子类永远无法直接访问,不管什么继承方式。

2. public 继承

父类的访问权限原样保留:

  • public → 依然 public
  • protected → 依然 protected
  • private → 依然不能直接访问

你这句话说得特别到位:“公有继承就是基类的成员在子类也有一份,子类的访问权限和基类一样。”只是父类 private 永远碰不到,构造析构不继承。

3. protected 继承

  • 父类 public、protected → 在子类里都变成 protected正确,父类 private子类永远无法直接访问

4. private 继承

  • 父类所有可继承成员 → 在子类都变成 private 正确,父类 private子类永远无法直接访问

虽然父类 private子类永远无法直接访问,但子类的成员函数可以调用父类的public,protected权限的 成员函数 通过父类间接访问

例子:

class A { private: int c = 100; // 父类私有 public: void show_c() { // 父类自己的函数,可以访问 c cout << c << endl; } }; class B : public A { public: void func() { // c = 200; ❌ 不能直接访问父类 private show_c(); // ✅ 可以调用父类 public 函数 // 间接访问 c } }; int main() { B a; a.show_c(); a.func(); }

三、基类和派生类之间的转换(切片)

#include <iostream> #include <string> using namespace std; class Person { public: Person(const string& name = "") : _name(name) {} string _name; int _age = 0; }; class Student : public Person { public: Student(const string& name, int stuId) : Person(name), _stuId(stuId) {} int _stuId; }; int main() { Student s("小明", 2024001); // 1. 子类对象赋值给父类对象(切片) Person p1 = s; // 只拷贝Person部分 cout << "p1._name: " << p1._name << endl; // 小明 // 2. 子类对象赋值给父类指针(指向子类中的父类部分) Person* p2 = &s; cout << "p2->_name: " << p2->_name << endl; // 小明 // p2->_stuId; // ❌ 错误:父类指针不能访问子类成员 // 3. 子类对象赋值给父类引用 Person& p3 = s; cout << "p3._name: " << p3._name << endl; // 小明 // 4. ❌ 父类对象不能赋值给子类对象 // Student s2 = p1; // 编译错误 // 5. 父类指针指向子类时,可以强制转换回子类指针(需要确保确实指向子类) Person* p4 = &s; Student* s2 = (Student*)p4; // C风格强制转换 cout << "s2->_stuId: " << s2->_stuId << endl; // 2024001 cout << "\n=== 切片示意图 ===" << endl; cout << "子类对象: [Person部分][Student部分]" << endl; cout << "父类指针: 指向 [Person部分] ← 切片" << endl; return 0; }

派生类对象可以直接赋值/初始化给基类对象 / 引用 / 指针派生类中独有的成员会被切掉,只保留基类部分,这就是对象切片。

派生类切片 → 切掉独有部分,只留基类成员,安全向上转换。

四、继承中的作用域(隐藏规则)

#include <iostream> #include <string> using namespace std; class Person { public: Person() : _num(111) {} void print() { cout << "Person::print()" << endl; } void func(int x) { cout << "Person::func(int) = " << x << endl; } protected: string _name = "Person"; int _num; // 身份证号 }; class Student : public Person { public: Student() : _num(999) {} void print() { cout << "Student::print()" << endl; } void func() { // 函数名相同,参数不同也构成隐藏 cout << "Student::func()" << endl; } void show() { // 同名成员:子类会隐藏父类的 cout << "子类_num: " << _num << endl; // 999 cout << "父类_num: " << Person::_num << endl; // 111(显式指定) // 同名函数:子类隐藏父类 print(); // 调用子类的 Person::print(); // 调用父类的 func(); // 调用子类的(无参) // func(10); // ❌ 错误:被隐藏了,找不到 Person::func(10); // ✅ 显式指定可以调用 } protected: int _num; // 学号(与父类同名) }; int main() { Student s; s.show(); cout << "\n=== 隐藏规则总结 ===" << endl; cout << "1. 子类和父类有同名成员(变量/函数),子类会隐藏父类" << endl; cout << "2. 函数同名即构成隐藏(不看参数)" << endl; cout << "3. 可以用 父类::成员 显式访问被隐藏的成员" << endl; cout << "4. 建议:尽量不要在子类定义同名成员" << endl; return 0; }

子类和基类的作用域不一样,所以子类有和基类一样的成员变量或者成员函数时,构不成重载(重载要作用域一样),根据就近原则,用子类的,除非显示调用父类的

作用域不同 → 不能重载 → 同名就隐藏 → 就近用子类 → 父类加::

五、派生类的默认成员函数(完整示例)

这是面试中的高频考点!

#include <iostream> #include <string> using namespace std; class Person { public: // 构造函数 Person(const string& name = "无名") : _name(name) { cout << "Person构造函数: " << _name << endl; } // 拷贝构造函数 Person(const Person& p) : _name(p._name) { cout << "Person拷贝构造函数: " << _name << endl; } // 赋值运算符重载 Person& operator=(const Person& p) { cout << "Person赋值运算符: " << p._name << " -> " << _name << endl; if (this != &p) { _name = p._name; } return *this; } // 析构函数 ~Person() { cout << "Person析构函数: " << _name << endl; } protected: string _name; }; class Student : public Person { public: // 构造函数:必须调用基类构造函数初始化基类部分 Student(const string& name, int stuId) : Person(name) // 显式调用基类构造函数 , _stuId(stuId) { cout << "Student构造函数: " << name << ", 学号:" << _stuId << endl; } // 拷贝构造函数:必须调用基类拷贝构造 Student(const Student& s) : Person(s) // 切片:Student对象赋值给Person引用 , _stuId(s._stuId) { cout << "Student拷贝构造函数: " << _stuId << endl; } // 赋值运算符:必须显式调用基类的赋值运算符 Student& operator=(const Student& s) { cout << "Student赋值运算符" << endl; if (this != &s) { Person::operator=(s); // 显式调用基类赋值运算符 _stuId = s._stuId; } return *this; } // 析构函数:会自动调用基类析构(无需显式调用) ~Student() { cout << "Student析构函数: 学号" << _stuId << endl; // 会自动调用 ~Person() } protected: int _stuId; }; int main() { cout << "=== 1. 创建对象(构造) ===" << endl; Student s1("小明", 1001); cout << "\n=== 2. 拷贝构造 ===" << endl; Student s2(s1); cout << "\n=== 3. 赋值运算 ===" << endl; Student s3("小红", 1002); s1 = s3; cout << "\n=== 4. 销毁对象(析构) ===" << endl; // 对象会按创建顺序逆序析构 return 0; }

1子类对象 = 父类部分 + 子类自己部分

子类构造函数初始化列表必须先调用父类构造函数(用匿名对象的方法:父类名(值),不然父类的成员变量无法初始化(上面说的的子类不能直接初始化父类成员),除非你不用父类的成员变量。

// 构造函数:必须调用基类构造函数初始化基类部分 Student(const string& name, int stuId) : Person(name) // 显式调用基类构造函数 , _stuId(stuId) { cout << "Student构造函数: " << name << ", 学号:" << _stuId << endl; }

name要初始化成学生自己的名字

2 拷贝构造时,s2 里面也包含父类的 name 变量,父类部分必须由父类的拷贝构造来初始化,所以子类必须调用父类的拷贝构造!
// 拷贝构造函数 Person(const Person& p) : _name(p._name) { cout << "Person拷贝构造函数: " << _name << endl; }
// 拷贝构造函数:必须调用基类拷贝构造 Student(const Student& s) : Person(s) // 切片:Student对象赋值给Person引用 , _stuId(s._stuId) { cout << "Student拷贝构造函数: " << _stuId << endl; } cout << "\n=== 2. 拷贝构造 ===" << endl; Student s2(s1);

这里的Person(s)还顺便复习了切片:sStudent的引用,参数匹配父类的拷贝构造,这里的p看到的只是s里面的Person 部分切掉子类部分,只保留父类部分

如果不写Person(s)会发生什么?编译器会自动调用父类的【默认构造】Person(),而不是拷贝构造!

Student(const Student& s) : Person() // 编译器自动插入 , _stuId(s._stuId) {}

结果:

  • s2 的名字 = 无名
  • s2 的学号 = 1001这就错了!

3

  1. 构造:子类必须在初始化列表调用父类构造
  2. 拷贝构造:子类必须调用父类拷贝构造
  3. 赋值重载:子类必须显式调用父类赋值重载Person::operator=(s)
  4. 析构:不用调用,编译器自动调用父类析构
3赋值重载:编译器不会自动帮你调用父类赋值!必须手动写!
// 赋值运算符重载 Person& operator=(const Person& p) { cout << "Person赋值运算符: " << p._name << " -> " << _name << endl; if (this != &p) { _name = p._name; } return *this; } // 赋值运算符:必须显式调用基类的赋值运算符 Student& operator=(const Student& s) { cout << "Student赋值运算符" << endl; if (this != &s) { Person::operator=(s); // 显式调用基类赋值运算符 _stuId = s._stuId; } return *this; }

子类赋值 = 父类赋值 + 子类赋值,缺一不可!

切片发生了!

  • sStudent
  • 传给父类的operator=(const Person& p)
  • 编译器自动切片 →只把父类部分传给父类赋值函数

调用父类的赋值重载

_name正确赋值过去。

如果不写:

  • 子类学号赋值成功
  • 父类名字根本没赋值!还是原来的值!

注意:

Person::operator=(s); // 显式调用基类赋值运算符

不加Person::子类和父类有同名函数 → 父类被直接隐藏!,发生无限递归,栈溢出

总结:

  1. 构造顺序:基类 → 派生类

  2. 析构顺序:派生类 → 基类

  3. 拷贝构造必须调用基类拷贝构造

  4. 赋值运算符必须显式调用基类赋值运算符

六、菱形继承与虚继承(重点难点)

6.1 菱形继承的问题

#include <iostream> #include <string> using namespace std; class Person { public: string _name = "Person"; int _age = 0; }; // Student和Teacher都继承Person class Student : public Person { public: int _stuId = 0; }; class Teacher : public Person { public: int _teacherId = 0; }; // Assistant同时继承Student和Teacher class Assistant : public Student, public Teacher { public: string _major = "计算机"; }; int main() { Assistant a; // ❌ 二义性:_name有两个副本(一份来自Student,一份来自Teacher) // a._name = "张三"; // 编译错误:对_name的访问不明确 // ✅ 需要显式指定从哪个路径访问 a.Student::_name = "张三(学生身份)"; a.Teacher::_name = "张三(老师身份)"; // 数据冗余:同一个Person对象有两份 cout << "Student::_name地址: " << &a.Student::_name << endl; cout << "Teacher::_name地址: " << &a.Teacher::_name << endl; cout << "两个地址不同,说明有两份数据" << endl; // 内存大小:包含两份Person的成员 cout << "\n对象大小: " << sizeof(Assistant) << " 字节" << endl; // string(32字节) * 2份 = 64字节 + 其他成员 return 0; }

对象内存布局:

分析:

Person / \ / \ Student Teacher \ / \ / Assistant

编译器看见:class Assistant : public Student, public Teacher

它就严格按从左到右执行:

1. 先构造左边第一个:Student

  • 构造 Student 必须先构造它的父类Person
  • 输出:Person 构造
  • 然后构造 Student 自己
  • 输出:Student 构造

2. 再构造右边第二个:Teacher

  • 构造 Teacher 必须先构造它的父类Person
  • 输出:Person 构造又来一次!
  • 然后构造 Teacher 自己
  • 输出:Teacher 构造

3. 最后构造自己:Assistant

  • 输出:Assistant 构造

所以:

  • _name存在两个不同的地址
  • 编译器不知道你要哪一个 →二义性错误
  • 数据冗余、浪费空间、容易出错

6.2 虚继承解决菱形继承

#include <iostream> #include <string> using namespace std; // 使用虚继承(virtual) class Person { public: Person() : _name("Person") { cout << "Person构造函数" << endl; } string _name; int _age = 0; }; // 虚继承Person class Student : virtual public Person { public: Student() : _stuId(0) { cout << "Student构造函数" << endl; } int _stuId; }; // 虚继承Person class Teacher : virtual public Person { public: Teacher() : _teacherId(0) { cout << "Teacher构造函数" << endl; } int _teacherId; }; class Assistant : public Student, public Teacher { public: Assistant() : _major("计算机") { cout << "Assistant构造函数" << endl; } string _major; }; int main() { cout << "=== 菱形虚继承 ===" << endl; Assistant a; // ✅ 可以直接访问,没有二义性 a._name = "张三"; cout << "姓名: " << a._name << endl; // ✅ 只有一份Person数据 cout << "&a._name: " << &a._name << endl; cout << "&a.Student::_name: " << &a.Student::_name << endl; cout << "&a.Teacher::_name: " << &a.Teacher::_name << endl; cout << "三个地址相同,说明只有一份数据" << endl; cout << "\n=== 构造顺序(虚继承) ===" << endl; // 输出会显示构造顺序:最远的基类先构造 return 0; }

虚继承写在StudentTeacher上,它的作用只有一个:告诉编译器:

Student 和 Teacher 不要各自复制一份 Person,而是共享同一份 Person,所以

cout << "&a._name: " << &a._name << endl; cout << "&a.Student::_name: " << &a.Student::_name << endl; cout << "&a.Teacher::_name: " << &a.Teacher::_name << endl;

他们地址完全一样。a.Student::_name意思是:我要访问的是从 Student 继承下来的 _name

作用:虚继承 = 只有一份公共的 Person 基类

让最终子类 Assistant 里,Person 只保留一份!不再有两份_name,不再有二义性!

6.3 虚继承的构造顺序(重要)

#include <iostream> using namespace std; class Grand { public: Grand() { cout << "Grand构造" << endl; } }; class Base1 : virtual public Grand { public: Base1() { cout << "Base1构造" << endl; } }; class Base2 : virtual public Grand { public: Base2() { cout << "Base2构造" << endl; } }; class Derived : public Base1, public Base2 { public: Derived() { cout << "Derived构造" << endl; } }; int main() { cout << "虚继承构造顺序:最远的虚基类最先构造" << endl; Derived d; // 输出顺序: // Grand构造 ← 最远的基类 // Base1构造 // Base2构造 // Derived构造 return 0; }

因为虚继承保证最高层基类只构造一份

所以必须让最终孙子类(Derived)直接负责构造爷爷(Grand)不能让 Base1、Base2 各自去构造,否则又会重复!

  • 最远的基类由最终派生类直接构造
  • 只构造一次,不会重复构造

七、继承与友元、静态成员

#include <iostream> using namespace std; class Person { public: friend void show(const Person& p); static int cnt; Person() { cnt++; } protected: string name = "Person"; }; int Person::cnt = 0; class Student : public Person {}; void show(const Person& p) { cout << "访问:" << p.name << endl; } int main() { // 静态:整个继承体系共用一个 Person p1, p2; Student s1, s2; cout << "总数:" << Person::cnt << endl; // 4 cout << Student::cnt << endl;; cout<<s2.cnt<<endl; cout << "地址一样:" << &Person::cnt << " " << &Student::cnt << endl; // 友元只作用于 Person,不继承给 Student Person p; show(p); // 可以 // Student s; // show(s); // 不行!友元不继承 }

1 静态成员属于类,不属于对象,存贮在静态变量/全局区,独一份

所有对象共享:父类、子类全都共用这一个

static int cnt;
  • 不是每个 Person 自带一个 cnt
  • 整个 Person 类、整个继承体系共用这一个
  • 所以创建 p1、p2、s1、s2 时每构造一次,同一份 cnt++
  • 结果自然就是4

2友元关系不能被继承,父类友元函数不是子类的友元函数不能访问子类的私有,保护变量

八、继承 vs 组合(设计原则)

#include <iostream> #include <string> #include<vector> using namespace std; // ========== 组合示例(has-a关系) ========== // 轮胎类 class Tire { public: Tire(const string& brand = "米其林", int size = 17) : _brand(brand), _size(size) { cout << "Tire构造: " << _brand << " " << _size << "寸" << endl; } void show() { cout << "轮胎品牌: " << _brand << ", 尺寸: " << _size << "寸" << endl; } private: string _brand; int _size; }; // 发动机类 class Engine { public: Engine(int power = 200) : _power(power) { cout << "Engine构造: " << _power << "马力" << endl; } void show() { cout << "Engine构造: " << _power << "马力" << endl; } private: int _power; }; class Car { public: // 给 tire 和 engine 也写上初始化! Car(const string& brand) : _brand(brand), _tire(), _engine() { cout << "Car构造: " << _brand << endl; } void show() { cout << "汽车品牌: " << _brand << endl; _tire.show(); _engine.show(); } private: string _brand; Tire _tire; Engine _engine; }; // ========== 继承示例(is-a关系) ========== // 交通工具类 class Vehicle { public: virtual void run() { cout << "交通工具在运行" << endl; } }; // 汽车也是一种交通工具 - is-a关系 class Benz : public Vehicle { public: void run() override { cout << "奔驰汽车在飞驰" << endl; } void luxury() { cout << "提供豪华配置" << endl; } }; class BMW : public Vehicle { public: void run() override { cout << "宝马汽车在狂飙" << endl; } void sport() { cout << "提供运动模式" << endl; } }; // ========== 实际开发建议 ========== // 栈的实现:既可以用继承,也可以用组合 template<typename T> class StackByInheritance : public vector<T> { // 继承方式 public: void push(const T& val) { vector<T>::push_back(val); } void pop() { vector<T>::pop_back(); } T& top() { return vector<T>::back(); } }; template<typename T> class StackByComposition { // 组合方式(推荐) public: void push(const T& val) { _v.push_back(val); } void pop() { _v.pop_back(); } T& top() { return _v.back(); } private: vector<T> _v; // 组合一个vector }; int main() { cout << "=== 组合示例(has-a) ===" << endl; Car myCar("特斯拉"); cout << "\n=== 继承示例(is-a) ===" << endl; Vehicle* v1 = new Benz(); Vehicle* v2 = new BMW(); v1->run(); // 多态 v2->run(); cout << "\n=== 设计原则 ===" << endl; cout << "1. 优先使用组合(耦合度低,更灵活)" << endl; cout << "2. 只有当确实是is-a关系时才使用继承" << endl; cout << "3. 需要多态时必须使用继承" << endl; cout << "4. 类之间的关系既适合继承也适合组合时,优先用组合" << endl; delete v1; delete v2; return 0; }

注意:为什么继承 vector 时,必须写vector<T>::push_back(val)

模板本身不是真正的类 / 函数,只是一张图纸。因为编译时父类还没生成,所以调用父类函数必须写 父类<T>时,编译器才会根据图纸生成真正的代码(也就是按需实例化)

1. 两种关系(最核心)

① 组合:has-a(有一个)

  • 汽车有一个轮胎
  • 汽车有一个发动机
  • 写法:类里面包含另一个类对象

构造顺序(组合):先构造成员对象 → 再构造自己

1. 两种关系(最核心) ① 组合:has-a (有一个) 汽车 有一个 轮胎 汽车 有一个 发动机 写法:类里面包含另一个类对象

② 继承:is-a(是一个)

  • 奔驰是一种交通工具
  • 宝马是一种交通工具
  • 写法:class Benz : public Vehicle

2. 设计原则(最重要!)

优先使用组合,少用继承!

  • 组合:耦合低、安全、灵活
  • 继承:耦合高、会继承所有接口,容易被乱用

例子:栈(Stack)

  • 用组合:只开放 push/pop/top
  • 用继承:能调用 vector 所有函数(不安全)

终极口诀

  1. is-a 用继承
  2. has-a 用组合
  3. 能组合绝不继承

九、练习

http://www.jsqmd.com/news/669860/

相关文章:

  • 文件上传漏洞靶场(upload-labs) 1~11关
  • Qwen3.5-9B-AWQ-4bit数据库课程设计智能辅导系统
  • Neeshck-Z-lmage_LYX_v2企业级:支持审计日志与生成记录全链路追踪
  • 黎阳之光:电力场站视频孪生解决方案(设备状态与现场画面联动监管)
  • 2026年3月中式线条实力厂家推荐,实木中式线条/中式线条,中式线条源头厂家选哪家 - 品牌推荐师
  • Pi0 Robot Control Center快速上手:Gradio Blocks高级布局与事件绑定技巧
  • 启发式算法WebApp实验室:从搜索策略到群体智能的能力进阶(十一)
  • LangFlow真实案例:用低代码工具3天完成智能助手开发
  • 066、代码实战十六:计算扩散模型的FID与IS分数
  • XUnity.AutoTranslator完整指南:Unity游戏实时自动翻译解决方案
  • UART串口驱动框架:从一次深夜调试说起
  • 下一代编辑器的最佳选择!一款基于AI驱动的开源富文本编辑器,兼容几乎所有主流架构,可PC+移动端无缝切换
  • Ostrakon-VL-8B嵌入式部署初探:轻量级餐饮设备端视觉应用构想
  • 067、高效训练技巧:梯度检查点、混合精度与分布式
  • 开启MySQL8的密码策略组件validate_password
  • 终极指南:AlienFX Tools深度解析与Alienware硬件控制完全手册
  • Phi-4-mini-reasoning实战教程:与LangChain结合构建可解释推理Agent
  • TTY子系统与线路规程:那个让我深夜抓狂的串口“丢包”问题
  • 仓库系统测试报告
  • HunyuanVideo-Foley镜像免配置:彻底告别torch版本冲突与依赖地狱
  • 零基础5分钟部署实时手机检测模型:DAMOYOLO-S小白快速上手教程
  • HPH的构造 高压均质机内部揭秘
  • 学Simulink——基于Simulink的数字孪生:实车数据驱动电机参数辨识
  • 怎样高效管理Windows驱动程序:DriverStore Explorer实用方案完全手册
  • [特殊字符] MoviePy 报错:配置了 ImageMagick 环境变量却不好使?
  • Java开发者快速上手:Phi-4-mini-reasoning本地API调用集成教程
  • mysql启动报错找不到my.cnf怎么办_mysql配置文件问题
  • 降AI率工具哪个好?知网维普双平台实测三款工具对比
  • Z-Image-Turbo-rinaiqiao-huiyewunv 与QT框架集成:开发跨平台桌面AI图像工具
  • 郭老师-一个人有没有才气?看这8个维度就明白了