目录
一、 泛型算法
泛型算法是STL库里面定义的一些算法,这些算法可以用同一个接口操作各种数据类型,因此称为泛型算法。
二、 简单实现sort
2.1 思路
在观赏STL源码之前,不妨想一想,如果是我自己的话,如何去做?首先,模板是要有的(不然没办法实现泛型)。对,提到排序,只需要明确——排序区间以及排序方式。具体如何实现是函数设计者该考虑的问题,这就是模块化编程的好处——函数使用者关注的更多是如何使用而不是如何实现,我们不能重复造轮子!但熟悉轮子可以帮助我们下一次提升轮子。
排序区间靠两个迭代器约束(first, last指向最后一个元素的下一个),左闭右开是编程时约定俗成的手法。排序方式靠函数对象去实现。函数对象——行为类似函数,但是其本质却是一个对象,而且是一个重载了()运算符的对象。
对于函数而言,括号称为函数调用操作符!如果光看见一条语句fun(),谁知道是函数调用还是对象调用成员函数呢?可能就是恰好有一个类实例化后的对象名为fun,而且该类也恰好重载了()呀!
2.2 包含的头文件
#include<iostream>
#include<vector>
#include<time.h> //生成随机数会用到。
using namespace std;
2.3 重载输出运算符
template<typename T>
ostream& operator<<(ostream& mcout, vector<T> vc) //重载<<运算符
{
typename vector<T>::iterator ite = vc.begin();
while (ite != vc.end())
{
mcout << *ite<<" ";
++ite;
}
return mcout;
}
不妨重载输出运算符看成是一个普通的全局函数吧,更准确地说——就是一个全局函数。
2.4 简单实现sort
定义函数对象
template<typename T> //谓词——说明是按照何种方式去排序,升序还是降序???
class mless
{
public:
bool operator()(T a, T b)
{
return a < b;
}
};
实际上,上述对象的功能就是指明按何种方式排序——此处是按照降序排列。特别地,这个对象有一个非常形象的名称——函数对象。行为和函数十分类似(重载了函数调用运算符的类实例化的对象就是函数对象)构造方法和析构方法都用默认的就可以了,因为我们只是想用重载成员函数()而已。
定义交换函数
template<typename T>
void mswap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
由模板实现的交换函数——利用函数模板类型自推功能,何解?就是不知道T到底是什么类型(这就是泛型的精华,事先一切都是未知的!),但是需要用T去定义变量。
定义泛型算法sort
template<typename SrcIt, typename Pre>
void mbubble_sort(SrcIt first, SrcIt last, Pre pre) //冒泡排序
{
SrcIt tmp = first;
int i = 0;
cout << sizeof(pre) << endl;
for (first; first != (last - 1); i++, first++) //控制趟数
{
bool flg = true; //某趟冒泡没有发生交换。
for (SrcIt ite = tmp; ite != (last - 1 - i); ++ite) //每一趟将剩余部分最值冒到最后
{
if (pre(*ite, *(ite + 1)))
{
mswap(*ite, *(ite + 1));
flg = false;
}
}
if (flg) break;
}
}
template<typename SrcIt, typename Pre>
void mselect_sort(SrcIt first, SrcIt last, Pre pre) //直接选择排序
{
for (first; first != (last-1); ++first) //执行多少趟
{
SrcIt ite = first;
for (SrcIt next = first + 1; next != last; ++next) //每一趟将剩余部分最值找到
{
if (pre(*ite, *next))
{
ite = next;
}
}
if (ite != first)
mswap(*ite, *first);
}
}
template<typename SrcIt, typename Pre>
void msort(SrcIt first, SrcIt last, Pre pre)
{
//mbubble_sort(first, last, pre); //测试冒泡排序
mselect_sort(first, last, pre); //测试直接选择排序
}
本文只是用选择排序来模拟sort的最精华的部分。 STL中实现方式是这样的,1、数据规模很大时,使用快速排序;2、随着数据倾向于有序,使用选择排序;3、数据已经很有序了,使用插入排序。有机会的话,自主编程来模拟一下吧。探讨排序效率不是本篇博客的重点。
2.5 交换函数实现遇见的问题(承接2.4节)
关于交换函数,有两点需要注意,参数类型是引用(或指针),不然交换不起作用;第二点,可能会犯以下代码出现的错误:
template<typename SrcIt>
void mswap(SrcIt& ite1, SrcIt& ite2)
{
SrcIt tmp;
*tmp = *ite1;
*ite1 = *ite2;
*ite2 = *tmp;
}
咋一看好像没什么问题,编译链接顺利通过!其实潜在的问题是最可怕的!tmp指向谁?指针有野指针的说法,迭代器可能也有“野迭代器”的说法(牵强附会) ,tmp指向谁?请看图1 , 报错信息的含义大概是说——tmp解引用是非法的(因为不知道tmp到底指向哪里)。要明确让tmp指向哪里,比较棘手。如果能知道到底是给什么容器(容器存放之物的类型也需要明确)配备的迭代器的话,在函数体内临时实例化容器类型的变量,让tmp指向该变量,比如说vector<int>的一个元素,就能解决这个问题。
2.6 冒泡函数实现遇见的问题(承接2.4节)
关于冒泡排序的实现,不能写成下面这种:
template<typename SrcIt, typename Pre>
void mbubble_sort(SrcIt first, SrcIt last, Pre pre) //冒泡实现
{
SrcIt tmp = first;
cout << sizeof(pre) << endl;
for (first; first != (last - 1); first++)
{
bool flg = true; //某趟冒泡没有发生交换。
for (SrcIt ite = tmp; ite != (last - 1 - first); ++ite)
{
if (pre(*ite, *(ite + 1)))
{
mswap(*ite, *(ite + 1));
flg = false;
}
}
if (flg) break;
}
}
for (SrcIt ite = tmp; ite != (last - 1 - first); ++ite)
冒泡排序的错误示范如果是一般的int i,j去实现的话,是没有问题的。外层循环控制趟数,内层循环控制每趟该将哪一个泡泡(最值)冒到最后面去(也可以是最前面)。
迭代器减去一个数字,结果是迭代器;迭代器减去一个迭代器,就是两个迭代器之间的距离(distance)。
运算符"!="有一种重载是针对迭代器的,操作对象两边都是迭代器。如果写成错误示例,左边是迭代器,右边是整型。当然会报错,正如图3 向我们反映的那样——没有重载关于这种情况的。就算重载了,也是没有意义的,迭代器不等于两个迭代器之间的距离,这本身就是不对的!
三、 总结
3.1 测试用例
int main()
{
vector<int> vc1;
srand((unsigned)time(0)); //随机数种子
for (int i = 0; i < 10; ++i)
vc1.push_back(rand() % 100 + 1);
msort(vc1.begin(), vc1.end(), mless<int>());
cout << vc1 << endl;
return 0;
}
msort(vc1.begin(), vc1.end(), mless<int>());
在泛型算法sort使用的时候,第一、二个参数都是迭代器,第三个参数为什么没有变量名啊?小技巧,类型后面加括号,将会产生一个暂时对象(道理就像int()会产生一个暂时对象一样)。因为是匿名的关系,所以无法使用,最大的作用就是值传递。这一点在STL中很常见。
--------参考文献 侯捷.STL源码剖析[M].武汉:华中科技大学出版社,2002.6:96-96.
3.2 感想
Any problem in computer science can be solved by another layer of indirection.
计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。
--------参考文献 余甲子.石凡.潘爱民.程序员自我修养[M].北京:电子工业出版社,2009.4:8-8.