21天好习惯 第一期-19

多态

什么是多态?同一个事物在不同场景下的多种形态,多态能使函数的接口通用化。

面向对象三大特性之一

优点:代码组织结构清晰;可读性强;有利于前期和后期的扩展和维护(基于开闭原则:在面向对象领域中,开闭原则规定“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的”,这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。)

缺点:降低了程序运行效率(多态需要去找虚表的地址);空间浪费

静态多态与动态多态

1、静态多态:函数重载与运算符重载。静态多态是编译器在编译期间完成的,编译器会根据实参类型来选择调用合适的函数,如果有合适的函数可以调用就调,没有的话就会发出警告或者报错

2、动态多态:派生类和虚函数实现运行时多态。程序运行时根据基类的引用(指针)指向的对象来确定自己具体该调用哪一个类的虚函数。

#include <iostream>
using namespace std;
class animal
{
public:
    void speak()
    {
        cout << "animal speak" << endl;
    }
};
class cat : public animal
{
public:
    void speak()
    {
        cout << "cat speak" << endl;
    }
};
void create(animal &m)
{
    m.speak();
}
void test()
{
    cat CAT;
    create(CAT);
}
int main(void)
{
    test();
    system("pause");
    return 0;
}

此程序因为在编译期间就确定了函数的地址,所以调用了基类函数而没有调用派生类函数。

如果使用动态多态

#include <iostream>
using namespace std;
class animal
{
public:
    virtual void speak()//虚函数
    {
        cout << "animal speak" << endl;
    }
};
class cat : public animal
{
public:
    void speak()
    {
        cout << "cat speak" << endl;
    }
};
void create(animal &m)
{
    m.speak();
}
void test()
{
    cat CAT;
    create(CAT);
}
int main(void)
{
    test();
    system("pause");
    return 0;
}

在函数前都加virtual这个虚拟关键字,创建虚函数构成多态,程序在程序运行时通过基类的引用所指向的对象(派生类成员),来确立函数地址。所以调用了派生类函数而不是调用基函数。

所以动态多态的构成条件为:

1、基类中必须包含虚函数,并且派生类中一定要对基类中的虚函数进行重写
2、通过基类对象的指针或者引用调用虚函数。

重写
(a)基类中将被重写的函数必须为虚函数
(b)基类和派生类中虚函数的原型必须保持一致(返回值类型,函数名称以及参数列表),协变和析构函数(基类和派生类的析构函数是不一样的)除外

**协变:**基类(或者派生类)的虚函数返回基类(派生类)的指针(引用)

哪些函数不可以作为虚函数?

1)友元函数,它不是类的成员函数
2)全局函数
3)静态成员函数,它没有this指针
3)构造函数,拷贝构造函数,以及赋值运算符重载(可以但是一般不建议作为虚函数)

模板

模板是建立通用的模具,大大提高了复用性,是一种多态机制

在 C++ 中,模板分为函数模板和类模板两种。

  • 函数模板是用于生成函数;

  • 类模板则是用于生成类的。

函数模板

//格式
template <class 类型参数1, class类型参数2, ...>
返回值类型  模板名(形参表)
{
    函数体
}
/*其中 class可以用typename来替换。
template <typename 类型参数1, typename 类型参数2, ...>*/

用例:实现数据交换函数模板

template <class T>
void Swap(T & x, T & y)
{
    T tmp = x;
    x = y;
    y = tmp;
}

编译器在编译到调用函数模板的语句时,会根据实参的类型判断该如何替换模板中的类型参数,如果实参的类型为int类型,编译器则会将T自动换成int类型,自动生成函数。

编译器由模板自动生成函数的过程叫模板的实例化。由模板实例化而得到的函数称为模板函数。

  • 函数模板中可以有不止一个类型参数

  • 并非只能通过模板调用语句的实参来实例化模板中的类型参数,模板调用语句可以明确指明要把类型参数实例化为哪种类型。

    模板函数名称<数据类型>(参数1, 参数2……)
    
  • 在函数模板中,类型参数不但可以用来定义参数的类型,还能用于定义局部变量和函数模板的返回值。

  • 函数模板和普通函数同时存在时,优先调用函数模板。

类模板

类模板是关于一组类的一个特征抽象。

为什么需要类模板?

有时需要两个或多个模板,其功能相同,但数据类型不同,所以需要建立一个通用类。

语法:

template <typename T>
class Tclass{

};
//<class T>中class用来说明T是一个类型参数,T不一定是一个类,也可以是基本类型
  • 类模板用于实现类所需数据的类型参数化
  • 类模板在表示如数组、表、图等数据结构显得特别重要
  • 这些数据结构的表示和算法不受所包含的元素类型的影响
  • 类模板没有自动类型推导的使用方式
  • 类模板在模板参数列表中可以有默认参数
  • 类模板的成员函数在调用时创建(普通类的成员函数一开始就创建)

类模板对象做函数参数

  1. 传入时指定数据类型(最常用)

    void printPerson1(Person<string, int> &p)
    {
        p.showPerson();
    }
    
  2. 参数模板化

    template <class T1, class T2>
    void printPerson1(Person<T1, T2> &p)
    {
        p.showPerson();
    }
    
  3. 类模板传入

    template <class T>
    void printPerson1(T &p)
    {
        p.showPerson();
        cout << "T 的类型:" << typeid(T).name() << endl;//typeid()函数表示类型的名称
    }
    

类模板与继承

派生类继承基类的类模板,需要知道基类中的数据类型才能继承。

template <class T>
class Base
{
public:
    T m;
};
class person :public Base<int>
{
    public:
    //……
};

如果想灵活指定基类数据类型,派生类需要定义为类模板

template <class T>
class Base{
    public:
    T m;
};
template <class T1>
class person:public Base<T1>
{
    public:
    //……
};

类模板成员函数的类外实现

实现类模板成员函数的类外实现要加上类模板的参数列表

//类模板构造函数类外实现
template <class T1, class T2>
person<T1, T2>::person(T1 name, T2 age)
{
    this->name = name;
    this->age = age;
}
//类模板成员函数类外实现
template <class T1, class T2>
person<T1, T2>:: show()
{
}

类模板与友元

虚函数

虚函数:被virtual关键字修饰的成员函数,就是虚函数。

作用:实现多态性,多态性是将接口与实现进行分离。允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。

虚函数的实现由两部分组成:虚函数指针和虚函数表

编译器会为每一个含有虚函数的类创建一个虚函数表,在表内存放该类所有虚函数地址。同时编译器会为每个包含虚函数类的对象自动创建一个虚函数指针(虚函数指针实际上是一个函数指针),该指针指向所属类的虚表,当子类调用虚函数的时候,实际上是通过调用该虚函数指针从而找到接口。

对于单继承,即派生类只继承一个基类的情况下,派生类仅有一个虚函数表,而基类的虚函数表与派生类的虚函数表不同,如果派生类没有重写基函数的虚函数的话,两个虚函数表的地址相同。当派生类重写基函数的虚函数的话,派生类的虚函数地址会覆盖基函数的地址。

多继承情况下,派生类中有多个虚函数表,虚函数的排列方式和继承的顺序一致。派生类重写函数将会覆盖所有虚函数表的同名内容,派生类自定义新的虚函数将会在第一个类的虚函数表的后面进行扩充

纯虚函数和抽象类

纯虚函数的意义:对于部分程序中,基类虚函数的内容无实际意义,因此可以将虚函数改写为纯虚函数(无函数体)virtual int sum() = 0;

当类中存在纯虚函数时,类也被称为是抽象类

抽象类的特点:

存在无实例的对象;派生类必须重写抽象类的纯虚函数

可以通过基类的指针和引用来指向派生类对象,通过this指针来调用接口。

虚析构和纯虚析构

如果派生类的属性开辟到堆区,基类的指针在析构时无法调用派生类的析构函数,导致内存泄漏。

解决方法:使用虚析构和纯虚析构(虚析构和纯虚析构都需要函数的具体实现)

虚析构格式:virtual ~person(){}

上一篇:JIRA配置手册 :问题类型管理


下一篇:JIRA配置手册 :字段和界面