我有一些代码从.txt文件中获取名称列表和 double 值列表,并将其显示在命令提示符下。为此,将动态分配结构数组。代码应基于.txt文件中的第一个值来了解数组的大小,然后再跟随其后的名称和相关值。然后,它应该分两部分显示列表,其名称的关联 double 值大于或等于首先列出的10.000。如果没有一个值符合要求,则在上半部分显示“无”。

该程序会执行,但是调试器会给出异常,并且输出与预期不符。

#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>
using namespace std;

struct donor
{
    string name;
    double contribution = 0;
};

int main()
{
    string filename;
    ifstream inFile;

    cout << "Enter name of data file: ";
    cin >> filename;

    inFile.open(filename);
    cin.clear();

    if(!inFile.is_open())
    {
        cout << "Could not open the file " << filename << endl;
        cout << "Program terminating.\n";

        exit(EXIT_FAILURE);
    }

    int amount;
    inFile >> amount;

    cin.clear();

    donor* dlist = new donor[amount];
    int i;

    while(inFile.good())
    {
        for(i = 0; i < amount; i++)
        {
            getline(inFile, dlist[i].name);
            cin.clear();

            inFile >> dlist[i].contribution;
            cin.clear();
        }
    }

    cout << "Here's the list of Grand Patrons:\n";
    bool grandpatrons = false;
    for(i = 0; i < amount; i++)
    {
        if(dlist[i].contribution >= 10000)
        {
            grandpatrons = true;

            cout << dlist[i].name << endl;
            cout << dlist[i].contribution << endl;
        }
    }

    if(grandpatrons == false)
    {
        cout << "None" << endl;
    }

    cout << "Here's the list of Patrons:\n";
    for (i = 0; 1 < amount; i++)
    {
        if (dlist[i].contribution < 10000)
        {
            cout << dlist[i].name << endl;
            cout << dlist[i].contribution << endl;
        }
    }

    delete[] dlist;
    return 0;
}

施主列表.txt文件如下所示:
4

Bob

400

Alice

11000

但是输出看起来像这样:
Enter name of data file: donorlist.txt

Here's the list of Grand Patrons:

None

Here's the list of Patrons:

0

0

0

0

调试器给我的异常(exception)是:
Exception thrown at 0x5914F3BE (ucrtbased.dll) in 6_9.exe: 0xC0000005: Access violation reading location 0xA519E363.

现在,我假设从动态分配的内存中读取数据出了问题。也许是某种原因导致我从分配的数组之外的内存中读取数据?我无法准确找到错误所在。

最佳答案

您的问题始于在数据文件中写入错误的amount
使用以下方法修复:

2
Bob
400
Alice
11000

然后,他们继续错误地读取文件。
请记住:混合operator>>getline()并不像看起来那样简单。
您会看到operator>> IGNORES newlinespace字符,直到找到其他任何字符为止。
然后,它将读取即将到来的字符,直到遇到下一个newlinespace字符,但不要丢弃它。

这是getline出现问题的地方。getline读取所有内容,直到遇到newline或指定的delim字符。
这意味着,如果您的operator>>在遇到newline后停止,则getline将立即读取newline,从而显示NOTHING。

要解决此问题,您需要处理newline字符。
为此,您可以先检查流中的下一个字符是否确实是newline,然后在其上使用istream::ignore()

int next_char = stream.peek();
if(next_char == '\n'){
    stream.ignore();
}

您的代码的有效示例为:

#include <iostream>
#include <fstream>
#include <string>

using namespace std;

//Suggestion: class/struct names should start with a capital letter.
struct Donor{
    //Suggestion: Use member initializer lists to specify default values.
    Donor() : name(), contribution(0){}

    string name;
    double contribution;
};

int main(){
    cout << "Enter the filename: ";

    string filename;
    cin >> filename;

    //Suggestion: Open the file immediately with the filename and use `operator bool` to check if it opened.
    ifstream inFile(filename);
    if(!inFile){
        cout << "Could not open the file " << filename << '\n';
        cout << "Program terminating.\n";

        exit(EXIT_FAILURE);
    }

    int amount;
    inFile >> amount; //! Leaves '\n'

    Donor* donors = new Donor[amount];

    for(int i = 0; i < amount; ++i){
        switch(inFile.peek()){
            case '\n': inFile.ignore();
                       break;

            case  EOF: cout << "Donor amount too big!\n";
                       exit(EXIT_FAILURE);
        }

        getline(inFile, donors[i].name);
        inFile >> donors[i].contribution;
    }

    cout << "Here's the list of Grand Patrons:\n";
    bool grandpatrons_exist = false;
    for(int i = 0; i < amount; ++i){
        if(donors[i].contribution >= 10000){
            grandpatrons_exist = true;

            cout << donors[i].name << '\n';
            cout << donors[i].contribution << '\n';
        }
    }

    if(!grandpatrons_exist){
        cout << "None\n";
    }

    cout << "Here's the list of Patrons:\n";
    for(int i = 0; 1 < amount; ++i){
        if(donors[i].contribution < 10000){
            cout << donors[i].name << '\n';
            cout << donors[i].contribution << '\n';
        }
    }

    delete[] donors;
    return 0;
}

现在,一个更好的解决方案是使用 vector 而不是原始指针并实现operator>>operator<<,这将大大简化
读取和打印对象。

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

class Donor{
    public:
        Donor() noexcept: name(), contribution(0){}

        friend istream& operator>>(istream& stream, Donor& donor){
            switch(stream.peek()){
                case  EOF: return stream;
                case '\n': stream.ignore();
            }

            getline(stream, donor.name);
            stream >> donor.contribution;

            return stream;
        }
        friend ostream& operator<<(ostream& stream, const Donor& donor){
            stream << donor.name << ' ' << donor.contribution;

            return stream;
        }

        const string& get_name() const noexcept{
            return name;
        }
        const double& get_contribution() const noexcept{
            return contribution;
        }

    private:
        string name;
        double contribution;
};

int main(){
    cout << "Enter the filename: ";

    string filename;
    cin >> filename;

    ifstream inFile(filename);
    if(!inFile){
        cout << "Could not open the file " << filename << '\n';
        cout << "Program terminating.\n";

        exit(EXIT_FAILURE);
    }

    int amount;
    inFile >> amount;

    vector<Donor> donors(amount);
    //Read it as `for donor in donors`
    for(Donor& donor : donors){
        inFile >> donor;
    }

    //An STL function that takes a lambda as the thirs argument. You should read up on them if you haven't.
    //I would prefer using this since it greatly improves readability.
    //This isn't mandatory, your implementation of this part is good enough.
    bool grandpatrons_exist = any_of(begin(donors), end(donors), [](const Donor& donor){ return donor.get_contribution() >= 10000; });

    cout << "Here's the list of Grand Patrons:\n";
    if(grandpatrons_exist){
        for(const Donor& donor : donors){
            if(donor.get_contribution() >= 10000){
                cout << donor << '\n';
            }
        }
    }
    else{
        cout << "None\n";
    }

    cout << "\nHere's the list of Patrons:\n";
    for(const Donor& donor : donors){
        if(donor.get_contribution() < 10000){
            cout << donor << '\n';
        }
    }

    return 0;
}

其他一些重大改进将是:
  • 使用partition将出色的顾客与正常顾客分开。
  • 使用流迭代器将对象读入 vector 。

  • int main(){
        cout << "Enter the filename: ";
    
        string filename;
        cin >> filename;
    
        ifstream inFile(filename);
        if(!inFile){
            cout << "Could not open the file " << filename << '\n';
            cout << "Program terminating.\n";
    
            exit(EXIT_FAILURE);
        }
    
        //Ignore the first line completely
        inFile.ignore(numeric_limits<streamsize>::max(), '\n');
        //Calls `operator>>` internally
        vector<Donor> donors(istream_iterator<Donor>{inFile}, istream_iterator<Donor>{});
    
        auto first_grand_patron = partition(begin(donors), end(donors), [](const Donor& donor){ return donor.get_contribution() >= 10000; });
    
        cout << "Here's the list of Grand Patrons:\n";
        if(first_grand_patron == begin(donors)){
            cout << "None!\n";
        }
        for(auto patron = begin(donors); patron != first_grand_patron; ++patron){
            cout << *patron << '\n';
        }
    
        cout << "\nHere's the list of Patrons:\n";
        for(auto patron = first_grand_patron; patron != end(donors); ++patron){
            cout << *patron << '\n';
        }
    
        return 0;
    }
    

    现在一些一般性提示:
  • 结构/类名应以大写字母开头。
  • Stop Using std::endl.
  • 无需cin.clear()。 Cin仅使用一次,不再使用。
  • 使用成员初始化器列表。
  • (可选)在++i循环中使用i++代替for来习惯于递增变量的正确方法,除非另有需要。
  • bool grandpatrons太多是一个标志的抽象名称。
  • donors是主观上更好的名称,而不是捐助者列表的简称。
  • 关于c++ - 在0x5914F3BE(ucrtbased.dll)引发异常,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/57988946/

    10-12 06:14