最近在看STL里的map容器,一开始是打算直接存储一个键值对map
首先解释函数对象:
重载函数调用操作符的类,其对象常称为函数对象(function object),即它们是行为类似函数的对象。又称仿函数。
以上是百度百科里的定义,附上链接:https://baike.baidu.com/item/%E5%87%BD%E6%95%B0%E5%AF%B9%E8%B1%A1/7721014?fr=aladdin
STL map源代码:
template<class _Kty,
class _Ty,
class _Pr = less<_Kty>,
class _Alloc = allocator<pair<const _Kty, _Ty>>>
class map
: public _Tree<_Tmap_traits<_Kty, _Ty, _Pr, _Alloc, false>>
通过查看源代码,STL自带的比较函数为less<_Kty>。
less<_Kty>的源代码:
template<class _Ty = void>
struct less
{ // functor for operator<
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty first_argument_type;
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty second_argument_type;
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef bool result_type;
constexpr bool operator()(const _Ty& _Left, const _Ty& _Right) const
{ // apply operator< to operands
return (_Left < _Right);
}
};
通过源代码知:这里重写类的()操作符,构造了一个函数对象(仿函数),内部实现是直接用<号比较值,而指针通过<号比较的是地址,即是开头问题所在。
接来下,我通过重写指针比较的()操作符:
class cmp_str
{
public:
cmp_str()
{
cout << "1111111" << endl;
}
bool operator()(char const *a, char const *b) const
{
return std::strcmp(a, b) < 0;
}
~cmp_str()
{
cout << "222222222" << endl;
}
};
这里构造函数为直接输出一句话,一会测试代码即会发现问题,不要忘了书写()操作符结尾的const,这个限定符的作用是防止函数改变对象的this指针。
测试代码:
int main()
{
map<char*,int, cmp_str> dict;
cin >> n;
for (int i = 0; i < n; i++)
{
char* tmp = new char[100];
cin >> tmp;
dict.insert(pair<char*,int>(tmp, i);
}
char* tmp = new char[100];
cin >> tmp;
map<char*, int>::iterator it1 = dict.find(tmp);
if (it1 != dict.end())//找到
{
cout << it1->first << " " << it1->second << endl;
}
system("pause");
return 0;
}
可以发现,在程序运行完map
但是为什么内部实现的时候调用cmp_str函数对象时没有再次构造匿名对象,或者为什么一开始构造完毕后有析构掉呢,这样会不会每次都浪费系统的构造资源。。。
带着这个疑问,我就做了下面的测试:
class cmp_str
{
public:
cmp_str()
{
cout << "1111111" << endl;
}
bool operator()(char const *a, char const *b) const
{
return std::strcmp(a, b) < 0;
}
~cmp_str()
{
cout << "222222222" << endl;
}
};
template <class T,class T1>
class Test
{
public:
Test();
~Test();
void find(T1 c);
private:
T cmp;
};
template<class T, class T1>
Test::Test()
{
}
template<class T, class T1>
Test::~Test()
{
}
template<class T, class T1>
void Test::find(T1 c)
{
if (cmp(c, "abc"))
{
cout << "找到了" << " " << c << endl;
}
}
int main()
{
Testchar*> t;
char* c = new char[4];
strcpy(c, "aaa");
t.find(c);
strcpy(c, "aab");
t.find(c);
strcpy(c, "abb");
t.find(c);
system("pause");
return 0;
}
这里可以看出,自己模拟实现的map(这里简单只存一个key值,运用Test类模拟)里通过has-a的关系内部包含一个cmp_str对象,调用find()函数时直接调用cmp仿函数进行查找。可是在运行完Test
接下来,我把Test里的私有成员T cmp;改为T *cmp;这样的话就不会主动调用cmp_str的构造函数,我们接下来在Test的构造函数自己new一个cmp_str对象:cmp = new T;
为了在执行完cmp_str的构造函数以后直接调用cmp_str的析构函数,所以new完以后直接delete。这样就可以实现输出完111111直接输出22222的现象。至于为什么这样,后面解释。
别忘了改为指针以后再()运算符里要对cmp指针进行解指针,即将if (cmp(c, “abc”))改为
if ((*cmp)(c, “abc”))。
改后的代码:
class cmp_str
{
public:
cmp_str()
{
cout << "1111111" << endl;
}
bool operator()(char const *a, char const *b) const
{
return std::strcmp(a, b) < 0;
}
~cmp_str()
{
cout << "222222222" << endl;
}
};
template <class T,class T1>
class Test
{
public:
Test();
~Test();
void find(T1 c);
private:
T *cmp;
};
template<class T, class T1>
Test::Test()
{
cmp = new T;
delete cmp;
}
template<class T, class T1>
Test::~Test()
{
}
template<class T, class T1>
void Test::find(T1 c)
{
if ((*cmp)(c, "abc"))
{
cout << "找到了" << " " << c << endl;
}
}
int main()
{
Testchar*> t;
char* c = new char[4];
strcpy(c, "aaa");
t.find(c);
strcpy(c, "aab");
t.find(c);
strcpy(c, "abb");
t.find(c);
system("pause");
return 0;
}
此时,即实现了map函数里的比较函数原理。
在这里,为什么new完以后又直接delete呢?我猜测有两点:
1.运用指针代替直接包含对象,节省空间。因为不知道对象的内存大小有多大,而指针一般都为4字节,节省了空间。
2.在Test构造函数里new一下是为了调用cmp_str的构造函数,这样就可以初始化一下cmp_str类的其他数据成员,不仅仅是只有重载()运算符,这里只是简单的举例。而后直接调用delete,运行析构函数,也是为了节省内存吧。
其实不用new和delete也可以直接运用if ((*cmp)(c, “abc”)),这样就不用调用cmp_str的构造函数了。而当直接在类的声明里实现()运算符的定义从而形成内联作用会加快程序的效率,这也可能是运用函数对象比运用函数指针效率高的原因之一吧。
总感觉哪里有点不对,分析STL的源码也没有找到合适的理由和实现。以上仅仅是我简单的猜测,有更好的解答的请评论联系我,共同学习。