目录
一、菱形继承
1.1 概念
1.2 格式
二、虚继承
2.1 作用
2.2 格式
2.3注意
三、多态
3.1函数重写
3.2 虚函数
3.3 赋值兼容规则
3.4 多态中,函数重写的原理
3.5 虚析构函数
3.5.1 格式
3.6 纯虚函数
3.6.1格式
四、抽象类
五、模板
5.1模板的特点
5.2 函数模板
5.2.1作用
5.2.2 格式
六、练习
1.定义一个基类 Animal,其中有一个虚函数 perform(),用于在子类中实现不同的表演行为。
结果为:
2.用函数模板实现不同数据类型的交换功能。
结果为:
一、菱形继承
1.1 概念
菱形继承又称为钻石继承,由公共基类派生出多个中间子类,又由多个中间子类共同派生出汇聚子类。汇聚子类会得到,中间子类从公共基类继承下来的多份成员。
问题:
汇聚子类会得到,中间子类从公共基类继承下来的多份成员,造成空间浪费,没有必要,还会对公共基类的成员多次初始化,或释放。
1.2 格式
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;
}
二、虚继承
2.1 作用
可以让汇聚子类只保留一份 中间子类从公共基类继承下来的成员。
2.2 格式
在中间子类的继承方式前 加上 virtual
class 类名 : virtual 继承方式 类名 //中间子类
{中间子类的拓展;
};
2.3注意
1> 中间子类虚继承公共基类后,汇聚子类的初始化列表,先调用中间子类的有参构造函数,中间子类再调用公共基类的有参构造函数,但是呢,虚继承之后,只保留一份中间子类从公共基类继承下来的有参构造函数,意味着不知道调用哪一个中间子类继承下来的公共基类的构造函数,这样就会默认调用公共基类的无参构造函数。
2> 如果汇聚子类想要对公共基类的数据成员初始化,需要显性调用公共基类的构造函数。
#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;
}
三、多态
类的三大属性:封装、继承、多态
静态多态(函数重载)、动态多态(运行时)
一种形式多种状态
多态就像一个人,可以有很多角色或者行为,取决于不同情境
父类的指针或者引用,指向或初始化子类的对象,调用子类对父类重写的函数,进而展开子类的功能。
3.1函数重写
1> 必须有继承关系
2> 子类和父类有同名同类型的函数
3> 父类中的该函数必须是虚函数
3.2 虚函数
1> 在函数前加上 virtual ---->该函数是虚函数
2> 虚函数满足继承,也就是说父类中该函数是虚函数,继承到子类中,该函数依旧是虚函数,如果子类再被继承,“孙类”中该函数还是虚函数....
#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;
}
3.3 赋值兼容规则
父类的指针或者引用,指向或初始化子类的对象
3.4 多态中,函数重写的原理
- 类中有虚函数时,类里就会有一个虚指针,虚指针也满足继承
- 虚指针在类的最前面,虚指针指向了一个虚函数表,虚函数表里记录了虚函数,包括子类对父类重写的函数。
- 虚指针和虚函数表是实现多态的重要机制。
3.5 虚析构函数
虚析构函数用来解决 父类指针指向子类时,父类指针释放,导致子类自拓展的空间没有得到释放
3.5.1 格式
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;
}
3.6 纯虚函数
当父类中虚函数被子类用来重写,且没有定义的意义,这个时候,一般把父类中的虚函数设置成纯虚函数。
3.6.1格式
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++中有两个重要的模板机制:函数模板和类模板
5.1模板的特点
1> 模板不能直接使用,它只是一个框架
2> 模板不是万能的
5.2 函数模板
5.2.1作用
建立一个通用的函数,其返回值类型或者形参类型 不具体制定,用一个虚拟的类型来代替。
5.2.2 格式
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;
}
六、练习
1.定义一个基类 Animal,其中有一个虚函数 perform(),用于在子类中实现不同的表演行为。
以下是一个简单的比喻,将多态概念与生活中的实际情况相联系:
比喻:动物园的讲解员和动物表演
想象一下你去了一家动物园,看到了许多不同种类的动物,如狮子、、猴子等。现在,动物园里有一位讲解员,他会为每种动物表演做简单的介绍。
在这个场景中,我们可以将动物比作是不同的类,而每种动物表演则是类中的函数。而讲解员则是一个基类,他可以根据每种动物的特点和表演,进行相应的介绍。
#include <iostream>using namespace std;class Animal{
protected:string species;
public:Animal(){}Animal(string sp): species(sp){}virtual void perform(){}
};class lion:public Animal{
public:lion(){}lion(string species):Animal(species){}void perform(){cout << Animal::species << "跳火圈" << endl;}
};class elephant:public Animal{
public:elephant(){}elephant(string sp):Animal(sp){}void perform(){cout << Animal::species << "踩背" << endl;}
};class monkey:public Animal{
public:monkey(){}monkey(string sp):Animal(sp){}void perform(){cout << Animal::species << "偷桃" << endl;}
};int main()
{lion it1("辛巴");Animal *p;p = &it1;p->perform();elephant it2("非洲象");p = &it2;p->perform();monkey it3("峨眉山猴子");p = &it3;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;int b = 50;char c = 'C';char d = 'D';fun(&a,&b);fun(&c,&d);cout << "a = " << a << " b = " << b << endl;cout << "c = " << c << " d = " << d << endl;return 0;
}