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.