tags:

views:

63

answers:

2

I think this has happened to me before. This isA3.txt:

%INSERT
MARK 29 
DAVID 21
JOHN 44
JOHN 51
LARRY 39
MARK 21
DAVID 18
JOHN 28
MARK 35
DONALD 41
PHIL 26

Even though I use sourcefile >> reader at the end of the loop, the program keeps outputting "reader: MARK", meaning the sourcefile >> reader; statement isn't working (i.e., it keeps getting the same input over and over again, or it's not getting any input).

#include <iostream>
#include <fstream>
#include <string>

using namespace std;  

struct data
{
 string name;
 int id;
 data* link;
};

data* start;
data* node;
ifstream sourcefile;

int main()
{
 data* start = new data;

 start -> link = NULL;

 string input;
 string reader;
 sourcefile.open("A3.txt");

 bool firstnode = true;

 sourcefile >> input;

 node = start;

 cout << "Recieved command: " << input << endl;

 if(input == "%INSERT")
 {
  // unlike the other ones, this function keeps reading until it hits another command
  sourcefile >> reader;

  cout << "Insert name: " << reader << endl;


  // iterates through the link list until it hits the final node
  while(node -> link != NULL)
    node = node -> link;


  while(reader[0] != '%')
  {
   if(firstnode)
    start -> link = new data;
   else
    node -> link = new data;


   sourcefile >> node -> name;
   sourcefile >> node -> id;
   node -> link = NULL;

   sourcefile >> reader;
   cout << "reader: " << reader << endl;
  }
 }
 else
  return 0;

}

Also... offtopic. The compiler said that switch statements can't be used with strings, is that really true, or was I doing something else wrong?

+2  A: 

sourcefile >> node -> id; fails, and after that none of the input operations from sourcefile succeed, as failbit becomes set in the sourcefile stream. sourcefile >> node -> id; fails because it tries to read an integer but encounters "DAVID" in the stream. That happens because sourcefile >> reader; consumes "MARK", sourcefile >> node -> name; consumes "29", so sourcefile >> node -> id; is then left with "DAVID". Try replacing sourcefile >> node -> name; with node -> name = reader.

And yes, you can't use strings in switch, only integral and enumeration expressions.

On another offtopic note, you don't seem to release the memory allocated for nodes (easy solution: just use std::list).

EDIT: Here's how your program might look like when if you used std::list:

#include <iostream>
#include <fstream>
#include <string>
#include <list>

using namespace std;  

struct data
{
 string name;
 int id;
};

ifstream sourcefile;

int main()
{
 list< data > datalist;

 string input;
 string reader;
 sourcefile.open("A3.txt");

 sourcefile >> input;

 cout << "Received command: " << input << endl;

 if(input == "%INSERT")
 {
  // unlike the other ones, this function keeps reading until it hits another command
  sourcefile >> reader;

  cout << "Insert name: " << reader << endl;

  while(reader[0] != '%')
  {
   data d;
   d.name = reader;
   sourcefile >> d.id;
   datalist.push_back( d );

   sourcefile >> reader;
   cout << "reader: " << reader << endl;
  }
 }
}
usta
thanks! I didn't even think about the memory allocation, I could just use delete to get rid of the nodes after I'm done with them, right? As for list... I never learned how to use lists. We skipped over it in class, although since I keep seeing it come up outside of class, I should probably look it up. Does it make this dynamic link list scenario easier?
jwaffe
@jwaffe: For learning purposes delete-ing the nodes at the end would be good enough, but not for production code. That's because if some exception is thrown before execution reaches delete operations, your memory will be leaked and appropriate destructors won't be called. Look up `RAII` in Wikipedia, and use that idiom in all your C++ code.
usta
@jwaffe: Avoid the need to explicitly call `delete`. Use either standard containers that'll do memory management for you (look at my answer's edit to see how much easier it all becomes when `std::list` is used), or hold your dynamically allocated objects in smart pointers.
usta
That would be my method of choice, I'll keep that in mind for later. My book encourages readers to use the author's own (wacky and poorly explained) version of list... I wonder why they don't just suggest std::list? The book was made in 1999, maybe that was before lists were implemented in the standard library... but then again they also go out of their way to redefine stacks and queues. Our teacher recommends we do the same.... for practice, possibly? In any case, I think I'll try it using list, I read the assignment again and he didn't say not to use STL this time.
jwaffe
@jwaffe: Possibly, but the teacher should also note if you're going to do it, it should be done right. And then it shouldn't be done at all. :) But you definitely want to *split* it as much as you can.
GMan
@jwaffe: Please, ignore what that book says then. The book authors either weren't aware of standard containers at that time (BTW they were standard by that time for at least 1 year already), in which case that book isn't worth reading these times, or the authors knew about them but insisted on teaching using their owns instead, in which case the best you can do would be to stay away from such book authors :) and like-minded teachers too! LOL
usta
+2  A: 

Right now your code does too much. Programs solve a collection of sub-problems in an attempt to solve a larger problem. This leads to the Single Responsibility Principle.

What that means is that one object (class, function, etc.) should solve one problem only. But right now that's not happening. For example, main trivially does more than one thing: it manages nodes for the list (incorrectly, too! Nothing is ever deleted!), and gets input from the user. This is too much.

Rather, split things up. You should make a list class that manages nodes, and then main should use it. Note the difference here: main no longer solves that problem, it utilizes something that does.

So with this in mind, it quickly follows the more we split things up, the easier it is to be correct, fix, and maintain. The act of taking code and splitting it up is "refactoring". Let's do that.

First, we need a linked list to use. Normally we have std::vector (note: linked lists are generally the worse container there is) or std::list, but since your teacher is dumbmisguided, he's making you write your own. Your assignment should be either write a list container or use a list container and read input, not both. (Again, in the real world we split things up; why teach people to mix them?)

You already have the basics down, it just needs to be encapsulated. (If you don't know classes yet, let me know and I'll expand there too; while we're at it, if you don't already you might want to get a good book to teach yourself what your teacher isn't):

// normally this should be a template so it can store anything,
// and yadda yadda (more features), but let's just make it basic
// this data class is what the linked list holds
struct data
{
    std::string name;
    int id;
};

class linked_list
{
public:
    linked_list() :
    mHead(0)
    {}

    // the magic: the destructor will always run 
    // on objects that aren't dynamically allocated,
    // so we're guaranteed our resources will be
    // released properly
    ~linked_list()
    {
        // walk through the list, free each node
        while (mHead)
        {
            node* toDelete = mHead; // store current head
            mHead = mHead->next; // move to next node

            delete toDelete; // delete old head
        }
    }

    void push_back(const data& pData)
    {
        // allocate the new node
        node* newNode = new node(pData, mHead); 

        // insert
        mHead = newNode;
    }

    data pop_back()
    {
        // remove
        node* oldNode = mHead;
        mHead = mHead->next;

        // deallocate
        data d = oldNode->data;
        delete oldNode;
        return d;

        /*
        the above is *not* safe. if copying the data throws
        an exception, we will leak the node. better would be
        to use auto_ptr like this:

        // now the node will be deleted when the function ends, always
        std::auto_ptr<node> n(oldNode);

        // copy and return, or copy and throw; either way is safe
        return n->data;

        but who knows if your <strike>dumb</strike>misguided
        would allow it. so for now, make it unsafe. i doubt
        he'd notice anyway.
        */
    }

private:
    // any class that manages memory (i.e., has a destructor) also needs to
    // properly handle copying and assignment.
    // this is known as The Rule of Three; for now we just make the class
    // noncopyable, so we don't deal with those issues.
    linked_list(const linked_list&); // private and not defined means it
    linked_list& operator=(const linked_list&); // cannot be copied or assigned

    struct node
    {
        // for convenience, give it a constructor
        node(const data& pData, node* pNext) :
        d(pData),
        next(pNext)
        {}

        data d; // data we store
        node* next; // and the next node
    };

    node* mHead; // head of list
};

Now you have a list to use. main will no longer be troubled with such things:

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

using namespace std; // should generally be avoided

// your linked_list code

int main()
{
    // don't declare variables until you need them,
    // and avoid globals. (the previous rule helps)
    ifstream sourcefile("A3.txt");

    // check that it opened
    if (!sourceFile.is_open())
    {
        cerr << "could not open file" << endl;

        // EXIT_FAILURE is inside <cstdlib>
        return EXIT_FAILURE;
    }

    string input;
    sourcefile >> input;

    cout << "Received command: " << input << endl;

    linked_list datalist;
    if (input == "%INSERT")
    {
        string reader;
        sourcefile >> reader;

        cout << "Insert name: " << reader << endl;

        while (reader[0] != '%')
        {
            data d;
            d.name = reader;
            sourcefile >> d.id;

            datalist.push_back(d);

            sourcefile >> reader;
            cout << "reader: " << reader << endl;
        }
    }
}

Note how much easier it is to read. You no longer manage a list, but simply use it. And the list manages itself, so you never leak anything.

This is the route you'll want to take: wrap things into working objects that solve one problem correctly, and use them together.

GMan
I never knew structs could have constructors. A couple lines are confusing me. Your constructor for node, specifically. You have a colon in there, is that inheritance? Functions can use inheritance? Also, what's the comma for? I noticed that next is a reserved word, did you intend that or was it supposed to be an identifier?
jwaffe
@jwaffe: Yup, structs and classes are exactly the same thing (both are "class types"), except structs default to public (with regards to inheritance and member access) and classes default to private. The colon is an "initialization list" (search term), and is how you initialize members of a class type. `next` is not a keyword or reserved identifier in C++, that's just SO's syntax highlighter.
GMan
Thanks for your help, I'm going to get one of the books in the recommendation section from the library. The book we're using in class just doesn't work anymore, and it's hard to read anyway. In the meantime I can use an ancient Borland C++ reference to help me with my classes and constructors (that I didn't learn very properly to begin with)
jwaffe
@jwaffe: No problem, sounds good. Don't be afraid to ask your questions here. :)
GMan
I just noticed you told me to not use "using namespace std;", why?
jwaffe
@jwaffe: It pollutes your namespace. In functions it's fine, but the more global the worse. File scope is generally okay, though most would just put `std::` before things anyway. *Never* put it in a header. Namespaces exist for a reason, and you completely circumvent it when you do that.
GMan
I noticed you put mHead(0) in the definition of linklist (I'm still working on this), are you setting mHead to the address of zero, or are you running mHead's constructor with a parameter of zero? Also (offtopic), is there any way to send messages to users on this site?
jwaffe
@jwaffe: Not privately, no. Only @name to notify them. That's the initializer list, and there things are *directly initialized*. That means the value I put in the parenthesis is the value it takes on. (You can do `void* p(0);`, for example, to directly initialize `p` to 0.) And yeah, 0 is the *null pointer constant*, which makes the pointer value null. Note that built-in types do not have constructors. (The syntax for direct initialization is the same thing you use to call constructors, though.)
GMan
@GMan - thanks! I was wondering, is there any other way I could reach you? I finished the assignment, but it's not very efficient, and it took me a very long time to write it. I don't want to post the code (for obvious reasons), but I would like to hear your opinion on it so maybe next time I'll be able to do it quicker/better.
jwaffe
@jwaffe: Hm, well I'm in chat a lot, you could upload it and the people who are in there could check it out. But "How do I improve my code?" is a very good question, if you ask it correctly. Be sure to include *why* you think the code is not efficient, and that you want to make it more efficient and produce quicker.
GMan