2014-08-31 206 views
1

我想教自己关于C++中的类,我遇到了一个绊脚石,我似乎无法清理。我希望有人能够指出我正确的方向。私人和公共职能

我决定构造一个小的Tree类,它构造了一个新的BST。我希望能够调用某些方法我的对象,像这样:

int main() { 
    Tree<int> tree1; 

    tree1.insert(5); 

    int treeMin = tree1.minValue(); 
    int treeMax = tree1.maxValue(); 

    tree1.printTree(); 
} 

眼下,为了调用这些函数,我定义这两个publicprivate功能,使您不会在调用函数多余的方式。例如:

(什么我试图避免)

int main() { 
    Tree<int> tree1; 

    tree1.insert(tree1, 5); 

    int treeMin = tree1.minValue(tree1); 
    int treeMax = tree1.maxValue(tree1); 

    tree1.printTree(tree1); 
} 

为了做到避免这种冗余,我定义相同功能的公共和私人的版本。通过这种方式,公共职能呼吁他们的私人对手。

template<class T> 
class Tree { 
private: 
    treeNode<T>* root; 
    treeNode<T>* newNode(T data); 
    void insert(treeNode<T>*& root, T data); 
    int minValue(treeNode<T>*& root); 
    int maxValue(treeNode<T>*& root); 
    void printTree(treeNode<T>*& root); 

public: 
    Tree(); 
    ~Tree(); 
    void insert(T data); 
    int minValue(); 
    int maxValue(); 
    void printTree(); 
}; 

然后,作为一个例子:

template<class T> 
int Tree<T>::minValue() { minValue(root); } 

template<class T> 
int Tree<T>::minValue(treeNode<T>*& root) { 
    if (root == NULL) { return 0; } 
    if (root->left == NULL) { return root->data; } 
    else { minValue(root->left); } 
} 

所以,我的问题是: 如果我在写我的递归函数,我明白,我需要声明接受一个私有函数论点,但这被认为是一种不好的风格?这是不是马虎?

感谢您的帮助!

+0

@CaptainObvlious因此,这是去了解它的正确方法?我知道这不是一个完整的BST课程,我只是想确保公共/私人界面是正确的方式去做,而且对于一个经验丰富的程序员来说,这看起来并不是完全荒谬的。 (谢谢!) – New 2014-08-31 00:17:08

+2

树是一个复杂的结构,没有一个“单向”来做到这一点。你对正在公开的内容持保守态度,这是一件好事。如果满足您的需求,您的方式就是正确的。 – 2014-08-31 00:31:01

+1

您存在的问题(需要将对象的引用发送给其自己的成员函数之一)不存在,并且当然不会通过将公共成员函数复制为私有成员函数来解决。 – 2014-08-31 01:18:15

回答

4

代码中的private成员函数只是一个不必要的复杂因素。我只是将他们的代码移动到公共成员函数:更少的代码,更干净的代码,更不直接,因此更直接grokable代码,都很好。对于其中的一些,您可能可能通过使其在details名称空间中成为自由函数来支持重用,但我认为这将是过早的泛化,并且可能不会发生可能的重用。

答案结束时的示例代码。


重新设计的另一个问题,声明

int minValue(); 
int maxValue(); 

排除调用const对象上这些成员函数。相反,做

int minValue() const; 
int maxValue() const; 

的第三个问题,这通常是一个非常糟糕的主意™做I/O在非I/O类。如果将树打印到标准输出中,您将如何在GUI程序中使用该类?所以,而不是

void printTree(); 

做例如,

ostream& operator<<(ostream& stream) const; 

或例如

string toString() const; 

第四个问题,你需要人主复制的 –读了三个”的“规则和零”的“规则。

做到这一点的最简单的方法是用

unique_ptr< treeNode<T> > root; 

其中unique_ptrstd::unique_ptr更换

treeNode<T>* root; 

或者声明至少一个复制构造函数和一个复制赋值运算符,或者继承“不可复制的”类。要使课程有效不可复制,您可以使这些运营商privateprotected。为了使它们成为可复制的,使它们成为public并且在每个中都做正确的事情(复制赋值操作符的一个很好的默认实现是通过复制和交换习惯用法来表达它的拷贝构造,这意味着引入一个非抛出swap函数)。


第五个问题是,实现

template<class T> 
int Tree<T>::minValue(treeNode<T>*& root) { 
    if (root == NULL) { return 0; } 
    if (root->left == NULL) { return root->data; } 
    else { minValue(root->left); } 
} 

强烈建议每个节点存储的值是隐式转换为int。您不提供treeNode的声明。但是,这看起来像是一个设计级别的错误,意图是minValue返回T,而不是int –和同上maxValue


一个非常小的编码问题(没有设计级别):在C++ 11以后,你应该优先使用nullptr,不NULL

  • nullptr可以通过参数转发功能被自由地通过,而NULL然后遭受衰减到一体型中,由于NULL仅仅是一个零常数整数类型。

  • nullptr不需要包含任何标题,而NULL由标题定义,即nullptr可避免标题依赖。


最后,关于

if (root == NULL) { return 0; } 

minValue,这当然可以被意图,设计。但是,可能您希望发信号失败或将该呼叫视为逻辑错误。

  • 将该呼叫视为错误,assert(root != nullptr);并提供客户端代码检查空树的方法。

  • 要信号故障,或者与任选的值(例如像boost::optional或巴顿/ Nackmann的原始Fallible),或抛出异常(在std::runtime_error类是一个良好的一般缺省异常类选择)返回对象。

  • 也可以将两种方法结合起来,以提供这两种方法,或许可以使用诸如minValueminValueOrX之类的名称。

更一般地说,有时候可以保留一些特殊的值作为“没有这样的”指标。例如。 std::numeric_limits<T>::min()。但是这会产生脆弱的代码,因为这样的值很容易在数据中自然发生,并且由于客户端代码可能很难检查特殊值。


例,编码C++ 11:

#include <assert.h> 
#include <iostream>  // std::cout, std::endl 
#include <string>  // std::string 

namespace my { 
    using std::string; 

    template<class T> 
    class Tree 
    { 
    private: 
     struct Node 
     { 
      T  value; 
      Node* p_left; 
      Node* p_right; 

      auto to_string() const -> string 
      { 
       using std::to_string; 
       string const left = (p_left == nullptr? "" : p_left->to_string()); 
       string const right = (p_right == nullptr? "" : p_right->to_string()); 
       return "(" + left + " " + to_string(value) + " " + right + ")"; 
      } 

      ~Node() { delete p_left; delete p_right; } 
     }; 

     Node* root_; 

     Tree(Tree const& ) = delete; 
     Tree& operator=(Tree const&) = delete; 

    public: 
     auto is_empty() const -> bool { return (root_ == nullptr); } 

     void insert(T const data) 
     { 
      Node** pp = &root_; 
      while(*pp != nullptr) 
      { 
       auto const p = *pp; 
       pp = (data < p->value? &p->p_left : &p->p_right); 
      } 
      *pp = new Node{ data, nullptr, nullptr }; 
     } 

     auto minValue() const -> T 
     { 
      assert(root_ != nullptr); 
      Node* p = root_; 
      while(p->p_left != nullptr) { p = p->p_left; } 
      return p->value; 
     } 

     auto maxValue() const -> T 
     { 
      assert(root_ != nullptr); 
      Node* p = root_; 
      while(p->p_right != nullptr) { p = p->p_right; } 
      return p->value; 
     } 

     auto to_string() const -> string 
     { 
      return (root_ == nullptr? "" : root_->to_string()); 
     } 

     ~Tree() { delete root_; } 

     Tree(): root_(nullptr) {} 

     Tree(Tree&& other): root_(other.root_) { other.root_ = nullptr; } 
    }; 

} // namespace my 

auto main() -> int 
{ 
    my::Tree<int> tree; 
    for(int const x : {5, 3, 4, 2, 7, 6, 1, 8}) 
    { 
     tree.insert(x); 
    } 

    using std::cout; using std::endl; 
    cout << tree.to_string() << endl; 
    cout << "min = " << tree.minValue() << ", max = " << tree.maxValue() << endl; 
} 

输出:

 
((((1) 2) 3 (4)) 5 ((6) 7 (8))) 
min = 1, max = 8 
+0

@Alf谢谢。这让我读了很多!非常感激。 – New 2014-08-31 01:44:46

+0

@新:我突然害怕我的建议可能是错的,所以我编写了一个例子。唯一的问题是'to_string'。上面的实现很简单,但在大O方面并不是最佳效率。 – 2014-08-31 04:05:52

+0

哦,关于递归函数:如果您需要递归树,那么对于上面的示例代码,您可以通过'Node'类中的成员函数来执行此操作,如'to_string'所示。 – 2014-08-31 04:12:58