虚拟函数-3、虚拟函数的运用

编写通用的功能模块,处理多种对象类型。关于这种用法,上面已经讨论许多,这里作简要总结。

q     处理的多种对象应该有共性,可以封装在一个类家族中。这个类家族的理想结构是多向二层次的(如图2-4所示),或者是单向多层次的(如图2-5所示)。

               

             图2-4  多向二层次类家族                         图2-5  单向多层次类家族

q     在基类中定义若干公共虚拟函数,派生类对其部分或全部重载,但重载的虚函数应该与基类的声明一致,即参数列表一致(当然可以省掉virtual关键字)。

q     每个类都封装一种处理对象的数据集和操作集。其中,几个处理对象的共性部分封装在相对的基类中;每个处理对象的特性操作封装在重载的虚函数中。

q     定义通用函数,其形参之一是基类的指针或引用。

以上是一般的应用模式,随着需求的不同会有一定差异。

 

2.4.1  不改变父类的代码,可以改变父类的行为
下面举例说明这一用法。示例2.2定义了一个Rectangle类,用于计算矩形面积、输出矩形信息。

示例清单2.2

#include "stdio.h"

class Rectangle

 

{

public:

         Rectangle(){ m_cx=0;m_cy=0;};

         Rectangle(float cx,float cy)

         {

         m_cx=cx;

         m_cy=cy;

         }

//声明为保护,为了给子类继承

protected:

//m_cx是水平宽度,m_cy是垂直高度

         float m_cx;

         float m_cy;

public:

//加const关键字,该函数不能直接或间接更改类成员

//在简单并且使用率高的成员函数前加inline关键字,可以提高效率

         inline float GetCx() const  

         {return m_cx;}

        

         inline float GetCy() const

         {return m_cy;}

        

         inline void SetCx(float cx)

         { m_cx=cx;}

        

         inline void SetCy(float cy)

         { m_cy=cy;}

 

         void DispRectInfo();

protected:

  //因为该虚函数只在类的内部使用,所以声明为保护。在本例中也可以声明为私有

         virtual float CalculateArea();

};

void Rectangle::DispRectInfo()

{

  printf("the cx=%f\n",GetCx());

  printf("the cy=%f\n",GetCy());

  printf("the area=%f\n",CalculateArea());

}

float Rectangle::CalculateArea()

{    //该虚函数用于计算矩形面积

 return m_cx*m_cy;

}

int main(float argc, char* argv[])

{

         Rectangle rect(20,30);

         rect.DispRectInfo();

         return 0;

}

程序输出:

the cx=20.000

the cy=30.000

the area=600.000

如果现在要修改示例2.2的功能,增加对平行四边形的处理,可以定义一个Rectangle的派生类Parallelogram。其代码如下:

class Parallelogram :public Rectangle

{

public:

         Parallelogram(float cx,float cy,float height):Rectangle(cx,cy)

         { m_height=height;}

         Parallelogram(){ m_height=0;}

 

protected:

         float m_height;

public:

         inline float GetHeight()const

         {return m_height;}

         inline void SetHeight(float height){m_height=height;}

//定义用于输出平行四边形信息的函数

         void DispParalleInfo();

 

protected:

//重载虚函数,计算平行四边形面积,而不是矩形面积

          float CalculateArea();

};

void Parallelogram::DispParalleInfo()

{

  printf("the height=%f\n",m_height);

//调用基类的成员函数,输出边和面积信息

  DispRectInfo(); 

}

float Parallelogram::CalculateArea()

{

//计算平行四边形面积

 return m_cx*m_height;

}

程序段

{

Parallelogram para(20,30,10);

para.DispParalleInfo();

}

的输出结果:

the height=10.000

the cx=20.000

the cy=30.000

the area=200.000

由输出结果可知,Rectangle::DispRectInfo()并没有调用Rectangle::CalculateArea(),而是调用了Parallelogram::CalculateArea()。边长同样是20和30,但输出结果不同。是的,父类的行为被改变了。

2.4.2  不知道对象的类型,可以执行对象的特定操作
如果正在设计一个数据库管理系统,那就必然要做数据录入的工作。面对各种数据类型如整形、浮点、特殊格式的字符串等,我们往往使用CEdit类进行处理。使用CEdit类的明显缺点是,在录入时要逐个进行专门校验,提交数据时,要记住每个CEdit控件关联的数据类型。

对于以上问题,可以从CEdit派生一个类,假设名为CCustEdit,在类中定义若干虚函数。然后针对每种数据类型,从CCustEdit派生出子类进行封装。子类通过重载虚函数实现录入校验、提交处理等工作。例如处理数据提交时,可以从控件父窗体逐一取得每个控件的CCustEdit*型的指针,调用其相应的虚函数,得到正确格式的数据。

示例2.3是生成insert SQL语句的演示程序。假设insert语句中,数值类型不加单引号,字符串类型加单引号。例如:

insert into user_database ( name, age, stature, weight) values(‘XinChangAn’,27,1.65,125.22)

本例程序定义了一个CCustEdit基类,由其派生出3个类CIntEdit、CFloatEdit、CVcharEdit分别封装对整型、浮点型、字符串型数据的处理。CCustEdit::BuildFields()函数用于构造insert语句的字段列表部分;子类重载的虚拟函数BuildValues()用于构造值列表部分。这两部分最后在main()中被连接起来,形成一个完整的SQL语句。

示例清单2.3

/C123.H

//insert语句字段列表以前的长度

#define MAXSIZE_SQL_FIELDS 200

//insert语句值列表以后的长度

#define MAXSIZE_SQL_VALUES 200

//name字段的最大长度

#define MAXSIZE_NAME_FIELD  20

//字段名称最大长度

#define MAXSIZE_FIELDNAME  20

//语句中最多字段数

#define MAX_FIELDSCOUNT 50

 

//基类CCustEdit的定义

class CCustEdit

{

public:

         CCustEdit();

         CCustEdit(const char* FieldName);

         virtual ~CCustEdit(){};

public:

         void BuildFields(char * Statement);

 //基类定义虚函数,为子类提供重载的形式

         void virtual BuildValues(char * Statement);

         void SetFieldName(const char * FieldName);

protected:

  //用于存储控件关联的字段名称

         char m_FieldName[MAXSIZE_FIELDNAME];

};

 

//子类CIntEdit的定义

class CIntEdit:public CCustEdit

{

public:

         CIntEdit();

         CIntEdit(const char * FieldName,int number);

         virtual ~CIntEdit(){};

 

public:

         void virtual BuildValues(char * Statement);

         CIntEdit& operator =(int iValue);

         void SetValue(int iValue);

         int GetValue()const;

private:

//用于封装整型字段值

         int m_data;

};

 

//子类CFoatEdit的定义

class CFloatEdit:public CCustEdit

{

public:

         CFloatEdit();

         CFloatEdit(const char* FieldName,float number);

         virtual ~CFloatEdit(){};

public:

         void virtual BuildValues(char * Statement);

         CFloatEdit& operator =(float fValue);

         void SetValue(float fValue);

         float GetValue()const;

private:

//用于封装浮点型字段值   

float m_data;

};

 

//子类CVcharEdit的定义

class CVcharEdit:public CCustEdit

{

public:

         CVcharEdit();

         CVcharEdit(const char * FieldName,const char * str);

         virtual ~CVcharEdit(){};

public:

         void virtual BuildValues(char * Statement);

         CVcharEdit& operator =(const char* sValue);

         void SetValue(const char * sValue);

         void GetValue(char * sValue,unsigned int MaxCount)const;

private:

//用于封装字符串型字段值

         char m_data[MAXSIZE_NAME_FIELD];

};

///C123.CPP

#include "stdio.h"

#include "string.h"

#include "stdlib.h"

#include "C123.h"

 

//基类CCustEdit的实现

CCustEdit::CCustEdit()

{

         memset(m_FieldName,0,MAXSIZE_FIELDNAME);

}

CCustEdit::CCustEdit(const char* FieldName)

//形参FieldName用于初始化m_FieldName

memset(m_FieldName,0,MAXSIZE_FIELDNAME);

           if(NULL==FieldName)

                  return;

          if(strlen(FieldName)>=MAXSIZE_FIELDNAME)

           {  printf("the field name %s is too long,truncated it",FieldName);

              strncpy(m_FieldName,FieldName,MAXSIZE_FIELDNAME-1);

           }

           else

                    strcpy(m_FieldName,FieldName);

}

 

void CCustEdit::BuildValues(char * Statement)

{

           printf("insert statement is %s\n",Statement);

}

 

void CCustEdit::BuildFields(char * Statement)

{

//构造语句的字段列表部分,即将字段名m_FieldName和一个','字符追加到实参Statement中

                if(NULL==Statement)

           return;

                  if(strlen(Statement)>MAXSIZE_SQL_FIELDS-strlen(m_FieldName)-2)

                          printf("out of buffer when build statement with fields\n");

                  else

                    sprintf(Statement+strlen(Statement),"%s,",m_FieldName);

}

void CCustEdit::SetFieldName(const char * FieldName)

{

         //形参FieldName用于设置m_FieldName

           if(NULL==FieldName)

                  return;

           memset(m_FieldName,0,MAXSIZE_FIELDNAME);

           if(strlen(FieldName)>=MAXSIZE_FIELDNAME)

           {  printf("the field name %s is too long,truncated it",FieldName);

              strncpy(m_FieldName,FieldName,MAXSIZE_FIELDNAME-1);

           }

           else

                    strcpy(m_FieldName,FieldName);

 

}

 

子类CIntEdit的实现

CIntEdit::CIntEdit(const char * FieldName,int number):CCustEdit(FieldName)

{

         m_data=number;

}

CIntEdit& CIntEdit::operator =(int iValue)

{

           m_data=iValue;

           return *this;

}

void CIntEdit::SetValue(int iValue)

{

           *this=iValue;

}

int CIntEdit::GetValue()const

{

         return m_data;

}

void CIntEdit::BuildValues(char * Statement)

//构造语句的值列表部分,即将m_data和一个','字符追加到实参Statement中

                if(NULL==Statement)

                  return;

                  char sTemp[20];

                  _itoa(m_data,sTemp,10);

                  if(strlen(Statement)>MAXSIZE_SQL_VALUES-strlen(sTemp)-2)

                          printf("out of buffer when build statement with values\n");

                  else

                  sprintf(Statement+strlen(Statement),"%d,",m_data);

}

 

//子类CFoatEdit的实现

CFloatEdit::CFloatEdit()

{

         m_data=0;

}

CFloatEdit::CFloatEdit(const char* FieldName,float number):CCustEdit(FieldName)

{

         m_data=number;

}

CFloatEdit& CFloatEdit::operator =(float fValue)

{

         m_data=fValue;

         return *this;

}

void CFloatEdit::SetValue(float fValue)

{

           *this=fValue;

}

float CFloatEdit::GetValue()const

{

         return m_data;

}

void CFloatEdit::BuildValues(char * Statement)

         {

//构造语句的值列表部分,即将m_data和一个','字符追加到实参Statement中

                  if(NULL==Statement)

                       return;

                  if(strlen(Statement)>MAXSIZE_SQL_VALUES-14)

                          printf("out of buffer when build statement with values\n");

                  else

                   sprintf(Statement+strlen(Statement),"%8.3f,",m_data);

         }

 

///子类CVcharEdit的实现

CVcharEdit::CVcharEdit()

{

         memset(m_data,0,MAXSIZE_NAME_FIELD);

}

CVcharEdit::CVcharEdit(const char * FieldName,const char * str):CCustEdit(FieldName)

{

//形参str用于初始化m_data

                  memset(m_data,0,MAXSIZE_NAME_FIELD);

                  if(NULL==str)

                       return;

                  if(strlen(str)<MAXSIZE_NAME_FIELD)

                          strcpy(m_data,str);

                  else

                          strncpy(m_data,str,MAXSIZE_NAME_FIELD-1);

}

void CVcharEdit::BuildValues(char * Statement)

{

//构造语句的值列表部分,即将m_data和一个','字符追加到实参Statement中

                 if(NULL==Statement)

                  return;

                  if(strlen(Statement)>MAXSIZE_SQL_VALUES-strlen(m_data)-4)

                          printf("out of buffer when build statement with values\n");

                  else

                   sprintf(Statement+strlen(Statement),"\'%s\',",m_data);

}

CVcharEdit& CVcharEdit::operator =(const char* sValue)

{

         //形参str用于设置m_data

                  if(NULL==sValue)

                  return *this;

                  memset(m_data,0,MAXSIZE_NAME_FIELD);

                  if(strlen(sValue)<MAXSIZE_NAME_FIELD)

                          strcpy(m_data,sValue);

                  else

                          strncpy(m_data,sValue,MAXSIZE_NAME_FIELD-1);

        

return *this;

}

void CVcharEdit::SetValue(const char * sValue)

{

           *this=sValue;

}

void CVcharEdit::GetValue(char * sValue,unsigned int MaxCount)const

{

           if(NULL==sValue)

                    return ;

                  if(strlen(m_data)>=MaxCount)

                          strncpy(sValue,m_data,MaxCount-1);

                  else

strcpy(sValue,m_data);

}

/*******************************************************************

全局的构造SQL语句帮助器函数

EditList      封装字段名和字段值的控件的列表。在这里不必关心每个控件的类型,利用每个

控件的虚函数,可以方便地构造出SQL语句

InsStaFields      用于保存insert语句字段列表以前部分

InsStaValues     用于保存insert语句值列表以后部分

********************************************************************/

void gBuildHelper(CCustEdit** EditList,char*InsStaFields,char * InsStaValues)

{

   for(int i=0;i<MAX_FIELDSCOUNT&&EditList[i]!=NULL;i++)

   {

            EditList[i]->BuildFields(InsStaFields);

            EditList[i]->BuildValues(InsStaValues);

   }

}

 

int main(int argc, char* argv[])

{

         char InsertStatement[MAXSIZE_SQL_FIELDS+MAXSIZE_SQL_VALUES+2];

         char InsStaFields[MAXSIZE_SQL_FIELDS];

         char InsStaValues[MAXSIZE_SQL_VALUES];

 

         CVcharEdit Name("name","XinChangAn");

         CIntEdit Age("age",27);

         CFloatEdit Stature("stature",(float)1.65);

         CFloatEdit Weight("weight",(float)125.22);

  CCustEdit **EditList;

 

         EditList=new CCustEdit*[5];

   

         EditList[0]=&Name;

         EditList[1]=&Age;

         EditList[2]=&Stature;

         EditList[3]=&Weight;

         EditList[4]=NULL;

        

         strcpy(InsStaFields,"insert into user_database(");

         strcpy(InsStaValues,"values(");

    //使用控件列表构造SQL语句

         gBuildHelper(EditList, InsStaFields,InsStaValues);

        

         //将最后一个字符','改为')'

         InsStaFields[strlen(InsStaFields)-1]=')';

         InsStaValues[strlen(InsStaValues)-1]=')';

        

         //连接两段语句到InsertStatement中

         strcpy(InsertStatement,InsStaFields);

         strcat(InsertStatement,InsStaValues);

         //输出完整的SQL语句

         printf("%s\n",InsertStatement);

          delete[] EditList;

         return 0;

}

程序输出的结果:

insert into user_database(name,age,stature,weight)values('XinChangAn',27,   1.650, 125.220)

以上讨论了虚函数的两种应用情况。现在读者可以得出这样的结论:每一种应用情况都是从不同的应用角度考虑问题的结果,其实质都是利用虚函数的动态联编,编写相对通用的模块。

2.4.3  如果类包含虚拟成员函数,则将此类的析构函数也定义为虚拟函数
这是很必要的,因为派生类对象往往由基类的指针引用,如果使用new操作符在堆中构造派生类对象,并将其地址赋给基类指针,那么最后要使用delete操作符删除这个基类指针(释放对象占用的堆栈)。这时如果析构函数不是虚拟的,派生类的析构函数不会被调用。例如示例2.4的程序将产生内存泄漏。

示例清单2.4

#include "stdio.h"

#include "string.h"

class Ca

{

public:

Ca(){m_Style=0;}

    Ca(int style){m_Style=style;}

         ~Ca(){}

         virtual void OutputValue()

         {

          printf("%d\n",m_Style);

         }

private:

         int m_Style;

};

class Cb:public Ca

{

public:

         Cb(){ m_pTitle=NULL;}

         Cb(const char* Title);

         ~Cb()

         {

         if(NULL!=m_pTitle)

                          delete[] m_pTitle;

         }

    virtual void OutputValue()

         {  

                  if(NULL!=m_pTitle)

                  printf("%s\n",m_pTitle);

         }

private:

  char *m_pTitle;

};

Cb::Cb(const char* Title)

{

         if(NULL==Title)

                  m_pTitle=NULL;

         else

         { m_pTitle=new char[strlen(Title)+1];

           strcpy(m_pTitle,Title);

         }

}

int main(int argc, char* argv[])

{  //在堆栈中构造派生类对象,由基类指针引用

         Ca* pa=new Cb("bill_server");

         pa->OutputValue();

/*删除堆栈中的对象,释放内存。但派生类的析构函数没有被调用,

其中的释放内存操作也就无法完成,即Cb::m_pTitle没有得到释放。*/

         delete pa;  //虽然可以改写为delete (Cb*)pa; 但必须预先知道转换的类型

return 0;

}

要解决这一问题很简单,只需在基类的析构函数前加virtual关键字即可,不必惊动派生类的代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值