C++ reading text files
I have a program which I am writing which manages a savings club. I have set up a functions which creates members (there are two members types) and outputs there data to a text file a line at a time in the format string, int, int, date(name, account number, balance, date). I have also written a host of other functions which modify the balances and so on.

My question is, how would I go about loading this data in from a file for use in the program so that the data can be used in my functions. I would also like to know how you would search the text file with say using the account holders name and bring up all the assoiated associted account data (one line of this text file is one account). The below code is what creates the data within the file (albiet this has been shortend and altered so that it can work alone). I would like to extract that data so that I can calculate an individual members interest etc.

One other problem, the code below is a shorted version of the code for use in my main program. It outputs data to the file, but for some reason everytime I run it, it deletes whats in the file junmembers.txt and replaces it with the new data, even though I have loaded the file in append mode. Here is my code

#include <iostream>
#include <fstream>
#include <date>

using namespace std;

int main()
{
string a;
string n;
int b;
int aNum;
int date;


ofstream jun("file.txt", ios::app | ios::ate ); 


if (jun.fail ())
{
cout << "Error opening file, program closing" << endl;
exit(1);
}

cout << "Please enter a name" << endl;
cin >> n;
cout << "Please enter an age" << endl;
cin >> a;
cout << "Please enter an expiry date" << endl;
cin >> date;


srand ( time(NULL) ); // this seeds the random number so it will not be dulplicated

aNum = rand() % 999 + 1000;/* The random number generator here is producing a

random number between 0 and 999 and then will make the

random value be within the range of +1000 - +1999 as it is

addingg 1000. Accounts with numbers begining with 1 will be 

Junior accounts and accounts with numbers beggining 

with 2 will be Full accounts */



b = 0; //default balance will be nil or 0

jun << n << " " << aNum << " " << b << " " << a << " " << date << endl;//output to file

jun.close();




}

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<MemberRecord>. 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.

Thanks for the help, but could you please expand on point number 2. ArkM. What is my text database file?

Thanks for the help, but could you please expand on point number 2. ArkM. What is my text database file?

A text database file is just your text file.

Thanks coolgamer48, but how would you load the data from the txt file into the STL Vector

firstly, you need to have a structure that can hold all the data that you need. In the example, this would be vector<MemberRecord>. As far as loading a MemberRecord into your program, you have a couple of options. Say you store data like this:

John 42 15.69
Bill 34 17.89

Meaning that John is 42 and has $15.69 and Bill is 34 years old and has $17.89. Say your structure looks like this:

struct MemberRecord
{
    std::string name;
    int age;
    float balance;
};

You could extract the data manually:

MemberRecord mem;
ifstream fin("myfile.txt");
fin >> mem.name >> mem.age >> mem.balance;
myvector.push_back(mem);

Or you could define a custom extraction operator for it:

ifstream& operator>>(ifstream& fin,MemberRecord& mem)
{
    fin >> mem.name >> mem.age >> mem.balance;
}

//... in some other function:
fin >> mem;
myvector.push_back(mem);

Thanks a million! Will be of great help.

Oh - sorry, there was an error in my overloaded extraction operator:

ifstream& operator>>(ifstream& fin,MemberRecord& mem)
{
    fin >> mem.name >> mem.age >> mem.balance;
    return fin;
}

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);
    }
}

Thanks for all that code but how would that be used in my program as I have two member types, a full member and a young member type. These member types have there own classes and a derived from a partent Member class.

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<Member*> in your variant of my PlainDb - that's all. Polymorphism works!..

I suspect that it's a true aim of your (homework?) task.

I wonder how you guessed that! Thanks for the help guys.

It might not be that simple if you also need to keep track of whether a member is full or young. You would need to output that in some form to your text file, and then as you read the data, create an appropriate object based on whether its a full or young member.

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 ...

Are you saying that it would be better to output both member types in one text file?

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.

Don't shoot me for this, but where do I write the structure for member type (I have two child classes and one parent class that defines the objects) and where do I put "vector<Member*>" and what do I do with it?

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.

How do you load the data from the txt files into a vector, thats what I can't understand. I have changed my program so that it only uses on text file, but each line of that files starts with either Full Mem or Young Mem depending on the account stored. Also the Young member has 4 pieces of data stored with it wheras the full mem only has three.

I want the program to call up this data so that it can be used in the accounts assciated functions i.e. deposit money, withdraw or calculate the interest etc.

A vector is a dynamic array. You can use the push_back() method to add an element to the end of the vector, and you can use the overloaded [] operator to access elements. That's just a quick overview though, see the link for more detailed information.

Also, I regret making that comment a few posts above. I think it would be much easier to put the member data into two separate files, one for full and one for young. I hadn't thought of that when I made the post.

One thing I do not understand is what is an element, is it the whole line from the txt file or is just a piece of data from it, say the accNum? I just cannot seem to be able to grasp who vectors work, and the link seems to confuse me further, as all of the varibles are integers as my I have a c few types of varibles in my program.

My task asks me to use STL vector of pointers to Members (customers) to represent the scheme

vector as coolgamer mentioned above is a dynamic array. you can use it to store data.It can grow as per requirement and you dont need to worry about the size at compile time. it's best suited for conditions where you need random search and less of inserts and updates.

example:

vector<int> v;
int i =10;
v.push_back(i);
//fetch an element
cout << v[0] << endl;

when i declare a vector i mention the type of elements it will store, in this case 'int'. then i can insert as many 'int's' into it as needed. if your task requires you to use a vector of Member Pointers then you should declare something like

vector<Member*> v;

and then get pointers to member objects and insert into it.
each value stored in a vector is an element of the vector. its upto you to decide what will be the element in your program.

But if there are different varible types then how do I store them in the same vector and how do I get each piece of data on a line of my text file to be stored in a vector, my text file goes (name, balance, expiry date)?

Then you need to create a structure and create a vector of that structure. which brings us back to the few first replies in this thread and the circle is complete....

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.