【问题标题】:Exception thrown at 0x5914F3BE (ucrtbased.dll)在 0x5914F3BE (ucrtbased.dll) 处引发异常
【发布时间】:2019-09-18 09:03:12
【问题描述】:

我有一些代码从 .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.

现在我假设从动态分配的内存中读取有问题。也许有什么东西导致我从分配的数组之外的内存中读取?我无法准确找到错误的出处。

【问题讨论】:

  • 由于拼写错误,您超出了数组边界。在第二个 for 循环中,条件中有 1,应该有 i。
  • 先修复输入文件,它有2个捐赠者,而不是4个。接下来修复文件读取代码,inFile >> dlist[i].contribution语句不读取行尾。所以下一个捐助者名称将是一个空字符串,程序现在注定要与文件不同步。那些是从屏幕上跳下来打我脸的虫子,可能还有更多。一定要练习使用调试器,这样你才能看到它出错了。
  • @Eugene Damnn,我看着那个。干杯。
  • @HansPassant 谢谢。我只是使用调试器一步一步地完成它,我确实看到一个空字符串被放在 getline(inFile, dlist[i].name); 行中。这难道不是由 inFile >> amount; 行引起的,因为贡献的输入出现在名称之后?我看不出它如何不读取行尾会导致上一行中的字符串输入采用空字符串。
  • iostream 需要一段时间才能停止吃你的午餐,它必须在艰苦的学校里学习。一揽子建议是始终如一地使用 getline() 。添加一些基本的错误检查以验证您没有得到空字符串。使用stringstream进行解析。

标签: c++


【解决方案1】:

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

2
Bob
400
Alice
11000

然后他们继续说您错误地读取了文件。
请记住:混合operator&gt;&gt;getline() 并不像看起来那么简单。
您会看到,operator&gt;&gt; 忽略 newlinespace 字符,直到找到任何其他字符。
然后它会读取即将出现的字符,直到遇到下一个 newlinespace 字符,但不会丢弃它。

这就是getline 的问题所在。getline 会读取所有内容,直到遇到newline 或指定的delim 字符。 意思是,如果您的operator&gt;&gt; 在遇到newline 后停止,getline 将不会读取任何内容,因为它会立即遇到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&gt;&gt;operator&lt;&lt;,这将大大简化 对象的读取和打印。

#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 只使用一次,以后再也不会使用。
  • 使用成员初始化器列表。
  • 可以选择在 for 循环中使用 ++i 而不是 i++ 来习惯递增变量的正确方法,除非另有需要。
  • bool grandpatrons 对于标志来说太抽象了。
  • donors 是一个主观上比捐赠者名单缩写更好的名称。

【讨论】:

  • 谢谢。你使用了很多我的书还没有处理的东西,但是你对问题的解释很清楚,非常欢迎你的一般提示。我不久前用' inFile.ignore(1, '\n') '修复了它,但我现在更好地理解它是如何出错的。
猜你喜欢
  • 2021-06-11
  • 2017-04-12
  • 2019-04-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-12
  • 1970-01-01
  • 2021-02-14
  • 1970-01-01
相关资源
最近更新 更多