私人和公共职能

New*_*New 1 c++ oop coding-style class

我试图自学C ++类,但遇到了一个绊脚石,我似乎无法解决。我希望有人能够指出正确的方向。

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

int main() {
  Tree<int> tree1;

  tree1.insert(5);

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

  tree1.printTree();
}
Run Code Online (Sandbox Code Playgroud)

现在,为了调用这些函数,我同时定义了publicprivate函数,以便您不会以多余的方式调用函数。例如:

(我想避免的事情)

int main() {
  Tree<int> tree1;

  tree1.insert(tree1, 5);

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

  tree1.printTree(tree1);
}
Run Code Online (Sandbox Code Playgroud)

为了避免出现这种冗余,我定义了同一功能的公共和私有版本。这样,公共职能部门会调用其私人部门。

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();
};
Run Code Online (Sandbox Code Playgroud)

然后,例如:

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); }
}
Run Code Online (Sandbox Code Playgroud)

因此,我的问题是:如果我递归地编写函数,我理解我需要声明一个接受参数的私有函数,但这被认为是不好的风格吗?这是草率的吗?

谢谢你的帮助!

Che*_*Alf 5

private代码中的成员函数只是不必要的复杂性。我只是将其代码移至公共成员函数:更少的代码,更多的简洁代码,更少的间接性,因此更直接的可替换代码,都很好。对于其中的一些,您可以通过在details名称空间中使它们成为自由函数支持重用,但是我认为这是过早的概括,将精力花在可能不会发生的重用上。

答案末尾的示例代码。


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

int minValue();
int maxValue();
Run Code Online (Sandbox Code Playgroud)

排除在const对象上调用这些成员函数的可能性。相反做

int minValue() const;
int maxValue() const;
Run Code Online (Sandbox Code Playgroud)

第三,在非I / O类中进行I / O通常是一个Really Bad Idea™。如果将树打印到标准输出,您将如何在GUI程序中使用该类?所以,代替

void printTree();
Run Code Online (Sandbox Code Playgroud)

做例如

ostream& operator<<( ostream& stream ) const;
Run Code Online (Sandbox Code Playgroud)

或例如

string toString() const;
Run Code Online (Sandbox Code Playgroud)

第四个问题,您需要负责复制 –阅读“ 3条规则”和“ 0条规则”。

最简单的方法是更换

treeNode<T>* root;
Run Code Online (Sandbox Code Playgroud)

unique_ptr< treeNode< T > > root;
Run Code Online (Sandbox Code Playgroud)

哪里unique_ptrstd::unique_ptr

或者,至少声明一个副本构造函数和一个副本赋值运算符,或从“不可复制”类继承。要使该类实际上不可复制,可以使这些运算符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); }
}
Run Code Online (Sandbox Code Playgroud)

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


一个很小的编码问题(不是设计级别):在C ++ 11和更高版本中,您应该优先使用nullptr,而不是NULL

  • nullptr可以自由地通过参数转发函数传递,但NULL随后会衰减到整数类型,因为NULL它只是整数类型的零常数。

  • nullptr不需要包含任何标头,而标头NULL定义了标头,即nullptr避免标头依赖性。


最后,关于

if (root == NULL) { return 0; }
Run Code Online (Sandbox Code Playgroud)

对于minValue,这当然可能是设计意图。但是可能您想发信号失败或将呼叫视为逻辑错误。

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

  • 要发出故障信号,请返回带有可选值的对象(例如like boost::optional或Barton / Nackmann's original Fallible),或引发异常(std::runtime_error该类是很好的常规默认异常类选择)。

  • 也可以将这两种方法结合使用,以提供两种名称,也许使用诸如minValue和的名称minValueOrX

更一般而言,有时可以保留一些特殊值作为“否”指示器。例如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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

(((((1)2)3(4))5((6)7(8)))
最小值= 1,最大值= 8