本来这是一道判断是否合法的题(English Version Page 113),我原以为错误原因是beg + end 超出了vector的范围,而编译之后发现事实上错误在于运算 + 根本没有定义。这里体现了亲手码程序的重要性。
我对二分中值的定义和书上不太一样,书中的其实是中值或两个中值的右边那个。我却麻烦的定义为中值或两个中值的左边那个(因为比较喜欢左边),结果是造成了程序34-49行的麻烦讨论(将错就错,不想改掉mid的定义)。
#include <iostream>
#include <vector>
#include <algorithm>
using std::cin;
using std::cout;
using std::cerr;
using std::endl;
using std::vector;
using std::sort;
int main()
{
vector<int> vint;
int i;
int seek;
cout << "Please input the number you seek:" << endl;
cin >> seek;
cout << "Please input numbers from where you want to seek a certain number:" << endl;
while (cin >> i)
vint.push_back(i);
sort(vint.begin(),vint.end());
// The next three lines are used for test in terms of the numbers after sorting.
cout << endl;
for (auto c : vint) cout << c << " "; // show the contents of 'vint'
cout << endl;
auto beg = vint.cbegin();
auto end = vint.cend(); // Remember that 'end' is one off the end.
auto mid = beg + (end - beg + 1) / 2 - 1; // 'mid' is at the middle or the first (left) one of the two middle ones.
cout << beg - vint.cbegin() << " " << mid - vint.cbegin() << " " << end - vint.cbegin() << endl;
cout << "*mid= " << *mid << endl;
while (mid != end && *mid != seek)
{
// This 'if' aims to move 'mid' to the next place as when there are only 2 elements, 'mid' remains still.
if (end - mid != 2)
{
if (seek < *mid) end = mid;
else beg = mid + 1;
mid = beg + (end - beg + 1) / 2 - 1;
}
else
{
if (*(mid + 1) == seek) // move 'mid' to the next place
mid += 1;
else
{
cerr << "Sorry, there is no " << seek << "." << endl; // to indicate there's no that number
return -1;
}
}
// The next two lines are used for test in terms of the current place and value of 'mid'.
cout << beg - vint.cbegin() << " " << mid - vint.cbegin() << " " << end - vint.cbegin() << endl;
cout << "*mid= " << *mid << endl;
}
// Check whether the one we find is the first of all. If not, move to the number to its left.
while (*(mid - 1) == seek && (mid - vint.cbegin()) != 0)
mid -= 1;
// The next line is used for test in terms of the final place and value of 'mid'.
cout << "*mid, &mid= " << *mid << "," << mid - vint.begin() << '\n' << endl;
// The place needs to plus one as the place in vector begins at 0, while we need it at 1.
cout << "The number " << seek << " is in No." << mid - vint.cbegin() + 1 << " place." << endl;
return 0;
}
以下是几次错误经历:
I 第38行忘记重新定义mid
这其实就是思路不够连贯的问题,当进入第二轮while循环时,应该将beg,mid,end全部准备好。(一个素养的问题)
II 第33-49行忘记分类讨论
程序仍然有问题,于是在草稿纸上列情况,发现当锁定在两个值之内后,mid将一直保持在同一个位置不再运动(stay still),因而有时在试验中会陷入糟糕的死循环。于是用一个if语句打了补丁。
然而程序还有问题!!!
III 第42行错误使用++
这个问题的发现得益于中间试验将中间结论输出,然后在有一次试验中发现两次beg,mid,end位置由6 6 8 直接跳转至6 8 8。奇怪,明明是将mid+=1,怎么会一下子进了两位?
经检查,发现是在if语句中错误使用++(这样犯了错误印象深刻啊),虽然是在判断语句,确也改变了mid了。
(其实在C++ Primer 4.5 Increment and Decrement Operators 中有更详细解释。)
IV 第55行错误使用- -
类似的,- -也作用到mid中了。
V 第60行输出忘记+1
这也是很严重的问题,原因为vector位置起始于0,而我们却从1开始数,因而用mid - vint.begin()数出来的位置会差1,造成结果的错误。(同样也从实验中间数据发现问题)
Conclusion
1.多思有益。虽然花了一些时间找bug,却也是一种提升的过程。
2.逻辑素养很重要。结构应清晰,不要紊乱,或脑子中想的在程序中却没有体现。
3.充分利用中间结论输出。这是很好的错误提示,可以让我们对症下药,对某个特定参数的与预期不同的现象进行处理。同时,几组试验可以让我们总结出规律来,因而可以指向错误。
4.概念要清。C++ Primer这本书中对概念的辨析还是比较多的,对此的充分理解记忆有助于不在编写中犯编译器无法察觉的致命错误(比如此处的++和- -)。
See also
Teddy van Jerry 的导航页
【C++ Primer(5th Edition) Exercise】练习程序 - Chapter3(第三章)