精华内容
参与话题
问答
  • C++:模板总结

    万次阅读 多人点赞 2019-03-16 12:34:26
    写在前面     &...模板(Template)指C++程序设计设计...模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成...

    写在前面

            模板(Template)指C++程序设计设计语言中采用类型作为参数的程序设计,支持通用程序设计。C++ 的标准库提供许多有用的函数大多结合了模板的观念,如STL以及IO Stream。模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。

    模板是一种对类型进行参数化的工具;

    通常有两种形式:函数模板和类模板;

    函数模板 针对仅参数类型不同的函数;
    类模板 针对仅数据成员和成员函数类型不同的类。

    使用模板的目的就是能够让程序员编写与类型无关的代码。

    1.函数模板

     template <class 形参名,class 形参名,......> 
     返回类型 函数名(参数列表)
    
       {
    
          函数体
    
       }
    

    其中template和class是关键字,class可以用typename 关键字代替,在这里typename 和class没区别,<>括号中的参数叫模板形参,模板形参和函数形参很相像,模板形参不能为空。一但声明了模板函数就可以用模板函数的形参名声明类中的成员变量和成员函数,即可以在该函数中使用内置类型的地方都可以使用模板形参名。模板形参需要调用该模板函数时提供的模板实参来初始化模板形参,一旦编译器确定了实际的模板实参类型就称他实例化了函数模板的一个实例。比如swap的模板函数形式为

          template <class T> void swap(T& a, T& b){}

    当调用这样的模板函数时类型T就会被被调用时的类型所代替,比如swap(a,b)其中a和b是int 型,这时模板函数swap中的形参T就会被int 所代替,模板函数就变为swap(int &a, int &b)。而当swap(c,d)其中c和d是double类型时,模板函数会被替换为swap(double &a, double &b),这样就实现了函数的实现与类型无关的代码。

    注意:对于函数模板而言不存在 h(int,int) 这样的调用,不能在函数调用的参数中指定模板形参的类型,对函数模板的调用应使用实参推演来进行,即只能进行 h(2,3) 这样的调用,或者int a, b; h(a,b)。

    2.类模板

    1、类模板的格式为:

        template<class  形参名,class 形参名,…>
        class 类名{ ... };
    

    类模板和函数模板都是以template开始后接模板形参列表组成,模板形参不能为空,一但声明了类模板就可以用类模板的形参名声明类中的成员变量和成员函数,即可以在类中使用内置类型的地方都可以使用模板形参名来声明。比如

        template<class T> class A
        {
           public: 
           T a;
           T b; 
           T hy(T c, T &d);
           };
    

    在类A中声明了两个类型为T的成员变量a和b,还声明了一个返回类型为T带两个参数类型为T的函数hy。

    2、类模板对象的创建:比如一个模板类A,则使用类模板创建对象的方法为A m;在类A后面跟上一个<>尖括号并在里面填上相应的类型,这样的话类A中凡是用到模板形参的地方都会被int 所代替。当类模板有两个模板形参时创建对象的方法为A<int, double> m;类型之间用逗号隔开。

    3、对于类模板,模板形参的类型必须在类名后的尖括号中明确指定。比如A<2> m;用这种方法把模板形参设置为int是错误的(编译错误:error C2079: ‘a’ uses undefined class ‘A’),类模板形参不存在实参推演的问题。也就是说不能把整型值2推演为int 型传递给模板形参。要把类模板形参调置为int 型必须这样指定A m。

    4、在类模板外部定义成员函数的方法为:

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

    比如有两个模板形参T1,T2的类A中含有一个void h()函数,则定义该函数的语法为:

        template<class T1,class T2> void A<T1,T2>::h(){}

    注意:当在类外面定义类的成员时template后面的模板形参应与要定义的类的模板形参一致。

    5、再次提醒注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。

    3.模板的非类型形参

    1 、非类型模板形参:模板的非类型形参也就是内置类型形参,如template<class T, int a> class B{};其中int a就是非类型的模板形参。

    2、 非类型形参在模板定义的内部是常量值,也就是说非类型形参在模板的内部是常量。

    3、 非类型模板的形参只能是整型,指针和引用,像double,String, String **这样的类型是不允许的。但是double &,double *,对象的引用或指针是正确的。

    4、 调用非类型模板形参的实参必须是一个常量表达式,即他必须能在编译时计算出结果。

    5 、注意:任何局部对象,局部变量,局部对象的地址,局部变量的地址都不是一个常量表达式,都不能用作非类型模板形参的实参。全局指针类型,全局变量,全局对象也不是一个常量表达式,不能用作非类型模板形参的实参。

    6、 全局变量的地址或引用,全局对象的地址或引用const类型变量是常量表达式,可以用作非类型模板形参的实参。

    7 、sizeof表达式的结果是一个常量表达式,也能用作非类型模板形参的实参。

    8 、当模板的形参是整型时调用该模板时的实参必须是整型的,且在编译期间是常量,比如template <class T, int a> class A{};如果有int b,这时A<int, b> m;将出错,因为b不是常量,如果const int b,这时A<int, b> m;就是正确的,因为这时b是常量。

    9 、非类型形参一般不应用于函数模板中,比如有函数模板template<class T, int a> void h(T b){},若使用h(2)调用会出现无法为非类型形参a推演出参数的错误,对这种模板函数可以用显示模板实参来解决,如用h<int, 3>(2)这样就把非类型形参a设置为整数3。显示模板实参在后面介绍。

    10、 非类型模板形参的形参和实参间所允许的转换
          1、允许从数组到指针,从函数到指针的转换。如:template <int *a> class A{}; int b[1]; A m;即数组到指针的转换
          2、const修饰符的转换。如:template<const int *a> class A{}; int b; A<&b> m; 即从int *到const int *的转换。
          3、提升转换。如:template class A{}; const short b=2; A m; 即从short到int 的提升转换
          4、整值转换。如:template class A{}; A<3> m; 即从int 到unsigned int的转换。
          5、常规转换。

    4.类模板的默认模板类型形参

    1、可以为类模板的类型形参提供默认值,但不能为函数模板的类型形参提供默认值。函数模板和类模板都可以为模板的非类型形参提供默认值。

    2、类模板的类型形参默认值形式为:template<class T1, class T2=int> class A{};为第二个模板类型形参T2提供int型的默认值。

    3、类模板类型形参默认值和函数的默认参数一样,如果有多个类型形参则从第一个形参设定了默认值之后的所有模板形参都要设定默认值,比如templateclass A{};就是错误的,因为T1给出了默认值,而T2没有设定。

    4、在类模板的外部定义类中的成员时template 后的形参表应省略默认的形参类型。比如template<class T1, class T2=int> class A{public: void h();}; 定义方法为template<class T1,class T2> void A<T1,T2>::h(){}。

    5.模板的实例化

    总结一下,C++只有模板显式实例化(explicit instantiation),隐式实例化(implicit instantiation),特化(specialization,也译作具体化,偏特化)。首先考虑如下模板函数代码:

    [cpp] view plaincopyprint?
    template <typename T>  
    void swap(T &a, T &b){  
    ...  
    }  
    

    1.隐式实例化
    我们知道,模板函数不是真正的函数定义,他只是如其名提供一个模板,模板只有在运行时才会生成相应的实例,隐式实例化就是这种情况:

    [cpp] view plaincopyprint?
    int main(){  
        ....  
        swap<int>(a,b);  
        ....  
    }  
    

    它会在运行到这里的时候才生成相应的实例,很显然的影响效率
    这里顺便提一下swap(a,b);中的是可选的,因为编译器可以根据函数参数类型自动进行判断,也就是说如果编译器不不能自动判断的时候这个就是必要的;

    2.显式实例化

    前面已经提到隐式实例化可能影响效率,所以需要提高效率的显式实例化,显式实例化在编译期间就会生成实例,方法如下:

    [cpp] view plaincopyprint?
    template void swap<int>(int &a,int &b);  
    

    这样就不会影响运行时的效率,但编译时间随之增加。

    3.特化

    这个swap可以处理一些基本类型如long int double,但是如果想处理用户自定义的类型就不行了,特化就是为了解决这个问题而出现的:

    [cpp] view plaincopyprint?
    template <> void swap<job>(job a,job b){...}  
    

    其中job是用户定义的类型.

    6.模板的特化(具体化)和偏特化

    类模板:

    测试代码如下:
    
    #include <iostream>
    using namespace std;
    template<typename T1,typename T2>
    class Test{
    public:
        Test(T1 i,T2 j):a(i),b(j){cout<<"模板类"<<endl;}
    private:
        T1 a;
        T2 b;
    };
    template<>   //全特化,由于是全特化,参数都指定了,参数列表故为空。
    class Test<int ,char>{
    public:
        Test(int i,char j):a(i),b(j){cout<<"全特化"<<endl;}
    private:
        int a;
        int b;
    };
    template<typename T2> //由于只指定了一部分参数,剩下的未指定的需在参数列表中,否则报错。
    class Test<char,T2>{
    public:
        Test(char i,T2 j):a(j),b(j){cout<<"个数偏特化"<<endl;}
    private:
        char a;
        T2 b;
    };
    template<typename T1,typename T2> //这是范围上的偏特化
    class Test<T1*,T2*>{
    public:
        Test(T1* i,T2* j):a(i),b(j){cout<<"指针偏特化"<<endl;}
    private:
        T1* a;
        T2* b;
    };
    template<typename T1,typename T2>//同理这也是范围上的偏特化
    class Test<T1 const,T2 const>{
    public:
        Test(T1 i,T2 j):a(i),b(j){cout<<"const偏特化"<<endl;}
    private:
        T1 a;
        T2 b;
    };
    int main()
    {
        int a;
        Test<double,double> t1(0.1,0.2);
        Test<int,char> t2(1,'A');
        Test<char,bool> t3('A',true);
        Test<int*,int*> t4(&a,&a);
        Test<const int,const int> t5(1,2);
        return 0;
    }
    

    运行结果截图:
    在这里插入图片描述
    函数模板:
    而对于函数模板,却只有全特化,不能偏特化:

    #include <iostream>
    using namespace std;
    //模板函数
    template<typename T1,typename T2>
    void fun(T1 a,T2 b){
        cout<<"模板函数"<<endl;
    }
    //全特化
    template<>
    void fun(int a,char b){
        cout<<"全特化"<<endl;
    }
    //函数不存在偏特化,以下代码是错误的
    /*
    template<typename T2>
    void fun(char a,T2 b){
        cout<<"偏特化"<<ednl;
    }
    */
    int main()
    {
        int a=0;
        char b='A';
        fun(a,a);
        fun(a,b);
        return 0;
    }
    

    运行截图如下:
    在这里插入图片描述

    7.模板类的继承

    模板类的继承包括四种:

    1.(普通类继承模板类)

    2.(模板类继承了普通类(非常常见))

    3.(类模板继承类模板)

    4.(模板类继承类模板,即继承模板参数给出的基类)

    其中,普通类继承模板类比较简单,如

    1 template<class T>
    2 class TBase{
    3     T data;
    4 ……
    5 };
    6 class Derived:public TBase<int>{
    7 ……
    8 };
    

    模板类继承普通类:

    1 class TBase{
    2 ……
    3 };
    4 template<class T>
    5 class TDerived:public TBase{
    6 T data;
    7 ……
    8 };
    

    类模板继承类模板:

     1 template<class T>
     2 class TBase{
     3 T data1;
     4 ……
     5 };
     6 template<class T1,class T2>
     7 class TDerived:public TBase<T1>{
     8 T2 data2;
     9 ……
    10 };
    
    1. 模板类继承模板参数给出的基类
      ——继承哪个基类由模板参数决定
    #include<iostream>
    using namespace std;
    
    
    class BaseA{
    public:
        BaseA(){cout<<"BaseA founed"<<endl;}
    };
    
    class BaseB{
    public:
        BaseB(){cout<<"BaseB founed"<<endl;}
    };
    
    template<typename T, int rows>
    class BaseC{
    private:
        T data;
    public:
        BaseC():data(rows){
            cout<<"BaseC founed "<< data << endl;}
    };
    
    template<class T>
    class Derived:public T{
    public:
        Derived():T(){cout<<"Derived founed"<<endl;}
    };
    
    
    void main()
    {
        Derived<Base A> x;// BaseA作为基类
        Derived<Base B> y;// BaseB作为基类
        Derived<Base C<int, 3> > z; // BaseC<int,3>作为基类
        
    

    模板实例化问题:

            在我们使用类模板时,只有当代码中使用了类模板的一个实例的名字,而且上下文环境要求必须存在类的定义时,这个类模板才被实例化。

    1.声明一个类模板的指针和引用,不会引起类模板的实例化,因为没有必要知道该类的定义。

    2.定义一个类类型的对象时需要该类的定义,因此类模板会被实例化。

    3.在使用sizeof()时,它是计算对象的大小,编译器必须根据类型将其实例化出来,所以类模板被实例化.

    4.new表达式要求类模板被实例化。

    5.引用类模板的成员会导致类模板被编译器实例化。

    6.需要注意的是,类模板的成员函数本身也是一个模板。标准C++要求这样的成员函数只有在被调用或者取地址的时候,才被实例化。用来实例化成员函数的类型,就是其成员函数要调用的那个类对象的类型

    展开全文
  • 模版

    2017-10-04 17:34:00
    模版的概念 ...... 函数模版 函数模版模版函数 ...类模版模板类 ...... 类模版的派生 ...... PS:有时间再说吧,就这样...... v、 转载于:https://www.cnblogs.com/tenjl-exv/p/7625881.html...

    模版的概念

    ......

    函数模版

    函数模版和模版函数

    ......

    重载模版函数

    ......

    类模版

    类模版和模板类

    ......

    类模版的派生

    ......

     

    PS:有时间再说吧,就这样......

    v、

    转载于:https://www.cnblogs.com/tenjl-exv/p/7625881.html

    展开全文
  • 模板

    2017-08-03 10:07:04
    函数模版: v 函数模板可以用来创建一个通用功能的函数,以支持多种不同形参,进一步简化重载函数的函数体设计。 v 定义方法: n template > 函数定义 v 模板参数表的内容 n 类型参数:class...

    函数模版:

    函数模板可以用来创建一个通用功能的函数,以支持多种不同形参,进一步简化重载函数的函数体设计。

    定义方法:

    template <模板参数表>

    函数定义

    模板参数表的内容

    类型参数:class(或typename) 标识符

    常量参数:类型说明符  标识符

    求绝对值函数的模板:

    template<typename T>

    T abs(T x) {

    return x < 0? -x : x;

    }

    求绝对值函数的模板分析:

    编译器从调用abs()时实参的类型,推导出函数模板的类型参数。例如,对于调用表达式abs(n),若实参nint型,所以推导出模板中类型参数Tint

    当类型参数的含义确定后,编译器将以函数模板为样板,生成一个函数:
    int abs(int x) {
      return x < 0 ? x : x;
    }

    类模板的作用:

    使用类模板使用户可以为类声明一种模式,使得类中的某些数据成员、某些成员函数的参数、某些成员函数的返回值,能取任意类型(包括基本类型的和用户自定义类型)。

     

    类模板的声明:

    类模板:

    template <模板参数表>

    class 类名

    {类成员声明}

    如果需要在类模板以外定义其成员函数,则要采用以下的形式:

    template <模板参数表>

    类型名 类名<模板参数标识符列表>::函数名(参数表)

     

    Ø 群体数据:

    线性群体

    线性群体的概念

    直接访问群体--数组类

    顺序访问群体--链表类

    栈类

    队列类

     

    群体的概念:

    群体是指由多个数据元素组成的集合体。群体可以分为两个大类:线性群体和非线性群体。

    线性群体中的元素按位置排列有序,可以区分为第一个元素、第二个元素等。

    非线性群体不用位置顺序来标识元素。

    线性群体中的元素次序与其位置关系是对应的。在线性群体中,又可按照访问元素的不同方法分为直接访问、顺序访问索引访问

    数组:

    静态数组是具有固定元素个数的群体,其中的元素可以通过下标直接访问。

    缺点:大小在编译时就已经确定,在运行时无法修改。

    动态数组由一系列位置连续的,任意数量相同类型的元素组成。

    优点:其元素个数可在程序运行时改变。

    vector就是用类模板实现的动态数组。

    数组类模板的构造函数:

    // 构造函数
    template <class T>
    Array<T>::Array(int sz) {
    
    	 //sz为数组大小(元素个数),应当非负,其作用是如果它的条件返回错误,则终止程序执行
    	assert(sz >= 0);	
    
    	// 将元素个数赋值给变量size
    	size = sz;
    
    	//动态分配size个T类型的元素空间
    	list = new T [size];	
    }

    数组类模板的拷贝构造函数:

    //拷贝构造函数
    template <class T>
    Array<T>::Array(const Array<T> &a) {
    	//从对象x取得数组大小,并赋值给当前对象的成员
    	size = a.size;
    	//为对象申请内存并进行出错检查
    	list = new T[size];// 动态分配n个T类型的元素空间
    	//从对象X复制数组元素到本对象  
    	for (int i = 0; i < size; i++)
    		list[i] = a.list[i];
    }

    数组类模板的重载"="运算符函数

    //重载“=”运算符
    template <class T>
    Array<T> &Array<T>::operator = (const Array<T>& rhs) {
    	if (&rhs != this) {
    		if (size != rhs.size) {
    			delete [] list;	//删除数组原有内存
    			size = rhs.size;	//设置本对象的数组大小
    			list = new T[size];	//重新分配n个元素的内存
    		}
    		//从对象X复制数组元素到本对象  
    		for (int i = 0; i < size; i++)
    			list[i] = rhs.list[i];
    	}
    	return *this;	//返回当前对象的引用
    }


    数组类模板的重载下标操作符函数

    template <class T>
    T &Array<T>::operator[] (int n) {
    	assert(n >= 0 && n < size);//越界检查
    	return list[n];	 //返回下标为n的数组元素
    }
    
    template <class T>
    const T &Array<T>::operator[] (int n) const {
    	assert(n >= 0 && n < size); //越界检查
    	return list[n];	 //返回下标为n的数组元素
    }


    为什么有的函数返回引用:

    如果一个函数的返回值是一个对象的值,就不应成为左值。(+ -的重载)

    如果返回值为引用。由于引。([]的重载,前置++和后置++用是对象的别名,通过引用当然可以改变对象的值)

    链表:

    链表是一种动态数据结构,可以用来表示顺序访问的线性群体。

    链表是由系列结点组成的,结点可以在运行时动态生成。

    每一个结点包括数据域和指向链表中下一个结点的指针(即下一个结点的地址)。如果链表每个结点中只有一个指向后继结点的指针,则该链表称为单链表。

     

    单链表的结点类模板:

    template<class T>
    class Node {
    private:
      Node *next;
    public:
      T data;
      Node(const T& item,Node* next = 0);
      void insertAfter(Node *p);
      Node *deleteAfter();
      Node *nextNode() const;
    };    
    


    链表的基本操作:

    生成结点

    插入结点

    查找结点

    删除结点

    遍历链表

    清空链表

     

    特殊的线性群体——栈:

    栈是只能从一端访问的线性群体,可以访问的这一端称栈顶,另一端称栈底。(先进后出)

    特殊的线性群体——队列:

    队列是只能向一端添加元素,从另一端删除元素的线性群体(先进先出)

    会在后面的STL中详细介绍。

     

    循环队列:

    在想象中将数组弯曲成环形,元素出队时,后继元素不移动,每当队尾达到数组最后一个元素时,便 再回到数组开头。

     

    Ø 群体数据的组织:

    插入排序

    选择排序

    交换排序

    顺序查找

    折半查找

     

    排序(sorting

    排序是计算机程序设计中的一种重要操作,它的功能是将一个数据元素的任意序列,重新排列成一个按关键字有序的序列。

    数据元素:数据的基本单位。在计算机中通常作为一个整体进行考虑。一个数据元素可由若干数据项组成。

    关键字:数据元素中某个数据项的值,用它可以标识(识别)一个数据元素。

    在排序过程中需要完成两种基本操作:

    比较两个数的大小

    调整元素在序列中的位置

    内部排序与外部排序:

    内部排序:待排序的数据元素存放在计算机内存中进行的排序过程。

    外部排序:待排序的数据元素数量很大,以致内存存中一次不能容纳全部数据,在排序过程中尚需对外存进行访问的排序过程。

    内部排序方法:

    插入排序

    每一步将一个待排序元素按其关键字值的大小插入到已排序序列的适当位置上,直到待排序元素插入 完为止。

    选择排序

    每次从待排序序列中选择一个关键字最小的元素,(当需要按关键字升序排列时),顺序排在已排序序 列的最后,直至全部排完。

    交换排序

    两两比较待排序序列中的元素,并交换不满足顺序要求的各对元素,直到全部满足顺序要求为止

    最简单的交换排序方法
                ——起泡排序

    对具有n个元素的序列按升序进行起泡排序的步骤:

    首先将第一个元素与第二个元素进行比较,若为逆序,则将两元素交换。然后比较第二、第三个元素,依次类推,直到第n-1和第n个元素进行了比较和交换。此过程称为第一趟起泡排序。经过第一趟,最大的元素便被交换到第n个位置。

    对前n-1个元素进行第二趟起泡排序,将其中最大元素交换到第n-1个位置。

    如此继续,直到某一趟排序未发生任何交换时,排序完毕。对n个元素的序列,起泡排序最多需要进行n-1趟。

     

    起泡排序函数模板:

    template <class T>
    void mySwap(T &x, T &y) {
    	T temp = x;
    	x = y;
    	y = temp;
    }
    template <class T>
    void bubbleSort(T a[], int n) {
    	int i = n – 1;
    	while (i > 0) {
    		int lastExchangeIndex = 0;
    		for (int j = 0; j < i; j++)
    			if (a[j + 1] < a[j]) {	
    				mySwap(a[j], a[j + 1]);
    				lastExchangeIndex = j;
    			}
    		i = lastExchangeIndex;
    	}
    }


    顺序查找

    其基本思想

    从序列的首元素开始,逐个元素与待查找的关键字进行比较,直到找到相等的。若整个序列中没有与待查找关键字相等的元素,就是查找不成功。

    顺序查找函数模板:

    template <class T>
    int seqSearch(const T list[], int n, const T &key) {
    	for(int i = 0; i < n; i++)
    		if (list[i] == key)
    			return i;            
    	return -1;                 
    }


    折半查找的基本思想:

    对于已按关键字排序的序列,经过一次比较,可将序列分割成两部分,然后只在有可能包含待查元素的一部分中继续查找,并根据试探结果继续分割,逐步缩小查找范围,直至找到或找不到为止。

    折半查找函数模板:

    template <class T>
    int binSearch(const T list[], int n, const T &key) {
    	int low = 0;
    	int high = n - 1;
    	while (low <= high) {	
    		int mid = (low + high) / 2;
    		if (key == list[mid])    
    			return mid;	
    		else if (key < list[mid])
    			high = mid – 1;
    		else
    			low = mid + 1;
    	}
    	return -1;
    }




     

     

     

     

     

     

    展开全文
  • C++Template 模版的本质

    万次阅读 多人点赞 2018-05-01 16:28:04
    C++ Template 模版的本质 自动化是人类进化的动力 AlexCool 本文出现的目的,就是尽量让人们理解C++模版设计的思想, 属于模板的心法。 我想知道上帝是如何创造这个世界的。我对这个或那个现象,这个或那个元素...

        C++ Template  模版的本质

         自动化是人类进化的动力

      AlexCool

                                 本文出现的目的,就是尽量让人们理解C++模版设计的思想, 属于模板的心法。

     

    我想知道上帝是如何创造这个世界的。我对这个或那个现象,这个或那个元素的能谱不感兴趣。我要知道的是他的思想。其他都是细节。

    ——爱因斯坦

     

    模版最初的目的就是为了减少重复代码,所以模版出现的目的就是为了解放C++程序员生产力。

     content
    
     C++模版是什么
    
         什么是参数化容器类
    
         什么是通用算法
    
    模板是怎么解决上面问题的
    
          C++实现参数化类(class template)技术
    
          C++实现模板函数(function template)技术
    
    实现C++模板的几个核心技术
    
    模版典型的应用场景有哪些
    
    对模版的展望
    
    更多细节请参考资料和进一步阅读

     

     C++模版是什么?

    程序 = 数据结构  + 算法

                                                  ---Niklaus EmilWirth

    最初C++是没有标准库的,任何一门语言的发展都需要标准库的支持,为了让C++更强大,Bjarne Stroustrup觉得需要给C++提供一个标准库,但标准库设计需要一套统一机制来定义各种通用的容器(数据结构)和算法,并且能很好在一起配合,这就需要它们既要相对的独立,又要操作接口保持统一,而且能够很容易被别人使用(用到实际类中),同时又要保证开销尽量小(性能要好)。 Bjarne Stroustrup 提议C++需要一种机制来解决这个问题,所以就催生了模板的产生,最后经标准委员会各路专家讨论和发展,就发展成如今的模版, C++ 第一个正式的标准也加入了模板。

    C++模版是一种解决方案,初心是提供参数化容器类和通用的算法(函数)。

    什么是参数化容器类?

    首先C++是可以提供OOP(面向对象)范式编程的语言,所以支持类概念,类本身就是现实中一类事物的抽象,包括状态和对应的操作,打个比喻,大多数情况下我们谈论汽车,并不是指具体某辆汽车,而是某一类汽车(某个品牌),或者某一类车型的汽车。

    所以我们设计汽车这个类的时候,各个汽车品牌的汽车大体框架(骨架)都差不多,都是4个轮子一个方向盘,而且操作基本上都是相同的,否则学车都要根据不同厂商汽车进行学习,所以我们可以用一个类来描述汽车的行为:

    class Car
    
    {
    
    public:
    
      Car(...);
    
      //other operations
    
      ...
    
    private:
    
    Tire m_tire[4];
    
    Wheel m_wheel;
    
    //other attributes
    
     ...
    
    };

    但这样设计扩展性不是很好,因为不同的品牌的车,可能方向盘形状不一样,轮胎外观不一样等等。所以要描述这些不同我们可能就会根据不同品牌去设计不同的类,这样类就会变得很多,就会产生下面的问题:

    1 代码冗余,会产生视觉复杂性,本身相似的东西比较多;

    2 用户很难通过配置去实现一辆车设计,不好定制化一个汽车;

    3 如果有其中一个属性有新的变化,就得实现一个新类,扩展代价太大。

    这个时候,就希望这个类是可以参数化的(属性参数化),可以根据不同类型的参数进行属性配置,继而生成不同的类。

    类模板就应运而生了,类模板就是用来实现参数化的容器类。

     

    什么是通用算法?

    程序=数据结构+算法;

    算法就是对容器的操作,对数据结构的操作,一般算法设计原则要满足KISS原则,功能尽量单一,尽量通用,才能更好和不同容器配合,有些算法属于控制类算法(比如遍历),还需要和其他算法进行配合,所以需要解决函数参数通用性问题。举个例子:

    以前我们实现通用的排序函数可能是这样:

    void sort (void* first, void* last, Cmp cmp);

    这样的设计有下面一些问题:

    1.为了支持多种类型,需要采用void*参数,但是void*参数是一种类型不安全参数,在运行时候需要通过类型转换来访问数据。

    2.因为编译器不知道数据类型,那些对void*指针进行偏移操作(算术操作)会非常危险(GNU支持),所以操作会特别小心,这个给实现增加了复杂度。

    所以要满足通用(支持各种容器),设计复杂度低,效率高,类型安全的算法,模板函数就应运而生了,模板函数就是用来实现通用算法并满足上面要求。

     

    模板是怎么解决上面问题的?

    C++标准委员会采用一套类似函数式语言的语法来设计C++模板,而且设计成图灵完备 (Turing-complete)(详见参考),我们可以把C++模板看成是一种新的语言,而且可以看成是函数式编程语言,只是设计依附在(借助于)C++其他基础语法上(类和函数)。

     

     

     C++实现参数化类(class template)技术:

    1.定义模板类,让每个模板类拥有模板签名。

    模板类语法:

    template<typename T>
    
    class X{...};

     

    • 上面的模板签名可以理解成:X<typename T>; 主要包括模板参数<typename T>和模板名字X(类名), 基本的语法可以参考《C++ Templates: The Complete Guide》,《C++ primer》等书籍。
    • 模板参数在形式上主要包括四类:  

         为什么会存在这些分类,主要是满足不同类对参数化的需求:

    • type template parameter,类型模板参数,以class或typename 标记;
      • 此类主要是解决朴实的参数化类的问题(上面描述的问题),也是模板设计的初衷。
    • non-type template parameter,非类型模板参数,比如整型,布尔,枚举,指针,引用等;
      • 此类主要是提供给大小,长度等整型标量参数的控制,其次还提供参数算术运算能力,这些能力结合模板特化为模板提供了初始化值,条件判断,递归循环等能力,这些能力促使模板拥有图灵完备的计算能力。
    • template template parameter,模板参数是模板;
      • 此类参数需要依赖其他模板参数(作为自己的入参),然后生成新的模板参数,可以用于策略类的设计policy-base class。
    •  parameter pack,C++11的变长模板参数 ;
      • 此类参数是C++11新增的,主要的目的是支持模板参数个数的动态变化,类似函数的变参,但有自己独有语法用于定义和解析(unpack),模板变参主要用于支持参数个数变化的类和函数,比如std::bind,可以绑定不同函数和对应参数,惰性执行,模板变参结合std::tuple就可以实现。

    2.在用模板类声明变量的地方,把模板实参(Arguments)(类型)带入模板类,然后按照匹配规则进行匹配,选择最佳匹配模板.

    • 模板实参和形参类似于函数的形参和实参,模板实参只能是在编译时期确定的类型或者常量,C++17支持模板类实参推导。

    3.选好模板类之后,编译器会进行模板类实例化--记带入实际参数的类型或者常量自动生成代码,然后再进行通常的编译。

     

    C++实现模板函数(function template)技术:

    模板函数实现技术和模板类形式上差不多:

    template<typename T>
    retType  function_name(T  t);

    其中几个关键点:

    1. 函数模板的签名包括模板参数,返回值,函数名,函数参数,  cv-qualifier;
    2. 函数模板编译顺序大致:名称查找(可能涉及参数依赖查找)->实参推导->模板实参替换(实例化,可能涉及 SFINAE)->函数重载决议->编译;
    3. 函数模板可以在实例化时候进行参数推导,必须知道每个模板的实参,但不必指定每个模板的实参。编译器会从函数实参推导缺失的模板实参。这发生在尝试调用函数、取函数模板地址时,和某些其他语境中;
    4. 函数模板在进行实例化后(template argument deduction/substitution)会进行函数重载解析(overloading resolution, 此时的函数签名不包括返回值;
    5. 函数模板实例化过程中,参数推导不匹配所有的模板或者同时存在多个模板实例满足,或者函数重载决议有歧义等,实例化失败;
    6. 为了编译函数模板调用,编译器必须在非模板重载、模板重载和模板重载的特化间决定一个无歧义最佳的模板;

     

    实现C++模板的几个核心技术:

    1. SFINAE -Substitution failure is not an error ;

          要理解这句话的关键点是failure和error在模板实例化中意义,模板实例化时候,编译器会用模板实参或者通过模板实参推导出参数类型带入可能的模板集(模板备选集合)中一个一个匹配,找到最优匹配的模板定义,

            Failure:在模板集中,单个匹配失败;

            Error: 在模板集中,所有的匹配失败;

    所以单个匹配失败,不能报错误,只有所有的匹配都失败了才报错误。

            2.  模板特化

          模板特化为了支持模板类或者模板函数在特定的情况(指明模板的部分参数(偏特化)或者全部参数(完全特化))下特殊实现和优化,而这个机制给与模板某些高阶功能提供了基础,比如模板的递归(提供递归终止条件实现),模板条件判断(提供true或者false 条件实现)等。

           3. 模板实参推导

    模板实参推导机制给与编译器可以通过实参去反推模板的形参,然后对模板进行实例化,具体推导规则见参考;

           4. 模板计算

    模板参数支持两大类计算:

      • 一类是类型计算(通过不同的模板参数返回不同的类型),此类计算为构建类型系统提供了基础,也是泛型编程的基础;
      • 一类是整型参数的算术运算, 此类计算提供了模板在实例化时候动态匹配模板的能力;实参通过计算后的结果作为新的实参去匹配特定模板(模板特化)。

           5. 模板递归

     模板递归是模板元编程的基础,也是C++11变参模板的基础。

     

    模版典型的应用场景有哪些?

    1. C++ Library:

     可以实现通用的容器(Containers)和算法(Algorithms),比如STL,Boost等,使用模板技术实现的迭代器(Iterators)和仿函数(Functors)可以很好让容器和算法可以自由搭配和更好的配合;

            2  C++ type traits

    通过模板技术,C++  type traits实现了一套操作类型特性的系统,C++是静态类型语言,所以需要再编译时候对类型进行检查,这个时候type traits可以提供更多类型信息给编译器,能让程序可以做出更多选择和优化。 C++创始人对traits的理解:

    ”Think of a trait as a small object whose main purpose is to carry information used by another object or algorithm to determine "policy" or "implementation details". - Bjarne Stroustrup

            3. Template metaprogramming-TMP

    随着模板技术的发展,模板元编程逐渐被人们发掘出来,metaprogramming本意是进行源代码生成的编程(代码生成器),同时也是对编程本身的一种更高级的抽象,好比我们元认知这些概念,就是对学习本身更高级的抽象。 TMP通过模板实现一套“新的语言”(条件,递归,初始化,变量等),由于模板是图灵完备,理论上可以实现任何可计算编程,把本来在运行期实现部分功能可以移到编译期实现,节省运行时开销,比如进行循环展开,量纲分析等。

                                                               (取自参考文献6)

            4. Policy-Based Class Design

    C++ Policy class design 首见于 Andrei Alexandrescu 出版的 《Modern C++ Design》 一书以及他在C/C++ Users Journal杂志专栏 Generic<Programming>,参考wiki。通过把不同策略设计成独立的类,然后通过模板参数对主类进行配置,通常policy-base class design采用继承方式去实现,这要求每个策略在设计的时候要相互独立正交。STL还结合CRTP (Curiously recurring template pattern)等模板技术,实现类似动态多态(虚函数)的静态多态,减少运行开销。

             5. Generic Programming

    由于模板这种对类型强有力的抽象能力,能让容器和算法更加通用,这一系列的编程手法,慢慢引申出一种新的编程范式:泛型编程。 泛型编程是对类型的抽象接口进行编程,STL库就是泛型编程经典范例。     

     

    对模版的展望

    1. 模版带来的缺点是什么?

         模板的代码和通常的代码比起来,可读性很差,所以很难维护,对人员要求非常高,开发和调试比较麻烦。 对模板代码,实际上很难覆盖所有的测试,所以为了保证代码的健壮性,需要大量高质量的测试,各个平台(编译器)支持力度不一样(模板递归深度,模板特性),可移植性不能完全保证。模板多个实例很有可能会隐式地增加二进制文件的大小等,所以模板在某些情况下有一定代价;

            2. 基于模板的设计模式

          随着C++模板技术的发展,以及大量的经验总结,逐渐形成了一些基于模板的经典设计,比如STL里面的特性(traits),策略(policy),标签(tag)等技法;Andrei Alexandrescu 提出的Policy-Based Class Design;以及Jim Coplien的curiously recurring template pattern (CRTP),以及衍生Mixin技法;或许未来,基于模板可以衍生更多的设计模式。

            3.  模板的未来

    C++标准准备引进Concepts(Concepts: The Future of Generic Programming by Bjarne Stroustrup)解决模板调试问题,对模板接口进行约束;随着模板衍生出来的泛型编程,模板元编程,模板函数式编程等理念的发展,将来也许会发展出更抽象,更通用编程理念。模板本身是图灵完备的,所以可以结合C++其他特性:编译期常量和常量表达式,编译期计算,继承,友元friend等开阔出更多优雅的设计,比如元容器,类型擦除,自省和反射等,将来会出现更多设计。

     

    希望本文对想学习C++模板的同学有一定的帮助,有不对的地方还请指正,多谢!

    更多细节请参考资料和进一步阅读:

            1《The design and Evolution of C++ 》Bjarne Stroustrup;

            2. C++ Templates are Turing Complete,Todd L. Veldhuizen,2003(作者网站已经停了,archive.org 保存的版本,archive.org 可能被限制浏览);

    3.  模板细节:

          wikipedia.org, cppreference.com(C++,模板template, Template metaprogramming, CRTP (Curiously recurring template pattern), Substitution failure is not an    error (SFINAE), template_argument_deduction ,Policy-based_class design, Expression templates,等);

                C++ Programming/Templates/Template Meta-Programming

           4.模板的基本语法:

             C++标准ISO+IEC+14882-1998,2003,2011;

           《C++ Templates: The Complete Guide》 by David Vandevoorde, Nicolai M. Josuttis;

           5.模板设计进价:

       Andrei Alexandrescu 的 《Modern C++ Design》;

         候捷的《STL源码剖析》;

            More C++ Idioms:wikipedia.org

           6. 模板元编程:

    Abrahams, David; Gurtovoy, Aleksey. C++ Template Metaprogramming: Concepts, Tools, and Techniques from Boost and Beyond. Addison-Wesley. ISBN 0-321-22725-5.

    Metaprogramming in C++,Johannes Koskinen,2004

    Boost 的MPL库

    7.Blog and papers:

    Coplien, James O. (February 1995). "Curiously Recurring Template Patterns" (PDF). C++ Report: 24–27.

    https://en.wikipedia.org/wiki/Mixin

    http://www.cnblogs.com/liangliangh/p/4219879.html

    http://b.atch.se/posts/non-constant-constant-expressions/

     

    展开全文
  • C++模板的特化详解(函数模版特殊,类模版特化)

    万次阅读 多人点赞 2016-03-22 14:40:24
    模版与特化的概念函数模版与类模版C++中模板分为函数模板和类模板 函数模板:是一种抽象函数定义,它代表一类同构函数。 类模板:是一种更高层次的抽象的类定义。 特化的概念所谓特化,就是将泛型的东东搞得具体化...
  • 基于React+antd的后台管理模板(可预览)

    万次阅读 多人点赞 2018-08-05 13:42:53
    自己利用业余时间,基于React+antd写了一个后台管理模板。主要是熟悉antd组件和React,页面主要还是展示页面,不涉及后台交互。 特点: 我用
  • MIPCMS模版 seo超好模板

    2019-05-27 14:08:44
    MIPCMS模版 seo超好模板 MIPCMS-04 仿大前端模板 mip模板免费下载
  • C++函数模板模板函数)详解

    万次阅读 多人点赞 2019-07-04 16:03:01
    C++函数模板模板函数)详解定义用法:函数模板的原理延申用法2.1为什么需要类模板2.2单个类模板语法2.3继承中的类模板语法案例1:案例2:2.4类模板的基础语法2.5类模板语法知识体系梳理1.所有的类模板函数写在类的...
  • IDEA类和方法注释模板设置(非常详细)

    万次阅读 多人点赞 2018-01-11 10:41:44
    IDEA自带的注释模板不是太好用,我本人到网上搜集了很多资料系统的整理了一下制作了一份比较完整的模板来分享给大家,我不是专业玩博客的,写这篇文章只是为了让大家省事。 这里设置的注释模板采用Eclipse的格式,...
  • 适合程序员的简历模板

    万次阅读 多人点赞 2018-08-26 19:56:34
    今天找了好多简历的模板,分享一下 这个链接里序号是8,23,34,37的,这几个都挺适合程序员。 链接:https://pan.baidu.com/s/1o8rCjwA#list/path=%2F 密码:5dky 需要其他的可以点击下面的链接 知乎上的简历...
  • idea自定义快捷代码生成模板

    万次阅读 2020-06-28 16:47:59
    idea中使用psvm 以及sout等都经常使用,idea也支持自定义模板 点击添加,选择那种语言,例如java 然后就可以如下快速生成代码
  • C++ 使用模板需要注意的事情

    万次阅读 2020-07-29 21:40:05
    //函数模板注意事项 template<class T> //typename 可以替换成class void mySwap(T&a, T&b){ T temp = a; a = b; b = temp; } //1、自动类型推导,必须推导出- -致的数据类型T才可以使用 vo.
  • ACM经典模板整理(十四个经典模版 杭电交大复旦清华等集合
  • 【VUE】vue后台常用模板

    万次阅读 多人点赞 2019-04-11 15:30:13
    vue后台常用模板: element文档: http://element-cn.eleme.io/#/zh-CN/component/installation vue API: https://cn.vuejs.org/v2/api/ 以下是在下收集的三个常用的vue模板 1、vue-manage-system git地址:...
  • opencv模板匹配步骤及Code

    万次阅读 多人点赞 2018-08-20 14:32:01
    opencv模板匹配步骤及Code 首先介绍一下模板匹配的适用场景: 1、图像检索 2、目标跟踪 简单的说,模板匹配最主要的功能就是在一幅图像中去寻找和另一幅模板图像中相似度最高的部分,这就是模板匹配。 比如,...
  • 模板模板

    万次阅读 多人点赞 2019-07-04 21:10:42
    模板模板类 所谓类模板,实际上是建立一个通用类,其数据成员、成员函数的返回值类型和形参类型不具体指定,用一个虚拟的类型来代表。使用类模板定义对象时,系统会实参的类型来取代类模板中虚拟类型从而实现了...
  • 计算机软件著作权模板及个人申请全套攻略-软著

    万次阅读 多人点赞 2018-09-26 17:11:09
    计算机软件著作权模板及个人申请全套攻略 文章目录计算机软件著作权模板及个人申请全套攻略写在前面提交材料各个资料的填写要求身份证复印件软件著作权申请表一份4张资料准备注意事项说明书一份16张(XXXX张)规则...
  • Discuz模版,Discuz x3.4商业模板宽屏设计素材教程资源
  • 滴滴出行模板WORD版,滴滴行程单模板WORD版

    千次下载 热门讨论 2018-06-17 21:39:05
    滴滴行程单WORD版本,是我在网上找了很久找人花了100元大洋按照原版设计的,分享给大家!滴滴报销单的福利哦!
  • beego模板语法 go语言模版语法

    千次阅读 2016-05-16 12:05:36
    如果您想要修改为其它符号,可以参考 模板标签。 使用 . 来访问当前位置的上下文 使用 $ 来引用当前模板根级的上下文 使用 $var 来访问创建的变量 模板中支持的 go 语言符号 {{"strin
  • 函数模板和类模板 模版特化

    万次阅读 2011-03-01 13:48:00
    函数模板和类模板模版特化关键字: template , template 模板 模板(template)是一个将数据类型参化的工具,它提供了一种将代码与数据类相脱离的机制,即代码不受具体的数据类型的影响。模板分为函数模板和类模板两种。...
  • maxcms,电影站超酷模版仿最新,奇热网模版4.0模板,双视网模板,适用于maxcms2.0,maxcms3.0,maxcms4.0
  • C++ 普通函数和模板函数调用规则

    万次阅读 2020-07-29 22:30:45
    2.可以通过空模板参数列表来强制调用函数模板 3.函数模板也可以发生重载 4.如果函数模板可以产生更好的匹配, 优先调用函数模板 */ #include<iostream>; using namespace std; /** 调用规则如下: 1.如果...
  • 作为一个Seoer,相信大家都知道网站模板的独特性与美观,决定着搜索引擎和用户的友好度。一个好的网站模版会让许多站长羡慕和不断的模仿,也可以更好的提高用户体验度。现在就给大家讲一下如何制作自己独有的原创模版...
  • Thymeleaf 模板引擎简介 与 Spring Boot 整合入门

    万次阅读 多人点赞 2018-07-15 16:48:13
    Thymeleaf 模板引擎 官方文档下载 Hello World 新建应用 后台控制器 前端页面 浏览器访问测试 Thymeleaf 模板引擎 1、Thymeleaf 是 Web 和独立环境的现代服务器端 Java 模板引擎,能够处理HTML,XML,...
  • C++中的函数模板

    万次阅读 多人点赞 2018-08-23 19:57:54
    之前我们知道的交换两个变量的方法有宏定义、函数,这两种方式都能实现两个变量的交换,但是各有各的优缺点 宏定义: - 优点:代码复用,适合所有的类型 - 缺点:缺少类型检查,宏在预处理阶段就被替换掉,编译器...
  • C++模板:函数模板

    万次阅读 2018-09-10 23:40:52
    函数模板: 函数模板: 我们看这段代码,compare是一个函数名还是一个模板名?其实他是一个模板名。如果要把他看成一函数来使用,就要用一个类型来实例化这个模板,在使用时可以给其后尖括号中加上要使用的...
  • C++模板:函数模板、类模板模板与继承

    万次阅读 多人点赞 2013-09-13 23:09:29
    C++模板:描述   C++提供一种模板的机制来减少代码重复。比如:对于同一样函数使用不同的数据类型,int,double,char等。C++模板属于“元编程”的范畴。 C++ 模板函数:  1.支持不同数据类型的函数重载: #include ...
  • Java微信公众平台开发之发送模板消息

    万次阅读 多人点赞 2017-01-02 16:59:53
    模板消息仅用于公众号向用户发送重要的服务通知,只能用于符合其要求的服务场景中,如信用卡刷卡通知,商品购买成功通知等。不支持广告等营销类消息以及其它所有可能对用户造成骚扰的消息。对于一般的服务号而言,...
  • 活着就意味必须要做点什么,...模版是泛型编程的基础, 模版分为 模版函数 和 模版类 函数模板格式: template &amp;amp;amp;amp;amp;amp;amp;amp;lt;class(或typename) 形参名1, class 形参名2, clas

空空如也

1 2 3 4 5 ... 20
收藏数 1,706,521
精华内容 682,608
关键字:

模版