简体   繁体   中英

Making the leap from Java to C++

As the topic says I'm very new to c++, but I have some experience with java. To start learning c++ I had the (not very original) idea of making a simple command line calculator. What I'm trying to do is store the numbers and operators in a binary tree.

#include <iostream>
using namespace std;

class Node
{
  bool leaf;
  double num;
  char oper;
  Node* pLNode;
  Node* pRNode;

public:

  Node(double n)
  {
    num = n;
    leaf = true;
    pLNode = 0;
    pRNode = 0;
  }

  Node(char o, Node lNode, Node rNode)
  {
    oper = o;
    pLNode = &lNode;
    pRNode = &rNode;
    leaf = false;
  }

  bool isLeaf()
  {
    return leaf;
  }

  double getNumber()
  {
    return num;
  }

  char getOperator()
  {
    return oper;
  }

  Node* getLeftNodePointer()
  {
    return pLNode;
  }

  Node* getRightNodePointer()
  {
    return pRNode;
  }

  //debug function
  void dump()
  {
    cout << endl << "**** Node Dump ****" << endl;
    cout << "oper: " << oper << endl;
    cout << "num: " << num << endl;
    cout << "leaf: " << leaf << endl;
    cout << "*******************" << endl << endl;
  }

};

class CalcTree
{
  Node* pRootNode;
  Node* pCurrentNode;
public:

  Node* getRootNodePointer()
  {
    return pRootNode;
  }

  Node* getCurrentNodePointer()
  {
    return pCurrentNode;
  }

  void setRootNode(Node node)
  {
    pRootNode = &node;
  }

  void setCurrentNode(Node node)
  {
    pCurrentNode = &node;
  }

  double calculateTree()
  {
    return calculateTree(pRootNode);
  }

private:

  double calculateTree(Node* nodePointer)
  {
    if(nodePointer->isLeaf())
    {
      return nodePointer->getNumber();
    }
    else
    {
      Node* leftNodePointer = nodePointer->getLeftNodePointer();
      Node* rightNodePointer = nodePointer->getRightNodePointer();
      char oper = nodePointer->getOperator();

      if(oper == '+')
      {
    return calculateTree(leftNodePointer) + calculateTree(rightNodePointer);
      }
      else if(oper == '-')
      {
    return calculateTree(leftNodePointer) - calculateTree(rightNodePointer);
      } 
      else if(oper == '*')
      {
    return calculateTree(leftNodePointer) * calculateTree(rightNodePointer);
      }
      else if(oper == '/')
      {
    return calculateTree(leftNodePointer) / calculateTree(rightNodePointer);
      }
    }
  }
};

int main(int argc, char* argv[])
{
  CalcTree tree;
  tree.setRootNode(Node('+', Node(1), Node(534)));
  cout << tree.calculateTree() << endl;
  return 0;
}

I've got a couple of questions about this code:

  1. This compiles but does not do what's intended. It seems that after tree.setRootNode(Node('+', Node(1), Node(534))); in main, the rightnode is initialized properly but the leftnode isn't. Compiling and running this prints out 534 for me (gcc, freebsd). What is wrong here?

  2. It seems in c++ people prefer to define members of a class outside the class, like

    class A { public: void member(); };

    A :: member(){std::cout << "Hello world" << std::endl;}

    why is that?

  3. I'd very much like some pointers on c++ conventions (naming, indenting etc.)

  4. I'm used to coding java with eclipse. Atm I'm using emacs for learning c++. Can someone advise me on a good (free) c++ ide, or should I stfu and stick with emacs like a real man? :)

  • First, I can only recommend to have a look at the book "Accelerated C++". It will jump-start you into STL , and C++ style , and can save you a year of bad experiences. (There is quite an amount of extraordinary well written literature on C++ out there, if you decide to go deeper).

  • C++ does not use automatic memory management on the heap. You are storing pointers to temporaries. Your program is incorrect as it tries to access destructed objects. Like it or not, you'll have to learn on object lifetime first. You can simplify a good part of this in simple cases by using value semantics (not storing pointers, but storing a copy of the object).

  • Eclipse/CDT is reported to be quite OK on linux. On Windows you'll do easier with Microsoft Visual C++ Express Edition. When you've gotten into the basics, switching later will be no problem for you.

  • Defining members outside of the class itself is preferred as we usually split header and implementation files. In the header you try not to expose any unnecessary information, and also to reduce code-size, it's a simple matter of compile time . But for many modern programming techniques (like using template meta-programming) this cannot be used, so quite some C++ code moves in the direction of inline definitions.

First, i recommend you a good book too. There are very good SO threads about C++ books, like The definitive C++ book guide and List . In the following, you find i tell you how to solve some problems, but i don't delve into details, because i think that's what a book can do much better.

I've glanced over the code, here is what i've figured:

  Node(char o, Node lNode, Node rNode)
  {
    oper = o;
    pLNode = &lNode;
    pRNode = &rNode;
    leaf = false;
  }

That constructor has 3 parameters, all of which are local to the function. When the function returns, the parameters do not exist anymore, and their memory occupied by them is cleaned up automatically. But you store their addresses into the pointers. That will fail then. What you want is passing pointers. By the way always use constructor initializer lists:

  Node(char o, Node *lNode, Node *rNode)
      :oper(o), pLNode(lNode), pRNode(rNode), leaf(false)
  { }

Creating the tree now does look different:

  CalcTree tree;
  tree.setRootNode(new Node('+', new Node(1), new Node(534)));
  cout << tree.calculateTree() << endl;

New creates a dynamic object and returns a pointer to it. The pointer must not be lost - otherwise you have a memory-leak because you can't delete the object anymore. Now, make sure you delete child nodes ordinarily by creating a destructor for Node (put it as you would put any other member function):

  ~Node() {
      delete pLNode;
      delete pRNode;
  }

Here you see it's important to always null-ify pointers: delete on a null pointer will do nothing. To start cleaning-up, you create a CalcTree destructor too, which starts the delete chain:

  ~CalcTree() {
      delete pRootNode;
  }

Don't forget to create a default constructor for CalcTree that initializes that pointer to 0! Now, you have one remaining problem: If you copy your object, the original object and the copy share the same pointer, and when the second object (the copy) goes out of scope, it will call delete on the pointer a second time, thus deleting the same object twice. That's unfortunate. It can be solved by forbidding copying your classes - or by using smart pointers that have shared ownership semantics (look into shared_ptr ). A third variant is to write your own copy constructor and copy assignment operator. Well, here is how you would disable copy constructor and copy assignment operators. Once you try to copy, you will get a compile error. Put this into Node:

  private:
      Node(Node const&);
      Node& operator=(Node const&);

The same for CalcTree, and you are protected from that subtle bug now.

Now, onto your other questions:

It seems in c++ people prefer to define members of a class outside the class

That is because the more code you add to the header, the more you have to include into your header from other files (because your code depends on something that's defined in them). Note that all other files that include that header then will transitively include the headers that one includes too. So you will in the end have less indirectly included headers when you put your code into separately compiled files instead. Another problem that is solved are cyclic references. Sometimes, you have to write a method that needs to have access to something defined later in the header. Since C++ is a single-pass language, the compiler can't resolve references to symbols that are declared after the point of use - generally.

I'd very much like some pointers on c++ conventions

That's very subjective, but i like this convention:

  • Class data-members are written like mDataMember
  • Function are written like getDataMember
  • Local variables are written like localVariable
  • Indenting with spaces, 4 spaces each (uh oh, this one is controversial. There are many possible ways to indent, please do not ask for the best one!)

I'm used to coding java with eclipse. Atm I'm using emacs for learning c++.

I'm using emacs for my C++ development, and eclipse for Java. Some use eclipse for Java too (there is a package called CDT for eclipse C++ development). It seems to be quite common gist (i agree) that Visual C++ on windows is the best IDE you get for windows. There are also some answers on SO regarding this: Best IDE for C++ (best search with google in stackoverflow, it will give you much more results than the builtin search).

  1. You're passing by value, not by reference when you say

    Node(char o, Node lNode, Node rNode)

It should be

Node(char o, Node &lNode, Node &rNode)

or better yet (for consistency with the rest of your code),

Node(char o, Node *lNode, Node *rNode)
  1. Compilation speed: The header (.h file) contains extra information not embedded in the .o files, so it must be recompiled by every other C++ file that includes it. Space: If you include method bodies, they are duplicated in every file that includes the .h file. This is in contrast to Java, where all of the relevant information is embedded in the .class file. C++ cannot do this because it is a much richer language. In particular, macros make it so that C++ will never be able to embed all the information in a .o file. Also, Turing-complete templates make it challenging to get rid of the .h / .cpp distinction.

  2. There are many conventions. The standard C++ library has one set, the standard C library has another, BSD and GNU each have their own, and Microsoft uses yet another. I personally like to be fairly close to Java when it comes to naming identifiers and indenting.

  3. Eclipse, NetBeans, KDevelop, vim, and emacs are all good options. If you're on Windows, Visual Studio is really nice.

There are very different conventions for C++. Google some. There is no "official" convention. ;)

To your IDE question: I am using the CDT (C[/C++] Development Tools, Eclipse). There has been a first release for another Eclipse based C++ IDE: http://www.eclipse.org/linuxtools/ I am going to test it out these days. Sounds very good.

Also try out KDevelop if you are using KDE.

2. It seems in c++ people prefer to define members of a class outside the 
class[..]

It's a matter of style. Mostly. We group the big bulky methods in a separate cpp file, and keep the small ones along with the header file. Declaring the method within the class declaration makes the function inline (which is a hint for the compiler to do, you guessed it -- inlining). This is something you may or may not want depending on the problem you want to solve.

3. I'd very much like some pointers on c++ conventions (naming, indenting etc.)

The standard library is a good reference. Start looking at the headers.

4. I'm used to coding java with eclipse. 

Eclipse can be configured to use a C++ compiler. Go(ogle) for it, please. Why punish yourself twice now that you've taken to C++ ;-)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM