The last problem:
Don't use ios::app|ios::ate flags together: ios::ate suppresses ios::app and truncate the file (don't ask me why now;)). If you want to append new records use ios::app only. If you want to append a record then seek and write at any other place (it's not your case with a text file), use:
fstream f(filename,ios::in|ios::out|ios::ate);
The main problem is more complicated:
1. You can't update lines in an existing text file: you must create a new file, write an updated contents then discard an old file then rename a new one - or rewrite an old file from the scratch. A file is only byte sequence, a line of a text file is a byte sequence terminated with '\n' or '\r' '\n' (depends of a target system). You can't ask a system to update i-th line - a system does not understand you (what is it: i-th line? Only you know!)
2. Try to load your "text database" file at the start of your program. Invent a proper structure - for example, use std::vector. Make add/update/erase operations under this structure. Write this structure back to the "text database" at the end of your program run. In addition you will get much more simpler and suitable for search/add/erase ops structure than cumbersome and unrobust mechanics with data file direct access.
ArkM
Postaholic
2,001 posts since Jul 2008
Reputation Points: 1,234
Solved Threads: 348
Well, CoolGamer48 got rights answers to all your questions...
May be it helps too (regrettably, I can't invent a very brief and clear example;)):
class Record
{
public:
Record(): age(0) {}
explicit Record(const char* pline);
explicit Record(const string& line);
Record(const char* pname, int age);
Record(const string& newname, int newage);
const string& getName() const
{
return name;
}
const string& setName(const char* pname)
{
if (pname)
name = pname;
return name;
}
const string& setName(const string& newname)
{
return name = newname;
}
int getAge() const { return age; }
int setAge(int newage)
{
if (newage >= 0 && newage < 150)
age = newage;
return age;
}
bool parse(const string& line);
string toString() const;
private:
static char delim;
string name;
int age;
};
ostream& operator << (ostream& os, const Record& r)
{
os << r.getName() << " " << r.getAge();
return os;
}
char Record::delim = '|';
Record::Record(const char* pline):age(0)
{
parse(pline);
}
Record::Record(const string& line):age(0)
{
parse(line);
}
Record::Record(const char* newname, int newage):age(0)
{
setName(newname);
setAge(newage);
}
Record::Record(const string& newname, int newage):age(0)
{
setName(newname);
setAge(newage);
}
bool Record::parse(const string& line)
{
istringstream ins(line);
char namebuf[64];
if (!ins.get(namebuf,sizeof namebuf,delim))
return false;
ins.ignore(1);
int age;
if (ins >> age)
{
name = namebuf;
setAge(age);
return true;
}
else return false;
}
string Record::toString() const
{
ostringstream ost;
ost << name << delim << age;
return ost.str();
}
class PlainDb
{
public:
int load(const char* fname);
int load(const string& fname)
{
return load(fname.c_str());
}
int save(const char* fname);
int save(const string& fname)
{
return save(fname.c_str());
}
Record& operator [](int i)
{
return i >= 0 && i < size()? db[i]: dummy;
}
int findByName(const char* pname)
{
if (pname)
{
string sname(pname);
for (int i = 0; i < size(); ++i)
if (db[i].getName() == sname)
return i;
}
return -1;
}
int findByName(const string& sname)
{
return findByName(sname.c_str());
}
int size() const { return db.size(); }
private:
static Record dummy;
vector<Record> db;
};
Record PlainDb::dummy("Unknown",0);
int PlainDb::load(const char* fname)
{
int nrecs = -1;
if (fname)
{
ifstream f(fname);
if (f)
{
Record r;
string line;
for (nrecs = 0; getline(f,line); ++nrecs)
{
if (!r.parse(line))
return -1;
db.push_back(r);
}
}
}
return nrecs;
}
int PlainDb::save(const char* fname)
{
int nrecs = -1;
if (fname)
{
ofstream f(fname,ios::ate|ios::trunc);
for (nrecs = 0; nrecs < size(); ++nrecs)
{
f << db[nrecs].toString() << "\n";
if (f.bad())
return nrecs?-nrecs:-1;
}
f.flush();
}
return nrecs;
}
void dbTest(const string& dbname)
{
PlainDb db;
if (db.load(dbname) <= 0)
return;
int i = db.findByName("Jesus");
if (i >= 0)
{
db[i].setName("Maria");
db[i].setAge(18);
db.save(dbname);
}
}
ArkM
Postaholic
2,001 posts since Jul 2008
Reputation Points: 1,234
Solved Threads: 348
It's exactly C++ battlefield!
Declare class Member as an abstract class (with pure virtual functions), define these virtual functions in FullMember and YoungMember classes, use vector in your variant of my PlainDb - that's all. Polymorphism works!..
I suspect that it's a true aim of your (homework?) task.
ArkM
Postaholic
2,001 posts since Jul 2008
Reputation Points: 1,234
Solved Threads: 348
Yes, it's (one of) a technical problem with deserializing. Fortunately, there are many ways to simplify its solution. We need an arbiter who make a decision (in the PlainDb::load() from my snippet equivalent): new FullMember() or new YoungMember ...
ArkM
Postaholic
2,001 posts since Jul 2008
Reputation Points: 1,234
Solved Threads: 348
you could add one more field to the structure which specifies the 'member_type' and write that to your file then when you load the file based on the 'member_type' you can create the correct object type.
Agni
Practically a Master Poster
655 posts since Dec 2007
Reputation Points: 431
Solved Threads: 116
you dont need a separate structure for a member_type, use the exisiting structure which coolgamer had suggested and add another field to it.
i think best would be that you try to write some code from whatever you have understood till now and post it, then we can take it from there.
Agni
Practically a Master Poster
655 posts since Dec 2007
Reputation Points: 431
Solved Threads: 116