0
点赞
收藏
分享

微信扫一扫

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


tags: C++

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

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

运行结果如下:

Fff

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

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

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

Acc

这是为什么呢?

分析

一开始我以为问题出在​​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<first
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());
}

思路的话这里不进一步说了, 熟悉模版的话很好理解的. 其实核心就是通过迭代器读取首元素和下一个元素, 比较之后更新​​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

运行结果如下:

A4_c
A2_c
c
NSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE
NSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE

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

#include <stdio.h>
#include <stdlib.h>
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;
}

这段代码运行结果为:

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

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

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

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

但是一旦变成双引号, 就默认变成字符数组, 也就是​​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.

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

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


举报

相关推荐

0 条评论