Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

Any suggestions?

#include<iostream>

using namespace std;

class node 
{       
        friend class linked;

        private:
                int data;

        public:
                node *link;
                node()
                {
                        data=0;
                        link=NULL;

                }


};
class linked
{
        public:
                node *start=NULL;
                node *ptr;
                node *temp=NULL;

                void append(int item);
                void traverse();

};

void linked::append(int item)
{       
        ptr = new node();
        ptr->data=item;
        if(start==NULL)
        {
                start=ptr;
                ptr->link=NULL;
                temp=start;


        }
        else
        {
                while(temp->link!=NULL)
                {
                     temp=temp->link;

                }
                temp->link=ptr;
                ptr->link=NULL;

         }       


}
void linked::traverse()
{       
        node *trav=start;
        while(trav!=NULL)
        {
                cout<<trav->data<<endl;
                trav=trav->link;
        }

}
int  main()
{
        int i;

        linked box;

        for(i=0;i<10;i++)
        {
           box.append(i);

        }

        box.traverse();      
}
share|improve this question

2 Answers 2

Starting at the top

//any suggestions

Yes, plenty. But don't add that to the code.

using namespace std;

Nearly every beginner includes this (and it's understandable because this is what they do in books).

In actual practice this is a bad idea. Never include this line in real code. The reason books include it is to save space (because each printed page costs extra). When writing real code, this will create bad habits that will bite you in the end so it is a habit you should break now.

see: Why is “using namespace std;” considered bad practice?

node class

protection level

There is no need to make the node class public. This is internal to your linked list so you should hide its implementation from other people by making it a private member class of the linked class.

style

class node 
{       
        friend class linked;

        private:
                int data;

        public:
                node *link;
                node()
                {
                        data=0;
                        link=NULL;

                }


};

Small note on vertical spacing: there is way too much here. It's a small class so it does not stand out. But if you do the same thing on a bigger class it becomes hard to read. Now don't take this to the extreme and completely eliminate vertical space-- break things up into logical units.

Since we are making this class a private member of linked there's no need for friendship; just make everything public.

But we can improve the code by having a constructor that takes the data value and the link (if you look at your code you could use both at the same time. When writing constructors, it's preferred to use the initializer list syntax to initialize members:

                node(int data, node* link)
                    : data(data)
                    , link(link)
                {}

You will notice here that everything is lower case. It is relatively common to define user-defined types with an initial capital letter, while objects have an initial lower case letter. This allows you to quickly see the difference between types and objects (an important distinction in C++).

So, following this style, your code should look like this:

class Linked
{
    struct Node
    {
        int    data;
        node*  next;
        Node(int data, Node* next)
            : data(data)
            , next(next)
        {}
    };

    ....

linked class

class linked
{
        public:
                node *start=NULL;
                node *ptr;
                node *temp=NULL;

                void append(int item);
                void traverse();

};

Not sure what all the pointers are for? OK start marks the start of the list (usually called head). But what are the others supposed to represent? If they are really just things you use a throw away then they can be local to the function methods and not part of the class.

You call new in your code. For every call to new there must be a corresponding call to delete. Otherwise you will leak memory (and resources). So the best place to do this is in the destructor.

Also you are missing a constructor. This means that all you member variables are likely to be undefined (this means they have random values in them. C++ (unlike Java and other languages at this level) does not do automatic initialization of variables.

see: Why aren't pointers initialized with NULL by default?

So lets add the constructor and destructor

class Linked
{
    struct Node
    {
        int    data;
        node*  next;
        Node(int data, Node* next)
            : data(data)
            , next(next)
        {}
    };

    Node*   head;

    Linked():
        head(nullptr)
    {}
    ~Linked()
    {
        Node* tmp;
        for(;head;head = tmp)
        {
            tmp = head->next;
            delete head;
        }
    }
    ...

If we look at append. You get the correct test for adding an element to an empty list. But it starts to go wrong in the second half where you are using a loop.

The member temp is not initialized. It is also public -- this means it could have been altered since last time you used it. So it is best to use a local variable that you initialize to head.

Note: you can also use the new constructor for node to reduce code in this function.

void Linked::append(int item)
{    
    if (head == nullptr)
    {
        head = new Node(item, nullptr);
    }
    else
    {
        // First find a pointer to the last item in the chain.
        Node* last = head;
        while(last->next)
        {
             last = last->next;
        }
        // Now just add the node.
        last->next = new Node(item, nullptr);


        // Notice this loop happens each time you add an element.
        // This makes adding an element relatively expensive.
        // To avoid this a lot of implementation keep two pointers
        // in the list a `head` and a `tail`. That way data elements
        // can be quickly added to the tail.

        // When you start adding things like delete. 
        // It becomes useful to know your previous element.
        // So it is also very common for the node object to have
        // two pointers `next` and `prev`
     }

Note: we always seem to place nullptr as the second argument. So we could potentially remove it. But if you try to implement an insert function then it becomes useful:

void Linked::insert_at_head(int value)
{
    head = new Node(item, head); // See very useful.
}

Not sure what your traverse is doing.

void linked::traverse()
{       
        node *trav=start;
        while(trav!=NULL)
        {
                cout<<trav->data<<endl;
                trav=trav->link;
        }

}

I would just name that print; traverse implies going through a tree.

One way to improve the functionality and modularity of traverse would be for it to take a parameter of a function that should be applied to all the nodes (but I think that is a bit beyond this exercise). So a couple of things here:

  1. Rename to print
  2. Mark the function as const
    It does not alter the state of the object. So it is safe to call on a const version of the object.
  3. Printing in C++ is usally done by operator<<
    so create one of these that calls your print method.

Like this:

void Linked::print(std::ostream& str = std::cout) const // Note const
{       
        node *trav=start;
        while(trav!=NULL)
        {
                // Added space here so yo can see the <<
                // and what they are applied too.
                //
                // Also prefer "\n" to std::endl (especially in here)
                // The std::endl forces a buffer flush. Doing this
                // after printing every integer becomes very inefficient.
                str << trav->data << "\n";
                trav=trav->link;
        }
}
std::ostream& operator<<(std::ostream& str, Linked const& data)
{
     data.print(str);
     return str;
}

I think we have now covered enough in one review. I would have another bash at this example. Once you have done that post the code for review again and we can go into some other details.

share|improve this answer

Structure Requirements

Just in a general form, nearly all data structures require these three methods:

  1. Insert
  2. Search/Find
  3. Delete

So far, you have insertion somewhat, but you are missing some key points:

  • You are currently only appending to the tail of the structure. What happens when you want to append at the head of the structure? Or even in between two nodes? You need to define the cases that would allow such an insertion to take place.

There is a real lack of functionality to delete data that is no longer needed. When considering deletion in linked list, it's important to note the following cases that can take place.

  • Deleting the head (first) node
  • Deleting a node between two nodes
  • Deleting the tail (last) node

Each one of these cases requires different logic.


For Search/Find, you have the hard part down; traversing the list and checking for null values. But you need to look for specific data at some point. Search is pretty simple and it only requires a couple additional lines to your void linked::traverse() method.


Naming Your Methods


Most of the time you are fine, but it's important to remember that you need to give names that represent what the methods/class/variable does. My main example in this instance is your void linked::traverse() method.

While the method does traverse the list, it is actually doing more than just going through the list and looking for null values; this method is actively displaying the data. Therefore, I would change this method definition to the following for clarity: void linked::printAll() or something along those lines.

I would then make a separate method that is specifically responsible for traversing the list, nothing else. By keeping the responsibilities of each method separate, you'll cut down on debugging time greatly as you extrapolate the structure.


Closing Pointers and Cleanup

This is a big one! I currently see no attempt to delete pointers, there are also no destructors. Any time you are using an Object-Oriented approach, especially in something such as a data structure, it is imperative that you make sure there are no memory leaks. What happens when this structure gets really big and you have no way to cleanup?

This is the kind of thing that will ruin your data structure as you do some serious testing on it.


Object-Oriented Design


When you are implementing your linked class, you should consider which of the following you will need.

  • Default constructor (if you wish to instantiate an object with default values)
  • Value-accepting constructor (nearly always)
  • Destructor (If using new keyword in your code, you need to delete whatever it is you instantiated at the end of use/program)
  • OPTIONAL A copy constructor (if using a destructor, this must be present)

EDIT:
As mentioned by Corbin in the comments, you should consider the rule of 3/5/0 when designing a class. Rule of Three C++
The Rule of Three states that if you are implementing one or more of the following methods, you should explicitly define all three.

  1. Copy Constructor MyObj obj1(obj2);
  2. Copy Assignment operator obj1 = obj2; or... MyObj obj1 = obj2;
  3. Destructor ~linked() { //Cleanup }
    The Rule of 5, and Rule of 0 expand on this and more can be found in the link provided above.
share|improve this answer
1  
Your "Anytime you design a calss, you need to create a ..." advice applies in this situation, but it's wrong when presented as general rules. Destructors are only required if you're managing some resource (which in modern C++ you very rarely should be) or if you are opening your class to be extended. As for a copy constructor: if you have a destructor you must have a copy constructor (even if it's just a private or deleted one). It might be worth throwing something about the rules of 3, 5 and 0 in there. –  Corbin yesterday
    
Also, your setter/getter seem to have gone Java for a second? And getData should be const. And the idiomatic way to do this for a container in C++ would be to return a reference in the non-const version and a const reference in the const version (although blah.data() = 5; does look pretty weird, so maybe I'm just trying to over apply iterator patterns). –  Corbin yesterday
1  
Getters/Setters break encapsulation. You are exposing the internal types of your class to the outside world. Methods should be actions that apply to your object. Getters/Setters are popular in languages like Java/C# because they can be used by tooling infrastructures like serializers. Of course there are always exceptions to the rule (that make it a rule). Containers expose there content but in C++ we usually do it via other techniques. Like iterators operator[] etc. –  Loki Astari yesterday
    
@Corbin Yeah, I swicthed to java w/o thinking about it :P I'll update it to be C++ once I'm back @ a PC. I'll update my answer with the suggestions you posted also, thanks for those btw. –  Evan Bechtol yesterday
    
@LokiAstari Thanks for pointing this out, I primarily use Java and over-looked this fact in C++. I'll update shortly! –  Evan Bechtol yesterday

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.