了解VC原理之高手请进!

newxixi 2003-08-25 09:43:31
小弟修炼中,有些问题不解,望大哥指教:
1、每个程序都有这个头,是什么意思?
#if !defined(AFX_……)
#define AFX_…… //省约号为类名加一长串东东

#if _MSC_VEER>1000
#progma once
#endif

下面部分不是每个都有,也不明白:
#ifndef _AFXWIN_H_
#error include 'stdfx.h' before including this file for PCH
#endif

还有这个:
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
staatic char THIS_FILE[]=_FILE_;

2、各个类的构造函数应该是怎样的?
新建一个SDI程序,其中doc/view/frame的构造是protectod的,而app和dlg的是public的。这是为什么?或者是根本就无所谓?

3、DECLARE_MESSAGE_MAP部分应该是protectod还是public?
同上例,其中除了app之外,都是procted,为何?

4、PreCreateWindow 和OnCreate有何区别?

以上几个问题,困扰已久,望高手不吝指教,不胜感激!分不够再加!
...全文
61 13 打赏 收藏 转发到动态 举报
写回复
用AI写文章
13 条回复
切换为时间正序
请发表友善的回复…
发表回复
xghost 2003-08-25
  • 打赏
  • 举报
回复
up
luogucai 2003-08-25
  • 打赏
  • 举报
回复
小弟修炼中,有些问题不解,望大哥指教:
1、每个程序都有这个头,是什么意思?
#if !defined(AFX_……)
#define AFX_…… //省约号为类名加一长串东东

/*用于防止被多次Include*/

#if _MSC_VEER>1000
#progma once
#endif

/*当版本大于1000时, 增加编译条件,will be included (opened) only once by the compiler in a build*/

下面部分不是每个都有,也不明白:
#ifndef _AFXWIN_H_
#error include 'stdfx.h' before including this file for PCH
#endif
/* 预编译头文件错误 */

还有这个:
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
staatic char THIS_FILE[]=_FILE_;

/*用于调试状态下, 从定义 new 为DEBUG_NEW, 它会多申请一些内存,用于记录内存块信息.
THIS_FILE[]=_FILE_; 用于记录当前文件的文件名.这样调试状态下,如果你使用new生成对象,并且没有被delete, 就会报告内存泄漏.*/

2、各个类的构造函数应该是怎样的?
新建一个SDI程序,其中doc/view/frame的构造是protectod的,而app和dlg的是public的。这是为什么?或者是根本就无所谓?
对于SDI程序,doc,view,frame,都是通过Doctemplate创建, 它又是通过DECLARE_DYNCREATE运行时信息动态创建的. 声明为protectod,就是不允许你显式创建, 比如, 不能使用 new doc;

3、DECLARE_MESSAGE_MAP部分应该是protectod还是public?
同上例,其中除了app之外,都是procted,为何?
/* DECLARE_MESSAGE_MAP 只是一个宏定义,你可以按下F12键,查看其定义的具体内容. 它用于定义消息映射所需要的变量.*/

4、PreCreateWindow 和OnCreate有何区别?

/* PreCreateWindow 是用于才窗口创建之前,更改其创建参数的,比如可以更改其边框特征,是这个窗口没有Border等.
OnCreate一般是用于窗口创建好了之后,应该做的一些事情,比如,创建这个窗口的子窗口等
dev_uoboy 2003-08-25
  • 打赏
  • 举报
回复
晕,std是标准中的命名空间啊,如果你在C++程序前写下如此代码,就不用表明std::了

#include <iostream>
using namespace std;

比如你的程序中可能经常用到 cout << "some message" << endl;
如果你开始不声明std命名空间的话就必须些这样写 std::cout << ....

ostream就是 out stream的意思,其实iostream里包含istream和ostream。

把类成员设计成const表示,他们不修改类对象,一般来说任何一个类如果期望被广泛使用就应该把那些不修改类数据成员的成员函数申明为const。但是把一个成员函数声明为const并不能阻止程序员可能做到的所有修改动作。把一个成员函数声明为const可以保证这个成员函数不修改类的数据成员,但是如果该类含有指针,那么在const成员函数中就能修改指针所指的对象。编译器并不会把这种修改检测为错误,这经常另一些C++初学者吃惊,呵呵当时的我就是如此。
newxixi 2003-08-25
  • 打赏
  • 举报
回复
To dev_uoboy(达克·秀男依达) :
感激感激!但是在下是个菜鸟,还有些东些不了解,顺便请教一下:
如:
void write( std::ostream & ) const;
中的std::ostream是什么意思?const又是什么作用呢?


PS..您让我佩服得五体投地!
oo 2003-08-25
  • 打赏
  • 举报
回复
up
dev_uoboy 2003-08-25
  • 打赏
  • 举报
回复
II. Visitor模式
我正在为一个设计问题苦恼。试用期快结束了,我希望自己解决这个问题,来证明自己的进步。每个人都记得自己的第一份工作吧,也都应该知道在这个时候把活儿做好是多么的重要!我亲眼看到其他的新雇员没有过完试用期就被炒了鱿鱼,就是因为他们不懂得如何对付那个大虾...,别误会,我不是说她不好,她是我见过最棒的程序员,可就是有点刻薄古怪...。现在我拜她为师,不为别的,就是因为我十分希望能达到她那个高度。
我想在一个类层次(class hierarchy)中增加一个新的虚函数,但是这个类层次是由另外一帮人维护的,其他人碰都不能碰:
class Personnel
{
public:
virtual void Pay ( /*...*/ ) = 0;
virtual void Promote( /*...*/ ) = 0;
virtual void Accept ( PersonnelV& ) = 0;
// ... other functions ...
};
class Officer : public Personnel { /* override virtuals */ };
class Captain : public Officer { /* override virtuals */ };
class First : public Officer { /* override virtuals */ };
我想要一个函数,如果对象是船长(Captain)就这么做,如果是大副(First Officer)就那么做。Virtual function正是解决之道,在Personnel或者Officer中声明它,而在Captain和First覆盖(override)它。
糟糕的是,我不能增加这么一个虚函数。我知道可以用RTTI给出一个解决方案:
void f( Officer &o )
{
if( dynamic_cast<Captain*>(&o) )
/* do one thing */
else if( dynamic_cast<First*>(&o) )
/* do another thing */
}
int main()
{
Captain k;
First s;
f( k );
f( s );
}
但是我知道使用RTTI是公司编码标准所排斥的行为,我对自己说:“是的,虽然我以前不喜欢RTTI,但是这回我得改变对它的看法了。很显然,除了使用RTTI,别无它法。”
“任何问题都可以通过增加间接层次的方法解决。”
我噌地一下跳起来,那是大虾的声音,她不知道什么时候跑到我背后,“啊哟,您吓了我一跳...您刚才说什么?”
“任何问...”
“是的,我听清楚了,”我也不知道哪来的勇气,居然敢打断她,“我只是不知道您从哪冒出来的。”其实这话只不过是掩饰我内心的慌张。
“哈,算了吧,小菜鸟,”大虾斜着眼看着我,“你以为我不知道你心里想什么!”她把声音提高了八度,直盯着我,“那些可怜的C语言门徒才会使用switch语句处理不同的对象类型。你看:”
/* A not-atypical C program */
void f(struct someStruct *s)
{
switch(s->type) {
case APPLE:
/* do one thing */
break;
case ORANGE:
/* do another thing */
break;
/* ... etc. ... */
}
}
“这些人学习Stroustrup教主的C++语言时,最重要的事情就是学习如何设计好的类层次。”
“没错,”我又一次打断她,迫不及待地想让Wendy明白,我还是有两下子的,“他们应该设计一个Fruit基类,派生出Apple和Orange,用virtual function来作具体的事情。
“很好,小菜鸟。C语言门徒通常老习惯改不掉。但是,你应该知道,通过使用virtual function,你增加了一个间接层次。”她放下笔,“你所需要的不就是一个新的虚函数吗?”
“是的。可是我没有权力这么干。”
“因为你无权修改类层次,对吧!”
“您终于了解了情况,我们没法动它。也不知道这个该死的类层次是哪个家伙设计的...” 我嘀嘀咕咕着。
“是我设计的。”
“啊...,真的?!这个,嘿嘿...”,我极为尴尬。
“这个类层次必须非常稳定,因为有跨平台的问题。但是它的设计允许你增加新的virtual function,而不必烦劳RTTI。你可以通过增加一个间接层次的办法解决这个问题。请问,Personnel::Accept是什么?”
”嗯,这个...”
“这个类实现了一个模式,可惜这个模式的名字起得不太好,是个PNP,叫Visitor模式。”
“啊,我刚刚读过Visitor模式。但是那只不过是允许若干对象之间相互迭代访问的模式,不是吗?”
她叹了一口气,“这是流行的错误理解。那个V,我觉得毋宁说是Visitor,还不如说是Virtual更好。这个PNP最重要的用途是允许在不改变类层次的前提下,向已经存在的类层次中增加新的虚函数。首先来看看Personnel及其派生类的Accept实现细节。”她拿起笔写下:
void Personnel::Accept( PersonnelV& v )
{ v.Visit( *this ); }
void Officer::Accept ( PersonnelV& v )
{ v.Visit( *this ); }
void Captain::Accept ( PersonnelV& v )
{ v.Visit( *this ); }
void First::Accept ( PersonnelV& v )
{ v.Visit( *this ); }
“Visitor的基类如下:”
class PersonnelV/*isitor*/
{
public:
virtual void Visit( Personnel& ) = 0;
virtual void Visit( Officer& ) = 0;
virtual void Visit( Captain& ) = 0;
virtual void Visit( First& ) = 0;
};
“啊,我记起来了。当我要利用Personnel类层次的多态性时,我只要调用Personnel::Accept(myVisitorObject)。由于Accept是虚函数,我的myVisitorObject.Visit()会针对正确的对象类型调用,根据重载法则,编译器会挑选最贴切的那个Visit来调用。这不相当于增加了一个新的虚拟函数了吗?”
“没错,小菜鸟。只要类层次支持Accept,我们就可以在不改动类层次的情况下增加新的虚函数了。”
“好了,我现在知道该怎么办了”,我写道:
class DoSomething : public PersonnelV
{
public:
virtual void Visit( Personnel& );
virtual void Visit( Officer& );
virtual void Visit( Captain& );
virtual void Visit( First& );
};
void DoSomething::Visit( Captain& c )
{
if( femaleGuestStarIsPresent )
c.TurnOnCharm();
else
c.StartFight();
}
void DoSomething::Visit( First& f )
{
f.RaiseEyebrowAtCaptainsBehavior();
}
void f( Personnel& p )
{
p.Accept( DoSomething() ); // 相当于 p.DoSomething()
}
int main()
{
Captain k;
First s;
f( k );
f( s );
}
大虾满意地笑了,“也许这个模式换一个名字会更好理解,可惜世事往往不遂人意...”。
dev_uoboy 2003-08-25
  • 打赏
  • 举报
回复
关于第2个问题,重技术角度上来说不好陈述,不过希望你看了下面的话之后有些启发,认真看哦,如果对你没有任何帮助,我也没办法,因为我也是菜鸟。。。。
人物介绍:
我 --- 一个追求上进的C++程序员,尚在试用期,聪明但是经验不足。
Wendy --- 公司里的技术大拿,就坐在我旁边的隔间里,C++大虾,最了不起的是,她是个女的!她什么都好,就是有点刻薄,我对她真是又崇拜又嫉妒。
----------------------------------------------------------
I. Virtually Yours -- Template Method模式

我在研究Wendy写的一个类。那是她为这个项目写的一个抽象基类,而我的工作就是从中派生出一个具象类(concrete class)。这个类的public部分是这样的:
class Mountie {
public:
void read( std::istream & );
void write( std::ostream & ) const;
virtual ~Mountie();
很正常,virtual destructor表明这个类打算被继承。那么再看看其protected部分:
protected:
virtual void do_read( std::istream & );
virtual void do_write( std::ostream & ) const;
也不过就是一会儿的功夫,我识破了Wendy的把戏:她在使用template method模式。public成员函数read和write是非虚拟的,它们肯定是调用protected部分do_read/do_write虚拟成员函数来完成实际的工作。啊,我简直为自己的进步而飘飘然了!哈,Wendy,这回你可难不住我,还有什么招数?尽管放马过来... 突然,笑容在我脸上凝固,因为我看到了其private部分:
private:
virtual std::string classID() const = 0;
这是什么?一个private纯序函数,能工作么?我站了起来,
“Wendy,你的Mountie类好像不能工作耶,它有一个private virtual function。”
“你试过了?”她连头都不抬。
“嗯,那倒是没有啦,可是想想也不行啊?我的派生类怎么能override你的private函数呢?” 我嘟囔着。
“嗬,你倒是很确定啊!”Wendy的声音很轻柔,“你怎么老是这也不行,那也不行的,这几个月跟着我你就没学到什么东西吗?小菜鸟。”
真是可恶啊...
“小菜鸟,你全都忘了,访问控制级别跟一个函数是不是虚拟的根本没关系。判断一个函数是动态绑定还是静态绑定是函数调用解析的最后一个步骤。好好读读标准的3.4和5.2.2节吧。”
我完全处于下风,只好采取干扰战术。“好吧,就算你说的不错,我也还是不明白,何必把它设为private?”
“我且问你,倘若你不想让一个类中的成员函数被其他的类调用,应当如何处理?”
“当然是把它设置为private的,” 我回答道。
“那么你去看看我的Mountie类实现,特别是write()函数的实现。”
我正巴不得逃开Wendy那刺人的目光,便转过头去在我的屏幕上搜索,很快,我找到了:
void Mountie::write(std::ostream &Dudley) const
{
Dudley << classID() << std::endl;
do_write(Dudley);
}
嗨,最近卡通片真是看得太多了,居然犯这样的低级失误。还是老是承认吧:“好了,我明白了。classID()是一个实现细节,用来在保存对象时指示具象类的类型,派生类必须覆盖它,所以必须是纯虚的。但是既然是实现细节,就应该设为private的。”
“这还差不多,小菜鸟。”大虾点了点头,“现在给我解释一下为什么do_read()和do_write()是protected的?”
这个问题并不难,我组织了一下就回答:“因为派生类对象需要调用这两个函数的实现来读写其中的基类对象。”
“很好很好,”大虾差不多满意了,“不过,你再解释解释为什么我不把它们设为public的?”
现在我感觉好多了:“因为调用它们的时候必须以一种特定的方式进行。比如do_write()函数,必须先把类型信息写入,再把对象信息写入,这样读取的时候,负责生成对象的模块首先能够知道要读出来的对象是什么类型的,然后才能正确地从流中读取对象信息。”
“聪明啊,我的小菜鸟!”Wendy停顿了一下,“就跟学习外国口语一样,学习C++也不光是掌握语法而已,还必须要掌握大量的惯用法。”
“是啊是啊,我正打算读Coplien的书...”
(就是James Coplien 1992年的经典著作Advanced C++ Programming Style and Idioms)
大虾挥了挥她的手,“冷静,小菜鸟,我不是指先知Coplien的那本书,我是指某种结构背后隐含的惯用法。比如一个类有virtual destructor,相当于告诉你说:‘嗨,我是一个多态基类,来继承我吧!’ 而如果一个类的destructor不是虚拟的,则相当于是在说:‘我不能作为多态基类,看在老天的份上,别继承我。’”
“同样的,virtual函数的访问控制级别也具有隐含的意义。一个protected virtual function告诉你:‘你写的派生类应该,哦,可是说是必须调用我的实现。’而一个private virtual function是在说:‘派生类可以覆盖,也可以不覆盖我,随你的便。但是你不可以调用我的实现。’”
我点点头,告诉她我懂了,然后追问道:“那么public virtual function呢?”
“尽可能不要使用public virtual function。”她拿起一支笔写下了以下代码:
class HardToExtend
{
public:
virtual void f();
};
void HardToExtend::f()
{
// Perform a specific action
}
“假设你发布了这个类。在写第二版时,需求有所变化,你必须改用Template Method。可是这根本不可能,你知道为什么?”
“呃,这个...,不知道。”
“由两种可能的办法。其一,将f()的实现代码转移到一个新的函数中,然后将f()本身设为non-virtual的:
class HardToExtend
{
// possibly protected
virtual void do_f();
public:
void f();
};
void HardToExtend::f()
{
// pre-processing
do_f();
// post-processing
}
void HardToExtend::do_f()
{
// Perform a specific action
}
然而你原来写的派生类都是企图override函数f()而不是do_f()的,你必须改变所有的派生类实现,只要你错过了一个类,你的类层次就会染上先知Meyers所说的‘精神分裂的行径’。” (参见Scott Meyers,Effective C++, Item 37,绝对不要重新定义继承而来的非虚拟函数)
“另一种办法是将f()移到private区域,引入一个新的non-virtual函数:”
class HardToExtend
{
// possibly protected
virtual void f();
public:
void call_f();
};
“这会导致无数令人头痛的问题。首先,所有的客户都企图调用f()而不是call_f(),现在它们的代码都不能编译了。更有甚者,大部分派生类都回把f()放在public区域中,这样直接使用派生类的用户可以访问到你本来想保护的细节。”
“对待虚函数要象对待数据成员一样,把它们设为private的,直到设计上要求使用更宽松的访问控制再来调整。要知道由private入public易,由public入private难啊!”
(这里所表达的思想具有一定的颠覆性,因为我们太容易在基类中设置public virtual function了,Java中甚至专门为这种做法建立了interface机制,现在竟然说这不好!一时间真是接受不了。但是仔细体会Wendy的意思,他并不是一般地反对public virtual function,只是在template method大背景下给出上述原则。虽然这个原则在一般的设计中也是值得考虑的,但是主要的应用领域还是在template method模式中。当然,template method是一种非常有用和常用的模式,因此也决定了本文提出的原则具有广泛的意义。)
newxixi 2003-08-25
  • 打赏
  • 举报
回复
To: ruihuahan(飞不起来的笨鸟) :
不会吧,难道doc/view/frame都没有实例吗?
newxixi 2003-08-25
  • 打赏
  • 举报
回复
TO: wanglh(宏) :
3、我知道那是宏,我的意思是在那个地方的函数应该是什么样子的
ruihuahan 2003-08-25
  • 打赏
  • 举报
回复
如果一个类的构造函数是保护的,说明此类只能被继承,不能直接实例化。
newxixi 2003-08-25
  • 打赏
  • 举报
回复
已很感激,请高手赐教!
wanglh 2003-08-25
  • 打赏
  • 举报
回复
1,文件加了个头,是避免文件被多次#include后造成类重复定义的错误
至于#ifdef _DEBUG,#ifndef _AFXWIN_H_是由于程序在某种状态下运行,需要定义相关的一些标志或变量或宏。
2,如果在代码中要直接用这个类的对象,构造函数就必须是public,如果不直接用而是在继承的类中用则用protected,请参考public和protected的定义
3,DECLARE_MESSAGE_MAP是宏定义,不是函数
4,PreCreateWindow是窗口创建前的调用,先于OnCreate
wqbmercury 2003-08-25
  • 打赏
  • 举报
回复
#if !defined(AFX_……)
#define AFX_…… //省约号为类名加一长串东东
这个是为了防止重复被include

#if _MSC_VEER>1000
#progma once
#endif
这个是先判断版本号是否大于1000,如果大于了才有这#progma once的指令,
这个指令的意思好像是只编译一次

#ifndef _AFXWIN_H_
#error include 'stdfx.h' before including this file for PCH
#endif
这里的意思是如果没有定义过_AFXWIN_H_
就出现编译时错误!include 'stdfx.h' before including this file for PCH

2,3我也没注意到,我也关注一下

4中的PreCreateWindow是在出现窗口前掉用的,可在重载来改变一些外观之类的
而OnCreate是在创建时掉用的



不好意思可能有些我也没能说的太明白,但希望能给你一些帮助!

HOHO

16,472

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • Web++
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧