代码改变世界

模板——泛型和STL的基础

2004-08-07 01:01  FantasySoft  阅读(990)  评论(1编辑  收藏

        所谓泛型,从字面上可以猜想,就是泛化的类型(型别、type)。那么是在什么时候,类型被泛化了呢?我们知道,在编写一个函数的时候,通常都会知道参数(parameter)是什么类型的,很多时候这是理所当然的事情,连类型都不知道,怎么对传入参数进行操作呢?面对这样类似的疑问,C++总会展露出她迷人的笑容,让人消魂。这次,她用温柔的声线告诉我:“我可是能实现可以接受任何适当类型参数的函数哦。
        不会吧,我可没有用Java写过这样的函数哦。嘿嘿,偶还没有勇气去养“老虎”,所以只能这样大惊小怪了。
        其实,我们是可以理解语言支持泛型的,如果为了针对不同的类型去实现相同的行为就要去将代码重复再重复,就真的太笨了,代码重复也正是代码坏味道的一种。在Java中,如ArrayList,Vector都是使用对象数组(Object[])来储存数据的,也就是说这些Collection是基于基础类型(base type)而实现,这样才能使得这个Collection具有通用性,即使如此,ArrayList和Vector也不能存储基本类型(primitive type),因为对象数组的元素根本就不能是int、double这样的基本类型,这样类型是不匹配的。除了这个缺点之外,由于实现这些Collection是基于基础类型的,因此编译器无法进行类型检查,同时也就没有办法进行Collection元素之间关系的处理了。举个简单例子,即使一个ArrayList只是装了Integer类型的数据,编译器都没有办法知道,同时也没有办法直接对这个ArrayList进行操作,从中找出最大值。这就是缺少范型支持的一个大缺陷了。泛型如此重要,而模板函数正是实现泛型函数的基础。

        重温C++的旧梦,我是从标准库开始的,在标准库中,处处都能看到template关键字的身影。Almost all parts of the library are written as templates. Without template support, you can't use the standard library. [1]
        看来还是从最根本的开始了——Template(模板)。在这里,我主要将注意力集中在模板函数上。以下是一个最简单的模板函数:

template <class T> 
inline T 
const& max (T const& a, T const&
 b) 

    
return a < b ?
 b : a; 
}
  

        我们在调用模板函数的时候跟调用一般的函数是没有什么区别的:

#include <iostream>
#include 
<string>
#include 
<vector>
#include 
"max.hpp"

int main() {
    
int i = 42

    std::cout 
<< "max(7,i): " << max(7,i) <<
 std::endl; 

    
double f1 = 3.4

    
double f2 = -6.7

    std::cout 
<< "max(f1,f2): " << max(f1,f2) <<
 std::endl; 

    std::
string s1 = "mathematics"

    std::
string s2 = "math"

    std::cout 
<< "max(s1,s2): " << ::max(s1,s2) <<
 std::endl; 
}

        模板函数之所以能够接受任何类型的参数,其实是在编译的过程中,根据模板使用的情况编译成相应的一般的函数去处理这些不同的参数。譬如例子中有三次调用,就会将模板函数编译成三种版本以处理这三种不同类型的参数了。对于第一个调用max( 7, i ),就是使用了将模板函数中的T替换成int的版本,这也说明了如果这样调用是会无法通过编译的:max ( 7,  3.4 )。
        除此之外,所谓能够接受任何类型的参数,其实也是有限制的。譬如说,max函数中的"<"操作符约束了参数a和b的类型,至少这两个参数能够支持这样的操作才是合法的。

        [1] C++ Standard Library, The: A Tutorial and Reference