C++/Storing Info in Files
Expert: Ralph McArdell - 5/1/2007
QuestionHey Ralph, hope all is well...
I plan to take official C++ classes in July this year. Seeing as it has been my weakness in the past, I decided to try out a little bit myself just to get ready for the whole thing. I've purchased a green book, "Problem Solving with C++" and I've read it up and am trying to do the projects in it myself. Thing is, some of the projects in the book have a solution given, but there is this one problem revolving around storing information in files, which I cannot get around. I'll let you see the problem.
It asks us to make a program (using files) that would let a person enter student deatils as follows:
1 > Add student Details
2 > View a student detail
3 > Edit a student's details
4 > Delete a student record
5 > Display all students' grades
6 > Display all students' marks
7 > Display class average and standard deviation
8 > Save a student detail to a file
9 > Exit
I've decided to try to do this problem, step by step (otherwise I get confused), and so I started out with let's say the user picking option 1, to enter student details. Here is my code thusfar.
___________________________________________________________
#include <iostream>
#include <cmath>
#include <string>
#include <fstream>
using namespace std;
int main ()
{
int user_input_number;
double student_id;
string last_name;
string first_name;
int counter;
int number_of_students;
ofstream student_name("information.dat");
cout << "Welcome to course marks maintenance" << endl;
cout << "Please enter a number corresponding to its function" << endl;
cout << "1 > Add student Details" << endl;
cout << "2 > View a student detail" << endl;
cout << "3 > Edit a student's details" << endl;
cout << "4 > Delete a student record" << endl;
cout << "5 > Display all students' grades" << endl;
cout << "6 > Display all students' marks" << endl;
cout << "7 > Display class average and standard deviation" << endl;
cout << "8 > Save a student detail to a file" << endl;
cout << "9 > Exit" << endl;
cin >> user_input_number;
if (user_input_number == 1)
{
cout << "You have chosen to add student details" << endl;
cout << "We require you to input 15 details per student" << endl;
cout << "Please tell us how many students' info you are about to enter" << endl;
cin >> number_of_students;
cout << "You are kindly requested to enter every student's name" << endl;
if (student_name.is_open())
{
for (counter=0; counter<=number_of_students; counter++)
{
cout << "Enter student last name ==>";
getline (cin, last_name);
student_name << last_name << endl;
cout << "Enter student first name ==>";
getline (cin, first_name);
student_name << first_name << endl;
}
}
student_name.close();
}
system ("pause");
return 0;
}
__________________________________________________________
Simply put, this program was supposed to work like this: if I entered "1", it would ask me to enter student details, and I wrote code till where I could enter student first and last names. I run the program and it does work, but I have a runtime error. I'll show you a sample output of what I got.
_______________________________________________________
when it enters the place where i key in student names (I chose 2 students), if gives this as output
Enter student last name ==> Enter student first name ==>
Enter student last name ==> (i can give input here)
Enter student first name ==> (i can give input here)
Enter student last name ==> (i can give input here)
Enter student first name ==> (i can give input here)
I wanted to ask, why does it initially repeat this line without any prompt of input?
"Enter student last name ==> Enter student first name ==>"
How can I fix it? What would also be a good efficient way for me to finish the rest of my program? Should I use files or should I use arrays and loops instead? Any tips? Sample program? Ofcourse I don't expect you to write it all for me, I just need a 'kick start'
Thanks alot in advance.
Regards,
Yanling
AnswerWell it is because of the previous input request for the number of students. You read it using:
cin >> number_of_students
All well and good but it stops reading the input stream at the end of line character so the first item read by the next:
getline (cin, last_name);
is the end of line from the previous input, which has the effect of causing getline to think it has an empty line already buffered so it consumes the end of line character without waiting and returns.
The fix is obvious - use a dummy call to getline for example:
getline (cin, last_name); // reads previous end of line
getline (cin, last_name); // reads last name entered by user
I cannot help much with the best way to proceed as I do not know what you have learnt so far nor what the question requires. However here are some points about your code.
As I do not know the book you are using I have no idea how much C++ you have covered, so if you have not covered any of the topics mentioned then you might like to peek ahead or just note the ideas as interesting!
First, it seems to me that using a few functions and maybe a switch statement would be a useful technique to create cleaner code:
switch (user_input_number)
{
case 1:
status = AddStudent( <arguments> );
break;
case 2:
// ...
default:
// Handle case for invalid action number
}
You will have to think about passing data around between main and the worker functions however. I did not think about what sort of data this might be in the example and just put the placeholder <arguments> where the actual argument values would go. I have assumed the action functions such as AddStudent return some value to indicate success or failure (a bool value) or maybe even success or an error code (an integer value). I assume the bool or integer status variable is defined elsewhere. You could go further and define a specific enumerated type for the values entered:
enum UserStudentActions
{ AddStudent = 1
, ViewStudentDetails
// ...
};
// ...
cout << AddStudent << " > Add student Details" << endl;
cout << ViewStudentDetails << " > View a student detail" << endl;
// ...
switch (user_input_number)
{
case AddStudent:
status = AddStudent( <arguments> );
break;
case ViewStudentDetails:
// ...
default:
// Handle case for invalid action number
}
My next point is that you are not checking to see if the input stream cin is in a good state after each input. If it is not then you will need to take action. If the action is recoverable (i.e. the program can continue) then the error state on the stream will have to be cleared. You should see if your book has any information on the IOStream status flags and functions that operate on them.
Oh and if you have covered user defined types (classes and structs) then you might consider defining a Student type that can contain all the information for a student and knows how to save (and load) itself from file as well as other operations such as accessing student details like first and last name as well more complex data such as their grades and marks.
Finally, I do not think you need to save the details to file until the use requests such an action so why open the information.dat file now?
As far as I can tell in this example you only need to save individual student data on request and never load it back into the program. So you will not have to open a file until the user selects the "Save a student detail to a file" option. At this point you should use a file name related to the student selected and will have to think about issues such as the file already exists or cannot be opened. The easiest solution to the former problem is to use the default open file for writing action of create if it does not exist and open and truncate (i.e. delete existing data) if it does exist.
This would tend towards using a collection (or collections if not using a class or struct for the student data) in memory for the student data and only writing to file when requested by the user to do so. However, I would go back and carefully read the question again just to make sure.
If I were doing something like this in a more realistic way I would ensure all student data is written to file once entered – that is adding a new student would end in it being saved. Of course you might have to let the user review the data before writing it to file and they may need to edit it if there are any mistakes. This would seem to require a different program structure to that of your problem.
Other functions may always read the data from file or they may work on cached data in memory which is written back to file if modified (again after the user is happy with the changes).
Why do I wish to work in this way? Well RAM is volatile. If there is a power cut or the program or OS crashes then the more (modified) data that is in memory that has not been saved the more work the user will loose. So if the user enters 20 student records, then edits then reviews and edits them one at a time then saves them one at a time the procedure is firstly very tedious for the user and secondly open to loosing data until the last record has been saved.