栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

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

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

        (2)模板类型参数

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

templateT foo(T *p)
{
    T tmp=*p;
    .......
    return tmp;
}

        (3)非类型模板参数

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

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

template
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 class Blob
{
public:
    typedef T value_type;
    typedef typename vector::size_type size_type;
    Blob();
    Blob(initializer_list 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> data;
    //若data[i]无效,则抛出msg
    void check(size_type i, const string& msg) const;
};

        (2)类模板的成员函数

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

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

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

        (3)Blob构造函数

templateBlob::Blob() :data(make_shared>) {};
templateBlob::Blob(initializer_list il) :data(make_shared>(il)) {};

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

        (5)模板类型别名

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

        typedef Blob StrBlob;

        或

template using twin=pair

twin authors ///authors是一个pair

 16.1.3 模板参数

        (1)模板参数与作用域

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

typedef double A;
templatevoid 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  //声明
template int compare(const int &,const int &)  //定义

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

16.2 模板实参推断

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

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

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

例:

template T fobj(T,T)        //实参被拷贝
template 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
int f(const A&v1,const B&v2)
{
    if(v1v2) return 1;
    return 0;
}

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

 16.2.2 函数模板显示实参

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

        (1)指定显示模板实参

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

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

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

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

16.2.3 模板实参推断和引用

        考虑下述例子

templatevoid f(T &p)

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

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

templatevoid f1(T &)//实参必须为一个左值

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

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

templatevoid f2(const T&)    //可以接受一个右值

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

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

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

16.3 可变模板参数

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

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

例:

//Args是一个模板参数包,rest是一个函数参数包
//Args表示零个或多个模板类型参数
//rest表示零个或多个函数参数
template
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 void g(Args...args)
{
    cout< 

16.3.1 编写可变参数函数模板

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

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

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

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

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

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

        

   

        

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1009059.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号