模板
1.函数模板
1.模板概念
当一个函数进行的操作对很多类型都适用时,我们没必要重载多个函数。可以定义一个通用
的函数模板。看一个栗子
template <typename T> bool compare(const T &val1,const T &val2) { if(val1 > val2) return true; return false; } int main() { cout << boolalpha << compare(34,90) << endl; cout << boolalpha << compare("hello","world") << endl; return 0; }
模板关键字以template开始,后面跟一个模板参数列表。在调用函数时,编译器会用函数实参来为我们推断模板参数,
生成相应的函数。
2.模板类型参数
一般的,模板参数列表中有一个类型参数T,在函数内部,我们可以把它看成一个类型说明符,就把它当成一个类类型或
者一个内置类型来使用,模板参数列表中可以使用typenaem ,也可以使用class.
3.非类型模板参数
除了类型模板参数,我们可以定义非类型模板参数,非类型参数表示一个值而不是一个类型,
例如在C++中,数组名传参时,当我们不想传入数组大小,或者希望能处理不同的数组大小的参
数传入时,使用模板参数。
template <size_t N,size_t M> int fun(const char (&p1)[N],const char (&p2)[M]) { return strcmp(p1,p2); } int main() { cout << fun("hello","world") << endl; return 0; }
调用fun()时,会生成 int fun(const char (&p1)[5],const char (&p2)[5]);
注意:非类型的模板参数必须是常量表达式。
4模板编译
当编译器发现一个模板时,并不生成代码,只有当我们调用函数时,才生成特定的模板实例,
一般来说,我们在头文件中放置函数的声明,在源文件中进行实现,但是,在有模板的情况下,
为了生成一个实例,编译器需要掌握函数模板的定义,因此,模板的头文件要包含声明和定义。
模板的编译有两个阶段,一个是检查模板函数的编写和模板函数的调用是否有语法的错误,
第二个阶段发生在模板调用时,编译器生成模板实例,只有在这个阶段才能发生类型相关的错误。
2.类模板
1.类模板概念
template <typename T> class node{ public: T data; node<T> *next; public: node(T _data=0) : data(_data),next(nullptr){} };
template<typename T> class Stack { public: typedef typename node<T>* ptr; Stack() { head = new node<T>; length=0; } Stack(int num) { head = new node<T>; length=0; for(int i=0;i<num;i++) insert(0); } Stack(const Stack& rhs) { head = new node<T>; length=0; node<T> *_node = rhs.head; for(int i=0;i<rhs.length;i++) { insert(_node->next->data); _node = _node->next; } } Stack &operator=(const Stack& rhs) { if(this == &rhs) return *this; int _length = this->length; for(int i=0;i<_length;i++) { erase(); } head->next = nullptr; node<T> *_node = rhs.head; for(int i=0;i<rhs.length;i++) { insert(_node->next->data); _node = _node->next; } return *this; } virtual ~Stack() { int _length = length; for(int i=0;i<_length;i++) erase(); delete head; } }
考虑这个语句 typedef typaname node<T>* ptr;
在实例化之前,编译器不知道T为何物,使用typename 让编译器知道这是一个类型。
在类模板自己的作用域中,我们可以直接使用模板名而不提供实参。
在类模板外定义成员时,必须指出返回类型是一个实例化的T。
2.类模板和友元
在声明友元类时,使用与类模板参数相同的参数声明,友元关系被限定在相同运算符之间
当使用不同的参数声明时,友元类的所有实例都是模板类的友元。
注意:为了引用模板的一个特定实例,我们必须声明模板自身。
3.模板类型别名
为模板类型定义别名时,由于模板不是类型,我们不能定义一个typedef 引用一个模板,必须使用
它的实例化
typedef BST bst;//false typedef BST<int> bst;//true
4.使用类的类型成员
当模板参数T为类类型时,使用该类的成员,例如T::value;编译器不知道T是什么
这时,使用typename,告诉编译器这是一个类型。
5.默认模板实参
我们可以在类模板中为实参类型添加默认类型
template <typename T=int> class BST{}; int main() { BST<> bst; return 0; }
默认生成int类型的类实例
3.成员模板
一个类中可以包含本身是模板的成员函数,这种成员函数被称为成员模板。成员模板不能是虚函数。
1.普通类的成员模板
2.类模板的成员模板
对于类模板,我们也可以定义成员模板,在这种情况下,类和成员模板各有自己独立的模板参数,
template <typename T=int> class BST{ public: template<typename IT> BST(IT a); }; template<typename T> template<typename IT> BST<T>::BST(IT a){ } int main() { BST<> bst(10); return 0; }
4.重载与模板
模板的出现很大程度上使得代替了重载的作用。
函数模板可以被另外一个模板或者一个普通非模板函数重载。
template <typename T> void fun(const T _a) { cout << "T _a" << endl; cout << _a << endl; } template <typename T> void fun(T *p) { cout << "T p" << endl; cout << p << endl; } int main() { const int a=10; char *p = "hello,world"; fun(a); fun(p); return 0; }
即使没有第二个重载函数,字符串依然可以输出,T会成为char*.但是加上重载函数,可以实现精确匹配。