菱形继承
概念
菱形继承又称为钻石继承,由公共基类派生出多个中间子类,又由中间子类共同派生出汇聚子类。汇聚子类会得到中间子类从公共基类继承下来的多份成员
格式
A --------公共基类/ \B C ------- 中间子类\ /D --------汇聚子类
存在的问题
汇聚子类会得到中间子类从公共基类继承下来的多份成员,造成空间浪费,这是完全没有必要的,而且还会对公共基类的成员多次初始化或释放
解决办法
采取虚继承
代码示例
#include <iostream>using namespace std;//封装公共基类 家具 类
class Jiaju
{
private:string color;
public://无参构造Jiaju() {cout << "家具的无参构造函数" << endl;}//有参构造Jiaju(string n):color(n){cout << "家具的有参构造函数" << endl;}
};//中间子类
//封装 沙发的类
class Sofa:public Jiaju
{
private:string sitting;
public://无参构造Sofa() {cout << "沙发的无参构造" << endl;}//有参构造函数Sofa(string s,string c):Jiaju(c),sitting(s){cout << "沙发的有参构造" << endl;}void display(){cout << sitting << endl;}
};//中间子类
//封装 床 类
class Bed:public Jiaju
{
private:string sleep;public://无参Bed() {cout << "床的无参构造" << endl;}//有参Bed(string s,string c):Jiaju(c),sleep(s){cout << "床的有参构造" << endl;}void display(){cout << sleep << endl;}
};//汇聚子类
//封装 沙发床类 继承于沙发 和 床
class Sofa_Bed:public Bed,public Sofa
{
private:int w;
public://Sofa_Bed(){cout << "沙发床的无参构造" << endl;}//有参构造Sofa_Bed(string sit, string s, int w,string c):Bed(s,c),Sofa(sit,c),w(w){cout << "沙发床的有参构造" << endl;}
};int main()
{
// Sofa_Bed s;Sofa_Bed s1("可坐","可躺",123,"pink");return 0;
}
虚继承
作用
可以让汇聚子类只保留一份,中间子类从公共基类继承下来的成员
格式
在中间子类的继承方式前 加上 关键字virtual
class 类名 : virtual 继承方式 类名 //中间子类
{中间子类的拓展;
};
注意
- 中间子类虚继承公共基类后,汇聚子类的初始化列表,先调用中间子类的有参构造函数,中间子类再调用公共基类的有参构造函数;虚继承之后,只保留一份中间子类从公共基类继承下来的有参构造函数,意味着不知道调用哪一个中间子类继承下来的公共基类的构造函数,这样就会默认调用公共基类的无参构造函数
- 如果汇聚子类想要对公共基类的数据成员初始化,需要显性调用公共基类的构造函数
代码示例
#include <iostream>using namespace std;//封装公共基类 家具 类
class Jiaju
{
private:string color;
public://无参构造Jiaju() {cout << "家具的无参构造函数" << endl;}//有参构造Jiaju(string n):color(n){cout << "家具的有参构造函数" << endl;}
};//中间子类
//封装 沙发的类
class Sofa:virtual public Jiaju //中间子类虚继承公共基类
{
private:string sitting;
public://无参构造Sofa() {cout << "沙发的无参构造" << endl;}//有参构造函数Sofa(string s,string c):Jiaju(c),sitting(s){cout << "沙发的有参构造" << endl;}void display(){cout << sitting << endl;}
};//中间子类
//封装 床 类
class Bed:virtual public Jiaju //中间子类虚继承公共基类
{
private:string sleep;public://无参Bed() {cout << "床的无参构造" << endl;}//有参Bed(string s,string c):Jiaju(c),sleep(s){cout << "床的有参构造" << endl;}void display(){cout << sleep << endl;}
};//汇聚子类
//封装 沙发床类 继承于沙发 和 床
class Sofa_Bed:public Bed,public Sofa
{
private:int w;
public://Sofa_Bed(){cout << "沙发床的无参构造" << endl;}//有参构造Sofa_Bed(string sit, string s, int w,string c):Jiaju(c),Bed(s,c),Sofa(sit,c),w(w) //需要在汇聚子类中显性调用公共基类的有参构造函数{cout << "沙发床的有参构造" << endl;}
};int main()
{
// Sofa_Bed s;Sofa_Bed s1("可坐","可躺",123,"pink");return 0;
}
多态
类的三大属性:封装、继承、多态
静态多态(函数重载)、动态多态(运行时)
- 多态:一种形式的多种状态(多态就像一个人,可以有很多角色或者行为,取决于不同情境)
- 父类的指针或引用,指向或初始化子类对象,调用子类对父类重写的函数,进而展开子类的功能
函数重写
- 必须有继承关系
- 子类和父类有同名同类型的函数
- 父类中的该函数必须是虚函数
虚函数
- 在函数前加上virtual---->该函数是虚函数
- 虚函数满足虚继承,也就是说父类中该函数时虚函数,继承到子类中,该函数依旧是虚函数,如果子类再被继承,"孙类"中该函数还是虚函数
代码示例
#include <iostream>using namespace std;// 封装 周 这个类
class Zhou
{
private:string name;int age;
public://无参构造Zhou() {}//有参构造函数Zhou(string n, int a):name(n),age(a){}//virtual void speek() //表示该函数是虚函数{cout << "阿巴阿巴。。" << endl;}
};//封装 周老师 类,继承于周类
class Teacher:public Zhou
{
private:int id;public://无参构造Teacher() {}//有参构造Teacher(string n, int a, int d):Zhou(n,a),id(d){}//void speek(){cout << "看我,上多态,认真听讲" << endl;}
};//封装 游戏玩家 类 继承于Zhou类
class Player:public Zhou
{
private:string game;
public://。。Player() {}//有参构造Player(string name, int age, string g):Zhou(name,age),game(g){}//void speek(){cout << "稳住,我们能赢" << endl;}
};int main()
{Teacher t("zhangsan",34,1001);Zhou *p; //父类的指针p = &t; //父类的指针,指向子类对象 相当于承当老师这个角色p->speek(); // 上课Player g("lisi",45,"王者");p = &g; //此时是游戏玩家这个角色p->speek();return 0;
}
赋值兼容规则
父类的指针或引用,指向或初始化子类的对象
多态中函数重写的原理
- 类中有虚函数时,类中就会有一个虚指针,虚指针也满足继承
- 虚指针在类的最前面,虚指针指向了一个虚函数表,虚函数表里记录了虚函数,包括子类对父类重写的函数
- 虚指针和虚函数表是实现多态的重要机制
虚机构函数
虚析构函数用来解决父类指针指向子类时,父类指针释放,导致子类自拓展的空间没有得到释放
格式
virtual 析构函数
{}
代码示例
#include <iostream>using namespace std;//封装 人 类
class Person
{
private:string name;
public://Person() {}//有参构造函数Person(string n):name(n){}virtual ~Person() //虚析构函数 满足继承{cout << "Person::析构函数" << endl;}
};//封装 学生 继承于人
class Stu:public Person
{
private:int id;
public://Stu(){}//有参构造Stu(string n , int i):Person(n),id(i){}~Stu(){cout << "Stu::析构函数" << endl;}
};int main()
{Person *p = new Stu("张三",1001);delete p; //如果没有虚析构函数,进行释放p是,子类自己拓展的空间就没有释放--内存泄漏return 0;
}
纯虚函数
当父类中虚函数被子类用来重写,且没有定义的意义,这个时候,一般把父类中的虚函数设置成纯虚函数
格式
virtual 函数返回值类型 函数名(形参列表) = 0; //纯虚函数
抽象类
抽象类一般是用来被继承的,它不能实例化出具体的一个对象,抽象类中至少有一个纯虚函数
如果子类没有对父类的纯虚函数重写,那么子类也是抽象类,不能实例化对象
#include <iostream>using namespace std;//..
class A //抽象类
{
private:int a;
public:A() {}virtual void show() = 0; //纯虚函数
};class B:public A
{
public:B() {} void show() //如果子类没有对父类的纯虚函数重写,那么子类也是抽象类,不能实例化一个对象{}
};int main()
{B b;return 0;
}
模板
- 模板是一个通用的摸具。大大提高了代码的复用性
- C++的另外一个编程思想—>泛式编程:主要利用的技术就是模板
- C++的两个重要模板机制:函数模板和类模板
模板特点
- 模板不能直接使用,只是一个框架
- 模板不是万能的
函数模板
作用
建立一个通用的函数,其返回值类型或者形参类型不具体制定,用一个虚拟的类型来代替
格式
template <typename T>
函数的声明或定义
template ----->表示开始创建模板
typename -->表明后面的符号是数据类型,typename 也可以用class代替
T ----->表示数据类型,可以其他符号代替
代码示例
#include <iostream>using namespace std;//创建函数模板
template <typename T>
void fun(T &a, T &b)
{T temp;temp = a;a = b;b = temp;
}//void fun(int &a, int &b)
//{
// int temp;
// temp = a;
// a = b;
// b = temp;//}
//void fun(double &a, double &b)
//{
// double temp;
// temp = a;
// a = b;
// b = temp;
//}//void fun(char &a, char &b)
//{
// char temp;
// temp = a;
// a = b;
// b = temp;
//}int main()
{int a = 10, b = 20;fun(a,b);cout << a << " " << b << endl;double c = 1.3, d = 1.4;fun(c, d);cout << c << " " << d << endl;return 0;
}
练习
以下是一个简单的比喻,将多态概念与生活中的实际情况相联系:
比喻:动物园的讲解员和动物表演
想象一下你去了一家动物园,看到了许多不同种类的动物,如狮子、大象、猴子等。现在,动物园里有一位讲解员,他会为每种动物表演做简单的介绍。
在这个场景中,我们可以将动物比作是不同的类,而每种动物表演则是类中的函数。而讲解员则是一个基类,他可以根据每种动物的特点和表演,进行相应的介绍。
具体过程如下:
定义一个基类 Animal,其中有一个虚函数 perform(),用于在子类中实现不同的表演行为。
#include <iostream>using namespace std;//定义一个基类
class Animal
{
public:void virtual perform() = 0;
};
class Lion:public Animal
{
public:void perform(){cout << "狮子表演" << endl;}
};
class Elephant:public Animal
{
public:void perform(){cout << "大象表演" << endl;}
};
class Monkey:public Animal
{
public:void perform(){cout << "猴子表演" << endl;}
};
int main()
{Animal *p = new Monkey;p->perform();return 0;
}
2.用函数模板实现不同数据类型的交换功能。
#include <iostream>using namespace std;
template <typename T>
void fun(T &a,T &b)
{T temp;temp = a;a = b;b = temp;
}
int main()
{int a = 10,b = 99;fun(a,b);cout << a << b << endl;double c = 9.9,d = 1.11;fun(c,d);cout << c << d << endl;return 0;
}