二十一、模板

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

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

1.png

1.函数模板

在函数模板未引入之前,对同一功能函数的不同类型参数实现只能通过函数重载实现,而模板则是把这个过程泛化

语法格式
template<typename T>
template<class T>

template<typename 类型参数表>
返回类型 函数模板名(函数参数列表)
{
   函数模板定义体
}

-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐

#include <iostream>

using namespace std;


//模板就是对参数类型的参数化

//此时编译器不知道T是什么
//告诉编译器T是一个类型的泛化
template <typename T>//告诉编译器T是一个泛化类型
template <class A>  //上下两种写法是等价的!
void mySwap(T &a,T &b)
{
   T temp = a;
   a = b;
   b = temp;
}


template <typename T>//每个函数都要声明一次!
void print(T &a,T &b)
{
   cout<<"a = "<<a<<" b = "<<b<<endl;
}

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

    cout<<"交换之前"<<endl;
    //<>在调用模板函数的时候,要告诉编译器T泛化的类型,具体到底是什么类型
    print<int>(a,b); 
    mySwap<int>(a,b);
    cout<<"交换之后"<<endl;
    print<int>(a,b); 



    char x = 'x';
    char y = 'y';

    cout<<"交换之前"<<endl;
    print<char>(x,y); 
    mySwap<char>(x,y);
    cout<<"交换之后"<<endl;
    print<char>(x,y); 

    return 0;
}

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

排序模板函数练习
//
#include <iostream>

using namespace std;

template<typename T>
int array_sort(T *array,int num)
{
   T temp;
   //len = sizeof(array)/sizeof(array[0]);
   //不能再这里写,这里的array已经退化成一个指针了,len只能是1
   for (int i = 0; i < num; ++i)
   {
      for (int j = i+1; j < num ; ++j)
      {
          if (array[j] > array[i])
          {
            temp = array[i];
            array[i] = array[j];
            array[j] = temp;
          }
      }
   }
   return 0;
}


//打印数组的方法
template<typename T>
void print_array(T *array,int num)
{
   for (int i = 0; i < num; ++i)
   {
      cout<<array[i]<<" ";
   }
   cout<<endl;
}


int main(void)
{
    char str[] = "dsagfsdhdsgbdfbgfdujhtfyhrd";
    int len = strlen(str);
     
    array_sort<char>(str,len);
    print_array<char>(str,len);


    cout<<"-------------------------"<<endl;
    //排序一个Int数组
    int array[] = {3,24,21,51,2,5,55,21};
    len = sizeof(array)/sizeof(array[0]);

    array_sort<int>(str,len);
    print_array<int>(str,len);


    return 0;
}

  • 当函数模板和普通函数都符合调用时,优先选择普通函数
  • 若显示使用函数模板,则使用<> 类型列表
  • 如果函数模板产生更好的匹配 使用函数模板

2.类模板

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

//基本语法
template<typename T>
class A
{
}
//----------------------------------------------------------
#include <iostream>

using namespace std;

template<typename T>
class A
{
public:
    A(int a){   
        this->a =a;
    }

    void printA(){
        cout<<"a = "<<a<<endl;
    }
private:
    T a;
};


//继承模板类,直接继承已经实例化的模板类
//这么继承完之后的B类,是普通类
class B:public A<int>
{
public:
    B(int a,int b):A(a){//A(a)加不加<int>都无所谓!
           this->b = b;            
    }

    void printB(){
        cout<<"b = "<<b<<endl;
    }
private:
    int b;
};

//C类继承的是一个模板类,没有实例化,此时C类仍然是一个模板类
template<typename T>
class C:public A<T>
{
public:
    C(T a,T c):A(a){
        this->c = c;
    }

    void printC(){
        cout<<"c = "<<c<<endl;
    }
private:
    T c;
};


//把模板类当参数
template<typename T>
void func(A<T> &a)
{
    a.printA();
}

//普通函数
void func2(A<int> &a)
{
    a.printA();
}


int main(void)
{
   //普通实例化
    A<int> a(10);

    a.printA();

    A<double> b(30.0);

    b.printA();

   
    cout<<"-----------------------"<<endl;
   //类作为参数
    func<int>(a);
    func2(a);


    cout<<"-----------------------"<<endl;
   //模板类继承
    B b(100,200);
    b.printB();


    C<int> c(1000,2000);//c是一个模板类

    c.printC();


    return 0;
}

2.1类模板的实现(函数体写在类外 有坑!)
//-------------------------------------Complex.h-------------------------------------------------
#include <iostream>
using namespace std;


#ifndef __COMPLEX_H__
#define __COMPLEX_H__
template<class T>
class Complex;

template<class T>
Complex<T> mySub(Complex<T> &c1,Complex<T> &c2);
//前置声明发现缺了Complex,又要前置声明Complex,所以很不建议滥用友元!


template<typename T>
class Complex
{
public:
    Complex()
    Complex(T a,T b)

    void printComplex(){
        cout<<"("<<a<<"+"<<b<<"i)"<<endl;
    }

    Complex operator+(Complex &another);
    Complex operator-(Complex &another);

     //如果给一个模板类提供一个友元函数(在外部定义),例如<< >>重载
    //要在这个重载符号和参数列表之间添加<T>
    friend ostream & operator<< <T>(ostream &os,Complex<T> &c);

    //滥用友元 调用就报错了,只能前置声明了!!!
    //滥⽤用友元函数,本来可以当成员函数,却要⽤用友元函数
    //如果说是⾮非<< >>在模板类中当友元函数
    //在这个模板类之前声明这个函数
    friend Complex<T> mySub<T>(Complex<T> &c1,Complex<T> &c2);
    //最终的结论,模板类不要轻易写友元函数,要写的就写<<和>>。  
  
    #if 0
    //或者可以在内部定义,就可以省去<T>
    friend ostream & operator<<(ostream &os,Complex<T> &c){
        os<<"("<<c.a<<"+"<<c.b<<"i)"<<endl;
        return os;
    }
    #endif

private:
    T a;
    T b;
}
#endif

//-------------------------------------Complex.hpp-------------------------------------------------

#include "Complex.h"
template<class T>
Complex<T>::Complex()
{
}

template<class T>
Complex<T>::Complex<T>(T a,T b)
{
  this­‐>a = a;
  this­‐>b = b;
}

template<typename T>
Complex<T> Complex<T>::operator+(Complex<T> &another)
 {
    Complex temp(this->a+another.a,this->b+another+another.b);
    return temp;
 }

template<typename T>
Complex<T> Complex<T>::operator+(Complex<T> &another)
 {
    Complex temp(this->a - another.a , this->b - another+another.b);
    //这些构造可以不加<T>,因为在类内已经声明了!和下面的mySub完全不一样
    return temp;
 }

template<typename T>
ostream & operator<<(ostream &os,Complex<T> &c)
{
    os<<"("<<c.a<<"+"<<c.b<<"i)"<<endl;
    return os;
}

template<typename T>
Complex<T> mySub(Complex<T> &c1,Complex<T> &c2)
{
    Complex<T> temp(c1.a - c2.a ,c1.b - c2.b);//这里构造函数记得加<T>
    return temp;
}

//-------------------------------------main.cpp-------------------------------------------------
#include <iostream>
#include "Complex.h"
//#include "Complex.cpp" //引入cpp文件之后才能运行,因为二次编译的缘故!
//但是因为cpp文件的封装性,不能够引入
#include "Complex.hpp"//改名!!!把cpp尾缀改成hpp
using namespace std;

//模板类的方法的实现不能够用多文件编写
//如果实现多文件 由于二次编译 真正地实现体是在cpp文件定义的,需要引入cpp头文件
int main(void)
{
   
   Complex<int> c1(10,20);
   c1.printComplex();

   Complex<int> c2(1,2);
   Complex<int> c3;

   c3 = c1 + c2;
   c3.printComplex();

   Complex<int> c4;
   c4 = c1 - c2;

   //重构<<
   cout<<c4;//报错了,这里有个c++编译器的坑

   Complex c5;
   c5 = mySub(c1,c2);//报错!!!//改完之后可以了,不建议滥用友元!

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

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

3.模板类中的static

2.png
#include <iostream>

using namespace std;

template <typename T>
class A
{
public:
private:
    T value;
    static T a;
};

template <typename T>
T A<T>::a = 0;//类中的静态成员需要在类的外部进行初始化

int main(void)
{
    //1.模板类通过二次编译根据调用的代码生成了两个不同的类A
    //一个是A<int> 一个是A<char>
   
   A<int> a1,a2,a3;
   A<char> b1,b2,b3;
   //a1 b1不是同一种类,所以static静态变量不是共用的!
   //证明:
   A<int>::a = 20;   //改变A<int>的静态成员   
   A<char>::a = 'X'; //改变A<char>的静态成员
 

   cout<<"a1:a="<<a1.a<<endl;//20
   cout<<"b1:a="<<b1.a<<endl;//X

   //a1 a2 a3是同一个类,静态变量共用的,b1 b2 b3同理!

   cout<<"a1:a="<<a2.a<<endl;//20
   cout<<"b1:a="<<b2.a<<endl;//X

   cout<<"a1:a="<<a3.a<<endl;//20
   cout<<"b1:a="<<b3.a<<endl;//X

   return 0;
}

4.练习 实现一个模板数组类

1.请设计一个数组类模板(MyVector),完成对int、char、Teacher类型元素的管理
2.需要实现构造函数、拷贝构造函数、<<、[]、=操作符

//自定义一个模板数组类
//1.该数组能够存放int double 还有自定义类型
//2.[] array[i] = ??
//3.cout<<array
//4.array1 = array2
//5.array1(array2)
//6.array[i] = array[j]
#ifndef __MYVECTOR_H__
#define __MYVECTOR_H__
#include <iostream>
using namespace std;
template<typename T>
class MyVector  //数组:插入、删除、修改、查找
{
public:

  MyVector();        //无参构造
  MyVector(int len); //有参构造
  MyVector(const MyVector &another);//拷贝构造

  T & operator[](int index);
  MyVector & operator=(const MyVector &another);

  friend ostream &operator << <T>(ostream &os,MyVector<T> &vector);
  #if 0 //注意有两种写法
  friend ostream &operator <<(ostream &os,MyVector<T> &vector){
    for (int i = 0; i < vector.len; ++i)
    {
        os<<vector[i]<<" ";//[]重载了,不然就是vector.a[i]了
    }
    os<<endl;
    return os;
  }
  #endif

  ~MyVector();
private:
   T* a;
   int len;
}
#endif
------------------------------------------------MyVector.hpp-----------------------------------
#include "MyVector.h"
template<typename T>
MyVector<T>::MyVector(){
    this->a = NULL;
    this->len = 0;
}

template<typename T>
MyVector<T>::MyVector(int len){
    this->len = len;
    if (this->a = NULL)this->a = new T[len];//在堆上连续开辟了sizeof(T)*len的空间
}

template<typename T>
MyVector<T>::MyVector(const MyVector &another)
{
    // if (this == another)return; 
    // delete[] this->a;
    this->len = another->len;
    this->a = new T[this->len];
    for(int i = 0; i < another.len;i++){
        this->a[i] = another.a[i];//如果存储对象,那么这个=号也是要重载实现深拷贝的!
    }
}

template<typename T>
T & MyVector::operator[](int index){
    return this->a[index];
}

template<typename T>
MyVector<T> & MyVector::operator=(const MyVector &another){
    if (this == another)return; 
    if(this->a != NULL)delete[] this->a;

    this->len = another.len;
    this->a = new T[this->len];
    for(int i = 0; i < another.len;i++){
        this->a[i] = another.a[i];
    }

    return *this;
}


template<typename T>
ostream & operator <<(ostream &os,MyVector<T> &vector){//<< <T>不是在定义上面写,而是在声明处写
    for (int i = 0; i < vector.len; ++i)
    {
        os<<vector[i]<<" ";//[]重载了,不然就是vector.a[i]了
    }
    return os;
}


MyVector::~MyVector(){
    if (this->a != NULL)
    {
        delete[] this->a;
        this->a = NULL;
        this->len = 0;
    }
}
--------------------------------------Teacher.h------------------------------

class Teacher
{
public:
    Teacher(){
        this->name = NULL;
        this->id = 0;
    }
    
    Teacher(int id,char *name)
    {
       this->id = id;

       int len = strlen(name);

       this->name = new char[len + 1];

       strcpy(this->name,name);
    }

    Teahcer(const Teahcer &t){
        this->id = id;
        int len = strlen(t.name);

        this->name =new char[len + 1];
        strcpy(this->name,t.name);
    }


    Teahcer & operator = (const Teahcer &t){

        if (this->name != NULL)
        {
            delete[] this->name;
            this->name = NULL;
            this->id = 0;
        }

        this->id = t.id;
        int len = strlen(t.name);
        this->name = new char[len + 1];
        strcpy(this->name,t.name);

        return *this;
    }
  

    void printTeacher(){
        cout<<"id = "<<this->id<<",name = "<<this->name<<endl;
    }
    friend ostream & operator <<(ostream &os,Teahcer &t);

    ~Teahcer(){
        if (this->name != NULL)
        {
            delete[] this->name;
            this->name = NULL;
            this->id = 0;
        }
    }
private:
    int id;
    char *name;
}

ostream & operator <<(ostream &os,Teahcer &t)
{
    t.printTeacher();
    return os;
}
--------------------------------------main.cpp------------------------------
#include <iostream>
#include "MyVector.h"
#include "MyVector.hpp"
using namespace std;
void test1(){
    MyVector<int> myVector1(10);

   for (int i = 0; i < 10; ++i)
   {
      myVector1[i] = i + 10;
   }

   cout<<"vector1"<<endl;
   cout<<myVector1<<endl;


   MyVector<int> myVector2(myVector1);//拷贝构造

   cout<<"vector2"<<endl;
   cout<<myVector2<<endl;

   MyVector<int> myVector3;
   myVector3 = myVector2;//=重载

   cout<<"vector3"<<endl;
   cout<<myVector3<<endl;
}


void test2(){
    //创建一些Teacher对象
    Teahcer t1(5,"zhang3");
    Teahcer t2(1,"zhang43");
    Teahcer t3(2,"zhang5");
    Teahcer t4(3,"zhang6");
    Teahcer t5(4,"zhang7");
    
    MyVector<Teahcer> myVector1(5);

    myVector1[0] = t1;//myVector.opetator[](0) = t1 和 teacher.operator=(t1)
    myVector1[1] = t2;
    myVector1[2] = t3;
    myVector1[3] = t4;
    myVector1[4] = t5;

    for (int i = 0; i < 5; ++i)
    {
        cout<<myVector1[i];
    }

}

int main(void)
{
    test1();
    test2()

   return 0;
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容