简体   繁体   中英

C++ - Reading a Table from a Text File

I have been trying to work on an assignment for class where I am supposed to read a table containing students' names and grades from a file called table.txt and store the values in C++ variables, and from there I need to manipulate the grade data, then present it all to the user via tables. When I run my code, the necessary tables are created, but the data does not appear. The row count stays at 0, and the Class Average comes up to -nan(ind). I'm afraid this might be an issue with the opening of the text file itself, but I'm not sure. This is where I need your help, and any help would be greatly appreciated, I apologize if my request seems simple or if my code is horrendous, as I'm new to the programming field!

table.txt

Anderson    91.5    95
Blake   75.5    90
Cheg    0   0
Dang    95  85
Engberg 80  100
Farris  55  90
Garcia  93.6    90.5
Hadad   65  60
Ionescu 100 95.5
Johnson 75  90
Kaloo   75  85
Lagos   55.5    80
Mikhailov   75  83.5
Nguyen  95  100
O'Neil  85  70

My current code

#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <iomanip>
using namespace std;

int main()
{
    ifstream fin("table.txt");
    int numStudents = 0;
    double avgNumGrade = 0.0;
    cout << setw(30) << "STUDENT STATISTICS" << endl;
    cout << setfill(' ') << setw(15) << "Student Name" << setw(15) << "Total Points" << setw(15) <<  "Numeric Grade" << setw(15) << "Letter Grade" << endl;
    string line;
    while (getline(fin, line)) {
        string name;
        double testGrade;
        int assnGrade;
        double totalPoints;
        double numGrade;
        char letterGrade;

        fin >> name;
        cout << setw(10) << left << name << setfill(' ');
        fin >> testGrade;
        fin >> assnGrade;
        totalPoints = testGrade + assnGrade;
        numGrade = totalPoints / 2;
        if (numGrade <= 100 && numGrade >= 89.5) {
            letterGrade = 'A';
        }
        else if (numGrade <= 89.49 && numGrade >= 79.5) {
            letterGrade = 'B';
        }
        else if (numGrade <= 79.49 && numGrade >= 69.5) {
            letterGrade = 'C';
        }
        else if (numGrade <= 69.49 && numGrade >= 59.5) {
            letterGrade = 'D';
        }
        else if (numGrade <= 59.49 && numGrade >= 0) {
            letterGrade = 'F';
        }
        cout << setw(5) << right << setprecision(3) << totalPoints << setfill(' ') << numGrade << setfill(' ');
        cout << setw(5) << letterGrade << endl;
        numStudents++;
        avgNumGrade = avgNumGrade + numGrade;
    }
    avgNumGrade = avgNumGrade / numStudents;

    cout << setw(30) << "CLASS STATISTICS" << endl;
    cout << setw(15) << left << "Number:" << setfill(' ') << numStudents << endl;
    cout << setw(15) << left << "Average:" << setfill(' ') << avgNumGrade << endl;
    fin.close();
}

Continuing from my comment, whenever you take any input (or for any other operation critical to the continued operation of your code), you must validate every step. Everything that is a prerequisite for something later must be validated and confirmed to succeed before you proceed. In your case this it is crucial to validate that your input file was actually opened for reading before you attempt to read from the file.

( note: I suspect in your case, the file table.txt is NOT in the current working directory that your executable is run from. If you are using an IDE for compiling and running your code, this can be a challenge. Make sure you know where your executable is being created and make sure your table.txt is in that directory -- or better yet, open a terminal, compile and run your code from there and remove all doubt...)

To validate a file was opened you use std::basic_ifstream::is_open (save the cppreference.com bookmark and refer to it for every part of C++ you have a question about, it is the best on the net). After attempting to open the file, simply:

    if (!fin.is_open()) {
        std::cerr << "error: file open failed '" << argv[1] <<"'.\n";
        return 1;
    }

While we are on the subject of opening files, never hardcode filenames. Either pass the filename to read as an argument to main() (that's what argc and argv are for), or prompt the user for a filename and take it as input. You shouldn't have to re-compile your program just to read from a different filename. It is simple to do, eg

int main (int argc, char **argv) {
    
    if (argc < 2) { /* validate one argument given for filename */
        std::cerr << "error: insufficient arguments.\n"
                     "usage: " << argv[0] << " filename.\n";
        return 1;
    }
    
    ...
    std::ifstream fin (argv[1]);
    
    if (!fin.is_open()) {   /* validate file open for reading */
        std::cerr << "error: file open failed '" << argv[1] <<"'.\n";
        return 1;
    }

Now, just as you validated that the file was open for reading, you must validate that you have read three values from every line into name , testGrade , and assnGrade . While a bit fragile, the simplest approach is just:

    /* validate the read of each value */
    while (fin >> name >> testGrade >> assnGrade) {
        ...

That ensure you receive a valid input for each name , testGrade , and assnGrade . It is fragile from the standpoint that any stray or corrupt data in any line of your file will cause the read to fail AND will cause the read of all remaining lines in the file to fail. (better to read each line with getline() and create a stringstream() from the line and then parse the value from the stringstream)

Your test for letterGrade is overly complicated. You do not need to check an upper-bound as a condition for each grade. The if()... else if()... condition will be tested in sequential order. So if numGrade isn't >= 89.5 , you just check next whether it is >= 79.5 and so on..., eg

        if (numGrade >= 89.5)           /* no need for an upper bounds compare */
            letterGrade = 'A';
        else if (numGrade >= 79.5)
            letterGrade = 'B';
        else if (numGrade >= 69.5)
            letterGrade = 'C';
        else if (numGrade >= 59.5)
            letterGrade = 'D';
        else
            letterGrade = 'F';

Your use of std::setfill(' ');is misplaced. The default fill is a space. There is no need to change the fill unless you want it set to other than a space. When you are outputting information with std::cout you never need more than one call to std::cout for any contiguous block of output. For example, you can output each students data with:

        std::cout << "   " << 
                     std::setw(10) << std::left << name << 
                     std::setw(15) << std::right << std::fixed << 
                     std::setprecision(2) << totalPoints <<
                     std::setw(15) << numGrade <<
                     std::setw(15) << (char)letterGrade << '\n';

If you note, we specify an explicit namespace for, eg std::cout , see Why is “using namespace std;” considered bad practice? .

If you put it altogether, you can so something similar to:

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

int main (int argc, char **argv) {
    
    if (argc < 2) { /* validate one argument given for filename */
        std::cerr << "error: insufficient arguments.\n"
                     "usage: " << argv[0] << " filename.\n";
        return 1;
    }
    
    size_t numStudents = 0;
    double avgNumGrade = 0., testGrade, assnGrade;
    std::string name {};
    std::ifstream fin (argv[1]);
    
    if (!fin.is_open()) {   /* validate file open for reading */
        std::cerr << "error: file open failed '" << argv[1] <<"'.\n";
        return 1;
    }
    
    /* only one call to std::cout is necessary */
    std::cout << std::setw(30) << "STUDENT STATISTICS\n" << 
                 std::setw(15) << "Student Name" << 
                 std::setw(15) << "Total Points" << 
                 std::setw(15) <<  "Numeric Grade" << 
                 std::setw(15) << "Letter Grade\n";
    
    /* validate the read of each value */
    while (fin >> name >> testGrade >> assnGrade) {
        char letterGrade = 0;
        double  totalPoints = testGrade + assnGrade,
                numGrade = totalPoints / 2.;
        
        if (numGrade >= 89.5)           /* no need for an upper bounds compare */
            letterGrade = 'A';
        else if (numGrade >= 79.5)
            letterGrade = 'B';
        else if (numGrade >= 69.5)
            letterGrade = 'C';
        else if (numGrade >= 59.5)
            letterGrade = 'D';
        else
            letterGrade = 'F';
        
        std::cout << "   " << 
                     std::setw(10) << std::left << name << 
                     std::setw(15) << std::right << std::fixed << 
                     std::setprecision(2) << totalPoints <<
                     std::setw(15) << numGrade <<
                     std::setw(15) << (char)letterGrade << '\n';
        numStudents += 1;
        avgNumGrade += numGrade;
    }
    avgNumGrade /= numStudents;
    
    std::cout << '\n' << std::setw(30) << "CLASS STATISTICS\n" <<
                 "   " << std::setw(12) << std::left << "Number:" << 
                 numStudents << "\n   " << 
                 std:: setw(12) << std::left << "Average:" << 
                 avgNumGrade << '\n';
}

Example Use/Output

Using your example data in the file dat/table.txt , you would do:

$ ./bin/student_table dat/table.txt
           STUDENT STATISTICS
   Student Name   Total Points  Numeric Grade  Letter Grade
   Anderson           186.50          93.25              A
   Blake              165.50          82.75              B
   Cheg                 0.00           0.00              F
   Dang               180.00          90.00              A
   Engberg            180.00          90.00              A
   Farris             145.00          72.50              C
   Garcia             184.10          92.05              A
   Hadad              125.00          62.50              D
   Ionescu            195.50          97.75              A
   Johnson            165.00          82.50              B
   Kaloo              160.00          80.00              B
   Lagos              135.50          67.75              D
   Mikhailov          158.50          79.25              C
   Nguyen             195.00          97.50              A
   O'Neil             155.00          77.50              C

             CLASS STATISTICS
   Number:     15
   Average:    77.69

Look things over and let me know if you have further questions.

I agree with @David C. Rankin that the most likely problem is that your file open is failing. And, the most likely reason for that is that your code may not be running from the directory you think it is.

In most IDE environments (VS, CLion), the "current working directory" for an executable is not the directory where the source code files live but rather a build folder that is often in a folder tree whose name depends on the build (release or debug).

Check the return status of your file open. If it is failing, figure out how to change the current working directory for your code to the location where table.txt lives. Or, locate the.exe file and move table.txt into that folder.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM