接上段.
我们在Ostrich类的基础写了fly(),tweet()成员函数,这是与基类的成员函数名字相同。那么它们将覆盖基类的函数,如果再调用Ostrich类的fly(),tweet()函数的话则会调用我们新写的这两个函数了。
基本代码如下:
#include <iostream.h>
#include <string.h>
class Aves
{
public:
Aves ();
~Aves ();
void tweet();
void run();
void fly();
char m_strHead[10];
char m_strTrunk[10];
char m_strCrura[10];
char m_strWing[10];
protected:
char m_strBowels[10];
};
Aves::Aves()
{
strcpy(m_strHead, "Head");
strcpy(m_strTrunk, "Trunk");
strcpy(m_strCrura, "Crura");
strcpy(m_strWing, "Wing");
strcpy(m_strBowels, "Bowels");
cout<<"a bird born!"<<endl;
}
Aves::~Aves()
{
cout<<"a bird die!"<<endl;
}
void Aves::tweet()
{
cout<<"jijijijijijiji"<<endl;
}
void Aves::run()
{
cout<<"I can run by "<<m_strCrura<<endl;
}
void Aves::fly()
{
cout<<"I can fly by "<<m_strWing<<endl;
}
class Ostrich : public Aves
{
public:
void tweet();
void fly();
};
void Ostrich::tweet()
{
cout<<"gugugugugugugu"<<endl;
}
void Ostrich::fly()
{
cout<<"I can't fly by "<<m_strWing<<endl;
}
void main()
{
{
Aves bird;
bird.fly();
bird.run();
bird.tweet();
}
cout <<"====================="<<endl;
{
Ostrich aOstrich;
aOstrich.fly();
aOstrich.run();
aOstrich.tweet();
}
}
在主函数中我加多加了两对大括号,请大家分析一下bird,aOstrich生存区域。
以上是一个单继承的例子,至于多继承解释理论是一样。大家可以自己尝试。在后的第七部分中的COM编写中将出现多继承的现象。
在继承派生还记得,派生类对象也是其基类的对象,基类的指针是可以指向派生类的对象的。如我们有是一个Aves *lpBird;指针,那么我们写lpBird=&aOstrich是合法的,因为鸵鸟也是一种鸟。
现在,要提到类最后的一个重要概念就是虚成员函数。上一段文字里说到一个基类指针可以指向一个派生类的对象。如果当lpBird指向了aOstrich,那么调用lpBird->fly();的结果会是什么呢?哇,是”I can fly by Wing”,快来看呐,我们指的那只鸟居然会飞了!显然这是我们不希望看到的结果。为了解决这个问题,我在Aves类声明体中将所有成员函数定义为virtual虚函数。
class Aves
{
public:
Aves ();
~Aves ();
virtual void tweet();
virtual void run();
virtual void fly();
char m_strHead[10];
char m_strTrunk[10];
char m_strCrura[10];
char m_strWing[10];
protected:
char m_strBowels[10];
};
再试试看,结果成为我们要的” I can’t fly by Wing”了。为什么呢?
是这样的。当一个类中有虚函数(包括基类含有的)的时候,会给这个类的所有虚函数建立起一个表,函数名与函数地址的映射(包括基类的虚函数)。当对象执行一个虚函数时,则系统先会查这个虚函数表(vtable),找到这个函数名对应的函数地址,调用它。当在派生类添加了与基类虚函数同名的函数,系统会自动将其设定为虚函数。并将这个函数地址改写到虚函数表中。如果再调用这个虚函数时,就会调用新添加的虚函数。像上面的例子,当调用lpBird->fly()时,系统会先查lpBird指向对象的虚函数表,而不会不管三七二十一地直接调用其本类的函数。示例代码如下:
#include <iostream.h>
#include <string.h>
class Aves
{
public:
Aves ();
~Aves ();
virtual void tweet();
virtual void run();
virtual void fly();
char m_strHead[10];
char m_strTrunk[10];
char m_strCrura[10];
char m_strWing[10];
protected:
char m_strBowels[10];
};
Aves::Aves()
{
strcpy(m_strHead, "Head");
strcpy(m_strTrunk, "Trunk");
strcpy(m_strCrura, "Crura");
strcpy(m_strWing, "Wing");
strcpy(m_strBowels, "Bowels");
cout<<"a bird born!"<<endl;
}
Aves::~Aves()
{
cout<<"a bird die!"<<endl;
}
void Aves::tweet()
{
cout<<"jijijijijijiji"<<endl;
}
void Aves::run()
{
cout<<"I can run by "<<m_strCrura<<endl;
}
void Aves::fly()
{
cout<<"I can fly by "<<m_strWing<<endl;
}
class Ostrich : public Aves
{
public:
void tweet();
void fly();
};
void Ostrich::tweet()
{
cout<<"gugugugugugugu"<<endl;
}
void Ostrich::fly()
{
cout<<"I can't fly by "<<m_strWing<<endl;
}
void main()
{
{
Aves bird;
bird.fly();
bird.run();
bird.tweet();
}
cout <<"====================="<<endl;
{
Ostrich aOstrich;
aOstrich.fly();
aOstrich.run();
aOstrich.tweet();
Aves *lpBird;
lpBird = &aOstrich;
lpBird->fly();
}
}
关于虚函数更具体的情况,请参看vckbase第12期的《解析动态联编》。关于其它C++语法,请自行查看C++教材。
那么MFC类呢?简单地来说,MFC类只是将许多有关联的API函数将其封装在一起。在WinSDK中WinAPI都是一些零零散散的函数,它们大部分中都会有一个参数是它的服务对象的句柄。比如,CreateWindow函数会需要一个句柄输出来返回一个窗口句柄来表达其建立的窗口对象,ShowWindow函数需要一个窗口句柄来指定哪一个窗口要改变显示状态,CloseWindow函数需要一个窗口句柄来指定哪一个窗口要被关闭。可以理解为是句柄在围绕着函数转,句柄在以函数为中心。而MFC是将几个若干有服务关联的函数封装在一起成为成员函数,每一个类会有一个保护的句柄成员变量来保存当前类对象所代表的服务对象,在对外调用上看就可以将其类对象看成其服务对象,这些成员函数就可以看成其服务对象本来固有的方法。在使用上比使用WinAPI更为形象和理解。下面做一个比照的例子.
SDK写法
HWND hCurrentWnd;
hCurrentWnd = ::CreateWindow (...);
::ShowWindow(hCurrent,SW_SHOW);
::CloseWindow(hCurrent);
MFC写法
CWnd CurrentWnd;
CurrentWnd.CreateWindow (…);
CurrentWnd.ShowWindow (SW_SHOW);
CurrentWnd.CloseWindow();
怎么样,MFC没有枯燥零碎的句柄的概念。我们足可以想象成一个类对象就是一个服务对象,它本身有许多对其控制的方法。这就是制作MFC的主要目的。
所有的MFC类的基类是CObject的。你可以用CObject的指针指代所有MFC类。CWnd类是所有关于窗口的API函数进行了封装。所有的控件都是派生于这个类的如CEdit,CButton,CDialog, CFrameWnd, CMDIFrameWnd, CMDIChildWnd, CView, CDialog等
MFC更深的理论比如消息映射,CRuntimeClass等在《深入浅出MFC》,《C++技术内幕》等书有详细探讨与讲解,强力推荐。我就是看这几本书入的门。MFC类的各个功能也参见MSDN。
下一部分将详细解释一个MFC对话框程序。
下面,我要介绍一下一些C++程序建议编写规范。
一、变、常、参量的建议:
1.常、变量应当定义函数体最前面或一对大括号的最前面,全局常、变量就当放在整个文件的最前面。这样便于管理与维护。
2.声明变、参量的应当使用匈牙利命名法。为变、参量添加适当的前缀,并以有意义的可拼读的名词性英文单词来命名,每个英文语素首字母都应当大写。如: m_nCount;则表示这是一个类的成员,为整型,是用做计数器用的。
常用的前缀有:
前缀
表示内容
前缀
表示内容
_或Afx
表示为全局
m_
表示为某个类的成员
b
表示为布尔型
h
表示为句柄
c或ch
表示为字符型
l
表示为长型
clr
表示为32位颜色值
n
表示为整型
cx或cy
表示为坐标的水平或垂直值
p或lp
表示为指针
w
表示为字(WORD)型
sz
表示为以0结尾的字符串
str
表示为CString字符型
dw
表示为双字(DWORD)型
3.常量应当用const来定义,而不是用预处理指令#define。并且常量名应当大写。
4.如果是全局的常、变量应当加前缀_。
二、函数的建议:
1.参数的定义位置要附和人性化,输出参数在前,输入参数在后。
2.如果参数是指针,且仅作输入用,则应在类型前加const,以防止该指针在函数体内被意外修改。
3.函数名如果是全局的应当加前缀Afx,函数名以有意义的可拼读的动词性英文单词或短语来命名,且每个英文语素首字母都就应当大写。如:AfxGetMessage(),Close()。
4.一般函数的返回值最好用来返回错误标志,而真正的返回值应当用输出参数来返回。
5.在函数的前几行,应当对入口参数进行有效性检查。
6.函数体的规模要小,尽量控制在50 行代码之内。否则,应当进行拆分。
7.函数体中不要声明使用静态变量,那样会使用函数难以控制。
三、 类的建议:
1.所有的成员变量应有m_前缀。
2.在类的声明体中应遵循公有,保护,最后私有,前函数,后成员的顺序进行声明。
3.不能在类的声明体中进行操作,这是不正确的,也是编译器所不允许的。即是初始化(例如:int m_nCount=0;)也不可以。
4.应当在一个以类名为名字,后缀为.h的文件中写入类的声明体,应当在一个以类名为名字,后缀为.cpp的文件中写入类的成员函数的实现语句。并且在.cpp文件的第一行写上#include “类名.h”。
四、其它的建议:
1.for,if,do,while等语句中,无论后面的语句是否为一行,必须用大括号括起来。
2.不可以在一行上写入多条语句,那样会使程序的可读性降低。
3.应当在不易理解的程序段或行上(比如内联汇编语句),加上注释。不要在显而易懂的语句上加上注释,(如:i++;)
4.在编程中,应当想到今后的可重性,给今后便于修改留下余地。
5.在要求技术性的程序上,尽量以最简捷的代码来完成功能。除非必要,否则不要去追求视觉界面效果。因为,界面代码要比功能代码混乱得多。会使代码不易维护。
6.应当将一些常用的功能整理成可以直接使用的类,这样不仅省功且使代码看起来简洁。如界面代码等。
7.在正式编程的时候,切勿直接编写代码。应当先规划好程序结构及其算法等程序实现,再去动手。因为在编程中重要的不是如何去代码,而是程序的实现方法。而且直接写码,肯定会导致程序代码的修修改改,使代码看起来很杂乱。
8.要习惯于在大部分时间去书写文档,因为程序关键是在于是否会被人能够接受使用。
9.要用70%的时间去设想程序的算法,要用27%的时间去书写文档,只能用1%的时间去编写代码,最后用2%的时间去调试代码。
第二部分完。