多态和模版

1. 多态

1.1 基本概述

多态的作用

  • 拓宽函数的参数范围,函数所需参数为父类类型,提供给当前函数的实际参数,可以是父类本身对象,或者其子类对象
  • 拓宽函数的返回值范围,函数返回值类型为父类类型,提供给返回值使用的实际类型,可以是父类本身,也可以是子类对象。
1.2 多态案例
#include <iostream>

using namespace std;

/*
以下案例就是一个多态案例,多态有一定的要求
    1. 父类中的成员函数,子类进行重写
    2. 父类中的成员函数可以是虚函数或者纯虚函数,父类成员函数
        可以作为函数执行兜底或者规则/规范
*/
class Animal 
{
public:
    virtual void eat() = 0;
};


class Dog : public Animal
{
public:
    void eat() { cout << "狗吃饭!" << endl; }
};

class HSQ : public Dog
{
public:
    void eat() { cout << "哈士奇撕家!" << endl; }
};

class Cat : public Animal
{
public:
    void eat() { cout << "猫吃饭!" << endl; }
};

class Panda : public Animal
{
public:
    void eat() { cout << "熊猫吃竹子!" << endl; }
};

/**
 * 喂养函数,提供给函数的参数是动物类型引用
 * 
 * @param ani 动物类型引用,可以提供的实际参数为动物对象本身,或者其后代对象
 */
void feed_animal(Animal &ani);

int main(int argc, char const *argv[])
{
    Dog dog;
    Panda panda;
    HSQ hsq;
    Cat cat;

    feed_animal(dog);
    feed_animal(panda);
    feed_animal(hsq);
    feed_animal(cat);
  
    return 0;
}

void feed_animal(Animal &ani)
{
    ani.eat();
}

2. 模版

2.1 模版引入

回顾

重写 Override

  • 必须存在必要的继承关系
  • 子类重写父类函数
  • 子类重写父类函数声明完全一致
  • 函数对应的函数体内容根据当前子类所需,进行特征化实现

重载 Reload

  • 在同一个区域内容,例如,同一个文件,同一个类,同一个结构体,同一个命名空间
  • 函数名必须一致
  • 函数的形式参数列表必须不一致
  • 函数的返回值类型不作为重载区分依据
#include <iostream>

using namespace std;

// #define NORMAL
#define TEMPLATE

/*
利用函数重载机制,完成当前函数,函数对应的名称一致,形式参数
列表数据类型不一致
*/

#ifdef NORMAL
void show(int n);
void show(float n);
void show(double n);
void show(string str);
void show(char c);
#endif

#ifdef TEMPLATE
template <typename T>
void show(T t);
#endif 

int main(int argc, char const *argv[])
{
    // void show<int>(int t)
    show(10);

    // void show<double>(double t)  
    show(10.5);

    // void show<float>(float t)
    show(10.5F);

    // void show<const char *>(const char *t)
    show("脸疼!");

    // void show<char>(char t)
    show('A');

    return 0;
}

#ifdef TEMPLATE
template <typename T>
void show(T t)
{
    cout << "Template Function Value : " << t << endl;
}
#endif

#ifdef NORMAL

void show(int n)
{
    cout << "Value : " << n << endl;
}
void show(float n)
{
    cout << "Value : " << n << endl;
}
void show(double n)
{
    cout << "Value : " << n << endl;
}
void show(string str)
{
    cout << "Value : " << str << endl;
}
void show(char c)
{
    cout << "Value : " << c << endl;
}
#endif
2.2 模版概述

模版是 C++ 中特殊的语法规则,主要目的是拓宽数据类型支持的多样性,同时满足数据类型一致化要求,可以用于【函数,类,结构体】。

涉及到的关键字

template<typename 模版数据类型占位符标记>
  • 模版数据类型占位符标记,常用的字符有 T(Type), E(Element), K(Key), V(Value), R(ReturnType)
  • 占位符标记不具备任何含义,当前程序需要明确数据类型,所有使用到占位符位置的内容都会被替换为【具体类型】
2.3 模版在函数中使用

函数自行声明模版类型,自行使用

template<typename 模版数据类型占位符标记>
返回值类型 函数名(形式参数列表);

在函数中返回值类型和形式参数列表,都可以使用模版类型。

【要求】函数形式参数列表中,必须有一个参数对应当前模版类型,用于【明确约束】当前模版对应的具体数据类型是哪一个类型。

#include <iostream>

using namespace std;

/*
声明当前模版数据类型占位符为 T
可以认为当前函数的声明是带有对应的模版声明内容,后续实现,需要
带有对应的模版声明
*/
template <typename T>
T get_type(T t);

/*
当前函数有两个模版占位符,分别对应 T1 和 T2
两个模版占位符 T1 和 T2 独立,没有任何关系,每一个模版都需要
自行提供对应具体数据类型。
*/
template <typename T1, typename T2>
void test(T1 t1, T2 t2);

/*
当前函数声明模版占位符,同时在形式参数列表中,多个参数使用的模版
都是同一个模版 T
*/
template <typename T>
void test(T t1, T t2, T t3, T t4);

int main(int argc, char const *argv[])
{
    /*
    原本当前函数的声明
        template <typename T>
        T get_type(T t);
            利用实际参数 10 明确约束当前模版对应的具体数据类型为 int 类型
            转换之后
        int get_type<int>(int t)
    */
    int v1 = get_type(10);
    cout << "v1 : " << v1 << endl;

    /*
    当前提供的实际参数为字符串类型,模版 T 转换为 const char *
    const char *get_type<const char *>(const char *t)
    */
    const char *str = get_type("换季谨防感冒!!!");
    cout << "str : " << str << endl;
  
    cout << "---------------------------------------------" << endl;

    /*
    当前函数声明了两个模版占位符,每一个模版都需要单独的提供具体数据类型
    进行数据约束行为。相互独立,没有任何关系。
    void test<int, const char *>(int t1, const char *t2)
    */
    test(10, "字符串");

    /*
    void test<double, float>(double t1, float t2)
    */
    test(1.5, 2.0F);

    /*
    ERROR 语法错误
        因为 void test(T t1, T t2, T t3, T t4); 函数中
        使用的模版占位符都是 T,第一个实际参数 10 提供给
        函数之后,所有使用的模版占位符 T 的位置都是 int 类型。

        【模版满足数据类型多样性,同时严格遵守数据类型一致化原则】

        后续提供的实际参数 5.5 和  6.0 都是 double 类型,不满足
        当前所需参数为 int 类型的要求。【语法错误】
    */
    test(10, 5, 5.5, 6.0); // ERROR 错误案例

    test(10, 5, 2, 1);

    return 0;
}

template <typename T>
T get_type(T t)
{
    return t;
}

template <typename T1, typename T2>
void test(T1 t1, T2 t2)
{
    cout << "t1 : " << t1 << endl;
    cout << "t2 : " << t2 << endl;
}

template <typename T>
void test(T t1, T t2, T t3, T t4)
{
    cout << "t1 : " << t1 << endl;
    cout << "t2 : " << t2 << endl;
    cout << "t3 : " << t3 << endl;
    cout << "t4 : " << t4 << endl;
}
2.4 模版在类声明使用

类声明模版类型,可以用于当前类中的成员变量,成员函数,并且支持成员函数二次再定义模版类型。

2.4.1 基本案例
#include <iostream>

#include <cstdio>
#include <cstdlib>
#include <cstring>

using namespace std;

/*
模版占位符在明确数据类型的情况下,所有使用对应模版的
类型都会被替换为具体数据类型。

template <typename K, typename V>
class Data
按照语法要求,当前 Data 类声明使用了两个模版占位符,第一个为 K,第二个为 V
编译器认为当前 Data 类完整类型是
    Data<K, V>
    外部类型要求是 Data 类型
    内部类型要求是 <K, V>
    [双重数据类型约束]
*/
template <typename K, typename V>
class Data
{
public:
    Data();
    Data(K key, V value);
    Data(const Data &data);
    ~Data();

    void set_key(K key);
    K get_key();

    void set_value(V value);
    V get_value();

private:
    K key;
    V value;
};

/*
函数在当前类外部进行实现操作
    1. 需要明确告知当前使用的模版情况
    2. Data 类型在编译器中的真实类型情况为
        Data<K, V>;
*/
template <typename K, typename V>
Data<K, V>::Data()
{
    /*
    this 在构造函数中表示当前实例化对象地址
    */
    memset(this, 0, sizeof(Data));
}

template <typename K, typename V>
Data<K, V>::Data(K key, V value)
    : key(key),
      value(value)
{
}

template <typename K, typename V>
Data<K, V>::Data(const Data &data)
    : key(data.key),
      value(data.value)
{
}

template <typename K, typename V>
Data<K, V>::~Data()
{
}

template <typename K, typename V>
void Data<K, V>::set_key(K key)
{
    // this 在成员函数,表示调用当前函数的对象地址。
    this->key = key;
}

template <typename K, typename V>
K Data<K, V>::get_key()
{
    return key;
}

template <typename K, typename V>
void Data<K, V>::set_value(V value)
{
    this->value = value;
}

template <typename K, typename V>
V Data<K, V>::get_value()
{
    return value;
}

int main(int argc, char const *argv[])
{
    /*
    实例化对象操作,当前 Data 类型的完整数据类型为
        Data<K, V>
    C++ 要求如果需要实例化带有模版的数据类型,必须在实例化
    过程中,对当前模版类型进行明确的数据类型约束。

    分为两种情况
        1. 实例化对象
        2. 利用 new 关键字 + 构造函数形式,申请内存【堆区】空间
            进行实例化对象操作
    */
    /*
    【实例化对象】
        利用 Data<K, V> 类内无参数构造函数,<string, int> 是明确
        告知当前模版
            【K 对应 string 类型, V 对应 int 类型】。

        可以认为对应当前 d1 对象,类内的成员内容如下
            {
            public:
                Data(); 【使用无参数构造函数】
                ~Data();

                void set_key(string key);
                string get_key();

                void set_value(int value);
                int get_value();

            private:
                string key;
                int value;
            };
    */
    Data<string, int> d1;

    d1.set_key("方城炝锅烩面");
    d1.set_value(12);

    cout << "Key : " << d1.get_key()
         << ", Value : " << d1.get_value()
         << endl;

    cout << "-----------------------------------------------" << endl;

    /*
    利用 new 关键字 + 构造函数情况,指针类型必须明确约束模版,同时
    在 new 之后的构造函数名称之后需要利用 <> 内容明确约束模版

        Data<int, string> * p_data = new Data<int, string>();
        明确告知编译器,当前实例化对象 Data 对应的
            【K 对应 int 类型, V 对应 string 类型】。

        可以认为对应当前 p_data 存储对象地址的指针,类内的成员内容如下
        {
        public:
            Data<int, string>(int k, string value);【使用有参数构造函数】
            ~Data();

            void set_key(int key);
            int get_key();
            void set_value(string value);
            string get_value();

        private:
            int key;
            string value;
        };
    */
    Data<int, string> *p_data = new Data<int, string>(10, "罗纳尔多");

    cout << "Key : " << d1.get_key()
         << ", Value : " << d1.get_value()
         << endl;

    delete p_data;
    return 0;
}
2.4.2 带有模版的类作为函数参数限制【重点】

data.hpp

#ifndef _MY_DATA_
#define _MY_DATA_

#include <iostream>

#include <cstdio>
#include <cstdlib>
#include <cstring>

using namespace std;

template <typename K, typename V>
class Data
{
public:
    Data();
    Data(K key, V value);
    Data(const Data &data);
    ~Data();

    void set_key(K key);
    K get_key();

    void set_value(V value);
    V get_value();

private:
    K key;
    V value;
};

/*
函数在当前类外部进行实现操作
    1. 需要明确告知当前使用的模版情况
    2. Data 类型在编译器中的真实类型情况为
        Data<K, V>;
*/
template <typename K, typename V>
Data<K, V>::Data()
{
    /*
    this 在构造函数中表示当前实例化对象地址
    */
    memset(this, 0, sizeof(Data));
}

template <typename K, typename V>
Data<K, V>::Data(K key, V value)
    : key(key),
      value(value)
{
}

template <typename K, typename V>
Data<K, V>::Data(const Data &data)
    : key(data.key),
      value(data.value)
{
}

template <typename K, typename V>
Data<K, V>::~Data()
{
}

template <typename K, typename V>
void Data<K, V>::set_key(K key)
{
    // this 在成员函数,表示调用当前函数的对象地址。
    this->key = key;
}

template <typename K, typename V>
K Data<K, V>::get_key()
{
    return key;
}

template <typename K, typename V>
void Data<K, V>::set_value(V value)
{
    this->value = value;
}

template <typename K, typename V>
V Data<K, V>::get_value()
{
    return value;
}

#endif

模版类作为函数参数案例

#include <iostream>

#include "data.hpp"

using namespace std;

/**
 * 当前函数所需参数为 Data 类型,但是模版对应的具体数据类型
 * 尚未明确,要求当前函数必须对使用的模版进行声明告知。
 *
 * 【分析】
 *      当前函数所需参数为 Data 类型,分为两个部分来分析数据类型
 *      限制
 *          1. 外部类型为 Data 类型
 *          2. 内部类型为 <K, V> 当前是模版情况下,尚未明确
 *      当前函数所需参数为 Data 类型,内部类型不限制,可以支持任意
 *      内部模版限制类型 Data 对象。
 */
template <typename K, typename V>
void show_data(Data<K, V> &data);

/**
 * 当前展示要求的参数必须是 Data 类型,同时限制 Data 中所需的两个
 * 模版类型都是 string
 */
void show_data_string_string(Data<string, string> &data);

int main(int argc, char const *argv[])
{
    Data<string, int> d1("鱼香肉丝", 13);
    Data<int, string> d2(1, "Rose");
    Data<string, string> d3("小鸡炖蘑菇", "东北大拉皮");
    Data<string, string> d4("黄瓜油条", "炒蒸菜");

    cout << "-------------------------------------------" << endl;

    /*
    void show_data<string, int>(Data<string, int> &data)
    */
    show_data(d1);
    /*
    void show_data<int, string>(Data<int, string> &data)
    */
    show_data(d2);

    /*
    void show_data<string, string>(Data<string, string> &data)
    */
    show_data(d3);
    show_data(d4);

    cout << "-------------------------------------------" << endl;
    /*
    d1 对应的数据类型为 Data<string, int>
    d2 对应的数据类型为 Data<int, string>

    外部类型满足当前函数所需,但是内部模版对应的具体数据类型不满足
    当前函数所需要的 <string, string>;
    d1 和 d2 无法作为函数实际参数,严格遵守数据类型一致化原则。
    */
    // show_data_string_string(d1); // ERROR
    // show_data_string_string(d2); // ERROR
    show_data_string_string(d3);
    show_data_string_string(d4);

    return 0;
}

template <typename K, typename V>
void show_data(Data<K, V> &data)
{
    cout << "Key : " << data.get_key()
         << ", Value : " << data.get_value()
         << endl;
}

void show_data_string_string(Data<string, string> &data)
{
    cout << "Key : " << data.get_key()
         << ", Value : " << data.get_value()
         << endl;
}
2.4.3 模版通过继承手段的延续性
#include <iostream>

using namespace std;

/*
Type 情况分析
    1. 类声明模版类型 T
    2. 类内使用 virtual 修饰声明两个纯虚函数

    【带有模版的抽象类】
*/
template <typename T>
class Type // Type<T>
{
public:
    virtual void test(T t) = 0;
    virtual T get_value(T t) = 0;
};

/*
子类继承带有模版约束的父类,有两种形式
    1. 自由模式
    2. 妻管严模式
*/

/*
自由模式
    子类继承父类,模版对应具体数据类型不明确,使用相同的
    模版占位符,在实例化对象时,明确模版对应数据类型。
*/
template <typename T>
class Sub_Type_Freedom : public Type<T>
{
public:
    void test(T t)
    {
        cout << "Value : " << t << endl;
    }

    T get_value(T t)
    {
        return t;
    }
};

/*
妻管严模式:
    子类继承带有模版的父类,继承过程中,父类对应的模版具体数据类型
    直接进行明确约束告知。子类没有任何的修改权限

    public Type<string>
        Type 类继承给子类的函数,对应的模版类型明确为 string 类型
    当前 Type 类型继承到子类要求实现的函数为
        virtual void test(string t) = 0;
        virtual string get_value(string t) = 0;
*/
class Sub_Type_Wife_Always_Right : public Type<string>
{
public:
    void test(string t)
    {
        cout << "t : " << t << endl;
    }
    string get_value(string t)
    {
        return t;
    }
};

int main(int argc, char const *argv[])
{
    /*
    自由模式实例化对象明确约束模版对应具体数据类型
    */
    Sub_Type_Freedom<string> stf1;

    /*
    string get_value(string t)
    */
    string str1 = stf1.get_value("孤勇者");

    cout << "str1 : " << str1 << endl;
    /*
    inline void test(string t)
    */
    stf1.test("夜曲");

    cout << "------------------------------" << endl;

    Sub_Type_Freedom<int> stf2;

    /*
    inline get_value(int t)
    */
    int ret = stf2.get_value(100);
    cout << "ret : " << ret << endl;

    stf2.test(1000);

    cout << "------------------------------" << endl;

    Sub_Type_Wife_Always_Right right;

    string str2 = right.get_value("老婆大人说的对!");
    cout << "str2 : " << str2 << endl;

    right.test("吃砂锅~~~");

    return 0;
}