2

First off, if this problem seems incredibly easy to you, I want to in advance apologize but I am only a beginner.

I have been stuck now for about a week with this problem and it is getting ridiculous since it shouldn't be that hard, even for a complete beginner like me.

I am writing a program which reads a bunch of information regarding receipts from a text file, like name, sum, date etc. and then prints it out to the screen. Simple enough, right? Well I started with using static arrays in my two classes Transaction and TransactionsList and it was working fine, I was printing the contents of the file to the screen just fine one line after the other. Now I need to do this using dynamic arrays.

Each line in the text file contains a date, type, name, sum, number of friends and name of those friends which should be read an stored as a Transaction class object inside the dynamic array trans. This is what I am having trouble understanding no matter how much theory and googling I do on the subject. Where should I use an overloaded assigment operator, where a copy constructor and how do I call them properly? I have read up on these concepts but I can't use them in my program still. These are questions just flying around in my head right now.

I have changed the arrays friends and trans to be declared as pointers which I understand is correct. I then want to allocate memory for the arrays with "new", but here I am starting to get unsure just where I allocate with new, inside the contructors of their classes or inside the functions where they are needed? I realize vectors is the answer to alot of these problems but I should tell you that I have not gotten into vectors yet, so I am trying to solve this problem without vectors. I realize this may be be a bit backwards, but I should be able to build my dynamically allocated array of objects and print it out without vectors I think. I have heard they are more practical but for now I have to understand this assignment without the concept of vectors. I have read up on difference between shallow copies and deep copies as well and I get the theory, but I just can't implement it somehow. (I am probably retarded I know). This is what I have got so far:

#include <iostream>
#include <string>
#include <fstream>
#include <cstdlib>
#include <iomanip>

using namespace std;

class Transaction
 {
  private:
   string date;
   string type;
   string name;
   double sum;
   int nr_friends;
   string *friends;

  public:
   Transaction();
   ~Transaction();
   Transaction &operator = ( const Transaction &t );
   string get_name();
   int get_no_friends();
   double get_sum();
   bool readOneTrans( istream &is );
   void writeOneTrans( ostream &os );
 };


class TransactionsList
 {
 private:
   Transaction *trans;
   int no_Trans;

 public:
   TransactionsList();
   ~TransactionsList();
   void read( istream & is );
   void print( ostream & os );
   void add( Transaction & t );

 };


int main()
{
    ifstream inFile("test.txt");
    Transaction t;
    TransactionsList tl;

   // t.readOneTrans(inFile);  // reading just one line works fine (when uncommented)
   // t.writeOneTrans(cout);  // printing works too just fine


    //tl.read(inFile); // here I want to read all contents of file

    //tl.print(cout); // and here print out them to the screen


return 0;

}


Transaction::Transaction()
    {
        date = "000000";
        type = "transp";
        name = "default";
        sum = 0.0;
        nr_friends = 0;
        friends = NULL;
    }

Transaction::~Transaction()
{
    delete [] friends;
}

Transaction &Transaction::operator = ( const Transaction &t )
{

        if ( this != &t )
        {
            delete[] friends;
            date = t.date;
            type = t.type;
            name = t.name;
            sum = t.sum;
            nr_friends = t.nr_friends;
            friends = new string[nr_friends];

            for ( int i = 0; i < nr_friends; i++ )
            {
                friends[i] = t.friends[i];
        }
    }


    return *this;
}

string Transaction::get_name()
    {
    return name;
}

double Transaction::get_sum()
    {
    return sum;
}

int Transaction::get_no_friends()
    {
        return nr_friends;
    }

bool Transaction::readOneTrans( istream &is )
    {
        is >> date >> type >> name >> sum >> nr_friends;

        friends = new string[nr_friends];

        for (int i = 0; i < nr_friends; i++)
            {
                is >> friends[i];
            }

        return is;
        return !is.eof();
    }

void Transaction::writeOneTrans( ostream &os )
    {
        os << left << setw(10) << date <<
        setw(10) << type << setw(10) << name
        << setw(10) << sum << setw(10)
        << nr_friends;

        for (int i = 0; i < nr_friends; i++)
            {
                os << left << setw(8) << friends[i];
            }

        os << endl;
    }



TransactionsList::TransactionsList()
{
        no_Trans = 1;
        trans = new Transaction[no_Trans];

}

TransactionsList::~TransactionsList()
{
    delete [] trans;
}

void TransactionsList::read( istream & is )
{
            Transaction t;

            while ( t.readOneTrans( is ))
                {

                    add( t );
                }

}

void TransactionsList::print( ostream & os )
{
    Transaction t;

    for (int i = 0; i < no_Trans; i++)
        {
            t = trans[i];
            t.writeOneTrans( os );
        }

    if (os == cout)
        {
            os << "\nNumber of transactions: " << no_Trans << endl;
        }

}

void TransactionsList::add( Transaction & t )
{
   // each time I read a line from the file it is passed in as object t here

   // here I want to add this object t to the dynamic array trans somehow
   // and keep building the array with a new class object every time

    // Probably by overloading assignment operator somehow but how?

        trans[no_Trans] = t;
        no_Trans++;

 // i have no idea what to put here to make it work...

}

So as you can see, what I want to do is continually build up the dynamic array trans with different objects of the class Transaction, each instance representing a different line in the text file I am reading from so that I can print out all the lines in the file to the screen in the end. The output lines should look like this:

  011216    food      John       300       2        Nathan   Julia

To do this now dynamically, I realize I must copy the contents of object t that is passed in in the method "add" and add it to the array trans and somehow without losing the data of the earlier t:s which are representing the previous text lines. This was easy for me to do while the arrays where static ones, as I just assigned the next element in the array trans to be equal to the current object t (inside the add function). This is how my add function looked with static arrays:

 void TransactionsList::add( Transaction & t )
{
        trans[no_Trans] = t;
        no_Trans++;
}

Obviously this doesn't work when you are working with dynamically allocated memory. I read some theory on this and I understand one cannot change the size of the array while it is running so the array actually has to be deleted and then allocated as a larger array and copy over the old contents using a deep copy, which doesn't just copy the memory address for the dynamic array but makes a new array with the olds content.

As you can see, I have read alot of theory but don't really understand it... Can anyone help? I would be immensely thankful as I have not learned anything in a week and this is really killing me right now. I need to make progress now!

user5846939
  • 415
  • 4
  • 11
  • 6
    Simply use a `std::vector`. – πάντα ῥεῖ Jan 27 '16 at 13:23
  • Sorry but we have not covered vectors yet. I realize they are better to use in these situations but I am not allowed to use them here unfortunately. Is it possible somehoe without vectors? – user5846939 Jan 27 '16 at 13:30
  • The sentence beginning "I read some theory on this" describes exactly what you need to do, except the order should be 1. Create a new array; 2. Copy the old data to the new array; 3. Delete the old array 4. Point your pointer to the new array. 5. Add your new element as before. (I think you're assuming that it's harder than it actually is.) – molbdnilo Jan 27 '16 at 13:30

3 Answers3

1

Some hints about the container:

  1. Don't use using namespace std; (why?)

  2. An unsigned integral size in c++ is usually represented as std::size_t from <cstddef>.

  3. Get familiar with rule of three / rule of three/four/five.

  4. A quite useful idiom that is usually applied to such classes is: 'Resource Acquisition Is Initialization (RAII)'.

Bottom line:

  1. When managing resources we usually need to have

    • a destructor
    • a copy constructor
    • a move constructor
    • a copy assignment operator
    • a move assignment operator
  2. Resource aquisition should only happen in the constructor.

    Functions such as add should not perform seperate resource acquisition but create a temporary of appropriate size and swap/move contents.

Community
  • 1
  • 1
Pixelchemist
  • 24,090
  • 7
  • 47
  • 71
0

The issue of constructing a dynamically-allocated array is completely separate from the issue of constructing the objects themselves.

class TransactionList {

    Transaction *trans;
    size_t trans_size;
    size_t no_Trans;
public:
    TransactionList(size_t initial_size)
         : trans(new Transaction[initial_size]),
           trans_size(initial_size),
           no_Trans(0)
    {
    }

    ~TransactionList()
    {
          delete[] trans;
    }

    // ...
};

That's it. There's nothing different about your existing add() method. It still works exactly the same way, because of the fact that an array is really just a pointer to the first element in the array, which is still the case here.

But you do need to figure out what to do when no_Trans reaches the actual allocated trans_size. That's going to be your homework assignment.

What you probably want to do, though, is to change this to an array of Transaction * objects, and also dynamically allocate each Transaction when it's added to the array. That will require additional work.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
0

(This answer requires no extra knowledge, and needs only a little bit change of your code)

Things get weird in the constructor:

no_Trans = 1;
trans = new Transaction[no_Trans];

People usually leave some space for future elements to add:

max_Trans = 100;
no_Trans = 0;
trans = new Transaction[max_Trans];

And in add()

if (no_Trans >= max_Trans) { // no more space?
    // make a new array that is as twice big as the old one
    max_Trans = 2 * max_Trans;
    Transaction new_trans = new Transaction[max_Trans];

    // copy elements to the new array
    for (int i = 0; i < no_Trans; i++)
        new_trans[i] = trans[i]; 

    // delete the old one and start to use the new one
    delete[] trans;
    trans = new_trans;
}

trans[no_Trans] = t;
no_Trans++;

Of course max_Trans can also be 1, and make it grow as 1, 2, 3, 4... But that requires new on each add operation, which is inefficient.

rolevax
  • 1,670
  • 1
  • 14
  • 21
  • Thanks so much for the help. I managed to print it out and load the array finally, however it shows one transaction too much and prints out an empty object at the end, basically it is creating one too many objects and adding it last in line before printing out. Any idea as to why this is? – user5846939 Jan 27 '16 at 18:41
  • You may check if `no_Trans` matches the actual number of instances in the data file. If it doesn't, there might be a bug in the reading process. (If you are using the memory allocation method in this answer, it is not just one more object that is created. The whole array is fulled of "empty" objects created by Transaction's default constructor when you allocate the array. Thus `no_Trans` is used to mark which those "empty" objects are overridden by the actual data.) – rolevax Jan 29 '16 at 01:43