🌈 个人主页:白子寰
🔥 分类专栏:重生之我在学Linux,C++打怪之路,python从入门到精通,数据结构,C语言,C语言题集👈 希望得到您的订阅和支持~
💡 坚持创作博文(平均质量分82+),分享更多关于深度学习、C/C++,python领域的优质内容!(希望得到您的关注~)
目录
继承本质意义是复用
继承的概念及定义
概念
什么是继承?
继承是面向对象程序设计中实现代码复用的关键机制,它允许程序员在保留基类特性的同时,创建具有额外功能的新类,即派生类。
这种机制展现了对象层次结构,并映射了从简单到复杂的认知发展过程。与传统的函数复用不同,继承实现了类级别的复用。
(简单来说就是儿子继承父业:儿子在父业的基础上开拓新的东西或装饰)
继承定义
格式
继承基类成员访问方式的变化
表格法(谁小谁做主)
总结
1)谁的权限小(派生类)听谁的
照public列来说:
①对应行基类的public成员:public小,听public的,派生类是public
②对应行基类的protected成员:protected小,听protected的,派生类是protected
③对应行基类的private成员:private小,听private的,派生类不可见
2)
①被其所属类的成员函数访问
②被类的对象直接访问
③被派生类的(成员/函数)访问在私有限定符下:①
保护限定符下:①③
3)
struct默认继承方式是public
class默认继承方式是private
4)实际中一般用public继承
代码
这是在父类的成员函数、成员变量全处于public(公有)下
如果成员变量不是处于public下呢,我们该怎么修改对象的其中一个成员变量?
答案是在公有public设置成员函数
基类和派生类对象赋值(兼容)转换
简称:切割/切片
概念
区别于:类型转换,中间会产生临时变量(隐式变量是常量加const)
(C语言的截断和提升:int型->(赋值)char, 然后char->int)
简单来说:
派生类到基类的赋值兼容性允许派生类对象被赋值给基类对象,而无需显式类型转换。这种赋值过程不涉及临时变量。
与类型转换不同,派生类到基类的赋值是一种切片操作,仅保留基类部分。
1、派生类对象可以赋值给基类对象
2、基类对象不能赋值给派生类对象
简单理解:不能把小的变成大的
原因
继承的作用域
规则
局部域、全局域、命名空间域、类域:
1.会影响语言编译查找规则;
怎么找?:先到局部找,再到全局找
2.局部域和全局域会影响生命周期
概念
1.隐藏/重定义
隐藏发生在派生类中,当派生类定义了与基类同名的成员函数或成员变量时,基类中的同名成员在派生类的作用域内被隐藏。
隐藏与重载不同,重载要求函数在同一个作用域内具有不同的参数列表
简单来说:
(隐藏:函数名相同;
区别于重载:重载前提要求在同一个作用域)
派生类中和基类有同名函数/同名变量
2.在实际中在继承体系里面最好不要定义同名的成员
如要确实使用/访问同名的,使用作用域解析运算符来明确指出你想要访问的是哪个类的成员(指定/显示)
class Base
{
public:
void Print()
{
cout << "Base_Print()" << endl;
}
};
class Derived : public Base
{
public:
// 定义一个与基类同名的成员函数
void Print()
{
cout << "Derived_Print()" << endl;
}
// 使用作用域解析运算符调用基类的成员函数
void Base_Print()
{
Base::Print();
}
};
int main()
{
Derived d;
d.Print();
// 下面↓↓↓两个等价
// 使用作用域解析运算符调用基类中的Print函数
d.Base_Print();
// 直接使用作用域解析运算符调用基类中的Print函数
d.Base::Print();
return 0;
}
派生类的默认成员函数
1)派生类的构造函数
①派生类对象的初始化顺序
基类构造函数先于派生类构造函数执行(先有基类[地基]再有派生类[建筑高层])
C++遵循顺序:按声明的顺序走
(必须先父后子,因为子类构造初始化可能会用父类成员;没有初始化父,父类成员就是随机值)
父类构造可以显示写,可以保证先父后子子类默认生成的构造
父类成员(整体) -- 默认构造
子类自己的内置成员 -- 一般不处理
子类自己的自定义成员 -- 默认构造
2)派生类的拷贝构造函数
(一般情况下不需要写浅拷贝)
派生类拷贝构造函数负责正确复制基类部分和派生类新增成员
子类默认生成的拷贝构造
父类成员(整体) -- 调用父类的拷贝构造
子类自己的内置成员 -- 值拷贝
子类自己的自定义成员 -- 调用他的拷贝构造
一般就不需要自己写了,子类成员涉及深拷贝,就必须自己实现
3)派生类的赋值运算符
(赋值重载跟拷贝构造相似)
派生类赋值运算符负责正确赋值基类部分和派生类新增成员
4)派生类的析构函数
①顺序
派生类析构函数先于基类析构函数执行【先有派生类的析构函数(毁掉高层)再有基类的析构函数(毁掉地基)】
父类析构不能显示调用,因为显式调用不能保证先子后父
子类默认生成的析构
父类成员(整体) -- 调用父类的析构
子类自己的内置成员 -- 不处理
子类自己的自定义成员 -- 调用析构不能先父后子原因:
因为子类析构时可能会用到父类成员的;先父后子,就可能会出问题
父类析构不是自己显示调用的,子类析构结束时自动调用的
②析构函数重写
派生类析构函数隐式调用基类析构函数
如果基类的析构函数不是虚函数,派生类的析构函数会隐藏基类的析构函数,而不是重写它。
继承与友元
友元关系不能继承
(基类友元不能访问子类私有成员和保护成员)
简单来说:爸爸的朋友不是我的朋友
关键词:friend
继承与静态成员
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员
简单来说:基类加了static修饰,该成员为为相同地址
关键词:static
class Base
{
public:
static int static_val;
};
// 初始化基类的静态成员
int Base::static_val = 10;
class Derived : public Base
{};
int main()
{
cout << "Base::static_val:" << Base::static_val << endl;
cout << "Derived::static_val:" << Derived::static_val << endl;
// 修改基类的val值
Base::static_val = 30;
// 打印派生类的val值
cout << "Derived::static_val:" << Derived::static_val << endl;
return 0;
}
复杂的菱形继承及菱形虚拟继承
继承与组合
面试题
1、
①什么是菱形继承?
菱形继承是指一个类同时继承自两个或多个类,而这些父类又共同继承自一个共同的祖先类,形成一个菱形结构
②菱形继承的问题是什么?
- 数据冗余:共同祖先类的成员在派生类中存在多份副本。
- 二义性:如果共同祖先类中有成员在派生类中被重新定义,那么在访问该成员时可能会产生二义性。
2、
①什么是菱形虚拟继承?
菱形虚拟继承是指在菱形继承结构中,共同祖先类通过虚拟继承的方式被继承,以确保在派生类中只存在一份共同祖先类的成员
②如何解决数据冗余和二义性的虚拟继承:通过在继承声明中使用关键字
virtual
,确保共同祖先类的成员在派生类中只被实例化一次,从而解决数据冗余和二义性问题
3、
①继承和组合的区别?
- 继承:是一种 “is-a” 关系,派生类是基类的一种特化。
- 组合:是一种 “has-a” 关系,一个类包含另一个类的对象作为其成员
②什么时候用继承?
继用时:
- 当派生类需要扩展或特化基类的行为时。
- 当派生类和基类之间存在明确的 “is-a” 关系时
③什么时候用组合?
组合时:
- 当类需要使用另一个类的功能,但不需要继承其接口时。
- 当需要复用代码,但又不希望引入继承带来的强耦合关系时
***********************************************************分割线*****************************************************************************
完结!!!
感谢浏览和阅读。
等等等等一下,分享最近喜欢的一句话:“跟随怎样的圈子,塑造怎样的自己。”。
我是白子寰,如果你喜欢我的作品,不妨你留个点赞+关注让我知道你曾来过。
你的点赞和关注是我持续写作的动力!!!
好了划走吧。