目录
给函数模板 定义 inline 和 constexpr 关键字 (581P)
对于一个实例化了的类模板, 类模板的成员函数只有在程序使用该成员函数时才会实例化。(587P)
使用一个类模板类型时,在类模板自身的作用域中,可以直接使用模板名而不提供实参 (587P)
用一个模板类型参数作用于一个函数中的多个参数类型(602Page)
用一个函数模板初始化一个函数指针或者对一个函数指针赋值 (607Page)
函数模板
- 注意: 在模板的定义中(不管是类模板还是函数模板),模板参数列表都不能为空。
模板参数列表中表示的是在某类或函数定义中需要用到的类型或值,当使用模板时,我们需要隐式地或显式地指定模板实参。当我们调用一个函数模板时, 编译器(通常)用函数实参类型来为我们推断模板的实参类型。示例程序:
template <typename T>
int compare(const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
int main()
{
cout << compare<int>(11, 0) << endl; // T is int, 显式指定模板实参类型, 将其绑定在 模板参数上
cout << compare(151.5, 12.36) << endl; // T is double ,隐式指定模板实参类型, 将其绑定在 模板参数上
system("pause");
return 0;
}
编译器用模板实参的类型为我们实例化一个特定类型 版本的函数。当编译器实例化一个模板时, 它使用实际的模板实参类型代替对应的模板类型参数来创建出该模板的一个新“实例”。
上述的程序用两个不同的类型实例化了两个不同版本的 compare ,这些编译器生成的特定类型版本的函数通常被称为模板的实例( 有些书上也叫模板函数 )。
模板类型参数 (579P)
模板类型参数 ( 就像上述程序中 “ < T > ”)有什么用处呢?:
- 可以用来指定返回类型或函数的参数类型, 以及在函数体内用于变量声明或强制类型转换。
注意的是,声明每一个模板类型参数前都必须使用关键字 class 或 typename( 它们含有相同,可以同时互换使用)。
通常我们应该使用 typename 关键字来声明模板类型参数, 因为它比class 更加的直观,也能更加清楚地指出随后的名字是一个类型名称。
非类型模板参数 (580P)
可以通过一个特定的类型名而非关键字class 或 typename 来给模板定义一个非类型参数, 非类型参数必须是一个常量表达式。
当一个模板被实例化时, 非类型参数被一个用户提供的或编译器推断出的值所代替, 从而允许编译器在编译时实例化模板。
有哪些类型可以作为非类型参数呢?
- bool、char、wchar_t、char16_t、char32_t、short、int、long、long long、
- 指向对象类型的指针
- 指向函数类型的指针
- 左值引用
绑定到非类型的整型参数的实参必须是一个常量表达式。 绑定到指针或引用的非类型参数的实参必须具有静态的生存期。
有哪些类型不可以作为非类型参数呢?
- double 、float、long double、类类型(比如:string)
- 我们不能用一个普通 (非 static) 局部变量或动态对象作为指针或引用非类型模板参数的实参。
template<unsigned N, unsigned M>
int compare(const char(&p1)[N], const char(&p2)[M])
{
return strcmp(p1, p2);
}
int main()
{
cout << compare("hi", "mom") << endl;
system("pause");
return 0;
}
在模板定义内, 模板非类型参数必须是一个常量值。在需要常量表达式的地方, 都可以使用非类型模板参数, 例如:指定数组大小。
给函数模板 定义 inline 和 constexpr 关键字 (581P)
inline或 constexpr 说明符放在模板参数列表之后, 返回类型之前:
// ok: inline specifier follows the template parameter list
template <typename T> inline T min(const T&, const T&);
// error: incorrect placement of the inline specifier
inline template <typename T> T min(const T&, const T&);
记住编写泛型代码有两个重要原则:
- 模板中的函数形参是对const的引用
- 函数体中的条件判断仅使用<比较运算。
那为什么函数参数是对const的引用?
- 因为将函数的参数设定为 const 引用可以保证该函数可以用于不能拷贝的类型( 比如: unique_ptr 和 IO 类型)。
- 当该函数处理大的对象时,那么这种设计也会使函数运行得更快。
那为什么仅使用<比较运算?
- 如果编写代码时只使用<运算符, 我们就降低了该函数对要处理的类型的要求。这些类型必须支持<, 但支不支持 > 并不重要。
less 关键字 (581P)
模板编译 (582P)
当编译器遇到一个模板定义时, 它并不生成代码。只有当我们实例化出模板的一个特定版本时, 编译器才会生成代码。
当我们使用 (而不是定义,是实例化)模板时, 编译器才生成代码, 这一特性影响了我们如何组织代码以及何时检测到错误。
为了生成一个实例化版本, 编译器需要有函数模板或类模板成员函数的定义代码。因此,与非模板代码不同, 模板的头文件通常既包括声明也包括定义。所以说 ,函数模板和类模板成员函数的定义通常放在头文件中。
模板的编译错误主要是在实例化过程中报告的 ( 582P)
练习16.1:
当调用一个函数模板时, 编译器会利用给定的函数实参来推断模板实参,它使用实际的模板实参来代替对应的模板参数来创建出模板的一个 “ 新实例 ”,也就是一个真正可以调用的函数, 这个过程称为实例化。
练习题16.3:
因为 Compare 函数中 使用的是 < 运算符进行操作的,需要类型 T 事先就定义该运算符, 但是我们的实际类型 Sales_data 它没有实现 < 运算符, 所以会发生错误
练习题16.4:
template <typename T,typename V>
T find_V( T v1, T v2, const V &v3)
{
while (v1 != v2 && *v1 != v3)
{
++v1;
}
return v1;
}
int main()
{
vector<int> v1{ 0,5,6,7,8 };
if (!v1.empty())
{
auto temp = find_V(v1.begin(), v1.end(),6);
if (temp == v1.end())
{
cout << "没有找到6" << endl;
}
else
cout << "找到6 " << endl;
}
list<string> v2 = { "huang","chengt","tao" };
if (!v2.empty())
{
auto temp = find_V(v2.begin(), v2.end(), "sda");
if (temp == v2.end())
{
cout << "没有找到sda" << endl;
}
else
cout << "找到sda " << endl;
}
system("pause");
return 0;
}
练习题16.5:
template <typename T>
constexpr void print( T &v1)
{
for (auto tt : v1)
{
cout << tt << " ";
}
cout << endl;
}
int main()
{
int v1[] = { 5,6,7,8,9,10 };
print(v1);
vector<string> v2{ "huang","chengt","tao" };
print(v2);
system("pause");
return 0;
}
练习题16.6:
begin 函数是指向数组首元素的指针, 而 end 指向数组尾元素之后的指针。 当使用该函数时,可以很容易写出一个循环并且处理数组中的元素。
template <typename T> constexpr void print( T v1,T v2) { while (v1 != v2) { cout << *v1 << " "; ++v1; } cout << endl; } int main() { int v1[]{ 5,6,7,8,9,5 }; int *p = v1; auto tt = end(v1) - begin(v1); // 计算数组中的数量 int *v = &v1[tt]; // 指向数组尾元素下一位置的指针 print(p, v); system("pause"); return 0; }
练习题16.7:
template <typename T>
constexpr int find_V( T v1, T v2)
{
int temp = 0;
while (v1!=v2)
{
++v1;
++temp;
}
return temp;
}
int main()
{
int v1[] = { 5,6,7,8,9,10 };
cout << "数组的大小为:" << find_V(begin(v1), end(v1)) << endl;
system("pause");
return 0;
}
练习题16.8:
因为大多数标准库容器的迭代器都定义了 == 和 !=, 但是它们大多数都没有定义< 运算符, 因此只要使用 迭代器 和 !=的习惯, 就不在意用的是哪种容器。
类模板 (583P)
类模板与函数模板的不同之处是:
- 编译器不能为类模板推断模板参数类型。我们创建类模板的实例化时,必须在模板名后的尖括号中提供 额外的信息, 它用来替换模板参数的模板实参列表。
当我们使用一个类模板时,我们必须提供显式的模板实参列表, 它们被绑定到模板参数。 使用实际的模板实参来代替对应的模板参数来创建出模板的一个 “ 新实例 ”,也就是一个特定类型的模板类。
template <typename T> class Blob
{
public:
typedef T value_type;
typedef typename std::vector<T>::size_type size_type;
// constructors
Blob();
Blob(std::initializer_list<T> il);
// number of elements in the Blob
size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
// add and remove elements
void p