字符图形问题

本文中,我们将要通过继承和上文的通用句柄Ptr,来保存图案的固有的结构信息,同时尽量减少程序占用的内存。

这里需要解决两个不同的问题。一个是设计问题——我们需要保存如何创建图像的结构信息。另一个是实现问题——我们希望尽可能的少保存数据的副本。

首先,设计问题:我们所创建的每个图案都有一个结构,每个结构我们都想保存。我们可能是通过一个输入字符集来形成图案,也可能通过3个操作来形成图案,这个3个操作是:frame(生成一个装框的图案);hcat或者vcat(创建水平连接或垂直连接的图案)。

可以得到下面的继承层次图:

image

接下来问题是,是否然用户能够看到这个继承层次?任何一个操作都不是处理特定种类的图案,相反,这写操作都是处理的这个图案的抽象概念,所以没有必要暴漏。

我们没有让用户直接操作上面的类,相反,我们定义了一个特定图案 的结构类。用户会访问这个类,而不需要考虑任何实现的细节。值得一提的时,使用一个接口类可以隐藏继承的层次结构,同时也隐藏了类依赖于Ptr的实际情况。

所以我们接下来需要定义6个类:接口类(Picture,内部使用Ptr来管理数据),继承层次需要的基类。还有4个派生类。

Picture类会有一个单独的数据成员,它的类型是Ptr<Pic_base>。由于要派生,我们需要为Pic_base类添加一个虚析构函数。我们把这个析构函数声明为公有,这样Ptr类就可以使用它了。

如下:

#include <iostream>
#include <vector>
#include <string>

#include "14_ptr(final handle).h"

//私有类实现
class pic_base //虚基类
{

};
//4个派生类
class String_Pic: public pic_base
{

};
class Frame_Pic: public pic_base
{

};
class Vcat_Pic: public pic_base
{

};
class Hcat_Pic: public pic_base
{

};
//picture接口类
class Picture
{
public:
	Picture(const std::vector<std::string>& = std::vector<std::string>() );
private:
	Ptr<pic_base> p;
};

接下来我们需要考虑如何表示Picture的其他操作:frame,hcat,vcat。这些操作并没有改变他们所操作的Picture的对象,所以不需要定义为成员函数,还有一个好理由:如果他们定义为非成员函数,就可以使用类型转换了(注:如果一个类支持类型转换,那么把二元操作符定义为非成员函数是一个很好的习惯)   。

我们进一步充实接口设计:

//充实接口设计
Picture frame(const Picture&);
Picture hcat(const Picture& ,const Picture& );
Picture vcat(const Picture& ,const Picture& );
//还有定义一个输出函数来输出Picture对象的内容
std::ostream& operator<<(std::ostream& ,const Picture&);

接下来,完成Pic_base类的实现细节:

class Pic_base //虚基类
{
	typedef std::vector<std::string>::size_type h_size;
	typedef std::string::size_type w_size;

	virtual w_size width() const = 0;
	virtual h_size height() const = 0;
	virtual void display(std::ostream&, h_size ,bool) const = 0;
public:
	virtual ~Pic_base() {}
};

接下来,考虑派生类:如果一个派生类定义了它继承的所有纯虚函数的话,它就是一个具体的类,我们可以创建它的对象。如果有一个纯虚函数没有定义,那么这个派生类也是一个纯虚函数。

image

上图就是我们的设计思想。我们虽然用的时Picture类,但是他中的指针指向的是Pic_base*的指针,可以表示派生类。我们定义的派生类即可以是存数据,又可以想其余的3个派生类一样进行对派生类的对象进行操作(如上图,加框的对象又进行了连接),所以派生类应该保存一个Ptr<Pic_base>类型的对象,这个既可以操作派生类,有把内存管理交给了Ptr这个通用句柄类。

如上图:当我们调用Frame(pic)时,就会创建一个新的Frame_Pic对象,这个对象会发Ptr指针,指向pic对象中指向的String_Pic对象。

//4个派生类
class String_Pic: public Pic_base
{//与下面的个派生类有所区别,它保存的时包含图像数据的vector<string>类型
 //对象的一份副本
	std::vector<std::string> data;
	//这是我们程序中唯一一处复制字符的地方,其他地方都是Ptr<Pic_base>对象
	String_Pic(const std::vector<std::string>& v):data(v) {}

	virtual w_size width() const = 0;
	virtual h_size height() const = 0;
	virtual void display(std::ostream&, h_size ,bool) const = 0;
};
class Frame_Pic: public Pic_base
{//没有公共成员
	Ptr<Pic_base> p;
	Frame_Pic(const Ptr<Pic_base>& pic):p(pic) { }

	w_size width() const;
	h_size height() const;
	void display(std::ostream& ,h_size ,bool) const;
};
class Vcat_Pic: public Pic_base
{
	Ptr<Pic_base> top,bottom;
	Vcat_Pic(const Ptr<Pic_base>& t,const Ptr<Pic_base>& b):
	top(t),bottom(b) {}

	w_size width() const;
	h_size height() const;
	void display(std::ostream& ,h_size ,bool) const;
};
class Hcat_Pic: public Pic_base
{
	Ptr<Pic_base> left,right;
	Vcat_Pic(const Ptr<Pic_base>& l,const Ptr<Pic_base>& r):
	left(l),right(r) {}

	w_size width() const;
	h_size height() const;
	void display(std::ostream& ,h_size ,bool) const;
};

大体框架就是这样,接下来就是具体实现的问题了。

 

1.首先实现用户的接口:

Picture frame(const Picture& pic)
{
	//Pic_base* ret = new Frame_Pic(pic.p);
	//接下来返回什么?
	return new Frame_Pic(pic.p);
}

我们先看注释的部分:

首先,Frame_Pic的构造函数是私有的,为了这个,我们需要使frame操作成为Frame_Pic类的一个友元。

其次,我们创建了一个Frame_Pic对象,但是我们需要一个Picture类型的对象,对,就是你想的,在Picture类中加入一个类型转换的构造函数

class Picture
{
public:
	Picture(const std::vector<std::string>& = std::vector<std::string>() );
private:
	Ptr<Pic_base> p;
	Picture(Pic_base* ptr):p(ptr) { } //为了frame,vcat,hcat函数
};

所以上面生成的Frame_Pic对象首先自动转化为基类的对象,再通过通用句柄类中的Ptr:Ptr(T*)构造函数转化成Ptr<Pic_base>类型的对象,最后再通过上面刚定义的这个构造函数转变成了Picture类型的对象。

在这里还要注意到,frame函数使用了Picture类的私有的构造函数,所以,是不是应该把frame成为Picture类的一个友元啊。

另外连个函数vcat, hcat同理,不再说明。

下面,我们再看Picture类中那个公有的函数,实现如下:

Picture::Picture(const vector<string>& v):
p(new String_Pic(v)) {}

注意同上面的区别,这里我们只是用了通用句柄类中的Ptr:Ptr(T*)构造函数转化成Ptr<Pic_base>类型的对象p,而没有用到Picture的私有构造函数。

最后定义<<:

ostream& operator << (ostream& os,const Picture& picture)
{
	const Pic_base::h_size hs=picture.p->height(); //调用了私有的p,声明友元
	for (Pic_base::h_size i=0;i!hs;++i)
	{
		picture.p->display(os,i,false); //使用了height和display,声明友元
		os<<endl;
	}
	return os;
}

 
2.String_Pic类的实现
class String_Pic: public Pic_base
{
	friend class Picture; 
 //与下面的个派生类有所区别,它保存的时包含图像数据的vector<string>类型
 //对象的一份副本
	std::vector<std::string> data;
	//这是我们程序中唯一一处复制字符的地方,其他地方都是Ptr<Pic_base>对象
	String_Pic(const std::vector<std::string>& v):data(v) {}

	virtual w_size width() const = 0;
	virtual h_size height() const = 0;
	virtual void display(std::ostream&, h_size ,bool) const = 0;
};

具体实现:

Pic_base::h_size String_Pic::height() const
{
	return data.size();
}
Pic_base::w_size String_Pic::width() const
{
	Pic_base::w_size n = 0;
	for (Pic_base::h_size i=0;i!=data.size();++i)
	{
		n=std::max(n,data[i].size());
	}
	return n;
}
void String_Pic::display(ostream& os,h_size row,bool do_pad) const
{
	w_size start = 0;
	if (row<height())
	{
		os<<data[row];
		start=data[row].size();
	}
	if(do_pad)
		pad(os,start,width());
}

这里最后有个pad函数(填充函数) :由于想要这个函数访问每一个派生类,所以我们把它定义为Pic_base类的成员函数,并使他为保护类型的static函数:

protected:
	static void pad(std::ostream& os,w_size beg,w_size end)
	{
		while (bed!end)
		{
			os<<" ";
			++beg;
		}
	}

 

3.vcat_Pic类

我们为vact操作添加适当的友元声明。并且做适当的内联。

class Vcat_Pic: public Pic_base
{
	friend Picture vcat(const Picture& ,const Picture& );
	Ptr<Pic_base> top,bottom;
	Vcat_Pic(const Ptr<Pic_base>& t,const Ptr<Pic_base>& b):
	top(t),bottom(b) {}

	w_size width() const
	{
		return std::max(top->width(),bottom->width());
	}
	h_size height() const
	{return top->height()+bottom->height();
	void display(std::ostream& ,h_size ,bool) const;
};

display函数不难实现:

void Vcat_Pic::display(ostream& os, h_size row, bool do_pad) const
{
	w_size w = 0;
	if (row < top->height()) {
		top->display(os, row, do_pad);
		w = top->width();
	} else if (row < height()) {
		bottom->display(os, row - top->height(), do_pad);
		w = bottom->width();
	}
	if (do_pad)
		pad(os, w, width());
}

4.Hcat_Pic

同上个类,差不多,具体类内部的定义就不说了,这里看下display函数是怎么实现的:

void Hcat_Pic::display(ostream& os,h_size row,bool do_pad) const
{
	left->display(os,row,do_pad||row<right->height());
	right->display(os,row,do_pad);
}

 

5.最后来看看Frame_pic类

不管是长还是宽,这里都要加4,内部类同上,差不多。

这里的disp函数比较长,但是不难实现:

void Frame_Pic::display(ostream& os, h_size row, bool do_pad) const
{
	if (row >= height()) { 
		if (do_pad)
			pad(os, 0, width());
	} else {
		if (row == 0 || row == height() - 1) {
			os << string(width(), '*');
		} else if (row == 1 || row == height() - 2) {
			os << "*";
			pad(os, 1, width() - 1);
			os << "*";
		} else {
			os << "* ";
			p->display(os, row - 2, true);
			os << " *";
		}
	}
}

 

到此所以的程序都完成了,但是最后的最后不要忘记友元的声明啊。

class Picture;

class Pic_base {
    friend std::ostream& operator<<(std::ostream&, const Picture&);
    friend class Frame_Pic;
    friend class HCat_Pic;
    friend class VCat_Pic;
    friend class String_Pic;

    // no public interface
    typedef std::vector<std::string>::size_type ht_sz;
    typedef std::string::size_type wd_sz;

    // this class is an abstract base class
    virtual wd_sz width() const = 0;
    virtual ht_sz height() const = 0;
    virtual void display(std::ostream&, ht_sz, bool) const = 0;

protected:
    static void pad(std::ostream& os, wd_sz, wd_sz);
};

class Picture {
    friend std::ostream& operator<<(std::ostream&, const Picture&);
    friend Picture frame(const Picture&);
    friend Picture hcat(const Picture&, const Picture&);
    friend Picture vcat(const Picture&, const Picture&);

public:
    Picture(const std::vector<std::string>& =
        std::vector<std::string>());

private:
    Picture(Pic_base* ptr): p(ptr) { }
    Ptr<Pic_base> p;
};

    // operations on Pictures
    Picture frame(const Picture&);
    Picture hcat(const Picture&, const Picture&);
    Picture vcat(const Picture&, const Picture&);
    std::ostream& operator<<(std::ostream&, const Picture&);

在这里Picture和Pic_base这两个类是相互调用的。这种依赖关系不出问题在于:Picture类并不直接包含Pic_base类型的成员。相反它包含一个Ptr<Pic_base>类型的成员,这个成员包含的是一个Pic_base*类型(使用指针可以避免使用无穷嵌套的对象)。

像下面这样就不行了:

class Yang;         // forward declaration

class Yin { Yang y; };

class Yang {
    Yin y; 
};
posted @ 2012-04-08 16:42  csqlwy  阅读(578)  评论(0编辑  收藏  举报