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

【C++】继承----下篇

文章目录

  • 前言
  • 一、实现一个不能继承的类
  • 二、友元与继承
  • 三、继承与静态成员
  • 四、多继承以及菱形继承问题
    • 1.继承模型:
    • 2.菱形继承的问题
    • 3.虚拟继承解决数据冗余和二义性的原理
    • 4.虚拟继承的原理
  • 五、继承的总结和反思
    • 1.继承和组合
  • 总结


前言

各位好呀!今天呢我们接着讲继承的相关知识,之前给大家已经分享了继承一部分知识。那今天小编就来给继承收个尾吧。来看看继承的剩下的一部分。
在这里插入图片描述


一、实现一个不能继承的类

想要实现一个不能被继承的类的呢有两种方法:

  1. 方法一:父类的构造函数私有,子类的构造必须调用父类的构造函数,但是父类的构造函数私有化以后呢,在子类中是不可见也不可调用的。那么 子类就无法实例化出对象,这样就可以达到父类不能被继承(C++98的方法)。
  2. 方法二:C++11新增了一个关键字final,用final修饰父类,那么子类就无法继承父类了
#include<iostream>
#include<string>
using namespace std;
class Person//C++11
{
public:
protected:string _name; 
private:Person()//私有化构造函数{}
};
class student:public Person  
{
public:
private:string ID;
};
int main()
{student s;//这里会报错的,因为构造函数已经被私有化,子类是调不到父类的构造函数的//但是这里要注意的是:如果我们这里不定义,代码是不会报错的。return 0;
}#include<iostream>
#include<string>
using namespace std;
class Person final//C++11
{
public:Person()//私有化构造函数 {} 
protected:string _name; 
private:};
class student:public Person  //像这样用final修饰父类的话,父类也不能被子类继承 
{
public:
private:string ID;
};
int main()
{student s;return 0;
}

二、友元与继承

注意:友元关系不能被继承,也就是说,父类的友元不能访问子类的私有成员和保护成员。

解决方法在子类中加上友元就可以了。还有就是要注意一下需要前置声明一下子类

#include<iostream>
#include<string>
using namespace std;
class student;  //前置声明
class Person
{friend void  Print(const Person& p, const student& s); //编译器在遇到一个变量和函数的时候,都只会向上查找(提高查找的效率),// 所以这里的student就会向上查找,但是上面没有student,student在下面//还有就是student不能放在方面取,因为student要继承Person。这两者相互依赖。//为了解决这个问题呢我们会在上面加一个前置声明
public:
protected:string _name="帅哥"; 
};
class student:public Person 
{friend void  Print(const Person& p, const student& s); //由于继承关系不能被继承下来,所以就访问不到student中_num成员变量//解决这个问只需要像这样,加一个友元就可以解决这个问题了。
public:
protected :string _num="123456";
};
void Print(const Person& p, const student& s)
{cout << p._name << endl;  cout << s._num << endl; 
}
int main()
{student v;Person  c;Print(c,v);    return 0;
}

三、继承与静态成员

#include<iostream>
#include<string>
using namespace std; 
class Person
{
public:string _name; static int n;       
}; 
int Person::n = 1; 
class student :public Person
{
public:string  _num;
};
int main()
{Person p;student s;//非静态成员变量的地址 cout << &p._name << endl;cout << &s._name << endl; cout << endl; //静态成员变量的地址cout << &p.n  << endl;cout << &s.n << endl; return 0;
}

在这里插入图片描述

我们通过看到非静态成员_name地址是不一样,这说明了子类继承下来的成员在子类和父类中各有一份。但是静态成员是不是地址相同呀?这又说明父类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。
还有一个就是在共有的情况下,父类和子类指定作用域就可以访问静态成员。这里突破作用域就可以访问,因为它没有存在对象里面,而是存在静态区,它只是受到类域的限制而已。还有就可以把静态成员理解成全局的。

四、多继承以及菱形继承问题

1.继承模型:

继承模型呢分为三种:单继承,多继承和菱形继承

  1. 单继承一个子类只有一个直接父类时称这种继承关系为单继承
    在这里插入图片描述
  2. 多继承一个子类有两个或以上直接父类时称这个继承关系为多继承
    在这里插入图片描述
  3. 菱形继承菱形继承属于一个特殊的多继承

在这里插入图片描述

2.菱形继承的问题

菱形继承主要时两个问题,分别是 二义性和数据冗余

二义性
首先,什么是二义性?二义性就是在访问数据的时候发生歧义,不知道访问那个。

示例:

#include<iostream>
#include<string>
using namespace std; 
class A
{
public:string _name;int _age;
};
class B:public A 
{
public:
protected:string  _number;
};
class C :public A
{
public:
protected:string Gender;  
};
class D :public B, public C
{
public:
protected:string ID;
};
int main()
{D d;d._name;//存在二义性 
}

在这里插入图片描述
问题分析
这里为什么会存在访问不明确呢?结合之前的知识,子类继承父类成员,那么子类和父类中是不是都分别有一份独立的成员。但是现在B和C这两个类都继承了A,然而D又继承B和C。也就是说A,B,C,D中分别都有一份_name,现在要访问父类中的成员_name,但是这里的D是继承了两个类,两个类中都有_name ,所以这里编译器就不知道该访问那个类里面的_name这就是二义性。

解决方法:
怎样解决这个问题呢?其实很简单,只需要显示指定访问那个父类中_name就可以解决问题,但是不能解决数据冗余的问题

#include<iostream>
#include<string>
using namespace std; 
class A
{
public:string _name;int _age;
};
class B:public A 
{
public:
protected:string  _number;
};
class C :public A
{
public:
protected:string Gender;  
};
class D :public B, public C
{
public:
protected:string ID;
};
int main()
{D d;d.B::_name="张三";//指定要访问的父类d.C::_name = "小张"; //指定要访问的父类
}

数据冗余:
数据冗余可以理解成数据重复造成空间的浪费

示例:
菱形继承还有个特别烦的点就是他会让空间变大,一个它的父类在被几个类继承时,那它就有几份。比如下列代码中,A在B,C中各有一份。

#include<iostream>
#include<string>
using namespace std; 
class A
{
public:string _name; int _age; 
};
class B:public A 
{
public:
protected:string  _number;
};
class C :public A
{
public:
protected: string Gender;  
};
class D :public B, public C
{
public:
protected:string ID;
};
class F:public A,public B
{
public:};
int main()
{D d;F f;cout << sizeof(d) << endl;//菱形继承大小cout << sizeof(f) << endl;//多继承的大小
}

在这里插入图片描述
可以看出两者的空间大小相差的将近一倍了。所以,一般不建议创建菱形继承,因为这样有太多的问题,有时候还把握不住,建议不使用。

3.虚拟继承解决数据冗余和二义性的原理

菱形继承一般不建议使用,但是如果非要使用,那该怎样解决二义性和数据冗余呢?这里就要引用一个新的关键字 virtual(虚拟继承)

那这个关键字该怎么用呢?该加在哪里呢?先看示例:

#include<iostream>
#include<string>
using namespace std;
class A
{
public:string _name;int _age;
};
class B :virtual public A
{
public:
protected:string  _number;
};
class C :virtual public A
{
public:
protected:string Gender;
};
class D :public B, public C
{
public:
protected:string ID;
};
int main()
{D d;d._name = "张三";return 0;
}

通过上面的代码可以看出:
virtual应该加在产生二义性和数据冗余继承的地方,现在A是不是产生了二义性和数据冗余 ,那virtual就加在B和C继承的哪里。这样就解决了二义性和数据冗余的问题,这点我们可以通过监视窗口可以看出。
在这里插入图片描述
从监视窗口展示的原因,这里虽然看起来是三份,但其实是一份。这样是不是就解决了数据冗余和二义性的问题啊。
注意:这里的virtual不能只加在B或者C,必须要同时加在B和C

在这里插入图片描述
大家看看这上图,图中的关系是不是菱形继承呢?其实 上图也时菱形继承哦,大家不要对形状太刻板了哦,认为菱形继承那他的形状就必须时菱形。形状不是菱形但是有二义性和数据冗余的产生那他就是菱形继承,

思考以及解决方法
那这里的virtual该加在哪里呢?BC?还是DC?其实正确是应该是加在BC,这里是不是A产生二义性和数据冗余,那就要加BC呀!那这里可以不可以在BCD都加上virtual呢?这里就好比一个人只需要两根拐棍,而你偏要给他三根是一样的性质。在D那里都没有产生二义性和数据冗余那就没必要加。
在这里插入图片描述
还有就是大家在写代码的时候遇到上图这种继承的时候都加上virtual,这种情况呢属于过度防范了。首先我们可以先看看B和C有没有被同一个类继承。如果没有,可以先不用加;如果有,那再加上virtual是不是也不迟啊?所以大家在写代码的时候不要过度的防范二义性和数据冗余。

4.虚拟继承的原理

#include<iostream>
#include<string>
using namespace std;
class A
{
public:A(const char*name,int age=18):_name(name),_age(age) {}string _name;int _age;
};
class B :virtual public A
{
public:B(const char*name,const char*number="1234567899"):A(name),_number(number) {}
protected:string  _number;
};
class C :virtual public A
{
public:C(const char* name, const char* gender="男"):A(name), Gender(gender) {}
protected:string Gender;
};
class D :public B, public C 
{
public:D(const char*name,const char*id="1263457"):B(name) ,C(name) //,A(naem)//这里必须显示调用,不然会报错,//还有就是这里初始化怎么多name,那到底以谁的为准呢?,ID(id) {}
protected:string ID;
};
int main()
{D d("张三"); return 0;
}

在这里插入图片描述
这里从我们之前学的知识来看这里代码的逻辑应该时没有 问题的呀。调用子类的构造函数先调用父类的默认构造函数嘛。但是这里说class A 不存在默认构造。那是怎么回事呢?这不得不就要看看虚拟继承的原理了。
在这里插入图片描述
在这里插入图片描述

相比于普通的多继承,虚拟继承呢是要把class A拿出来放在最底下的一个类中。他就不像普通多继承那样class A分别存在class B 和class C中。因为他要解决二义性和数据冗余。与此同时,这里还要引入一个虚基表和虚基表指针,复杂的很,所以小编这里就没有展示出来。小编这里主要是像让大家看看这两种继承的有什么不同。回到上面的问题:由于这里A不在B和C里面了,而是一个单独的父类,所以A也因该显示调用。

总结:不要轻易使用菱形继承和写出菱形继承的代码,多继承可以用

五、继承的总结和反思

1. 很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。

  1. 多继承可以认为是C++的缺陷之一,很多后来的OO语言都没有多继承,如Java。

1.继承和组合

  1. public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
  2. 组合是一种has-a的关系,。假设B组合了A,每个B对象中都有一个A对象。
    示例:我们以实现一个简单的栈来演示:
//stack和cin构成has-a的关系
#include<iostream>
#include<string>
#include<stdbool.h> 
using namespace std;
class stack
{
public :void push(const int& x){cin.push_back(x);}void pop(){cin.pop_back();}const int& top()const {return cin.back();}bool empty(){return cin.empty();}
private:  vector<int >  cin; 
};
int main()
{stack s;s.push(1);s.push(2);s.push(3);while (!s.empty()){cout << s.top() << " ";s.pop();}return 0;
}//stack和vector是is-a关系
#include<iostream>
#include<stdbool.h> 
#include<vector>
using namespace std;
class stack:public std::vector<int>  
{
public:void push(const int& x){vector<int> ::push_back(x);}void pop(){vector<int> ::pop_back();}const int& top()const{return vector<int> ::back();}bool empty(){return vector<int> ::empty(); }
};
int main()
{stack s; s.push(1); s.push(2);s.push(3); while (!s.empty()) {cout << s.top() << " "; s.pop();}return 0;
}
  1. 优先使用对象组合,而不是类继承 。
  2. 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。
  3. 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装
  4. 实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。

总结

到这里继承的相关知识小编就基本分享完了咯,如果有什么疑问欢迎大家讨论。那今天就到这里吧。
在这里插入图片描述

相关文章:

  • BUUCTF-[GWCTF 2019]re3
  • 大模型驱动智能服务变革:从全流程赋能到行业纵深落地
  • DPIN河内AI+DePIN峰会:共绘蓝图,加速构建去中心化AI基础设施新生态
  • 【合新通信】---浸没式液冷光模块化学兼容性测试方法
  • Lesar: 面向 Lustre/Scade 语言的形式化模型检查工具
  • DeepSeek/AI驱动的销售业绩倍增实战
  • 施工安全巡检二维码制作
  • Linux文件管理(2)
  • PyTorch 实现食物图像分类实战:从数据处理到模型训练
  • 聚力共赢:超聚变联合枫清科技,构建“算力底座+知识中台”企业智能化新引擎
  • 【C/C++】深入理解指针(五)
  • 智慧联络中心SaaS平台Java项目面试实战
  • Linux操作系统从入门到实战(三)Linux基础指令(上)
  • 【现代深度学习技术】循环神经网络06:循环神经网络的简洁实现
  • MySQL8的安装方法
  • 四步完成机房3D建模仿真:小白也能快速上手
  • 【Rust】Rust中的枚举与模式匹配,原理解析与应用实战
  • Pandas读取Excel文件教程:从入门到精通
  • 制作一个简单的操作系统9
  • AI 编程工具:Augment Code
  • 解放军仪仗司礼大队参加越南纪念南方解放50周年庆典活动
  • 文庙印象:一周城市生活
  • 中信证券:“国家队”未曾减持ETF,应充分相信国家维稳决心
  • 新东方:2025财年前三季度净利增29%,第四财季海外业务将承压
  • 蔚来李斌:当下国际贸易环境有不确定性,但坚信中国汽车产业最终将占全球四成份额
  • 佩索阿稳定常销,陀翁不断加印,青少年喜欢黑塞