ATSpiro
ATSpiro

Reputation: 13

How do you open a file by user input? C++

I want to open a txt file by user input and display it when the code is ran so I can then modify it.

Every time I have tried to open the file it does not open. Do I need to save the file in a certain place or is my code just wrong?

This is what my file looks like if it helps:

inventory.txt

My code:

const int max_items = 30;
Item items[max_items];
char mainChoice, subChoice;
int num_items = 0;
int parts;
string filename, description;
ifstream fin;

cout << "Enter the name of the inventory file : ";
getline(cin, filename);

filename = filename.substr(0, filename.length() - 1);

fin.open(filename.c_str());

if (fin.is_open())
{
    while (!fin.eof())
    {
        getline(fin, items[num_items].description); 

        items[num_items].description = items[num_items].description.substr(0, items[num_items].description.length() - 1);

        fin >> items[num_items].num_parts; 
        fin.ignore(100, '\n'); 
        num_items++; 
    }

    fin.close(); 
}

else
{
    cout << "Unable to open file" << endl;
}

Upvotes: 0

Views: 569

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 595772

You are unconditionally removing the last character from filename before using it. std::getline() does not output the '\n' character that terminates the line. I'm guessing that you expect an Enter press on the terminal to generate an \r\n sequence and then std::getline() to output the '\r' and dismiss the '\n'. That is simply not the case when reading from std::cin, only '\n' is generated, which std::getline() does not output. So you need to get rid of that substr() call altogether and just use filename as-is:

cout << "Enter the name of the inventory file : ";
getline(cin, filename);

//filename = filename.substr(0, filename.length() - 1);

fin.open(filename.c_str());
...

However, it is certainly possible for std::getline() to output an '\r' character when reading from a file, if the file is using CRLF-style line breaks, and the code is not running on Windows. In that case, you do need to account for the extra '\r', but it should be done conditionally instead, eg:

getline(fin, description);

if (!description.empty() && description.back() == '\r')
    description.pop_back();

items[num_items].description = description;

That being said, there are some other issues with your code:

  • while (!fin.eof()) is bad.

  • when calling a stream's ignore() method to discard everything up to a specified delimiter, don't use a hard-coded length value. Use std::numeric_limits<std::streamsize>::max() instead.

  • you are overflowing your items[] array, if the file has more than 30 items in it. You should use a std::vector when you don't know the number of items at compile-time.

  • you are not parsing the contents of the file correctly at all, given the text file you have shown.

Try something more like this instead:

#include <iostream>
#include <fstream>
#include <string>
//#include <vector>
#include <limits>
using namespace std;

const int max_items = 30;
Item items[max_items];
int num_items = 0;
//vector<Item> items;

cout << "Enter the name of the inventory file : ";

string filename;
getline(cin, filename);

ifstream fin(filename.c_str());
if (fin.is_open())
{
    // ignore the file header
    fin.ignore(numeric_limits<streamsize>::max(), '\n');
    fin.ignore(numeric_limits<streamsize>::max(), '\n');

    char description[25];

    while (fin.get(description, 25))
    {
        size_t len = fin.gcount();
        if (len > 0 && description[len-1] == '\r')
            --len;

        /*
        Item item;
        item.description = string(description, len);
        fin >> item.num_parts;
        items.push_back(item); 
        */

        items[num_items].description = string(description, len);
        fin >> item[num_items].num_parts;
        ++num_items;
        if (num_items == max_items)
            break;

        //

        fin.ignore(numeric_limits<streamsize>::max(), '\n'); 
    }

    fin.close(); 

    // use items as needed...
}
else
{
    cout << "Unable to open file" << endl;
}

Alternatively, use std::getline() to read whole lines from the file, so you don't have to call ignore() on each iteration. You can use std::istringstream to parse each line, eg:

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
//#include <vector>
#include <limits>
using namespace std;

const int max_items = 30;
Item items[max_items];
int num_items = 0;
//vector<Item> items;

cout << "Enter the name of the inventory file : ";

string filename;
getline(cin, filename);

ifstream fin(filename.c_str());
if (fin.is_open())
{
    // ignore the file header
    fin.ignore(numeric_limits<streamsize>::max(), '\n');
    fin.ignore(numeric_limits<streamsize>::max(), '\n');

    string line;
    while (getline(fin, line))
    {
        istringstream iss(line);
        char description[25];

        iss.get(description, 25);

        size_t len = iss.gcount();
        if (len > 0 && description[len-1] == '\r')
            --len;

        /*
        Item item;
        item.description = string(description, len);
        iss >> item.num_parts;
        items.push_back(item); 
        */

        items[num_items].description = string(description, len);
        iss >> item[num_items].num_parts;
        ++num_items;
        if (num_items == max_items)
            break;
    }

    fin.close(); 

    // use items as needed...
}
else
{
    cout << "Unable to open file" << endl;
}

Upvotes: 1

Related Questions