C++ Primer 学习笔记_76_模板与泛型编程 --模板定义[续](一)

2014-11-24 13:01:43 · 作者: · 浏览: 0

模板与泛型编程

--模板定义[续]

四、模板类型形参

类型形参由关键字class或 typename后接说明符构成。在模板形参表中,这两个关键字具有相同的含义,都指出后面所接的名字表示一个类型。

模板类型形参可作为类型说明符在模板中的任何地方,与内置类型说明符或类类型说明符的使用方式完全相同。具体而言,它可以用于指定返回类型或函数形参类型,以及在函数体中用于变量声明或强制类型转换。

template 
  
   
T calc(const T &a,const T &b)
{
    T tmp = a;
    //...
    return tmp;
}

  

1、typename与class的区别

在函数模板形参表中,关键字typenameclass具有相同含义,可以互换使用,两个关键字都可以在同一模板形参表中使用:

template 
  
    calc (const T&, const U&);

  

使用关键字typename代替关键字class指定模板类型形参也许更为直观,毕竟,可以使用内置类型(非类类型)作为实际的类型形参,而且,typename更清楚地指明后面的名字是一个类型名。但是,关键字typename是作为标准C++的组成部分加入到C++中的,因此旧的程序更有可能只用关键字class



2、在模板定义内部指定类型

除了定义数据成员或成员函数外,类还可以定义类型成员。例如,标准库的容器类定义了不同的类型,如size_type,使我们能够以独立于机器的方式使用容器。如果要在函数模板内部使用这样的类型,必须告诉编译器我们正在使用的名字指的是一个类型。必须显式地这样做,因为编译器(以及程序的读者)不能通过检查得知,由类型形参定义的名字何时是一个类型何时是一个值。例如,考虑下面的函数:

template 
  
   
Parm fcn(Parm *array,U value)
{
    Parm: :size_type *p;    	//如果Parm::size_type是一个类型,则该语句是一个声明
                            		//如果Parm::size_type是一个对象,则该语句是一个乘法运算
}

  

默认情况下,编译器假定这样的名字指定数据成员,而不是类型。

如果希望编译器将size_type当作类型,则必须显式告诉编译器这样做:

template 
  
   
Parm fcn(Parm *array,U value)
{
    typename Parm::size_type *p;   //OK
}

  

通过在成员名前面加上typename作为前缀,可以告诉编译器将成员当做类型。通过编写typenameparm::size_type,指出绑定到Parm的类型的size_type成员是类型的名字。当然,这一声明给用实例化fcn的类型增加了一个职责:那些类型必须具有名为size_type的成员,而且该成员是一个类型

【最佳实践】

如果拿不准是否需要以typename指明一个名字是一个类型,那么指定它是个好主意。在类型之前指定typename没有害处,因此,即使typename是不必要的,也没有关系。

//P533 习题16.12
template 
  
   
typename IterType::value_type
mostApr(IterType first,IterType last)
{
    map
   
     count; while (first != last) { ++ count[*first]; ++first; } int apper = 0; typename IterType::value_type maxAprItem; for (typename map
    
     ::iterator iter = count.begin(); iter != count.end(); ++iter) { if (iter -> second > apper) { maxAprItem = iter -> first; apper = iter -> second; } } return maxAprItem; } int main() { vector
     
       ivec; int val; ifstream inFile("input"); while (inFile >> val) { ivec.push_back(val); } cout << mostApr(ivec.begin(),ivec.end()) << endl; } 
     
    
   
  

//习题16.13
template 
  
   
void printCntanr(ContainerType contnr)
{
    typename ContainerType::size_type n = 0;

    while (n != contnr.size())
    {
        cout << contnr[n ++] << endl;
    }
}

  

//习题16.14
template 
  
   
void printCntanr(IterType first,IterType last)
{
    while (first != last)
    {
        cout << *first << endl;
        ++first;
    }
}

  


五、非类型模板形参

模板形参不必都是类型。

在调用函数时非类型形参将用值代替,值的类型在模板形参表中指定

template 
  
   
void array_init(T (&parm)[N])
{
    for (size_t i = 0; i != N; ++i)
    {
        parm[i] = 0;
    }
}

  

模板非类型形参是模板定义内部常量值,在需要常量表达式的时候,可使用非类型形参指定数组的长度。

当调用array_init时,编译器从数组实参计算非类型形参的值

    int x[42];
    double y[10];

    array_init(x);	//T被int替代,N被42替代
    array_init(y);	//T被double替代,N被10替代

编译器将为array_init调用中用到的每种数组实例化一个array_init版本。



类型等价性与非类型形参

对模板的非类型形参而言,求值结果相同的表达式将认为是等价的。比如下面的两个array_init调用引用的是相同的实例 array_init

    int x[42];
    const int sz = 40;
    int y[sz + 2];

    //两个的函数实例相同
    array_init(x);
    array_init(y);


//P534 习题16.15/16
template 
  
   
std::size_t getArrSize(Type (&arr)[N])
{
    return N;
}

template 
   
     void printArr(ArrType (&arr)[N]) { for (std::size_t i = 0; i != N; ++ i) { cout << arr[i] << endl; } } int main() { int X[] = {3,34,5,65,67,7,7}; cout << "array size is: " << getArrSize(X) << endl; cout << "array items:" << endl; printArr(X); } 
   
  

六、编写泛型程序

编写模板时,代码不可能针对特定类型,但模板代码总是要对将使用的类型做一些假设。例如,虽然compare函数从技术上说任意类型都是有效的,但实际上,实例化的版本可能是非法的

产生的程序是否合法,取决于函数中使用的操作以及所用类型支持的操作:

    if (v1 < v2)
        return -1; // T类型的对象必须支持 <
    if (v1 > v2)
        return 1; // T类型的对象必须支持 >

如果用不支持<操作符的对象调用compare,则调用是无效的:

    Sales_item item1, item2;
    cout << compare(item1, item2) << endl;  //Error:该程序不能编译

【小心地雷】

在函数模板内部完成的操作限制了可用于实例化该函数的类型。程序员的责任是:保证用作函数实参的类型实际上支持所用的任意操作,以及保证在模板使用那些操作的环境中那些操作运行正常!



编写独立于类型的代码

【最佳实践】

编写模板代码时,对实参类型的要求尽可能少是很有益的。

虽然简单,但它说明了编