[Solved] Inputing strings in c++ using class is not working properly


  • As Bo Persson said, your program does not provide any memory for name and designation. You declare pointers like name in your class alright, but they do not point to anything. You could just declare e.g. name as char name[100]; and hope that 100 is enough (and, in production code, add checks to ensure it isn’t exceeded).

    But in C++ there is the string class which frees you of many worries. In particular, it makes input of arbitrarily long strings easy. If the string can’t have whitespace in it, a simple string s; cin >> s; reads a “word” from the input into the string. If it can have whitespace, you need some way to tell where it starts and ends. Databases, Excel etc. often use quotes to surround a string. If it cannot contain newlines, ensuring that it is on a line of its own is sufficient and obviates the need for parsing. That’s what we’ll do here.

  • The second issue you faced is what’s technically called “serialization”. You want to store (“persist”) your class on disk and later (much later, perhaps) re-read it into a new instance. Nathan Oliver pointed you to a resource which discusses serialization. For a simple class like employee with a limited number of simple data members we can simply roll our own ad-hoc serialization: We write everything to disk with operator<<(), and read everything back with operator>>().

  • The main thing to consider is that strings may have whitespace, so that we’ll put them on a line of their own.

  • An addition is that in order to be more robust when reading from a file we start each employee with a start marker (header in the code below). This way reading an employee will work from any position in the file. Also, if later employees should have more fields we can still read our basic employee data and just skip the additional fields before we read the next employee in a sequence of employees on disk.

  • streams are closed automatically when they are destroyed at the end of their scope; we use block scope for that (check the additional {} in the code).

  • A plain float is not precise enough for higher salaries (it only has about 7 decimal digits, so that for salaries > 167772.16 (if I can believe Wikipedia) in whatever currency the pennies start to fall off the cliff). I use long double and make sure to not lose precision when converting it to text.

  • You didn’t compare reals but I did in order to check whether I read the employee back correctly. Care must be taken there. I wiggle out of the gory details by comparing half pennies which should be appropriate for money.

Here is the whole program. (Compared with my previous version I have simplified the (de)serialization, in particular I have eliminated the useless tags.)

It would be wise to perform error checking after each read/write in order to make sure it succeeded; remember, a stream coverts to bool, so a simple if(!os) { cerr << "oops" << endl; /* exit? */} is enough; but I didn’t want to distract from the actual program too much.

#include <iostream>
#include <string>
#include <sstream>
#include <fstream>
#include <cfloat>   // for LDBL_DIG in ostream precision.
#include <cstdlib>  // for exit()
using namespace std;


/** A simple class holding employee information */
class employee
{   
    public:
 
        /** This header starts each 
            "serialization" of an employee on a line
            of its own. */
        static constexpr const char *header = "--- Employee ---";
        
        // use C++ std::string
        string name;

        int age;

        // use C++ std::string
        string designation;

        // Be as precise as possible,
        // for later uses like salary increases
        // by 4.5% or so :-)
        // The salary is in units like USD or EUR.
        // The fraction part is pennies, and fractions of them.
        long double salary;

    public:
        void readdata(istream &is);
        void writedata(ostream &os);
        void display();

        bool operator==(employee &rhs)
        {   return 
                name == rhs.name 
            &&  age == rhs.age 
            &&  designation == rhs.designation 
            
            // Do not compare floats directly.
            // We compare pannies, leaving slack for rounding.
            // If two salaries round to the same penny value,  
            // i.e. 0.01, they are equal for us.
            // (This may not be correct, for an accounting app,
            // but will do here.)
            &&  salary - rhs.salary < 0.005 
            &&  rhs.salary - salary < 0.005;
        }
};

/** Write a header and then 
    each data member in declaration order, converted to text,
    to the given stream. The header is used to find the start
    of the next employee; that way we can have comments or other
    information in the file between employees.
    The conversion is left to operator<<.
    Each member is written to a line of its own, so that we
    can store whitespace in them if applicable.
    The result is intended to be readable by @readdata().
*/
void employee::writedata(ostream &os)
{           
    os.precision(LDBL_DIG); // do not round the long double when printing
    
    // make sure to start on a new line....
    os                  << endl
        // ... write the header on a single line ...
        << header       << endl
        // ... and then the data members.
        << name         << endl
        << age          << endl
        << designation  << endl
        << salary       << endl;
}

/** 
    Read an amployee back which was written with @writedata().
    We first skip lines until we hit a header line,
    because that's how an employee record starts.
    Then we read normal data mambers with operator>>. 
    (Strictly spoken, they do not have to be on lines
    of thier own.)
    Strings are always on a line of their own,
    so we remove a newline first.
*/
void employee::readdata(istream &is)
{
    string buf;

    
    while(getline(is, buf)) // stream converts to bool; true is "ok"
    {
        if( buf == header) break; // wait for start of employee
    }
    if( buf != header ) 
    { 
        cerr << "Error: Didn't find employee" << endl;
        return;
    }
    
    getline(is, name);  // eats newline, too
    is >> age;          // does not eat newline:
    
    // therefore skip all up to and including the next newline
    is.ignore(1000, '\n');
    
    // line on its own, skips newline
    getline(is, designation);
    
    is >> salary;
}

int main()
{
    const char *const fname = "emp.txt";
    
    employee empa;
    empa.name = "Peter A. Schneider";
    empa.age = 42;
    empa.designation = "Bicycle Repair Man";
    empa.salary = 12345.67;

    employee empb;
    empb.name = "Peter B. Schneider";
    empb.age = 43;
    empb.designation = "Bicycle Repair Woman";
    empb.salary = 123456.78;

    {
        ofstream os(fname);
        if(!os) 
        { 
            cerr << "Couldn't open " 
            << fname << " for writing, aborting" << endl;
            exit(1);
        }

        empa.writedata(os);
        cout << "Employee dump:" << endl;
        empa.writedata(cout);

        // insert a few funny non-employee lines
        os << endl << "djasdlköjsdj" << endl << endl;
        
        empb.writedata(os);
        cout << "Employee dump:" << endl;
        empb.writedata(cout);
    }

    // show the file contents
    {
        ifstream is(fname);
        if(!is) 
        { 
            cerr << "Couldn't open " 
                << fname << " for reading, aborting" << endl;
            exit(1);
        }

        string line;
        cout << "-------------- File: -------------" << endl;
        while(getline(is, line)) cout << line << endl;
        cout << "---------------End file ----------" << endl;
    }
    /////////////////////////////////////////////////////////

    {
        ifstream is(fname); // read from the file "emp.txt" just written
        if(!is) 
        { 
            cerr << "Couldn't open " 
                << fname << " for reading, aborting" << endl;
            exit(1);
        }

        
        {
            employee emp2;  // new employee, sure to be empty
            cout << endl << "Re-reading an employee..." << endl;
            emp2.readdata(is);

            cout << endl << "Re-read employee dump:" << endl;
            emp2.writedata(cout);

            cout << "Original and written/read employee are " 
                << (empa == emp2 ? "" : "NOT ") << "equal" << endl;
        }
    
        {   
            employee emp2;  // new employee, sure to be empty
            
            // now read next employee from same stream.
            // readdata() should skip garbage until the header is found.
            cout << endl << "Re-reading an employee..." << endl;
            emp2.readdata(is);

            cout << endl << "Re-read employee dump:" << endl;
            emp2.writedata(cout);

            cout << "Original and written/read employee are " 
                << (empb == emp2 ? "" : "NOT ") << "equal" << endl;
        }
    }
}

Sample session:

Employee dump:

--- Employee ---
Peter A. Schneider
42
Bicycle Repair Man
12345.6700000000001
Employee dump:

--- Employee ---
Peter B. Schneider
43
Bicycle Repair Woman
123456.779999999999
-------------- File: -------------

--- Employee ---
Peter A. Schneider
42
Bicycle Repair Man
12345.6700000000001

djasdlköjsdj


--- Employee ---
Peter B. Schneider
43
Bicycle Repair Woman
123456.779999999999
---------------End file ----------

Re-reading an employee...

Re-read employee dump:

--- Employee ---
Peter A. Schneider
42
Bicycle Repair Man
12345.6700000000001
Original and written/read employee are equal

Re-reading an employee...

Re-read employee dump:

--- Employee ---
Peter B. Schneider
43
Bicycle Repair Woman
123456.779999999999
Original and written/read employee are equal

solved Inputing strings in c++ using class is not working properly