字符图形问题
本文中,我们将要通过继承和上文的通用句柄Ptr,来保存图案的固有的结构信息,同时尽量减少程序占用的内存。
这里需要解决两个不同的问题。一个是设计问题——我们需要保存如何创建图像的结构信息。另一个是实现问题——我们希望尽可能的少保存数据的副本。
首先,设计问题:我们所创建的每个图案都有一个结构,每个结构我们都想保存。我们可能是通过一个输入字符集来形成图案,也可能通过3个操作来形成图案,这个3个操作是:frame(生成一个装框的图案);hcat或者vcat(创建水平连接或垂直连接的图案)。
可以得到下面的继承层次图:
![image image](https://images.cnblogs.com/cnblogs_com/qlwy/201204/201204081642233296.png)
接下来问题是,是否然用户能够看到这个继承层次?任何一个操作都不是处理特定种类的图案,相反,这写操作都是处理的这个图案的抽象概念,所以没有必要暴漏。
我们没有让用户直接操作上面的类,相反,我们定义了一个特定图案 的结构类。用户会访问这个类,而不需要考虑任何实现的细节。值得一提的时,使用一个接口类可以隐藏继承的层次结构,同时也隐藏了类依赖于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() {} };
接下来,考虑派生类:如果一个派生类定义了它继承的所有纯虚函数的话,它就是一个具体的类,我们可以创建它的对象。如果有一个纯虚函数没有定义,那么这个派生类也是一个纯虚函数。
上图就是我们的设计思想。我们虽然用的时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; }
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; };