• C++字符串比较的踩坑/详解



    tags: C++

    最近学习C++11中的initializer_list这一新特性, 一个实例是关于字符串比较大小的, 代码如下:

    cout << max({string("Fff"), string("Eoa"), string("Acc")}) << endl;
    
    • 1

    运行结果如下:

    Fff
    
    • 1

    很显然最大值就是字符串"Fff"(依字典序), 但是我觉得string这个关键字可以去掉, 也就是将上述代码改为:

    cout << max({"Fff", "Eoa", "Acc"}) << endl;
    
    • 1

    但是这时候结果就变成了:

    Acc
    
    • 1

    这是为什么呢?

    分析

    一开始我以为问题出在max()函数的身上, 找出源码后发现其本质还是逐元素比较, 每次更新最大值, 只不过用到了一些initializer_list的东西, 这里列出核心的代码:

    template<class ForwardIterator, class Compare>
    ForwardIterator __max_elem(ForwardIterator first, ForwardIterator last, Compare comp) {
        if (first == last)
            return first;
        ForwardIterator result = first;
        while (++first != last)
            if (comp(result, first))//result
                result = first;
        return result;
    }
    
    
    struct Iter {
        //仿函数
        template<typename I1, typename I2>
        bool operator() (I1 it1, I2 it2) const
        {return *it1 < *it2;}
    };
    
    
    inline Iter __iter_less_iter() {
        return Iter();
    }
    
    
    template<class T>
    inline T max_elem(T first, T last) {
        return __max_elem(first, last, __iter_less_iter());
    }
    
    
    template <typename T>
    inline T max(initializer_list<T> l) {
        return *max_elem(l.begin(), l.end());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    思路的话这里不进一步说了, 熟悉模版的话很好理解的. 其实核心就是通过迭代器读取首元素和下一个元素, 比较之后更新result, 里面的迭代器解包用到了仿函数, 通过struct构造了一个结构体实现.


    那么不是这个问题的话, 又是什么呢? 由于C++的主要特性就是面向对象, 那么就可以将这些字符串的类型输出出来, 看看能不能找出问题所在:

    //C 风格的char-array, size=4
    cout << typeid("abc").name() << endl;
    cout << typeid("a").name() << endl;
    cout << typeid('a').name() << endl;
    // ----------------------------------
    cout << typeid(string("abc")).name() << endl;
    cout << typeid(string("a")).name() << endl;
    // cout << typeid(string('a')).name() << endl; //error cannot conversion from char to string
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    运行结果如下:

    A4_c
    A2_c
    c
    NSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE
    NSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可以看出, 是否加string的区别还是很大的, 不加的话还是C-style的字符数组, 而数组之间比较大小默认比较的是首地址, 即地址对应的十六进制值, 可以看下面的一段代码:

    #include 
    #include 
    int main(int argc, char const *argv[])
    {
        char s1[]="aae", s2[]="cde";
        printf("s1:%p, s2:%p, s1>s2:%d\n", s1,s2,s1>s2);
        char *s3="aae", *s4="cde";
        printf("s3:%p, s4:%p, s3>s4:%d\n", s3,s4,s3>s4);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这段代码运行结果为:

    warning: array comparison always evaluates to a constant [-Wtautological-compare]
        printf("s1:%p, s2:%p, s1>s2:%d\n", s1,s2,s1>s2);
                                                   ^
    1 warning generated.
    s1:0x16f57347c, s2:0x16f573478, s1>s2:1
    s3:0x10088ff98, s4:0x10088ff9c, s3>s4:0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    可以看出, 比较都是通过首地址来进行的, 这就不难解释为什么直接调用cout << max({"a", "c", "b"}) << endl;会得到最后一个元素"b"了.


    这里还有另外一个知识点, 那就是单引号和双引号, 学过C语言的话大家应该非常熟悉了, 单引号只能有单个字符, 这个字符为0~255的ASCII字符, 只占用一个字节, 所以这时候直接拿来比较的话不会出错, 即:

    cout << max({'a', 'c', 'b'}) << endl;
    // output: c
    
    • 1
    • 2

    但是一旦变成双引号, 就默认变成字符数组, 也就是char[]类型, 这里的比较也就进存在于首地址之间了.


    更进一步, 通过上面的例子, 还可以发现一个问题, 通过char[]char*得到的两种字符数组并不一样, 除了一般的下标赋值等问题, 剩下的就是创建变量分配内存的问题, 指针是顺序分配, 而数组是逆序, 下面的一个回答1很好地说明了这一点:

    The diference is the STACK memory used.

    For example when programming for microcontrollers where very little memory for the stack is allocated, makes a big difference.

    char a[] = "string"; // the compiler puts {'s','t','r','i','n','g', 0} onto STACK 
    char *a = "string";  // the compiler puts just the pointer onto STACK 
                          // and {'s','t','r','i','n','g',0} in static memory area.
    
    • 1
    • 2
    • 3

    所以可以说使用initializer_list读取字符串进行比较的时候, 采用的是char*的形式, 所以才会造成max总是返回initializer_list的最后一个元素这种情况.


    1. c++ - Difference between char* and char[] - Stack Overflow; ↩︎

  • 相关阅读:
    it监控系统可以电脑吗?有什么效果
    大三第十二周学习笔记
    第一章:最新版零基础学习 PYTHON 教程(第十节 - Python 语句中的 – Python 如果否则)
    使用element的过渡效果来做动效
    【LeetCode刷题-滑动窗口】--340.至多包含K个不同字符的最长子串
    Spring后处理器-BeanPostProcessor
    【思科】MPLS VPN 实验配置
    HTML+CSS:移动端分辨率、视口、Flex布局、文字溢出显示省略号、溢出两行显示省略号
    vue页面批量引入组件
    ChatGPT如何协助人们学习新的科学和技术概念?
  • 原文地址:https://blog.csdn.net/qq_41437512/article/details/126120450