006__C++类的特殊用法
一、类的组合
1)类的组合的概念
类的组合即:在一个类中再内嵌一个类或多个类注意:这里的内嵌类不能是自己同名的类,否则会一直申请内存导致内存不足,报错类的组合这个语法,是在开发的时候设计代码框架 需要考虑是否要不要用的问题。
2)类本体和内嵌类的构造函数和析构函数调用顺序
代码验证的例子:
#include <iostream>
#include <string>using namespace std;//验证类的组合中构造和析构的顺序问题:结果为:先内构本构本析内析class demo
{
public:demo(){cout<<"我是内嵌的基构函数"<<endl;}~demo(){cout<<"我是内嵌的析构函数"<<endl;}
};class DEMO
{
public:class demo d1;DEMO(){cout<<"我是本体类的基构函数"<<endl;}~DEMO(){cout<<"我是本体类的析构函数"<<endl;}
};int main()
{class DEMO D1;return 0;
}
运行结果:
3)关于初始化内嵌类对象的写法:显式调用内嵌类的构造函数
即可以通过本体的构造函数的初始化列表去主动选择调用内嵌类的构造函数
代码示例:
#include <iostream>
#include <string>using namespace std;//怎么在类的本体中初始化内嵌类的成员?还是使用本体的构造函数中的初始化列表实现class b
{
public:int date0;b(){}//这个是为能使b1(set_date_b)显示调用用的b(int set_date0):date0(set_date0){}
};class B
{
public:int date;B(){}//构造函数中初始化内嵌类,这里注意的顺序由成员的定义顺序来决定的//因为是先int date;,后 class b b1;//而继承中的顺序是因为先基构派构派析基析的顺序,先基的,所以是先写基的初始化B(int set_date, int set_date_b):date(set_date),b1(set_date_b){}class b b1; //在本体中内嵌类void Show(){cout<<this->date<<","<<this->b1.date0<<endl;}};int main()
{class B B0(11,22);B0.Show();return 0;
}
运行结果:
4)关于初始化内嵌类对象的写法:
方法1:可通过类本体的构造函数的初始化列表初始化内嵌类
#include <iostream>
#include <string>using namespace std;//这个代码是:通过本体的 构造函数的初始化列表 来 调用 内嵌的 拷贝函数 初始化 内嵌类的成员class demo
{
public:demo(){}demo(class demo const & tmp) //内嵌类的拷贝函数,给内嵌类的成员进行赋值{this->date = tmp.date;}int date;
};class DEMO
{
public:DEMO(){}//这里传入的数据用引用可以防止副本浪费资源,然后初始化调用显示调用d1(tmp1)内嵌的第二个构造函数来给demo里的date赋值DEMO(int set_Date,class demo &tmp1):Date(set_Date),d1(tmp1){}int Date; //通过本体的构造函数的初始化列表进行赋值初始化class demo d1; //为了在本体里初始化 调用拷贝函数
};int main()
{//实例化一个内嵌类的类对象class demo d1;d1.date = 222;//实例化本体的类对象class DEMO D1(111,d1);cout<<D1.Date<<","<<D1.d1.date<<endl;return 0;
}
运行结果:
方法2:通过本体的拷贝构造函数初始化列表来调用内嵌的拷贝构造函数来初始化内嵌类的成员。
代码示例:
#include <iostream>
#include <string>using namespace std;//这个代码是:方法2通过本体的 拷贝构造函数的初始化列表 来 调用 内嵌的 拷贝函数 初始化 内嵌类的成员class demo
{
public:demo(){}demo(int set_date):date(set_date){}demo(class demo const & tmp) //内嵌类的拷贝函数,给内嵌类的成员进行赋值{this->date = tmp.date;}int date;
};class DEMO
{
public:DEMO(){}DEMO(int set_Date):Date(set_Date){}DEMO(class DEMO const & D1, class demo const & d1):d0(d1){this->Date = D1.Date;}int Date; //通过本体的构造函数的初始化列表进行赋值初始化class demo d0; //为了在本体里初始化 调用拷贝函数
};int main()
{class demo dd(110);class DEMO DD(119);class DEMO D_all(DD,dd);cout<<D_all.Date<<","<<D_all.d0.date<<endl;return 0;
}
运行结果:
二、类的多继承
1)基本写法
class 派生类:继承方式 基类1,继承方式 基类2 继承多个基类,让派生类的行为和属性更加丰富。
{变量成员 属性函数成员 行为
};多继承在开发中的应用场景:1:纯软软件开发羊驼:派生类 继承羊(基类1) 和继承 骆驼(基类2)2:嵌入式软件开发视频类 继承 音频类 (基类1)视频类 继承 像素类 (基类2)3:纯软软件开发虎狮兽继承 老虎虎狮兽继承 狮子4:纯软软件开发近战武器类刀远程武器类步枪带刺刀的步枪:继承近战武器类 继承远程武器类多继承会让 派生类 的 属性和行为 更加丰富。注意:派生类继承 多个基类的的继承顺序(从左往右),决定派生类的构造函数的初始化列表中调用基类的构造函数的顺序。
代码例子(鸡蛋):
#include <iostream>
#include <string>using namespace std;//多继承的一个例子:鸭蛋的示例(蛋黄,蛋白,蛋)class egg_white
{
public:float egg_white_weight;egg_white(){}egg_white(float w):egg_white_weight(w){}
};class egg_yellow
{
public:float egg_yellow_weight;egg_yellow(){}egg_yellow(float w1):egg_yellow_weight(w1){}
};class egg:public egg_white,egg_yellow
{
public:float egg_shell_weight; //蛋壳float egg_all_weight; //总重量egg(){}egg(float w2,float w3, float w4):egg_white(w2),egg_yellow(w3),egg_shell_weight(w4){egg_all_weight = this->egg_shell_weight+egg_white::egg_white_weight+egg_yellow::egg_yellow_weight;}void Show_date(){cout<<"蛋壳的重量为:"<<this->egg_shell_weight<<endl;cout<<"蛋白的重量为:"<<egg_white::egg_white_weight<<endl;cout<<"蛋黄的重量为:"<<egg_yellow::egg_yellow_weight<<endl;cout<<"鸭蛋的总重量为:"<<this->egg_all_weight<<endl;}
};int main()
{//实例化一个鸭蛋class egg duck_egg(30.6,34.5,12.4);duck_egg.Show_date();return 0;
}
运行结果:
2)构造函数的调用顺序和析构函数的调用顺序是?
通过下面的代码验证可知是:
基类1构--基类2构--派类构--派类析--基类2析--基类1析
看代码例子验证:
#include <iostream>
#include <string>using namespace std;//多继承中的基构,析构的顺序是怎么样的?由前面推理可知
//首先是继承,所以先基类(但基类也有顺序则按顺序即可)(注意这里的先基类是一个括号类的意思)
//即括号里是基构--析构这么一个模型。class egg_white
{
public:float egg_white_weight;egg_white(){}egg_white(float w):egg_white_weight(w){cout<<"我是蛋白的基构函数"<<endl;}//注意:析构函数一般放在类的最下面~egg_white(){cout<<"我是蛋白的析构函数"<<endl;}
};class egg_yellow
{
public:float egg_yellow_weight;egg_yellow(){}egg_yellow(float w1):egg_yellow_weight(w1){cout<<"我是蛋黄的基构函数"<<endl;}~egg_yellow(){cout<<"我是蛋黄的析构函数"<<endl;}
};class egg:public egg_white,egg_yellow
{
public:float egg_shell_weight; //蛋壳float egg_all_weight; //总重量egg(){}egg(float w2,float w3, float w4):egg_white(w2),egg_yellow(w3),egg_shell_weight(w4){egg_all_weight = this->egg_shell_weight+egg_white::egg_white_weight+egg_yellow::egg_yellow_weight;cout<<"我是蛋的基构函数"<<endl;}void Show_date(){cout<<"蛋壳的重量为:"<<this->egg_shell_weight<<endl;cout<<"蛋白的重量为:"<<egg_white::egg_white_weight<<endl;cout<<"蛋黄的重量为:"<<egg_yellow::egg_yellow_weight<<endl;cout<<"鸭蛋的总重量为:"<<this->egg_all_weight<<endl;}~egg(){cout<<"我是蛋的析构函数"<<endl;}
};int main()
{//实例化一个鸭蛋class egg duck_egg(30.6,34.5,12.4);duck_egg.Show_date();return 0;
}
运行结果:
三、棱形继承和虚基类
1)虚基类概念
虚基类的作用是解决在多重继承中重复继承的二义性问题,重复继承常见出现在菱形继承中。
2)格式
class 子类名称:virtual 继承方式 父类名
{};
3) 菱形继承
设计好类,实现类的菱形继承,验证实例化类对象D之后,构造函数的调用顺序。譬如:猫科类动物 (A类)狮子(B类) 老虎(C类)虎狮兽(D类)
示例代码:
#include <iostream>
#include <string>using namespace std;//棱形继承中虚基类的作用:解决继承中访问老基类的路线的二义性问题。class cat_animal
{
public:cat_animal(){}cat_animal(string f1, string f2):feature_1(f1),feature_2(f2){}string feature_1; //爪子string feature_2; //胡须
};class tiger:virtual public cat_animal
{
public:tiger(){}tiger(string t1, string t2):tiger_feature_1(t1),tiger_feature_2(t2){}string tiger_feature_1; //有条纹string tiger_feature_2; //王字印记
};class lion:virtual public cat_animal
{
public:lion(){}lion(string l1, string l2):lion_feature_1(l1),lion_feature_2(l2){}string lion_feature_1; //爆炸头string lion_feature_2; //体型大
};class tigon:public tiger, public lion //每一个都要写继承方式,不然默认时私有继承
{
public:tigon(){}tigon(string ti1, string ti2):tigon_feature_1(ti1),tigon_feature_2(ti2){//老虎类特性this->tiger::tiger_feature_1 = "王字印记";this->tiger::tiger_feature_2 = "有条纹";//狮子类特性this->lion::lion_feature_1 = "爆炸头";this->lion::lion_feature_2 = "体型大";//老基类的猫科动物的特性,如果没加virtual会报二义性的错误,所以前面的老虎,狮子要加virtual,虚基类继承this->tiger::cat_animal::feature_1 = "爪子";this->lion::cat_animal::feature_2 = "胡须";}string tigon_feature_1; //先天性弱string tigon_feature_2; //更敏捷};int main()
{//实例化虎狮兽class tigon ti("先天性弱", "更敏捷");//猫科的特性cout<<"特性1:"<<ti.tiger::cat_animal::feature_1<<endl;cout<<"特性2:"<<ti.tiger::cat_animal::feature_2<<endl;//老虎的特性cout<<"特性3:"<<ti.tiger::tiger_feature_1<<endl;cout<<"特性4:"<<ti.tiger::tiger_feature_2<<endl;//狮子的特性cout<<"特性5:"<<ti.lion::lion_feature_1<<endl;cout<<"特性6:"<<ti.lion::lion_feature_2<<endl;//虎狮兽的特性cout<<"虎狮兽自己的特性1:"<<ti.tigon_feature_1<<endl;cout<<"虎狮兽自己的特性2:"<<ti.tigon_feature_2<<endl;return 0;
}
运行结果:
4)菱形继承中的构造顺序(重要⭐)
示例代码:
#include <iostream>
#include <string>using namespace std;//棱形继承中虚基类的作用:解决继承中访问老基类的路线的二义性问题。class cat_animal
{
public:cat_animal(){cout<<"我是猫的构造函数"<<endl;}~cat_animal(){cout<<"我是猫的析构函数"<<endl;}string feature_1; //爪子string feature_2; //胡须
};class tiger:virtual public cat_animal
{
public:tiger(){cout<<"我是老虎的构造函数"<<endl;}~tiger(){cout<<"我是老虎的析构函数"<<endl;}string tiger_feature_1; //有条纹string tiger_feature_2; //王字印记
};class lion:virtual public cat_animal
{
public:lion(){cout<<"我是狮子的构造函数"<<endl;}~lion(){cout<<"我是狮子的析构函数"<<endl;}string lion_feature_1; //爆炸头string lion_feature_2; //体型大
};class tigon:public tiger, public lion //每一个都要写继承方式,不然默认时私有继承
{
public:tigon(){cout<<"我是虎狮兽的构造函数"<<endl;}~tigon(){cout<<"我是虎狮兽的析构函数"<<endl;}string tigon_feature_1; //先天性弱string tigon_feature_2; //更敏捷
};int main()
{class tigon ti;return 0;
}
两次的运行结果如下:
① 没有虚基继承的 和 有虚基继承,不难推出没有虚基继承,多出了一次 老基构 基构 和 基析 老基析
总结菱形构造和析构调用顺序
没有虚基继承:这里会多出一次猫构和猫析(因为会访问猫科动物两次)猫构 --> 基1构 --> 猫构 --> 基2构 --> 派构派析 --> 基2析 --> 猫析 --> 基1析 --> 猫析有虚基继承:猫构 --> 基1构 --> 基2构 --> 派构派析 --> 基2析 --> 基1析 --> 猫析如果没有虚基继承,派生类在多重继承时会重复实例化基类空间,这会导致同名对象的重复存在,
甚至会导致访问基类成员时出现二义性问题。虚基继承的引入确保了基类只会有一个实例,
从而解决了这些问题。
四、虚函数(类继承中的一种函数)
后面的知识点有空会补上...................