2006年7月16日
最近在学习Hoops 的引擎(http://www.hoops3d.com )
模拟它的MVO架构,不过没有原代码,所以很难a
设计了一个交互绘图基本类,
但是还有错误,鼠标左键点击两下
winGDI.cpp中出错。
请大虾指教一二。
我已经在这个问题上花了很多的心思。
其中最重要的就是Painter抽象类的设计
他的子类QBufferDC继承CDC
而SGView包含Painter指针,方便在SGView ::drawEntity调用。
SGObject - 对象的抽象类,也就是几何对象
SGModel- 负责对象管理,没有实现所有的功能,准备用SceneTree来实现
SGView - 负责显示SGModel中的数据,关键的函数
void SGView::drawEntity(SGObject* pObj)
{
pObj->draw(m_pPainter,this)
}
Painter - 封装CDC的功能,见代码
在CSGView创建的时候创建Painter对象
很可能这里有问题!!!!!
void CSGView::OnCreate(..)
{
CDC* pDC = GetDC();
Painter* painter = new QBufferDC(pDC);
m_pSGView->setPainter(painter);
}
MFC 相关的Document/View架构
CSGDocument - 管理SGModel
CSGView - 和SGView建立联系,并负责把windows的消息发送给SGView
见原代码
SGActionManager - 负责工具的管理
SGBaseAction - 工具的抽象基类
SGActionDrawLine - 绘制直线的工具
源代码连接:
http://www.cppblog.com/Files/richardzeng/MVOTest.rar
2006年5月27日
我要设计的应用程序其中的一个模块就是封装 windows GDI中的画笔,画刷等GDI object
把GDI object 再封装成resource,以实现多种样式多线条的画笔及画刷资源
画笔,画刷等资源继承resource
为了避免发生资源泄露和resource的管理
设计ResourceManager类,负责资源的创建,加载和卸载以及删除
两个抽象类 Resource 和 ResourceManager
两个具体类 ConcreateResource 和 ConcreateResourceManager
分别派生于上面的抽象类
以上设计是看了 OGRE 游戏引擎的资源管理部分,
对它的资源管理类ResourceManager不是很理解
resource 派生了pen,brush等类
pen类可以来自文件,也可以自己创建SubPen 添加到SubPenList中
ResourceManager 负责创建资源Resource
1. 如果我在抽象的 ResourceManager 声明 createRes函数,并返回基类resource
势必会要强制转换,然后在用到具体的Resource时候又要转换回来
2. 如果我在具体类 ConcreateResourceManager 声明 createConcreateRes函数
那么就白费了我应用设计模式设计这么多类
// abstract class for resource
class Resource{
public:
// standard constructor
Resource(const string& name, const string& group)
:mName(name),mGroup(group){}
~Resource(){}
protected:
// prevent default construct
Resource():mName(""),mGroup(""){}
string mName;
string mGroup;
static unsigned long mHandle;
};
// subclass of resource
// concreateResource such as PEN
class Pen:
public Resource{
Pen(const string& name, const string& group)
:Resource(name,group){}
~Pen(){}
void loadfromFile(string& filename);
// add into vector
void addSubPen(SubPen* sub){
mSubPenList.push_back(sub);
}
public:
typedef std::vector<SubPen> SubPenList;
SubPenList mSubPenList;
};
class
// abstract class for resource manager
class ResourceManager{
public:
ResourceManager(){}
~ResourceManager(){}
public:
// here , I cannot understand OGRE degsin
Resource* createRes(const string& name,const string& group);
// resource map
typedef std::map<string,Resource*> ResourceMap;
ResourceMap mResources;
};
// subclass ResourceManager
class ConcreateResourceManager
:public ResourceManager
{
ConcreateResourceManager(){}
~ConcreateResourceManager(){}
// how can design here!!
Pen* createPen(const string& name,const string& group){}
}
2006年5月15日
设计一个程序实现如何保存一系列的SPen(如下定义)对象到文件,或者称为序列化SPen collection
SPenCollection::Load和 Save函数实现打开画笔文件(文件的内容是一系列SPen对象)
// for example
SPenCollection pc;
pc.Load("C:\\1.pen");
我不知道如何序列化容器类对象,请大虾指教。
// SPen object
class SPen : public CObject
{
DECLARE_SERIAL(SPen)
public:
SPen();
virtual ~SPen();
public:
int lineStyle;
int lineWidth;
COLORREF lineColor;
public:
virtual void Serialize(CArchive& ar);
};
// SPen.cpp : 实现文件
//
#include "stdafx.h"
#include "ArchiveTest.h"
#include "SPen.h"
// SPen
IMPLEMENT_SERIAL(SPen,CObject,1)
SPen::SPen()
{
lineStyle = PS_SOLID;
lineWidth = 2;
lineColor = RGB(255,0,0);
}
SPen::~SPen()
{
}
// SPen 成员函数
void SPen::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{ // storing code
ar<<lineStyle;
ar<<lineWidth;
ar<<lineColor;
}
else
{ // loading code
ar>>lineStyle;
ar>>lineWidth;
ar>>lineColor;
}
}
///////////////////////////////////////
// 关键是要实现如何保存一系列的SPen对象
// load 和 save函数实现打开画笔文件(文件的内容是一系列SPen对象)
// for example
/** SPenCollection pc;
pc.Load("C:\\1.pen");
**/
#pragma once
// SPenCollection 命令目标
#include "SPen.h"
#include <afxtempl.h>
class SPenCollection : public CObject
{
DECLARE_SERIAL(SPenCollection)
public:
SPenCollection();
virtual ~SPenCollection();
void AddPen(SPen* pen);
void Load(CString strFileName);
void Save(CString strFileName);
// CArray 不知道用得对不对,请大虾指教
CArray<SPen*,SPen*> pens;
};
下面是Boost serialization 中的demo例子
为何写了serialize 函数还写个ostream<<
阿,
我对ostream 不是很了解,
我的印象是iostream 是在控制台里输入输出的
/////////////////////////////////////////////////////////////////////////////
class gps_position
{
friend std::ostream & operator<<(std::ostream &os, const gps_position &gp);
friend class boost::serialization::access;
int degrees;
int minutes;
float seconds;
template<class Archive>
void serialize(Archive & ar, const unsigned int /* file_version */){
ar & degrees & minutes & seconds;
}
public:
// every serializable class needs a constructor
gps_position(){};
gps_position(int _d, int _m, float _s) :
degrees(_d), minutes(_m), seconds(_s)
{}
};
std::ostream & operator<<(std::ostream &os, const gps_position &gp)
{
return os << ' ' << gp.degrees << (unsigned char)186 << gp.minutes << '\'' << gp.seconds << '"';
}
最近想试试boost,下载了boost 1.33.1
我目前使用的是VC++2005,
首先编译bjam, 不过用boost 1.33.1 \tools \jam-src 下的build.bat不用又从sf上下载了
最新的bjam,然后编译bjam,倒是很简单很快就好生成bjam.exe
然后把bjam的路径设置到path 环境变量中
一切都运行正常。
我首先就编译了boost -serialization
在命令行模式下
cd <boost-serialization 目录>
运行bjam "-sTOOLS=vc-8_0"
编译完成后在boost 目录下生成了bin 目录C:\boost_1_33_1\bin\
我编译的serialization lib 文件在这里
C:\boost_1_33_1\bin\boost\libs\serialization\build\boost_serialization.dll\vc-8_0\debug\threading-multi,当然还有其它的lib,dll
首先设置vc++包含文件目录C:\boost_1_33_1\
库文件设置在C:\boost_1_33_1\bin\boost\libs\serialization\build\boost_serialization.dll\vc-8_0\debug\threading-multi,
(这个肯定不好,难道我需要一个lib文件就要在这里加一个路径,因为编译的lib文件太分散,没有集中,不知道copy到一起是否可行)
然后我copy boost serialization 中demo代码,
编译demo.cpp,就提示 fatal error LNK1104: 无法打开文件“libboost_serialization-vc80-mt-gd-1_33_1.lib”
我想是不是lib文件的路径不对阿,把要的libboost_serialization-vc80-mt-gd-1_33_1.lib 拷贝到C:\boost_1_33_1\libs
下还是不行,设置项目依赖文件也不行,
到底在vc++2005 中怎么设置boost的环境阿
2006年5月10日
我在VS2005中编译Vector3D类出现
error C2662: “Vector3D::dotP”: 不能将“this”指针从“const Vector3D”转换为“Vector3D &”
怎么样改正呢,这个类也是看到别人这样写的,编译也没有错误。
#pragma once
#define M_PI 3.141
#include <math.h>
class Vector3D{
Vector3D(){x=y=z=0.0;}
Vector3D(double vx, double vy,double vz=0.0){
x = vx;
y = vy;
z = vz;
}
double magnitude() const{
return sqrt(x*x+y*y+z*z);
}
double dotP(const Vector3D& v1,const Vector3D& v2){
return (v1.x*v2.x+v1.y*v2.y+v1.z*v2.z);
}
// get the vector angle
double angle() const{
double ret = 0.0;
double m = magnitude();
if (m>1.0e-6) {
// 问题出在这里!!!!
// ==============================
double dp = dotP(*this,Vector3D(1.0,0.0));
//==============================
if (dp/m>=1.0) {
ret = 0.0;
}
else if (dp/m<-1.0) {
ret = M_PI;
}
else {
ret = acos( dp / m);
}
if (y<0.0) {
ret = 2*M_PI - ret;
}
}
return ret;
}
protected:
double x;
double y;
double z;
};#pragma once
#define M_PI 3.141
#include <math.h>
class Vector3D{
Vector3D(){x=y=z=0.0;}
Vector3D(double vx, double vy,double vz=0.0){
x = vx;
y = vy;
z = vz;
}
double magnitude() const{
return sqrt(x*x+y*y+z*z);
}
double dotP(const Vector3D& v1,const Vector3D& v2){
return (v1.x*v2.x+v1.y*v2.y+v1.z*v2.z);
}
// get the vector angle
double angle() const{
double ret = 0.0;
double m = magnitude();
if (m>1.0e-6) {
double dp = dotP(*this,Vector3D(1.0,0.0));
if (dp/m>=1.0) {
ret = 0.0;
}
else if (dp/m<-1.0) {
ret = M_PI;
}
else {
ret = acos( dp / m);
}
if (y<0.0) {
ret = 2*M_PI - ret;
}
}
return ret;
}
protected:
double x;
double y;
double z;
};
2006年4月21日
这些东西在网上都很多了,但是我觉得他们的使用都不符合我的要求,所以自己动手丰衣足食,写一个自己能用的,够用就好。
#include <iostream>
using namespace std;
//单件模板类
template<typename T> class Singleton
{
protected:
static T* m_Instance;
Singleton(){}
virtual~Singleton(){}
public:
//实例的获得
static T* Instance()
{
if(m_Instance==0)
m_Instance=new T;
return m_Instance;
}
//单件类的释放
virtual void Release()
{
if(m_Instance!=0)
{
delete m_Instance;
m_Instance=0;
}
}
};
//单件模板测试类
class Test:public Singleton<Test>
{
friend class Singleton<Test>; //声明为友员,不然会出错
protected:
Test()
{
a=b=c=0;
}
virtual ~Test(){}
public :
int a;
int b;
int c;
};
//初始化静态成员。。。
template<> Test*Singleton<Test>::m_Instance=0;
//以下为测试代码
void main()
{
Test*t=Test::Instance();
t->a=5;
t->b=25;
t->c=35;
cout<<"t: a="<<t->a<<" b="<<t->b<<" c="<<t->c<<endl;
Test*t2;
t2=Test::Instance();
cout<<"t2 a="<<t2->a<<" b="<<t2->b<<" c="<<t2->c<<endl;
t2->Release();
}
|
2006年3月24日
我看到EffectiveC++2ed中函数返回对象中的说明感觉以后再也不想让返回任何东西啦。比较怕。
但是有的时候不返回任何东西是不行的阿。
返回引用,返回指针,返回对象到底怎么写?!
——————————————————————————————
下面是EC中的内容
条款23: 必须返回一个对象时不要试图返回一个引用
据说爱因斯坦曾提出过这样的建议:尽可能地让事情简单,但不要过于简单。在c++语言中相似的说法应该是:尽可能地使程序高效,但不要过于高效。
一旦程序员抓住了“传值”在效率上的把柄(参见条款22),他们会变得十分极端,恨不得挖出每一个隐藏在程序中的传值操作。岂不知,在他们不懈地追求纯粹的“传引用”的过程中,他们会不可避免地犯另一个严重的错误:传递一个并不存在的对象的引用。这就不是好事了。
看一个表示有理数的类,其中包含一个友元函数,用于两个有理数相乘:
class rational {
public:
rational(int numerator = 0, int denominator = 1);
...
private:
int n, d; // 分子和分母
friend
const rational // 参见条款21:为什么
operator*(const rational& lhs, // 返回值是const
const rational& rhs)
};
inline const rational operator*(const rational& lhs,
const rational& rhs)
{
return rational(lhs.n * rhs.n, lhs.d * rhs.d);
}
很明显,这个版本的operator*是通过传值返回对象结果,如果不去考虑对象构造和析构时的开销,你就是在逃避作为一个程序员的责任。另外一件很明显的事实是,除非确实有必要,否则谁都不愿意承担这样一个临时对象的开销。那么,问题就归结于:确实有必要吗?
答案是,如果能返回一个引用,当然就没有必要。但请记住,引用只是一个名字,一个其它某个已经存在的对象的名字。无论何时看到一个引用的声明,就要立即问自己:它的另一个名字是什么呢?因为它必然还有另外一个什么名字(见条款m1)。拿operator*来说,如果函数要返回一个引用,那它返回的必须是其它某个已经存在的rational对象的引用,这个对象包含了两个对象相乘的结果。
但,期望在调用operator*之前有这样一个对象存在是没道理的。也就是说,如果有下面的代码:
rational a(1, 2); // a = 1/2
rational b(3, 5); // b = 3/5
rational c = a * b; // c 为 3/10
期望已经存在一个值为3/10的有理数是不现实的。如果operator* 一定要返回这样一个数的引用,就必须自己创建这个数的对象。
一个函数只能有两种方法创建一个新对象:在堆栈里或在堆上。在堆栈里创建对象时伴随着一个局部变量的定义,采用这种方法,就要这样写operator*:
// 写此函数的第一个错误方法
inline const rational& operator*(const rational& lhs,
const rational& rhs)
{
rational result(lhs.n * rhs.n, lhs.d * rhs.d);
return result;
}
这个方法应该被否决,因为我们的目标是避免构造函数被调用,但result必须要象其它对象一样被构造。另外,这个函数还有另外一个更严重的问题,它返回的是一个局部对象的引用,关于这个错误,条款31进行了深入的讨论。
那么,在堆上创建一个对象然后返回它的引用呢?基于堆的对象是通过使用new产生的,所以应该这样写operator*:
// 写此函数的第二个错误方法
inline const rational& operator*(const rational& lhs,
const rational& rhs)
{
rational *result =
new rational(lhs.n * rhs.n, lhs.d * rhs.d);
return *result;
}
首先,你还是得负担构造函数调用的开销,因为new分配的内存是通过调用一个适当的构造函数来初始化的(见条款5和m8)。另外,还有一个问题:谁将负责用delete来删除掉new生成的对象呢?
实际上,这绝对是一个内存泄漏。即使可以说服operator*的调用者去取函数返回值地址,然后用delete去删除它(绝对不可能——条款31展示了这样的代码会是什么样的),但一些复杂的表达式会产生没有名字的临时值,程序员是不可能得到的。例如:
rational w, x, y, z;
w = x * y * z;
两个对operator*的调用都产生了没有名字的临时值,程序员无法看到,因而无法删除。(再次参见条款31)
也许,你会想你比一般的熊——或一般的程序员——要聪明;也许,你注意到在堆栈和堆上创建对象的方法避免不了对构造函数的调用;也许,你想起了我们最初的目标是为了避免这种对构造函数的调用;也许,你有个办法可以只用一个构造函数来搞掂一切;也许,你的眼前出现了这样一段代码:operator*返回一个“在函数内部定义的静态rational对象”的引用:
// 写此函数的第三个错误方法
inline const rational& operator*(const rational& lhs,
const rational& rhs)
{
static rational result; // 将要作为引用返回的
// 静态对象
lhs和rhs 相乘,结果放进result;
return result;
}
这个方法看起来好象有戏,虽然在实际实现上面的伪代码时你会发现,不调用一个rational构造函数是不可能给出result的正确值的,而避免这样的调用正是我们要谈论的主题。就算你实现了上面的伪代码,但,你再聪明也不能最终挽救这个不幸的设计。
想知道为什么,看看下面这段写得很合理的用户代码:
bool operator==(const rational& lhs, // rationals的operator==
const rational& rhs); //
rational a, b, c, d;
...
if ((a * b) == (c * d)) {
处理相等的情况;
} else {
处理不相等的情况;
}
看出来了吗?((a*b) == (c*d)) 会永远为true,不管a,b,c和d是什么值!
用等价的函数形式重写上面的相等判断语句就很容易明白发生这一可恶行为的原因了:
if (operator==(operator*(a, b), operator*(c, d)))
注意当operator==被调用时,总有两个operator*刚被调用,每个调用返回operator*内部的静态rational对象的引用。于是,上面的语句实际上是请求operator==对“operator*内部的静态rational对象的值”和“operator*内部的静态rational对象的值”进行比较,这样的比较不相等才怪呢!
幸运的话,我以上的说明应该足以说服你:想“在象operator*这样的函数里返回一个引用”实际上是在浪费时间。但我没幼稚到会相信幸运总会光临自己。一些人——你们知道这些人是指谁——此刻会在想,“唔,上面那个方法,如果一个静态变量不够用,也许可以用一个静态数组……”
请就此打住!我们难道还没受够吗?
我不能让自己写一段示例代码来太高这个设计,因为即使只抱有上面这种想法都足以令人感到羞愧。首先,你必须选择一个n,指定数组的大小。如果n太小,就会没地方储存函数返回值,这和我们前面否定的那个“采用单个静态变量的设计”相比没有什么改进。如果n太大,就会降低程序的性能,因为函数第一次被调用时数组中每个对象都要被创建。这会带来n个构造函数和n个析构函数的开销,即使这个函数只被调用一次。如果说"optimization"(最优化)是指提高软件的性能的过程, 那么现在这种做法简直可以称为"pessimization"(最差化)。最后,想想怎么把需要的值放到数组的对象中以及需要多大的开销?在对象间传值的最直接的方法是通过赋值,但赋值的开销又有多大呢?一般来说,它相当于调用一个析构函数(摧毁旧值)再加上调用一个构造函数(拷贝新值)。但我们现在的目标正是为了避免构造和析构的开销啊!面对现实吧:这个方法也绝对不能选用。
所以,写一个必须返回一个新对象的函数的正确方法就是让这个函数返回一个新对象。对于rational的operator*来说,这意味着要不就是下面的代码(就是最初看到的那段代码),要不就是本质上和它等价的代码:
inline const rational operator*(const rational& lhs,
const rational& rhs)
{
return rational(lhs.n * rhs.n, lhs.d * rhs.d);
}
的确,这会导致“operator*的返回值构造和析构时带来的开销”,但归根结底它只是用小的代价换来正确的程序运行行为而已。况且,你所担心的开销还有可能永远不会出现:和所有程序设计语言一样,c++允许编译器的设计者采用一些优化措施来提高所生成的代码的性能,所以,在有些场合,operator*的返回值会被安全地除去(见条款m20)。当编译器采用了这种优化时(当前大部分编译器这么做),程序和以前一样继续工作,只不过是运行速度比你预计的要快而已。
以上讨论可以归结为:当需要在返回引用和返回对象间做决定时,你的职责是选择可以完成正确功能的那个。至于怎么让这个选择所产生的代价尽可能的小,那是编译器的生产商去想的事。
2006年3月23日
摘要: 在
google
上搜了很久的关于
Doxygen
使用方法的咚咚,只不过都是英文,而且都很多的规则。实际上大家只需要告诉基本的规则就可以。下面是我对
Doxygen
的摸索
首先熟知
Do...
阅读全文
2006年3月20日
最近在看吉林大学的C程序设计的课件,有一章讲到这个函数动手写了一下。
题目:
编写一个Insert函数实现在字符串s中的第i个位置插入字符串s1;
在VC++2005中编译这段程序没有任何的Error和warning但是运行就会错误,不知道为什么阿,请高手指点一二。
#include "stdafx.h"
#include <iostream>
using namespace std;
void Insert(char *s, char *s1, int i)
{
char *p,*q;
p = s + strlen(s); // p 指向s的末尾+1
q = p + strlen(s1); //q 指向新构造的字符串的\0
*q = '\0';
//
for(p--,q--;p>=s+i-1;)
{
*(p--) = *(q--);
}
//
for(p=s+i-1;*s1;)
{
*(p++) = *(s1++);
}
}
int _tmain(int argc, _TCHAR* argv[])
{
char *s = "Student";
char *s1 = "Teacher";
Insert(s,s1,3);
// 期待的输出是StuTeacherdent;
cout<<s;
return 0;
}
// 还有我如果把insert函数改成下面的应该也是可以的吧
void Insert2(char *s, char *s1, int i)
{
char *p,*q;
p = s + strlen(s); // p 指向s的末尾+1
q = p + strlen(s1); //q 指向新构造的字符串的\0
*q = '\0';
//
for(p--,q--;p>=s+i-1;)
{
*p-- = *q--;
}
//
for(p=s+i-1;*s1;)
{
*p++ = *s1++;
}
}