C++模板与STL 容器
这是C++语言的一系列文章,内容取自于网易微专业《C++开发工程师(升级版)》。
本文的主题是 模板基本概念与STL容器简介。
Part 1:模板简介
模板允许参数化的类型,它可以根据类型参数生成函数和类。它是对类或函数更高层次的抽象。它可以分为两类:类模板和函数模板。 类模板根据传入的类型参数定义了一组关联的类;函数模板则定义了针对不同类型或类的一组类似操作。
注意:模板在编译期展开,而非运行时确定参数的具体类型,因此会造成不同程度的代码膨胀。 STL是基于模板实现的,关于代码膨胀的讨论,可以参考知乎的一篇帖子“为何某些公司不允许使用 C++ STL?”, 里面观点各异,自行分辨。
下面我们对类模板和函数模板分别介绍。
1.1 类模板 class template
1.1.2 声明一个 类模板
类模板定义了一组针对不同类型的类,它也是参数化类型的一种。模板的变量可以是类型或特定类型的值。msdn 上给出了下面这个例子:
// class_templates.cpp
template <class T, int i> class TempClass
{
public:
TempClass( void );
~TempClass( void );
int MemberSet( T a, int b );
private:
T Tarray[i];
int arraysize;
};
int main()
{
// ... 省略代码 ...
}
上面这个例子中,模板 TempClass 接收两个参数:类型 T 和 整型数 i 。T 可以是 int、double等基本类型,也可以是结构体和类。 变量 i 作为一个整形常量传递给 TempClass。由于 i 是一个常量,在编译期就可以确定,所以你可以使用标准语法声明一个数组作为其成员变量。
1.1.3 类模板的特化
之前侯捷老师提到过,特化分为两种:个数上的“偏”和类型上的“偏”。下面我们对这两种类型一一讨论。 MSDN上的讲解十分详细, 这里我会使用上面的讲解和代码。
个数上的“偏”:通常情况下,模板有一个或多个类型参数,我们可以将某些类型参数固化,从而使其接受更少(或不接收)类型参数。 举个前面使用过的例子:
// 定义一个类模板,它使用一个模板作为模板参数
template <typename T,
template <typename T>
class Container
>
class XCls {
private:
Container<T> c;
public:
// ...
};
// 定义Lst
template<typename T>
using Lst = list<T, allocator<T> >; // 注意: Lst 只有一个模板参数,而 list 有两个模板参数
// 使用该模板
int main() {
XCls<string, list> mylist1; // 合法的定义
//XCls<string, Lst> mylist2; // 不合法,因为 XCls 的第二个模板参数只接受一个参数(有点绕,think about it)
}
上面这个例子中, Lst 就是 std::list 的一个偏特化版本。
类型上的“偏”:这种方式允许模板接收 指针、引用、函数指针等作为参数类型。我们看一个例子:
// https://msdn.microsoft.com/en-us/library/3967w96f.aspx
// partial_specialization_of_class_templates.cpp
template <class T> struct PTS {
enum {
IsPointer = 0,
IsPointerToDataMember = 0
};
};
template <class T> struct PTS<T*> {
enum {
IsPointer = 1,
IsPointerToDataMember = 0
};
};
template <class T, class U> struct PTS<T U::*> {
enum {
IsPointer = 0,
IsPointerToDataMember = 1
};
};
struct S{};
extern "C" int printf_s(const char*,...);
int main() {
S s, *pS;
int S::*ptm;
printf_s("PTS<S>::IsPointer == %d PTS<S>::IsPointerToDataMember == %d\n",
PTS<S>::IsPointer, PTS<S>:: IsPointerToDataMember);
printf_s("PTS<S*>::IsPointer == %d PTS<S*>::IsPointerToDataMember ==%d\n"
, PTS<S*>::IsPointer, PTS<S*>:: IsPointerToDataMember);
printf_s("PTS<int S::*>::IsPointer == %d PTS"
"<int S::*>::IsPointerToDataMember == %d\n",
PTS<int S::*>::IsPointer, PTS<int S::*>::
IsPointerToDataMember);
}
这段代码会打印出:
PTS<S>::IsPointer == 0 PTS<S>::IsPointerToDataMember == 0
PTS<S*>::IsPointer == 1 PTS<S*>::IsPointerToDataMember ==0
PTS<int S::*>::IsPointer == 0 PTS<int S::*>::IsPointerToDataMember == 1
msdn 还有三个偏特化的例子,都不错,有兴趣的花,点击“Partial specialization”。
1.1.4 默认模板参数
不多说,参考 msdn 文档
1.2 函数模板 function template
函数模板,定义了一类行为。和类模板一样,在C++编译出的二进制文件中,不存在函数模板这个概念。 在编译期,在调用函数模板时,编译器会以某个类型作为参数生成一个特化的函数。 我们首先看个例子:
// https://msdn.microsoft.com/en-us/library/y2se4kz7.aspx
// function_templates1.cpp
template< class T > void MySwap( T& a, T& b ) {
T c(a);
a = b;
b = c;
}
上面这个函数实现了两个变量值的交换(pass by reference)。
Part 2 偏特化
有时候,模板并不能满足所有特化类型的需求,所以需要针对某些类型指定特化版本。
在标准库中,典型的偏特化有std::vector 和 [std::vector
我们再举一个例子,如果我们想覆盖上面 MySwap 针对 double 的特化函数,则可以像下面这样定义一个函数:
template<> void MySwap(double a, double b);
当一个函数调用匹配多个模板实例时,编译器会选择一个最佳匹配。如何去选择,下面我们讨论下这个主题。
2.1 函数模板的偏序 (partial ordering)
这部分内容的理解均来自于 msdn
这里我只取一部分自认为比较重要规则放到这里:
- A template specialization for a specific type is more specialized than one taking a generic type argument.
- A template taking only T* is more specialized than one taking only T, because a hypothetical type X* is a valid argument for a T template argument, but X is not a valid argument for a T* template argument.
- const T is more specialized than T, because const X is a valid argument for a T template argument, but X is not a valid argument for a const T template argument.
- const T* is more specialized than T, because const X is a valid argument for a T* template argument, but X* is not a valid argument for a const T* template argument.
下面这个例子也是从msdn上拷贝过来的,比较能体现 一般类型、指针特化、const 指针特化 的偏序:
// partial_ordering_of_function_templates.cpp
// compile with: /EHsc
#include <iostream>
extern "C" int printf(const char*,...);
template <class T> void f(T) {
printf_s("Less specialized function called\n");
}
template <class T> void f(T*) {
printf_s("More specialized function called\n");
}
template <class T> void f(const T*) {
printf_s("Even more specialized function for const T*\n");
}
int main() {
int i =0;
const int j = 0;
int *pi = &i;
const int *cpi = &j;
f(i); // Calls less specialized function.
f(pi); // Calls more specialized function.
f(cpi); // Calls even more specialized function.
// Without partial ordering, these calls would be ambiguous.
}
上面这段代码会打印出:
Less specialized function called
More specialized function called
Even more specialized function for const T*
Part 3 STL 库的容器(类模板)
标准库中包含了不少优秀的类模板和函数模板,本文中列出一部分常用的容器:
- std::vector
- std::deque
- std::list
- std::stack
- std::queue
- std::map & std::multimap
- std::set & std::mutliset
关于这些容器的用法,上面已经给出了链接,需要的时候查文档即可。