泛型编程与STL

昨天又有做软件的朋友谈到STL,让我想起当初看到STL的时侯惊为天人的情景,今天抽空来谈谈C++和STL。

-------------------------------------------
    C++提供了二种功能强大的抽象方法:面向对象编程与泛型编程。面向对象编程大家一定很熟悉了,这里就不再哆嗦了。提到泛型编程(Generic Programming),有的人可能还不太熟悉,但是提到STL,你就一定会有所耳闻了。
    generic Programming的思想精髓是基于接口编程(相对于OOP,连多态所需的基类都不要了),它的技术出发点是选择子,核心技术是:类型推导、类型萃取、特化/偏特化,其成果是STL库:一组通用容器和一组操作于通用容器上的通用算法。 STL(Standard Template Library,标准模板库) 其实就是泛型编程的实现品,STL是由Alexander Stepanov(STL之父)、David R Musser和Meng Lee三位大师共同发展,于1994年被纳入C++标准程序库。STL虽然加入C++标准库的时间相对较晚,但它却是C++标准程序库中最具革命性的部分,同时也是C++标准程序库中最重要的组成部分。由于新的C++标准库中几乎每一样东西都是由模板(Template)构成的,当然,STL也不会例外。所以,在这里有必要先概要说明一下模板的有关概念。

模板概念
    通过使用模板可以使程序具有更好的代码重用性。记住,模板是对源代码进行重用,而不是通过继承和组合重用对象代码,当用户使用模板时,参数由编译器来替换。模板由类模板和函数模板二部分组成,以所处理的数据类型的说明作为参数的类就叫类模板,而以所处理的数据类型的说明作为参数的函数叫做函数模板。模板参数可以由类型参数或非类型参数组成,类型参数可用class和typename关键字来指明,二者的意义相同,都表示后面的参数名代表一个潜在的内置或用户定义的类型,非类型参数由一个普通参数声明构成。
    说明一个问题最好的方法就是举例,stl的使用暂且不说(这个开发环境带有参考手册),先来看看一个函数(算法)或容器(数据结构)是怎么模板化的,原理清楚了使用应该就不是问题。
    依据简单原则,我们来看一个最简单的算法: “线性查找”,此算法用来从一含有一堆未排序的数据的容器中查找一个值,如果找到,返回下标,否则,返回一个表示没找到的值。

    首先, 我们看最简单的用例: 从整行数组中查找值, 约定失败返回 -1, 因为正常下标都是从0开始的整数。

    int find(const int a[], int n, int key)
    {
        for(int i=0; i< n; i++)

        {
           if(a[i] == key)

               return i;
        }
        return -1;

    }

    这个算法大概是最简单最常用的了, 现在, 我们希望它不仅可以对int对象,还可以对double对象,对用户定义的任何对象使用,安以前的常规做法,就需要对每个类型编写一个同样的算法。嗯,当然,你可以 Ctrl+C, Ctrl+V来完成^^, 不过,现在我们用模板,有更好的方法了:
   template<typename T>
    int find(const T a[], int n, T key)
    {
        for(int i=0; i< n; i++)

        {
           if(a[i] == key)

               return i;
        }
        return -1;

    }

     // 调用:
     int a[5] ={ 1, 2, 3, 5, 6};
     int i = find(a, 5, 2); // i==1
    现在,我们可以对任意的类型T进行查找,要求是 T 类型可以用" == " 来比较, 这对大部分来说是没问题的,但是对C风格字符串来说就不对了,因为我们希望比较的是字符串的内容,而不是地址, 好在可以为特别的类型定义特别的实现(这称之为“模板偏特化”):

     template< >
     int find< char* > (const char* a[], int n, char* key)
     {
        for(int i=0; i< n; i++)
        {
           if(strcmp(a[i], key)==0)
               return i;
        }
        return -1;
    }
    
    好了, 现在可对字符串操作了, 但是, 我如果想要不分大小写比较呢? 或者,是其它的对象,我需要多种不同的比较呢? 我们把测试条件也模板出来!

template<typename Type, typename Equaler>
int find(const Type* array, int n, Type key)
{
   int i = 0;
   for(int i = 0; i < n; i++ )
   {
       if(Equaler()(key, array[i]))
       {
           return i;
       }
    }
   return -1;
}

现在,对于比较部分, 全部用第二个模板参数来确定, Equaller 的定义可以如下
//等于比较器
template<typename Ty>
struct equal
{
    bool operator()(const Ty& _left, const Ty& _right)
   {
      return (_left == _right);
   }
};

分大小写的比较器可以如下:
template< >
struct equal<char* >
{
   bool operator()(const char* & _left, const char* & _right)
   {
     return (strcmp(_left, _right) == 0 );
   }
};

不分大小写的比较器可以如下:

struct equalNoCase
{
   bool operator()(const char* & _left, const char* & _right)
   {
      return (stricmp(_left, _right) == 0 );
   }
};

//不分大小写的查找可以这样调用, 分大小写的 用 euqal<char*>
int i = find< char*, equalNoCase<char* >  >(strs, n, strKey);

函数也可以换一种写法,  这样就可以直接传递参数给函数, 不需要用尖括号指定模板了
template<typename Type, typename Equaler>
int find(const Type* array, int n, Type key,Equaler eql )
{
   int i = 0;
   for(int i = 0; i < n; i++ )
   {
       if( eql(key, array[i]) )
       {
           return i;
       }
    }
   return -1;
}

// 调用
int i = find(strs, n, strKey,equalNoCase() );

好了,现在我们看看, 加入要查找符合某个条件的呢, 可以用一个一元操作符来测试
template< typename T >
struct Pr
{
    bool operator()(const T& v)
    {
        if(...)  //满足测试条件
          return true;
        else
          return false;
    }
};

现在可以写一个 find_if
template<typename Type, typename Pr>
int find_if(const Type* array, int n, Type key,Pr  pr )
{
   int i = 0;
   for(int i = 0; i < n; i++ )
   {
       if( pr(array[i]) )
       {
           return i;
       }
    }
   return -1;
}

    现在,我们看看,我们的find函数可以工作得很好了,不过, 如果要找小于5的你得写一个pr, 小于6的你得又写pr, 是不是很烦呢? 现在,我们实现一个绑定器,绑定我们要的参数,以便可以这样使用:
    int find_if(array, n, key,bind2nd(less<int>,5) ); //小于5的条件

// 下面实现一个简单的绑定器
为了实现绑定,我们需要修改下 less比较器, 加上类型信息
template<typename T>
{
  typdef T arg_type;// 增加这个类型定义
  //
};

template < typename Fn_type,  Fn_type::arg_type rgiht>
struct bind2nd
{
    typedef  Fn_type::arg_type arg_tye;

    bind2nd(Fn & fn,  arg_type rgiht): _Func(fn), _Value( right)
    bool operator()( const arg_tye & left)
    {
         return fn(left, right);
    }
    Fn_type   _Func;// 二参数的函数对象
    arg_type  _Value;// 右边的操作数
};

    同样你给greater, equal 添加一个 typedef 定义。 好了,现在你可以用这个绑定器绑定大于, 等于, 小于的比较器了。

    stl 的实现为了更通用, 比这个要复杂一些, 但是原理是一样的。

    现在我们的 find_if、find 函数可以作用在任何 可以通过下标访问的数组上了, 但是, 如果使用的是不能通过下标的容器呢? 比如 list 等,下次我们将把位置指示器模板化, 这个就是 iterator 了, 通常翻译的是 迭代器, 其实叫位置指示器更确切,其行为类似于指针。

吃饭了去,下次继续。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值