ReadingLab / Discussion-for-Cpp

C++ 中文讨论区
MIT License
88 stars 63 forks source link

C++ primer里那个书店程序 #28

Closed 0oWillo0 closed 9 years ago

0oWillo0 commented 9 years ago
#include <iostream>
#include "Sales_item.h"

int main()
{
    Sales_item total; // variable to hold data for the next transaction

    // read the first transaction and ensure that there are data to process
    if (std::cin >> total) {
        Sales_item trans; // variable to hold the running sum
        // read and process the remaining transactions
        while (std::cin >> trans) {
            // if we're still processing the same book
            if (total.isbn() == trans.isbn())
                total += trans; // update the running total 
            else {
                // print results for the previous book 
                std::cout << total << std::endl;
                total = trans;  // total now refers to the next book
            }
        }
        std::cout << total << std::endl; // print the last transaction
    }
    else {
        // no input! warn the user
        std::cerr << "No data?!" << std::endl;
        return -1;  // indicate failure
    }

    return 0;
}

这是答案,然后我运行时输入a 1 2 b 1 2结果只会显示出a商品的情况,b无法显示。我试了ctrl+z也是不行。现在不知道要怎么搞才能让ab商品一起输出,希望大神能帮忙解决下这个疑惑。

pezy commented 9 years ago

这个代码的注释非常清楚。

仅看输出就清楚了。

// print results for the previous book std::cout << total << std::endl;

这句输出,是在 total.isbn() != trans.isbn() 发生的。即,在你输入了

a 1 2

之后,再输入

b 1 2

的时候, b != a,程序走到这一个输出语句,然后就会输出 a.

因为程序的逻辑是,当你输入了另一个商品 b 的时候,默认你是完成了商品 a 的输入的,于是输出 a 的总价。但此刻,商品 b 仍处于未完成的状态,你有可能接下来还要输入商品 b 的另一条单价,也有可能输入商品 c,只有当你输入另一个商品 c 的时候,才会认为商品 b 已经输入完成,才会输出 b 的总价。

所以此刻仅仅会输出商品 a 。

std::cout << total << std::endl; // print the last transaction

这是第二处输出,是当你结束 while 循环的时候输出的,何时结束?在 Windows 操作系统上就是按 Ctrl + Z 键结束输入。那么当你按下这两个键的时候,程序会将最后一个商品,如此刻的商品 b,置为完成状态,然后输出出来。

所以当你输入完 b,紧接着会输出 a,然后你按下 Ctrl + Z,会输出 b。


这就是整个程序逻辑的分析,相信你该明白为何 a 和 b 不会一起输出出来了吧。

那么如果你非要在输入商品 b 的时候,将此刻完成状态的商品 a 和未完成状态的商品 b 都输出出来。 请修改代码:

else {
                // print results for the previous book 
                std::cout << total << std::endl;
                // 这里加入一句话
                std::cout << trans << std::endl;
                total = trans;  // total now refers to the next book
            }

当然,这样的话,输出信息就和程序本身的逻辑有点不协调了。但满足了你的需求。

0oWillo0 commented 9 years ago
#include <iostream>

int main()
{
    // currVal is the number we're counting; we'll read new values into val
    int currVal = 0, val = 0;

    // read first number and ensure that we have data to process
    if (std::cin >> currVal) {
        int cnt = 1;  // store the count for the current value we're processing
        while (std::cin >> val) { // read the remaining numbers 
            if (val == currVal)   // if the values are the same
                ++cnt;            // add 1 to cnt 
            else { // otherwise, print the count for the previous value
                std::cout << currVal << " occurs "
                    << cnt << " times" << std::endl;
                currVal = val;    // remember the new value
                cnt = 1;          // reset the counter
            }
        }  // while loop ends here
        // remember to print the count for the last value in the file
        std::cout << currVal << " occurs "
            << cnt << " times" << std::endl;
    } // outermost if statement ends here
    return 0;
}

这个程序在1 1 2 2 2 最后加个ctrl+z然后再回车可以一起输出,但是为什么上面那个不行啊?不应该也是最后一个^Z然后相当于输入结束然后把前一个打印出来吗?

pezy commented 9 years ago

@0oWillo0

这个程序在1 1 2 2 2 最后加个ctrl+z然后再回车可以一起输出,但是为什么上面那个不行啊?

按照你对上面那个程序的输入:

a 1 2 b 1 2 Ctrl+Z

我们来分析发生了什么。

 if (std::cin >> total)

首先这句里面的 std::cin >> total,你要了解背后是什么。这一点是与你写的那个程序最大的不同。 total 是一个 Sales_item 对象,它恰好又重载了 operator>> 操作符。我将这句话展开一下:

std::istream& 
operator>>(std::istream& in, Sales_item& s)
{
    double price;
    in >> s.bookNo >> s.units_sold >> price; // 真正的输入在这里
    // check that the inputs succeeded
    if (in)
        s.revenue = s.units_sold * price;
    else 
        s = Sales_item();  // input failed: reset object to default state
    return in;
}

好,if 那一句读入 a 1 2,没问题,接下来重点分析 while 那一句:

while (std::cin >> trans)

第一次执行 while,正常读入 b 1 2,没问题吧。此刻输出了 a,然后第二次读入你最后加的那个 Ctrl+Z,即 EOF。它实际执行的是哪一句呢?

in >> s.bookNo >> s.units_sold >> price;
      ^
     EOF 跑到 s.bookNo 的位置

EOF 本质就是个枚举值,你可以把它看作是一个bit,不同编译器是不同的值,譬如在 VS2013 里,它被定义为 0x1。那么由于 bookNo 是一个 std::string,那么 std::ios_base 在处理这个 bit 的时候,就默认将其作为 std::string 来处理,什么结果呢?有兴趣你可以 debug 跟踪这个过程,大体来讲,最终处理成了空字符串(""),且 ios_base::iostate 的值依然是 good。即什么也没有。但输入流并未结束啊!还等着输入 units_sold 呢。这是一个 unsigned 类型,它遇到 eofbit 不会处理的如 std::string 那么复杂,依然可以跟踪一下,最终 ios_base::iostate 的值会是 eofbit,即输入流结束,后续的 if 判断会 false,然后构造一个空的 Sales_item 对象。

说了这么多,其实就是 std::string 惹的货。

我稍微改动一下你最后那个“行为正常”的代码:

#include <iostream>
#include <string>

int main()
{
    // currVal is the number we're counting; we'll read new values into val
    std::string currName, name;
    int currVal = 0, val = 0;

    // read first number and ensure that we have data to process
    if (std::cin >> currName >> currVal) {
        int cnt = 1;  // store the count for the current value we're processing
        while (std::cin >> name >> val) { // read the remaining numbers 
            if (val == currVal)   // if the values are the same
                ++cnt;            // add 1 to cnt 
            else { // otherwise, print the count for the previous value
                std::cout << currVal << "(" << currName << ")" << " occurs "
                    << cnt << " times" << std::endl;
                currVal = val;    // remember the new value
                currName = name;
                cnt = 1;          // reset the counter
            }
        }  // while loop ends here
        // remember to print the count for the last value in the file
        std::cout << currVal << "(" << currName << ")" << " occurs "
            << cnt << " times" << std::endl;
    } // outermost if statement ends here
    return 0;
}

然后按照你的顺序输入:

a 1 a 1 b 2 b 2 b 2 Ctrl+Z

会是什么结果呢?

仅仅只会输出

1(a) occurs 2 times

然后等待输入。和书上的程序保持了一致。

理解了?

pezy commented 9 years ago

不应该也是最后一个^Z然后相当于输入结束然后把前一个打印出来吗?

想做到你这一点,有两个办法:

  1. 别让 Sales_item 类里面的 bookNo 作为第一个输入。
  2. 这样输入:a 1 2 b 1 2 b Ctrl+Z (注意 Ctrl+Z 前面那个字符,随便给个即可,主要是为了让 bookNo 正常读进去)

都可以做到你想要的结果。