博文

Visual C++ 编程小技巧(2005-11-15 10:32:00)

摘要:
 1.如何改变窗口的图标?   向窗口发送 WM_SECTION消息。   Example: HICON hIcon=AfxGetApp() ->LoadIcon(IDI_ICON);
ASSERT(hIcon);
AfxGetMainWnd() ->SendMessage(WM_SECTION,TRUE,(LPARAM) hIcon);   2. 如何改变窗口的缺省风格?   重写 CWnd:: PreCreateWindow 并修改CREATESTRUCT 结构来指定窗口风格和其他创建信息. Example: Delete "Max" Button and Set Original Window’s Position and Size BOOL CMainFrame:: PreCreateWindow (CREATESTRUCT &cs)
{
 cs.style &=~WS_MAXINIZEMOX;
 cs.x=cs.y=0;
 cs.cx=GetSystemMetrics(SM_CXSCREEN/2);
 cs.cy=GetSystemMetrics(SM_CYSCREEN/2);
 return CMDIFramewnd ::PreCreateWindow(cs); }   3. 如何将窗口居中显示? Easy, Call Function CWnd:: Center Windows
Example(1): Center Window( ); //Relative to it’s parent
// Relative to Screen
Example(2): Center Window(CWnd:: GetDesktopWindow( ));
//Relative to Application’s MainWindow
AfxGetMainWnd( ) -> Center Window( );   4. 如何让窗口和 MDI窗口一启动就最大化和最小化?   先说窗口。   在 InitStance 函数中设定 m_nCmdShow的 取值. m_nCmdShow=SW_SHOWMAXMIZED ; /......

阅读全文(2371) | 评论:0

关于指针(2005-11-13 20:43:00)

摘要:       不可否认,指针是C的精华,也是C的难点。每一个学C的人,当年应该都是在“漆黑”中摸过来的。我现在编程序时用指针用得很多,可以说绝大多数值与是通过指针来传递的,而struct、class的实例等则是完全用指针或引用来传递的。
  我不敢说我是什么高手,也不敢说我对指针有多了解,对于网上的一大堆提问,我不敢说我能很好地回答。但是,我想:我把自己的一点点感悟写出来,多多少少会给读者一点启示。写得不到之处,请读者朋友们斧正。 一、指针就是变量:
  虽然申明指针的时候也提类型,如:
  char *p1;
  int *p2;
  float *p3;
  double *p4;
  .....
  但是,这只表示该指针指向某类型的数据,而不表示该指针的类型。说白了,指针都是一个类型:四字节无符号整数。 二、指针的加减运算很特殊:
  p++、p--之类的运算并不是让p这个“四字节无符号整数”加一或减一,而是让它指向下一个或上一个存储单元,它实际加减的值就是它所指类型的值的size。
  比如:
  char *型指针,每次加减的改变量都是1;
  float *型的指针,每次加减的改变量都是4;
  void *型指针无法加减。
  还要注意的是:指针不能相加,指针相减的差为int型。
  正是因为指针有着不同于其它变量的运算方式,所以,在任何时候用到指针都必须明确“指针的类型”(即指针所指的变量的类型)。这就不难理解为什么函数声明时必须用“int abc(char *p)”而调用的时候却成了“a = abc(p);”这样的形式了。 三、用指针做参数传递的是指针值,不是指针本身:
  要理解参数传递,首先必须把“形参”与“实参”弄明白。
  函数A在调用函数B时,如果要传递一个参数C,实际是在函数B中重新建立一个变量C,并将函数A中的C值传入其中,于是函数B就可以使用这个值了,在函数B中,无论有没有修改这个C值,对于函数A中的C都没有影响。函数B结束时,会将所有内存收回,局部变量C被销毁,函数B对变量C所做的一切修改都将被抛弃。
  以上示例中,函数A中的变量C称为“实参”,函数B中的变......

阅读全文(3254) | 评论:0

C++从零开始(十二)何谓面向对象编程思想 (2005-11-13 13:49:00)

摘要:

--------------------------------------------------------------------------------



前面已经说明了C++中最重要的概念——类,并且介绍了大部分和类相关的知识,至此,已经可以开始做些编程方面比较高级的应用——设计程序,而不再只是将算法变成代码。要说明如何设计程序,有必要先了解何谓编程思想。


编程思想

  编程,即编写程序,而之前已经说过,程序就是方法的描述,那么编程就是编写方法的描述。我知道如何到人民公园,然后我就编写了到人民公园的方法的描述——从市中心开始向东走400米再向右转走200米就是。接着另一个人也知道如何去,但他编的程序却是——从市中心沿人民东路走过两个交叉口,在第三个交叉口处右转,直走就能在右手方看到。很明显,两个程序不同,但最后走的路线是相同的,即大家的方法相同,但描述不同。
  所谓的编程思想,就是如何编程,即编写程序的方法。那么之前在《C++从零开始(八)》中说的编程的三个步骤其实就是一种编程思想。这也是为什么不同的人对同一算法编写出的程序不同(指程序逻辑,不是简单的变量或函数名不同),不同的人的编程思想不同。
  如果多编或多看一些程序,就会发现编程思想是很重要的。好的编程思想编出的程序条理分明,可维护性高;差的编程思想编出的程序晦涩难懂,可维护性低。注意,这里是从程序的易读性来比较的,实际出于效率,是会使用不符合人脑思维习惯的编程思想,进而导致代码的难于维护,但为了效率还是会经常在程序的瓶颈位置使用被优化了的代码(这种代码一般使用汇编语言编写,算法则很大程度上是数学上的优化,丢弃了大部分其在人类世界中的意义)。
  本系列一直坚持并推荐这么一个编程思想——一切均按照语义来编写。而语义是语言的意义,之前说它是代码在人类世界中的意义。比如桌子,映射成一个结构,有桌脚数、颜色等成员变量,那么为什么没有质量、材料、价格、生产日期等成员?对此有必要说明一下“人类世界”的含义。


世界

  我们生活在一个四维的客观物理世界中,游戏中的怪物生活在游戏定义的游戏世界中,白雪公主生活在一个童话世界中。什么......

阅读全文(2796) | 评论:0

C++从零开始(十一)(下)——类的相关知识 (2005-11-13 13:48:00)

摘要:

--------------------------------------------------------------------------------



本文的中篇已经介绍了虚的意思,就是要间接获得,并且举例说明电视机的频道就是让人间接获得电视台频率的,因此其从这个意义上说是虚的,因为它可能操作失败——某个频道还未调好而导致一片雪花。并且说明了间接的好处,就是只用编好一段代码(按5频道),则每次执行它时可能有不同结果(今天5频道被设置成中央5台,明天可以被定成中央2台),进而使得前面编的程序(按5频道)显得很灵活。注意虚之所以能够很灵活是因为它一定通过“一种手段”来间接达到目的,如每个频道记录着一个频率。但这是不够的,一定还有“另一段代码”能改变那种手段的结果(频道记录的频率),如调台。
  先看虚继承。它间接从子类的实例中获得父类实例的所在位置,通过虚类表实现(这是“一种手段”),接着就必须能够有“另一段代码”来改变虚类表的值以表现其灵活性。首先可以自己来编写这段代码,但就要求清楚编译器将虚类表放在什么地方,而不同的编译器有不同的实现方法,则这样编写的代码兼容性很差。C++当然给出了“另一段代码”,就是当某个类在同一个类继承体系中被多次虚继承时,就改变虚类表的值以使各子类间接获得的父类实例是同一个。此操作的功能很差,仅仅只是节约内存而已。如:
  struct A { long a; };
  struct B : virtual public A { long b; }; struct C : virtual public A { long c; };
  struct D : public B, public C { long d; };
  这里的D中有两个虚类表,分别从B和C继承而来,在D的构造函数中,编译器会编写必要的代码以正确初始化D的两个虚类表以使得通过B继承的虚类表和通过C继承的虚类表而获得的A的实例是同一个。
  再看虚函数。它的地址被间接获得,通过虚函数表实现(这是“一种手段”),接着就必须还能改变虚函数表的内容。同上,如果自己改写,代码的兼容性很差,而C++也给出了“另一段代码”,和上面一样,通......

阅读全文(3685) | 评论:0

C++从零开始(十一)(中)——类的相关知识 (2005-11-13 13:47:00)

摘要:

--------------------------------------------------------------------------------



由于篇幅限制,本篇为《C++从零开始(十一)》的中篇,说明多重继承、虚继承和虚函数的实现方式。


多重继承

  这里有个有趣的问题,如下:
  struct A { long a, b, c; char d; }; struct B : public A { long e, f; };
  上面的B::e和B::f映射的偏移是多少?不同的编译器有不同的映射结果,对于派生的实现,C++并没有强行规定。大多数编译器都是让B::e映射的偏移值为16(即A的长度,关于自定义类型的长度可参考《C++从零开始(九)》),B::f映射20。这相当于先把空间留出来排列父类的成员变量,再排列自己的成员变量。但是存在这样的语义——西红柿即是蔬菜又是水果,鲸鱼即是海洋生物又是脯乳动物。即一个实例既是这种类型又是那种类型,对于此,C++提供了多重派生或称多重继承,用“,”间隔各父类,如下:
  struct A { long A_a, A_b, c; void ABC(); }; struct B { long c, B_b, B_a; void ABC(); };
  struct AB : public A, public B { long ab, c; void ABCD(); };
  void A::ABC() { A_a = A_b = 10; c = 20; }
  void B::ABC() { B_a = B_b = 20; c = 10; }
  void AB::ABCD() { A_a = B_a = 1; A_b = B_b = 2; c = A::c = B::c = 3; }
  void main() { AB ab; ab.A_a = 3; ab.B_b = 4; ab.ABC(); }
  上面的结构AB从结构A和结构B派生而来,即我们可以说ab既是A的实例也是B的......

阅读全文(3600) | 评论:0

C++从零开始(十一)(上)——类的相关知识 (2005-11-13 13:47:00)

摘要:
--------------------------------------------------------------------------------



前面已经介绍了自定义类型的成员变量和成员函数的概念,并给出它们各自的语义,本文继续说明自定义类型剩下的内容,并说明各自的语义。


权限

  成员函数的提供,使得自定义类型的语义从资源提升到了具有功能的资源。什么叫具有功能的资源?比如要把收音机映射为数字,需要映射的操作有调整收音机的频率以接收不同的电台;调整收音机的音量;打开和关闭收音机以防止电力的损耗。为此,收音机应映射为结构,类似下面:
  struct Radiogram
  {
    double Frequency; /* 频率 */ void TurnFreq( double value );   // 改变频率
    float Volume;   /* 音量 */ void TurnVolume( float value ); // 改变音量
    float Power;     /* 电力 */ void TurnOnOff( bool bOn );     // 开关
    bool   bPowerOn;   // 是否开启
  };
  上面的Radiogram::Frequency、Radiogram::Volume和Radiogram::Power由于定义为了结构Radiogram的成员,因此它们的语义分别为某收音机的频率、某收音机的音量和某收音机的电力。而其余的三个成员函数的语义也同样分别为改变某收音机的频率、改变某收音机的音量和打开或关闭某收音机的电源。注意这面的“某”,表示具体是哪个收音机的还不知道,只有通过成员操作符将左边的一个具体的收音机和它们结合时才知道是哪个收音机的,这也是为什么它们被称作偏移类型。这一点在下一篇将详细说明。
  注意问题:为什么要将刚才的三个操作映射......

阅读全文(3759) | 评论:0

C++从零开始(十)——何谓类 (2005-11-13 13:46:00)

摘要:

--------------------------------------------------------------------------------



160前篇说明了结构只不过是定义了内存布局而已,提到类型定义符前还可以书写class,即类型的自定义类型(简称类),它和结构根本没有区别(仅有一点小小的区别,下篇说明),而之所以还要提供一个class,实际是由于C++是从C扩展而成,其中的class是C++自己提出的一个很重要的概念,只是为了与C语言兼容而保留了struct这个关键字。不过通过前面括号中所说的小小区别也足以看出C++的设计者为结构和类定义的不同语义,下篇说明。
  暂时可以先认为类较结构的长足进步就是多了成员函数这个概念(虽然结构也可以有成员函数),在了解成员函数之前,先来看一种语义需求。


操作与资源

  程序主要是由操作和被操作的资源组成,操作的执行者就是CPU,这很正常,但有时候的确存在一些需要,需要表现是某个资源操作了另一个资源(暂时称作操作者),比如游戏中,经常出现的就是要映射怪物攻击了玩家。之所以需要操作者,一般是因为这个操作也需要修改操作者或利用操作者记录的一些信息来完成操作,比如怪物的攻击力来决定玩家被攻击后的状态。这种语义就表现为操作者具有某些功能。为了实现上面的语义,如原来所说进行映射,先映射怪物和玩家分别为结构,如下:
  struct Monster { float Life; float Attack; float Defend; };
  struct Player { float Life; float Attack; float Defend; };
  上面的攻击操作就可以映射为void MonsterAttackPlayer( Monster &mon, Player &pla );。注意这里期望通过函数名来表现操作者,但和前篇说的将过河方案起名为sln一样,属于一种本末倒置,因为这个语义应该由类型来表现,而不是函数名。为此,C++提供了成员函数的概念。


成员函数

  与之前一样,在类型定义符......

阅读全文(3409) | 评论:0

C++从零开始(九)——何谓结构 (2005-11-13 13:46:00)

摘要:
--------------------------------------------------------------------------------



前篇已经说明编程时,拿到算法后该干的第一件事就是把资源映射成数字,而前面也说过“类型就是人为制订的如何解释内存中的二进制数的协议”,也就是说一个数字对应着一块内存(可能4字节,也可能20字节),而这个数字的类型则是附加信息,以告诉编译器当发现有对那块内存的操作语句(即某种操作符)时,要如何编写机器指令以实现那个操作。比如两个char类型的数字进行加法操作符操作,编译器编译出来的机器指令就和两个long类型的数字进行加法操作的不一样,也就是所谓的“如何解释内存中的二进制数的协议”。由于解释协议的不同,导致每个类型必须有一个唯一的标识符以示区别,这正好可以提供强烈的语义。

typedef

  提供语义就是要尽可能地在代码上体现出这句或这段代码在人类世界中的意义,比如前篇定义的过河方案,使用一char类型来表示,然后定义了一数组char sln[5]以期从变量名上体现出这是方案。但很明显,看代码的人不一定就能看出sln是solution的缩写并进而了解这个变量的意义。但更重要的是这里有点本末倒置,就好像这个东西是红苹果,然后知道这个东西是苹果,但它也可能是玩具、CD或其它,即需要体现的语义是应该由类型来体现的,而不是变量名。即char无法体现需要的语义。
  对此,C++提供了很有意义的一个语句——类型定义语句。其格式为typedef <源类型名> <标识符>;。其中的<源类型名>表示已存在的类型名称,如char、unsigned long等。而<标识符>就是程序员随便起的一个名字,符合标识符规则,用以体现语义。对于上面的过河方案,则可以如下:
  typedef char Solution; Solution sln[5];
  上面其实是给类型char起了一个别名Solution,然后使用Solution来定义sln以更好地体现语义来增加代码的可读性。而前篇将两岸的人数分布映射成char[4],为了增强语义,则可以如下:
 ......

阅读全文(3521) | 评论:0

C++从零开始(八)——C++样例一 (2005-11-13 13:44:00)

摘要:

--------------------------------------------------------------------------------



前篇说明了函数的部分实现方式,但并没有说明函数这个语法的语义,即函数有什么用及为什么被使用。对于此,本篇及后续会零散提到一些,在《C++从零开始(十二)》中再较详细地说明。本文只是就程序员的基本要求——拿得出算法,给得出代码——给出一些样例,以说明如何从算法编写出C++代码,并说明多个基础且重要的编程概念(即独立于编程语言而存在的概念)。


由算法得出代码

  本系列一开头就说明了何谓程序,并说明由于CPU的世界和人们存在的客观物理世界的不兼容而导致根本不能将人编写的程序(也就是算法)翻译成CPU指令,但为了能够翻译,就必须让人觉得CPU世界中的某些东西是人以为的算法所描述的某些东西。如电脑屏幕上显示的图片,通过显示器对不同象素显示不同颜色而让人以为那是一幅图片,而电脑只知道那是一系列数字,每个数字代表了一个象素的颜色值而已。
  为了实现上面的“让人觉得是”,得到算法后要做的的第一步就是找出算法中要操作的资源。前面已经说过,任何程序都是描述如何操作资源的,而C++语言本身只能操作内存的值这一种资源,因此编程要做的第一步就是将算法中操作的东西映射成内存的值。由于内存单元的值以及内存单元地址的连续性都可以通过二进制数表示出来,因此要做的第一步就是把算法中操作的东西用数字表示出来。
  上面做的第一步就相当于数学建模——用数学语言将问题表述出来,而这里只不过是用数字把被操作的资源表述出来罢了(应注意数字和数的区别,数字在C++中是一种操作符,其有相关的类型,由于最后对它进行计算得到的还是二进制数故使用数字进行表示而不是二进制数,以增强语义)。接着第二步就是将算法中对资源的所有操作都映射成语句或函数。
  用数学语言对算法进行表述时,比如将每10分钟到车站等车的人的数量映射为一随机变量,也就前述的第一步。随后定此随机变量服从泊松分布,也就是上面的第二步。到站等车的人的数量是被操作的资源,而给出的算法是每隔10分种改变这个资源,将它的值变成按给定参数的泊松函数分布的一随机值。

阅读全文(3328) | 评论:0

C++从零开始(七)——何谓函数 (2005-11-13 13:44:00)

摘要:
--------------------------------------------------------------------------------


本篇之前的内容都是基础中的基础,理论上只需前面所说的内容即可编写出几乎任何只操作内存的程序,也就是本篇以后说明的内容都可以使用之前的内容自己实现,只不过相对要麻烦和复杂许多罢了。
  本篇开始要比较深入地讨论C++提出的很有意义的功能,它们大多数和前面的switch语句一样,是一种技术的实现,但更为重要的是提供了语义的概念。所以,本篇开始将主要从它们提供的语义这方面来说明各自的用途,而不像之前通过实现原理来说明(不过还是会说明一下实现原理的)。为了能清楚说明这些功能,要求读者现在至少能使用VC来编译并生成一段程序,因为后续的许多例子都最好是能实际编译并观察执行结果以加深理解(尤其是声明和类型这两个概念)。为此,如果你现在还不会使用VC或其他编译器来进行编译代码,请先参看其他资料以了解如何使用VC进行编译。为了后续例子的说明,下面先说明一些预备知识。


预备知识

  写出了C++代码,要如何让编译器编译?在文本文件中书写C++代码,然后将文本文件的文件名作为编译器的输入参数传递给编译器,即叫编译器编译给定文件名所对应的文件。在VC中,这些由VC这个编程环境(也就是一个软件,提供诸多方便软件开发的功能)帮我们做了,其通过项目(Project)来统一管理书写有C/C++代码的源文件。为了让VC能了解到哪些文件是源文件(因为还可能有资源文件等其他类型文件),在用文本编辑器书写了C++代码后,将其保存为扩展名为.c或.cpp(C Plus Plus)的文本文件,前者表示是C代码,而后者表示C++代码,则缺省情况下,VC就能根据不同的源文件而使用不同的编译语法来编译源文件。
  前篇说过,C++中的每条语句都是从上朝下执行,每条语句都对应着一个地址,那么在源文件中的第一条语句对应的地址就是0吗?当然不是,和在栈上分配内存一样,只能得到相对偏移值,实际的物理地址由于不同的操作系统将会有各自不同的处理,如在Windows下,代码甚至可以没有物理地址,且代码对应的物理地址还能随时变化。
  当要编写一个稍......

阅读全文(3740) | 评论:0