C++沉思录读书笔记
2012-10-18 15:23
141 查看
C++沉思录读书笔记(1-3章)
C++的核心概念是类
C语言的方案缺乏一个合适的位置来存放辅助的状态信息,这会导致这种方案的扩展很困难
C++鼓励使用类来表示事物,而类就提供了一个理想的位置来防止状态信息
编程就是通过建立抽象来忽略那些我们此刻并不重视的因素,C++使得程序员更容易把程序看做抽象的集合,并且隐藏了用户无须关心的抽象工作细节
C++使得程序员可以用一种更抽象的风格来编程,同时又保留了C中那些有用的和已经深入人心的特色,C++是为实用主义教徒准备的
C中的字符串常量实际是未命名的字符数组的简称,通过下列语句可以表现
char hello[]="hello";
许多成功的系统都是从小系统开始做起的,如unix\C\Basic\FORTRAN\MS-DOS
对于大项目组,交流的开销过于严重,有些所谓的管理知识一种经过精心组织的开销
抽象的概念,能让我们在处理问题的某个部分的同时,完全不必要担心其他部分
汇编语言也包含了一些抽象,内存,寄存器等概念本身就是一种抽象
如果抛开抽象,则程序的运行就得表示成处理器内无数个门电路的状态变换,如果想象力更丰富点,则其实还有更多层次的抽象,有些抽象不是语言的一部分,比如文件,文件只是组织长期存储数据的一种方式
malloc函数实际就可以看成是C语言提供的某种抽象
FORTRAN=Formula Translation(公式翻译)
对于内存管理,有些通过使用垃圾收集(garbage collection)来解决问题,C++则通过在构造函数和析构函数中精确定义如何分配和释放该类所对应的动态资源,这种方式相比垃圾收集机制的明显优势是它对环境的要求很低,内存不要用了就会被释放,而不需要等待垃圾机制发现才能释放
作者有某种反软件工程的思想
C++是一门编程语言,而不是编程环境,这让这门语言富有可移植性和可并存性
作者 yucan1001
C++沉思录读书笔记(4章)-限定用户只能读而不能写类数据成员的方法
限定用户只能读而不能写类数据成员的方法
方法1:可以通过使用常引用,如下所示
class Vector
{
public:
const int & length;//使用构造函数将length绑定到true_length上
//...
private:
int true_length;
};
方法2:通常还是使用如下方式
class Vector
{
public:
int length() const;
//...
private:
int true_length;
};
如果一个类需要显式的调用构造函数,也就是说它没有一个默认构造函数,则试图生成该类对象的数组是非法的
构造函数的用途是用一种明确定义的状态来设置对象
假设D继承自B,只要存在对指向D类型对象的B*指针执行delete表达式,就需要对B加上一个虚析构函数,无论B是否有动态分配内存的数据成员
如果不想用户能够复制类的成员,就声明复制构造函数为私有的
类X的赋值操作符应该返回X&,注意对象的自我复制问题,赋值操作符的一般形式如下:
X& X::operator=(const X& x)
{
if(this != &x)
{...}
return *this;
}
一定要记得在复制构造函数和赋值操作符的参数类型中加上const,由于绑定一个非const引用到一个临时的对象是非法的,所以使用X::X(X&)作为复制构造函数不会允许复制任何特殊表达式的结果,X& X::operator=(const X&)也是如此
只有当函数想改变参数时,它才应该有不用const声明的引用参数
C++沉思录读书笔记(5章)-代理类
问题描述:如何设计一个容器,能使得它能包含类型不同但是彼此相关的对象?
问题描述:如何复制编译时类型未知的对象?
解决方案一:使用容器存储指向对象的指针,通过继承来处理类型不同的对象。
这个方案有严重的问题:
问题一:无法让容器中的指针指向一个局部变量,例如:
Vehicle * parking_lot[1000];
Automobile x = /*..*/
Parking_lot[num_vehicles++] = &x;
一旦x不存在了,Parking_lot就不知道指向哪里了。
我们可以变通一下,让放入Parking_lot的值,指向原来对象的副本,而不直接存放原来对象的地址,例如:
Parking_lot[num_vehicles++]= new Automobile(x);
这个改进方案又带来一个问题,那就是我们必须知道x的静态类型。假如我们想让parking_lot[p]指向的Vehicle的类型和值与parking_lot[q]指向的对象相同,那在我们无法得知parking_lot[q]的静态类型的情况下,我们无法做到这点。
解决方案二(方案一的改进):增加虚拟复制函数来复制编译时类型未知的对象,代码描述如下
class Vehicle
{
public:
virtual Vehicle * copy() const = 0;
/**/
};//Vehicle的派生类都自实现copy函数
class Truck : public RoadVehicle
{
public:
Vehicle* copy() const { return new Truck(*this); }
/**/
};
假如我们想让parking_lot[p]指向的Vehicle的类型和值与parking_lot[q]指向的对象相同,可以简单是使用如下代码:parking_lot[p] =parking_lot[q].copy();
解决方案三(与方案二作用类似,但实现手段有不同):使用代理类
代理类:行为与原类相似,又潜在的表示了所有继承自原类的类
Vehicle类的代理类VehicleSurrogate的描述如下:
class VehicleSurrogate
{
public:
VehicleSurrogate() : vp(NULL) {};
VehicleSurrogate(const Vehicle& v) : vp(v.copy()) {};
~VehicleSurrogate() {};
VehicleSurrogate(const VehicleSurrogate& v) : vp(v.vp ? v.vp->copy() : NULL) {}; //v.vp非零检测
VehicleSurrogate& operator=(const VehicleSurrogate& v)
{
if (this != &v) // 确保没有将代理赋值给它自身
{
delete vp;
vp = (v.vp ? v.vp->copy() : NULL);
}
return *this;
};
//来自类Vehicle的操作
void start()
{
if (vp == 0)
throw "empty VehicleSurrogate.start()";
vp->start();
};
private:
Vehicle* vp;
};
完成了这些工作后,就很容易定义我们所需要的操作了,如下:
VehicleSurrogate parking_lot[1000];
Automobile x = /*..*/
Parking_lot[num_vehicles++] = x;
//Parking_lot[num_vehicles++] = VehicleSurrogate(x);//此语句与上条语句作用一致
/*...*/
Parking_lot[p] = Parking_lot[q];
C++沉思录读书笔记(6章)-句柄:第一部分
创建代理会复制所代理的对象,如何避免这些复制呢?
可以通过句柄(handle)类来实现。句柄类和智能指针有点类似,通过重载句柄类的*和->操作符,可将句柄类改造称为智能指针。
从效果上来说,handle就是一种只包含单个对象的容器,它通过允许多个handle对象指向同一个对象来避免复制。
定义句柄类,我们还需要新定义一个类来容纳被引用类对象的引用计数和被引用类对象本身。
示例代码如下
#include <iostream>
using namespace std;
class Point
{
private:
int xval, yval;
public:
Point() : xval(0), yval(0)
{
cout << "Point" << endl;
}
Point(int x, int y) : xval(x), yval(y)
{
cout << "Point" << endl;
}
int x() const { return xval;}
int y() const { return yval;}
Point& x(int xv)
{
xval = xv;
return *this;
}
Point& y(int yv)
{
yval = yv;
return *this;
}
~Point()
{
cout << "~Point" << endl;
}
};
class UPoint //这个类对用户来说是不可见的, 就是一个间接层
{
friend class Handle;
Point p; //Point 对象
int u; //计数器
UPoint():u(1) //提供所有的point的构造方式
{
cout << "UPoint::" << u << endl;
}
UPoint(int x, int y):p(x, y), u(1)
{
cout << "UPoint::" << u << endl;
}
UPoint(const Point &p0) : p(p0), u(1)
{
cout << "UPoint::" << u << endl;
}
~UPoint()
{
cout << "~UPoint::" << u << endl;
}
};
class Handle
{
private:
UPoint *up; //和间接层UPoint打交道了
public:
Handle();
Handle(int, int);
Handle(const Point&);
Handle(const Handle&);
Handle& operator=(const Handle&);
~Handle();
int x() const;
Handle& x(int);
int y() const;
Handle& y(int);
};
Handle::Handle():up(new UPoint)
{
cout << "Handle::" <<up->u << endl;
}
Handle::Handle(int x, int y):up(new UPoint(x, y)) //按创建Point的方式构造handle,handle->UPoint->Point
{
cout << "Handle::" <<up->u << endl;
}
Handle::Handle(const Point &p):up(new UPoint(p)) //创建Point的副本
{
cout << "Handle::" <<up->u << endl;
}
Handle::~Handle()
{
if(--up->u == 0)
{
cout << "~Handle::" <<up->u << endl;
delete up;
}
else
{
cout << "~Handle::" <<up->u << endl;
}
}
Handle::Handle(const Handle &h):up(h.up)
{
++up->u; //此处复制的是handle,但是底层的point对象并未复制,只是引用计数加1
cout << "Handle::" <<up->u << endl;
}
Handle& Handle::operator=(const Handle &h)
{
++h.up->u; //右边的对象引用计数加1,左边的减1
if(--up->u == 0)
delete up;
up = h.up;
cout << "Handle::" <<up->u << endl;
return *this;
}
int Handle::x() const
{
return up->p.x();
}
int Handle::y() const
{
return up->p.y();
}
Handle& Handle::x(int x0)//假设句柄为指针语义
{
up->p.x(x0);
return *this;
}
Handle& Handle::y(int y0)
{
up->p.y(y0);
return *this;
}
/*
Handle& Handle::x(int x0)//假设句柄为值语义(写时复制)
{
if(up->p != 1)
{
--up->u;
up = new UPoint(up->p);
}
up->p.x(x0);
return *this;
}
Handle& Handle::y(int y0)
{
if(up->p != 1)
{
--up->u;
up = new UPoint(up->p);
}
up->p.y(y0);
return *this;
}
*/
void f(Handle h) //测试函数,复制handle,但未复制Point
{
cout << h.x() << h.y() << endl;
}
int main()
{
Handle h(3,5);
cout << h.x() << h.y() << endl;
f(h);
Point o(10,9);
Handle a(o);
Handle b(a);
return 0;
}
C++沉思录读书笔记(7章)-句柄:第二部分
上一回讨论的句柄技术有一个明显的缺点:为了将句柄捆绑到类T的对象上,必须要新定义一个具有类型为T的成员对象的新类。
这个毛病相当麻烦,如果想新设计一个类的句柄类,就需要新定义两个类。
C++之父提到过一种定义句柄类的技术可以弥补这一个缺点,主要思想就是将引用技术从数据中分离出来,把引用计数放到句柄类自己的对象之中。
示例代码如下所示:
#include <iostream>
using namespace std;
//-----------------------------------------
class Point
{
private:
int xval,yval;
public:
Point():xval(0),yval(0){}
Point(int x,int y):xval(x),yval(y){}
int x()const{return xval;}
int y()const{return yval;}
Point& x(int xv){xval=xv;return *this;}
Point& y(int yv){yval=yv;return *this;}
};
//------------------------------------------------------
class UseCount
{
private:
int* p;
public:
UseCount();
UseCount(const UseCount&);
UseCount& operator=(const UseCount&);
~UseCount();
bool only();
bool reattach(const UseCount&);
bool make_only();
};
UseCount::UseCount():p(new int(1)){}
UseCount::UseCount(const UseCount&u):p(u.p){++*p;}
UseCount::~UseCount()
{
if (--*p==0)
{
delete p;
}
}
bool UseCount::only()
{
return *p==1;
}
bool UseCount::reattach(const UseCount& u)
{
++*u.p;
if (--*p==0)
{
delete p;
p=u.p;
return true;
}
p=u.p;
return false;
}
bool UseCount::make_only()
{
if (*p==1)
return false;
--*p;
p=new int(1);
return true;
}
//-------------------------------------------
class Handle
{
private:
Point* p;
UseCount u;
public:
Handle();
Handle(int,int);
Handle(const Point&);
Handle(const Handle&);
Handle& operator =(const Handle&);
~Handle();
int x()const;
Handle&x(int);
int y()const;
Handle&y(int);
};
Handle::Handle():p(new Point){}
Handle::Handle(int x,int y):p(new Point(x,y)){}
Handle::Handle(const Point&p0):p(new Point(p0)){}
Handle::Handle(const Handle&h):u(h.u),p(h.p){}
Handle::~Handle()
{
if (u.only())
{
delete p;
}
}
Handle& Handle::operator=(const Handle &h)
{
if (u.reattach(h.u))
delete p;
p=h.p;
return *this;
/* //自定义版本不使用reattach辅助方法,自认为更容易理解,但很丑陋
(*(h.u.p))++;
if(*(u.p) == 1)
{
delete p;
delete u.p;
}
else
(*(u.p))--;
u.p = h.u.p;
p = h.p;
return *this;
*/
}
int Handle::x()const
{
return p->x();
}
int Handle::y()const
{
return p->y();
}
Handle& Handle::x(int x0)
{
if (u.make_only())
p=new Point(*p);
p->x(x0);
return *this;
/* //自定义版本,不使用辅助方法make_only,自认为更容易理解,但很丑陋
if(*(u.p) == 1)
p->x(x0);
else
{
(*(u.p))--;
u.p = new int(1);
p = new Point(*p);
p->x(x0);
}
retrun *this;
*/
}
Handle& Handle::y(int y0)
{
if (u.make_only())
p=new Point(*p);
p->y(y0);
return *this;
/* //自定义版本,不使用辅助方法make_only,自认为更容易理解,但很丑陋
if(*(u.p) == 1)
p->y(x0);
else
{
(*(u.p))--;
u.p = new int(1);
p = new Point(*p);
p->y(x0);
}
retrun *this;
*/
}
//---------------------------------------------------
int main()
{
Handle h(3,4);
Handle h2 = h;
h2.x(5);
int n = h.x();
cout<<n<<endl;
return 0;
}
这个策略的一个重要优势:UseCount类可以在不了解其使用者任何信息的情况下与之合为一体。这样一来,我们就可以把这个类当成句柄实现的一部分,与各种不同的数据结构协同工作。
C++沉思录读书笔记(8章)-一个面向对象程序范例2
此回是针对上回所设计方案的扩展,下面将会演示
(1)如何增加计算表达式的值的功能
(2)如何增加新的节点类型(已经有表示单个数、一元操作符、二元操作符的节点类型Int_node\Unary_node\Binary_node,将演示如何增加三元操作符(Ternary_node))
完整代码如下:
#include <iostream>
#include <string>
using namespace std;
class Expr_node
{
friend class Expr; //友元类可以被继承
int use; //引用计数
public:
virtual void print(ostream&) const = 0;
public:
Expr_node():use(1) {}
virtual ~Expr_node() {}
virtual int eval() const = 0;
};
class Expr //句柄类
{
friend ostream& operator<<(ostream &o, const Expr &e);
private:
Expr_node *p; //指向基类的指针
public:
Expr(int n);
Expr(const string &op, Expr t);
Expr(const string &op, Expr left, Expr right);
Expr(const string &op, Expr left, Expr middle, Expr right);
Expr(const Expr &t);
Expr& operator=(const Expr&);
~Expr()
{
if(--p->use == 0)
delete p;
}
int eval() const
{
return p->eval();
}
};
class Int_node: public Expr_node
{
private:
int n;
public:
Int_node(int k):n(k) {}
void print(ostream &o) const
{
o << n;
}
int eval() const { return n; }
};
class Unary_node: public Expr_node
{
private:
//friend class Expr;
string op;
Expr opnd;
public:
Unary_node(const string &a, Expr b):op(a), opnd(b) {}
void print(ostream &o) const
{
o << "(" << op << opnd << ")";
}
int eval() const
{
if(op == "-")
return -opnd.eval();
throw "error!错误的一元操作符";
}
};
class Binary_node: public Expr_node
{
private:
//friend class Expr;
string op;
Expr left;
Expr right;
public:
Binary_node(const string &a, Expr b, Expr c):op(a), left(b), right(c) {}
void print(ostream &o) const
{
o << "(" << left << op << right << ")";
}
int eval() const
{
int op1 = left.eval();
int op2 = right.eval();
if(op == "-") return op1 - op2;
if(op == "+") return op1 + op2;
if(op == "*") return op1 * op2;
if(op == "/") return op1 / op2;
throw "error! 错误的二元操作符!";
}
};
class Ternary_node: public Expr_node
{
private:
string op;
Expr left;
Expr middle;
Expr right;
public:
Ternary_node(const string& a, Expr b, Expr c, Expr d):op(a), left(b), middle(c), right(d) {}
void print(ostream &o) const
{
o << "(" << left << "?" << middle << ":" << right << ")";
}
int eval() const
{
if(left.eval())
return middle.eval();
else
return right.eval();
}
};
Expr::Expr(int n) { p = new Int_node(n); }
Expr::Expr(const string& op, Expr t) { p = new Unary_node(op, t); }
Expr::Expr(const string &op, Expr left, Expr right) { p = new Binary_node(op, left, right); }
Expr::Expr(const string &op, Expr left, Expr middle, Expr right) { p = new Ternary_node(op, left, middle, right); }
Expr::Expr(const Expr& t) { p = t.p; ++p->use; }
Expr& Expr::operator=(const Expr& rhs)
{
rhs.p->use++;
if(--p->use == 0)
delete p;
p = rhs.p;
return *this;
}
ostream& operator<<(ostream &o, const Expr &e)
{
e.p->print(o);
return o;
}
void main()
{
Expr t = Expr("*",
Expr("-", Expr(5)),
Expr("+", Expr(3), Expr(4)));
cout << t << "=" << t.eval() << endl;
Expr t2 = Expr("-", Expr(5), Expr("?:", Expr(0), Expr(1), Expr(2)));
cout << t2 << "=" << t2.eval() << endl;
}
运行结果如下:
((-5)*(3+4))=-3
(5-(0?1:2))=3
C++沉思录课堂练习另解--消息发送(优化版)
缘起,看到一篇文章(懒得超链接),重新研究《C++沉思录》的那一个课堂练习,综合利用好几种设计模式,并且很好地遵守结合的面向对象的原则,嗯,不管怎么样,还是表扬这位同学的面向对象与设计模式的功力,确实不容易。只是,在下从模式堆里爬出来之后,已经对模式大倒胃口,看到这么小的练习,居然要用上这么多模式,一时技痒,有必要这样吗?对我来说,代码中每引入一种模式,就表示要添加各种接口、虚函数,然后,免不了就要用到继承,这些,我非常深痛恶疾,因为,这都意味着间接层越来越厚了,偏离了问题的本质。
先看看沉思录对于这道题的描述,嗯,这个,还是请各位自己去看看书是怎么写的,作者提供了两种方案,佩服作者和译者的文笔,两种方案解释得相当细腻。下面,尝试另一种解决方式。先秀一下代码的运行效果。
对于这个课堂练习,其中,给图片加边框,最适合于用装饰模式来解决。但是,两幅图片的横接、竖接,怕是就不那么容易了。但是,首先,要对问题重新描述一下,现实中,一张图片,可以给予添加一层又一层的片框,也可以和其他的图片组合在一块,或横,或竖,……,但是,图片,始终只有一张,对它处理之后,它就一直是那个样子了,不可能同时看到它的两个样子。如果,拿这张图片去进行复制,那又自是另当别论,但那已经是另外一张新图片了。
以下的设计用上了消息,如果有现成消息框架给予支持,实现起来就会很简单。但是,即使没有消息框架的支持,利用消息发送来解决这个问题,也是相当小儿科的事情。为了突出重点,忽略了各种异常处理,没有优化,也不管什么编程风格,纯粹直奔主题。解决这个例子的最重要一点,就在于打印图片时,要一行一行地从顶到底顺次打印下来。因此,先定义这个图片可以发送的消息,它必须处理两条消息,图片的高度(即行数),和图片每一行的数据。即如下所示
enum {PM_HEIGHT, PM_ROW};
struct PictureMsg
{
typedef int (*ProcPictureMsg)(void* pThis, int nMessage, void* param1, void* param2);
public:
void* pThis;
ProcPictureMsg proc;
int GetHeight()
{
return (*proc)(pThis, PM_HEIGHT, NULL, NULL);
}
int GetRow(int nRow, string& sRow)
{
return (*proc)(pThis, PM_ROW, (void*)nRow, (void*)&sRow);
}
};
ostream& operator << (ostream& out, PictureMsg& msg)
{
int nHeight = msg.GetHeight();
string sRow;
for (int nRow = 0; nRow<nHeight; nRow++)
{
sRow.clear();
msg.GetRow(nRow, sRow);
out << sRow;
out << endl;
}
return out;
} www.2cto.com
其中的回调函数用以发送消息,本来回调函数搞成成员函数会更加好,但由于c++的成员函数是一个大坑,而我又不想引入function或者其他delegate等庞然大物。为了让客户更加方便地发送消息,特意编写了两个辅助函数。
但是,要发送消息给对象时,首先要求这个对象必须已经活着了,也即是要创建一个能接收消息的对象。由于没有消息框架的支持,同时,也为了使用上的方便,我只好编写一个具体的类继承于图片消息结构,并提供了初步的消息处理,嗯,用到了继承,很难受。
好了,接下来就可以全心全意地对消息进行特别地处理了。要求,对图片的各种装饰操作,不能影响到图片原有的功能,也不要求改变用户的代码。或者说,图片本身并不知道自己已经被外界做了各种各样的处理,它还是按照原来的样子处理各种消息。下面,只是实现了两种装饰操作,加边框与横接,至于竖接,实在很容易实现的。此外,由于在实现装饰操作时,需要用到图片的宽度,因此,图片消息又添加了一条新的消息,用以处理图片的宽度。
由于是玩具代码,在下也没有做太大的优化,有几行代码显得特别难看,特别是横接的装饰方法,强制指定了左边的图片为操作后的结果,还有各个装饰类的析构函数,可以撤消对图片的装饰,以及添加新的消息,以返回图片的拷贝,或返回当前正在进行的装饰操作,或者装饰类的动态创建等等。总之,可优化升级的方法多种多样,而且都不会影响代码的结构。以下,是一陀代码,从第一版上稍加优化的,不读也罢。
enum { PM_WIDTH, PM_HEIGHT, PM_ROW};
struct PictureMsg
{
typedef int (*ProcPictureMsg)(void* pThis, int nMessage, void* param1, void* param2);
public:
void* pThis;
ProcPictureMsg proc;
int GetWidth()
{
return (*proc)(pThis, PM_WIDTH, NULL, NULL);
}
int GetHeight()
{
return (*proc)(pThis, PM_HEIGHT, NULL, NULL);
}
int GetRow(int nRow, string& sRow)
{
return (*proc)(pThis, PM_ROW, (void*)nRow, (void*)&sRow);
}
};
ostream& operator << (ostream& out, PictureMsg& msg)
{
int nHeight = msg.GetHeight();
string sRow;
for (int nRow = 0; nRow<nHeight; nRow++)
{
sRow.clear();
msg.GetRow(nRow, sRow);
out << sRow;
out << endl;
}
return out;
}
class CPicture : public PictureMsg
{
public:
CPicture(const char* pDatas[], int nCount)
{
m_pDatas = pDatas;
m_nCount = nCount;
m_nWidth = 0;
for (int i=0; i<m_nCount; i++)
{
int nLen = strlen(m_pDatas[i]);
if (m_nWidth < nLen)
m_nWidth = nLen;
}
pThis = this;
proc = HandleMsg;
}
private:
const char** m_pDatas;
int m_nCount;
int m_nWidth;
static int HandleMsg(void* pThis, int nMessage, void* param1, void* param2);
};
int CPicture::HandleMsg(void* pThis, int nMessage, void* param1, void* param2)
{
CPicture* pSelf = (CPicture*)pThis;
switch (nMessage)
{
case PM_WIDTH:
return pSelf->m_nWidth;
case PM_HEIGHT:
return pSelf->m_nCount;
break;
case PM_ROW:
int nRow = (int)param1;
string& sRow = *(string*)param2;
if (nRow >= pSelf->m_nCount)
break;
int i=0;
for (; pSelf->m_pDatas[nRow][i] != 0; i++)
sRow.push_back(pSelf->m_pDatas[nRow][i]);
for (; i<pSelf->m_nWidth; i++)
sRow.push_back(' ');
}
return 0;
}
class CFrameDecorater
{
public:
CFrameDecorater(PictureMsg& imp)
{
m_PrevImp = imp;
imp.pThis = this;
imp.proc = HandleMsg;
}
private:
PictureMsg m_PrevImp;
static int HandleMsg(void* pThis, int nMessage, void* param1, void* param2);
};
int CFrameDecorater::HandleMsg(void* pThis, int nMessage, void* param1, void* param2)
{
CFrameDecorater* pSelf = (CFrameDecorater*)pThis;
PictureMsg& prevImp = pSelf->m_PrevImp;
switch (nMessage)
{
case PM_WIDTH:
return prevImp.GetWidth()+2;
case PM_HEIGHT:
return prevImp.GetHeight()+2;
case PM_ROW:
int nRow = (int)param1;
string& sRow = *(string*)param2;
bool bMyRow = nRow == 0 || nRow>prevImp.GetHeight();
if (nRow >= prevImp.GetWidth()+2)
break;
if (nRow == 0 || nRow>prevImp.GetHeight())
{
sRow.push_back('+');
for (int i=0; i<prevImp.GetWidth(); i++)
sRow.push_back('-');
sRow.push_back('+');
}
else
{
sRow.push_back('|');
prevImp.GetRow(nRow-1, sRow);
sRow.push_back('|');
}
}
return 0;
}
class CHorseDecorater
{
public:
CHorseDecorater(PictureMsg& impLeft, PictureMsg& impRight)
{
m_Left = impLeft;
m_Right = impRight;
impLeft.pThis = this;
impLeft.proc = HandleMsg;
}
private:
PictureMsg m_Left;
PictureMsg m_Right;
static int HandleMsg(void* pThis, int nMessage, void* param1, void* param2);
static void PrintRow(PictureMsg& pict, int nRow, string& sRow)
{
if (nRow < pict.GetHeight())
pict.GetRow(nRow, sRow);
else
{
for (int i=0; i<pict.GetWidth(); i++)
sRow.push_back(' ');
}
}
};
int CHorseDecorater::HandleMsg(void* pThis, int nMessage, void* param1, void* param2)
{
CHorseDecorater* pSelf = (CHorseDecorater*)pThis;
PictureMsg& pictLeft = pSelf->m_Left;
PictureMsg& pictRight = pSelf->m_Right;
switch (nMessage)
{
case PM_WIDTH:
return pictLeft.GetWidth()+pictRight.GetWidth();;
case PM_HEIGHT:
return max(pictLeft.GetHeight(), pictRight.GetHeight());
case PM_ROW:
int nRow = (int)param1;
string& sRow = *(string*)param2;
PrintRow(pictLeft, nRow, sRow);
PrintRow(pictRight, nRow, sRow);
}
return 0;
}
int main()
{
const char* init1[] = {"Paris", "in the", "Spring", "HaHa"};
CPicture pict1(init1, 3);
//cout << pict1;
CFrameDecorater framer1(pict1);
//cout << pict1;
CFrameDecorater framer2(pict1);
//cout << pict1;
CPicture pict2(init1, 4);
CHorseDecorater hors(pict1, pict2);
//cout << pict1;
CFrameDecorater framerHorse(pict1);
//cout << pict1;
CHorseDecorater hors2( pict2, pict1);
//cout << pict2;
CFrameDecorater framer3(pict2);
CHorseDecorater hors3( pict1, pict2);
cout << pict1;
return 0;
}
消息发送VS设计模式 C++沉思录的一练习题的另解
缘起,看到一遍文章,题材取自于《C++沉思录》,综合利用好几种设计模式,并且结合所谓的面向对象的技巧,然后洋洋自得,并且在最后,还反问:“有更好的解决方案吗?”。本座暗叹,又一个设计模式的毒害者。以下,就提出另一种解决方案。
首先,先声明一点,现实中,一张图片,可以给予添加一层又一层片框,也可以和其他的图片组合在一块,或横,或竖,……,但是,图片,始终只有一张,对它处理之后,它就一直是那个样子了,不可能同时看到它的两个样子,或加边框,或和其他照片组合在一块。如果,拿这张图片去进行复制,那又自是另当别论,但那已经是另外一第图片了。当然,对以下例子稍加修改,就能支持修饰的图片的复制操作。
本座最近在写一套消息框架,如果这个例子能用上那套框架,写起来就会很简单。但是,即使没有消息框架的支持,利用消息发送来解决这个问题,也是相当小儿科的事情。为了突出重点,忽略了各种异常处理,没有优化,也不管什么编程风格,纯粹直奔主题。解决这个例子的最重要一点,就在于打印图片时,要一行一行地从顶到底顺次打印下来。 www.2cto.com
typedef int (*ProcPictureImp)(void* pThis, int nMessage, void* param1, void* param2);
enum { PM_WIDTH, PM_HEIGHT, PM_PRINT_ROW};
struct PictureImp
{
void* pThis;
ProcPictureImp proc;
int GetWidth()
{
return (*proc)(pThis, PM_WIDTH, NULL, NULL);
}
int GetHeight()
{
return (*proc)(pThis, PM_HEIGHT, NULL, NULL);
}
int PrintRow(int nRow, ostream& out)
{
return (*proc)(pThis, PM_PRINT_ROW, (void*)nRow, (void*)&out);
}
};
class CPicture
{
public:
CPicture(const char* pDatas[], int nCount)
{
m_pDatas = pDatas;
m_nCount = nCount;
m_nWidth = 0;
for (int i=0; i<m_nCount; i++)
{
int nLen = strlen(m_pDatas[i]);
if (m_nWidth < nLen)
m_nWidth = nLen;
}
m_Imp.pThis = this;
m_Imp.proc = HandleMsg;
}
void Print(ostream& out);
public:
PictureImp m_Imp;
private:
const char** m_pDatas;
int m_nCount;
int m_nWidth;
static int HandleMsg(void* pThis, int nMessage, void* param1, void* param2);
};
int CPicture::HandleMsg(void* pThis, int nMessage, void* param1, void* param2)
{
CPicture* pSelf = (CPicture*)pThis;
switch (nMessage)
{
case PM_WIDTH:
return pSelf->m_nWidth;
case PM_HEIGHT:
return pSelf->m_nCount;
break;
case PM_PRINT_ROW:
int nRow = (int)param1;
ostream& out = *(ostream*)param2;
if (nRow >= pSelf->m_nCount)
break;
int i=0;
for (; pSelf->m_pDatas[nRow][i] != 0; i++)
out << pSelf->m_pDatas[nRow][i];
for (; i<pSelf->m_nWidth; i++)
out << ' ';
}
return 0;
}
void CPicture::Print(ostream& out)
{
int nHeight = m_Imp.GetHeight();
for (int nRow = 0; nRow<nHeight; nRow++)
{
m_Imp.PrintRow(nRow, out);
out << endl;
}
}
class CFrameDecorater
{
public:
CFrameDecorater(PictureImp& imp)
{
m_PrevImp = imp;
imp.pThis = this;
imp.proc = HandleMsg;
}
private:
PictureImp m_PrevImp;
static int HandleMsg(void* pThis, int nMessage, void* param1, void* param2);
};
int CFrameDecorater::HandleMsg(void* pThis, int nMessage, void* param1, void* param2)
{
CFrameDecorater* pSelf = (CFrameDecorater*)pThis;
PictureImp& prevImp = pSelf->m_PrevImp;
switch (nMessage)
{
case PM_WIDTH:
return prevImp.GetWidth()+2;
case PM_HEIGHT:
return prevImp.GetHeight()+2;
case PM_PRINT_ROW:
int nRow = (int)param1;
ostream& out = *(ostream*)param2;
bool bMyRow = nRow == 0 || nRow>prevImp.GetHeight();
if (nRow >= prevImp.GetWidth()+2)
break;
if (nRow == 0 || nRow>prevImp.GetHeight())
{
out << '+';
for (int i=0; i<prevImp.GetWidth(); i++)
out << '-';
out << '+';
}
else
{
out << '|';
prevImp.PrintRow(nRow-1, out);
out << '|';
}
}
return 0;
}
class CHorseDecorater
{
public:
CHorseDecorater(PictureImp& impLeft, PictureImp& impRight)
{
m_Left = impLeft;
m_Right = impRight;
impLeft.pThis = this;
impLeft.proc = HandleMsg;
impRight.pThis = this;
impRight.proc = HandleMsg;
}
private:
PictureImp m_Left;
PictureImp m_Right;
static int HandleMsg(void* pThis, int nMessage, void* param1, void* param2);
static void PrintRow(PictureImp& pict, int nRow, ostream& out)
{
if (nRow < pict.GetHeight())
pict.PrintRow(nRow, out);
else
{
for (int i=0; i<pict.GetWidth(); i++)
out << ' ';
}
}
};
int CHorseDecorater::HandleMsg(void* pThis, int nMessage, void* param1, void* param2)
{
CHorseDecorater* pSelf = (CHorseDecorater*)pThis;
PictureImp& pictLeft = pSelf->m_Left;
PictureImp& pictRight = pSelf->m_Right;
switch (nMessage)
{
case PM_WIDTH:
return pictLeft.GetWidth()+pictRight.GetWidth();;
case PM_HEIGHT:
return max(pictLeft.GetHeight(), pictRight.GetHeight());
case PM_PRINT_ROW:
int nRow = (int)param1;
ostream& out = *(ostream*)param2;
PrintRow(pictLeft, nRow, out);
PrintRow(pictRight, nRow, out);
}
return 0;
}
int main()
{
const char* init1[] = {"Paris", "in the", "Spring", "HaHa"};
CPicture pict1(init1, 3);
pict1.Print(cout);
CFrameDecorater framer1(pict1.m_Imp);
//pict1.Print(cout);
CFrameDecorater framer2(pict1.m_Imp);
pict1.Print(cout);
CPicture pict2(init1, 4);
CHorseDecorater hors(pict1.m_Imp, pict2.m_Imp);
pict1.Print(cout);
CFrameDecorater framerHorse(pict1.m_Imp);
pict1.Print(cout);
return 0;
}
嗯,明眼人一眼就看出来了,切,这不过是模仿WINDOWS的窗口消息处理函数的伎俩而已。没有继承、没有虚函数,何其美妙哉!
又,注意到main()中的代码,虽然pict1被装饰了好几次,但其图片打印,始终都是pict1.Print(cout)。所谓不管八风乱起,我自岿然不动
C++的核心概念是类
C语言的方案缺乏一个合适的位置来存放辅助的状态信息,这会导致这种方案的扩展很困难
C++鼓励使用类来表示事物,而类就提供了一个理想的位置来防止状态信息
编程就是通过建立抽象来忽略那些我们此刻并不重视的因素,C++使得程序员更容易把程序看做抽象的集合,并且隐藏了用户无须关心的抽象工作细节
C++使得程序员可以用一种更抽象的风格来编程,同时又保留了C中那些有用的和已经深入人心的特色,C++是为实用主义教徒准备的
C中的字符串常量实际是未命名的字符数组的简称,通过下列语句可以表现
char hello[]="hello";
许多成功的系统都是从小系统开始做起的,如unix\C\Basic\FORTRAN\MS-DOS
对于大项目组,交流的开销过于严重,有些所谓的管理知识一种经过精心组织的开销
抽象的概念,能让我们在处理问题的某个部分的同时,完全不必要担心其他部分
汇编语言也包含了一些抽象,内存,寄存器等概念本身就是一种抽象
如果抛开抽象,则程序的运行就得表示成处理器内无数个门电路的状态变换,如果想象力更丰富点,则其实还有更多层次的抽象,有些抽象不是语言的一部分,比如文件,文件只是组织长期存储数据的一种方式
malloc函数实际就可以看成是C语言提供的某种抽象
FORTRAN=Formula Translation(公式翻译)
对于内存管理,有些通过使用垃圾收集(garbage collection)来解决问题,C++则通过在构造函数和析构函数中精确定义如何分配和释放该类所对应的动态资源,这种方式相比垃圾收集机制的明显优势是它对环境的要求很低,内存不要用了就会被释放,而不需要等待垃圾机制发现才能释放
作者有某种反软件工程的思想
C++是一门编程语言,而不是编程环境,这让这门语言富有可移植性和可并存性
作者 yucan1001
C++沉思录读书笔记(4章)-限定用户只能读而不能写类数据成员的方法
限定用户只能读而不能写类数据成员的方法
方法1:可以通过使用常引用,如下所示
class Vector
{
public:
const int & length;//使用构造函数将length绑定到true_length上
//...
private:
int true_length;
};
方法2:通常还是使用如下方式
class Vector
{
public:
int length() const;
//...
private:
int true_length;
};
如果一个类需要显式的调用构造函数,也就是说它没有一个默认构造函数,则试图生成该类对象的数组是非法的
构造函数的用途是用一种明确定义的状态来设置对象
假设D继承自B,只要存在对指向D类型对象的B*指针执行delete表达式,就需要对B加上一个虚析构函数,无论B是否有动态分配内存的数据成员
如果不想用户能够复制类的成员,就声明复制构造函数为私有的
类X的赋值操作符应该返回X&,注意对象的自我复制问题,赋值操作符的一般形式如下:
X& X::operator=(const X& x)
{
if(this != &x)
{...}
return *this;
}
一定要记得在复制构造函数和赋值操作符的参数类型中加上const,由于绑定一个非const引用到一个临时的对象是非法的,所以使用X::X(X&)作为复制构造函数不会允许复制任何特殊表达式的结果,X& X::operator=(const X&)也是如此
只有当函数想改变参数时,它才应该有不用const声明的引用参数
C++沉思录读书笔记(5章)-代理类
问题描述:如何设计一个容器,能使得它能包含类型不同但是彼此相关的对象?
问题描述:如何复制编译时类型未知的对象?
解决方案一:使用容器存储指向对象的指针,通过继承来处理类型不同的对象。
这个方案有严重的问题:
问题一:无法让容器中的指针指向一个局部变量,例如:
Vehicle * parking_lot[1000];
Automobile x = /*..*/
Parking_lot[num_vehicles++] = &x;
一旦x不存在了,Parking_lot就不知道指向哪里了。
我们可以变通一下,让放入Parking_lot的值,指向原来对象的副本,而不直接存放原来对象的地址,例如:
Parking_lot[num_vehicles++]= new Automobile(x);
这个改进方案又带来一个问题,那就是我们必须知道x的静态类型。假如我们想让parking_lot[p]指向的Vehicle的类型和值与parking_lot[q]指向的对象相同,那在我们无法得知parking_lot[q]的静态类型的情况下,我们无法做到这点。
解决方案二(方案一的改进):增加虚拟复制函数来复制编译时类型未知的对象,代码描述如下
class Vehicle
{
public:
virtual Vehicle * copy() const = 0;
/**/
};//Vehicle的派生类都自实现copy函数
class Truck : public RoadVehicle
{
public:
Vehicle* copy() const { return new Truck(*this); }
/**/
};
假如我们想让parking_lot[p]指向的Vehicle的类型和值与parking_lot[q]指向的对象相同,可以简单是使用如下代码:parking_lot[p] =parking_lot[q].copy();
解决方案三(与方案二作用类似,但实现手段有不同):使用代理类
代理类:行为与原类相似,又潜在的表示了所有继承自原类的类
Vehicle类的代理类VehicleSurrogate的描述如下:
class VehicleSurrogate
{
public:
VehicleSurrogate() : vp(NULL) {};
VehicleSurrogate(const Vehicle& v) : vp(v.copy()) {};
~VehicleSurrogate() {};
VehicleSurrogate(const VehicleSurrogate& v) : vp(v.vp ? v.vp->copy() : NULL) {}; //v.vp非零检测
VehicleSurrogate& operator=(const VehicleSurrogate& v)
{
if (this != &v) // 确保没有将代理赋值给它自身
{
delete vp;
vp = (v.vp ? v.vp->copy() : NULL);
}
return *this;
};
//来自类Vehicle的操作
void start()
{
if (vp == 0)
throw "empty VehicleSurrogate.start()";
vp->start();
};
private:
Vehicle* vp;
};
完成了这些工作后,就很容易定义我们所需要的操作了,如下:
VehicleSurrogate parking_lot[1000];
Automobile x = /*..*/
Parking_lot[num_vehicles++] = x;
//Parking_lot[num_vehicles++] = VehicleSurrogate(x);//此语句与上条语句作用一致
/*...*/
Parking_lot[p] = Parking_lot[q];
C++沉思录读书笔记(6章)-句柄:第一部分
创建代理会复制所代理的对象,如何避免这些复制呢?
可以通过句柄(handle)类来实现。句柄类和智能指针有点类似,通过重载句柄类的*和->操作符,可将句柄类改造称为智能指针。
从效果上来说,handle就是一种只包含单个对象的容器,它通过允许多个handle对象指向同一个对象来避免复制。
定义句柄类,我们还需要新定义一个类来容纳被引用类对象的引用计数和被引用类对象本身。
示例代码如下
#include <iostream>
using namespace std;
class Point
{
private:
int xval, yval;
public:
Point() : xval(0), yval(0)
{
cout << "Point" << endl;
}
Point(int x, int y) : xval(x), yval(y)
{
cout << "Point" << endl;
}
int x() const { return xval;}
int y() const { return yval;}
Point& x(int xv)
{
xval = xv;
return *this;
}
Point& y(int yv)
{
yval = yv;
return *this;
}
~Point()
{
cout << "~Point" << endl;
}
};
class UPoint //这个类对用户来说是不可见的, 就是一个间接层
{
friend class Handle;
Point p; //Point 对象
int u; //计数器
UPoint():u(1) //提供所有的point的构造方式
{
cout << "UPoint::" << u << endl;
}
UPoint(int x, int y):p(x, y), u(1)
{
cout << "UPoint::" << u << endl;
}
UPoint(const Point &p0) : p(p0), u(1)
{
cout << "UPoint::" << u << endl;
}
~UPoint()
{
cout << "~UPoint::" << u << endl;
}
};
class Handle
{
private:
UPoint *up; //和间接层UPoint打交道了
public:
Handle();
Handle(int, int);
Handle(const Point&);
Handle(const Handle&);
Handle& operator=(const Handle&);
~Handle();
int x() const;
Handle& x(int);
int y() const;
Handle& y(int);
};
Handle::Handle():up(new UPoint)
{
cout << "Handle::" <<up->u << endl;
}
Handle::Handle(int x, int y):up(new UPoint(x, y)) //按创建Point的方式构造handle,handle->UPoint->Point
{
cout << "Handle::" <<up->u << endl;
}
Handle::Handle(const Point &p):up(new UPoint(p)) //创建Point的副本
{
cout << "Handle::" <<up->u << endl;
}
Handle::~Handle()
{
if(--up->u == 0)
{
cout << "~Handle::" <<up->u << endl;
delete up;
}
else
{
cout << "~Handle::" <<up->u << endl;
}
}
Handle::Handle(const Handle &h):up(h.up)
{
++up->u; //此处复制的是handle,但是底层的point对象并未复制,只是引用计数加1
cout << "Handle::" <<up->u << endl;
}
Handle& Handle::operator=(const Handle &h)
{
++h.up->u; //右边的对象引用计数加1,左边的减1
if(--up->u == 0)
delete up;
up = h.up;
cout << "Handle::" <<up->u << endl;
return *this;
}
int Handle::x() const
{
return up->p.x();
}
int Handle::y() const
{
return up->p.y();
}
Handle& Handle::x(int x0)//假设句柄为指针语义
{
up->p.x(x0);
return *this;
}
Handle& Handle::y(int y0)
{
up->p.y(y0);
return *this;
}
/*
Handle& Handle::x(int x0)//假设句柄为值语义(写时复制)
{
if(up->p != 1)
{
--up->u;
up = new UPoint(up->p);
}
up->p.x(x0);
return *this;
}
Handle& Handle::y(int y0)
{
if(up->p != 1)
{
--up->u;
up = new UPoint(up->p);
}
up->p.y(y0);
return *this;
}
*/
void f(Handle h) //测试函数,复制handle,但未复制Point
{
cout << h.x() << h.y() << endl;
}
int main()
{
Handle h(3,5);
cout << h.x() << h.y() << endl;
f(h);
Point o(10,9);
Handle a(o);
Handle b(a);
return 0;
}
C++沉思录读书笔记(7章)-句柄:第二部分
上一回讨论的句柄技术有一个明显的缺点:为了将句柄捆绑到类T的对象上,必须要新定义一个具有类型为T的成员对象的新类。
这个毛病相当麻烦,如果想新设计一个类的句柄类,就需要新定义两个类。
C++之父提到过一种定义句柄类的技术可以弥补这一个缺点,主要思想就是将引用技术从数据中分离出来,把引用计数放到句柄类自己的对象之中。
示例代码如下所示:
#include <iostream>
using namespace std;
//-----------------------------------------
class Point
{
private:
int xval,yval;
public:
Point():xval(0),yval(0){}
Point(int x,int y):xval(x),yval(y){}
int x()const{return xval;}
int y()const{return yval;}
Point& x(int xv){xval=xv;return *this;}
Point& y(int yv){yval=yv;return *this;}
};
//------------------------------------------------------
class UseCount
{
private:
int* p;
public:
UseCount();
UseCount(const UseCount&);
UseCount& operator=(const UseCount&);
~UseCount();
bool only();
bool reattach(const UseCount&);
bool make_only();
};
UseCount::UseCount():p(new int(1)){}
UseCount::UseCount(const UseCount&u):p(u.p){++*p;}
UseCount::~UseCount()
{
if (--*p==0)
{
delete p;
}
}
bool UseCount::only()
{
return *p==1;
}
bool UseCount::reattach(const UseCount& u)
{
++*u.p;
if (--*p==0)
{
delete p;
p=u.p;
return true;
}
p=u.p;
return false;
}
bool UseCount::make_only()
{
if (*p==1)
return false;
--*p;
p=new int(1);
return true;
}
//-------------------------------------------
class Handle
{
private:
Point* p;
UseCount u;
public:
Handle();
Handle(int,int);
Handle(const Point&);
Handle(const Handle&);
Handle& operator =(const Handle&);
~Handle();
int x()const;
Handle&x(int);
int y()const;
Handle&y(int);
};
Handle::Handle():p(new Point){}
Handle::Handle(int x,int y):p(new Point(x,y)){}
Handle::Handle(const Point&p0):p(new Point(p0)){}
Handle::Handle(const Handle&h):u(h.u),p(h.p){}
Handle::~Handle()
{
if (u.only())
{
delete p;
}
}
Handle& Handle::operator=(const Handle &h)
{
if (u.reattach(h.u))
delete p;
p=h.p;
return *this;
/* //自定义版本不使用reattach辅助方法,自认为更容易理解,但很丑陋
(*(h.u.p))++;
if(*(u.p) == 1)
{
delete p;
delete u.p;
}
else
(*(u.p))--;
u.p = h.u.p;
p = h.p;
return *this;
*/
}
int Handle::x()const
{
return p->x();
}
int Handle::y()const
{
return p->y();
}
Handle& Handle::x(int x0)
{
if (u.make_only())
p=new Point(*p);
p->x(x0);
return *this;
/* //自定义版本,不使用辅助方法make_only,自认为更容易理解,但很丑陋
if(*(u.p) == 1)
p->x(x0);
else
{
(*(u.p))--;
u.p = new int(1);
p = new Point(*p);
p->x(x0);
}
retrun *this;
*/
}
Handle& Handle::y(int y0)
{
if (u.make_only())
p=new Point(*p);
p->y(y0);
return *this;
/* //自定义版本,不使用辅助方法make_only,自认为更容易理解,但很丑陋
if(*(u.p) == 1)
p->y(x0);
else
{
(*(u.p))--;
u.p = new int(1);
p = new Point(*p);
p->y(x0);
}
retrun *this;
*/
}
//---------------------------------------------------
int main()
{
Handle h(3,4);
Handle h2 = h;
h2.x(5);
int n = h.x();
cout<<n<<endl;
return 0;
}
这个策略的一个重要优势:UseCount类可以在不了解其使用者任何信息的情况下与之合为一体。这样一来,我们就可以把这个类当成句柄实现的一部分,与各种不同的数据结构协同工作。
C++沉思录读书笔记(8章)-一个面向对象程序范例2
此回是针对上回所设计方案的扩展,下面将会演示
(1)如何增加计算表达式的值的功能
(2)如何增加新的节点类型(已经有表示单个数、一元操作符、二元操作符的节点类型Int_node\Unary_node\Binary_node,将演示如何增加三元操作符(Ternary_node))
完整代码如下:
#include <iostream>
#include <string>
using namespace std;
class Expr_node
{
friend class Expr; //友元类可以被继承
int use; //引用计数
public:
virtual void print(ostream&) const = 0;
public:
Expr_node():use(1) {}
virtual ~Expr_node() {}
virtual int eval() const = 0;
};
class Expr //句柄类
{
friend ostream& operator<<(ostream &o, const Expr &e);
private:
Expr_node *p; //指向基类的指针
public:
Expr(int n);
Expr(const string &op, Expr t);
Expr(const string &op, Expr left, Expr right);
Expr(const string &op, Expr left, Expr middle, Expr right);
Expr(const Expr &t);
Expr& operator=(const Expr&);
~Expr()
{
if(--p->use == 0)
delete p;
}
int eval() const
{
return p->eval();
}
};
class Int_node: public Expr_node
{
private:
int n;
public:
Int_node(int k):n(k) {}
void print(ostream &o) const
{
o << n;
}
int eval() const { return n; }
};
class Unary_node: public Expr_node
{
private:
//friend class Expr;
string op;
Expr opnd;
public:
Unary_node(const string &a, Expr b):op(a), opnd(b) {}
void print(ostream &o) const
{
o << "(" << op << opnd << ")";
}
int eval() const
{
if(op == "-")
return -opnd.eval();
throw "error!错误的一元操作符";
}
};
class Binary_node: public Expr_node
{
private:
//friend class Expr;
string op;
Expr left;
Expr right;
public:
Binary_node(const string &a, Expr b, Expr c):op(a), left(b), right(c) {}
void print(ostream &o) const
{
o << "(" << left << op << right << ")";
}
int eval() const
{
int op1 = left.eval();
int op2 = right.eval();
if(op == "-") return op1 - op2;
if(op == "+") return op1 + op2;
if(op == "*") return op1 * op2;
if(op == "/") return op1 / op2;
throw "error! 错误的二元操作符!";
}
};
class Ternary_node: public Expr_node
{
private:
string op;
Expr left;
Expr middle;
Expr right;
public:
Ternary_node(const string& a, Expr b, Expr c, Expr d):op(a), left(b), middle(c), right(d) {}
void print(ostream &o) const
{
o << "(" << left << "?" << middle << ":" << right << ")";
}
int eval() const
{
if(left.eval())
return middle.eval();
else
return right.eval();
}
};
Expr::Expr(int n) { p = new Int_node(n); }
Expr::Expr(const string& op, Expr t) { p = new Unary_node(op, t); }
Expr::Expr(const string &op, Expr left, Expr right) { p = new Binary_node(op, left, right); }
Expr::Expr(const string &op, Expr left, Expr middle, Expr right) { p = new Ternary_node(op, left, middle, right); }
Expr::Expr(const Expr& t) { p = t.p; ++p->use; }
Expr& Expr::operator=(const Expr& rhs)
{
rhs.p->use++;
if(--p->use == 0)
delete p;
p = rhs.p;
return *this;
}
ostream& operator<<(ostream &o, const Expr &e)
{
e.p->print(o);
return o;
}
void main()
{
Expr t = Expr("*",
Expr("-", Expr(5)),
Expr("+", Expr(3), Expr(4)));
cout << t << "=" << t.eval() << endl;
Expr t2 = Expr("-", Expr(5), Expr("?:", Expr(0), Expr(1), Expr(2)));
cout << t2 << "=" << t2.eval() << endl;
}
运行结果如下:
((-5)*(3+4))=-3
(5-(0?1:2))=3
C++沉思录课堂练习另解--消息发送(优化版)
缘起,看到一篇文章(懒得超链接),重新研究《C++沉思录》的那一个课堂练习,综合利用好几种设计模式,并且很好地遵守结合的面向对象的原则,嗯,不管怎么样,还是表扬这位同学的面向对象与设计模式的功力,确实不容易。只是,在下从模式堆里爬出来之后,已经对模式大倒胃口,看到这么小的练习,居然要用上这么多模式,一时技痒,有必要这样吗?对我来说,代码中每引入一种模式,就表示要添加各种接口、虚函数,然后,免不了就要用到继承,这些,我非常深痛恶疾,因为,这都意味着间接层越来越厚了,偏离了问题的本质。
先看看沉思录对于这道题的描述,嗯,这个,还是请各位自己去看看书是怎么写的,作者提供了两种方案,佩服作者和译者的文笔,两种方案解释得相当细腻。下面,尝试另一种解决方式。先秀一下代码的运行效果。
对于这个课堂练习,其中,给图片加边框,最适合于用装饰模式来解决。但是,两幅图片的横接、竖接,怕是就不那么容易了。但是,首先,要对问题重新描述一下,现实中,一张图片,可以给予添加一层又一层的片框,也可以和其他的图片组合在一块,或横,或竖,……,但是,图片,始终只有一张,对它处理之后,它就一直是那个样子了,不可能同时看到它的两个样子。如果,拿这张图片去进行复制,那又自是另当别论,但那已经是另外一张新图片了。
以下的设计用上了消息,如果有现成消息框架给予支持,实现起来就会很简单。但是,即使没有消息框架的支持,利用消息发送来解决这个问题,也是相当小儿科的事情。为了突出重点,忽略了各种异常处理,没有优化,也不管什么编程风格,纯粹直奔主题。解决这个例子的最重要一点,就在于打印图片时,要一行一行地从顶到底顺次打印下来。因此,先定义这个图片可以发送的消息,它必须处理两条消息,图片的高度(即行数),和图片每一行的数据。即如下所示
enum {PM_HEIGHT, PM_ROW};
struct PictureMsg
{
typedef int (*ProcPictureMsg)(void* pThis, int nMessage, void* param1, void* param2);
public:
void* pThis;
ProcPictureMsg proc;
int GetHeight()
{
return (*proc)(pThis, PM_HEIGHT, NULL, NULL);
}
int GetRow(int nRow, string& sRow)
{
return (*proc)(pThis, PM_ROW, (void*)nRow, (void*)&sRow);
}
};
ostream& operator << (ostream& out, PictureMsg& msg)
{
int nHeight = msg.GetHeight();
string sRow;
for (int nRow = 0; nRow<nHeight; nRow++)
{
sRow.clear();
msg.GetRow(nRow, sRow);
out << sRow;
out << endl;
}
return out;
} www.2cto.com
其中的回调函数用以发送消息,本来回调函数搞成成员函数会更加好,但由于c++的成员函数是一个大坑,而我又不想引入function或者其他delegate等庞然大物。为了让客户更加方便地发送消息,特意编写了两个辅助函数。
但是,要发送消息给对象时,首先要求这个对象必须已经活着了,也即是要创建一个能接收消息的对象。由于没有消息框架的支持,同时,也为了使用上的方便,我只好编写一个具体的类继承于图片消息结构,并提供了初步的消息处理,嗯,用到了继承,很难受。
好了,接下来就可以全心全意地对消息进行特别地处理了。要求,对图片的各种装饰操作,不能影响到图片原有的功能,也不要求改变用户的代码。或者说,图片本身并不知道自己已经被外界做了各种各样的处理,它还是按照原来的样子处理各种消息。下面,只是实现了两种装饰操作,加边框与横接,至于竖接,实在很容易实现的。此外,由于在实现装饰操作时,需要用到图片的宽度,因此,图片消息又添加了一条新的消息,用以处理图片的宽度。
由于是玩具代码,在下也没有做太大的优化,有几行代码显得特别难看,特别是横接的装饰方法,强制指定了左边的图片为操作后的结果,还有各个装饰类的析构函数,可以撤消对图片的装饰,以及添加新的消息,以返回图片的拷贝,或返回当前正在进行的装饰操作,或者装饰类的动态创建等等。总之,可优化升级的方法多种多样,而且都不会影响代码的结构。以下,是一陀代码,从第一版上稍加优化的,不读也罢。
enum { PM_WIDTH, PM_HEIGHT, PM_ROW};
struct PictureMsg
{
typedef int (*ProcPictureMsg)(void* pThis, int nMessage, void* param1, void* param2);
public:
void* pThis;
ProcPictureMsg proc;
int GetWidth()
{
return (*proc)(pThis, PM_WIDTH, NULL, NULL);
}
int GetHeight()
{
return (*proc)(pThis, PM_HEIGHT, NULL, NULL);
}
int GetRow(int nRow, string& sRow)
{
return (*proc)(pThis, PM_ROW, (void*)nRow, (void*)&sRow);
}
};
ostream& operator << (ostream& out, PictureMsg& msg)
{
int nHeight = msg.GetHeight();
string sRow;
for (int nRow = 0; nRow<nHeight; nRow++)
{
sRow.clear();
msg.GetRow(nRow, sRow);
out << sRow;
out << endl;
}
return out;
}
class CPicture : public PictureMsg
{
public:
CPicture(const char* pDatas[], int nCount)
{
m_pDatas = pDatas;
m_nCount = nCount;
m_nWidth = 0;
for (int i=0; i<m_nCount; i++)
{
int nLen = strlen(m_pDatas[i]);
if (m_nWidth < nLen)
m_nWidth = nLen;
}
pThis = this;
proc = HandleMsg;
}
private:
const char** m_pDatas;
int m_nCount;
int m_nWidth;
static int HandleMsg(void* pThis, int nMessage, void* param1, void* param2);
};
int CPicture::HandleMsg(void* pThis, int nMessage, void* param1, void* param2)
{
CPicture* pSelf = (CPicture*)pThis;
switch (nMessage)
{
case PM_WIDTH:
return pSelf->m_nWidth;
case PM_HEIGHT:
return pSelf->m_nCount;
break;
case PM_ROW:
int nRow = (int)param1;
string& sRow = *(string*)param2;
if (nRow >= pSelf->m_nCount)
break;
int i=0;
for (; pSelf->m_pDatas[nRow][i] != 0; i++)
sRow.push_back(pSelf->m_pDatas[nRow][i]);
for (; i<pSelf->m_nWidth; i++)
sRow.push_back(' ');
}
return 0;
}
class CFrameDecorater
{
public:
CFrameDecorater(PictureMsg& imp)
{
m_PrevImp = imp;
imp.pThis = this;
imp.proc = HandleMsg;
}
private:
PictureMsg m_PrevImp;
static int HandleMsg(void* pThis, int nMessage, void* param1, void* param2);
};
int CFrameDecorater::HandleMsg(void* pThis, int nMessage, void* param1, void* param2)
{
CFrameDecorater* pSelf = (CFrameDecorater*)pThis;
PictureMsg& prevImp = pSelf->m_PrevImp;
switch (nMessage)
{
case PM_WIDTH:
return prevImp.GetWidth()+2;
case PM_HEIGHT:
return prevImp.GetHeight()+2;
case PM_ROW:
int nRow = (int)param1;
string& sRow = *(string*)param2;
bool bMyRow = nRow == 0 || nRow>prevImp.GetHeight();
if (nRow >= prevImp.GetWidth()+2)
break;
if (nRow == 0 || nRow>prevImp.GetHeight())
{
sRow.push_back('+');
for (int i=0; i<prevImp.GetWidth(); i++)
sRow.push_back('-');
sRow.push_back('+');
}
else
{
sRow.push_back('|');
prevImp.GetRow(nRow-1, sRow);
sRow.push_back('|');
}
}
return 0;
}
class CHorseDecorater
{
public:
CHorseDecorater(PictureMsg& impLeft, PictureMsg& impRight)
{
m_Left = impLeft;
m_Right = impRight;
impLeft.pThis = this;
impLeft.proc = HandleMsg;
}
private:
PictureMsg m_Left;
PictureMsg m_Right;
static int HandleMsg(void* pThis, int nMessage, void* param1, void* param2);
static void PrintRow(PictureMsg& pict, int nRow, string& sRow)
{
if (nRow < pict.GetHeight())
pict.GetRow(nRow, sRow);
else
{
for (int i=0; i<pict.GetWidth(); i++)
sRow.push_back(' ');
}
}
};
int CHorseDecorater::HandleMsg(void* pThis, int nMessage, void* param1, void* param2)
{
CHorseDecorater* pSelf = (CHorseDecorater*)pThis;
PictureMsg& pictLeft = pSelf->m_Left;
PictureMsg& pictRight = pSelf->m_Right;
switch (nMessage)
{
case PM_WIDTH:
return pictLeft.GetWidth()+pictRight.GetWidth();;
case PM_HEIGHT:
return max(pictLeft.GetHeight(), pictRight.GetHeight());
case PM_ROW:
int nRow = (int)param1;
string& sRow = *(string*)param2;
PrintRow(pictLeft, nRow, sRow);
PrintRow(pictRight, nRow, sRow);
}
return 0;
}
int main()
{
const char* init1[] = {"Paris", "in the", "Spring", "HaHa"};
CPicture pict1(init1, 3);
//cout << pict1;
CFrameDecorater framer1(pict1);
//cout << pict1;
CFrameDecorater framer2(pict1);
//cout << pict1;
CPicture pict2(init1, 4);
CHorseDecorater hors(pict1, pict2);
//cout << pict1;
CFrameDecorater framerHorse(pict1);
//cout << pict1;
CHorseDecorater hors2( pict2, pict1);
//cout << pict2;
CFrameDecorater framer3(pict2);
CHorseDecorater hors3( pict1, pict2);
cout << pict1;
return 0;
}
消息发送VS设计模式 C++沉思录的一练习题的另解
缘起,看到一遍文章,题材取自于《C++沉思录》,综合利用好几种设计模式,并且结合所谓的面向对象的技巧,然后洋洋自得,并且在最后,还反问:“有更好的解决方案吗?”。本座暗叹,又一个设计模式的毒害者。以下,就提出另一种解决方案。
首先,先声明一点,现实中,一张图片,可以给予添加一层又一层片框,也可以和其他的图片组合在一块,或横,或竖,……,但是,图片,始终只有一张,对它处理之后,它就一直是那个样子了,不可能同时看到它的两个样子,或加边框,或和其他照片组合在一块。如果,拿这张图片去进行复制,那又自是另当别论,但那已经是另外一第图片了。当然,对以下例子稍加修改,就能支持修饰的图片的复制操作。
本座最近在写一套消息框架,如果这个例子能用上那套框架,写起来就会很简单。但是,即使没有消息框架的支持,利用消息发送来解决这个问题,也是相当小儿科的事情。为了突出重点,忽略了各种异常处理,没有优化,也不管什么编程风格,纯粹直奔主题。解决这个例子的最重要一点,就在于打印图片时,要一行一行地从顶到底顺次打印下来。 www.2cto.com
typedef int (*ProcPictureImp)(void* pThis, int nMessage, void* param1, void* param2);
enum { PM_WIDTH, PM_HEIGHT, PM_PRINT_ROW};
struct PictureImp
{
void* pThis;
ProcPictureImp proc;
int GetWidth()
{
return (*proc)(pThis, PM_WIDTH, NULL, NULL);
}
int GetHeight()
{
return (*proc)(pThis, PM_HEIGHT, NULL, NULL);
}
int PrintRow(int nRow, ostream& out)
{
return (*proc)(pThis, PM_PRINT_ROW, (void*)nRow, (void*)&out);
}
};
class CPicture
{
public:
CPicture(const char* pDatas[], int nCount)
{
m_pDatas = pDatas;
m_nCount = nCount;
m_nWidth = 0;
for (int i=0; i<m_nCount; i++)
{
int nLen = strlen(m_pDatas[i]);
if (m_nWidth < nLen)
m_nWidth = nLen;
}
m_Imp.pThis = this;
m_Imp.proc = HandleMsg;
}
void Print(ostream& out);
public:
PictureImp m_Imp;
private:
const char** m_pDatas;
int m_nCount;
int m_nWidth;
static int HandleMsg(void* pThis, int nMessage, void* param1, void* param2);
};
int CPicture::HandleMsg(void* pThis, int nMessage, void* param1, void* param2)
{
CPicture* pSelf = (CPicture*)pThis;
switch (nMessage)
{
case PM_WIDTH:
return pSelf->m_nWidth;
case PM_HEIGHT:
return pSelf->m_nCount;
break;
case PM_PRINT_ROW:
int nRow = (int)param1;
ostream& out = *(ostream*)param2;
if (nRow >= pSelf->m_nCount)
break;
int i=0;
for (; pSelf->m_pDatas[nRow][i] != 0; i++)
out << pSelf->m_pDatas[nRow][i];
for (; i<pSelf->m_nWidth; i++)
out << ' ';
}
return 0;
}
void CPicture::Print(ostream& out)
{
int nHeight = m_Imp.GetHeight();
for (int nRow = 0; nRow<nHeight; nRow++)
{
m_Imp.PrintRow(nRow, out);
out << endl;
}
}
class CFrameDecorater
{
public:
CFrameDecorater(PictureImp& imp)
{
m_PrevImp = imp;
imp.pThis = this;
imp.proc = HandleMsg;
}
private:
PictureImp m_PrevImp;
static int HandleMsg(void* pThis, int nMessage, void* param1, void* param2);
};
int CFrameDecorater::HandleMsg(void* pThis, int nMessage, void* param1, void* param2)
{
CFrameDecorater* pSelf = (CFrameDecorater*)pThis;
PictureImp& prevImp = pSelf->m_PrevImp;
switch (nMessage)
{
case PM_WIDTH:
return prevImp.GetWidth()+2;
case PM_HEIGHT:
return prevImp.GetHeight()+2;
case PM_PRINT_ROW:
int nRow = (int)param1;
ostream& out = *(ostream*)param2;
bool bMyRow = nRow == 0 || nRow>prevImp.GetHeight();
if (nRow >= prevImp.GetWidth()+2)
break;
if (nRow == 0 || nRow>prevImp.GetHeight())
{
out << '+';
for (int i=0; i<prevImp.GetWidth(); i++)
out << '-';
out << '+';
}
else
{
out << '|';
prevImp.PrintRow(nRow-1, out);
out << '|';
}
}
return 0;
}
class CHorseDecorater
{
public:
CHorseDecorater(PictureImp& impLeft, PictureImp& impRight)
{
m_Left = impLeft;
m_Right = impRight;
impLeft.pThis = this;
impLeft.proc = HandleMsg;
impRight.pThis = this;
impRight.proc = HandleMsg;
}
private:
PictureImp m_Left;
PictureImp m_Right;
static int HandleMsg(void* pThis, int nMessage, void* param1, void* param2);
static void PrintRow(PictureImp& pict, int nRow, ostream& out)
{
if (nRow < pict.GetHeight())
pict.PrintRow(nRow, out);
else
{
for (int i=0; i<pict.GetWidth(); i++)
out << ' ';
}
}
};
int CHorseDecorater::HandleMsg(void* pThis, int nMessage, void* param1, void* param2)
{
CHorseDecorater* pSelf = (CHorseDecorater*)pThis;
PictureImp& pictLeft = pSelf->m_Left;
PictureImp& pictRight = pSelf->m_Right;
switch (nMessage)
{
case PM_WIDTH:
return pictLeft.GetWidth()+pictRight.GetWidth();;
case PM_HEIGHT:
return max(pictLeft.GetHeight(), pictRight.GetHeight());
case PM_PRINT_ROW:
int nRow = (int)param1;
ostream& out = *(ostream*)param2;
PrintRow(pictLeft, nRow, out);
PrintRow(pictRight, nRow, out);
}
return 0;
}
int main()
{
const char* init1[] = {"Paris", "in the", "Spring", "HaHa"};
CPicture pict1(init1, 3);
pict1.Print(cout);
CFrameDecorater framer1(pict1.m_Imp);
//pict1.Print(cout);
CFrameDecorater framer2(pict1.m_Imp);
pict1.Print(cout);
CPicture pict2(init1, 4);
CHorseDecorater hors(pict1.m_Imp, pict2.m_Imp);
pict1.Print(cout);
CFrameDecorater framerHorse(pict1.m_Imp);
pict1.Print(cout);
return 0;
}
嗯,明眼人一眼就看出来了,切,这不过是模仿WINDOWS的窗口消息处理函数的伎俩而已。没有继承、没有虚函数,何其美妙哉!
又,注意到main()中的代码,虽然pict1被装饰了好几次,但其图片打印,始终都是pict1.Print(cout)。所谓不管八风乱起,我自岿然不动
相关文章推荐
- C++沉思录读书笔记(8章)-一个面向对象程序范例2
- C++沉思录读书笔记(31章)- C++对复杂性的哲学
- C++沉思录读书笔记(9,10章)-感想
- C++沉思录读书笔记(一)
- C++沉思录读书笔记(11章)-什么时候不应当使用虚函数?
- C++沉思录读书笔记(7章)-句柄:第二部分
- C++沉思录读书笔记(一)代理类
- C++沉思录读书笔记(12章)-一个简单容器类的实现
- c++沉思录读书笔记1
- C++沉思录读书笔记(二)句柄类.上
- C++沉思录读书笔记(13、14章)-简单容器类的改进
- C++沉思录读书笔记 12-设计容器类
- C++沉思录读书笔记
- C++沉思录读书笔记(15,16章)-将模板应用于接口
- c++沉思录读书笔记(一)代理类
- C++沉思录读书笔记(17章)-见识泛型算法的威力
- C++沉思录读书笔记(一)
- C++沉思录读书笔记(18章)- 确定一个泛型算法能支持的数据结构的方法
- C++沉思录读书笔记(二)
- C++沉思录读书笔记(19章)- 使用泛型迭代器将泛型算法应用到不同的场合