STL学习笔记:map容器里find函数的第三个参数实现原理,函数对象(仿函数)

最近在看STL里的map容器,一开始是打算直接存储一个键值对map <char*,int>类型,发现在调用map.find(key)是无法查到对应元素值,仔细debug一下,发现在存储key是存储的是char* 的地址,因此map.find()函数在调用内部自带的比较函数是直接比较指针的地址,这样就永远找不到合适的值。
首先解释函数对象:
重载函数调用操作符的类,其对象常称为函数对象(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<char*,int, cmp_str> dict;时,cmp_str的构造函数和析构函数都已调用完毕,接下还map内部会调用cmp_str函数对象来进行查找对应的key值,最后查到元素d。

但是为什么内部实现的时候调用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<T, T1>::Test()
{

}

template<class T, class T1>
Test<T, T1>::~Test()
{

}

template<class T, class T1>
void Test<T, T1>::find(T1 c)
{
    if (cmp(c, "abc"))
    {
        cout << "找到了" << "  " << c << endl;
    }
}

int main()
{
    Test<cmp_str,char*> 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<cmp_str,char*> t;时,只是调用了构造函数,没有调用析构函数,说明map内部实现不仅仅是简单的包含关系。
接下来,我把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<T, T1>::Test()
{
    cmp = new T;

    delete cmp;
}

template<class T, class T1>
Test<T, T1>::~Test()
{

}

template<class T, class T1>
void Test<T, T1>::find(T1 c)
{
    if ((*cmp)(c, "abc"))
    {
        cout << "找到了" << "  " << c << endl;
    }
}



int main()
{
    Test<cmp_str,char*> 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的源码也没有找到合适的理由和实现。以上仅仅是我简单的猜测,有更好的解答的请评论联系我,共同学习。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值