c++模板与泛型编程(一)模板定义 ——《c++ primer》读书笔记 by斜风细雨QQ:253786989 2012-02-26
泛型编程就是指编写独立于特定类型的代码,c++ STL就是泛型编程的极致运用。比如vector,它就是一个泛型容器,它里面可以装n多种类的元素。在使用的时候,再去指定元素类型:vector<int> vi;或者vector<float> vf;
模板是泛型编程的根基,没有模板的支持,泛型编程就无从谈起。
函数模板(function template)
函数模板是一种独立于类型的特殊函数,由函数模板可以产生针对特定类型的函数版本。
如:
template <typename T> int compare(const T &lhs, const T &rhs) { if (lhs < rhs) return -1; if (rhs < lhs) return 1; return 0; }
compare既可以比较两个int类型的数,也可以比较两个string类型的字符串。从这就可以看出模板与泛型编程的强大。如果不运用模板技术,而且我既想比较int类型,又想比较string类型,又想比较自定义类型等等,那就要针对这些类型分别编写一个函数,这样既增加了代码量,也不容易维护。
<typename T>称为模板形参表,T就是一个模板形参,多个模板形参以逗号隔开,模板形参表不允许为空。
函数模板的使用
如:
int main() { cout << compare(3, 5) << endl; string s1 = "hello", s2 = "world"; cout << compare(s1, s2) << endl; }
对于“compare(3, 5)”,编译器将首先推断出函数参数的类型为int,然后将int绑定到compare的模板形参T,这个过程成为实例化(instantiate)了函数模板的一个实例。其实就是编译器帮助程序员编写了一个int版的compare。
模板形参类型
模板形参有两种类型,一种叫做类型形参(type parameter)。如上面的“typename T”就是类型形参,因为T代表一个类型。还有一种模板形参叫做非类型形参(nontype parameter)。
如:
template <typename T, size_t N> void array_init(T (&parm)[N]) { for (size_t i = 0; i != N; ++i) { parm[i] = i; } }
这里面“size_t N”就是非类型形参,因为它并不代表某个类型。当调用array_init时,编译器从数组实参计算出函数模板的非类型形参的值。如:
int ia[10]; array_init(ia);
则编译器将函数模板array_init的形参绑定到int[10],从而实例化一个array_init版本如下:array_init(int (&)[10]),也就是N的值为10。
模板形参的名字不能在模板内部重用
template <typename T> int compare(const T &lhs, const T &rhs) { typedef int T; //error // … return 0; }
这段代码会出现编译错误,因为模板形参T,在模板内部不能重用。
内联(inline)函数模板
函数模板也可以声明为inline,格式如下(注意inline的位置):
template <typename T> inline int compare(const T &lhs, const T &rhs);
类模板(class template)
所谓类模板就是说本类可以支持不同类型的对象。
如:
template <typename T> class Queue { public: Queue(); T &front(); const T &front()const; void push(const T &); void pop(); bool empty() const; private: // ... };
类模板的定义与普通类的定义看起来差不多,主要不同就是类型T,使得Queue可以针对不同的类型实例化不同的类。如:
Queue<int> qi; Queue<string> qs;
需要注意的是类模板也仅仅是一个模板,它并不是一个类,所以:Queue q;是不会通过编译的,因为Queue并不是一个类型。而Queue<int>才是根据类模板Queue实例化出来的一个具体的类,所以可以定义一个Queue<int>类型的变量qi。
在模板定义的内部指定类型
这句话要结合具体的例子才能容易理解:
template <typename Parm, typename U> Parm fcn(Parm* array, U value) { Parm::size_type * p; // ... }
Parm是一个类,类既可以有数据成员和函数成员,也可以有类型成员,比如STL容器类都有一个类型成员:size_type。而在上面这个函数模板的内部,编译器在遇到“Parm::size_type”时,不知道它是一个数据,还是一个类型,通常编译器假定这样的名字是一个数据,所以“Parm::size_type * p”就是两个数据的相乘,这肯定不是我们想要的结果。为了让编译器知道“Parm::size_type”是一个类型,就要显示的在其前面加上“typename”,如下:
template <typename Parm, typename U> Parm fcn(Parm* array, U value) { typename Parm::size_type * p; // ... }
编写泛型程序
如下函数模板,如果用来比较某个类型的两个值,则程序员必须保证该类型支持“<”操作符,否则就会编译出错。比如某个自定义类型,如果没有定义“<”操作符,则无法使用该函数模板。
template <typename T> int compare(const T &lhs, const T &rhs) { if (lhs < rhs) return -1; if (rhs < lhs) return 1; return 0; }
编写泛型程序时,模板参数通常使用const引用,这样该模板就可以使用不允许赋值的类型。另外编写模板内部的操作时尽量减少对类型T的要求。比如上面的函数如果像下面这样写,就要要求类型T同时支持“<”操作符和“>”操作符了。
template <typename T> int compare(const T &lhs, const T &rhs) { if (lhs < rhs) return -1; if (lhs > rhs) return 1; return 0; }
c++模板与泛型编程(一)模板定义 ——《c++ primer》读书笔记 by斜风细雨QQ:253786989 2012-02-26