Reputation: 59
I am beginner in c++. trying to store data from one function to another and I have several classes and functions which are inheretance. Would appreciate if someone can explain to me how to use the write function to store data to a file. Here is my code
`#include <iostream>
#include <fstream>
using namespace std;
class studentDetails
{
public:
string studentName;
int age;
char grade;
int birthYear;
void getDetails();
};
void studentDetails::getDetails()
{
cout<<"Enter the correct data in the forms below\n";
cout<<"Enter the full names of the student\n";
getline(cin, studentName);
cout<<"Enter the age of the student\n";
cin>>age;
cout<<"Enter the year of birth\n";
cin>>birthYear;
}
//define function captureStudentDetails()
void captureStudentDetails()
{
studentDetails inform;//declared object
ofstream myfile("studentDetails.txt", ios::out|ios::app);
inform.getDetails();
myfile.write((char *)&inform, sizeof(studentDetails));//NEED HELP!!!!!!!!!
myfile.close();
}
int main()
{
cout<<"Welcome to the student database\n";
captureStudentDetails();
return (0);
}
I know i can use
myfile<<name
for every variable but its a lot of process but it's a long process and could anyone help me with these meaning
outFile.write(reinterpret_cast<char *> (&creation), sizeof(bankAccount)
and ios::binary
thanks in advance.
Upvotes: 0
Views: 434
Reputation: 84609
If you are still stuck on the problem, here are a few thoughts that may get you going. While there are still a few points that are unclear from your question, (e.g. whether grade
is 'A'
, 'B'
, 'C'
, ... or 1
-12
, and whether your attempt to use write
is a requirement), but you are making things unnecessarily difficult by choosing write
and attempting to write the information out in a binary format. While there are many -- many ways you can choose to read and write data to a file, for simple data like name
, age
, birthyear
and grade
, simply writing as text as comma separated values makes things much easier.
Don't Hardcode Filenames
A new programmer mistake that many, if not all make, is to hardcode the filename inside a function. It may be convenient for a quick-test, never hardcode filenames in your actual finished code. While it is perfectly okay to provide a default filename to use if the user doesn't provide a filename, the user should not have to modify your source-code and recompile your program just to read and write to another filename.
Instead, take the filename as a program argument or prompt the user for the filename. Taking the filename as a command-line argument is easy to do, as is creating a constant for your default "studentDetails.txt"
. To provide a default, simply #define
the filename or create a constant expression. For example, using a simple #define
, you can do:
/* don't hardcode filenames in functions
* if you need a constant, either #define one
* or use a constant expression
*/
#define STUDENT_FILE "studentDetails.txt"
...
int main (int argc, char **argv) {
/* use 1st program argument as filename (STUDENT_FILE if none provided) */
std::string datafile = argc > 1 ? argv[1] : STUDENT_FILE;
(note: the subtle distinction between using a #define
(macro) or constant expression is that a #define
is a simple compile-time substitution that takes no program storage -- but -- cannot have it's address taken at runtime. If an address is needed, use a constant expression)
Getting the User-Input and Writing the CSV File
Since you are only holding one student's data in your program at a time, you will need to take the user input and then write the data to your datafile. As discussed in the comments, using a file as storage has a number of limitations, not the least of which is if you ever need to remove a student's data or checking if the student entered by the user already exists in your file.
You would normally want to read and hold all student data in memory so you could check for duplicates, add students, remove them and then write a new datafile when your program ends. However, since using the file is part of your assignment you will simply need to code around those limitations, which may include creating a temporary and deleting the original datafile and then renaming the temp file as your datafile if you need to remove students and reading the entire current file to check for duplicates before each addition is made. (those steps are left to you)
To aid with writing your data out to file, overloading the <<
operator allows you to write your student data simply using file << student;
. See operator overloading under the Stream extraction and insertion heading. When using a class
you can make the overload a friend
of the class
to provide access to private data members.
Taking that into consideration, your studentDetails
class could look like the following for the getDetails()
and write of the information to your datafile:
class studentDetails
{
private:
std::string name;
int age,
birthyear;
char grade;
public:
studentDetails() { name = "", age = birthyear = grade = 0; }
bool getDetails(); /* prompt user to enter student data */
/* declaration for overload of << to write csv student data to stream */
friend std::ostream& operator<< (std::ostream& os, const studentDetails& s);
};
When defining the friend-overload of <<
outside of the class, you drop the friend
from the beginning, e.g.
/* friend function definition of operator<< overload
* (you drop friend when defining the friend function outside of the class)
*/
std::ostream& operator<< (std::ostream& os, const studentDetails& s)
{
/* valdate data is complete before output */
if (s.name.length() && s.age && s.birthyear && s.grade) {
os << s.name << ',' << s.age << ',' << s.birthyear << ',' << s.grade << '\n';
}
else { /* show error */
std::cerr << "(studentDetails not set)\n";
}
return os; /* return stream state */
}
(note: since this is an output overload, the class-reference parameter (studentDetails& s
) is const
qualified -- it will not change)
Taking Input From the User
There is nothing sexy about taking user-input, you just take it piece-by-piece and ensure you validate every input and every conversion. Note also, that you generally do NOT want to use use a void
type for an input function return-type. You want a return-type that can indicate whether the input succeeded or failed. bool
is fine, as is a simple int
(1/0
) to indicate whether all inputs succeeded or failed. With that as a consideration, you can define your getDetails()
function something like:
/* member function definition for getDetails()
* prompts user to enter student data, returns true if
* complete data entered, false otherwise, allows user to
* simply press Enter at the name prompt to end data entry.
*/
bool studentDetails::getDetails()
{
/* provide instruction, prompt for name */
std::cout << "\nEnter the correct data in the forms below\n"
"(Enter alone for name to quit)\n\n"
" Full name of student : ";
if (!getline (std::cin, name)) { /* read/validate name */
/* handle manual EOF or stream state error */
std::cerr << "error: manual EOF or stream error reading name.\n";
return false;
}
if (!name.length()) { /* check if name empty - done with input */
return false;
}
/* prompt for age */
std::cout << " Age of student : ";
if (!(std::cin >> age)) { /* read/validate integer input */
/* handle error */
std::cerr << "error: invalid integer value.\n";
/* empty input stream of characters left by failed int read */
std::cin.ignore (std::numeric_limits<std::streamsize>::max(), '\n');
return false;
}
/* prompt for birthyear */
std::cout << " Year of birth : ";
if (!(std::cin >> birthyear)) { /* read/validate integer input */
/* handle error */
std::cerr << "error: invalid integer value.\n";
/* empty input stream of characters left by failed int read */
std::cin.ignore (std::numeric_limits<std::streamsize>::max(), '\n');
return false;
}
/* prompt for grade */
std::cout << " Grade : ";
if (!(std::cin >> grade)) { /* read char, ignoring whitespace */
/* handle manual EOF or stream state error */
std::cerr << "error: manual EOF or stream error reading name.\n";
return false;
}
/* empty input stream of any characters remaining in line */
std::cin.ignore (std::numeric_limits<std::streamsize>::max(), '\n');
return true;
}
(note: the user will indicate they are done with input simply by pressing Enter alone at the name
prompt -- which creates a name
of length 0
used to indicate the user is done with input -- you can use whatever method you like)
Make sure you understand why std::basic_istream::ignore is used and how to use it.
That's all that is needed from you class to be able to prompt the user for input, validate the input, and write the input to your datafile. You can write a short main()
to accomplish the rest, e.g.
int main (int argc, char **argv) {
/* use 1st program argument as filename (STUDENT_FILE if none provided) */
std::string datafile = argc > 1 ? argv[1] : STUDENT_FILE;
studentDetails student {}; /* instance of studentDetails class */
/* open output stream in append mode for storing data */
std::fstream f (datafile, std::ios::out | std::ios::app);
if (!f.is_open()) { /* validate file is open for writing */
std::cerr << "error: file open failed '" << datafile << "'.\n";
return false;
}
while (student.getDetails()) { /* loop collecting student data */
f << student; /* write in csv format to file */
}
f.close(); /* close output file - done collecting data */
}
Note, while you can simply allow the file to be closed when your program exits, since your next step will be to open the file again in input mode, the file was explicitly closed to allow the reuse of f.open (...)
for reading your data back in. You may want separate stream instances (e.g. f_out
and f_in
) since you will need some way to check if the student entered by the user already exists in your file before writing it to the file. (another significant limitation of not holding all students in memory) Once you write your function to read the csv values back in, you can loop over each student and compare the values to what the user enters before you write the new data to the end of the data file.
Example Use/Resulting Data File
Adding the needed headers and then compiling and running the file you can confirm the behavior (full-source included below for your reference). For example with the program compiled to student-file-build
and writing to a new filename of studentdetails.txt
, you would do:
$ ./student-file-build studentdetails.txt
Enter the correct data in the forms below
(Enter alone for name to quit)
Full name of student : Mickey Mouse
Age of student : 95
Year of birth : 1928
Grade : D
Enter the correct data in the forms below
(Enter alone for name to quit)
Full name of student : Minnie Mouse
Age of student : 95
Year of birth : 1928
Grade : A
Enter the correct data in the forms below
(Enter alone for name to quit)
Full name of student : Pluto (the dog)
Age of student : 92
Year of birth : 1931
Grade : F
Enter the correct data in the forms below
(Enter alone for name to quit)
Full name of student :
Checking the resulting datafile, you have:
$ cat studentdetails.txt
Mickey Mouse,95,1928,D
Minnie Mouse,95,1928,A
Pluto (the dog),92,1931,F
The data is all there in csv format which you can easily read back in with another friend
function overload of the >>
operator. It's class declaration would look like: friend std::istream& operator>> (std::istream& is, studentDetails& s);
The Complete Source
The complete source for the user-input and write to the datafile discussed above should look similar to:
#include <iostream>
#include <fstream>
#include <limits> /* for std::numeric_limits */
#include <string>
/* don't hardcode filenames in functions
* if you need a constant, either #define one
* or use a constant expression
*/
#define STUDENT_FILE "studentDetails.txt"
class studentDetails
{
private:
std::string name;
int age,
birthyear;
char grade;
public:
studentDetails() { name = "", age = birthyear = grade = 0; }
bool getDetails(); /* prompt user to enter student data */
/* declaration for overload of << to write csv student data to stream */
friend std::ostream& operator<< (std::ostream& os, const studentDetails& s);
};
/* member function definition for getDetails()
* prompts user to enter student data, returns true if
* complete data entered, false otherwise, allows user to
* simply press Enter at the name prompt to end data entry.
*/
bool studentDetails::getDetails()
{
/* provide instruction, prompt for name */
std::cout << "\nEnter the correct data in the forms below\n"
"(Enter alone for name to quit)\n\n"
" Full name of student : ";
if (!getline (std::cin, name)) { /* read/validate name */
/* handle manual EOF or stream state error */
std::cerr << "error: manual EOF or stream error reading name.\n";
return false;
}
if (!name.length()) { /* check if name empty - done with input */
return false;
}
/* prompt for age */
std::cout << " Age of student : ";
if (!(std::cin >> age)) { /* read/validate integer input */
/* handle error */
std::cerr << "error: invalid integer value.\n";
/* empty input stream of characters left by failed int read */
std::cin.ignore (std::numeric_limits<std::streamsize>::max(), '\n');
return false;
}
/* prompt for birthyear */
std::cout << " Year of birth : ";
if (!(std::cin >> birthyear)) { /* read/validate integer input */
/* handle error */
std::cerr << "error: invalid integer value.\n";
/* empty input stream of characters left by failed int read */
std::cin.ignore (std::numeric_limits<std::streamsize>::max(), '\n');
return false;
}
/* prompt for grade */
std::cout << " Grade : ";
if (!(std::cin >> grade)) { /* read char, ignoring whitespace */
/* handle manual EOF or stream state error */
std::cerr << "error: manual EOF or stream error reading name.\n";
return false;
}
/* empty input stream of any characters remaining in line */
std::cin.ignore (std::numeric_limits<std::streamsize>::max(), '\n');
return true;
}
/* friend function definition of operator<< overload
* (you drop friend when defining the friend function outside of the class)
*/
std::ostream& operator<< (std::ostream& os, const studentDetails& s)
{
/* valdate data is complete before output */
if (s.name.length() && s.age && s.birthyear && s.grade) {
os << s.name << ',' << s.age << ',' << s.birthyear << ',' << s.grade << '\n';
}
else { /* show error */
std::cerr << "(studentDetails not set)\n";
}
return os; /* return stream state */
}
int main (int argc, char **argv) {
/* use 1st program argument as filename (STUDENT_FILE if none provided) */
std::string datafile = argc > 1 ? argv[1] : STUDENT_FILE;
studentDetails student {}; /* instance of studentDetails class */
/* open output stream in append mode for storing data */
std::fstream f (datafile, std::ios::out | std::ios::app);
if (!f.is_open()) { /* validate file is open for writing */
std::cerr << "error: file open failed '" << datafile << "'.\n";
return false;
}
while (student.getDetails()) { /* loop collecting student data */
f << student; /* write in csv format to file */
}
f.close(); /* close output file - done collecting data */
}
Understand, this just scratches the tip-of-the-iceberg as far as the thought process and considerations that go into taking user-input. There is much much more you can do to write a robust user-input routine. Look things over and let me know if you have questions. If you get stuck writing the code to read the csv file back into your program, drop a comment and I'm happy to help further.
Upvotes: 1