多继承
概念
一个类由多个类共同派生。
格式:
class 类名:继承方式1 类名1,继承方式2 类名2,......继承方式n 类名n
{
子类的拓展;
};
例:
#include <iostream>
using namespace std;
class Sofa
{
private:
string color;
public:
//无参
Sofa()
{
cout << "sofa无参" << endl;
}
//有参
Sofa(string c):color(c)
{
cout << "sofa有参" << endl;
}
void show()
{
cout << color << endl;
}
};
class Bed
{
private:
string sleep;
public:
//无参
Bed()
{
cout << "bed无参" << endl;
}
Bed(string s):sleep(s)
{
cout << "bed有参" << endl;
}
//有参
void show()
{
cout << sleep << endl;
}
};
class Sofabed:public Sofa,public Bed
{
private:
int money;
public:
//无参
Sofabed()
{
cout << "sofabed无参" << endl;
}
Sofabed(string s,string p,int m):Sofa(s),Bed(p),money(m)
{
cout << "sofabed有参" << endl;
}
//有参
void show()
{
Sofa::show();
Bed::show();
cout << money << endl;
}
};
int main()
{
Sofabed sb("yellow","可睡",65);
return 0;
}
注意:
- 多继承,存在多个父类,那么调用构造函数的先后顺序,子类一定是后构造,而多个父类之间的吸纳后顺序和初始化列表的先后顺序没有关系,和继承的先后顺序有关,所以初始化列表调用父类的构造函数时,注意先后顺序
菱形继承
菱形继承又称为钻石继承,由公共基类派生出多个中间子类,又由这些中间子类共同派生汇聚子类,汇聚子类会得到多份中间子类从公共基类继承下来的数据成员
问题:
- 汇聚子类会得到多份从公共基类继承下来的数据成员。造成空间浪费。
- 会对公共基类的数据成员进行多次初始化或者多次析构。(多次调用构造函数,析构函数)
格式:
虚继承
作用:
- 解决菱形继承存在的问题
- 使得汇聚子类只得到一份中间子类从公共基类继承下来的数据成员
格式:
在中间子类的继承方式前加上:virtual
class 类名:virtual 继承方式 类型 //中间子类
{
中间子类的拓展;
}
#include <iostream>
using namespace std;
// 封装 家具 类
class Jiaju // -------->公共基类
{
private:
int w; //重量
public:
Jiaju() {cout << "Jiaju::无参构造函数" << endl;}
Jiaju(int w):w(w)
{
cout << "Jiaju::有参构造函数" << endl;
}
~Jiaju()
{
cout << "Jiaju::析构函数" << endl;
}
};
//封装 沙发 类, 共有继承于家具类
class Sofa:virtual public Jiaju // 虚继承 ------>中间子类
{
private:
string sitting;
public:
Sofa() { cout << "Sofa::无参构造函数" << endl;}
Sofa(string sit, int w):Jiaju(w),sitting(sit)
{
cout << "Sofa::有参构造函数" << endl;
}
~Sofa()
{
cout << "Sofa::析构函数" << endl;
}
void show()
{
cout << sitting << endl;
}
};
//封装 床 类, 共有继承于家具类
class Bed:virtual public Jiaju // 虚继承 -------->中间子类
{
private:
string sleep;
public:
Bed() {cout << "Bed::无参构造函数" << endl;}
Bed(string s, int w):Jiaju(w),sleep(s)
{
cout << "Bed::有参构造函数" << endl;
}
~Bed()
{
cout << "Bed::析构函数" << endl;
}
void show()
{
cout << sleep << endl;
}
};
//封装 沙发床 类 共有继承于沙发类 共有继承于床类
class SofaBed:public Sofa,public Bed //------->汇聚子类
{
private:
string color;
public:
SofaBed(){cout << "SofaBed::无参构造函数" << endl;}
//汇聚子类手动调用公共基类的有参构造函数
SofaBed(string sit, string sleep, string c, int w):Jiaju(w),Sofa(sit,w),Bed(sleep,w),color(c)
{
cout << "SofaBed::有参构造函数" << endl;
}
~SofaBed()
{
cout << "SofaBed::析构函数" << endl;
}
void show()
{
Sofa::show();
Bed::show();
cout << color << endl;
}
};
int main()
{
SofaBed bs("可坐","可躺","pink",100);
return 0;
}
注意
虚继承后,编译不知该保留哪个中间子类从公共基类继承下来的数据成员,所以编译会自动调用公共基类的无参构造函数,此时如果需要对公共基类的数据成员初始化,则需要在汇聚子类中手动调用公共基类的有参构造函数来完成数据成员的初始化工作。
多态
静态多态:函数重载(在编译时)
动态多态:(在运行时)
多态:一种形式多种形态,
概念
父类的指针或引用,可以指向或者初始化子类对象,调用子类对父类重写的函数,进而展开子类的功能
函数重写
必须有继承关系。
父类和子类中必须有同名同类型的函数
父类中该函数必须时虚函数
虚函数
在成员函数前加virtual,表示该函数就是虚函数
虚函数满足继承,如果父类中的虚函数被继承到子类中,该函数在子类中依然是虚函数,如果子类再被继承,则该函数依旧是虚函数
例:
#include <iostream>
using namespace std;
class Li
{
private:
string name;
int age;
public:
Li()
{}
Li(string n,int a):name(n),age(a)
{}
virtual void speak()
{
cout << "hello" << endl;
}
};
class Teacher:public Li
{
private:
int id;
public:
Teacher()
{}
Teacher(string n,int a,int i):Li(n,a),id(i)
{}
void speak()
{
cout << "学习" << endl;
}
};
class Player:public Li
{
private:
string game;
public:
Player()
{}
Player(string n,int a,string g):Li(n,a),game(g)
{}
void speak()
{
cout << "菜就多练" << endl;
}
};
int main()
{
Teacher t("li",18,1001);
Li *p = &t;
p->speak();
Player q("li",18,"yuan");
p = &q;
p->speak();
return 0;
}
赋值兼容规则
父类的指针或引用,可以指向或者初始化子类的对象
函数重写的原理
类中有虚函数,就会有一个虚指针
虚指针在类的最前面,指向了虚函数表,虚函数表记录了虚函数。
虚指针和虚函数表是实现多态的重要原理。
#include <iostream>
using namespace std;
class A
{
private:
int a;
public:
virtual void show() //虚函数
{
cout << &a << endl;
cout << this << endl;
}
};
int main()
{
//cout << sizeof(A) << endl; // 结果是16 有个虚指针
A b;
b.show();
return 0;
}
虚析构函数
由于赋值兼容规则,允许父类指针指向子类对象,但是父类指针只作用于子类从父类继承下来的那片空间,父类指针释放,也只释放子类从父类继承下来的那片空间,而子类自己拓展的空间没有得到释放,从而造成内存泄漏。(堆区)
作用:
正确引导子类释放自己的空间。
在父类的析构函数前加virtual。
纯虚函数
如果类中的虚函数只是用来被子类进行重写的,并且没有实现的意义,可以将该虚函数设置成纯虚函数。
格式:
virtual 函数返回值 函数名(形参列表) = 0; //纯虚函数
抽象类
纯虚函数所在的类,称为抽象类。抽象类不能具体实例化一个对象,主要是用来被继承的。
如果父类中的纯虚函数被继承到子类中,子类没有对该纯虚函数进行重写,则子类也是一个抽象类,不能实例对象
类中至少有一个纯虚函数的类称为抽象类
模板
模板就是建立一个通用的模具,大大提高了代码的复用性。
C++的另一个中编程思想:泛型编程,主要利用的技术就是模板。
C++提供了两个主要的模板机制:函数模板和类模板
特点:
模板是通用的,不是万能的
模板只是个框架,不能直接使用
函数模板
概念:
就是建立一个通用的函数,其函数的返回值,参数类型不具体指定,用一个虚拟类型来代替。
格式:
template<typename T>
函数定义
template ---> 表示开始创建模板
typename ---> 表示后面是虚拟类型,其typename可以用class替换
T ---> 虚拟类型名
注意:
使用类模板时,必须表明依赖模板的参数类型
#include <iostream>
using namespace std;
template <typename T,class N>
class Stu
{
private:
T name;
N age;
public:
Stu(){}
Stu(T n,N a):name(n),age(a)
{
}
void show()
{
cout << name << " " << age << endl;
}
};
int main()
{
Stu<string, int> s("张三", 18);//使用类模板时,必须表明依赖模板的参数类型
s.show();
Stu<string, char> s2("男", 'a');
s2.show();
Stu<int, int> s3(1001, 15);
s3.show();
return 0;
}
练习
以下是一个简单的比喻,将多态概念与生活中的实际情况相联系:
比喻:动物园的讲解员和动物表演
想象一下你去了一家动物园,看到了许多不同种类的动物,如狮子、大象、猴子等。现在,动物园里有一位讲解员,他会为每种动物表演做简单的介绍。
在这个场景中,我们可以将动物比作是不同的类,而每种动物表演则是类中的函数。而讲解员则是一个基类,他可以根据每种动物的特点和表演,进行相应的介绍。
具体过程如下:
定义一个基类 Animal,其中有一个虛函数perform(),用于在子类中实现不同的表演行为。
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void perform()
{
cout << "讲解" << endl;
}
};
class Lion:public Animal
{
private:
string color;
public:
Lion()
{}
Lion(string c):color(c)
{}
void perform()
{
cout << "狮子" << endl;
}
};
class Tiger:public Animal
{
private:
string color;
public:
Tiger()
{}
Tiger(string c):color(c)
{}
void perform()
{
cout << "老虎" << endl;
}
};
int main()
{
Lion t("yellow");
Animal *p = &t;
p->perform();
Tiger q("black");
p = &q;
p->perform();
return 0;
}