模板真的是绕晕我了……
函数模板
定义函数模板
普通的函数只能接受指定类型的参数,比如我们定义一个int compare(int, int);
来比较大小,则我们只能比较两个整型的大小,如果需要比较浮点数的大小,则需要重新写一个新的函数,但新的函数和原函数做的事情其实是一样的。为了提高代码的复用率,故需要用模板
。
我们可以定义一个这样的模板:
1 |
|
第一行以关键字template
开始,后面跟着模板参数列表<typename T1, typename T2, ...>
(旧的C++标准用<class T1, class T2, ...>
,class和typename可以混用),每个T
表示一种类型。
函数模板和普通函数一样可以重载,并且我们既可以用另一个模板来重载它,也可以用普通函数来重载它。比如:
1 |
|
顺便说一句,inline
和constexpr
跟在模板参数列表后面。
实例化函数模板
函数模板用起来和普通的函数一样:
1 |
|
编译器会根据实参来实例化一个特定版本的函数int compare(const int&, const int&)
、
模板参数
类型模板参数
类型模板参数就是我们上面的那种模板参数:
1 |
|
T1
、T2
称为类型参数
,我们可以把它当作类型说明符,就像内置类型一样使用,特别是,它可以用为返回类型、参数类型、函数内的变量声明和类型转换。
非类型模板参数
非类型参数表示一个值而非类型:
1 |
|
非类型参数可以是一个整型,或指向对象或函数类型的指针或引用绑定。绑定到非类型整型参数的实参必须是常量表达式;绑定到指针、引用的非类型参数的实参必须具有静态的生存期。在模板定义内,非类型参数是一个常量值,用在需要常量表达式的地方。比如:
1 |
|
类型模板参数和非类型模板参数可以混用,但必须放在一个template<>里面,因为参数模板列表只能有一个:
1 |
|
模板编译
编译器只有在遇到模板实例时,才会生成代码。这与普通的函数不同。因此,在头文件中,要有下面几个部分:
- 普通函数的声明
- 类定义+类中的函数的声明
- 函数模板的声明+定义
- 类模板的成员函数的定义
类模板
类模板大致上和函数模板差不多,但编译器无法通过实参推断参数类型。我们需要在模板名后的尖括号中提供额外的信息。
定义类模板
和函数模板一样,都是在类名前加上template <[模板参数列表]>
1 |
|
我们需要手动为类模板提供<模板实参>
,如果在类外定义成员函数,必须这样:
1 |
|
但在类的作用域里面,我们可以不加<>
,默认与类实例化的类型一致:
1 |
|
特别的,在类外的成员函数定义中,我们也无需加<>
1 |
|
使用类模板
使用类模板时,必须提供显式模板实参。
1 |
|
就酱。
类模板与友元
类模板可以将其他普通的类或函数声明为友元(这样对所有类型的类模板都是友元,也就是多对一),也可以将另一个类模板或函数模板声明为友元。反过来,一个普通的类可以将特定类型的类模板声明为友元,也可以将所有类型的类模板声明为友元。
一对一
可以将某一类型的类模板或某一类型的函数模板声明为友元
1 |
|
有几点要注意:
- 为了在函数中用模板特例,我们需要先声明模板自身。也就是上面一二行。
- 为了让函数只能在特定类型的模板中使用,函数也要是模板函数。
特定的模板的友元关系
1 |
|
类模板与静态成员
只是说一句,静态成员static
只在相同类型的模板类之间共享,不同类型的类模板的静态成员相互独立。