C++ primer 第十六章 模板与泛型编程

目录

16.1 定义模板

16.1.1 函数模板

16.1.2 类模板

 16.1.3 模板参数

  16.1.4 成员模板

16.2 模板实参推断

 16.2.1 类型转换与模板类型参数

 16.2.2 函数模板显示实参

16.3 可变模板参数

16.3.1 编写可变参数函数模板


16.1 定义模板

16.1.1 函数模板

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

        模板定义以关键字template开始,后跟一个模板参数列表,这是一个都好分割的一个或多个模板参数的列表。

        (1)实例化函数模板

        调用函数模板时,编译器用函数实参来为我们推断模板实参。

cout << compare(1, 0) << endl;
//实例化出 int compare(const int&,const int&)
vector<int> vec1{ 1,2,3 }, vec2{ 4,5,6 };
cout << compare(vec1, vec2) << endl;
//实例化出 int compare(const vector<int>&,const vector<int>&)

        (2)模板类型参数

        可以将模板类型参数看作类型说明符,就像内置类型或类类型是说明符一样

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

        (3)非类型模板参数

        可以在模板中定义非类型参数。一个非类型参数表示一个值而非一个类型。

        例如,编写一个处理字符串字面常量。第一个模板参数表示第一个数组长度,第二个模板参数表示第二个数组长度。

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

        调用时:conpare("hi","mom"),编译器会使用字面值蟾宫i昂大小替代N和M,加上结束符,上述的N为3,M为4.

        一个非类型参数可以是一个整型,或者是一个指向对象或函数类型的指针或引用。绑定到非类型整形参数的实参必须是一个常量表达式,绑定到指针或引用非类型参数的实参必须是具有静态的生存周期。

16.1.2 类模板

        (1)定义类模板

template<typename T> class Blob
{
public:
    typedef T value_type;
    typedef typename vector<T>::size_type size_type;
    Blob();
    Blob(initializer_list<T> il);
    size_type size() const  //Blob中的元素数目
    {
        return data->size();
    }
    bool empty() const
    {
        return data->empty();
    }
    void push_back(T&& t)
    {
        data->push_back(move(t));
    }
    void push_back(const T& t)
    {
        data->push_back(t);
    }
    void pop_back();
    T& back(); //元素访问
    T& operator[](size_type i);
private:
    shared_ptr<vector<T>> data;
    //若data[i]无效,则抛出msg
    void check(size_type i, const string& msg) const;
};

        (2)类模板的成员函数

        定义在类模板外的成员函数必须以关键字template开始,后接类模板参数列表。

template<typename T>void Blob<T>::check(size_type i, const string& msg) const
{
    if (i >= data->size())
        throw out_of_range(msg);
}
template<typename T>
T& Blob<T>::back()
{
    check(0, "back on empty blob");
    return data->back();
}
template<typename T>
T& Blob<T>::operator[](size_type i)
{
    check(0, "out of range");
    return (*data)[i];
}

template<typename T> void Blob<T>::pop_back()
{
    check(0, "pop_back on empty Blob");
    data->pop_back();
}

        (3)Blob构造函数

template<typename T>Blob<T>::Blob() :data(make_shared<vector<T>>) {};
template<typename T>Blob<T>::Blob(initializer_list<T> il) :data(make_shared<vector<T>>(il)) {};

        (4)一个类模板的成员函数只有当程序用到他时才进行实例化

        (5)模板类型别名

        可以定义一个typdef来引用实例化的类:

        typedef Blob<string> StrBlob;

        或

template<typename T> using twin=pair<T,T>

twin<string> authors ///authors是一个pair<string,string>

 16.1.3 模板参数

        (1)模板参数与作用域

        一个模板参数的可用范围是在其声明之后,至模板声明或定义结束之前。与其他名字一样,模板参数会隐藏外层作用域中声明的相同名字。

typedef double A;
template<typename A,typename B>void f(A a,B b)
{
    A tmp=a;  //tmp的类型为模板参数A的类型,而非double
    double B;    //错误,重声明模板参数B
}

  16.1.4 成员模板

        当模板被使用时才会被实例化,相同实例化会出现在多个文件中。当两个或多个独立编译的文件使用相同模板,并提供相同模板参数时,每个文件中就都会有该模板的一个实例。

        多个文件中实例化相同模板的额外开销非常严重。可以通过显示实例化来避免开销,一个显示实例化有如下形式

        extern templte declaration        //实例化声明

        template declaration                //实例化定义

        例如

extern template class Blob<string>  //声明
template int compare(const int &,const int &)  //定义

        编译器遇到extern模板声明时,不会在本文件中生成实例化代码。将一个实例化声明为extern就代表在程序其他位置有该实例化的一个非extern声明。

16.2 模板实参推断

 16.2.1 类型转换与模板类型参数

        与非模板函数一样,我们在一次调用中传递给函数模板的实参被用来初始化函数的形参。如果一个函数形参的类型使用了模板类型参数,则采用特殊的转换规则。

  • const转换:可以将一个非const对象的引用传递给一个const的引用形参。
  • 数组或函数指针转换:一个数组实参可以转换为一个指向其元素的指针,类似的,一个函数实参可以转换为一个该函数类型的指针。

例:

template<typename T> T fobj(T,T)        //实参被拷贝
template<typename T> T fref(cosnt T&,const T&)    //引用

string s1("hi")
const string s2("h")
fobj(s1,s2)    //const被忽略
fref(s1,s2)    //将s1转换为const是允许的

        在fobj调用中,实参被拷贝,因此原对象是否是const没有关系。在fref调用中,参数类型是const的引用,对于一个引用参数来说,转换为const是允许的,因此这个调用也是合法的。

int a[10],b[42];
fobj(a,b);    //调用fobj(int*,int*)
fref(a,b);    //错误:数组类型不匹配

        在fobj调用中,数组大小无关紧要,两个数组都会转换为指针。fobj中的模板类型为int*。但是,fref调用是不合法的,如果形参是一个引用,则数组不会转换为指针,a和b的类型是不匹配的,因此调用时错误的。

        (1)使用相同模板参数类型的函数形参

        一个模板类型参数可以用作多个函数形参类型。传递给这些形参的实参必须具有相同类型y

long lng;
compare(lng,1024)//错误,不能实例化compare(long,int)

             可以将函数模板定义为两个类型参数:

template<typename A,typename B>
int f(const A&v1,const B&v2)
{
    if(v1<v2) return -1;
    if(v1>v2) return 1;
    return 0;
}

long lng;
f(lng,1024);    //正确,调用f(long,int)

 16.2.2 函数模板显示实参

        某些情况下,编译器无法推断出模板实参的类型,我们希望允许用户控制模板实例化。当函数返回类型与参数列表中任何类型都不相同时,这两种情况最常出现。

        (1)指定显示模板实参

        例:我们可以定义表示返回类型的第三模板参数,从而允许用户控制返回类型。

//编译器无法推断T1,他未出现在函数参数列表中
template<typename T1,typename T2,typename T3>
T1 sum(T2,T3);

       上述中,没有任何函数实参的类型可用来推断T1的类型,每次调用sum时调用者都必须为T1提供一个显示模板实参

//T1是显示指定的,T2和T3是从函数实参类型推断而来的
auto val3=sum<long long>(i,lng)   //long long sum(int,long)
显示指定T1的类型,T2和T3由编译器从i和lng中推断而来

16.2.3 模板实参推断和引用

        考虑下述例子

template<typename T>void f(T &p)

        (1)从左值引用函数参数推断类型

        当一个函数参数是模板类型参数的一个普通左值引用时,只能传递给他一个左值,实参可以是const也可以不是,如果实参是const,则T被推断为const 类型

template<typename T>void f1(T &)//实参必须为一个左值

f1(i);     //i是一个int,模板参数类型T是int
f1(ci);    //ci是一个const int,模板参数类型T是int
f1(5);    //错误,传递给一个&参数的实参必须是左值

        如果一个函数参数类型是const T&,可以传递给他任何类型的实参——一个对象(const或非const),一个临时对象或者一个字面值常量。

template<typename T>void f2(const T&)    //可以接受一个右值

f2(i);    //i是一个int,模板参数T是int
f2(ci);    //ci是一个const int,但模板参数T是int
f2(5);    //一个const &参数可以绑定到一个右值,T是int

        (2)从右值引用推断函数参数推断类型

template<typename T> void f2(T&&)
f3(42);    //实参是一个int类型的右值,T是int

16.3 可变模板参数

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

        用一个省略号指出一个模板参数或函数参数表示一个包。

例:

//Args是一个模板参数包,rest是一个函数参数包
//Args表示零个或多个模板类型参数
//rest表示零个或多个函数参数
template<typename T,typename... Args>
void foo(const T &t,Args&...rest);

        编译器还会推断包中参数的数目

int i=0;double d=3.14;string s="hi"
foo(i,s,42,d);    //包中有三个参数
foo(s,42,"hi");    //包中有两个参数
foo(d,s);            //包中有一个函数
foo("hi");            //空包

        编译器会为foo实例化出四个版本:

void foo(const int &,const string &,const int &,const double &)
void foo(const string&,const int &,const char[3]&);
void foo(const double &,const string &);
void foo(const char[3]&)

        当我们需要知道包中有多少个元素时,可以采用sizeof....运算符。

template<typename...Args> void g(Args...args)
{
    cout<<sizeof...(Args)<<endl;
    cout<<sizeof...(args)<<endl;
}

16.3.1 编写可变参数函数模板

        首先定义一个名为print的函数,在一个给定流上打印给定实参列表的内容

//用来终止递归并打印最后一个元素的函数
//此函数必须在可变参数版本的print定义之前声明
template<typename T>
ostream &print(ostream &os,const T &t)
{    
    return os<<t;
}

          可变参数函数通常是递归的。第一步调用处理包中的第一个实参,然后用剩余实参调用自身

//包中除了最后一个元素之外的其他元素都会调用这个版本的print
template<typename T,typename...Args>
ostream &print(ostream &os,const T &t,const Args&...rest)
{
    os<<t<<",";
    return print(os,rest...);
}

        第一个版本print负责终止递归并打印初始调用中的最后一个实参。第二个版本的print是可变参数版本,打印绑定到t的实参,并调用自身来打印函数参数包中的剩余部分。

        例,对于print(cout,i,s,42);

        

   

        

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值