模板函数:
template <typename T>
bool isEqual( const T& t1, const T& t2 )
{
return t1 == t2; // 注意:此处实际上规定了泛型T必须支持 operator==
}
模板类:
template <class T> class TList{
public:
...
// 编写泛型链表,链表中储存泛型数据
// 成员函数可能对泛型做出了一些要求,例如拥有成员函数:bool isEqual( const T& t )
bool equal( const TList<T>& t )
{
return _t.isEqual( t.get() ); // 假设TList<T>::get() 返回 TList<T>类中私有成员区的_t
}
// Best Practices:模板程序应该尽量减少对实参类型的要求!
private:
T _t;
...
};
class TPerson {
public:
...
// 编写待存储在TList中的类
// 提供模板中所要求的成员函数等
bool isEqual( const TPerson& t )
{
return t == *this;
}
private:
...
};
TList<TPerson> pList; // 实例化一个TPserson类对象的TList
什么是模板实例化?
通常,将从模板生成一个新类(或函数)的过程(或者,提供模板实参创建一个新模板类或函数的过程),称为模板实例化( template instantiation )。
调用函数与实例化模板的区别?
带实参的函数调用实际上是在程序运行时期完成的(不运行编译后的程序当然不会产生调用函数动作啦233);
实例化模板的过程:编译器在实例化一个模板时,首先检查该泛型是否是一个已知类型,并确定泛型实参的个数;一旦通过这些检查,将在代码中生成一个新类(对上述例子来说,可能是 TList_TPerson ),以 TList 为例,编译器重复TList的代码(用到哪部分,就生成哪部分),用 TPerson 代替 T,然后编译生成的类,并进一步确保生成代码的正确性。一旦这些工作都完成,TList_TPerson 就相当于程序员定义的类。
但是,以上所有工作都在编译时期完成,使用模板类时并没有附带任何运行时开销。这就是调用函数(带实参)与实例化模板类(带模板实参)的主要区别。
什么时候使用操作符而不是成员函数?
仍以上述例子为例,考虑:
TList<int> pList;
以内置类型int实例化该模板。当调用 TList::equal( const TList<T>& t ) 必然产生错误,因为 int 类型并没有成员函数 isEqual(const int& t)。而如果使用操作符完成上述函数:
bool equal( const TList<T>& t )
{
return _t == t;
}
生成的代码将顺利通过编译。语言为所有内置类型都提供了 ==、!= 等操作符,因此,在涉及这类操作时,最好使用操作符。当然,还有另一种方式使上述功能通过编译:模板特例化。
需要注意的是,若使用操作符,泛型 T 必须支持操作符。
什么是显式模板特例化?
针对模板类的特定实例,实现特定的代码,称为显示模板实例化。
如何进行显式模板特例化?
// C++ Primer Bset Practices:函数模板和类模板成员函数的定义通常放在头文件中 // 也可以分离声明和实现,这样的话,需要在 main.cpp 中除了包含 testcase.h 外,也包含 testcase.cpp // 文件: testcase.h #ifndef TestCase_H #define TestCase_H #include <iostream> template<class T> class TestCase { public: TestCase<T>(const T &t, std::string &name); ~TestCase<T>(); TestCase<T> &operator=(const TestCase<T> &assign); void get(); void set(T t); private: std::string _name; T _t; }; /*----------------------implement---------------------------*/ template<class T> void TestCase<T>::get() { std::cout << "this is get() "; } template<class T> void TestCase<T>::set(T t) { this->_t = t; } template<class T> TestCase<T> &TestCase<T>::operator=(const TestCase<T> &assign) { this->_t = assign._t; this->_name = assign._name; return *this; } template<typename T> TestCase<T>::TestCase(const T &t, std::string &name): _t(t), _name(name) { } template<typename T> TestCase<T>::~TestCase<T>() { std::cout << "this is destructor" << std::endl; } template<> void TestCase<std::string>::get() { std::cout << "this is partial specialization "; } #endif //TestCase_H
在另一个文件中引入该头文件:
// // 文件: main.cpp // #include <iostream> #include "testcase.h" //也可以在其他文件中定义模板片特殊化函数 //template<> TestCase<int>::~TestCase() { // std::cout<<"这是第二种模板偏特殊化方法"<<std::endl; //} //Best Practices: 一个特定文件所需要的所有哦模板的声明通常一起放置在文件开始位置,出现于任何使用这些模板的代码之前。 template<> void TestCase<std::string>::get(); // 必须向编译器说明针对函数 TestCase<std::string>::get() 的特化版本已经定义过了,不用再定义了。不然会报错:'TestCase<std::string>::get()被多次定义' int main() { using namespace std; string name("TestCase"); string cs("construct string"); TestCase<string> TestCase(cs, name); TestCase.get(); return 0; }
typename和class有什么区别呢?
在作为泛型形参时,typename 和 class 没有区别,例如:
template <typename T> bool isEqual( const T& t1, const T& t2 ) { return t1 == t2; } /*------------------------------------------------------------------------------------------------------*/ template <class T> bool isEqual( const T& t1, const T& t2 ) { return t1 == t2; }
考虑以下情况:
T::size_type * p;
如何确定该语句:声明了一个T::size_type类型的指针? or 一个乘法表达式,代表着 T 的成员 size_type 与 p 相乘?
默认情况下,C++语言假定作用于运算符访问的名字不是类型。因此,如果我们希望使用一个模板类型参数的类型成员,就必须显式的告诉编译器该名字是一个类型。
typename T::size_type * p;
当我们希望通知编译器一个名字表示类型时,必须使用关键字 typename,而不能使用class。