一道考验你设计能力的C++编程题
看到这道题,我们就开始设计这个图像类了,按照面向对象“依赖倒置”的设计原则,我们站在客户的立场,来考虑我们这个类该提供哪些接口,很快我们设计了如下一个类
class CSimplePicture
{
public:
CSimplePicture(char* init[], int nCount);
CSimplePicture(CSimplePicture& p1, CSimplePicture& p2, bool bVerCat);
void Frame();
void Print(std::ostream& os) const;
protected:
std::vector<std::string> m_arData;
};
CSimplePicture(char* init[], int nCount);
根据字符串数组构造一幅图像.
CSimplePicture(CSimplePicture& p1, CSimplePicture& p2, bool bVerCat);
根据两幅图像构造一幅图像,bVerCat表明是纵联接还是横联接.
void Frame();
给图像对象加框
void Print(std::ostream& os) const;
打印输出图像
std::vector<std::string> m_arData;
存储图像数据的字符串数组
下面来考虑具体实现,这个对于有一定开发的经验的人来说还是很容易的,就不具体写了,
CSimplePicture(char* init[], int nCount)无非是数据的拷贝,CSimplePicture(CSimplePicture& p1, CSimplePicture& p2, bool bVerCat)就是把2幅图片的数据连接,合在一起,void Frame()修改里面的数据加上边框,void Print(std::ostream& os) const遍历字符串数组输出。
根据上面的设计和实现,应该已经满足我们这个题目的要求了。
但是客户的需求是多变的,现在客户又有一个新的需求,要求把一幅图片去掉边框。
另外客户觉得我们这个图片类的性能太差了,每次加框或是合成图片都要大量的内存拷贝。
这时我们傻眼了,该死的客户,根据我们上面的设计,根本不支持这些新功能,因为我们存储的是图像的内部的字符串数据,根本不知道它是不是加框过的,另外我们的图像数据本身就是不支持共享的。
接下来我们就要重新考虑设计了,如何让我们的图像对象支持UnFrame(去边框)操作,关键是要建立我们的图像类型层次,这样就可以判断是否是加框的类对象,于是有了如下的类层次:
//图象接口基类
class CPic_Base
{};
//字符串图像类
class CPic_String: public CPic_Base
{};
//加框图像类
class CPic_Frame: public CPic_Base
{}
//纵联接图像类
class CPic_VCat: public CPic_Base
{};
//横联接图像类
class CPic_HCat: public CPic_Base
{};
然后我们考虑如何共享图像数据,这就要用到智能指针了,智能指针在C++里一般有2种实现,一种是STL 里的auto_ptr,还有一种就是基于引用计数。auto_ptr的本质是拥有关系,也就是你拥有了这对象后,别人就不能拥有了,所以这里不符合我们的要求。引用计数是个好东西,对于共享对象特别有用,COM里的IUnknow接口就是基于这个技术的,还有很多脚本语言里变量自动销毁,实际上都是基于引用计数的技术。这里分享一个基于引用计数的智能指针类。
class CRefCountBase
{
public:
CRefCountBase()
{
m_nRefCount = 0;
}
int GetRefCount() const
{
return m_nRefCount;
}
int AddRefCount()
{
return ++m_nRefCount;
}
int SubRefCount()
{
return --m_nRefCount;
}
void ResetRefCount()
{
m_nRefCount = 0;
}
private:
int m_nRefCount;
};
template<typename T>
class CRefPtr
{
public:
T* operator->() const
{
return m_pRawObj;
}
T& operator()() const
{
return *m_pRawObj;
}
T& operator*() const
{
return *m_pRawObj;
}
T* GetPtr() const
{
return m_pRawObj;
}
bool IsNull() const
{
return m_pRawObj == NULL;
}
CRefPtr()
{
m_pRawObj = NULL;
}
CRefPtr(T* p)
{
m_pRawObj = p;
if(p != NULL)
{
p->AddRefCount();
}
}
CRefPtr(const CRefPtr& ref)
{
m_pRawObj = ref.m_pRawObj;
if(m_pRawObj != NULL)
{
m_pRawObj->AddRefCount();
}
}
~CRefPtr()
{
if(m_pRawObj != NULL && m_pRawObj->SubRefCount() == 0)
{
delete m_pRawObj;
}
}
CRefPtr& operator = (const CRefPtr& ref)
{
if(this != &ref)
{
if(m_pRawObj != NULL
&& m_pRawObj->SubRefCount() == 0)
{
delete m_pRawObj;
}
m_pRawObj = ref.m_pRawObj;
if(m_pRawObj != NULL)
{
m_pRawObj->AddRefCount();
}
}
return *this;
}
bool operator == (const CRefPtr& ref) const
{
return m_pRawObj == ref.m_pRawObj;
}
CRefPtr<T> Copy()
{
if(m_pRawObj != NULL)
{
T* p = new T(*m_pRawObj);
p->ResetRefCount();
return p;
}
else
{
return NULL;
}
}
private:
T* m_pRawObj;
};
这样使用这个类
class A: public CRefCountBase
{
Public:
Void fun1();
};
CRefPtr<A> p = new A;
p->fun1();
重新设计我们的CPic_Base,
class CPic_Base: public CRefCountBase
{
public:
virtual ~CPic_Base() {}
//打印输出图像
void Print(std::ostream& os) const;
//返回图像宽度
virtual int GetWidth() const = 0;
//返回图像高度
virtual int GetHeight() const = 0;
//返回某行的图像字符串数据
virtual std::string GetLineData(int nLineIndex) const = 0;
//返回去掉边框的对象
virtual CRefPtr<CPic_Base> GetUnFrame() const { return NULL; }
};
这里Print方法实现就很简单了:
void CPic_Base::Print(std::ostream& os) const
{
for(int i=0; i<GetHeight(); ++i)
{
os << GetLineData(i);
os << "\n";
}
}
然后考虑实现CPic_String
class CPic_String: public CPic_Base
{
public:
CPic_String(char* p[], int nCount);
virtual int GetWidth() const;
virtual int GetHeight() const;
virtual std::string GetLineData(int nLineIndex) const;
protected:
std::vector<std::string> m_arData;
};
这个类里存储真正的字符串图像数据,里面方法的实现也很简单,和最开始的的第一种实现类似,就不详写了。
再考虑实现CPic_Frame
class CPic_Frame: public CPic_Base
{
public:
CPic_Frame(CRefPtr<CPic_Base>& pic);
virtual int GetWidth() const;
virtual int GetHeight() const;
virtual std::string GetLineData(int nLineIndex) const;
virtual CRefPtr<CPic_Base> GetUnFrame() const { return m_pic; }
protected:
CRefPtr<CPic_Base> m_pic;
};
可以看到这里我们引用了一个其他的图像数据,而不是真正存储这些数据,方法实现也很简单, 主要依赖于m_pic所指向的图像类,同时m_pic是个基于引用计数的智能指针, 所以赋值时也没有内存拷贝, 注意GetUnFrame这个方法只有这里返回非NULL,表示只有这种对象支持去边框。
CPic_Frame::CPic_Frame(CRefPtr<CPic_Base>& pic)
: m_pic(pic)
{
_ASSERTE(!m_pic.IsNull());
}
int CPic_Frame::GetWidth() const
{
return m_pic->GetWidth() + 2;
}
int CPic_Frame::GetHeight() const
{
return m_pic->GetHeight() + 2;
}
string CPic_Frame::GetLineData(int nLineIndex) const
{
int nWidth = GetWidth();
int nHeight = GetHeight();
_ASSERTE(nLineIndex < nHeight && nLineIndex >= 0);
if(nLineIndex == 0 //first line and last line
|| nLineIndex == nHeight - 1)
{
int nPadding = nWidth - 2;
return string("+") + string(nPadding, '-') + string("+");
}
else
{
return string("|") + m_pic->GetLineData(nLineIndex - 1) + string("|");
}
}
再考虑实现CPic_VCat
class CPic_VCat: public CPic_Base
{
public:
CPic_VCat(CRefPtr<CPic_Base>& pic1, CRefPtr<CPic_Base>& pic2);
virtual int GetW
补充:软件开发 , C++ ,