目录
一.继承
1.定义:
继承,听起来好像是要继承遗产一样,但在编程里,它其实更像是一个“优点大派送”的过程。
想象一下,有两位“宇宙超级无敌可爱的”老爸(也就是两个类),他们各自都有一堆超酷的技能和属性。然后,有一个“小机灵鬼”儿子(也就是子类)出现了,他想:“哇,这两位老爸的技能都太赞了,我能不能都要呢?”
当然可以!于是,小机灵鬼使用了“继承”这个魔法,把两位老爸的所有优点都继承了过来。这样一来,他就可以同时拥有老爸A的篮球技巧和老爸B的音乐天赋了。
但是,有时候两位老爸可能会有一些相同的“家产”(也就是属性或方法名),这可怎么办呢?没关系,小机灵鬼自有妙招。当他想要使用某个“家产”时,他会清楚地指明是“老爸A的”还是“老爸B的”。
就这样,通过继承,小机灵鬼不仅拥有了两位老爸的优点,还成功地避免了“家产”冲突的问题。真是个聪明的家伙!
所以,简单来说,继承就是“小机灵鬼”儿子把两位“宇宙超级无敌可爱的”老爸的优点都继承过来,同时还解决了可能出现的“家产”冲突问题。真是个一举两得的好方法!
2.语法
语法:class 子类:继承方式 父类
子类 也称为 派生类
父类 也成为 基类
二.继承的作用
当我们随便进入一个编程学习网页,引入眼帘的是各式各样的语言课程。今天我们简单学习一下,如何用C++去实现课程的排版的底层代码。
1.普通实现
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;
//普通实现页面
//java页面
class Java
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++、...(公共分类列表)" << endl;
}
void content()
{
cout << "Java学科视频" << endl;
}
};
//python
class Python
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++、...(公共分类列表)" << endl;
}
void content()
{
cout << "Python学科视频" << endl;
}
};
void test01()
{
cout << "Java下载视频页面如下:" << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "---------------------------" << endl;
cout << "Python下载视频页面如下:" << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout << "---------------------------" << endl;
}
int main()
{
test01();
return 0;
}
2.继承实现(减少重复代码)
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;
//公共页面类
class BasePage
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++、...(公共分类列表)" << endl;
}
};
//Java
class Java :public BasePage
{
public:
void content()
{
cout << "Java学科视频" << endl;
}
};
//Python
class Python :public BasePage
{
public:
void content()
{
cout << "Python学科视频" << endl;
}
};
//C++
class CPP :public BasePage
{
public:
void content()
{
cout << "CPP学科视频" << endl;
}
};
void test01()
{
cout << "Java下载视频页面如下:" << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "---------------------------" << endl;
cout << "Python下载视频页面如下:" << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout << "---------------------------" << endl;
cout << "CPP下载视频页面如下:" << endl;
CPP cp;
cp.header();
cp.footer();
cp.left();
cp.content();
cout << "---------------------------" << endl;
}
int main()
{
test01();
return 0;
}
三.继承方式
1.公共继承
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son1 :public Base1
{
public:
void func()
{
m_A = 10;//父类中的公共权限成员 到子类中依然是公共权限
m_B = 10;//父类中的保护权限成员 到子类中依然是保护权限
//m_C = 10;//父类中的私有成员 子类访问不到
}
};
void test01()
{
Son1 sa;
sa.m_A = 100;
//s1.m_B = 100; //到Son1中m_B是保护权限 类外访问不到
}
2.保护继承
class Son2 :protected Base1
{
public:
void func()
{
m_A = 100; //父类中公共成员,到子类中变为保护权限
m_B = 100; //父类中保护成员,到子类中变为保护权限
//m_C = 100;//父类中私有成员 子类访问不到
}
};
void test02()
{
Son2 sa;
//sa.m_A = 1000;//在Son2中m_A变为保护权限,因此类外访问不到
//sa.m_B = 1000;//在son2中m_B保护权限 不可以访问
}
3.私有继承
class Son3 :private Base1
{
public:
void func()
{
m_A = 100;//父类中公共成员 到子类中变为 私有成员
m_B = 100;//父类中保护成员 到子类中变为 私有成员
//m_C = 100;//父类中私有成员,子类访问不到
}
};
void test03()
{
Son3 sa;
//sa.m_A = 1000;//到Song3中 变为 私有成员 类外访问不到
//sa.m_B = 1000;//到Song3中 变为 私有成员 类外访问不到
}
4.子类继承子类
class GrandSon3 :public Son3
{
public:
void func()
{
//m_A = 1000;//到了Son3中 m_A 变为私有,即使是儿子,也是不可访问
//m_B = 1000;//到了Son3中 m_A 变为私有,即使是儿子,也是不可访问
}
};
四.继承中的对象模型
1.示例代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;
//继承中的对象模型
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son :public Base
{
public:
int m_D;
};
void test01()
{
cout << "size of Son = " << sizeof(Son) << endl;
}
int main()
{
test01();
return 0;
}
父类成员m_C在子类中无法访问,如何得知m_C是否被成功继承呢?我们通过计算子类的大小,得知父类中所有非静态成员属性都会被子类继承下去,父类中私有成员属性 是被编译器给隐藏了,因此是访问不到的,但确实是被继承下去了。接下来,教大家如何直观的观察子类中继承的私有成员。
2.开发人员命令提示符
首先,找到代码文件的路径。右击代码文件名,选择打开所在文件夹,复制路径。
其次,找到并打开vs命令提示符(X64_X86)。输入命令,跳转到文件所在的文件盘里(如在D盘,输入D:即可) 。cd 后加文件路径,dir 查询该文件夹下所有的文件。
然后,输入命令cl /d1 reportSingleClassLayout+类名 +文件名。借由此工具,我们可以清晰看到Son类中的成员。
五. 继承中构造和析构的顺序
继承中的构造函数和析构顺序如下:先构造父类,再构造子类,析构的顺序与构造的顺序相反
class Base
{
public:
Base()
{
cout << "Base构造函数" << endl;
}
~Base()
{
cout << "Base析构函数" << endl;
}
};
class Son :public Base
{
public:
Son()
{
cout << "Son构造函数" << endl;
}
~Son()
{
cout << "Son析构函数" << endl;
}
};
void test01()
{
Son a;
}
int main()
{
test01();
return 0;
}
六.继承中的同名问题
1.同名成员属性处理
如果通过子类对象 访问到父类中的同名成员,需要加作用域。
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;
class Base
{
public:
Base()
{
m_A = 100;
}
void func()
{
cout << "Base - func()调用" << endl;
}
void func(int a)
{
cout << "Base - func(int a)调用" << endl;
}
int m_A;
};
class Son :public Base
{
public:
Son()
{
m_A = 200;
}
void func()
{
cout << "Son - func()调用" << endl;
}
int m_A;
};
void test01()
{
Son s;
cout << "Son 下 m_A = " << s.m_A << endl;
cout << "Base 下 m_A = " << s.Base::m_A << endl;
}
int main()
{
test01();
return 0;
}
2.同名成员函数处理
a.直接调用 调用是子类中的同名成员;
b.如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名函数;
c.如果想访问到父类中被隐藏的同名成员函数,需要加作用域。
class Base
{
public:
Base()
{
m_A = 100;
}
void func()
{
cout << "Base - func()调用" << endl;
}
void func(int a)
{
cout << "Base - func(int a)调用" << endl;
}
int m_A;
};
class Son :public Base
{
public:
Son()
{
m_A = 200;
}
void func()
{
cout << "Son - func()调用" << endl;
}
int m_A;
};
void test02()
{
Son s;
s.func();//直接调用 调用是子类中的同名成员
//如何调用到父类中的同名成员函数?
s.Base::func();
//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名函数
//如果想访问到父类中被隐藏的同名成员函数,需要加作用域
s.Base::func(100);
}
int main()
{
test02();
return 0;
}
七.同名静态成员
1.同名静态成员属性
class Base
{
public:
static int m_A;
static void func()
{
cout << "Base - static void func()" << endl;
}
static void func(int a)
{
cout << "Base - static void func(int)" << endl;
}
};
int Base::m_A = 100;
class Son :public Base
{
public:
static int m_A;
static void func()
{
cout << "Son - static void func()" << endl;
}
};
//在C++中,静态成员变量必须在类外部进行初始化,
//这主要是因为静态成员变量不属于任何一个类的实例,而是属于类本身。
int Son::m_A = 200;
void test01()
{
//通过对象访问
cout << "通过对象访问" << endl;
Son s;
cout << "Son 下 m_A = " << s.m_A << endl;
cout << "base 下 m_A = " << s.Base::m_A << endl;
//通过类名访问
cout << "通过类名访问" << endl;
cout << "Son 下 m_A = " << Son::m_A << endl;
//第一个::代表通过类名方式访问 第二个::代表访问父类作用
cout << "base 下 m_A = " << Son::Base::m_A << endl;
}
int main()
{
test01();
return 0;
}
2.同名静态成员函数
class Base
{
public:
static int m_A;
static void func()
{
cout << "Base - static void func()" << endl;
}
static void func(int a)
{
cout << "Base - static void func(int)" << endl;
}
};
int Base::m_A = 100;
class Son :public Base
{
public:
static int m_A;
static void func()
{
cout << "Son - static void func()" << endl;
}
};
//在C++中,静态成员变量必须在类外部进行初始化,
//这主要是因为静态成员变量不属于任何一个类的实例,而是属于类本身。
int Son::m_A = 200;
void test02()
{
Son s;
//通过对象名访问
cout << "通过对象访问" << endl;
s.func();
s.Base::func();
//通过类名访问
cout << "通过类名访问" << endl;
Son::func();
Son::Base::func();
//子类出现和父类同名静态成员函数,也会隐藏父类中所有同名成员函数
//如果想访问父类中被隐藏同名成员,需要加作用域
Son::Base::func(100);
}
int main()
{
test02();
return 0;
}
八.多继承
1.语法
举例:子类 需要继承Base1和Base2
语法:class 子类 :继承方式 父类 , 继承方式 父类,...
2.代码案例
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;
//多继承语法
class Base1
{
public:
Base1()
{
m_A = 100;
}
int m_A;
};
class Base2
{
public:
Base2()
{
m_A = 200;
}
int m_A;
};
//子类 需要继承Base1和Base2
//语法 class 子类 :继承方式 父类 , 继承方式 父类
class Son :public Base1, public Base2
{
public:
Son()
{
m_C = 300;
m_D = 400;
}
int m_C = 300;
int m_D = 400;
};
void test01()
{
Son s;
cout << "sizeof Son = " << sizeof(s) << endl;
//当父类中出现同名成员,需要加作用域区分
cout << "Base1::m_A = " << s.Base1::m_A << endl;
cout << "Base2::m_A = " << s.Base2::m_A << endl;
}
int main()
{
test01();
return 0;
}
3.总结
尽管多继承在某些情况下可能看起来很有用,但它通常不被推荐使用,原因如下:
- 复杂性增加:当使用多继承时,类的层次结构变得更加复杂,这可能导致代码更难理解和维护。同时,多继承可能导致菱形继承(diamond inheritance)问题,即一个类从两个父类继承,而这两个父类又有一个共同的父类。这种情况下,可能会导致多重继承和歧义。
- 歧义性问题:在多重继承的情况下,如果一个方法在多个父类中都存在,那么子类在调用这个方法时就会产生歧义。子类需要明确指定要调用哪个父类的方法,这可能会导致代码难以阅读和维护。
- 增加错误的可能性:多继承可能导致命名冲突和隐藏继承问题。例如,子类可能无意中覆盖了父类的方法,或者父类的方法可能在子类中产生不可预见的行为。
- 破坏封装性:多继承可能会破坏对象的封装性。子类可能会访问和修改它不应该访问或修改的父类的属性和方法。
- 替代方案:在许多情况下,可以通过其他方式实现多继承的功能,如接口(interface)、组合(composition)或混合(mixin)等。这些替代方案通常更加灵活,易于理解和维护。
因此,尽管多继承在某些特定情况下可能有用,但通常建议避免使用它,而是寻找其他更简洁、更易于理解和维护的解决方案。
九.菱形继承
1.介绍
菱形继承(Diamond Inheritance)是面向对象编程中多继承的一种特殊情况,它涉及到一个类从两个不同的类继承,而这两个类又都继承自同一个基类。这种情况下的类层次结构图形成了一个菱形的形状,因此得名。
具体来说,在菱形继承中:
有一个顶层基类(Top Base Class)。
有两个中间层类(Middle Classes),它们都继承自顶层基类。
有一个底层派生类(Bottom Derived Class),它同时继承这两个中间层类。
2.问题产生
底层派生类实际上间接地继承了顶层基类的两份拷贝。在某些编程语言中,这可能会导致问题,特别是如果顶层基类包含数据成员时。这是因为底层派生类可能会包含顶层基类数据成员的两个不同实例,这通常不是程序员所期望的。
这个问题在C++中尤为突出,因为C++支持多继承,并且默认情况下,每个基类在派生类中都有自己的实例。这可能会导致数据冗余和歧义,因为同一个基类的成员可能会在派生类中存在多个副本。
3.解决办法
为了解决这个问题,C++提供了虚继承(Virtual Inheritance)的机制。通过使用虚继承,可以确保在菱形继承中顶层基类只被包含一次,从而避免了数据冗余和歧义。
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;
//菱形继承
//动物类
class Animal
{
public:
int m_Age;
};
//利用虚继承 解决菱形继承文本问题
//继承之前 加上关键字 virtual 变为虚继承
//Animal类称为 虚基类
//羊类
class Sheep :virtual public Animal
{
};
//驼类
class Tuo :virtual public Animal
{
};
//羊驼类
class SheepTuo :public Sheep, public Tuo
{
};
void test01()
{
SheepTuo st;
st.Sheep::m_Age = 18;
st.Tuo::m_Age = 28;
//当菱形继承,两个父类拥有相同数据,需要加以作用域区分
//这份数据我们知道,只有一份可以,菱形继承导致数据有两份
//使用虚继承后,数据变为一份
cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
}
int main()
{
test01();
return 0;
}
4.虚继承原理
使用虚继承后,SheepTuo会生成一个虚基表(运用vs命令提示符查询,本文第五点中的方法)如下图所示。
虚基类表
C++中支持虚继承(Virtual Inheritance)机制的一部分。当使用虚继承时,编译器会生成一个虚基类表来帮助解决菱形继承(Diamond Inheritance)问题,确保基类在派生类中只被实例化一次。
虚基类表工作原理
在C++中,当使用虚继承时,每个包含虚基类的对象都会包含一个指向虚基类表的指针。这个表存储了关于虚基类实例的位置信息。由于每个虚基类可能在对象内存布局中的位置不同,因此需要通过这个表来动态地找到虚基类的实例。
在运行时,当通过派生类对象访问虚基类的成员时,编译器会首先查找虚基类表,以确定虚基类实例的位置,然后再访问相应的成员。
继承的有关知识暂时分享到这里,感谢观看。
看到这里,不妨点个攒,关注一下吧!
最后,谢谢你的观看