泛型编程就是以独立于任何特定类型的方式编写代码。使用泛型程序时,我们需要提供具体程序实例操作的类型或只值。
泛型编程和面向对象编程一样,都依赖于某种形式的多态性。
面向对象编程中的多态性在运行时依赖存在继承关系的类。我们能够编写使用这些类的代码,忽略基类与派生类之间类型上的差异。只要使用基类的引用或指针,基类类型或派生类类型的对象就可以使用相同的代码。
如果说面向对象是一种通过间接层来调用函数以换取一种抽象(创建一个接口类),那么泛型编程则是更直接的抽象,它不会因为间接层而损失效率。不同于面向对象的动态期多态,泛型编程是一种静态期多态,通过编译器生成最直接的代码。泛型编程可以将算法与特定类型、结构剥离,尽可能复用代码。标准库中的容器、迭代器和算法是很好的泛型编程的例子,几乎可以在任意类型上使用标准库的类和函数。
C++中,模板是泛型编程的基础。模板是创建类或函数的蓝图或公式。
函数模板
模板定义以关键字template开始,后接模板形参表。模板形参表是用尖括号括住的一个或多个模板形参的列表,形参之间以逗号分隔。模板形参表不能为空。
模板形参表很像函数形参表,函数形参表定义了特定类型的局部变量但并不初始化那些变量,在运行时再提供实参来初始化形参。同样,模板形参表示可以在类或函数的定义中使用的类型或值。
1 | //声明一个名为T的类型形参 |
类型形参T跟在关键字class或者typename之后定义,这里class和typename没有区别。模板形参可以是表示类型的类型形参,也可以是表示常量表达式的非类型形参。模板形参选择的名字没有本质含义。可以给模板形参赋予的唯一含义是区别形参是类型形参还是非类型形参。如果是类型形参,我们就知道该形参表示的是未知类型;如果是非类型形参,我们就知道他是一个未知值。模板非类型形参是模板定义内部的常量值。
上面那个模板函数若再加上inline(不要放在template之前,要放在模板形参之后,返回类型之前),使其成为模板内联函数。这个模板内联函数可以用下列宏定义替换:
1 |
|
由此可见,用模板内联函数可以避免宏定义中犯人的括号。而且还可以避免莫名其妙的情况,上面例子可见第一次运算的时候a的递增次数竟然依赖于它被拿来和谁比较。因此我们应该尽量少使用宏!!
(宏定义和内联的区别:宏定义是在预处理阶段进行代码替换,内联函数是在编译阶段插入代码;其次宏没有类型检查,函数由类型检查)
1 | /* |
泛型编程是把算法和具体的数据结构分开了,我们不需要考虑类型本身是什么,直接用一套逻辑把所有的类型都涵盖了,如果需要针对某些特殊类型做处理,我们就进行单独的“特化”。这里比较复杂的操作是编译器的推理过程,程序员所做的工作无非是把该定义好的类型通知编译器,让编译器帮助我们做处理。
类模板
1 | template<class Type> |
使用类模板时,必须为模板形参显式指定实参,编译器使用实参来实例化这个类的特定类型版本,重新编写Queue类。
1 | /* |
模板编程的难点很大程度上在于对编译器的理解,我们需要直到怎么帮助编译器提供需要生成代码的信息。