我试图用C ++教自己关于课程的问题,而且我遇到了一些绊脚石,我似乎无法理解。我希望有人能够指出我正确的方向。
我决定构建一个小Tree
类,它构造一个新的BST。我希望能够在我的对象上调用某些方法,如下所示:
int main() {
Tree<int> tree1;
tree1.insert(5);
int treeMin = tree1.minValue();
int treeMax = tree1.maxValue();
tree1.printTree();
}
现在,为了调用这些功能,我定义了public
和private
函数,以便您不会以冗余方式调用函数。例如:
(我试图避免的)
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 :(得分:4)
代码中的private
成员函数只是一种不必要的复杂问题。我只是将他们的代码移动到公共成员函数:更少的代码,更干净的代码,更少的间接,所以更直接的可移植代码,都很好。对于其中一些,可能支持重用,方法是在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;
第四个问题,你需要负责复制 - 阅读“三规则”和“零规则”。
最简单的方法是替换
treeNode<T>* root;
与
unique_ptr< treeNode< T > > root;
其中unique_ptr
为std::unique_ptr
。
或者至少声明一个复制构造函数和一个复制赋值运算符,或者从“不可复制”类继承。要使课程有效不可复制,您可以制作这些操作符private
或protected
。为了使它可以复制,让它们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
或Barton / Nackmann的原始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;
}
输出:
(((( 1 ) 2 ) 3 ( 4 )) 5 (( 6 ) 7 ( 8 ))) min = 1, max = 8