坑
最近学习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
的最后一个元素这种情况.