C++ Primer第五版笔记——模板参数与成员模板

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/rest_in_peace/article/details/82415470

模板参数
类似函数参数的名字,一个模板参数的名字也没有什么内在含义,通常将类型参数命名为T,但是实际上可以是任何名字。
模板参数与作用域:
模板参数遵循普通的作用域规则。一个模板参数的可用范围是在其声明后,至模板声明或定义结束之前。与其他名字一样,模板参数会隐藏外层作用域中声明的相同的名字。但是不同的是,在模板内不能重用模板参数名:

typedef double A;
template <typename A,typename B> void f(A a,B b){
    A tmp = a;      //隐藏外部的A,此处tmp不是double类型
    double B;       //错误
} 

正常的名字隐藏规则使得外部的A的声明被隐藏,因此tmp不是一个double类型变量,其类型在使用f函数时才会确定,另外由于不能重用模板参数名,所以将变量名取为B是错误的。
因为参数名不能重用,因此一个模板参数名在一个特定的函数模板中也只能出现一次:

//错误,模板参数名重用
template <typename V,typename V> //...

使用类的类型成员:
通常可以通过::来访问static成员或者类型成员。在普通的代码中,编译器知道类的定义,因此它可以知道通过作用域运算符访问到的名字是类型还是static成员。但是对于模板代码就有些困难。例如:

//假定T是一个模板的类型参数
T::size_type * p;

从上面的代码无法知道是在定义一个名为p的变量,还是一个名为size_type的static成员变量与名为p的变量相乘。
默认情况下,c++假定通过作用域运算符访问的是名字而不是类型。因此当希望使用一个模板类型参数的类型成员时,需要显式的指定,通过关键typename来做到:

template <typename T>
typename T::value_type top(const T& c){
    if(!c.empty())
        return c.back();
    else
        return typename T::value_type();
}

使用了typename指明返回值是一个类型,并在c中没有元素的时候生成一个值初始化的元素返回给调用者。(看不懂就把T::value_type换成int看看)
当希望通知编译器一个名字表示类型时,必须使用关键字typename而不能使用class。

默认模板实参:
新标准中,可以为函数和类模板提供默认实参,更早的只能为类模板提供默认实参。
例如,使用标准库中的less函数对象模板编写compare函数:

template <typename T typename F = less<T>>
int compare(const T& t1,const T& t2,F f = F()){
    if(f(t1,t2)) return -1;
    if(f(t2,t1)) return 1;
    return 0;
}

当实例化时的类型是less支持的类型时,就不用自己提供比较操作:

bool i = compare(0,42);         //默认使用less

当调用函数传入的实参是自定义类型,第三个参数也可自己传值。

模板默认实参与类模板:

template <typename T = int> class Number{
public:
    Number(T v = 0) : val(v){}
private:
    T val;
};

//实例化
Number<double> d_num;       //不使用默认类型
Number<> i_num;             //使用默认类型

成员模板
一个类(无论是普通类还是类模板)可以包含本身是模板的成员函数。这种成员被称为成员模板。成员模板不能是虚函数。

普通类的成员模板:
作为例子,这里定义一个类,类似删除器的作用,类中包含一个重载的函数调用运算符,它接受一个指针并对此指针执行delete操作,这里将调用运算符定义为一个模板:

class DebugDelete{
public:
    //构造函数,用于初始化输出流
    DebugDelete(std::ostream &s = std::cerr):os(s){}
    //T的类型由编译器推断
    template <typename T> void operator()(T* p) const{
        os<<"deleteing p"<<endl;
        delete p;
    }
private:
    std::ostream &os;
};

//代替delete操作
double* p = new double;
DebugDelete d;
d(p);               //释放p

与其他模板相同,成员模板也是以模板参数列表开始的,每个DebugDelete对象都有一个ostream成员,用于写入数据;还包含一个自身是模板的成员函数,这个类可以代替delete操作符。

类模板的成员模板:
对类模板也可以为其定义成员模板,在此情况下,类和成员各自有各自的、独立的模板参数。
例如为模板类Blob定义一个构造函数,接受两个迭代器参数,表示要拷贝的元素的范围。由于希望支持不同类型序列的迭代器,因此将构造函数定义为模板:

template <typename T> class Blob{
    template <typename It> Blob(It a,It b);
};

构造函数有自己的模板类型参数It,与类模板的普通成员函数不同的是成员模板是函数模板,当在外部定义一个成员模板时,必须同时为类模板和成员模板提供模板参数列表:

template <typename T>       //类的模板类型参数
template <typename It>      //构造函数的类模板类型参数
Blob<T>::Blob(It a,It b){...}

实例化和成员模板:
为了实例化一个类模板的成员模板,须同时提供类和函数模板的实参。和往常一样,在哪个对象上调用成员模板,编译器就根据该对象的类型推断类模板参数的实参。与普通函数模板相同,编译器通常根据传递给成员模板的函数实参来推断它的模板实参。

vector<long> v = {0,1,2,3,4};
//实例化对象和构造函数
Blob<int> s(v.begin(),v.end());

定义s时,显式的指出了编译器应该实例化出一个int版本的Blob,构造函数自己的类型参数则通过传递的参数来推断,这里是vector<long>::iterator,因此会实例化为以下版本:

Blob<int>::Blob(<vector<long>::iterator>,<vector<long>::iterator>);

猜你喜欢

转载自blog.csdn.net/rest_in_peace/article/details/82415470
今日推荐