在0x5914F3BE(ucrtbased.dll)引发异常

时间:2019-09-18 09:03:12

标签: c++

我有一些代码可以从.txt文件中获取名称列表和双精度值列表,并将其显示在命令提示符下。为此,将动态分配结构数组。代码应基于.txt文件中的第一个值来了解数组的大小,然后再跟随其后的名称和相关值。然后,它应分两部分显示列表,其名称的关联双精度值大于或等于首先列出的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;
}

donorlist.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 thrown at 0x5914F3BE (ucrtbased.dll) in 6_9.exe: 0xC0000005: Access violation reading location 0xA519E363.

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

1 个答案:

答案 0 :(得分:1)

您的问题开始于在数据文件中写入错误的amount。 修复它:

2
Bob
400
Alice
11000

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

这里是getline出现问题的地方。getline读取所有内容,直到遇到newline或指定的delim字符。 意思是,如果您的operator>>在遇到newline之后停止了,getline会读为NOTHING,因为它立即遇到newline

要解决此问题,您需要处理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;
}

现在,更好的解决方案是使用向量代替原始指针并实现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将好主顾与普通顾客分开。
  • 使用流迭代器将对象读取到向量中。
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在主观上比捐赠者名单的简称好。