readme
STL容器算法几乎我们每天都会用,查找一个元素需要用到find,删除一个元素需要用到erase和remove,统计需要用到accumulate或for_each等等。许多算法有两种版本,一种是容器自己的成员函数,例如vector, set都有自己的find成员函数;另一种是通用的非成员函数,例如STL标准算法也有find函数,可以完成vector和set容器中的查找工作。这篇博客以set容器和find算法为例,从效率和功能两方面对比了两种版本算法之间的差别。
效率方面
成员函数find和非成员函数find之间的关系很像量产和定制。一件量产商品通常追求销量,要满足尽可能更多用户的需求,这也意味着这件商品通常不会致力于每个用户都百分百满意。而定制商品不同,它们追求让小部分用户得到极致的满足,而在必要时放弃考虑大多数用户的需求。
有点扯远了,LOL~~。不过话糙理不糙,set容器的成员函数find,就像是为set定制的一样,它清楚set内部使用了红黑树结构,它的复杂度是对数级别的。而非成员函数find并没有做这种定制,因为它同时要服务vector,set,list等容器,它只能一个一个的找。虽然这两种版本都是为了找到set容器中是否存在某个元素,但是当元素数量较大时,这种效率差异就会非常明显。
功能方面
之前有写过一篇博客介绍“相等”和“等价”之间的差别,在这里又用上了。还是相同的道理,非成员函数find同时要服务vector,set,list等容器,它判断元素是否存在的底层技术是“相等”。而set成员函数find用到的底层技术是“等价”,set容器还允许用户自定义一个比较判别式,set成员函数find会用到这个比较判别式。
基于上述事实,两种版本的find函数所做的事情又不尽相同,因为它们判断的方法不一样,一个用的是是否“相等”,另一个用的是可以定制化的是否“等价”。
测试程序
//common.h
#ifndef _COMMON_H_
#define _COMMON_H_
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <utility>
#include <algorithm>
#include <vector>
#include <iterator>
#include <sys/time.h>
using namespace std;
#endif
//Timer.h
#ifndef _TIMER_H
#define _TIMER_H
#include <sys/time.h>
#include "common.h"
class Timer
{
public:
Timer(string strCallInfo = "Timer", bool us_need = false)
{
_desc = strCallInfo;
_us_need = us_need;
gettimeofday(&_begin, NULL);
gettimeofday(&_end, NULL);
};
~Timer()
{
gettimeofday(&_end,NULL);
printf("%s, cost %ldms\n", _desc.c_str(), (_end.tv_sec - _begin.tv_sec) * 1000 + (_end.tv_usec - _begin.tv_usec) / 1000);
if (_us_need)
{
printf("%s, cost %ldus\n", _desc.c_str(), (_end.tv_sec - _begin.tv_sec) * 1000000 + _end.tv_usec - _begin.tv_usec);
}
};
private:
Timer(){};
string _desc;
bool _us_need;
timeval _begin;
timeval _end;
};
#endif
//member_vs_nonmember.cpp
#include "common.h"
#include "Timer.h"
struct Aastrcmp:public binary_function<string, string, bool>
{
bool operator()(const string& str1, const string& str2)
{
return strcasecmp(str1.c_str(), str2.c_str());
}
};
int main(void)
{
set<int> testSetInt;
for (int i = 0; i < 10000; ++i)
{
testSetInt.insert(i);
}
vector<int> intVector;
intVector.reserve(10000);
for (int i = 0; i < 10000; ++i)
{
intVector.push_back(i + 5000);
}
{
Timer timer("non-member function find cost");
for (int i = 0; i < 10000; ++i)
{
find(testSetInt.begin(), testSetInt.end(), intVector[i]);
}
}
{
Timer timer("member function find cost");
for (int i = 0; i < 10000; ++i)
{
testSetInt.find(intVector[i]);
}
}
set<string, Aastrcmp> testSetStr;
testSetStr.insert("Abcd");
testSetStr.insert("abcd");
if (find(testSetStr.begin(), testSetStr.end(), "ABCD") == testSetStr.end())
{
printf("ABCD not found by non_member function find\n");
}
else
{
printf("ABCD found by non_member function find\n");
}
if (testSetStr.find("ABCD") == testSetStr.end())
{
printf("ABCD not found by member function find\n");
}
else
{
printf("ABCD found by member function find\n");
}
}
编译和执行结果
g++ -o member_vs_nonmember member_vs_nonmember.cpp -O0 -g -Wall -std=c++11
./member_vs_nonmember
non-member function find cost, cost 1012ms
member function find cost, cost 2ms
ABCD not found by non_member function find
ABCD found by member function find
测试程序说明
效率方面,可以从打印信息直观的看到,两个版本的find算法效率差了不止一个数量级。功能方面,两个版本的find算法得到的结论也不一致。综上,大多数场景下,推荐优先考虑成员函数版本的算法。