Home Articles

实现从Java到C的跨越

Asked
Viewed 1081 times
3

正如主题所说,我对c很新,但我对java有一些经验 . 要开始学习c,我有一个(不是非常原始的)创建一个简单的命令行计算器的想法 . 我想要做的是将数字和运算符存储在二叉树中 .

#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;
}

我对这段代码有几个问题:

  • 这编译但不执行预期的操作 . 似乎在tree.setRootNode(Node('',Node(1),Node(534)))之后;在main中,rightnode被正确初始化,但leftnode却没有 . 编译并运行它为我打印534(gcc,freebsd) . 这有什么不对?

  • 似乎在c中,人们更喜欢在类之外定义类的成员,比如

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

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

这是为什么?

  • 我非常喜欢关于c约定的一些指针(命名,缩进等)

  • 我习惯用eclipse编写java . 我正在使用emacs来学习c . 有人可以告诉我一个好的(免费)c ide,或者我应该像一个真正的男人一样坚持和坚持使用emacs? :)

5 Answers

  • 5
    • 首先,我只能建议你看看这本书"Accelerated C++" . 它将启动你进入 STL 和C style ,并可以为你节省一年的糟糕经历 . (如果你决定深入研究,那里有相当多关于C的非常好的文献 . )

    • C不在堆上使用自动内存管理 . 您正在存储指向临时指针的指针 . 您的程序尝试访问被破坏的对象时不正确 . 无论喜欢与否,您都必须先了解对象的生命周期 . 通过使用值语义(不存储指针,但存储对象的副本),您可以在简单的情况下简化这一部分 .

    • 据报道Eclipse / CDT在linux上相当不错 . 在Windows上,使用Microsoft Visual C Express Edition可以更轻松地完成 . 当你掌握了基础知识后,稍后切换对你来说没问题 .

    • 首选定义类本身之外的成员是首选,因为我们通常会分割 Headers 和实现文件 . 在 Headers 中,您尝试不暴露任何不必要的信息,并且为了减少代码大小,这是编译 time 的简单问题 . 但是对于许多现代编程技术(比如使用模板元编程),这是不能使用的,所以相当一些C代码在内联定义的方向上移动 .

  • 3

    首先,我也推荐你一本好书 . 有很多关于C书的SO主题,比如The definitive C++ book guide and List . 在下文中,您会发现我告诉您如何解决一些问题,但我不会更好地了解一本书可以做些什么 .

    我已经浏览了代码,这是我的想法:

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

    该构造函数有3个参数,所有参数都是函数的本地参数 . 当函数返回时,参数不再存在,并且它们占用的内存会自动清除 . 但是你将它们的地址存储在指针中 . 那将失败 . 你想要的是传递指针 . 顺便说一句,总是使用构造函数初始化列表:

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

    现在创建树看起来确实不同:

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

    New创建一个动态对象并返回指向它的指针 . 指针不能丢失 - 否则您有内存泄漏,因为您无法再删除该对象 . 现在,确保通常通过为Node创建析构函数来删除子节点(将其放置为放置任何其他成员函数):

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

    在这里你看到始终为null-ify指针很重要:删除空指针将不执行任何操作 . 要开始清理,您还要创建一个CalcTree析构函数,它将启动删除链:

    ~CalcTree() {
          delete pRootNode;
      }
    

    唐'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'不幸 . 它可以通过禁止复制类来解决 - 或者通过使用具有共享所有权语义的智能指针来解决(查看 shared_ptr ) . 第三种变体是编写自己的复制构造函数和复制赋值运算符 . 那么,这里是如何禁用复制构造函数和复制赋值运算符 . 一旦您尝试复制,您将收到编译错误 . 把它放到Node中:

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

    对于CalcTree来说也是如此,现在你可以免受那个微妙的bug .

    现在,关于你的其他问题:

    似乎在c中,人们更喜欢在课堂外定义一个类的成员

    这是因为您添加到标头的代码越多,就必须从其他文件中包含到标头中的次数越多(因为您的代码取决于它们中定义的内容) . 请注意,包含该标头的所有其他文件将可传递地包含一个包含的标头 . 因此,当您将代码放入单独编译的文件中时,最终会有更少间接包含的标头 . 解决的另一个问题是循环引用 . 有时,您必须编写一个方法,该方法需要访问 Headers 中稍后定义的内容 . 由于C是单通道语言,因此编译器无法解析对在使用点之后声明的符号的引用 - 通常 .

    我非常喜欢关于c约定的一些指示

    这是非常主观的,但我喜欢这个惯例:

    • 类数据成员写成 mDataMember

    • 函数写成 getDataMember

    • 局部变量写成 localVariable

    • 用空格缩进,每个有4个空格(呃哦,这个有争议 . 有很多可能的缩进方法,请不要问最好的方法!)

    我习惯用eclipse编写java . 我正在使用emacs来学习c .

    我正在使用emacs进行C开发,使用eclipse进行Java . 有些人也使用eclipse for Java(有一个名为CDT的软件包用于eclipse C开发) . 似乎是非常普遍的主旨(我同意)Windows上的Visual C是Windows获得的最佳IDE . SO上也有一些答案关于这个:Best IDE for C++(在stackoverflow中使用google进行最佳搜索,它会比内置搜索提供更多结果) .

  • 1
    • 当你说时,你正在通过 Value 而不是参考

    节点(char o,Node lNode,Node rNode)

    它应该是

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

    或者更好(为了与代码的其余部分保持一致),

    Node(char o, Node *lNode, Node *rNode)
    
    • Compilation speed: Headers (.h文件)包含未嵌入.o文件中的额外信息,因此必须由包含它的每个其他C文件重新编译 . Space: 如果包含方法体,则它们将复制到包含.h文件的每个文件中 . 这与Java形成对比,Java中所有相关信息都嵌入在.class文件中 . C不能这样做,因为它是一种更丰富的语言 . 特别是,宏使得C永远不能将所有信息嵌入到.o文件中 . 此外,图灵完整模板使摆脱.h / .cpp区别变得具有挑战性 .

    • 有很多惯例 . 标准C库有一个集合,标准C库有另一个,BSD和GNU各有自己的,而Microsoft使用另一个 . 我个人希望在命名标识符和缩进时非常接近Java .

    • Eclipse,NetBeans,KDevelop,vim和emacs都是不错的选择 . 如果你在Windows上,Visual Studio非常好 .

  • 0

    C有很多不同的约定 . 谷歌一些 . 没有“官方”公约 . ;)

    对于您的IDE问题:我正在使用CDT(C [/ C]开发工具,Eclipse) . 已经有另一个基于Eclipse的C IDE的第一个版本:http://www.eclipse.org/linuxtools/这些天我将测试它 . 听起来很不错 .

    如果您使用的是KDE,还可以试用KDevelop .

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

    这是一种风格问题 . 大多 . 我们将单个庞大的方法分组在一个单独的 cpp 文件中,并将小文件与头文件一起保存 . 在类声明中声明方法使函数内联(这是编译器要做的提示,你猜对了 - 内联) . 根据您要解决的问题,这可能是您可能想要或不想要的 .

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

    标准库是一个很好的参考 . 开始查看 Headers .

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

    Eclipse可以配置为使用C编译器 . 请转到(谷歌)吧 . 既然你已经去了C,为什么要惩罚自己两次;-)

Related