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, e.g.
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..., e.g.
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, e.g. 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.