模板、函数模板、类模板

一、模板  

  泛型(Generic Programming)即是指具有在多种数据类型上皆可操作的含意。泛型编程的代表作品 STL 是一种高效、泛型、可交互操作的软件组件
  泛型编程最初诞生于 C++中,目的是为了实现 C++的 STL(标准模板库)。其语言支持机制就是模板(Templates)。模板的精神其实很简单:参数化类型。换句话说, 把一个原本特定于某个类型的算法或类当中的类型信息抽掉,抽出来做成模板参数 T。

  所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。

二、函数模板

1.语法格式

  template 是语义是模板的意思,尖括号中先写关键字 typename 或是class,后面跟一个类型 T,此类即是虚拟的类型。至于为什么用 T,用的人多了,也就是 T 了。

2.函数模板的实例

  调用过程是这样的,先将函数模板实在化为函数,然后再发生函数调用。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>

using namespace std;

template<class T>
void MySwap(T& a, T& b)
{
    T temp = a;
    a = b;
    b = temp;
}

int main(void)
{
    int a = 10;
    int b = 20;

    //1.自动类型推导
    cout << "a:" << a << ",b:" << b << endl;//a:10,b:20
    MySwap(a, b);//编译器根据你传的值,进行类型自动推导
    cout << "a:" << a << ",b:" << b << endl;//a:20,b:10

    //2.显式的指定类型
    MySwap<int>(a, b);
    cout << "a:" << a << ",b:" << b << endl;//a:10, b:20

    double da = 10.01;
    double db = 20.02;
    cout << "da:" << da << ",db:" << db << endl;//da:10.01,db:20.02
    MySwap(da, db);//编译器根据你传的值,进行类型自动推导
    cout << "da:" << da << ",db:" << db << endl;//da:20.02,db:10.01
    MySwap<double>(da, db);
    cout << "da:" << da << ",db:" << db << endl;//da:10.01,db:20.02

    return 0;
}

  函数模板,只适用于函数的参数个数相同而类型不同,且函数体相同的情况。如果个数不同,则不能用函数模板。

 3.函数模板与普通函数的区别

  • 函数模板不允许自动类型转化
  • 普通函数能够自动进行类型转化

4.函数模板和普通函数在一起调用规则

  • 函数模板可以像普通函数那样被重载;
  • C++编译器优先考虑普通函数;
  • 如果函数模板可以产生一个更好的匹配,那么选择匹配;
  • 可以通过空模板实参列表的语法限定编译器只能通过模板匹配。

5.编译器对模板机制剖析

编译器编译原理

总结: 

(1)编译器并不是把函数模板处理成能够处理任意类的函数;

(2)编译器从函数模板通过具体类型产生不同的函数;

(3)编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。

函数模板案例——char和int类型数组排序

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

//对char类型和int类型数组进行排序

//打印函数
template<class T>
void PrintArray(T* arr, int len)
{
    for (int i = 0;i < len;i++)
    {
        cout << arr[i] << " ";
    }
    cout << endl;
}

//排序
template<class T>
void MySort(T* arr, int len)
{
    for (int i = 0;i < len;i++)
    {
        for (int j = i + 1;j < len;j++)
        {
            //从大到小排序
            if (arr[i] < arr[j])
            {
                T temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
    }
}

int main(void)
{
    int arr[] = { 2,6,1,8,9,2 };
    int len = sizeof(arr) / sizeof(int);

    cout << "排序前:";
    PrintArray(arr,len);//排序前:2 6 1 8 9 2
    MySort(arr, len);
    cout << "排序后:";
    PrintArray(arr, len);//排序后:9 8 6 2 2 1

    char chArr[] = { 'a','c','q','d','t', };
    len = sizeof(chArr) / sizeof(char);

    cout << "排序前:";
    PrintArray(chArr, len);//排序前:a c q d t
    MySort(chArr, len);
    cout << "排序后:";
    PrintArray(chArr, len);//排序后:t q d c a

    return 0;
}

 三、类模板

1.类模板定义

  类模板与函数模板的定义和使用类似,有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,所以将类中的类型进行泛化。

类模板示例:

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>

using namespace std;

template<class T>
class Person
{
public:
    Person(T id, T age)
    {
        this->m_id = id;
        this->m_age = age;
    }

    void Show()
    {
        cout << "id:" << this->m_id << ",age:" << this->m_age << endl;
    }

public:
    T m_id;
    T m_age;
};

void test()
{
    //函数模板在调用的时候,可以自动类型推导
    //类模板必须显式指定类型
    Person<int> p(10, 20);
    p.Show();
}

int main(void)
{
    test();//id:10,age:20
    return 0;
}

2.类模板派生普通类、类模板派生类模板

#include <iostream>
using namespace std;

template<class T>
class Person
{
public:
    Person(T age)
    {
        this->age = age;
    }
private:
    T age;
};

//模板类派生普通类
//为什么继承的类型需显式,而不是T?
//原因:类去定义对象,这个对象需要编译分配内存,所以要在
//public Person<int>这里显式的指定类型,可以知道给父类分配多少内存
class SubPerson1 :public Person<int>
{
public:
    SubPerson1(int age, int id) :Person<int>(age)
    {
        this->id = id;
    }
private:
    int id;
};

//模板类派生模板类
template<class T>
class SubPerson2 :public Person<T>
{
public:
    SubPerson2(T age, T id) :Person<T>(age)
    {
        this->id = id;
    }
private:
    int id;
};

 3、类模板实现

(1)类模板类内实现

(2)类模板类外实现(在一个.cpp中)

  模板类不要轻易使用友元函数

(3)类模板类外实现(在.h和.cpp中)

  由于二次编译,模板类在.h在第一次编译之后,并没有最终确定类的具体实现,只是编译器的词法校验和分析。在第二次确定类的具体实现后,是在.hpp文件生成的最后的具体类,所以main函数需要引入.hpp文件。

  引入hpp文件一说也是曲线救国之计,所以实现模板方法建议在同一个文件.h中完成。

(4)类模板中的static

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

template<class T>
class Person
{
public:
    static T a;
};
//类外初始化
template<class T> T Person<T>::a = 0;

int main(void)
{
    Person<int> p1, p2, p3;
    Person<char> pp1, pp2, pp3;

    p1.a = 10;
    pp1.a = 'c';

    cout << p1.a << " " << p2.a << " " << p3.a << endl;//10 10 10
    cout << pp1.a << " " << pp2.a << " " << pp3.a << endl;//c c c
//通过以上结果,说明p1,p2,p3是属于Person<int>家族的,他们共享Person<int>::a; //pp1,pp2,pp3是属于Person<char>家族的,他们共享Person<char>::a; return 0; }

练习:

设计一个数组模板类(MyArray),完成对int、char类型元素的管理。

需要实现 构造函数 拷贝构造函数  [] 重载=操作符。

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

template<class T>
class MyArray
{
public:
    MyArray(int capacity)
    {
        this->mCapacity = capacity;
        this->mSize = 0;
        //申请内存
        this->pAddr = new T[this->mCapacity];
    }
    MyArray(const MyArray<T>& arr)
    {
        this->mCapacity = arr.mCapacity;
        this->mSize = arr.mSize;

        //申请内存空间
        this->pAddr = new T[this->mCapacity];
        //数据拷贝
        for (int i = 0; i < this->mSize;i++)
        {
            this->pAddr[i] = arr.pAddr[i];
        }
    }

    T& operator[](int index)
    {
        return this->pAddr[index];
    }

    MyArray<T> operator=(const MyArray<T>& arr)
    {
        if (this->pAddr != NULL)
        {
            delete[] this->pAddr;
        }

        this->mCapacity = arr.mCapacity;
        this->mSize = arr.mSize;
        //申请内存空间
        this->pAddr = new T[this->mCapacity];
        //数据拷贝
        for (int i = 0; i < this->mSize;i++)
        {
            this->pAddr[i] = arr.pAddr[i];
        }

        return *this;
    }

    void PushBack(T& data)
    {
        //判断容器中是否有位置
        if (this->mSize >= this->mCapacity)
        {
            return;
        }

        //调用拷贝构造 =操作符
        //1.对象元素必须能够被拷贝
        //2.容器都是值寓意,而非引用寓意 向容器中放入元素,都是放入的元素的拷贝
        //3.如果元素的成员有指针,注意深拷贝和浅拷贝问题
        this->pAddr[this->mSize] = data;
        this->mSize ++ ;
    }

    //T&& 对右值取引用
    void PushBack(T&& data)
    {
        //判断容器中是否有位置
        if (this->mSize >= this->mCapacity)
        {
            return;
        }

        this->pAddr[this->mSize] = data;
        this->mSize++;
    }

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

public:
    //一共可以容下多少元素
    int mCapacity;
    //当前数组有多少元素
    int mSize;
    //保存数据的首地址
    T* pAddr;
};

class Person 
{
    

};
void test2()
{
    Person p1, p2;

    MyArray<Person> arr(10);
    arr.PushBack(p1);
    arr.PushBack(p2);
}

void test1()
{
    MyArray<int> marray(20);
    int a = 10, b = 20, c = 30, d = 40;
    marray.PushBack(a);
    marray.PushBack(b);
    marray.PushBack(c);
    marray.PushBack(d);

    //不能对右值取引用
    //左值 可以在多行使用
    //临时变量 只能当前行使用
    marray.PushBack(100);
    marray.PushBack(200);
    marray.PushBack(300);

    for (int i = 0;i < marray.mSize;i++)
    {
        cout << marray[i] << " ";
    }
    cout << endl;
}
int main(void)
{
    test1();
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/yuehouse/p/9893087.html