I am making an airline reservation software and I don't know much about the Visual C++. I am using the simple compiler "TCWIN45". In my program I wish to use file handling and I am succeed to save all the inputs in text file. i need to add search option and modification option. if user choose search and Enter the name then how can I access specific number of lines. because my files contains the record of multiple passengers but I want to show the only one's data. same is the case for modification. I want to access specific location or line and also to overwrite it. please suggest me the most simplest way.
This is my code to save all the record in one text file:
ofstream thefile("ID.txt" , ios::app);
thefile<<"\n\nDay : "<<p1[i].day<<"\nFlight Date : "<<p1[i].date<<"\nFlight Type : "<<p1[i].type<<"\nReturn Date : "<<p1[i].rdate<<"\nDeparture Place : "<<p1[i].from<<"\nDestination : "<<p1[i].to<<"\nClass Type : "<<p1[i].clas<<"\nTime of Flight : "<<p1[i].time<<"\nTitle : "<<p1[i].prefix<<"\nFirst Name : "<<p1[i].fname<<"\nLast Name : "<<p1[i].lname<<"\nDate of Birth : "<<p1[i].dob<<"\nPassport Number : "<<p1[i].ppt_no<<"\nExpiry Date : "<<p1[i].edate<<"\n Contact Number : "<<p1[i].cont<<"\nMeal Type : "<<p1[i].meal<<"\n\n------------------------------";
Ali, this can be done in a flat file if you really want to not use a database. The trick, is to either: 1.) have all records the same size OR 2.) have a "record header" that provides "enough" information to be able to unserialize the record from the hard disk. If you store different kinds of records, "enough" information could be size of the record or a record type for RTTI purposes. I find it useful to also store an ID for each record so that I can store an index table for record offsets.
If you records have varying sizes, then your record's serialization functions have to be able to handle this. In fact, it is trivial to do this.
The index table is a table of file offsets.
typedef uint16_t record_id;
typedef long offset_t;
offset_t indices[ MAX_RECORDS ];
typedef struct _record {
uint16_t type;
uint16_t id;
offset_t next;
offset_t prev;
} record;
typedef struct _header {
uint32_t count;
offset_t first_record;
offset_t deleted_record;
} header;
So to find the position of the record, you find the offset into the file, which is indices[ record_id ]. Adding a record is like adding a node to a linked list, but the nodes are in the file.
Deleting records is a little tricky. You have to use "lazy delete" to delete records and later these deleted records get reused. You can even write a shrink function that will remove all deleted records from the file to free up unused space.
The limitations of this technique is that you can only search by record id. If you have other information, you will need to generate additional data structures to support this.
I have code available that does this in C if you would like a working example. However, doing this from scratch is feasible but NOT WORTH THE EFFORT. Just use a database like Sqlite or MySQL--it will save time!
Example Code
flat-db.c
flat-db.h
test-flat-db.c
From your comments to other answers, it does not seem like the best way for you to do this is to store the data in a text file at all. You will probably want a Reservation class that contains all of the information for the reservation. Then, use some kind of Collection to store all of the reservations. Writing to a text file just adds a huge amount of unnecessary difficulty.
Something like this:
class Reservation
{
std::string day;
std::string date;
std::string flightType;
std::string meal;
/* ... */
};
It would be even better if you made separate classes for each one of the class members (like a Day class, a FlightType class, etc.).
You would then use some kind of Map to access a particular reservation and change its members.
You'll probably want to define a reservation class that represents a single reservation, and a data class, that holds all your data, as a vector of reservations. The data class will want to have a member function that takes a std::ostream by reference, and saves the reservations to a text file, (easiest is one variable per line). It will also want a member function that takes a std::istream by reference and reads in the data from the text file.
The main part of your program would (I'm making TONS of assumptions here) load the file into the data class with the std::istream member function, and asks the user for some sort of ID. You then call a member function of data that checks all of the elements in datas vector until it finds the matching ID (by reference), and lets the user change some members. Then it calls the std::ostream member function again to save the changes.
Streams are handled like this. In this sample, I do not use the data class or a vector, since this question looks suspiciously like homework, but this shows the tricky parts of file handling.
#include <string>
#include <iostream>
#include <fstream>
class Reservation {
std::string ID;
std::string date;
public:
//default constructor
Reservation()
{}
//helpful constructor
Reservation(std::string _id, std::string _date)
:ID(_id), date(_date)
{}
//copy constructor
Reservation(const Reservation& b)
:ID(b.ID), date(b.date)
{}
//move constructor
Reservation(Reservation&& b)
:ID(std::move(b.ID)), date(std::move(b.date))
{}
//destructor
~Reservation()
{}
//assignment operator
Reservation& operator=(const Reservation& b)
{
ID = b.ID;
date = b.date;
return *this;
}
//move operator
Reservation& operator=(Reservation&& b)
{
ID = std::move(b.ID);
date = std::move(b.date);
return *this;
}
//save
std::ostream& save(std::ostream& file) {
file << ID << '\n';
file << date << '\n';
return file; //be in the habit of returning file by reference
}
//load
std::istream& load(std::istream& file) {
std::getline(file, ID);
std::getline(file, date);
return file; //be in the habit of returning file by reference
}
};
int main() {
Reservation reserve; //create a Reservation to play with
{ //load the reservation from loadfile
std::ifstream loadfile("myfile.txt");
reserve.load(loadfile);
}
//display the reservation
reserve.save(cout);
{ //save the reservation to a different file
std::ofstream savefile("myfile2.txt");
reserve.save(savefile);
}
return 0;
}
Related
I'm using pointers of class objects.Then i write in reservation file data.And if i read from .txt file i cannot read poiners.What's the good way to initialize pointers
Should i use additional variables(userLogin,roomId) and then match additional variables with variables of objects and get address from them?
class Guest
{
public:
string login;
string password;
string name;
};
class Room
{
public:
int id;
string type;
};
class Reservation
{
public:
string checkOut;
string checkIn;
Room* room;
Guest* user;
string userLOgin;
int roomId;
};
class Manage
{
void MakeResrvation(Guest* user,Room* room,bool appendToFile)
{
string checkIn,checkOut;
ofstream writer(ReservationPath, appendToFile ? ios::app : ios::out);
if (writer.is_open()) {
writer<<room->id<<Delimiter<<user->login<<Delimiter<<checkIn<<Delimiter<<checkOut<<Delimiter;
writer<<endl;
}
}
vector<Reservation*> LoadFromFile() {
ifstream reader(ReservationPath);
vector<Reservation*> reservations;
if (reader.is_open()) {
string line;
while (getline(reader, line))
{
vector<string> items = Split(line, Delimiter);
Reservation* reservation = new Reservation;
reservation->roomId=stoi(items.at(0));
reservation->userLOgin=items.at(1);
reservation->checkIn=items.at(2);
reservation->checkOut=items.at(3);
reservations.push_back(reservation);
}
reader.close();
}
return reservations;
};
I expect to get pointer in the most optimized way.
Why it doesn't make sense to read and write pointers to files ?
It makes no sense to read a pointer from a file. A pointer is an address within the memory space. When you create your reservation, the pointer points to an address where a valid object (e.g. a Room or a Guest) is supposed to be stored:
void test()
{
Room r{12,"big room"};
Guest g{"XYZ","...","Mr XY, Z"};
manager.MakeReservation(&g, &r, true);
} // r and g are destroyed here
When you later read the pointer, you are not sure that a valid object is still at the place where it was before. In the example above, the pointers pointed to local objects, and these have been destroyed in the meantime.
Worse, if you would read the file from another process (e.g., when running your program again the next day) the address space might be completely different, and the object you where hoping pointing to might never have existed there.
How to solve your problem ?
In a Reservation you have the key information to uniquely identify the objects you are looking for: roomId should uniquely identify a Room, and userLOgin should uniquely identify a Guest (at least I'll assume so hereafter), and regardless of the place where they are stored.
So what you need to do is to store somewhere repositories of rooms and guests: when you read your reservation, you need to search in the repository the corresponding objects, and set the pointers accordingly. OF course, you need to adapt your code: either pass the repositories as argument to the function that needs them, or store them as members of Manage.
Another alternative, could be when you save the object, also save the data of the pointed objects. When you then read the data, you'd have to create not only the reservation, but also the "embedded" objects, and initialise the pointers accordingly. This is a little bit more complex, but you can google for serialization to know more about this process.
Other improvements
Attention when you write data, in case the strings you are writing contain the delimiter: this could lead to nasty bugs when reading the file.
I'd suggest also to use in reservation either a roomId and always find the room in the repository when it's needed, or a pointer to Room, knowing that the id can be found there. Having redundant information in Reservation could lead weird things to happen such as pointing to an object with a different id than expected.
Now if you're managing your repositories in form of vector<Reservation*>, vector<Room*>, and vector<Guest*>, you may be interested in using vector<shared_ptr<Reservation>>, vector<shared_ptr<Room>>, and vector<shared_ptr<Guest>>, since shared_ptr facilitates memory management.
Finally, for your repositories, you may consider using std::map instead of std::vector, where the key to the map would be the unique identifier.
and I've hit my first wall on my coding project/assignment.
I'm to implement functionality into code that's been done to some stage, and I cannot alter the given code so I have to work around the given structure.
The code, in a nutshell, reads family relations from a text file and populates database with the family relation data and later on allows user to print out information he wants to access.
What I'm having trouble with is understanding how I can and how I have to utilize a struct given to me in the assignment. The struct is
struct Person
{
std::string id_ = NO_ID;
int height_ = NO_HEIGHT;
std::vector<Person*> parents_{nullptr, nullptr};
std::vector<Person*> children_;
};
and I'm using it at least in the initialization phase of the data structure.
I start by calling the process in main.cpp with
database->addRelation(it->child_, it->parents_, std::cout);
In the naming/height adding phase I'd simply do it with
MyPerson.id_ = id;
MyPerson.height_ = height;
where MyPerson is defined by Person MyPerson;
but as far as I can tell, I have to somehow access the object pointers to be able to populate the vectors for when I want to add children/parents to the person.
The class functions that are called when initializing person's name, height and family relations are these two:
void Familytree::addNewPerson(const string &id, const int &height, ostream &output)
{
MyPerson.id_ = id;
MyPerson.height_ = height;
}
void Familytree::addRelation(const string &child,
const std::vector<string>
&parents, ostream &output)
{
}
The addRelation fuction is what I'm having a hard time getting to work. Simply appending the strings to it won't work since it expects Person* -objects, which are, as far as I can tell, just pointers to the other Persons, but I'm not sure how I can access them.
Also, let me know if anything here is excessive or if I'm missing anything crucial, I'll edit it to the best of my ability
Editing with additional information:
The only things I've added myself that can be seen here is
Person MyPerson;
and the contents of the class function addNewPerson. The other snippets I can not change in any shape or form.
Edit#2
Current progress, debatable whether I'm closer or further from the goal
My persons map is using Personmap = std::map<std::string, Person >;
and I'm using it in addNewPerson with
persons_[id] = id;
persons_[id] = height;
, but I'm still randomly trying different things to try and make it work for the next phase where I need to somehow add the objects to the vectors.
The biggest problem I have is the fact that I do now know how to play around the difference of *Person and Person
I am having trouble using file I/O to create instances of my classes for a game I am working on. It might well be a dumb question, but I cannot fathom out why the compiler seems to successfully creates the objects from the data stored in the text file and then I can't access them. (I took out the .display() function calls to test this, and added a simple cout << "Object created"; into the constructor to check something had been created.)
But the code trying to access the individual objects gives me Error: "identifier" is undefined when trying to access the objects member functions. I am probably doing something completely wrong and I would appreciate a push in the right direction, I have tried changing the syntax in the while loop for creating the object, but I haven't cracked it yet. Thank you in advance! Code below...
main.cpp
#include <iostream>
#include <string>
#include <fstream>
#include "Attributes.h"
using std::cout;
using std::endl;
using std::cin;
using std::ofstream;
using std::ifstream;
using std::getline;
using std::cerr;
int main() {
std::string line;
ifstream attdata;
attdata.open("data.txt");
if (attdata.is_open())
{
while (attdata.good())
{
getline (attdata, line);
Attributes * line = new Attributes;
}
attdata.close();
}
else cerr << "Unable to open file.";
health.display();
fatigue.display();
attack.display();
skill.display();
defence.display();
skilldef.display();
speed.display();
luck.display();
};
data.txt
health
fatigue
attack
skill
defence
skilldef
speed
luck
Atributes.h
#pragma once
#include <string>
class Attributes
{
public:
Attributes(void);
Attributes(std::string name, std::string shortName, std::string desc, int min, int max);
~Attributes(void);
void display();
private:
std::string m_nameLong;
std::string m_nameShort;
std::string m_desc;
int m_minValue;
int m_maxValue;
};
In C++, all your variables need to be declared by name in your code. You are declaring a bunch of pointer variables all named line in your loop, and then trying to use other named variables like health, fatigue, etc that have not been created.
I don't think you can directly create variables by name from a file like this, but you could read the file and create an array or vector of objects that contain the data from the file. You could pass the string read by getline() into your Attributes constructor, and then store the created pointers in an array or map that you can access later to call methods like display(). If you really want a variable called health in your code, it has to be declared somewhere in the code.
Another minor point is that you are reusing the variable name line (which you previously declared to be a std::string) in the loop scope. This may work, but is confusing and should be avoided. Call your pointer variable something else, like attItem.
For example:
Attributes * attItem = new Attributes(line);
attList.push_back(attItem);
You arent sending any of the information that you received to create the new object. Add a constructor that takes in a string with the information and then initialize Attributes like so:
Atrributes::Attributes(String data){
//parse string and initialize data here
}
Also, I would recommend not making your Attributes object have the same name as the variable that holds the data. Even if it's harmless (and im not sure that it is), its just not very clean.
C and C++ doesn't allow new names of variables to be created at runtime. Thus health in health.display(); can not come from reading a file.
What you can do is have a collection of Attributes (e.g. attList) and a function that finds the appropriate attribute for you:
Attribute health = attList.find("health");
(Or if you prefer to use a map, you could do:
Attribute health = attList["health"];
Another approach of course is to have attributes stored in each object, e.g.
class PlayerBase
{
private:
Attribute health;
Attribute speed;
...
public:
void SetAttribute(const string& name, const Attribute& attr);
};
Then you'd find the right attribute by comparing string name:
void SetAttribute(const string& name, const Attribute& attr)
{
if (name == "health") health = attr;
if (name == "speed") speed = attr;
...
}
I've got a class outputInterface; that should handle the output (to files) of some data. The data is contained in objects of some custom classes, say dataClassA and dataClassB, that all derive from a common base class dataClassBase.
Now I want the data to be written to different files according to its type. So data of type dataClassA should go to fileA, data of type dataClassB should go to fileB and so on. As this output happens very often I would like the file handles (fileA and fileB) to stay open, i.e. I don't want to open and close the files for the output of each piece of data. One outputInterface object can be expected to exist all the time.
So what I would like to achieve is something like this:
Dynamically associate data of type dataClassA with the file handle fileA etc.
When receiving data of type dataClassA check whether fileA is already connected to a file, if not, open the file.
How can I get this behavior (or least something similar / better)?
I've been thinking of making the file handles static members of dataClassA and dataClassB (or the base class dataClassBase?). But then, how do I take care of closing the files? I would have to somehow keep track of the data types that have actually been used (the files that have actually been opened).
Try something like this:
#ifndef OUTPUTINTERFACE?H
#define OUTPUTINTERFACE?H
#include <string>
#include <fstream>
#include <map>
class DataClass
{
public:
virtual bool WriteData(std::ofstream& FStream) = 0;
};
class DataClass1 :
public DataClass
{
virtual bool WriteData(std::ofstream& FStream)
{
FStream << "teletubbies";
}
};
class DataClass2 :
public DataClass
{
virtual bool WriteData(std::ofstream& FStream)
{
FStream << "garbage";
}
};
class OutputInterface
{
public:
OutputInterface()
{
}
~OutputInterface()
{
//Release stream pointers
}
template<typename T>
bool WriteData(T& Data)
{
std::string dClassUID = std::string(typeid(T).name);
tFStreamMap::iterator it this->streamMap.find(dClassUID);
std::ofstream* stream = NULL;
if(it != streamMap.end())
{
stream = it->second;
}
else
{
stream = new std::ofstream();
stream->open(dClassUID + ".txt");
streamMap.insert(std::make_pair(dClassUID, stream));
}
Data.WriteData(stream);
}
private:
typedef std::map<std::string, std::ofstream*> tFStreamMap;
tFStreamMap streamMap;
};
#endif
This is just a prove of concept and can be optimized in many ways.
I would rather stick with overloaded functions than with runtime type checks.
This is fairly easy to implement in C++11, using an
std::map<std::type_index, std::ostring*> outputMap. (In C++03, you'll have to
implement the equivalent of std::type_index yourself.) You get the
output stream using outputMap[typeid(*data)]. The only problem is
getting the streams into the map to begin with: you can do something
like:
std::ostream*& destPtr = outputMap[typeid(*data)];
if ( destPtr == NULL ) {
destPtr = new std::ofstream("...");
}
std::ostream& dest = *destPtr;
But from where do you get the filename?
There's also the question of when you close the streams: you can't
normally close an output stream in a destructor, since closing an output
stream is an operation which can fail, and you need to detect and react
to that failure. Probably with an exception, which is why you don't
want to do it in a destructor.
Since the "data" part comes from dataClassBase, you can make a virtual/pure-virtual function 'WriteData` in this class, and let derive class implement it.
The class outputInterface may take objects of type dataClassBase and would directly call WriteData. Other than WriteData you may also add other virtual functions in dataClassBase
You did not mention relationship between outputInterface and dataClassBase
I am using mysql/C++ connector to connect to a mysql database. I have some complex data structures so I need to serialize those and save in the database.
I tried something like the following.
vector<int> vectorTest(10,100);
istream *blob = NULL;
ostringstream os;
int size_data = sizeof(vector<int>);
blob = new istringstream((char*)&vectorTest, istringstream::in | istringstream::binary);
string qry = "INSERT INTO vector(id,object) VALUES (?,?)";
prep_stmt = con->prepareStatement(qry);
prep_stmt->setInt(1,1);
prep_stmt->setBlob(2,blob);
prep_stmt->execute();
I just tried a small example here. However the vector object is not getting saved.
Alternatively can I can use the following approach.
ostringstream os;
int size_data = sizeof(vector<int>);
os.write((char*)&vectorTest, size_data);
However I don't know how to redirect the outputstream to an inputstream, because the setBlob() method needs an istream as the input parameter.
Can I know how to get any of this examples working ? If my approach is incorrect can anyone provide a code example or improve the given code segment ? Your immediate response is greatly appreciated.
Thanks
You're going about this completely the wrong way. This isn't "serialization", in fact it's quite possibly the opposite of serialization -- it's just trying to write out a raw memory dump of a vector into the database. Imagine for a second that vector looked like something this:
struct vector_int {
unsigned int num_elements;
int* elements;
};
Where elements is a dynamically allocated array that holds the elements of the vector.
What you would end up writing out to your database is the value of num_elements and then the value of the pointer elements. The element data would not be written to the database, and if you were to load the pointer location back into a vector on a different run of your program, the location it points to would contain garbage. The same sort of thing will happen with std::vector since it contains dynamically allocated memory that will will be written out as pointer values in your case, and other internal state that may not be valid if reloaded.
The whole point of "serialization" is to avoid this. Serialization means turning a complex object like this into a sequence of bytes that contains all of the information necessary to reconstitute the original object. You need to iterate through the vector and write out each integer that's in it. And moreover, you need to devise a format where, when you read it back in, you can determine where one integer ends and the next begins.
For example, you might whitespace-delimit the ints, and write them out like this:
1413 1812 1 219 4884 -57 12
And then when you read this blob back in you would have to parse this string back into seven separate integers and insert them into a newly-created vector.
Example code to write out:
vector<int> vectorTest(10,100);
ostringstream os;
for (vector<int>::const_iterator i = vectorTest.begin(); i != vectorTest.end(); ++i)
{
os << *i << " ";
}
// Then insert os.str() into the DB as your blob
Example code to read in:
// Say you have a blob string called "blob"
vector<int> vectorTest;
istringstream is(blob);
int n;
while(is >> n) {
vectorTest.push_back(n);
}
Now, this isn't necessarily the most efficient approach, space-wise, since this turns your integers into strings before inserting them into the database, which will take much more space than if you had just inserted them as binary-coded integers. However, the code to write out and read in would be more complex in that case because you would have to concern yourself with how you pack the integers into a byte sequence and how you parse a byte sequence into a bunch of ints. The code above uses strings so that the standard library streams can make this part easy and give a more straightforward demonstration of what serialization entails.
My solution to writing to a MySQL database was to use the Visitor design pattern and an abstract base class. I did not use the BLOB data structure, instead used fields (columns):
struct Field
{
// Every field has a name.
virtual const std::string get_field_name(void) = 0;
// Every field value can be converted to a string (except Blobs)
virtual const std::string get_value_as_string(void) = 0;
// {Optional} Every field knows it's SQL type.
// This is used in creating the table.
virtual unsigned int get_sql_type(void) = 0;
// {Optional} Every field has a length
virtual size_t get_field_length(void) = 0;
};
I built a hierarchy including fields for numbers, bool, and strings. Given a Field pointer or reference, an SQL INSERT and SELECT statement can be generated.
A Record would be a container of fields. Just provide a for_each() method with a visitor:
struct Field_Functor
{
virtual void operator()(const Field& f) = 0;
};
struct Record
{
void for_each(Field_Functor& functor)
{
//...
functor(field_container[i]); // or something similar
}
};
By using a more true Visitor design pattern, the SQL specifics are moved into the visitor. The visitor knows the field attributes due to the method called. This reduces the Field structure to having only get_field_name and get_value_as_string methods.
struct Field_Integer;
struct Visitor_Base
{
virtual void process(const Field_Integer& fi) = 0;
virtual void process(const Field_String& fs) = 0;
virtual void process(const Field_Double& fd) = 0;
};
struct Field_With_Visitor
{
virtual void accept_visitor(Visitor_Base& vb) = 0;
};
struct Field_Integer
{
void accept_visitor(Visitor_Base& vb)
{
vb.process(*this);
}
};
The record using the `Visitor_Base`:
struct Record_Using_Visitor
{
void accept_visitor(Visitor_Base& vistor)
{
Field_Container::iterator iter;
for (iter = m_fields.begin();
iter != m_fields.end();
++iter)
{
(*iter)->accept_visitor(rv);
}
return;
}
};
My current hurdle is handling BLOB fields with MySQL C++ Connector and wxWidgets.
You may also want to add the tags: MySQL and database to your next questions.
boost has a serialization library (I have never used it tho)
or XML or JSON