七、C++之泛型编程

一、函数模板

1.1 函数模板的基本使用

  C++提供函数模板,所谓函数模板,实际上是建立一种通用函数,其函数类型和形参模型不具体制定,用一个虚拟的类型来代表,这个通用函数称为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定以多个函数类型,只需要在模板中定义一次即可,在调用函数时系统会根据实参的类型来取代模板的虚拟类型,从而实现不同函数的功能。

// 交换两个变量 当数据
/*
    template(class T) 告诉编译器紧跟着的出现的T不要报错。
     1. 自动类型推导, 必须要有参数类型才能推导, 类型参数化,必须要传入
     2. 显示指定类型, mySwap<int>(a, b);
     3. 模板必须要指定T才能使用
*/

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

void mySwapInt(int &a, int &b)
{
    int tmp;
    tmp = a;
    a = b;
    b = tmp;
}

void mySwapDouble(double &a, double &b)
{
    double tmp;
    tmp = a;
    a = b;
    b = tmp;

}

// 类型,逻辑非常相似
// 类型参数化,泛型编程 -- 模板技术
template < class T > // 告诉编译器 下面代码吐过出现T, 不要报错, T是一个通用类型
// 等价于 template<typename T>
void mySwap(T &a, T &b)
{
    T tmp = a;
    a = b;
    b = tmp;
}

template<typename T>
void mySwap2(T a, T b) {}

void test()
{
    int a = 10;
    int b = 20;
    mySwapInt(a, b);
    cout << "a = " << a << endl;  // 20
    cout << "b = " << b << endl;  // 10
    double c = 3.14;
    double d = 1.23;
    mySwapDouble(c, d);
    cout << "c = " << c << endl;  // 1.23
    cout << "d = " << d << endl;  // 3.14
    // 1. 自动类型推导, 必须要有参数类型才能推导 如 char e = 'e'; mySwap(a, e);这样就不行。
    mySwap(a, b);
    mySwap(c, d);
    cout << "a = " << a << endl;  // 10
    cout << "b = " << b << endl;  // 20
    cout << "c = " << c << endl;  // 3.14
    cout << "d = " << d << endl;  // 1.23
    // 2. 显示指定类型, 
    mySwap<int>(a, b);
    cout << "a = " << a << endl;  // 20
    cout << "b = " << b << endl;  // 10
    // 3. 模板必须要指定T才能使用
    // mySwap2(); 这样会报错的,没有指定参数的类型
}

int main()
{
    test();
    return EXIT_SUCCESS;
}

 

1.2 函数模板实现通用的数组排序(char, int)

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

//  对 char和int数组进行用选择排序从大到小排序
template < class T >
void Swap(T &a, T&b)
{
    T temp = a;
    a = b;
    b = temp;
}


template<class T>
void Sort(T arr[], int len)
{
    for (int i = 0; i < len; i++)
    {
        int max = i;
        for (int j = i + 1; j < len; j++)
        {
            if (arr[max] < arr[j])
            {
                // 交换下表
                max = j;
            }
        }
        if (max != i)
        {
            // 交换数据
            Swap(arr[max], arr[i]);
        }
    }
}

// 输出数组元素的模板
template < class T >
void printArray(T arr[], int len)
{
    for (int i = 0; i < len; i++)
    {
        cout << arr[i] << " ";
    }
    cout << endl;
}

void test()
{
    char charArr[] = "helloworld";
    int num = sizeof(charArr) / sizeof(char);
    Sort(charArr, num);
    printArray(charArr, num);
    int intArr[] = { 2, 5, 6, 3, 1, 9, 0, 8, 7, 4 };
    num = sizeof(intArr) / sizeof(int);
    Sort(intArr, num);
    printArray(intArr, num);
}

int main()
{
    test();
    return EXIT_SUCCESS;
}

 

1.3 普通函数与函数模板的区别以及调用规则

/*
区别: 普通函数,可以进行隐式类型转换, 函数模板不可以
调用规则:
    1. 如果出现重载,优先使用普通函数调用,  即使普通函数只有声明没有实现,只有声明会执行时报错的
    2. 如果向强制执行模板,那么可以使用参数列表
    3. 函数模板可以发生重载
    4. 如果函数模板可以发生更好的匹配, 那么优先调用函数模板
*/
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

// 1. 普通函数与函数模板的区别
template<class T>
void myPlus(T a, T b)
{
    return a + b;

}
int myPlus2(int a, int b)
{
    return a + b;
}

void test()
{
    int a = 10;
    int b = 20;
    char c = 'c';
    // myPlus(a, c); 类型推导不出来
    myPlus2(a, c); // 可以执行, 普通函数,可以进行隐式类型转换
}

// 2. 普通函数和函数模板的调用规则
template<class T>
void myPrint(T a, T b)
{
    cout << "模板调用的myPrint" << endl;
};

void myPrint(int a, int b)
{
    cout << "普通的函数调用myPrint" << endl;
}

template<class T>
void myPrint(T a, T b, T c)
{
    cout << "模板调用的myPrint发生重载" << endl;
};

void myPrint2();

void test02()
{
    int a = 10;
    int b = 20;
    // 1. 如果出现重载,优先使用普通函数调用,  即使普通函数只有声明没有实现,只有声明会执行时报错的
    myPrint(a, b); //  普通的函数调用myPrint  
    
    // 2. 如果向强制执行模板,那么可以使用参数列表
    myPrint<>(a, b); // 模板调用的myPrint

    // 3. 函数模板可以发生重载
    int c = 30;
    myPrint(a, b, c); // 模板调用的myPrint发生重载

    // 4. 如果函数模板可以发生更好的匹配, 那么优先调用函数模板
    char d = 'd';
    char e = 'e';
    myPrint(d, e);  // 模板调用的myPrint,因为调用普通函数需要发生隐式数据类型转换
}

int main()
{
    test();
    test02();
    return EXIT_SUCCESS;
}

 

1.4 函数模板的局限性

/*    
    模板不能解决所有的数据类型,如果出现不能解决的数据类型,可以通过具体化来解决问题
    语法 template<> 返回值 函数名<具体类型>(函数参数)
*/

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

class Person
{
public:
    Person(string name, int age)
    {
        this->Name = name;
        this->Age = age;
    }
    string Name;
    int Age;
};

template<class T>
bool myCompare(T &a, T &b)
{
    if (a == b)
    {
        return true;
    }
    return false;
}
// 通过具体化自定义数据类型, 解决自定义数据类型无法比较的问题
// 如果具体化能够优先匹配,那么就选择具体化
// 语法  template<> 返回值 函数名<具体类型>(函数参数)
template<> bool myCompare<Person>(Person &a, Person &b)
{
    if (a.Age == b.Age && a.Name == b.Name)
    {
        return true;
    }
    return false;
}

void test()
{
    int a = 10;
    int b = 2;
    int ret = myCompare(a, b);
    cout << "ret = " << ret << endl;


    Person p1("Tom", 30);
    Person p2("Jerry", 15);
    ret = myCompare(p1, p2); // 直接这样是不行的,是会报错的, 需要具体化自定义数据类型解决问题
    cout << "ret = " << ret << endl;
    
}

int main()
{
    test();
    return EXIT_SUCCESS;
}

 

二、类模板

2.1 类模板的基本使用

/*
    与函数模板区别,参数可以有默认类型
    函数模板可以进行自动类型转换, 类模板不行   Person<string, int> p1("wangyong", 27);
*/

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

// template<class NameType, class AgeType> // 没问题
template<class NameType, class AgeType = int> // 类模板可以有默认类型

class Person
{
public:
    Person(NameType name, AgeType age)
    {
        this->Name = name;
        this->Age = age;
    }
    void show()
    {
        cout << "姓名:" << this->Name << endl;
        cout << "年龄:" << this->Age << endl;
    }
    NameType Name;
    AgeType Age;
};

void test()
{
    // 自动类型推倒, 类内模板不支持
    // Person p1("wangyong", 27);

    // 显示指定类型
    Person<string, int> p1("wangyong", 27);
    p1.show();

}

int main()
{
    test();
    return EXIT_SUCCESS;
}

 

2.2 成员函数的创建时机

/*
    成员函数一开始并不会创建出来,而是在运行时才会创建
*/
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

class Person1
{
public:
    void showPerson1()
    {
        cout << "showPerson1" << endl;
    }
};

class Person2
{
public:
    void showPerson2()
    {
        cout << "showPerson2" << endl;
    }
};

template<class T>
class myClass
{
public:
    T obj;
    void func1()
    {
        obj.showPerson1();
    }
    void func2()
    {
        obj.showPerson2();
    }
};

// 成员函数一开始并不会创建出来,而是在运行时才会创建
void test()
{
    myClass<Person1> myclass;  
    myclass.func1(); // showPerson1

}

int main()
{
    test();
    return EXIT_SUCCESS;
}

 

2.3 类模板做函数参数

/*
    1. 显示指定数据类型
    2. 参数模板化
    3. 整体模板化
    
    // 如何查看类型
    cout << typeid(T1).name() << endl;
*/
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

template<class NameType, class AgeType = int> // 类模板可以有默认类型

class Person
{
public:
    Person(NameType name, AgeType age)
    {
        this->Name = name;
        this->Age = age;
    }
    void show()
    {
        cout << "姓名:" << this->Name << endl;
        cout << "年龄:" << this->Age << endl;
    }
    NameType Name;
    AgeType Age;
};

// 1. 指定传入类型
void doWork(Person<string, int> &p)
{
    p.show();
}

// 2. 参数模板化
template<class T1, class T2>
void doWork2(Person<T1, T2> &p)
{
    // 如何查看类型
    cout << typeid(T1).name() << endl;
    cout << typeid(T2).name() << endl;
    p.show();
}

// 3. 整体模板化
template<class T>
void doWOrk3(T &p)
{
    cout << typeid(T).name() << endl;
    p.show();
}

void test()
{
    // 1. 指定传入类型
    Person<string, int> p1 ( "wangyong", 27 ) ;
    doWork(p1);

    // 2. 参数模板化
    Person<string, int> p2("wangyong", 27);
    doWork2(p2);

    // 3. 整体模板化
    Person<string, int> p3("wangyong", 27);
    doWOrk3(p3);
}

int main()
{
    test();
    return EXIT_SUCCESS;
}

2.4 类模板碰到继承的问题

/*
    基类如果是模板类, 必须让子类告诉编译器 基类中的 T 是什么类型,如果不告诉的话,那么无法分配内存,编译不通过
    
    继承模板的书写方式:    class Child : public Base<int> 
*/
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

template<class T>
class Base
{
public:
    T A;
};

// Child 继承 Base, 必须要告诉 Base中的 T 的类型,都这 T 无法分配内存
class Child : public Base<int> 
{
public:

};

template <class T1, class T2>
class Child2 :public Base<T2>
{
public:
    Child2()
    {
        cout << typeid(T1).name() << endl;  // int
        cout << typeid(T2).name() << endl;  // double
    }
    T1 B;
};

void test()
{
    Child2<int, double> child;  //说明 T1是int类型, T2是double类型, T2是double类型,那么继承的Base的T就是double类型
}

int main()
{
    test();
    return EXIT_SUCCESS;
}

 

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

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

template<class T1, class T2>
class Person
{
public:
    Person(T1 name, T2 age);
    /*{
        // 类内实现
        this->Name = name;
        this->Age = age;
    }*/
    void showPerson();
    //{
    //    cout << "姓名: " << this - Name << endl;
    //    cout << "年龄: " << this->Age << endl;
    //}
    T1 Name;
    T2 Age;
};


// 类外实现成员函数
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
    this->Name = name;
    this->Age = age;
}

template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
    cout << "姓名: " << this -> Name << endl;
    cout << "年龄: " << this->Age << endl;
}

void test()
{
    Person<string, int> p1("wangyong", 27);
    p1.showPerson();
}


int main()
{
    test();
    return EXIT_SUCCESS;
}

 

2.6 类模板的分文件编写

/*
    .p 和 .cpp 分别写声明和实现,但是由于类模板的成员函数运行阶段才去创建, 导致包含 .h 头文件,不会创建函数的实现,就无法解析外部命令。
    解决方案:
        1. #include "Person.cpp"   不推荐
        2. 不要进行分文件编写, 写到同一个文件进行声明和实现,后缀名改为 .hpp。 将Person.cpp的代码直接写到 .h声明的文件中, 然后修改 .h为 .hpp,然后#include "Person.hpp"
*/

// .hpp
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;


template < class T1, class T2 >
class Person
{
public:
    Person(T1 name, T2 age);
    void showPerson();
    T1 Name;
    T2 Age;
};


template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
    this->Name = name;
    this->Age = age;
}

template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
    cout << "姓名: " << this->Name << endl;
    cout << "年龄: " << this->Age << endl;
}


// main.cpp
#include "Person.hpp"

void test()
{
    // 编译过程会出现问题的, 所以模板一般不建议分文件编写, 
    // 解决方案:1. #include "Person.cpp"   2. 将Person.cpp的代码直接写到 .h声明的文件中, 然后修改 .h为 .hpp,然后#include "Person.hpp"
    Person<string, int> p1("wangyong", 27); 
    p1.showPerson();
}


int main()
{
    test();
    return EXIT_SUCCESS;
}

 

2.7 类模板碰到友元函数

2.7.1 友元函数类内实现

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

template<class T1, class T2>
class Person
{
    // 友元函数类内实现
    friend void printPerson(Person<T1, T2> &p)
    {
        cout << "姓名: " << p.Name << endl;
        cout << "年龄: " << p.Age << endl;
    }
public:
    Person(T1 name, T2 age)
    {
        this->Name = name;
        this->Age = age;
    }
private:
    T1 Name;
    T2 Age;
};




void test()
{
    Person<string, int> p1("wangyong", 27);
    printPerson(p1);
}


int main()
{
    test();
    return EXIT_SUCCESS;
}

 

2.7.2 友元函数类外实现

/*
    friend void printPerson(Person<T1, T2> &p);     // 普通函数声明
    friend void printPerson<>(Person<T1, T2> &p);   // 模板函数声明  
    需要让编译器看到函数并且看到这个Person类型提前声明
*/
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

//让编译器提前看到 printPerson 声明
template<class T1, class T2>class Person;
template<class T1, class T2>void printPerson(Person<T1, T2> &p);

template<class T1, class T2>
class Person
{
    // 友元函数类内实现
    // friend void printPerson(Person<T1, T2> &p); // 这是普通函数的声明,但是实现是模板函数实现,所以直接这样写是报错的
    friend void printPerson<>(Person<T1, T2> &p);   // 这样代表模板函数声明
    
public:
    Person(T1 name, T2 age)
    {
        this->Name = name;
        this->Age = age;
    }
private:
    T1 Name;
    T2 Age;
};

// 友元函数类外实现
template<class T1, class T2>
void printPerson(Person<T1, T2> &p)
{
    cout << "姓名: " << p.Name << endl;
    cout << "年龄: " << p.Age << endl;
}

void test()
{
    Person<string, int> p1("wangyong", 27);
    printPerson(p1);
}


int main()
{
    test();
    return EXIT_SUCCESS;
}

 

2.8 类模板的应用--数组类的封装

2.8.1 MyArry.hpp

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

template<class T>
class MyArray
{
public:
    // 构造
    explicit MyArray(int capacity) // 防止隐式类型转换
    {
        this->Capacity = capacity;
        this->Size = 0;
        this->pAddress = new T[this->Capacity];
    }

    MyArray(const MyArray & myArray)
    {
        this->Capacity = myArray.Capacity;
        this->Size = myArray.Size;
        this->pAddress = new T[this->Capacity];
        for (int i = 0; i < this->Size; i++)
        {
            this->pAddress[i] = myArray[i];
        }
    }

    ~MyArray()
    {
        if (this->pAddress != NULL)
        {
            delete[] this->pAddress;
            this->pAddress = NULL;
        }
    }

    // 赋值操作符重载
    MyArray& operator=(MyArray & myArray)
    {
        // 先判断原始数据,有就清除
        if (this->pAddress != NULL)
        {
            delete[] this->pAddress;
            this->pAddress = NULL:
        }
        this->Capacity = myArray.Capacity;
        this->Size = myArray.Size;
        this->pAddress = new T[this->Capacity];
        for (int i = 0; i < this->Size; i++)
        {
            this->pAddress[i] = myArray[i];
        }
    }

    // [] 重载
    T& operator[](int index)
    {
        return this->pAddress[index];
    }
    // 尾插法
    void pushBack(T val)
    {
        this->pAddress[this->Size] = val;
        this->Size++;
    }

    // 获取大小
    int getSIze()
    {
        return this->Size;
    }
    // 获取容量
    int getCapacity()
    {
        return this->Capacity;
    }
private:
    T * pAddress;
    int Capacity;
    int Size;

};

 

2.8.2 main.cpp

#include "MyArray.hpp"


class Person
{
public:
    Person(){};
    Person(string name, int age)
    {
        this->Name = name;
        this->age = age;
    }
    string Name;
    int age;
};


// 输出 int 类型的方法
void printInt(MyArray<int> &arr)
{
    for (int i = 0; i < arr.getSIze(); i++)
    {
        cout << arr[i] << endl;
    }
}

// 输出 Person 类型的数组
void printPerson(MyArray<Person> &array)
{
    for (int i = 0; i < array.getSIze(); i++)
    {
        cout << "姓名: " << array[i].Name << endl;
        cout << "年龄: " << array[i].age << endl;
    }
}


int main()
{
    MyArray<int> arr(10);
    for (int i = 0; i < 10; i++)
    {
        arr.pushBack(i+100);
    }
    printInt(arr);

    Person p1("wang", 27);
    Person p2("yong", 28);
    Person p3("zhou", 26);
    MyArray<Person> arr2(10);
    arr2.pushBack(p1);
    arr2.pushBack(p2);
    arr2.pushBack(p3);
    printPerson(arr2);
    return EXIT_SUCCESS;
}

 

posted on 2022-03-16 22:23  软饭攻城狮  阅读(92)  评论(0)    收藏  举报

导航