C++------模板与泛型编程

一、定义模板

1、函数模板

template<typename T>
int compare(const T &v1,const T &v2 )
{
    if(V1<V2) return -1;
    if(v1>v2) return 1;
}

    模板定义以关键字template开始,后跟一个模板参数列表,这是一个逗号分隔的一个或多个模板参数的列表,用<>包围起来。

    实例化函数模板

    当调用一个函数模板时,编译器会根据函数实参来推断模板实参。

   模板类型参数

    模板类型参数可以看作类型说明符。类型参数可以用来指定返回类型和函数的参数类型,以及在函数体内用于变量声明和类型转换。模板类型参数前必须使用关键字typename或者class。两者意义完全一样。最好使用typname。

template <typename T> T foo(T* p)
{
    T tem =*p;
    return tem;
}

    非类型模板参数

    除了定义类型参数还可以在模板中定义非类型参数。一个非类型参数表示一个值而非一个类型。一个模板被实例化的时候,非类型参数被一个用户提供的或编译器推断出的值所代替。这些值必须是常量表达式。

template <unsigned N,unsigned M>
int compare(const (&p1)[N],const (&p2)[M])
{
    return strcmp(p1,p2);
}

//调用
compare("hi","abc");
//实例化为
int compare(const char (&p1)[3],const char (&p2)[4])

    inline和constexpr的函数模板

//注意位置
template <typename T> inline T min(const T&,const T&)

   模板程序应该尽量减少对实参类型的要求。

   函数模板和类模板成员函数的定义通常放在头文件中。

   注意模板在实例化的时候,需要对所使用的类型有一些假设,要确保实例化的对象满足模板的操作。也就是保证传递给模板的实参支持模板模板所要求的操作,以及这些操作在模板中能正确的工作。

   模板编译,为了生成一个实例化版本,编译器需要掌握函数模板和类模板的成员函数的定义,因此模板的头文件经常既包括声明也包括定义。

2、类模板

    编译器不会为类模板推断参数类型,所以必须自己提供模板实参列表。

    定义在类模板内的成员函数被隐式声明为内联函数。

    成员函数只有在使用的时候才会被实例化。

    在类模板自身作用域内不需要提供实参,可以直接使用类自身。

    如果一个类模板包含一个非模板友元,则友元被授权可以访问所有模板实例,如果友元自身是模板,则类可以授权给所有友元模板实例,也可以只授权特定实例。

3、模板参数

    模板参数遵循普通的作用域规则。

    实用类的类型成员 使用typename。不能使用class。

    可以为一个模板默认实参。

    无论什么时候使用一个类模板时,都要在模板名后接上尖括号,尖括号里指明类必须从一个模板实例化而来,如果,一个类模板为所有的模板参数都提供了默认实参,那么要加上一个空的尖括号。

4、成员模板

    一个类可以包含本身是模板的成员函数,这种成员称为成员模板,成员模板不能是虚函数。

    对于类模板,也可以定义成员模板,它们有各自的模板参数。

    实例化一个类模板的成员模板,必须同时提供类和函数模板的实参。

5、控制实例化

   当模板被使用时才会实例化,因此可能相同的实例会出现在多个对象中,当两个或多个独立编译的源文件使用了相同的模板并提供了相同的模板参数时,每个文件都会有一个实例化的模板,为了避免这种情况可以显式实例化来避免开销。使用关键字extern来表示这个模板不会再本文件中生成实例化代码。这个模板必须在其他文件中实例化。

    一个类的实例化定义会实例化该模板的所有成员包括内联的成员函数。所以要保证所用的类型能用于类的所有成员函数。

二、模板实参推断

1、类型转换与模板类型参数

    顶层const无论是在形参还是实参中都会被忽略。其他能在调用中应用于函数模板的包括下面两项:

    1)const转换,可以将一个非const对象的引用或指针传递给一个const的引用或指针形参

    2)数组或函数指针的转换,如果函数类型不是引用类型,可以对数组或函数类型的实参应用正常的指针转换。

   如果函数参数类型不是模板参数,对实参进行正常的类型转化。

2、函数模板显式实参

    可以指定显式模板实参。

    显式模板实参按照从左到右的顺序与对应的模板参数匹配,只有最右的参数显式模板实参才可以忽略,前提是它们可以根据函数参数推导出来。

    正常类型转换也应用于显式指定的实参。

3、尾置返回类型与类型转换

    尾置返回类型在形参列表后以一个->符号开头,为了表示函数真正的返回类型跟在形参列表之后,需要在本来返回值的地方加上一个auto。

    进行类型转换的标准库type_traits。

4、函数指针和实参推断

    当使用一个函数模板初始化一个函数指针或为一个函数指针赋值时,编译器就用指针的类型来推断模板实参。

    当参数是一个函数模板实例的地址时,程序上下文必须满足对每个模板参数能唯一确定其类型或值。

5、模板实参推断与引用

    当一个函数参数是模板类型参数的一个普通左值引用时,绑定规则告诉我们,只能传递给它一个左值,实参可以是const也可以不是。

    当一个函数参数是一个右值引用,正常的绑定规则告诉我们可以传递一个右值。

    如果一个函数参数是指向模板参数类型的右值引用如T&&,则可以传递给他任意类型的实参。如果将一左值传递给这样的参数,则函数参数被实例化为一个普通的左值引用。、

6、标准库move函数

    是一个右值引用的很好的例子。可以查看源码,仔细研究。

    从一个左值static_cast到一个右值引用是允许的。

7、转发

    如果一个函数参数是指向模板类型参数的右值引用,它对应的实参的const属性左值右值属性得到保持。

三、重载与模板

    函数模板可以被另一模板或一个普通非模板函数重载。

    设计函数模板,函数匹配规则会受到一些影响,如果有一个函数比其他函数更好地匹配则选择这个函数。如果多个函数提供同样好的匹配,则,如果同样好的匹配只有一个非模板函数则选择此函数,如果没有非模板函数,而是有多个函数模板,且其中一个模板比其他模板更特例化,则选此模板,否则会有歧义。

    当有多个重载模板对一个调用提供同样好的匹配时,应该选择最特例化的版本。

    对一个调用,如果一个非函数模板与一个函数模板提供同样好的匹配时,选择非模板版本。

    在定义任何函数之前,记得声明所有重载的函数模板,这样不必担心编译器实例化一个你不需要的版本。

四、可变参数模板

    一个可变参数模板就是可以接受可变数目参数的模板函数或模板类。可变参数被称为参数包,存在两种参数包,模板参数包和函数参数包。

    可以使用sizeof求出包中有多少元素。

template<typedef ...Args> void g(Args ...arg){
    cout<<sizeof...(Args)<<endl;   //类型参数数目
    cout<<sizeof...(args)<<endl;   //函数参数的数目
}

五、模板特例化

    通用模板的定义对于某些特定类型是不合适的,这时候我们可以定义类或函数模板的特例化版本。

    一个特例化的版本就是模板的一个独立的定义,在其中一个或多个模板参数被指定为特定的类型。

    当特例化一个函数模板时,必须为原模板中的每个模板参数都提供实参,为了指出正在实例化一个模板,应使用关键字template后面跟一个空尖括号,空尖括号指出我们将为原模板的所有模板参数提供实参。

    一个特例化版本本质上是一个实例,而非函数名的一个重载版本。

    与函数模板不同的是类模板的特例化不必为所有模板参数都提供实参,可以只指定一部分而非所有模板参数,或者是参数的一部分而非全部特性。一个类的部分特例化本身就是一个模板。

    我们只能部分特例化类模板,不能部分特例化函数模板。

   可以只特例化成员函数而不是整个模板。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值