前几天有网友问到一个QMap问题。问题其实很简单,就是创建一个QMap的对象,然后用迭代器遍历该对象,结果程序总是出错。
QMap<QString,QString> map;map.insert("beijing","010");
map.insert("shanghai","021");
QMapIterator<QString,QString> it(map);
while(it.hasNext()){
qDebug()<< " " << it.peekNext().key() << " " << it.next().value();
}
问题出在 qDebug() 这条语句上。可是初看起来没什么问题,很正常嘛:先执行的peekNext()不改变迭代器it的值,后执行的next()改变it的值,正是我们需要的
可能让人不解的是,一旦我们将这条语句分成两句来写,程序就完全没问题了:如下
qDebug()<< " " << it.peekNext().key();qDebug()<< " " << it.next().value();
而更有意思的是,当我们调换qDebug()后面两项的顺序后,程序竟能正常工作了(所用环境 win xp + vs2008 及 ubuntu gcc4)
qDebug()<< " " << it.next().value() << " " << it.peekNext().key();这说明了什么,说明了当我们书写 qDebug()<<f1()<<f2() 时,先执行的是 f2,而后才是 f1
到这儿,这不再是 Qt 中的问题,而是 C++ 中的问题:C++语句的行为和序列点
C++语句的行为C++ 语句有4中行为: 完全定义行为(fully defined behavior)、由实现定义行为(implementation defined behavior)、未指定行为(unspecified behavior)、未定义行为(undefined behavior)
完全定义行为这是由C++标准完全指定的行为,也就是语句的行为完全确定的。
比如
int len1 = sizeof 'a';int len2 = sizeof(char);
在 C++ 中,len1 和 len2 的值必须是1,这是C++标准所要求的。(而在C语言中,len1的大小是实现定义的,不同的C编译器会给出不通的结果)
由实现定义的行为C++标准没规定具体的结果,具体由C++编译器指定(不同的编译器将可能得到不同的结果),但编译器说明书、手册中会明确告知结果。
比如
int len1 = sizeof(int);char c(-1);
len1 的结果将由编译器定义,可能是2,可能是4,也可能是8,...
c 是有符号字符(signed char),还是无符号字符(unsigned char),不同编译器将给出不通的结果
未指定行为同样是 C++ 未规定的具体结果,但编译器可以自由选择任意一种合理的动作,而且它不需要在文档中说明编译器将选择哪一种动作。
std::cout<<f1()<<f2()<<f3();int sum = f1() + f2() + f3();
上面的三个函数,哪一个会先执行?C++ 标准只规定编译器要按某种顺序调用(不能并行),至于何种顺序,这个问题上编译器可以自由选择,可以从左到右,也可从右到左,可以随机顺序。而且debug和release两种模式下可以选择不同的顺序。
前面的 QMap 所遇到的就是一个未指定行为,只不过碰巧两个编译器都是从右往左的。
未定义行为这个是很恐怖的事情。太恐怖了,C++标准未做任何要求,编译器可以随意进行实现(它可以做任何事情)。
int i=0;i = (i++);
比如上面这条语句,具有未定义行为。或许碰巧某款编译器给出的结果与你期望的一样,这样的bug就更危险了。
对未定义行为,编译器可以随其发挥,比如上面这句,编译器如果将其按照"关机"或"重启"等命令来解释,仍不会影响它是非常标准的编译器
序列点与行为密切相关的就是序列点了,这个问题上个人仍是一头雾水。摘抄一些语句放下面,备忘
序列点(sequence points)
- 在两个序列点之间,我们随时可以自由地读取任何表示对象的内存,前提是我们没有改写那片内存。
- 然而,如果在序列点之间改写了一片内存,则必须只能改写一次。
- 此外,我们只能将对那片内存的读取,作为“决定程序将要向那片内存写入某些东西的过程”的一部分。
- 违反这些规则中任意之一,都将导致未定义行为。
序列点位置
- 完全表达式(full expression)
- 在完全表达式求值的结尾存在一个序列点。完全表达式的 值不被直接作为求解某个其他表达式的一部分使用。
- 函数调用(function call)
- 两个序列点保护一个函数调用。在所有实参赋值之后立即有一 个序列点,从而函数体可以在“所有初始化形参的副作用已经完成”的假定之上继续执行。第 二个序列点位于返回点,它确保任何提供返回值的副作用在调用该函数的代码恢复执行前已经 完成。
- 条件操作符(conditional operator)
- 在条件操作符的左操作数的求值,和其他两个操作数 中被选中的那一个的求值之间,存在一个序列点。
- || 和&& 操作符
- 对于这些操作符的内建版本而言,左操作数(表达式)的求值后面都存 在一个序列点。这意味着左操作数是完全求解的,所有结果的副作用在计算右操作数之前已经 完成。
-
http://book.csdn.net/bookfiles/548/10054818386.shtml
-
http://blog.csdn.net/pongba/archive/2005/12/01/541440.aspx