第六十九讲 细说List(1)

五一小长假想必大家都回来了,不知道哪天上一讲留给大家的问题是不是都解决了呢?其实这个问题不难,如果我们用C的过程式来设计,这个问题也不难决解,当然为了前进的车轮,这一点我就不说了,如果有朋友用C来写了这个程序,没关系,正好可以和我们今天的实现来对比一下,哪一个更为方便一些,我想大家看完之后一切都会明了。

从面向对象的原则出发,我们可以用类来表示概念,所以,我们可以定义一个抽象类:
———————————-
class Shape{
public:

Shape();



const std::string GetName() const{

return m_name;


}

virtual const double GetArea() const = 0;



virtual copy() const = 0;



virtual ~Shape();


protected:

std::string m_name;


};


———————————–

这个抽象类应该是很好理解的吧,GetName()正是我们希望得到的一个形状的名称,又由于每个形状返回的都是m_name,所以我们将他声明为protected,接下来我们可以定义我们的Circle和Rectangle了。
———————————-
class Circle:public Shape{
public:

Circle():mRadiu(1){

m_name = "

Circle"

;


}


const double GetArea() const{

return PI*mRadiu*mRadiu;


}

void SetRadiu(const double&

d){

mRadiu = d;


}


Shape* copy() const{

return new Circle(*this);


}
private:


double mRadiu;



};


———————————–

也没啥好说的,当然我们得定义好PI:
———————————–
const double PI = 3.1415926;


———————————–
将这句代码放在我们的class的最前面。
我们再来定义一个Rectangle:
————————————
class Rectangle:public Shape{
public:

Rectangle():m_Length(1),m_Width(1){

m_name = "

Rectangle"

;


}

const double GetArea() const{

return m_Length*m_Width;


}

void SetRect(const double&

l,const double&

w){

m_Length = l;


m_Width = w;


}

Shape* copy() const{

return new Rectangle(*this);


}

private:

double m_Length,m_Width;



};


————————————–

我们上面这个class有个地方不好,如果要拿出去给别人用的话,因为我们的SetRect()这个函数不够人性化,通常我会选择先初始化长再初始化宽,但是别人会这样做吗?所以这个方法不人性化,那么怎么定义一个让人易用的接口呢?我们将初始化的顺序交给使用该类的人。所以我们修改一下SetRect,我们让他成为两个函数:
—————————————–
Rectangle&

SetLength(const double l) const;


Rectangle&

SetWidth(const double w) const;


——————————————

有这两个函数后,我们可以按随意的顺序来初始化长宽,而且我们还可以连写:
————————————-
Rectangle r;


r.SetLength(7).SetWidth(6);


————————————–

通常情况下,我们都认为这个图形库算是成型了(当然我们可以添加其他的图形进来),至少有了上面的这些之后,我们就可以使用了:
———————————
Circle c;


Rectangle r;


———————————

当然,这样是没任何问题,但是,当我们要管理这些图形的时候会遇见问题,我们不可以这样用:
—————————-
Shape m_shape[N];


m_shape[0] = c;


m_shape[1] = r;


—————————-

为什么我们会说上面的使用是错的呢?因为Shape是个抽象类,所以是不能够有对象的,就算是有,也不是我们想要的,还记得我们以前说过的切割问题吗?当然还有一种经典也是比较传统的做法,那就是指针或者引用,当然这里我们只能用指针:
———————————
Shape* p1 = new Circle(c);


Shape* p2 = new Rectangle(r);


——————————–
我们这样使用解决了上面我们所说的问题,但是却又带来了另一个问题,Shape的指针是依附于对象的存在的,同时还带来内存管理的问题,如果还不够明白,我们再来看看下面的问题:
——————————–
Shape* m_shape[10];

//使用指针数组可以达到上面的目的
Shape* p1;


Shape* p2;


……
m_shape[i] = new Rectangle(pi);

//这两句话是为了说明问题,知道这个是怎么回事就好。
m_shape[j] = new Circle(pj);


——————————–
上面的代码看上还没什么问题,但接下来,如果我们想要让m_shape[n]指向一个我们新建的一个Shaped对象(将设我们不知道这个类型到底是Circle还是Rectangle),这个对象和m_shape[m]所指的一样,那么我们该怎么用呢?下面的两种方法都不能用的:
——————————–
if(n != m){

delete m_shape[n];



m_shape[n] = m_shape[m];


}
//下面的还是不能用//
if(n != m){

delete m_shape[n];



m_shape[n] = new Shape(m_shape[m]);


}
——————————-
第一个我们不能用,是因为这样一来我们的m_shape[n]和m_shape[m]指向同一个对象,这在一个数组里面是很危险的,第二种方法又回到我们早些时候的问题,因为Shape是个抽象类,他不该有对象,那么怎么解决这个问题呢?这就是我们要引入的代理类,我们用一个类来封装Shape的指针,也就是相当于代理了所有Shape的派生类对象,但是这之前我们还得依赖一个函数,一个复制虚函数,我们希望通过这个函数可以从任何派生类中得到Shape的指针:
———————————-
virtual Shape* copy() const = 0;


———————————-
在派生类中,这个函数的实现很简单,比如:
———————————–
Shape* Circle::copy() const{

return new Circle(*this);


}
———————————–
现在有了这个复制虚函数之后,我们可以很简单的写出一个代理类:
———————————–
class My_Shape{
public:

My_Shape();



My_Shape(const Shape&

s);

//这个函数很重要

My_Shape(const My_Shape&

ms);



My_Shape&

operator=(const Shape&

s);



My_Shape&

operator=(const My_Shape&

ms);



const std::string GetName() const;



const double GetArea() const;



~My_Shape();

private:

Shape *p;



};


———————————-
如果大家从开始都跟着我们的课堂的脚步走的话,那么现在我就不再说那么具体了,我直接贴出实现代码来:
———————————-
#include "

Shape.h"


using namespace My_Code;

My_Shape::My_Shape():p(0){
}

My_Shape::~My_Shape(){

delete p;


}

My_Shape::My_Shape(const Shape&

s):p(s.copy()){
}

My_Shape::My_Shape(const My_Shape&

ms):
p(ms.p?ms.p->copy():0){
}

My_Shape&

My_Shape::operator=(const My_Shape&

ms){

if(this != &

ms){

delete p;


p = ms.p?ms.p->copy():0;


}

return *this;


}

const std::string My_Shape::GetName() const{

if(p==0)

throw "

invalid data!!!"

;


return p->GetName();


}

const double My_Shape::GetArea() const{

if(p==0)

throw "

invalid data!!!"

;


return p->GetArea();


}

My_Shape&

My_Shape::operator=(const Shape&

s){

p = s.copy();


return *this;


}
———————————
到现在,我们可以不用担心Shape是个虚函数了的问题了,他不但可以像早些时候的那样使用,还可以和容器使用,比如我们可以这样用:
———————————
list<My_Shape>mlist
Circle c;


c.SetRadiu(10);


Rectangle r;


r.SetRect(6,5);


mlist.push_back(c);


mlist.push_back(r);


list<My_Shape>::iterator it = mlist.begin();


while(it != mlist.end()){

cout<<it->GetName()<<"

:"

<<it->GetArea()<<endl;


it++;


}
———————————
这个例子有些简单,但却实用,如果大家好好理解的话。
这就是我们上一讲留给大家的问题,大家觉得怎么样呢?是不是和想象中的有些不一样呢?还是认为是我在小题大做了呢?这个问题留给大家去思考。
list,下一讲正式开始说吧。
============================
回复D直接查看目录


原文始发于微信公众号(

C/C++的编程教室

):第六十九讲 细说List(1)

|

发表评论