第一节 函数模板和类模板
参数多态性
⭐参数多态性就是将程序所处理的对象的类型 参数化,使得一段程序可以用于处理多种/不同类型对象。(泛型思想)
函数模板
⭐定义格式:
template<模板参数表> 类型名 函数名 (参数表)
{
//函数体
}
⭐尖括号的模板参数表由用逗号隔开的模板参数构成。模板参数用”typename”或者”class”修饰。
类模板
⭐定义格式:
template<模板参数表> class 类名
{
//类成员声明
}
⭐使用类模板使得类中某些数据成员、成员函数的参数/返回值/局部变量能取任意类型。
模板参数(类型参数和非类型参数)
类型参数
示例:template< class T >
使用关键字class或typename指出T为类型参数。
非类型参数
示例:template< int n >
非类型参数又称为表达式参数。
常用于数组等容器。
- 限制和缺点:
- 表达式参数只可以是整型、枚举、引用、指针。目前的C++11标准还不支持浮点数作为表达式参数。
- 不能修改参数的值,也不能使用参数的地址。
- 每个值都将生成自己的模板。而使用构造函数传递参数值只会生成一个类声明,并且更灵活通用。
※ 其他多功能性 ※
- 类模板继承
子类继承的父类是一个类模板,那么子类在声明时需要指定父类中T的类型。否则编译器无法为子类分配内存。
如果想灵活指定父类中T的类型,子类也需要变为类模板。
- 递归使用
可以递归使用模板。例如Container<Container<int, 5>, 10> container;
。
- 多类型参数和默认类型参数
如上文所述。
- 类模板作为类模板的类型参数
类模板可以作为类模板的类型参数。格式template<template<typename T> class TC>
。
第一节补充-模板相关
类模板相比函数模板的区别
- 类模板没有自动类型推导
template<class T> class Object
{
public:
T value;
Object(T value);
}
Object obj("aaa"); //错误的
Object<string> obj("aaa"); //正确的
- 类模板的类型参数列表中可以有默认参数
template<class T = string> class Object
{}
函数模板的类型参数不能带有默认值,但是非类型参数可以有默认值。
普通类和类模板区别
- 成员函数创建时机:
普通类的成员函数一开始就创建。
类模板的成员函数调用时才创建。
template<class T>
class TestClass
{
public :
T obj;
void Test()
{
obj.Foo();//可以编译通过
}
};
模板的实例化和具体化
隐式实例化
模板默认使用隐式实例化。
实例化对象时,指出所需类型,然后编译器生成具体的类定义。编译器在需要对象之前,不会生成类的隐式实例化。
CRE:也就是说类定义是发生在编译期间的。而C#泛型类型的替换在运行时执行,并且可以获取类型信息。
显式实例化
使用template关键字并指出所需类型来声明类时,编译器将生成类声明的显示实例化。声明必须位于模板定义所在的名称空间中。
在这种情况下,虽然没有创建或提及类对象,编译器也将生成类定义。
示例:template class Container<string, 100>;
显式具体化和部分具体化
有时可能需要在为特殊类型实例化时,对模板进行修改,使其行为不同以满足需求。这种情况下可以创建显式具体化(explicit specialization)。
示例:
//假设Comparable模板类的定义对于数字比较管用,但是无法用于字符串类型。就可以显示具体化。
template<typename T> class Comparable
{
//...
}
//显式具体化
template<> class Comparable<string>
{
//...
}
C++还允许部分具体化(partial specialization)。
示例:
template<class T1, class T2> class Pair{...}
template<class T1> class Pair<T1, int>{...}
成员模板
类模板和函数模板可用作结构体、类、类模板的成员。并且模板类的模板参数和其成员的模板参数可以不一样。
很老的编译器不支持模板成员。有一些编译器支持模板成员但是不支持类外定义。
模板类外定义成员模板函数的时候,如果模板是嵌套的,template语句也需要嵌套。
template <typename T>
class Test
{
public:
template<typename U>
U Foo(U u);
}
//成员定义
template <typename T>
template <typename U>
U Test<T>::Foo(U u)
{
//....
}
类模板成员的类外实现和文件外实现
问题:类模板中成员函数的创建是在调用阶段,导致文件编写时链接不到。
解决方法1:直接包含.cpp文件。
解决方法2:把.h和.cpp中的内容写到一个文件,默认后缀名.hpp。
模板与友元
模板类声明也可以由友元。模板的友元分为3类:
- 非模板友元。
- 约束(bound)模板友元。
- 非约束(unbound)模板友元。
非模板友元
friend void counts();
上述声明使该函数成为模板所有实例化的友元。
模板类的约束模板友元函数
- 第一步:类定义的前面声明每个函数模板
template <typename T> void counts();
template <typename T> void report(T &);
- 第二步:声明友元
template <typename TC>
class Test
{
friend void counts<TC>();
friend void counts<Test<TC>>(Test<TC> &);
//可以省略为friend void count<>(Test<TC> &); //编译器自动推断
}
- 第三步:为友元提供模板定义
template <typename TC>
class Test
{
//....友元声明略
}
//模板友元函数定义
template <typename T>
void counts(){ ... }
template <typenmae T>
void report(T & arg) { ... }
模板类的非约束模板友元函数
通过在类内部声明模板,可以创建非约束友元函数。即每个函数具体化都是每个类具体化的友元。
template<typename T>
class Test
{
template <typename C, typename D> friend void foo(C &, D &);
}
template <typename C, typename D> void foo(C & c, D & d){ ... }
补充:操作符重载声明为友元函数
示例:
friend ostream & operator << <>(ostream & os, T & obj);
模板别名(C++11)
typedef std::array<double, 12> arrd;
typedef std::array<int, 12> arri;
第二节 泛型程序设计
泛型程序设计
⭐泛型程序设计,就是编写不依赖具体数据类型的程序。
⭐在C++中,模板是泛型程序设计的主要工具。
⭐泛型编程常应用于编写不同类型通用的数据结构。
术语
概念:用于描述泛型编程中作为参数的数据类型所需要具备的功能。(内涵是功能,外延是具备这些功能的所有数据类型)
模型:具备一个概念所需要功能的数据类型称为这一概念的一个模型。
第三节 C++标准模板库(STL)
STL简介
⭐标准模板库(Standard Template Library,STL)最初是由HP公司开发的一个用于支持C++泛型编程的模板库。后被纳入C++标准,成为C++的一部分。STL有不同的实现和版本,但是它们为用户提供的接口都遵循共同标准。
⭐STL提供了一些常用的数据结构和算法。例如向量容器vector、链表容器等。STL还提供排序、查找等现有的函数模板。
⭐STL更大的意义在于,它定义了一套概念体系,为泛型程序设计提供了逻辑基础。STL中的各个类模板、函数模板的参数都是用这个体系中的概念来规定的。使用STL的一个模板时所提供的类型参数既可以是C++标准库已有类型,也可以是自定义的类型,只要这些类型是所要求概念的模型。因此,STL是一个开放的体系。