1. 需求
构造一个二叉树,所有数据按照升序排列。
2. 原型版本
先考虑一个最简单的原型。代码如下:
/**
* tree.cc
*
* A example to show how to construct a binary tree.
*
* Command:
* g++ tree.cc
* valgrind --tool=memcheck ./a.out
*
*/
#include <stdio.h>
#include <stdlib.h>
class Data {
public:
explicit Data(int id):_id(id){}
Data(const Data& data);
Data(): _id(-1) {}
int getId() const {
return _id;
}
private:
int _id;
};
Data::Data(const Data& data):_id(data._id)
{
}
class Tree
{
public:
explicit Tree();
~Tree();
void insert(const Data &data);
void dump() const;
private:
void insert_left(const Data &data);
void insert_right(const Data &data);
private:
Data* _data;
Tree* left_child;
Tree* right_child;
};
Tree::Tree():_data(NULL), left_child(NULL), right_child(NULL) {}
Tree::~Tree()
{
if (_data != NULL) {
delete _data;
}
if (left_child != NULL) {
delete left_child;
left_child = NULL;
}
if (right_child != NULL) {
delete right_child;
right_child = NULL;
}
}
void Tree::insert(const Data &data)
{
if (NULL == _data) {
_data = new Data(data);
return;
}
if (data.getId() <= _data->getId()) {
insert_left(data);
} else {
insert_right(data);
}
}
void Tree::insert_left(const Data &data)
{
if (NULL == left_child) {
left_child = new Tree();
left_child->insert(data);
return;
}
left_child->insert(data);
}
void Tree::insert_right(const Data &data)
{
if (NULL == right_child) {
right_child = new Tree();
right_child->insert(data);
return;
}
right_child->insert(data);
}
void Tree::dump() const
{
if (left_child != NULL) {
left_child->dump();
}
printf("%d\t", _data->getId());
if (right_child != NULL) {
right_child->dump();
}
}
int main()
{
Tree tree;
int data[] = {1, 3, 5, 7, 2, 4, 6, 8};
int i;
for (i = 0; i < sizeof(data) / sizeof(int); i++) {
tree.insert(Data(data[i]));
}
tree.dump();
printf("\n");
return 0;
}
运行结果:
flying-bird@flyingbird:~/examples/cpp/tree$ g++ tree.cc
flying-bird@flyingbird:~/examples/cpp/tree$ ./a.out
1 2 3 4 5 6 7 8
flying-bird@flyingbird:~/examples/cpp/tree$ valgrind --tool=memcheck ./a.out
==7673== Memcheck, a memory error detector
==7673== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==7673== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info
==7673== Command: ./a.out
==7673==
1 2 3 4 5 6 7 8
==7673==
==7673== HEAP SUMMARY:
==7673== in use at exit: 0 bytes in 0 blocks
==7673== total heap usage: 15 allocs, 15 frees, 116 bytes allocated
==7673==
==7673== All heap blocks were freed -- no leaks are possible
==7673==
==7673== For counts of detected and suppressed errors, rerun with: -v
==7673== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
flying-bird@flyingbird:~/examples/cpp/tree$
3. 重构
我们通常会搭建一个可以运行的、满足基本功能的代码,然后再快速重构、逐步演进。
3.1 operator <<
在C++中,用printf总是不如ostream正式。所以,首先对dump()进行重构,为Tree定义一个operator<<()操作符。
代码如下://同时头文件风格进行优化:用cstdio代替stdio.h (参考Effective C++, 2nd, Item 49)
/**
* tree2.cc
*
* A example to show how to construct a binary tree.
*
* Command:
* g++ tree2.cc
* valgrind --tool=memcheck ./a.out
*
*/
#include <cstdlib>
#include <iostream>
class Data {
public:
explicit Data(int id):_id(id){}
Data(const Data& data);
Data(): _id(-1) {}
int getId() const {
return _id;
}
private:
int _id;
};
Data::Data(const Data& data):_id(data._id)
{
}
class Tree
{
public:
explicit Tree();
~Tree();
void insert(const Data &data);
private:
void insert_left(const Data &data);
void insert_right(const Data &data);
private:
Data* _data;
Tree* left_child;
Tree* right_child;
friend std::ostream& operator<<(std::ostream &os, const Tree& tree);
};
Tree::Tree():_data(NULL), left_child(NULL), right_child(NULL) {}
Tree::~Tree()
{
if (_data != NULL) {
delete _data;
}
if (left_child != NULL) {
delete left_child;
left_child = NULL;
}
if (right_child != NULL) {
delete right_child;
right_child = NULL;
}
}
void Tree::insert(const Data &data)
{
if (NULL == _data) {
_data = new Data(data);
return;
}
if (data.getId() <= _data->getId()) {
insert_left(data);
} else {
insert_right(data);
}
}
void Tree::insert_left(const Data &data)
{
if (NULL == left_child) {
left_child = new Tree();
left_child->insert(data);
return;
}
left_child->insert(data);
}
void Tree::insert_right(const Data &data)
{
if (NULL == right_child) {
right_child = new Tree();
right_child->insert(data);
return;
}
right_child->insert(data);
}
std::ostream& operator<<(std::ostream &os, const Tree& tree)
{
if (tree.left_child != NULL) {
os<<*(tree.left_child);
}
os<<tree._data->getId()<<'\t';
if (tree.right_child != NULL) {
os<<*(tree.right_child);
}
return os;
}
int main()
{
Tree tree;
int data[] = {1, 3, 5, 7, 2, 4, 6, 8};
int i;
for (i = 0; i < sizeof(data) / sizeof(int); i++) {
tree.insert(Data(data[i]));
}
std::cout<<tree<<std::endl;
return 0;
}
3.2 Data抽象
从逻辑上来讲,以上两个数据模型,即Tree和Data应该是松耦合,或者是接口依赖,而不能是实际数据依赖。所以,接下来对Data进行重构,目标就是Tree无须知晓Data的内部形态。显然,我们要为Data定义operator<=()操作符。
代码如下:
/**
* tree3.cc
*
* A example to show how to construct a binary tree.
*
* Command:
* g++ tree3.cc
* valgrind --tool=memcheck ./a.out
*
*/
#include <cstdlib>
#include <cassert>
#include <iostream>
class Data {
public:
explicit Data(int id):_id(id){}
Data(const Data& data);
Data(): _id(-1) {}
int getId() const {
return _id;
}
private:
int _id;
};
bool operator<=(const Data& lhs, const Data& rhs)
{
return lhs.getId() <= rhs.getId();
}
bool operator>(const Data& lhs, const Data& rhs)
{
assert(false); // Not used now.
return false;
}
Data::Data(const Data& data):_id(data._id)
{
}
class Tree
{
public:
explicit Tree();
~Tree();
void insert(const Data &data);
private:
void insert_left(const Data &data);
void insert_right(const Data &data);
private:
Data* _data;
Tree* left_child;
Tree* right_child;
friend std::ostream& operator<<(std::ostream &os, const Tree& tree);
};
Tree::Tree():_data(NULL), left_child(NULL), right_child(NULL) {}
Tree::~Tree()
{
if (_data != NULL) {
delete _data;
}
if (left_child != NULL) {
delete left_child;
left_child = NULL;
}
if (right_child != NULL) {
delete right_child;
right_child = NULL;
}
}
void Tree::insert(const Data &data)
{
if (NULL == _data) {
_data = new Data(data);
return;
}
if (data <= *_data) {
insert_left(data);
} else {
insert_right(data);
}
}
void Tree::insert_left(const Data &data)
{
if (NULL == left_child) {
left_child = new Tree();
left_child->insert(data);
return;
}
left_child->insert(data);
}
void Tree::insert_right(const Data &data)
{
if (NULL == right_child) {
right_child = new Tree();
right_child->insert(data);
return;
}
right_child->insert(data);
}
std::ostream& operator<<(std::ostream &os, const Tree& tree)
{
if (tree.left_child != NULL) {
os<<*(tree.left_child);
}
os<<tree._data->getId()<<'\t';
if (tree.right_child != NULL) {
os<<*(tree.right_child);
}
return os;
}
int main()
{
Tree tree;
int data[] = {1, 3, 5, 7, 2, 4, 6, 8};
int i;
for (i = 0; i < sizeof(data) / sizeof(int); i++) {
tree.insert(Data(data[i]));
}
std::cout<<tree<<std::endl;
return 0;
}
3.3 Data进一步抽象
让Tree有赖于Data的接口,而不是实现。