STL与泛型编程(第一周)
part 1 C++模版简介
一,模版概观
1.模板 (Templates)是C++的一种特性,允许函数或类(对象)通过泛型(generic types)的形式表现或运行。
模板可以使得函数或类在对应不同的型别(types) 的时候正常工作,而无需为 每一个型别都写一份代码。
2.C++主要有两种类型的模板:
(1)类模板(Class template): 使用泛型参数的类(classes with generic parameters)
(2)函数模板(Function template): 使用泛型参数的函数(functions with generic parameters)
3.模版实例化
模板的声明(declaration)其实并未给出一个函数或类的完全定义(definition),只是提供了一个函数或类的语法框架(syntacticalskeleton)。
实例化是指从模板构建出一个真正的函数或类的过程,比如:
template <typename T> struct Object { . . . };
可以用来构建诸如Object<int>, Object<char>, Object<int*>, Object<MyClass*>等等不同型别的具体实例。
实例化有两种类型:
(1)显式实例化–在代码中明确指定要针对哪种型别进行实例化。
(2)隐式实例化–在首次使用时根据具体情况使用一种合适的型别进行实例化。
二,模版函数
4.函数模版
定义:函数模板是参数化的一族函数(a family of functions)。
通过函数模板, 可以定义一系列函数,这些函数都基于同一套代码,但是可以作用在不同型别的参数上。
template <typename T> T max(const T& a, const T& b) { return (a>b) ? a : b; }
也可以使用 class 来代替 typename , 但是不推荐使用。
5.模版的使用
int i = 1, j = 2; max(i, j); // 正确 int f = 3.1, g = 4.2 max(f, g); //正确 max(i, f); //错误 //compile error: template parameter 'T' is ambiguous
6. 模版实例化
用具体型别替代模板参数T的过程叫做实例化(instantiation);从而产生了一个模板实例。
一旦使用函数模板,这种实例化过程便由编译器自动触发的,不需要额外去请求模板实例化。
如果实例化一种型别,而该型别内部并不支持函数所使用的操作,那么就会导致一个编译错误。
例如:std::complex并没有重载“>”,也就是说该型别并不支持使用“>”比较大小,而Max函数使用“>”来判断c1、c2的大小,所以无法通过Max(complex1, complex2)得到预期的结果。
结论: 模板被编译了两次
1)没有实例化之前,检查模板代码本身是否有语法错误。
2)实例化期间, 检查对模板代码的调用是否合法。
7.参数推导
模板参数是有传递给模板函数的实参决定的不允许自动型别转换:每个T必须严格匹配!
Max(1, 2) // OK:两个实参的型别都是 int Max(1, 2.0) // ERROR:第一个参数型别是 int,第二个参数型别是 double
一般有两种处理这种错误的方法:
1) 用 static_cast或强制转换参数型别以使两者匹配。
2) 显式指定T的型别。
Max(static_cast<double>(1), 2.0) Max<double>(1, 2.0)
8.函数模版重载
(1)重载:函数模板也可以像普通函数一样被重载
(2)同名:非模板函数可以和同名的模板函数共存
(3)参数推导:编译器通过函数模板参数推导来决定使用调用哪个重载
// 普通函数 inline int const& Max(const int const& a, const int const& b) // 第一个 template <typename T> inline T const& Max(const T const& a, const T const& b) // 第二个 template <typename T> inline T const& Max(const T const& a, const T const& b, const T const& c) // 第三个
Max(7, 42, 68): 调用接受三个参数的模板 // 调用 第三个
Max(7.0, 42.0): 调用 Max<double>(参数推导) // 调用 第二个
Max('a', 'b'): 调用 Max<char>(参数推导) // 调用 第二个
Max(7, 42): 调用非模板函数,参数型别为 int // 调用 第一个 其他因素都相同的情况下,重载裁决过程调用非模板函数,而不是从模板产生实例
Max<>(7, 42): 调用 Max< int>(参数推导) // 调用 第二个 允许空模板参数列表
Max<double>(7, 42): 调用 Max<double>(无需参数推导) // 调用 第二个
Max('a', 42.7): 调用非模板函数,参数型别为 int // 调用 第一个 对于型别不同的参数只能调用非模板函数( char 型别 'a' 和 double 型别 42.7 都将转化为 int 型别)
总结
对于不同的实参型别,模板函数定义了一族函数
当传递模板实参的时候,函数模板依据实参的型别进行实例化
可以显式指定模板的实参型别
函数模板可以重载
当重载函数模板时, 将改变限制在:显式指定模板参数
所有的重载版本的声明必须位于它们被调用的位置之前
三,类模版
1.1与函数模版类似,类也可以通过函数参数泛化,从而可以构建出一族不同型别的类实例(对象)。
1.2 类模版实参可以是某一型别或常量(仅限int或enum)。
2. 类模版实例
const std::size_t DefaultStackSize = 1024; template<typename T, std::size_t n = DefaultStackSize> class Stack { public: void Push(const T const& element); int Pop(T& element); int Top(T& element) const; private: std::vector<T> m_Members; std::size_t m_nMaxSize = n; };
n是编译时定义的常量,n可以有默认值。
3.类模板的声明
除了 Copy constructor 之外,如果在类模版中需要使用到这个类本身,比如 operator= ,那么应该使用其完整的定义(Stack<T>),而不是省略型别 T 。如下面的例子所示:
template <typename T, std::size_t n> class Stack { public: Stack(Stack<T, n> const&); // copy constructor Stack<T>& operator= (Stack<T, n> const&); //assignment operator };
4. 类模板的实现
要定义一个类模板的成员函数,则要指明其是一个模版函数。
4.1 例如,Push 函数的定义应当如下:
template <typename T, std::size_t nMaxSize> void Stack<T, nMaxSize>::Push(const T const& element) { if (m_Members.size() >= m_nMaxSize) { //error handing ... return; } m_Members.push_back(element); }
4.2 Pop 函数:从 Stack 中弹出顶部元素,但是没有 pop 出该元素:
template <typename T, std::size_t nMaxSize> int Stack<T, nMaxSize>::Top(T& element) const { if (m_Members.empty()) return 0; element = m_Members.back(); return 1; }