C++: Read and write classes - c++

#include <cstdlib>
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
class PERSON{
string name, surname;
public:
void set_name(string aname, string asurname){
name = aname;
surname = asurname;
};
void read(){
}
void output(){
cout << name << " " << surname << "\n";
}
void save(PERSON *x){
ofstream file("test.bin", ios::out | ios::app | ios::binary);
if(!file.is_open()){
cout << "ERROR\n";
}else{
file.write((char*)x, sizeof(*x));
file.close();
}
}
};
/*
*
*
*
*/
int main(int argc, char** argv) {
PERSON * person1 = new PERSON;
PERSON * person2 = new PERSON;
person1->set_name("Amon", "Raa");
person1->save(oseba1);
ifstream file2("test.bin", ios::in | ios::binary);
if(!file2.is_open()){
cout << "Error\n";
return 0;
}
while(!file2.eof()){
file2.read((char *)person2, sizeof(*person2));
person2->output();
}
file2.close();
return 0;
}
This is my code...what I am doing wrong?
What I am trying to do is to save each time a class to the end of the binary file and then read all entries...
but each time I run the program I get printed only the last entered name
so run it first time
the file is written correctly and the output is OK
then I change the name to something else, lets say John Doe, I get the output of 2times John Doe
Please help... I am a complete beginner ;(

Serialization of classes is for example included in the boost package.
http://www.boost.org/doc/libs/1_53_0/libs/serialization/doc/index.html
I do not think you actually want to implement these kind of functionality yourself.

You can't simply write out the binary image of a class in C++, especially one that contains pointers and non-POD members. Once you have indirections in your data, simply writing out a memory image doesn't work, both because just writing out the memory image of the class doesn't include the pointed-to data and because in order for this to work you'd have to load all the data including the pointed-to data into the exact same memory location that they were in when you saved them. That's not easily possible (to put it mildly).
You have two options, one manual, one using a third party library:
1) You write and read each member out separately with a bunch of bookkeeping information. That should work in you case as all you really have to load and save is the contents of the two strings and their respective lengths
2) The other option - especially when the data structure is more complex than the one you are using - is to use something like boost::serialization to do the grunt work for you.

You have to use pointer array of class PERSON. Then read from the binary file and populate array of persons.
ifstream input("PERSON.std",ios::binary);
input.seekg(0, ios::end);
int count = input.tellg() / sizeof(PERSON);
PERSON *persons = new PERSON[count];
input.seekg(0, ios::beg);
input.read((char*) persons, sizeof(PERSON)*count);
cout << count << endl;
for (int j = 0; j < count; j++)
{
cout << count << endl;
cout <<persons[j].output() << "\n";
}
cout << '\n';
input.close();

Related

Writing a vector of objects to file and then reading it

I'm new to C++ and stackoverflow so forgive me any mistakes in my post ;). I need to create a code, which allows me to fill new objects with data from std::cin and export these objects to binary file later. Also, I need to import objects exported to file at some point. Objects represent users with standard user information like username, ID, lvl etc.
#include <vector>
#include <string>
#include <iostream>
class User {
std::string username;
unsigned int ID, lvl;
public:
User(std::string un, int uID, int ulvl) {
username = un;
ID = uID;
lvl = ulvl;
}
};
int main() {
std::string u_name;
int u_ID,u_lvl;
bool finish = false;
char choice;
std::vector<User> test_user_vec;
do {
std::cout << "Enter username: ";
std::cin >> u_name;
std::cout << "Enter ID: ";
std::cin >> u_ID;
std::cout << "Enter lvl: ";
std::cin >> u_lvl;
test_user_vec.push_back(User(u_name, u_ID, u_lvl));
std::cout << "Do you want to add another user? (y/n)?";
choice = getch();
if (choice == 'y') finish = true;
} while (!finish);
return 0;
}
I assume that test_user_vec stores every object I created while my program is running. My problem occurs when I want to export that vector to file. The purpose of this action is to store objects' data even after my program terminates and import all the data when I run my program again.
I was trying to solve this problem on my own, nothing really came to my mind. While I was looking for some info i found something like this:
#include <fstream>
#include <vector>
#include <string>
int main()
{
std::vector<std::string> v{ "one", "two", "three" };
std::ofstream outFile("my_file.txt");
// the important part
for (const auto &e : v) outFile << e << "\n";
}
I've tested it with <string> and <int> vectors and my variables. It's good until I try to export <object>vector.
Also i found another solution and tried to do something with it on another test code:
class Test {
public:
int number;
float number2;
};
int main(){
Test test1;
test1.number = 122;
test1.number2=12;
std::fstream testfile("test1.bin", std::ios::out | std::ios::binary);
testfile.write((char*)&test1, sizeof(test1));
testfile.close();
//ater writing an object with variables i commented this section
//then uncommented this section and run the program again
std::fstream testfile2("test1.bin", std::ios::in);
testfile2.read((char*)&test1, sizeof(test1));
std::cout << test1.number;
testfile2.close();
return 0;
}
Again, it works, i can read test1.number until I want to use vector of objects, not a single object. With vector of objects my cout printed some random values like 11314123e-03.
I was trying to somehow combine these 2 solutions, but nothing worked out. I would like to have a binary file, because i heard it's faster and has any data protection (i can't just open it in notepad and read the data) I'm new to c++, there is a great chance of me trying to do it reeeeeealy inefficient way, so pls help :D.
Data member getter functions can be added to the User class and used in fstream output operations. This should provide the general idea:
std::string userName;
for (const auto &u : v)
{
outFile.write(u.GetID(), sizeof(int));
outFile.write(u.GetLvl(), sizeof(int));
userName = u.GetName();
outFile.write(username.length(), sizeof(size_t));
outFile.write(userName.data(), username.length());
}
For userName, the length is written to precede the userName string data in the file so that the file can be parsed when read. The binary encoding/convention is designer's decision as there are several options. Another option would be to encode the entire object as a null-terminated string, although this would generally be less size efficient except for the userName string itself.
Note: test_user_vec.push_back(User(u_name, u_ID, u_lvl)); is creating temporary User objects on the stack. As #drescherjm and #RaymondChen pointed out, that is OK, but this is a better alternative: test_user_vec.emplace_back(...);

output/input mutiple files to desktop

working on some code that will place (output/input) 5 different files onto my desktop. Finally, got it down to one error message which is "error <23>: C2109: subscript an array or pointer type is required". Its with myfile.open; I've tried -> operator. Not exactly how to make this into an array, if that is what I'm suppose to do as I have tried making string into char and warnings occur. Can anybody let me know how I can modify my code to correct this? I'm relatively new to C++ and programming, only a few months.
#include <iostream>
#include <fstream>
using namespace std;
struct pizza{
string FILENAMES[9];
};
int main ()
{
int i;
char FILENAMES;
pizza greg = {"file1.doc", "file2.doc", "file3.doc", "file4.doc", "file5.doc"};
cout << "Input is invalid. Program will end. " << "\n" ;
for (i = 0; i < 5; i++)
{
const char *path="/Desktop/Libraries/Documents" ;
ofstream myfile(path);
myfile.open (FILENAMES[i]) ;
myfile << "How you like math?\n" ;
myfile.close();
};
return 0;
}
Your suggestions helped a lot, and my program is now up and running. (no pun intended, haha.)
The loop should really look something like this:
const char *path="/Desktop/Libraries/Documents";
for (int i = 0; i < 5; ++i)
{
std::string name(path + greg.FILENAMES[i]);
std::ofstream myfile(name.c_str());
if (myfile) {
myfile << "How you like math?\n" ;
}
else {
std::cerr << "ERROR: failed to open '" << name << "' for writing\n";
}
}
char FILENAMES;
FILENAMES is not an array. Even if it were, you would have to make it an array of strings or a two dimensional array of characters to do what you intend here.
What you probably intend to do is access the field inside greg.
myfile.open (greg.FILENAMES[i]);

Write and read records to .dat file C++

I am quite new to C++ and am trying to work out how to write a record in the format of this structure below to a text file:
struct user {
int id;
char username [20];
char password [20];
char name [20];
char email [30];
int telephone;
char address [70];
int level;
};
So far, I'm able to write to it fine but without an incremented id number as I don't know how to work out the number of records so the file looks something like this after I've written the data to the file.
1 Nick pass Nick email tele address 1
1 user pass name email tele address 1
1 test test test test test test 1
1 user pass Nick email tele addy 1
1 nbao pass Nick email tele 207 1
Using the following code:
ofstream outFile;
outFile.open("users.dat", ios::app);
// User input of data here
outFile << "\n" << 1 << " " << username << " " << password << " " << name << " "
<< email << " " << telephone << " " << address << " " << 1;
cout << "\nUser added successfully\n\n";
outFile.close();
So, how can I increment the value for each record on insertion and how then target a specific record in the file?
EDIT: I've got as far as being able to display each line:
if (inFile.is_open())
{
while(!inFile.eof())
{
cout<<endl;
getline(inFile,line);
cout<<line<<endl;
}
inFile.close();
}
What you have so far is not bad, except that it cannot handle cases where there is space in your strings (for example in address!)
What you are trying to do is write a very basic data base. You require three operations that need to be implemented separately (although intertwining them may give you better performance in certain cases, but I'm sure that's not your concern here).
Insert: You already have this implemented. Only thing you might want to change is the " " to "\n". This way, every field of the struct is in a new line and your problem with spaces are resolved. Later when reading, you need to read line by line
Search: To search, you need to open the file, read struct by struct (which itself consists of reading many lines corresponding to your struct fields) and identifying the entities of your interest. What to do with them is another issue, but simplest case would be to return the list of matching entities in an array (or vector).
Delete: This is similar to search, except you have to rewrite the file. What you do is basically, again read struct by struct, see which ones match your criteria of deletion. You ignore those that match, and write (like the insert part) the rest to another file. Afterwards, you can replace the original file with the new file.
Here is a pseudo-code:
Write-entity(user &u, ofstream &fout)
fout << u.id << endl
<< u.username << endl
<< u.password << endl
<< ...
Read-entity(user &u, ifstream &fin)
char ignore_new_line
fin >> u.id >> ignore_new_line
fin.getline(u.username, 20);
fin.getline(u.password, 20);
...
if end of file
return fail
Insert(user &u)
ofstream fout("db.dat");
Write-entity(u, fout);
fout.close();
Search(char *username) /* for example */
ifstream fin("db.dat");
user u;
vector<user> results;
while (Read-entity(u))
if (strcmp(username, u.username) == 0)
results.push_back(u);
fin.close();
return results;
Delete(int level) /* for example */
ifstream fin("db.dat");
ofstream fout("db_temp.dat");
user u;
while (Read-entity(u))
if (level != u.level)
Write-entity(u, fout);
fin.close();
fout.close();
copy "db_temp.dat" to "db.dat"
Side note: It's a good idea to place the \n after data has been written (so that your text file would end in a new line)
Using typical methods at least you will need to use fix size records if you want to have random access when reading the file so say you have 5 characters for name it will be stored as
bob\0\0
or whatever else you use to pad, this way you can index with record number * record size.
To increment the index you in the way you are doing you will need to the read the file to find the high existing index and increment it. Or you can load the file into memory and append the new record and write the file back
std::vector<user> users=read_dat("file.dat");
user user_=get_from_input();
users.push_back(user_);
then write the file back
std::ofstream file("file.dat");
for(size_t i=0; i!=users.size(); ++i) {
file << users.at(i);
//you will need to implement the stream extractor to do this easily
}
I suggest to wrap the file handler into a Class, and then overload the operator >> and << for your struct, with this was you will control the in and out.
For instance
struct User{
...
};
typedef std::vector<User> UserConT;
struct MyDataFile
{
ofstream outFile;
UserConT User_container;
MyDataFile(std::string const&); //
MyDataFile& operator<< (User const& user); // Implement and/or process the record before to write
MyDataFile& operator>> (UserConT & user); // Implement the extraction/parse and insert into container
MyDataFile& operator<< (UserConT const & user); //Implement extraction/parse and insert into ofstream
};
MyDataFile& MyDataFile::operator<< (User const& user)
{
static unsigned myIdRecord=User_container.size();
myIdRecord++;
outFile << user.id+myIdRecord << ....;
return *this;
}
int main()
{
MydataFile file("data.dat");
UserConT myUser;
User a;
//... you could manage a single record
a.name="pepe";
...
file<<a;
..//
}
A .Dat file is normally a simple text file itself that can be opened with notepad . So , you can simply read the Last Line of the file , read it , extract the first character , convert it into integer . THen increment the value and be done .
Some sample code here :
#include <iostream.h>
#include <fstream.h>
using namespace std;
int main(int argc, char *argv[])
{
ifstream in("test.txt");
if(!in) {
cout << "Cannot open input file.\n";
return 1;
}
char str[255];
while(in) {
in.getline(str, 255); // delim defaults to '\n'
//if(in) cout << str << endl;
}
// Now str contains the last line ,
if ((str[0] >=48) || ( str[0] <=57))
{
int i = atoi(str[0]);
i++;
}
//i contains the latest value , do your operation now
in.close();
return 0;
}
Assuming your file format doesn't not need to be human readable.
You can write the struct out to file such as.
outFile.open("users.dat", ios::app | ios::binary);
user someValue = {};
outFile.write( (char*)&someValue, sizeof(user) );
int nIndex = 0;
user fetchValue = {};
ifstream inputFile.open("user.data", ios::binary);
inputFile.seekg (0, ios::end);
int itemCount = inputFile.tellg() / sizeof(user);
inputFile.seekg (0, ios::beg);
if( nIndex > -1 && nIndex < itemCount){
inputFile.seekg ( sizeof(user) * nIndex , ios::beg);
inputFile.read( (char*)&fetchValue, sizeof(user) );
}
The code that writes to the file is a member function of the user struct?
Otherwise I see no connection with between the output and the struct.
Possible things to do:
write the id member instead of 1
use a counter for id and increment it at each write
don't write the id and when reading use the line number as id

how can i close files after open them in a function in c++

I work on using extensible hash to find the query FASTER.
my code is this steps:
1)read the main text file ( hudge file 4 GiB)
the file is some thing like this :
12435 alex romero
13452 jack robert
13485 marya car
45132 gun tribble
...
the user want to know that for example the key 12435 is related to what ?(answer:alex romero)
2)create a hash table for the keys in the file (i means 12435,13452,13485,...)
and i save this tables dynamically in hard disk in some text files named:0.txt,1.txt,2.txt and ....
3)when the user get query to the program then the program must calculate the hash function on its value and find the file that must be read then it is faster to find the result.
i have a function:
#define LIMIT 7
void writeInFile(int key , const char* charPos ){
int remainder = key%(LIMIT*LIMIT);
string myFileName;
ostringstream convert;
convert << remainder ;
myFileName = convert.str();
myFileName += ".txt";
FILE *my_file;
my_file = fopen(myFileName.c_str() ,"a");
fputs("\n" ,my_file);
fputs(charPos , my_file);
//fclose(my_file);
}
i wondered that when i use fclose then the speed of the program will reduced !!!
then i dont use it at the end of the function but a problem that is when i use this function many times i can't close them then i cant get access to the files.
i want to create a "list" of FILEs that i can send refrence of them to the function like: FILE &* myFiles[] or FILE &** myFiles as 3th parameter that function gets...
but i see the errors .i dont know how is its syntax of this.i means some syntax like:
void writeInFile(int key , const char* charPos , FILE &*myFiles[] ) // this makes error
the other method that i think is that can i close those files that now I can't access to them ? or can i change my code that cause this ?
update:this is my full code
#include <iostream>
#include <fstream>
#include <limits>
#include <string>
#include <sstream>
#include <stdio.h>
#include <vector>
#define LIMIT 7
using namespace std;
void writeInFile(int key , const char* charPos ){
int remainder = key%(LIMIT*LIMIT);
string myFileName;
ostringstream convert;
convert << remainder ;
myFileName = convert.str();
myFileName += ".txt";
FILE *my_file;
my_file = fopen(myFileName.c_str() ,"a");
fputs("\n" ,my_file);
fputs(charPos ,my_file);
//fclose(my_file);
}
int main(){
string fileName;
cout << "hello, please inter your file destination : " ;
cin >> fileName;
ifstream myFile ;
myFile.open(fileName.c_str() ,ifstream::in |ifstream::binary);
cout << "building the hash,please wait";
string havij;//:D this is an unusable variable in this section :))
int current;
int index;
int isCout=0;
char buffer [10];
//FILE *my_file[49];
while(!myFile.eof()){
cout << isCout << endl;
isCout++;
index = myFile.tellg();
itoa(index , buffer ,10);
//cout << buffer << endl;
myFile >> current;
writeInFile(current ,buffer);
getline(myFile,havij);
}
myFile.close();
fstream test;
//for(int i =0 ; i<LIMIT*LIMIT-1 ; i++){
// fclose(my_file[i]);
//}
cout << endl << "static extensible hash structure builded please inter your query : " ;
int query;
cin >> query;
int remainder = query%(LIMIT*LIMIT);
string myFileName;
ostringstream convert;
convert << remainder ;
myFileName = convert.str();
myFileName += ".txt";
ifstream myFile2;
//myFile2 is now the files that create by program like : 12.txt ,25.txt ,....
myFile2.open(myFileName.c_str() , ifstream::in | ifstream::binary);
ifstream mainFile;
mainFile.open(fileName.c_str(), ifstream::in | ifstream::binary);
int position;
string wanted;
int tester;
while(!myFile2.eof()){
myFile2 >> position;
mainFile.seekg(position ,ios::beg);
mainFile >> tester;
if (tester == query ){
getline(mainFile ,wanted);
cout << "the result of the key " << tester << " is " << wanted << endl;
}
}
return 0;
}
Or you could do this:
void writeInFile(int key , const char* charPos , std::vector<std::ofstream> & myFiles );
I find this makes my brain hurt less.
If you don't close your file in the same context where the FILE* variable is declared, you are leaking that file descriptor. At some point you are going to run out of resources and the program will crash.
Since you are using C++ from the snippet you've shown, then you would be much better off using std::vector and std::ofstream.
void writeInFile(int key, const char* charPos, std::vector<std::ofstream> my_files )
As has been said, you should close the file in the scope it is opened. This is the default behavior for C++ streams.
However it does not mean that you should open/close for each word you add! The files you write to should be kept open as long as you have things to add to them (beware there is a limit in the number of file descriptors an OS can handle).
Therefore, you should have:
Open all destination files (*)
For each line, select the appropriate file in a table/map and write into it
Close all destination files
(*) As said, you might run into a hard limit, in this case there is not much you can do, caching is unlikely to be effective if your hash function is worth anything. A possibility would be to make several runs over the big file and saving only a portion of the hashes at each run (say run 1: hashes in [0-9], run 2: hashes in [10-19], ...).
The fundamental type FILE* or ofstream that you use is of little importance, both have comparable speed (correctly tuned).

Unable to take input from a file containing string variables

I decided to make a phonebook in c++ and decided to take input of names, address and number from a file.
So I made a class called contact and declared the public variables name, address and number.
Using a constructor I initialized these to name="noname"(string), number=0(int), address="no address"(string)
Now my body of main goes as:
int main(){
contact *d;
d= new contact[200];
string name,add;
int choice,modchoice;//Variable for switch statement
int phno,phno1;
int i=0;
int initsize=0, i1=0;//i is declared as a static int variable
bool flag=false,flag_no_blank=false;
//TAKE DATA FROM FILES.....
//We create 3 files names, phone numbers, Address and then abstract the data from these files first!
fstream f1;
fstream f2;
fstream f3;
string file_input_name;
string file_input_address;
int file_input_number;
f1.open("./names");
while(f1>>file_input_name){
d[i].name=file_input_name;
i++;
}
initsize=i;
f2.open("./numbers");
while(f2>>file_input_name){
d[i1].phonenumber=file_input_number;
i1++;
}
f3.open("./address");
while(f3>>file_input_address){
d[i1].address=file_input_address;
i1++;
}
now when I later search for a particular entry by name, the name is displayed correctly but the phoneumber is returned as a garbage value and address as "Noaddress"
I dont understand why this is happening...
In case u want to look at entire code, do let me know....
This is how i search for a particular entry which returns the name if matched but returns garbage for phone number....
cout<<"\nEnter the name";//Here it is assumed that no two contacts can have same contact number or address but may have the same name.
cin>>name;
int k=0,val;
cout<<"\n\nSearching.........\n\n";
for(int j=0;j<=i;j++){
if(d[j].name==name){
k++;
cout<<k
<<".\t"
<<d[j].name
<<"\t"<<d[j].phonenumber
<<"\t"<<d[j].address
<<"\n\n";
val=j;
}
}
Thanks in advance
When your reading the file with the phone numbers
f2.open("./numbers");
while(f2>>file_input_name){
d[i1].phonenumber=file_input_number;
i1++;
}
You store the phone number in the string file_input_name but then you use a different var, file_input_number to store the information in the array d;
Hey guys I figured out the problem....
the problem is that i1 should be set to 0 after the second loop
and that the file taking input numbers should be f2.open("numbers") and not names....silly mistake!!
Since you are using C++, and not C, you should take advantage of the things that come with the language. Don't use arrays to store your data, use a std::vector. That way you don't have to remember how many things you have already put into the vector, because you can always ask the vector to tell you the size().
If I had to read in the three files I would go like this:
#include <vector>
#include <string>
#include <iostream>
#include <fstream>
#include <algorithm>
using std::cin;
using std::cout;
using std::fstream;
using std::string;
using std::vector;
class contact {
public:
string name;
string address;
int phone;
};
void print_contact(const contact &c) {
cout << "name " << c.name << " address " << c.address << " phone " << c.phone << "\n";
}
int main(int argc, char **argv)
{
vector<contact> contacts;
string name;
string address;
int phone;
fstream f1("d:\\names.txt");
fstream f2("d:\\phones.txt");
fstream f3("d:\\addresses.txt");
// note that I am using getline() here.
while (getline(f1, name) && f2 >> phone && getline(f3, address)) {
contact c;
c.name = name;
c.address = address;
c.phone = phone;
contacts.push_back(c);
}
for_each(contacts.begin(), contacts.end(), print_contact);
// for the Windows console window
cout << "Press return to continue ...";
string s;
getline(cin, s);
return 0;
}