一、类模板的定义:
- 类似函数模板,类模板以关键字template开始,后跟模板参数列表,即
<typename T, ...>
- 在类模板(及其成员函数)的定义中,我们将模板参数当作替身,代替使用模板时用户提供的类型或值。
代码示例:
template <typename T> //用T这个模板类型参数,来表示A中保存的元素的类型。
//当用户实例化A时,T就会被替换为特定的模板实参类型
class A{
private:
int size;
T* list;
public:
//构造函数
A();
A(int size);
A(T[], int size);
//析构函数
~A();
}
二、实例化类模板:
实例化:将函数模板中的“T”编译为具体的数据类型。
在实例化类时,必须提供元素的类型,如:
A<int> a1;
A<double> a2;
实例化之后,编译器自动实例化出与下面等价的类:
//对于上述的A<int> a1:
template <>
class A<int>{
private:
int size;
T* list;
public:
A();
A(int i);
A(int a[], int size);
~A();
};//就是把所有的T都变为了目标类型
一个类模板的每个实例都形成一个独立的类。类型A<string>
与其他的A类型都无关,也不会对其他A类型的成员有特殊的访问权限(即一个A<int>
和A<string>
之间不能进行运算或者访问对方的成员)
三、类模板的成员函数:
与其他任何类相同,我们既可以在类模板的内部,也可以在类模板的外部为其定义成员函数。而定义位置的不同也导致了定义时的写法上的差别。
定义在类模板内的成员函数被隐式声明为内联函数。(因此在写类模板的成员函数时有特殊的写法)
类模板的成员函数本身是一个普通函数。不同的是,类模板的每个实例(如A<int>
, A<string>
等等)都有其自己版本的成员函数。因此,类模板的成员函数具有和模板相同的模板参数<typename T, ...>
。
因而,定义在类模板之外的成员函数就必须以关键字template开头,后接类模板参数列表。接下来讲一下如何定义一个类外函数。
四、类外定义函数的方法:
说明成员函数属于哪个类,而且从一个模板生成的类的名字中必须包含其模板参数T(如 A<T>::
)
(注意模板实参和模板形参相同)
如,对于类内定义函数:
ret-type StrA::member-name(parm-list);
对应在类外定义时应为:
template<typename T>
ret-type StrA<T>::member-name(parm-list){...}
五、具体讲解几种成员函数的定义(都是在类模板外定义):
1. 只是用了模板参数列表:
//若data[i]无效(即超过原有长度),就抛出msg
template<typename T>
void Blob<T>::check(size_type i, const std::string &msg){
//size_type是想要访问的位置,msg是输入的一句话,它是一个字符串常量
if(i >= data->size())//size()返回本身长度,这是访问了类内的成员函数
throw std::out_of_range(msg);
}
该函数的特点是:除了使用了模板参数列表外(即使用了Blob<T>::
),此函数与非模板Blob类的check成员函数一毛一样!!!
2. 返回类型是模板类型且使用了模板参数列表:
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)//i是即将要提取的数的位置
{
//如果i太大,check会抛出异常,阻止访问一个不存在的元素
check(i,"subscript out of range");
return (*data)[i];
}
3. 构造函数:
3-1:再拿Blob函数举例:
template<typename T>
Blob<T>::Blob(): data( std::make_shared< std::vector<T> >() ){}
这段代码在作用域Blob<T>
中定义了名为Blob的成员函数,类似非模板类的默认构造函数。
此构造函数分配一个空的vector,并将指向vector的指针保存在data中。
如前所示,我们将类模板自己的类型参数作为vector的模板实参来分配vector。
3-2:类似的,接受一个initialize_list 参数的构造函数将其类型参数T作为initialize_list参数的元素类型:
template<typename T>
Blob<T>::Blob(std::initializer_list<T> il)
: data(std::make_shared<std::vector<T>>(il)){}
类似默认构造函数,此构造函数分配一个新的vector。在本例中,我们用参数 il 来初始化此vector。
为了使用这个构造函数,我们必须传递给他一个initializer_list,其中的元素必须和Blob元素类型兼容:
Blob<string> bl = {"a","b","c"}
这条语句中,构造函数的参数类型为:initializer_list<string>
。list中的每个字符串常量都隐式转换为一个string。
**七、类模板成员函数的实例化:(在main中的实现)
note:默认情况下,一个类模板的成员函数只有当程序用到它时才会进行实例化。
例如下列代码:
// 实例化Blob<int>和接受initializer_list<int>的构造函数
Blob<int> squares = {0,1,2,3,4,5,6,7,8,9}
// 实例化Blob<int>::size() const
for(size_t i = 0; i != squares.size(); i++)
squares[i] = i*i; // 实例化Blob<int>::operator[](size_t)
实例化了Blob<int>
类和它的三个成员函数: operator[ ], size和接受initializer_list<int>
的构造函数
八、在类代码内简化模板类名的使用
一般我们使用类模板类型时都要提供模板实参,但是也有不用提供实参的特殊情况:
在类模板自己的作用域中,我们可以直接使用模板名而不提供实参:
// 若试图访问一个不存在的元素,BlobPtr抛出一个异常
template<typename T> class BlobPtr{
public:
BlobPtr(): curr(0){}
BlobPtr(Blob<T> &a, size_t sz=0):
wptr(a.data), curr(sz);
T& operator*() const{
auto p = check(curr, "dereference past end");
return (*p)[curr];//(*p)为本对象指向的vector
}
//递增和递减:
BlobPtr& operator++();
BlobPtr& operator--();
private:
//若检查成功,check返回一个vector的shared_ptr
std::shared_ptr<std::vector<T>>
check(std::size_t, const std::string&) const;
std::size_t curr;// 数组中的当前位置
};
观察到,
BlobPtr& operator++();
BlobPtr& operator--();
这里的返回值类型是BlobPtr&
,而不是BlobPtr<T>&
。
所以当我们在一个类模板的作用域中时,编译器在处理模板自身引用时就好像我们已经提供了和模板参数匹配的实参一样。
所以在类模板作用域中写的上述两行就相当于:
BlobPtr<T>& operator++();
BlobPtr<T>& operator--();
而在类模板外使用定义类模板的成员函数的时候,就要标出来BlobPtr<T>&
了。
note:在类模板的作用域内,可以直接使用模板名而不用指定模板实参!
九、小结:
- 说明了如何定义和实例化类模板,在定义时,将需要泛化的类型都换成T,在实例化时给出具体的数据类型来替换T。
- 类模板的成员函数也需要给出参数列表,但由于定义成员函数时的位置不同,在写法上也有差别。在类外定义时,需要给出完整的参数列表。
- 随后分析了3中常见的类模板成员函数在类外定义时的形式,并且讲了在main中具体的实现方式;
- 讲完了类外,就讲了类内定义时的简化(即在具体的条件下可以将返回类中的
<T>
省去)