学习C++的地图比较器

99 阅读4分钟

一个地图是一个元素的列表结构。每个元素都是一个键/值对,而且键是唯一的。一个地图可以按键从最小到最大进行排序。在排序中,一个键随其对应的值移动。像这样的升序排序使用了map的比较器,形式为小于()。

下面的代码在创建地图的过程中,按键以升序对地图进行排序。

        map<char, int, less<char>> mp = {{'P',1}, {'N',2}, {'Q',3}, {'M',4}, {'O',5}};

        for (map<char, int>::iterator ite = mp.begin(); ite != mp.end(); ite++)
            cout << ite->first << " => " << ite->second << endl;

输出结果是:

    M => 4
    N => 2
    O => 5
    P => 1
    Q => 3

这里的比较器的实际代码是 "less",在地图模板的特殊化中。下面的代码是在创建地图的过程中,按照键的降序对地图进行排序。

        map<char, int, greater<char>> mp = {{'P',1}, {'N',2}, {'Q',3}, {'M',4}, {'O',5}};

        for (map<char, int>::iterator ite = mp.begin(); ite != mp.end(); ite++)
            cout << ite->first << " => " << ite->second << endl;

输出结果是:

    Q => 3
    P => 1
    O => 5
    N => 2
    M => 4

这里的比较器的实际代码是map模板特殊化中的" greater"。

对于上面的代码样本,程序中包含了地图库。默认的比较器形式是less。所以,在前面的代码中可以省略掉它,以有。

        map<char, int> mp

用于map模板的特殊化。

如果程序员对C++提供的比较器不满意,程序员可以自己编写。这篇文章解释了程序员如何将自己的比较器应用到map上。字符串键的正常比较是区分大小写的。使用的是不区分大小写的比较的例子:

文章内容

字符串键的正常比较

考虑一个有五个学生的班级,对于名字与分数的映射,在测试中,如下所示。

    Mary => 70
    John => 60
    Susan => 50
    Paul => 45
    Joseph => 75

一个默认比较器的C++地图,less在程序中。

    #include <iostream>
    #include <map>
    #include <string>
    using namespace std;

    int main()
    {
        map<string, int> mp = {{"Mary",70}, {"John",60}, {"Susan",50}, {"Paul",45}, {"Joseph",75}};

        for (map<string, int>::iterator ite = mp.begin(); ite != mp.end(); ite++)
            cout << ite->first << " => " << ite->second << endl;

        return 0;
    }

输出结果是:

    John => 60
    Joseph => 75
    Mary => 70
    Paul => 45
    Susan => 50

这种比较是区分大小写的。程序开始时,为cout对象加入了iostream库。接下来,map库被包含在内;然后字符串库也被包含在内。后面的语句确保程序中使用的任何名称都来自标准名称空间,除非另有说明。

在main()函数中,map被声明为初始化的。main()函数中的下一个代码段显示了排序后的结果(按键)。

自定义不区分大小写的比较运算符

C++标准库中的字符串类的小于(<)运算符是区分大小写的。如果程序员需要一个不区分大小写的字符串字面的小于运算符,他必须要有自己的运算符函数。操作符实际上是一个以特殊方式开始的函数。

从用户的角度来看,小于操作是:

    left < right

其中左和右是操作数。如果左边的操作数小于右边的操作数,这个操作会产生真值。如果左边的操作数与右边的操作数相同或更大,则产生假数。该操作符用于升序排序。

一个自定义的不区分大小写的小于比较运算符,对于字符串字面来说,是。

  bool operator()(char const* left, char const* right) const {
            for ( ; *left != '\0' && *right != '\0'; ++left, ++right ) {
                if ( tolower(*left) != tolower(*right) )
                {
                   return ( tolower(*left) < tolower(*right) );
                }
                else if ( *left != *right)
                {
                   if ( *(left+1) == '\0' && *(right+1) == '\0' )
                   {
                      return (*left < *right);
                   }
                }
             }
             return (tolower(*left) < tolower(*right));
        }

这里,运算符的符号不是<,而是(),它被编码为意味着<。参数left和right分别是指左和右的操作数。"char const*"表示内容字符不能被改变。在参数列表后面的 "const "意味着在地图中引用的键值(字符串)不能被改变,相对于键/值对。这并不意味着地图中每个键/值对元素的顺序不能被改变。

尽管目标小于运算符被定义为(),<仍然在代码中使用。

for-loop从最左边的字符开始,逐个字符地比较左右两个字符串字面。当两个操作数都是小写时,它检查每个操作数的第一个字符是否相同(不区分大小写)。如果它们不相同,那么如果左边的字符小于右边的字符,则返回true;否则,返回false;并且操作函数停止迭代,因为已经得到了答案。如果它们是相同的,那么就继续迭代到第二对相应的字符。

在继续比较第二对相应的字符之前,代码会检查相应的字符是否相同,但属于不同的情况;以及字符串字面的长度是否相同,并且已经到达了它们的末端。如果这些都是真的,那么",可以写成降序排序,但本文将不涉及这个问题。

比较模板的特殊化

简单来说,map的模板参数列表是:

    template<class Key, class T, class Compare = less<Key>>

注意默认的比较器是less。对于上面的map,mp,以及不区分大小写的比较器,其特殊化将是。

    map<const char*, int, cicomp>

其中cicomp是比较器,它是一个类型。这个类型是一个结构的名称或一个类的名称。该结构或类通常只有一个成员,也就是上面的运算符函数定义。在这个特殊化中,使用了 "const char*"而不是字符串类型。

对于结构类型,前面没有指定 "public: "的成员默认为public。对于类类型,上述操作函数必须是一个公共成员。因此,结构类型的定义将是。

    struct cicomp {
        bool operator()(char const* left, char const* right) const {
            for ( ; *left != '\0' && *right != '\0'; ++left, ++right ) {
                if ( tolower(*left) != tolower(*right) )
                {
                   return ( tolower(*left) < tolower(*right) );
                }
                else if ( *left != *right)
                {
                   if ( *(left+1) == '\0' && *(right+1) == '\0' )
                   {
                      return (*left < *right);
                   }
                }
             }
             return (tolower(*left) < tolower(*right));
        }
    };

这两个定义的主要区别是使用了 "public: "这个指定词。类的类型定义将是。

 class cicomp {
        public:
        bool operator()(char const* left, char const* right) const {
            for ( ; *left != '\0' && *right != '\0'; ++left, ++right ) {
                if ( tolower(*left) != tolower(*right) )
                {
                   return ( tolower(*left) < tolower(*right) );
                }
                else if ( *left != *right)
                {
                   if ( *(left+1) == '\0' && *(right+1) == '\0' )
                   {
                      return (*left < *right);
                   }
                }
             }
             return (tolower(*left) < tolower(*right));
        }
    };

这个比较器的程序应该是这样开始的。

    #include <&iostreamgt;
    #include <&mapgt;
    #include <&cctypegt;
    using namespace std;

cctype库是用于tolower()函数的。

自定义比较器的主函数

下面的C++ main()函数适用于结构类型或类类型。

    int main()
    {
        map<const char*, int, cicomp> mp = {{"Mary",70}, {"John",60}, {"Susan",50}, {"Paul",45}, {"Joseph",75}};
 
        for (map<const char*, int, cicomp>::iterator ite = mp.begin(); ite != mp.end(); ite++)
            cout << ite->first << " => " << ite->second << endl;

        return 0;
    }

输出结果是:

    John => 60
    Joseph => 75
    Mary => 70
    Paul => 45
    Susan => 50

为不区分大小写的字符串字面比较器。注意,迭代器的模板特殊化与地图的模板特殊化是一样的。

结论

地图排序的默认比较器是less。比较器是一个类型,它的名字是map声明的第三个参数,属于map模板专业化。这个类型可以是一个结构体或一个类,它的唯一成员可能是一个自定义运算符的定义。本文已经展示了自定义小于运算符的情况。其他的比较运算符也可以同样被定义。