STL传递比较函数进容器的三种方式

对于STL中的依靠比较排序的容器,均提供了一个模板参数来传递比较函数,默认的为std::less<>。

查阅Containers - C++ Reference可以看到典型的使用比较函数的容器有

template <class T, class Container = vector<T>,
  class Compare = less<typename Container::value_type> > class priority_queue;
template < class T,                        // set::key_type/value_type
           class Compare = less<T>,        // set::key_compare/value_compare
           class Alloc = allocator<T>      // set::allocator_type
           > class set;
template < class Key,                                     // map::key_type
           class T,                                       // map::mapped_type
           class Compare = less<Key>,                     // map::key_compare
           class Alloc = allocator<pair<const Key,T> >    // map::allocator_type
           > class map;

分别是优先队列、集合、映射,当然multiset和multimap也一样。

这里以优先队列为例,分别给出三种传递方式,将比较函数从默认的less<>(升序)改成降序。

这里先看看优先队列的构造函数源码

protected:
    _Container c;    // the underlying container
    _Pr comp;    // the comparator functor
    priority_queue(const _Myt& _Right)
        : c(_Right.c), comp(_Right.comp)
        {    // construct by copying _Right
        }

    explicit priority_queue(const _Pr& _Pred)
        : c(), comp(_Pred)
        {    // construct with empty container, specified comparator
        }

1、函数指针

bool greaterInt(const int& lhs, const int& rhs)
{
    return lhs > rhs;
}
priority_queue<int, vector<int>, bool(*)(const int&, const int&)> q(greaterInt);

典型C风格写法,有种调用C库的qsort的感觉,代码有点长,虽然可以在前面加一句typedef bool(*Compare)(const int&, const int&)来缩短单行代码,但代码总共还是很长

2、函数对象

template <typename T>
struct Greater
{
    bool operator()(const T& lhs, const T& rhs) const
    {
        return lhs > rhs;
    }
};
priority_queue<int, vector<int>, Greater<int>> q;

注意,这里的q是采取默认构造。回顾之前的构造函数,字段comp在默认构造函数是直接用默认构造的,所以这里可以不写参数,而对于函数指针则不同,函数指针不是类,没有构造函数,所以必须添上参数。

那么,如果采用显式写法呢?

priority_queue<int, vector<int>, Greater<int>> q(Greater<int>());

编译器会提出警告

 warning C4930: ‘std::priority_queue<int,std::vector<int,std::allocator<_Ty>>,Greater<int>> q(Greater<int> (__cdecl *)(void))‘:                 prototyped function not called (was a variable definition intended?)
1>          with
1>          [
1>              _Ty=int
1>          ]

这句代码不再是定义一个优先队列,而是声明一个函数指针,类似下面这种

Type q(Greater<int>());

函数重载是个语法糖,重载括号操作符的Greater<int>()(const int&, const int&)就像定义某个函数Greater_Int_XXX(const int&, const int&),Greater<int>()自然就对应了Greater_Int_XXX。

如果继续测试下去(比如q.push(1);)可以发现编译无法通过,提示left of ‘.push‘ must have class/struct/union,证明了q在这里不是容器对象,而被当成了函数指针。

所以没必要画蛇添足,直接用默认构造就行了。

3、lambda表达式

    auto comp = [](const int& lhs, const int& rhs) { return lhs > rhs; };
    priority_queue<int, vector<int>, decltype(comp)> q(comp);

由于lambda表达式类型要无法手写出来,所以C++ 11提供了decltype关键字来取得类型。至于decltype的用法,本文不多做说明,网上资料很多。

比较

使用函数指针是最快的,因为无需构造对象或者lambda表达式,但是通用性不强,函数对象使用模板类只需改变模板参数就能适应不同类型,对特殊类型还可以做模板特化。而lambda表达式写起来比函数对象更为方便,适用于无需重复利用的比较函数。不过像==、!=、<、>、<=、>=等比较运算的函数对象都已经有现成的(见<functional> - C++ reference中的equal_to、not_equal_to、less、greater、less_equal、greater_equal),似乎lambda表达式一般用不着?

啊,说到这里,要绑定这三种不同的比较函数形式,用通用的function模板即可

  typedef std::function<bool(const int& lhs, const int& rhs)> Compare;
    typedef std::priority_queue<int, std::vector<int>, Compare> MyPriorQueue;
    // 1. 函数指针
    MyPriorQueue q1(greaterInt);
    // 2. 函数对象
    Greater<int> compFunctor;
    MyPriorQueue q2(compFunctor);
    // 3. lambda表达式
    auto compLambda = [](const int& lhs, const int& rhs) { return lhs > rhs; };
    MyPriorQueue q3(compLambda);

    vector<pair<string, MyPriorQueue>> my_prior_queues = {
        make_pair("函数指针", q1),
        make_pair("函数对象", q2),
        make_pair("lambda表达式", q3)
    };
    for (auto item : my_prior_queues)
    {
        auto& method = item.first;
        cout << method << "\t";
        auto& q = item.second;
        q.push(1);
        q.push(3);
        q.push(2);
        q.push(4);
        q.push(0);
        while (!q.empty())
        {
            cout << q.top() << " ";
            q.pop();
        }
        cout << endl;
    }

时间: 2024-11-01 00:45:48

STL传递比较函数进容器的三种方式的相关文章

部署webapp到web容器的三种方式(这里的web容器Tomcat)

*******************************这是看传智播客的学习视频学到的*********************************** 0.首先看看我们的例子** 1.第一种方式: 直接将项目放到webapps目录下即可.   * /hello:项目的访问路径-->虚拟目录 * 简化部署:将项目打成一个war包,再将war包放置到webapps目录下. * war包会自动解压缩 1.1直接把webapp的文件夹复制 1.2把webapp的war包放下面 1.3运行结果

谈谈vector容器的三种遍历方法

说明:本文仅供学习交流,转载请标明出处,欢迎转载! vector容器是最简单的顺序容器,其使用方法类似于数组,实际上vector的底层实现就是采用动态数组.在编写程序的过程中,常常会变量容器中的元素,那么如何遍历这些元素呢?本文给出三种遍历方法. 方法一:采用下标遍历 由于vector容器就是对一个动态数组的包装,所以在vector容器的内部,重载了[]运算符,函数原型为:reference operator [] (size_type n);所以我们可以采用类似于数组的方式来访问vector容

数据结构《19》----String容器的三种实现

一.序言 一个简单的string 容器到底是如何实现的? 本文给出了 String 的三种从易到难的实现,涉及了 reference counting, copy on write 的技术. 二.第一个实现 我们设计的string类里面包含一个char* 的指针, 通过指针的管理,来实现string的基本功能. 废话不多说了,直接上代码: 几点注意的:类包含指针,因此需要 copy control, 也就是自行实现拷贝构造函数,赋值构造函数,析构函数, 而不能依赖于编译器生成的默认版本,默认版本

容器间通信的三种方式 - 每天5分钟玩转 Docker 容器技术(35)

容器之间可通过 IP,Docker DNS Server 或 joined 容器三种方式通信. IP 通信 从上一节的例子可以得出这样一个结论:两个容器要能通信,必须要有属于同一个网络的网卡. 满足这个条件后,容器就可以通过 IP 交互了.具体做法是在容器创建时通过 --network 指定相应的网络,或者通过 docker network connect 将现有容器加入到指定网络.可参考上一节 httpd 和 busybox 的例子,这里不再赘述. Docker DNS Server 通过 I

docker-修改容器的挂载目录三种方式

原文:docker-修改容器的挂载目录三种方式 方式一:修改配置文件(需停止docker服务) 1.停止docker服务 systemctl stop docker.service(关键,修改之前必须停止docker服务) 2.vim /var/lib/docker/containers/container-ID/config.v2.json 修改配置文件中的目录位置,然后保存退出 "MountPoints":{"/home":{"Source"

菜鸟学习Spring——SpringIoC容器基于三种配置的对比

一.概述 对于实现Bean信息定义的目标,它提供了基于XML.基于注解及基于java类这三种选项.下面总结一下三种配置方式的差异. 二.Bean不同配置方式比较. 三.Bean不同配置方式的适用场合. 四.总结. 一般我们适用XML配置DataSource.SessionFactory等资源的Bean,在XML中利用aop.context命名空间进行主题的配置.其他所有项目中开发的Bean用注解的形式来配置.这就是采用了"XML+基于配置"的配置方式,很少采用基于Java类的配置方式.

Ioc容器Autofac 三种注册方式

简单来说,所谓注册组件,就是注册类并映射为接口,然后根据接口获取对应类,Autofac将被注册的类称为组件. 虽然可像上篇提到的一次性注册程序集中所有类,但AutoFac使用最多的还是单个注册.这种注册共有三种方式,其中最简单的就是用As方法,例如,ArrayList继承了IEnumerable接口,若将其注册到Autofac中,写法如下所示: ContainerBuilder builder = new ContainerBuilder(); builder.RegisterType<Arra

【Spring】创建对象的三种方式

关于Spring的搭建可参见:浅析Spring框架的搭建.在测试之前还是应该先将环境配置好,将相关Jar包导进来.Spring创建的对象,默认情况下都是单例模式,除非通过scope指定. 一.通过构造函数创建对象. 2.1 利用无参构造函数+setter方法注入值 最基本的对象创建方式,只需要有一个无参构造函数(类中没有写任何的构造函数,默认就是有一个构造函数,如果写了任何一个构造函数,默认的无参构造函数就不会自动创建哦!!)和字段的setter方法. Person类: package com.

apache两种方案三种方式实现反向代理tomcat

目录 1.概述 2.方案一:以proxy_module方式反向代理 3.方案二:以mod_jk方式反向代理 4.总结 1.概述 在前一博客(http://zhaochj.blog.51cto.com/368705/1639740)中实现了tomcat的在standalone模式下的部署,这样tomcat就身兼职两职,一方向要对http的请求作出响应,又要处理JSP程序,而处理http请求不是tomcat的强项,所以这样的请求就交给httpd.nginx这样的的专业处理http请求的套件来处理,而