网站排名必做阶段性seo策略,常州专业网站建设公司,腾讯自媒体平台注册,厦门哪里有教网站建设Hello大家好#xff01; 很高兴与大家见面#xff01; 给生活添点快乐#xff0c;开始今天的编程之路。 我的博客:但愿. 我的专栏:C语言、题目精讲、算法与数据结构、C 欢迎点赞#xff0c;关注 目录 一 继承的概念及定义 1.1继承的概念 1.2继承的定义 1.2.1定义格式 1…Hello大家好 很高兴与大家见面 给生活添点快乐开始今天的编程之路。我的博客:但愿.我的专栏:C语言、题目精讲、算法与数据结构、C欢迎点赞关注目录一继承的概念及定义1.1继承的概念1.2继承的定义1.2.1定义格式1.2.2类继承基类方式改变对应成员访问⽅式的变化1.2.3继承类模板【类继承类似】二基类和派⽣类间的转换2.1不同的转换方式2.1.1会产生临时变量2.1.2不会产生临时变量(基类和派⽣类间的转换2.1.2.1不会产生临时变量(基类和派⽣类间的转换概念2.1.2.2不会产生临时变量(基类和派⽣类间的转换规则三继承中的作⽤域3.1隐藏规则3.2考察继承作⽤域相关选择题四派⽣类的默认成员函数五实现⼀个不能被继承的类5.1方法两种)六继承与友元、继承与静态成员6.1继承与友元6.2继承与静态成员七多继承、菱形继承、虚继承7.1多继承、菱形继承、虚继承的关系7.2 继承模型7.2.1单继承7.2.2多继承7.2.3菱形继承7.3菱形继承问题数据冗余和二义性的解决办法7.3.1二义性解决办法1 —— 指定类域显示访问7.3.2二义性解决办法2—— 虚拟继承7.4多继承中指针偏移问题7.5总结八继承和组合一继承的概念及定义1.1继承的概念继承(inheritance)机制是⾯向对象程序设计使代码可以复⽤的最重要的⼿段。继承是在已有类特性的基础上进⾏扩展增加⽅法(成员函数)和属性(成员变量)这样产⽣新的类。原来的类称为基类(父类);新类称为派⽣类(子类)。继承可以提高代码的复用性呈现出⾯向对象程序设计的层次结构。以前我们接触的函数层次的复⽤继承是类设计层次的复⽤。【实例】//普通方式-设计一个学生类和一个老师类型 class Student { public: // 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 void identity() { // ... } // 学习 void study() { // ... } protected: string _name peter; // 姓名 string _address; // 地址 string _tel; // 电话 int _age 18; // 年龄 int _stuid; // 学号 }; class Teacher { public: // 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 void identity() { // ... } // 授课 void teaching() { //... } protected: string _name 张三; // 姓名 int _age 18; // 年龄 string _address; // 地址 string _tel; // 电话 string _title; // 职称 }; //我们看到没有继承之前我们设计了两个类Student和TeacherStudent和Teacher都有姓名 / 地址 / //电话 / 年龄等成员变量都有identity⾝份认证的成员函数设计到两个类⾥⾯就是冗余的。 //继承方式把两个类中的公有成员提取出来设计成一个类在继承这个类 class Person { public: // 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 void identity() { // ... } protected: string _name peter; // 姓名 string _address; // 地址 string _tel; // 电话 int _age 18; // 年龄 }; class Student :public Person { public: // 学习 void study() { // ... } protected: int _stuid; // 学号 }; class Teacher :public Person { public: // 授课 void teaching() { //... } protected: string _title; // 职称 };1.2继承的定义1.2.1定义格式下⾯我们看到Person是基类也称作⽗类。Student是派⽣类也称作⼦类。继承方式和访问限定符一样有三种public继承、protected继承、private继承。1.2.2类继承基类方式改变对应成员访问⽅式的变化继承和访问限定符分类如下继承基类的方式不同不然会导致访问基类成员的方式不同具体如下规则1.基类private成员在派⽣类中⽆论以什么⽅式继承都是不可⻅的。这⾥的不可⻅是指基类的私有成员还是被继承到了派⽣类对象中但是语法上限制派⽣类对象不管在类⾥⾯还是类外⾯都不能去访问它。即无法在派生类中显示调用可以通过在基类中设计一个公有函数进行访问)2.基类private成员在派⽣类中是不能被访问如果基类成员不想在类外直接被访问但需要在派⽣类中能访问就定义为protected。保护成员限定符是因继承才出现的。3.基类的私有成员无论任何生成派生类在派⽣类都是不可⻅。基类的其他成员在派⽣类的访问⽅式 Min(成员在基类的访问限定符继承⽅式)public protected private。4.使⽤关键字class时默认的继承⽅式是private使⽤struct时默认的继承⽅式是public不过最好显⽰的写出继承⽅式。注意在实际运⽤中⼀般使⽤都是public继承⼏乎很少使⽤protetced/private继承也不提倡使⽤protetced/private继承因为protetced/private继承下来的成员都只能在派⽣类的类⾥⾯使⽤实际中扩展维护性不强。【示例】//1基类中的private(私有成员)不管与什么继承方式都在派生类中不可见 // 但是不一定是不能使用可以在基类中设计一个公有函数进行访问。例如父亲的私房钱虽然不可见但是可以通过简介的方式得到(全部告诉妈妈)。 //2基类中的其他类型成员在派生类中的限制是基类中成员变量的访问限定符和派生类的继承方式两种访问限定符种更小的一个 class Person { public: // 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 void identity() { // ... } //为了满足基类中的私有成员可以在派生类中可见在基类中设计一个公有函数进行访问。 void func() { _age; } protected: string _name peter; // 姓名 string _address; // 地址 string _tel; // 电话 private: int _age 18; // 年龄 }; class Student :public Person { public: // 学习 void study() { // ... _name 张三; } protected: int _stuid; // 学号 }; class Teacher :public Person { public: // 授课 void teaching() { //... //_age;基类中的(private)私有成员不管与哪种方式继承都是在派生类中不可见 } protected: string _title; // 职称 }; int main() { Student s; Teacher t; //s._name;基类中的(protected)保护成员通过tublic(公有继承)是派生类中的保护成员,在类外不可见在类中可将 s.identity();//基类中的公有成员通过tublic(公有继承)是派生类中的公有成员; s.func();//对基类中的私有成员进行访问; t.identity(); return 0; }1.2.3继承类模板【类继承类似】【示例】实现栈以前是使用组合(一个类中有一个类后面会讲这里通过继承类模板来实现//原来实现一个栈是一个组合(类中有一个类)后面会讲 templateclass T,class Container class Stack { private: Container _con; }; //继承-模板继承 //私有模板继承私有一个栈 namespace wzy { // templateclass T class stack : public std::vectorT//如果展开了std就不用指定 { public: void push(const T x) { // 基类是类模板时需要指定⼀下类域 // 否则编译报错:error C3861: “push_back”: 找不到标识符 // 因为stackint实例化时也实例化vectorint了 // 但是模版是按需实例化push_back等成员函数未实例化所以找不到 vectorT::push_back(x); //push_back(x); } void pop() { vectorT::pop_back(); } const T top() { return vectorT::back(); } bool empty() { return vectorT::empty(); } }; } int main() { wzy::stackint st; st.push(1); st.push(2); st.push(3); while (!st.empty()) { cout st.top() ; st.pop(); } return 0; }二基类和派⽣类间的转换2.1不同的转换方式2.1.1会产生临时变量前面不同类型变量自己支持类型转换并且之间会产生临时对象(由于临时对象具有常性所以一般用const来修饰转换的结果),例如int和double类型之间的转换是支持的只是可能由于精度的原因值可能改变。【实例】int main() { //为什么i加引用不加const会报错. //原因在于赋值转化时首先会将变量d转化为int类型并将结果存在一个临时变量里 //所以这里引用绑定的对象实际是这个临时变量所以必须加const,否则会编译报错。 double d 1.2; //int i d; //error const int i d; return 0; }2.1.2不会产生临时变量(基类和派⽣类间的转换2.1.2.1不会产生临时变量(基类和派⽣类间的转换概念在基类和派生类对象之间的赋值转换并不会产生临时变量。派生类赋值给基类对象的指针或者基类对象的引用我们认为这是天然的中间不产生临时变量这个叫做赋值兼容规则或切割、切片。【验证】class Person { protected: string _name; // 姓名 string _sex; // 性别 int _age; // 年龄 }; class Student : public Person { public: int _No; // 学号 }; int main() { Student sobj; //派⽣类对象可以赋值给基类的指针/引⽤,并且中间不会产生临时变量 //将派生类对象赋值给 基类的指针 / 基类的引⽤从而可以得到派⽣类中基类(两者共有那部分叫切⽚或者切割。 Person* pp sobj; Person rp sobj; return 0; }我们运行这代码发现可以正常通过也从侧面说明基类和派生类对象之间的赋值转换不会产生临时变量。通过观察类pp和rp的成员变量也从侧面说明将派生类对象赋值给 基类的指针 / 基类的引⽤从而可以得到派⽣类中基类那部分叫切⽚或者切割(但要注意在基类和派⽣类间的转换-建立在公有(public)继承的方式条件下2.1.2.2不会产生临时变量(基类和派⽣类间的转换规则•public继承(前提条件不是public继承没有这个之说)的派⽣类对象 可以赋值给 基类的指针 / 基类的引⽤。这⾥有个形象的说法叫切⽚或者切割。寓意把派⽣类中基类那部分切出来基类指针或引⽤指向的是派⽣类中切出来的基类那部分。•基类对象不能赋值给派⽣类对象。•基类的指针或者引⽤可以通过强制类型转换赋值给派⽣类的指针或者引⽤。但是必须是基类的指针是指向派⽣类对象时才是安全的。这⾥基类如果是多态类型可以使⽤RTTI(Run-Time TypeInformation)的dynamic_cast 来进⾏识别后进⾏安全转换。ps这个我们后⾯类型转换章节再单独专⻔讲解这⾥先提⼀下【示例】//基类和派⽣类间的转换-建立在公有(public)继承的方式条件下 //将派生类对象赋值给 基类的指针 / 基类的引⽤从而可以得到派⽣类中基类那部分叫切⽚或者切割。 class Person { protected: string _name; // 姓名 string _sex; // 性别 int _age; // 年龄 }; class Student : public Person { public: int _No; // 学号 }; int main() { Student sobj; // 1.派⽣类对象可以赋值给基类的指针/引⽤ Person* pp sobj; Person rp sobj; // 派⽣类对象可以赋值给基类的对象是通过调⽤后⾯会讲解的基类的拷⻉构造完成的 Person pobj sobj; //2.基类对象不能赋值给派⽣类对象这⾥会编译报错 //sobj pobj; //像类型转化之间会产生临时对象所以要用const修饰 //而将派生类对象赋值给基类的指针/基类的引⽤虽然它们之间类型比特但是它们之间进行转换是不会产生临时对象 int i 1; const double d i; return 0; }三继承中的作⽤域继承中的作用域主要注意基类和派生类的成员变量中间可能会产生隐藏。3.1隐藏规则1.在继承体系中基类和派⽣类都有独⽴的作⽤域。2.派⽣类和基类中有同名成员派⽣类成员将屏蔽基类对同名成员的直接访问这种情况叫隐藏。在派⽣类成员函数中可以使⽤ 基类::基类成员 显⽰访问3.需要注意的是如果是成员函数的隐藏只需要函数名相同就构成隐藏。 [注意和函数重载进行区分函数重载要建立在同一作用域的前提下(不在同一作用域没有函数重载之说基类和派生类形成成员函数隐藏由于不同类属于不同作用域所以函数隐藏是在不同作用域中函数名相同而函数参数不同(而基类和派生类形成成员函数隐藏只需要函数名相同即可才形成函数重载】注意在实际中在继承体系⾥⾯最好不要定义同名的成员。【实例】//1成员变量形成隐藏,怎么访问基类中的成员变量-通过指定命名空间 class Person { protected: string _name wdefg; // 姓名 int _num 111; // ⾝份证号 }; class Student : public Person { public: void Print() { cout 姓名: _name endl; cout 身份证号: Person ::_num endl;//要想访问基类中的_num成员变量由于和派生类中的变量_num形成隐藏所以要指定命名空间访问 cout 学号: _num endl;//就像我们前面讲的全局/和局部中有同名变量一样先从局部中找局部没有才会到全局中找 //只有指定命名空间才会在其他地方查找就近原则)这里也是默认在派生类中找只有指定在基类才会在基类中查找 } protected: int _num 999; // 学号 }; int main() { Student s1; s1.Print(); return 0; }; //成员函数形成隐藏,怎么访问基类中的成员函数-通过指定命名空间 // 注意基类和派生类之间成员函数之间形成隐藏与函数重载之间的区分 // 函数重载要建立在同一个作用域中(不在同一个作用域不用函数重载之说)并且保证函数参数不同; // 而基类和派生类之间成员函数之间形成隐只需要保证函数名相同即可 class A { public: void fun() { cout func() endl; } }; class B :public A { public: void fun(int i) { cout func(int i) endl; } }; int main() { B b; b.fun(1);//访问派生类中的成员函数 b.A::fun();//访问基类中的成员函数 }3.2考察继承作⽤域相关选择题class A { public: void fun() { cout func() endl; } }; class B : public A { public: void fun(int i) { cout func(int i) iendl; } }; int main() { B b; b.fun(10); b.fun(); return 0; };3.2.1A和B类中的两个func构成什么关系A. 重载 B. 隐藏 C.没关系//这里和函数重载进行区分即可函数重载要建立在同一作用域的前提下(不在同一作用域没有函数重载之说基类和派生类形成成员函数隐藏由于不同类属于不同作用域所以函数隐藏是在不同作用域中函数名相同而函数参数不同(而基类和派生类形成成员函数隐藏只需要函数名相同即可才形成函数重载。所以是B3.2.2上⾯程序的编译运⾏结果是什么A. 编译报错 B. 运⾏报错 C. 正常运⾏//由于基类和派生类中的成员函数fun形成了隐藏所以要访问基类中的成员函数要指定空间而这里没有指定。所以是A四派⽣类的默认成员函数6个默认成员函数默认的意思就是指我们不写编译器会变我们⾃动⽣成⼀个那么在派⽣类中常见的4个成员函数【默认构造、拷贝构造、赋值重载、析构函数】是如何⽣成的呢4个常⻅默认成员函数规则1.派⽣类的构造函数必须调⽤基类的构造函数初始化基类的那⼀部分成员。如果基类没有默认的构造函数则必须在派⽣类构造函数的初始化列表阶段显⽰调⽤。2.派⽣类的拷⻉构造函数必须调⽤基类的拷⻉构造完成基类的拷⻉初始化3.派⽣类的operator必须要调⽤基类的operator完成基类的复制。需要注意的是派⽣类的operator隐藏了基类的operator所以显⽰调⽤基类的operator需要指定基类作⽤域4.派⽣类的析构函数会在被调⽤完成后⾃动调⽤基类的析构函数清理基类成员。因为这样才能保证派⽣类对象先清理派⽣类成员再清理基类成员的顺序。5.派⽣类对象初始化先调⽤基类构造再调派⽣类构造。6.派⽣类对象析构清理先调⽤派⽣类析构再调基类的析构。7.因为多态中⼀些场景析构函数需要构成重写重写的条件之⼀是函数名相同(这个我们多态章节会讲解)。那么编译器会对析构函数名进⾏特殊处理处理成destructor()所以基类析构函数不加virtual的情况下派⽣类析构函数和基类析构函数构成隐藏关系。总结这里我们只有记住两点即可1记住前面默认成员函数的规则2把基类成员当成一个整体规则和以前的自定义类型规则一样【示例】下面主要演示4个主流的默认成员函数至于其他2个基本没用这里主要讲自己手动实现class Person { public: Person(const char* name peter) : _name(name) { cout Person() endl; } Person(const Person p) : _name(p._name) { cout Person(const Person p) endl; } Person operator(const Person p) { cout Person operator(const Person p) endl; if (this ! p) _name p._name; return *this; } ~Person() { cout ~Person() endl; } protected: string _name; // 姓名 }; class Student : public Person { //对于4大默认成员函数其中默认构造函数一般都是自己手动写的 // 编译器自动生成的一般不能满足需求,当然你不想写只要简单的实现也可以在声明的地方给缺省值 //而对于拷贝构造赋值重载析构函数一般都是同时出现的只有深拷贝是才要手动的写(有资源管理) //这里我们主要学习怎么写派生类中的4大默认成员函数这里我们只需要把基类当成以前的自定义类型一样处理规则和前面讲的一样 public: //1默认构造 //如果基类中有默认构造函数和以前的自定义类型一样就会自动调用对应的默认构造函数 //那如果要自己手动写此时就要调用基类中的默认构造函数那怎么调用呢通过基类名(参数)调用 Student(const char* name, const char* address, int num) :Person(name)//自己手动写此时就要调用基类中的默认构造函数那怎么调用呢通过基类名(参数)调 ,_address(address) ,_num(num) { } //拷贝构造 //如果基类中有拷贝构造函数和以前的自定义类型一样就会自动调用对应的构造函数 //那如果要自己手动写此时就要调用基类中的拷贝构造函数那怎么调用呢通过基类名(参数)调用 //此时就有一个问题怎么传参了(怎么通过派生类得到基类中的成员由于基类中的拷贝构造函数是一个引用所以这里可以使用前面的切⽚ //将派生类对象赋值给 基类的指针 / 基类的引⽤从而可以得到派⽣类中基类那部分。这也是为什么拷贝构造参数有引用的原因之一。 Student(const Student s) :Person(s)//显⽰调⽤基类的拷贝构造函数可以通过切⽚进行传参 ,_address(s._address) ,_num(s._num) {} //赋值重载 //如果基类中有拷贝函数和以前的自定义类型一样就会自动调用对应的赋值函数 //那如果要自己手动写此时就要调用基类中的默认构造函数那怎么调用呢 //由于基类和派生类中的赋值重载函数和基类中的赋值重载函数形成隐藏所以显⽰调⽤基类的operator需要指定基类作⽤域 Student operator(const Student s) { if (this ! s) { Person::operator(s);//显⽰调⽤基类的operator需要指定基类作⽤域 _address s._address; _num s._num; } return *this; } ~Student() { //Person::~Person();//这里不显示调用基类的析构函数由于编译器会对析构函数名进⾏特殊处理处理成destructor()所以派⽣类析构函数和基类析构函数构成隐藏关系 //所以要调用基类的析构函数需要指定空间。而这里不显示调用基类的析构函数的原因是为了保证先子后父(由于基类中的成员可能在子类中会被用到如果先析构父的就会出错的析构顺序 // 编译器会在结束后在后面自动call基类的析构函数 如果此时我们还显示调用就调用了两次就会出问题【核心还是编译器不放心程序员】 //delete[] _ptr; } protected: string _address 李四; int _num 1; //学号 //资源管理 //int* _ptr new int[10]; };五实现⼀个不能被继承的类5.1方法(两种)⽅法1基类的构造函数私有派⽣类的构成必须调⽤基类的构造函数但是基类的构成函数私有化以后派⽣类看不⻅就不能调⽤了那么派⽣类就⽆法实例化出对象。⽅法2C11新增了⼀个final关键字final修改基类派⽣类就不能继承了。(现在一般用这种【实例】// C11的⽅法 class Base final { public: void func5() { cout Base::func5 endl; } protected: int a 1; private: // C98的⽅法 /*Base() {}*/ };六继承与友元、继承与静态成员6.1继承与友元友元关系不能继承(由于相互依赖关系导致)也就是说基类友元不能访问派⽣类私有和保护成员.【实例】一个类外函数想同时访问基类和派生类种的成员变量。// 前置声明 class Student; class Person { public: friend void Display(const Person p, const Student s); protected: string _name; // 姓名 }; // 友元关系不能继承 class Student : public Person { friend void Display(const Person p, const Student s); protected: int _stuNum; // 学号 }; //问题一如果将友元函数定义在基类中此时找不到派生类如果将派生类定义在基类之前此时派生类有找不到基类(这就是相互依赖关系。 // 此时可以在基类前加一个派生类的声明此时解决了问题1 //那怎么解决问题2怎么访问派生类中的私有成员由于友元函数不能继承所以只能在派生类中在定义成友元函数 void Display(const Person p, const Student s) { cout p._name endl;//问题1 //cout s._stuNum endl;//问题2 } int main() { Person p; Student s; // 编译报错error C2248: “Student::_stuNum”: 无法访问 protected 成员 // 解决方案Display也变成Student 的友元即可 Display(p, s); return 0; }6.2继承与静态成员基类定义了static静态成员则整个继承体系⾥⾯只有⼀个这样的成员(由于其作用域导致静态成员变量作用域是静态区。⽆论派⽣出多少个派⽣类都只有⼀个static成员实例【也就是不会生成副本想普通成员不仅会继承还会生成副本】。【实例】统计定义了多少个对象都是继承关系【分析】我们可以定义一个静态变量由于是继承关系所有对象的默认构造是都需要调用子类的默认构造来初始化对象中子类的成员。所以我们可以在最开始的类中显示的写默认构造函数 定义静态变量同时每运行一次说明创建了一个对象将静态变量即可。【代码】class Person { public: string _name; static int _count; }; int Person::_count 0; class Student : public Person { protected: int _stuNum; }; int main() { Person p; Student s; // 这里的运行结果可以看到非静态成员_name的地址是不一样的 // 说明派生类继承下来了父派生类对象各有一份说明普通成员变量继承会生成副本 cout p._name endl; cout s._name endl; // 这里的运行结果可以看到静态成员_count的地址是一样的 // 说明派生类和基类共用同一份静态成员说明静态成员变量继承不会生成副本 cout p._count endl; cout s._count endl; Student s1; Person p1; cout 人数 : Person::_count endl; Student s2; Student s3; cout 人数 : Person::_count endl; return 0; }七多继承、菱形继承、虚继承7.1多继承、菱形继承、虚继承的关系继承分为单继承和多继承。而菱形继承是多继承的一直特殊情况虚拟继承则是为了解决其中的一些问题孕育而生。7.2 继承模型7.2.1单继承⼀个派⽣类只有⼀个直接基类时称这个继承关系为单继承7.2.2多继承⼀个派⽣类有两个或以上直接基类时称这个继承关系为多继承多继承对象在内存中的模型是先继承的基类在前⾯后⾯继承的基类在后⾯派⽣类成员在放到最后⾯。7.2.3菱形继承菱形继承是多继承的⼀种特殊情况。菱形继承的问题从下⾯的对象成员模型构造可以看出菱形继承有数据冗余和⼆义性的问题在Assistant的对象中Person成员会有两份。⽀持多继承就⼀定会有菱形继承像Java就直接不⽀持多继承规避掉了这⾥的问题所以实践中我们也是不建议设计出菱形继承这样的模型的。7.3菱形继承问题数据冗余和二义性的解决办法【示例】class Person { public: string _name; // 姓名 }; class Student : public Person { protected: int _num; //学号 }; class Teacher : public Person { protected: int _id; // 职工编号 }; class Assistant : public Student, public Teacher { protected: string _majorCourse; // 主修课程 };几个类之间的关系7.3.1二义性解决办法1 —— 指定类域显示访问虽然显示指定访问哪个父类的成员可以解决二义性问题但是数据冗余问题无法解决。a.Student::_name wzy; a.Teacher::_name wzz;7.3.2二义性解决办法2—— 虚拟继承在上面的继承关系中在Student和Teacher的继承Person时使用虚拟继承在继承方式前加virtual关键字即可解决问题。需要注意的是虚拟继承不要在其他地方去使用。【实例】class Person { public: string _name; // 姓名 }; class Student : virtual public Person { protected: int _num; //学号 }; class Teacher : virtual public Person { protected: int _id; // 职工编号 }; class Assistant : public Student, public Teacher { protected: string _majorCourse; // 主修课程 }; void Test() { Assistant a; a._name peter; }7.4多继承中指针偏移问题下⾯说法正确的是( )Ap1 p2 p3 Bp1 p2 p3 Cp1 p3 ! p2 Dp1 ! p2 ! p3class Base1 { public: int _b1; }; class Base2 { public: int _b2; }; class Derive : public Base1, public Base2 { public: int _d; }; int main() { Derive d; Base1* p1 d; Base2* p2 d; Derive* p3 d; return 0; }7.5总结有了多继承就存在菱形继承有了菱形继承就有菱形虚拟继承底层实现就很复杂性能也会有⼀些损失所以最好不要设计出菱形继承。多继承可以认为是C的缺陷之⼀。我们可以设计出多继承但是不建议设计出菱形继承因为菱形虚拟继承以后⽆论是使⽤还是底层都会复杂很多。当然有多继承语法⽀持就⼀定存在会设计出菱形继承像Java是不⽀持多继承的就避开了菱形继承。那是不是生活中就没人使用菱形继承呢答案肯定是否定的就连C的库中也用了菱形继承。例如IO库中的菱形虚拟继承八 继承和组合•public继承是⼀种is-a的关系。也就是说每个派⽣类对象都是⼀个基类对象。•组合是⼀种has-a的关系。假设B组合了A每个B对象中都有⼀个A对象。•继承允许你根据基类的实现来定义派⽣类的实现。这种通过⽣成派⽣类的复⽤通常被称为⽩箱复⽤(white-box reuse)。术语“⽩箱”是相对可视性⽽⾔在继承⽅式中基类的内部细节对派⽣类可⻅ 。继承⼀定程度破坏了基类的封装基类的改变对派⽣类有很⼤的影响。派⽣类和基类间的依赖关系很强耦合度⾼。•对象组合是类继承之外的另⼀种复⽤选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接⼝。这种复⽤⻛格被称为⿊箱复⽤(black-box reuse)因为对象的内部细节是不可⻅的。对象只以“⿊箱”的形式出现。 组合类之间没有很强的依赖关系耦合度低。优先使⽤对象组合有助于你保持每个类被封装。•优先使⽤组合⽽不是继承。实际尽量多去⽤组合组合的耦合度低代码维护性好。不过也不太那么绝对类之间的关系就适合继承(is-a)那就⽤继承另外要实现多态也必须要继承。类之间的关系既适合⽤继承(is-a)也适合组合(has-a)就⽤组合【实例】// Tire(轮胎)和Car(⻋)更符合复用has-a的关系 class Tire { protected: string _brand Michelin; // 品牌 size_t _size 17; // 尺⼨ }; class Car { protected: string _colour ⽩⾊; // 颜⾊ string _num 陕ABIT00; // ⻋牌号 Tire _t1; // 轮胎 Tire _t2; // 轮胎 Tire _t3; // 轮胎 Tire _t4; // 轮胎 }; class BMW : public Car { public: void Drive() { cout 好开-操控 endl; } }; // Car和BMW/Benz更符合继承is-a的关系 class Benz : public Car { public: void Drive() { cout 好坐-舒适 endl; } }; templateclass T class vector {}; // stack和vector的关系既符合继承is-a也符合复用has-a templateclass T class stack : public vectorT {}; templateclass T class stack { public: vectorT _v; }; int main() { return 0; }本篇文章就到此结束欢迎大家订阅我的专栏欢迎大家指正希望有所能帮到读者更好理解C相关知识 觉得有帮助的还请三联支持一下~后续会不断更新C/C相关知识我们下期再见。