搜档网
当前位置:搜档网 › MFC中对各种函数尤其是虚函数的应用原理

MFC中对各种函数尤其是虚函数的应用原理

MFC中对各种函数尤其是虚函数的应用原理
MFC中对各种函数尤其是虚函数的应用原理

MFC对象的创建

前面几章介绍了MFC的核心概念和思

想,即介绍了MFC对Windows对象的

封装方法和特点;MFC对象的动态创

建、序列化;MFC消息映射机制。

现在,考查MFC的应用程序结构体系,

即以文档-视为核心的编程模式。学

习本章,应该弄清楚以下问题:

MFC中诸多MFC对象的关系:应用程序对象,文档对象,边框窗口对象,文档边框窗口对象,视对象,文档模板对象等。

MFC对象的创建和销毁:由什么对象创建或销毁什么对象,何时创建,何时销毁?MFC提供了那些接口来支持其编程模式?

1.MFC对象的关系

1.创建关系

这里讨论应用程序、文档模板、边框窗口、视、文档等的创建关系。

图5-1大略地表示了创建顺序,但表5-1更直接地显示了创建与被

创建的关系。

表5-1 MFC对象的创建关系

2.交互作用关系

应用程序对象有一个文档模板列表,存放一个或多个文档模板对象;文档

模板对象有一个打开文档列表,存放一个或多个已经打开的文档对象;文

档对象有一个视列表,存放显示该文档

数据的一个或多个视对象;还有一个指

针指向创建该文档的文档模板对象;视

有一个指向其关联文档的指针,视是一

个子窗口,其父窗口是边框窗口(或者

文档边框窗口);文档边框窗口有一个

指向其当前活动视的指针;文档边框窗

口是边框窗口的子窗口。

Windows 管理所有已经打开的窗口,把

消息或事件发送给目标窗口。通常,命令消息发送给主边框窗口。

图5-2大略地表示了上述关系:

MFC 提供了一些函数来维护这些关系。

表5-2列出了从一个对象得到相关对象的方法。

表5-2 从一个对象得到另一个对象的方法

表5-3 从一个对象通知另一个对象的方法:

可以通过表5-2得到相关对象,再调用表5-3中相应的函数。例如:视在接受了新数据或者数据被修改之后,使用表5-2中的函数GetDocument 得到关联文档对象,然后调用表5-3中的文档函数UpdateAllViews更新其他和文档对象关联的视。

在表5-2和表5-3中,CView对象指CView或派生类的实例;成员函数列中如果没有指定类属,就是第一列对象的类的成员函数。

2.MFC提供的接口

MFC编程就是把一些应用程序特有的东西填入MFC框架。MFC提供了两种填入的方法:一种就是使用前一章论述的消息映射,消息映射给应用程序的各种对象处理各种消息的机会;另一种就是使用虚拟函数,MFC在实现许多功能或者处理消息、事件的过程中,调用了虚拟函数来完成一些任务,这样就给了派生类覆盖这些虚拟函数实现特定处理的机会。

下面两节将列出两类接口,有两个目的:一是为了让读者获得整体印象,二是后文将涉及到或者讨论其中的许多函数时,不显得突兀。

1.虚拟函数接口

几乎每一个MFC类都定义和使用了虚拟成员函数,程序员可以在派

生类中覆盖它们。一般,MFC提供了这些函数的缺省实现,所以覆

盖函数应该调用基类的实现。这里给出一个MFC常用虚拟函数的总

览表(见表5-4),更详细的信息或它们的缺省实现动作参见MFC文

档。由于基类的虚拟函数被派生类继承,所以在派生类中不作重复

说明。

覆盖基类的虚拟函数可以通过ClassWizard进行,不过,并非所有

的函数都可以这样,有的必须手工加入函数声明和实现。

表5-4 常见MFC类的虚拟函数接口

2.消息映射方法和标准命令消息

窗口对象可以响应以“WM_”为前缀的标准Windows消息,消息处理函数名称以“ON”为前缀。不同类型的Windows窗口处理的Windows消息是有所不同的,因此,不同类型的MFC窗口实现的消息处理函数也有所不同。例如,多文档边框窗口能处理WM_MDIACTIVATE消息,其他类型窗口就不能。程序员从一定的MFC窗口派生自己的窗口类,对感兴趣的消息,覆盖基类的消息处理函数,实现自己的消息处理函数。

所有的命令目标(CCmdTarger或导出类对象)可以响应命令消息,程序员可以指定应用程序对象、框架窗口对象、视对象或文档对象等来处理某条命令消息。一般地,尽量由与命令消息关系密切的对象来处理,例如隐藏/显示工具栏由框架窗口处理,打开文件由应用程序对象处理,数据变化的操作由文档对象处理。

对话框的控制子窗口可以响应各类通知消息。

对于命令消息,MFC实现了一系列标准命令消息处理函数。标准命令ID 在afxres.h中定义。表5-5列出了MFC标准命令的实现,从ID或者函数

名可以大致地看出该函数的目的、功用,具体的实现有的后续章节会讲解,详细参见MFC技术文档。

程序员可以自己来处理这些标准消息,也可以通过不同的类或从不同的类导出自己的类来处理这些消息,不过最好遵循MFC的缺省实现。比如处理ID_FILE_NEW命令,最好由CWinApp的派生类处理。

表5-5 标准命令消息处理函数

3.MFC对象的创建过程

应用程序使用MFC的接口是把一些自己的特殊处理填入MFC框架,这些处理或者在应用程序启动和初始化的时候被调用,或者在程序启动之后和用户交互的过程中被调用,或者在程序退出和作清理工作的时候被调用。这三个阶段中,和用户交互阶段是各个程序自己的事情,自然都不一样,但是程序的启动和退出两个阶段是MFC框架所实现的,是MFC框架的一部分,各个程序都遵循同样的步骤和规则。显然,清楚MFC框架对这两个阶段的处理是很有必要的,它可以帮助深入理解MFC框架,更好地使用MFC框架,更有效地实现应用程序特定的处理。

MFC程序启动和初始化过程就是创建MFC对象和Windows对象、建立各种对象之间的关系、把窗口显示在屏幕上的过程,退出过程就是关闭窗口、

销毁所创建的Windows对象和MFC对象的过程。所以,下面要讨论几种常用MFC对象的结构,它们是构成一个文档-视模式应用程序的重要部件。

1.应用程序中典型对象的结构

本节将主要分析应用程序对象、文档对象、文档模板等的数据结构。

通过考察类的结构,特别是成员变量结构,弄清它的功能、目的以

及和其他类的关系;另外,在后续有关分析中必定会提到这些成员

变量,这里先作个说明,到时也不会显得突兀。

下面几节以表格的形式来描述各个类的成员变量。表格中,第一列

打钩的表示是MFC类库文档有说明的;没打钩的在文档中没有说

明,如果是public,则可以直接访问,但随着MFC版本的变化,

以后MFC可能不支持这些成员;第二列是访问属性;第三列是成员

变量名称;第四列是成员变量的数据类型;第五列是对成员变量的

功能、用途的简要描述。

1.应用程序类的成员变量

应用程序对象的数据成员表由两部分组成,第一部分是

CWinThread的成员变量,如表5-6所示,CWinApp继承了

CWinThread的数据成员。第二部分是CWinApp自己定义的

成员变量,如表5-7所示。

表5-6 CwinThread的成员变量

表5-7 CWinApp的成员变量

2.CDocument的成员变量

表5-8 文档对象的属性。

3.文档模板的属性

表5-9列出了文档模板的成员变量,5-10列出了单文档模板的成员变量,5-11列出了多文档模板的成员变量。单、多文档模板继承了文档模板的成员变量。

表5-9 文档模板的数据成员

表5-10 单文档模板的成员变量

表5-11 单文档模板的成员变量

2.WinMain入口函数

1.WinMain流程

现在讨论MFC应用程序如何启动。

WinMain函数是MFC提供的应用程序入口。进入WinMain前,

全局应用程序对象已经生成。WinMain流程如图5-3所示。

图中,灰色框是对被调用的虚拟函数的注释,程序员可以或

必须覆盖它以实现MFC要求的或用户希望的功能;大括号所

包含的图示是相应函数流程的细化,有应用程序对象App

的初始化、Run函数的实现、PumpMessage的流程,等等。

从图中可以看出:

(1)一些虚拟函数被调用的时机

对应用程序类(线程类)的InitIntance、ExitInstance、Run、ProcessMessageFilter、OnIdle、PreTranslateMessage 来说,InitInstance在应用程序初始化时调用,ExitInstance在程序退出时调用,Run在程序初始化之后调用导致程序进入消息循环,ProcessMessageFilter、OnIdle、

PreTranslateMessage在消息循环时被调用,分别用来过滤

消息、进行Idle处理、让窗口预处理消息。

(2)应用程序对象的角色

首先,应用程序对象的成员函数InitInstance被WinMain

调用。对程序员来说,它就是程序的入口点(真正的入口点

是WinMain,但MFC向程序员隐藏了WinMain的存在)。由

于MFC没有提供InitInstance的缺省实现,用户必须自己

实现它。稍后将讨论该函数的实现。

其次,通过应用程序对象的Run函数,程序进入消息循环。

实际上,消息循环的实现是通过CWinThread::Run来实现

的,图中所示的是CWinThread::Run的实现,因为CWinApp

没有覆盖Run的实现,程序员的应用程序类一般也不用覆盖

该函数。

(3)Run所实现的消息循环

它调用PumpMessage来实现消息循环,如果没消息,则进行

空闲(Idle)处理。如果是WM_QUIT消息,则调用

ExitInstance后退出消息循环。

(4)CWinThread::PumpMessage

该函数在MFC函数文档里没有描述,但是MFC建议用户使用。

它实现获取消息,转换(Translate)消息,发送消息的消息

循环。在转换消息之前,调用虚拟函数

PreTranslateMessage对消息进行预处理,该函数得到消息

目的窗口对象之后,使用CWnd的WalkPreTranslateTree

让目的窗口及其所有父窗口得到一个预处理当前消息的机

会。关于消息预处理,见消息映射的有关章节。如果是

WM_QUIT消息,PumpMessage返回FALSE;否则返回TRUE。

2.MFC空闲处理

MFC实现了一个Idle处理机制,就是在没有消息可以处理时,进行Idle处理。Idle处理的一个应用是更新用户接口对象的状态。更新用户接口状态的内容见消息映射的章节。

1.空闲处理由函数OnIdle完成,其原型为BOOL OnIdle(int)。参数的含

义是当前空闲处理周期已经完成了多少次OnIdle调用,每个空闲处理周

期的第一次调用,该参数设为0,每调用一次加1;返回值表示当前空闲

处理周期是否继续调用OnIdle。

2.MFC的缺省实现里,CWinThread::OnIdle完成了工具栏等的状态更新。如

果覆盖OnIdle,需要调用基类的实现。

3.在处理完一个消息或进入消息循环时,如果消息队列中没有消息要处理,

则MFC开始一个新的空闲处理周期;

4.当OnIdle返回FASLE,或者消息队列中有消息要处理时,当前的空闲处

理周期结束。

从图5-3中Run的流程上可以清楚的看到MFC空闲处理的情况。

本节描述了应用程序从InitInstance开始初始化、从Run进入消息循环的过程,下面将就SDI应用程序的例子描述该过程中创建各个所需MFC对象的流程。

1.SDI应用程序的对象创建

如前一节所述,程序从InitInstance开始。在SDI应用程

序的InitInstance里,至少有以下语句:

//第一部分,创建文档模板对象并把它添加到应用程序的模

板链表

CSingleDocTemplate* pDocTemplate;

pDocTemplate = new CSingleDocTemplate(

IDR_MAINFRAME,

RUNTIME_CLASS(CTDoc),

RUNTIME_CLASS(CMainFrame), // main SDI frame window

RUNTIME_CLASS(CTView));

AddDocTemplate(pDocTemplate);

//第二部分,动态创建文档、视、边框窗口等MFC对象和对

应的Windows对象

//Parse command line for standard shell commands, DDE,

file open

CCommandLineInfo cmdInfo;

ParseCommandLine(cmdInfo);

// Dispatch commands specified on the command line

C++虚函数与纯虚函数用法与区别

C++虚函数与纯虚函数用法与区别 1.C++虚函数与纯虚函数用法与区别,.虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class)。 2.虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class) 只有声明而没有定义。 3.虚函数和纯虚函数都可以在子类(sub class)中被重载,以多态的形式被调用。 4.虚函数和纯虚函数通常存在于抽象基类(abstract base class -ABC)之中,被继承的子类重载,目的是提供一个统一的接口。 5.虚函数的定义形式:virtual {method body} 纯虚函数的定义形式:virtual { } = 0; 在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期bind,然而虚函数却是动态绑定(run-time bind),而且被两者修饰的函数生命周期(life recycle)也不一样。 6. 如果一个类中含有纯虚函数,那么任何试图对该类进行实例化的语句都将导致错误的产生,因为抽象基类(ABC)是不能被直接调用的。必须被子类继承重载以后,根据要求调用其子类的方法。 //father class class Virtualbase { public: virtual void Demon()= 0; //prue virtual function virtual void Base() {cout<<"this is farther class"<}; }

虚函数和纯虚函数

虚函数和纯虚函数 在面向对象的C++语言中,虚函数(virtual function)是一个非常重要的概念。因为它充分体现了面向对象思想中的继承和多态性这两大特性,在C++语言里应用极广。比如在微软的MFC类库中,你会发现很多函数都有virtual关键字,也就是说,它们都是虚函数。难怪有人甚至称虚函数是C++语言的精髓。 那么,什么是虚函数呢,我们先来看看微软的解释: 虚函数是指一个类中你希望重载的成员函数,当你用一个基类指针或引用指向一个继承类对象的时候,你调用一个虚函数,实际调用的是继承类的版本。 ——摘自MSDN 这个定义说得不是很明白。MSDN中还给出了一个例子,但是它的例子也并不能很好的说明问题。我们自己编写这样一个例子: #include "stdio.h" #include "conio.h" class Parent { public: char data[20]; void Function1(); virtual void Function2(); // 这里声明Function2是虚函数 }parent; void Parent::Function1() { printf("This is parent,function1\n"); } void Parent::Function2() { printf("This is parent,function2\n"); } class Child:public Parent { void Function1(); void Function2(); } child; void Child::Function1() { printf("This is child,function1\n");

C++中虚析构函数的作用

C++中虚析构函数的作用 我们知道,用C++开发的时候,用来做基类的类的析构函数一般都是虚函数。可是,为什么要这样做呢?下面用一个小例子来说明: 有下面的两个类: class ClxBase { public: ClxBase() {}; virtual ~ClxBase() {cout<<”aaa”<DoSomething(); delete pTest; 的输出结果是: Do something in class ClxDerived! Output from the destructor of class ClxDerived! aaa 这个很简单,非常好理解。 但是,如果把类ClxBase析构函数前的virtual去掉,那输出结果就是下面的样子了: Do something in class ClxDerived! aaa 也就是说,类ClxDerived的析构函数根本没有被调用!(注:肯定不会被调用,因为动态联

sizeof计算含有虚函数的类的空间大小

sizeof计算含有虚函数的类的空间大小 当我们计算一种数据类型所占用的空间大小时,很easy,sizeof就可以解决掉。如果我们计算一个类,一个空类,或者一个含有虚函数然后又派生子类时,这时候他们所占用的内存空间是如何变化的呢?下面我们就通过代码来介绍下。 一个不含有虚函数的普通类与其派生类的内存关系 class Base { public: Base(int x):a(x) {} void print() { cout<

2.对于Derived类 Derived类继承于Base类,自然的继承了其成员变量a,自身又扩展了自己的成员变量b,因而多了4个字节。所以Derived类所占用的内存空间大小应该为8字节。 一个含有虚函数的类与其派生类的内存空间占用关系 class A { public: A(int x):a(x){} virtual void print(){cout<

纯虚函数

#include using namespace std; const double PI=3.1415926; class Shape { public: virtual double Area()=0; }; class Triangle:public Shape { private: double d,h; public: Triangle(double di,double gao) { d=di; h=gao; } double Area() { cout<<"三角形面积为:"; return d*h*1/2; } }; class Circle:public Shape { private: double r; public: Circle(double radius) { r=radius; } double Area() { cout<<"圆面积为:"; return PI*r*r; } }; class Ractangle:public Shape

{ private: double a,b; public: Ractangle(double chang,double kuang) { a=chang; b=kuang; } double Area() { cout<<"矩形面积为:"; return a*b; } }; void main() { Shape *p; double a,b; cout<<"请输入三角形底边和高:"; cin>>a>>b; Triangle t(a,b); p=&t; cout<Area()<>a; Circle c(a); p=&c; cout<Area()<>a>>b; Ractangle r(a,b); p=&r; cout<Area()<

实验8 多态性与虚函数

实验八多态性与虚函数 一、实验目的和要求 1.了解多态的概念; 2.了解虚函数的作用及使用方法; 3.了解静态关联和动态关联的概念和用法; 4.了解纯虚函数和抽象类的概念和用法 二、实验内容和结果 1.阅读下面的程序 1.1请写出程序的执行结果,并在上机时对照理解 class Vehicle {public: void run() const { cout << "run a vehicle. "<

airplane.run(); cout<<"(b) 用指向基类的指针访问成员函数: "<run(); vp=&airplane; vp‐>run(); } 1.2 如果将Vehicle 类的定义修改为虚函数,其余不变,请写出程序的执行结果,并在上机时对照理解 class Vehicle {public: virtual void run() const { cout << "run a vehicle. "<

c++抽象类和纯虚函数

纯虚函数和抽象类: 含有纯虚函数的类是抽象类,不能生成对象,只能派生。他派生的类的纯虚函数没有被改写,那么,它的派生类还是个抽象类。 定义纯虚函数就是为了让基类不可实例化化,因为实例化这样的抽象数据结构本身并没有意义.或者给出实现也没有意义 一. 纯虚函数 在许多情况下,在基类中不能给出有意义的虚函数定义,这时可以把它说明成纯虚函数,把它的定义留给派生类来做。定义纯虚函数的一般形式为: class 类名{ virtual 返回值类型函数名(参数表)= 0; // 后面的"= 0"是必须的,否则,就成虚函数了}; 纯虚函数是一个在基类中说明的虚函数,它在基类中没有定义,要求任何派生类都定义自己的版本。纯虚函数为各派生类提供一个公共界面。 从基类继承来的纯虚函数,在派生类中仍是虚函数。 二. 抽象类 1. 如果一个类中至少有一个纯虚函数,那么这个类被称为抽象类(abstract class)。 抽象类中不仅包括纯虚函数,也可包括虚函数。抽象类中的纯虚函数可能是在抽象类中定义的,也可能是从它的抽象基类中继承下来且重定义的。 2. 抽象类特点,即抽象类必须用作派生其他类的基类,而不能用于直接创建对象实例。 一个抽象类不可以用来创建对象,只能用来为派生类提供一个接口规范,派生类中必须重载基类中的纯虚函数,否则它仍将被看作一个抽象类。 3. 在effective c++上中提到,纯虚函数可以被实现(定义),但是,不能创建对象实例,这也体现了抽象类的概念。 三. 虚析构函数 虚析构函数: 在析构函数前面加上关键字virtual进行说明,称该析构函数为虚析构函数。虽然构造函数不能被声明为虚函数,但析构函数可以被声明为虚函数。 一般来说,如果一个类中定义了虚函数,析构函数也应该定义为虚析构函数。 例如: class B { virtual ~B(); //虚析构函数 … };

C++试题及答案 (五)

C++程序设计模拟试卷(五) 一、单项选择题(本大题共20小题,每小题1分,共20分)在每小题列出的四个备选项中 只有一个是符合题目要求的,请将其代码填写在题后的括号内。错选、多选或未选均无 分。 1. 静态成员函数没有() A. 返回值 B. this指针 C. 指针参数 D. 返回类型 答案:B 解析:静态成员函数是普通的函数前加入static,它具有函数的所有的特征:返回类型、 形参,所以使用静态成员函数,指针可以作为形参,也具有返回值。静态成员是类具有的 属性,不是对象的特征,而this表示的是隐藏的对象的指针,因此静态成员函数没有this 指针。静态成员函数当在类外定义时,要注意不能使用static关键字作为前缀。由于静态成员函数在类中只有一个拷贝(副本),因此它访问对象的成员时要受到一些限制:静态成员函数可以直接访问类中说明的静态成员,但不能直接访问类中说明的非静态成员;若要访问非静态成员时,必须通过参数传递的方式得到相应的对象,再通过对象来访问。 2. 在类的定义中,用于为对象分配内存空间,对类的数据成员进行初始化并执行其他内部管 理操作的函数是() A. 友元函数 B. 虚函数 C. 构造函数 D. 析构函数 答案:C 解析:定义构造函数作用就是初始化对象,而析构函数释放对象空间。虚函数用于完成多 态性,友元增加访问方便性。 3. 所有在函数中定义的变量,都是() A. 全局变量 B. 局部变量 C. 静态变量 D. 寄存器变量 答案:B 解析:变量存储类可分为两类:全局变量和局部变量。 (1)全局变量:在函数外部定义的变量称为全局变量,其作用域为:从定义变量的位置开始 到源程序结束。全局变量增加了函数之间数据联系的渠道,全局变量作用域内的函数,均可使用、修改该全局变量的值,但是使用全局变量降低了程序的可理解性,软件工程学提倡尽量避免使用全局变量。 (2)局部变量:在函数内部定义的变量称为局部变量,其作用域为:从定义变量的位置开始 到函数结束。局部变量包含自动变量(auto)静态变量(static)以及函数参数。 auto变量意味着变量的存储空间的分配与释放是自动进行的。说明符auto可以省略。函数中 的局部变量存放在栈空间。在函数开始运行时,局部变量被分配内存单元,函数结束时,局部变量释放内存单元。因此,任两个函数中的局部变量可以同名,因其占有不同的内存单元而不影响使用。这有利于实现软件开发的模块化。 static变量是定义在函数体内的变量,存放在静态存储区,不用栈空间存储,其值并不随存 储空间的释放而消失。 4. 假定AB为一个类,则执行“AB a(2), b[3],*p[4];”语句时调用该类构造函数的次数 为() A. 3 B. 4 C. 5 D. 9 答案:B 解析: a(2)调用1次带参数的构造函数,b[3]调用3次无参数的构造函数,指针没有给它 分配空间,没有调用构造函数。所以共调用构造函数的次数为4。 5. 如果表达式++a中的“++”是作为成员函数重载的运算符,若采用运算符函数调用格式,则可表示为() A. a.operator++(1) B. operator++(a) C. operator++(a,1) D. a.operator++() 答案:D 解析:运算符的重载,前缀先让变量变化。调用++a,等价为a.operator++(),注意无参 的形式。后缀的话a++,等价于a.operator(0),带形参,形参名可省。 6. 已知f1和f2是同一类的两个成员函数,但f1不能直接调用f2,这说明() A. f1和f2都是静态函数 B. f1不是静态函数,f2是静态函数 C. f1是静态函数,f2不是静态函数

C++ 类的应用

C++考查题 关键词:友员函数成员函数抽象类纯虚函数 一.建立一个复数类imaginary,其私有数据成员x和y表示复数的实部和虚部,构造函数imaginary用于对复数的实部和虚部初始化,成员函数show用于显示复述对象,形式为“实部+虚部i”;友员函数add,sub,mul和div分别用于进行复数的加、减、乘和除法运算。在主函数中,实例化两个复数,并输入一个运算符,按运算符选择相应的友员函数进行复数运算,然后调用成员函数show输出计算结果。 编码实现上述要求并回答以下问题 (1)四个友员函数的形参和返回值分别是什么? (2)四个友员函数可以定义为相应的成员函数吗,写出原型 (3)比较友员函数与成员函数的用法 答:(1)四个友员函数的形参是f1,f2,函数返回值是f (2)四个友员函数可以定义为相应的成员函数,原型如下: 在Imaginary类内的函数声明: Imaginary operator+(Imaginary const&f2); Imaginary operator-(Imaginary const&f2); Imaginary operator*(Imaginary const&f2); Imaginary operator/(Imaginary const&f2); 在Imaginary类外的函数定义: Imaginary Imaginary::operator+(Imaginary const&f2) { Imaginary f; f.x=x+f2.x; f.y=y+f2.y; return f; } Imaginary Imaginary::operator-(Imaginary const&f2) { Imaginary f; f.x=x-f2.x; f.y=y-f2.y; return f; }

虚函数和纯虚函数的作用与区别

虚函数和纯虚函数的作用与区别 虚函数为了重载和多态的需要,在基类中是由定义的,即便定义是空,所以子类中可以重写也可以不写基类中的函数! 纯虚函数在基类中是没有定义的,必须在子类中加以实现,很像java中的接口函数! 虚函数 引入原因:为了方便使用多态特性,我们常常需要在基类中定义虚函数。 class Cman { public: virtual void Eat(){……}; void Move(); private: }; class CChild : public CMan { public: virtual void Eat(){……}; private: }; CMan m_man; CChild m_child; //这才是使用的精髓,如果不定义基类的指针去使用,没有太大的意义 CMan *p ; p = &m_man ; p->Eat(); //始终调用CMan的Eat成员函数,不会调用CChild 的 p = &m_child; p->Eat(); //如果子类实现(覆盖)了该方法,则始终调用CChild的Eat函数 //不会调用CMan 的Eat 方法;如果子类没有实现该函数,则调用CMan的Eat函数 p->Move(); //子类中没有该成员函数,所以调用的是基类中的 纯虚函数 引入原因: 1、同“虚函数”; 2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。 //纯虚函数就是基类只定义了函数体,没有实现过程定义方法如下 // virtual void Eat() = 0; 直接=0 不要在cpp中定义就可以了 //纯虚函数相当于接口,不能直接实例话,需要派生类来实现函数定义 //有的人可能在想,定义这些有什么用啊,我觉得很有用 //比如你想描述一些事物的属性给别人,而自己不想去实现,就可以定 //义为纯虚函数。说的再透彻一些。比如盖楼房,你是老板,你给建筑公司 //描述清楚你的楼房的特性,多少层,楼顶要有个花园什么的 //建筑公司就可以按照你的方法去实现了,如果你不说清楚这些,可能建筑

C++箴言:多态基类中将析构函数声明为虚拟

C++箴言:多态基类中将析构函数声明为虚拟 有很多方法可以跟踪时间的轨迹,所以有必要建立一个 TimeKeeper 基类,并为不同的计时方法建立派生类: class TimeKeeper { public: TimeKeeper(); ~TimeKeeper(); ... }; class AtomicClock: public TimeKeeper { ... }; class WaterClock: public TimeKeeper { ... }; class WristWatch: public TimeKeeper { ... }; 很多客户只是想简单地取得时间而不关心如何计算的细节,所以一个 factory 函数--返回一个指向新建派生类对象的基类指针的函数--被用来返回一个指向计时对象的指针:TimeKeeper* getTimeKeeper(); // returns a pointer to a dynamic- // ally allocated object of a class // derived from TimeKeeper 按照 factory 函数的惯例,getTimeKeeper 返回的对象是建立在堆上的,所以为了避免泄漏内存和其他资源,最重要的就是要让每一个返回的对象都可以被完全删除。 TimeKeeper *ptk = getTimeKeeper(); // get dynamically allocated object // from TimeKeeper hierarchy ... // use it delete ptk; // release it to avoid resource leak 现在我们精力集中于上面的代码中一个更基本的缺陷:即使客户做对了每一件事,也无法预知程序将如何运转。 问题在于 getTimeKeeper 返回一个指向派生类对象的指针(比如 AtomicClock),那个对象通过一个基类指针(也就是一个 TimeKeeper* 指针)被删除,而且这个基类

C++函数中那些不可以被声明为虚函数的函数

常见的不不能声明为虚函数的有:普通函数(非成员函数);静态成员函数;内联成员函数;构造函数;友元函数。 1、为什么C++不支持普通函数为虚函数? 普通函数(非成员函数)只能被overload,不能被override,声明为虚函数也没有什么意思,因此编译器会在编译时邦定函数。 2、为什么C++不支持构造函数为虚函数? 这个原因很简单,主要是从语义上考虑,所以不支持。因为构造函数本来就是为了明确初始化对象成员才产生的,然而virtual function主要是为了再不完全了解细节的情况下也能正确处理对象。另外,virtual函数是在不同类型的对象产生不同的动作,现在对象还没有产生,如何使用virtual函数来完成你想完成的动作。(这不就是典型的悖论) 3、为什么C++不支持内联成员函数为虚函数? 其实很简单,那内联函数就是为了在代码中直接展开,减少函数调用花费的代价,虚函数是为了在继承后对象能够准确的执行自己的动作,这是不可能统一的。(再说了,inline函数在编译时被展开,虚函数在运行时才能动态的邦定函数) 4、为什么C++不支持静态成员函数为虚函数? 这也很简单,静态成员函数对于每个类来说只有一份代码,所有的对象都共享这一份代码,他也没有要动态邦定的必要性。 5、为什么C++不支持友元函数为虚函数? 因为C++不支持友元函数的继承,对于没有继承特性的函数没有虚函数的说法。 ********************************************************************* 1、顶层函数:多态的运行期行为体现在虚函数上,虚函数通过继承方式来体现出多态作用,顶层函数不属于成员函数,是不能被继承的。 2、构造函数:(1)构造函数不能被继承,因而不能声明为virtual函数。 (2)构造函数一般是用来初始化对象,只有在一个对象生成之后,才能发挥多态的作用,如果将构造函数声明为virtual函数,则表现为在对象还没有生成的情况下就使用了多态机制,因而是行不通的,如下例:

多态性和虚函数

多态性和虚函数

Problem A: C++习题抽象基类Description 编写一个程序,声明抽象基类Shape,由它派生出3个派生类:Circle(圆形)、Rectangle(矩形)、Triangle(三角形),用一个函数printArea分别输出以上三者的面积(结果保留两位小数),3个图形的数据在定义对象时给定。 Input 圆的半径 矩形的边长 三角形的底与高 Output 圆的面积 矩形的面积 三角形的面积 Sample Input 12.6 4.5 8.4 4.5 8.4 Sample Output area of circle = 498.76 area of rectangle = 37.80 area of triangle = 18.90 #include #include using namespace std; class Shape { public:

virtual double area()const=0; }; class Circle:public Shape { public: Circle(double r):radius(r) {} virtual double area() const { return 3.14159*radius*radius; }; protected: double radius; }; class Rectangle:public Shape { public: Rectangle(double w,double h):width(w),height(h) {} virtual double area() const { return width*height; }

c++多态性与虚函数习题答案

多态性与虚函数 1.概念填空题 1.1 C++支持两种多态性,分别是编译时和运行时。 1.2在编译时就确定的函数调用称为静态联编,它通过使用函数重载,模板等实现。 1.3在运行时才确定的函数调用称为动态联编,它通过虚函数来实现。 1.4虚函数的声明方法是在函数原型前加上关键字virtual。在基类中含有虚函数,在派生类中的函数没有显式写出virtual关键字,系统依据以下规则判断派生类的这个函数是否是虚函数:该函数是否和基类的虚函数同名;是否与基类的虚函数参数个数相同、类型;是否与基类的虚函数相同返回类型。如果满足上述3个条件,派生类的函数就是虚函数。并且该函数覆盖基类的虚函数。 1.5 纯虚函数是一种特别的虚函数,它没有函数的函数体部分,也没有为函数的功能提供实现的代码,它的实现版本必须由派生类给出,因此纯虚函数不能是友元函数。拥有纯虚函数的类就是抽象类类,这种类不能实例化。如果纯虚函数没有被重载,则派生类将继承此纯虚函数,即该派生类也是抽象。 3.选择题 3.1在C++中,要实现动态联编,必须使用(D)调用虚函数。 A.类名 B.派生类指针 C.对象名 D.基类指针 3.2下列函数中,不能说明为虚函数的是(C)。 A.私有成员函数 B.公有成员函数 C.构造函数 D.析构函数 3.3在派生类中,重载一个虚函数时,要求函数名、参数的个数、参数的类型、参数的顺序和函数的返回值(A)。 A.相同 B.不同 C.相容 D.部分相同 3.4当一个类的某个函数被说明为virtual时,该函数在该类的所有派生类中(A)。 A.都是虚函数 B.只有被重新说明时才是虚函数 C.只有被重新说明为virtual时才是虚函数 D.都不是虚函数 3.5(C)是一个在基类中说明的虚函数,它在该基类中没有定义,但要求任何派生类都必须定义自己的版本。 A.虚析构函数B.虚构造函数 C.纯虚函数D.静态成员函数 3.6 以下基类中的成员函数,哪个表示纯虚函数(C)。 A.virtual void vf(int);B.void vf(int)=0; C.virtual void vf( )=0;D.virtual void vf(int){ } 3.7下列描述中,(D)是抽象类的特性。 A.可以说明虚函数 B.可以进行构造函数重载 C.可以定义友元函数 D.不能定义其对象 3.8类B是类A的公有派生类,类A和类B中都定义了虚函数func( ),p是一个指向类A对象的指针,则p->A::func( )将(A)。

C++复习题

一、单项选择题(本大题共20小题,每小题1分,共20分) 1. 静态成员函数没有() A. 返回值 B. this指针 C. 指针参数 D. 返回类型 2. 在类的定义中,用于为对象分配内存空间,对类的数据成员进行初始化并执行其他内部管理操作的函数是() A. 友元函数 B. 虚函数 C. 构造函数 D. 析构函数 3. 所有在函数中定义的变量,都是() A. 全局变量 B. 局部变量 C. 静态变量 D. 寄存器变量 4. 假定AB为一个类,则执行“AB a(2), b[3],*p[4];”语句时调用该类构造函数的次数为() A. 3 B. 4 C. 5 D. 9 5. 如果表达式++a中的“++”是作为成员函数重载的运算符,若采用运算符函数调用格式,则可表示为() A. a.operator++(1) B. operator++(a) C. operator++(a,1) D. a.operator++() 6. 已知f1和f2是同一类的两个成员函数,但f1不能直接调用f2,这说明()

A. f1和f2都是静态函数 B. f1不是静态函数,f2是静态函数 C. f1是静态函数,f2不是静态函数 D. f1和f2都不是静态函数 7. 一个函数功能不太复杂,但要求被频繁调用,则应把它定义为() A. 内联函数 B. 重载函数 C. 递归函数 D. 嵌套函数 8. 解决定义二义性问题的方法有() A. 只能使用作用域分辨运算符 B. 使用作用域分辨运算符或成员名限定 C. 使用作用域分辨运算符或虚基类 D. 使用成员名限定或赋值兼容规则 9. 在main函数中可以用p.a的形式访问派生类对象p的基类成员a,偶中a是() A. 私有继承的公有成员 B. 公有继承的私有成员 C. 公有继承皀保护成员 D. 公有廧承的公有成员 10. 在C++中不返回任何????数应该说明为() A. int B. char C. void D. double 11. 若Sample类中的一个成员函数说明如下: void set(Sample& a),则Sample& a的含义是() A. 指向类Sample的名为a的指针

c++多态性与虚函数习题

作业题 一、写出下列程序运行结果 1.#include using namespace std; class A { public: virtual void func( ) {cout<<”func in class A”< using namespace std; class A{ public: virtual ~A( ){ cout<<”A::~A( ) called “<

}; void fun(A *a) { delete a; } int main( ) { A *a=new B(10); fun(a); } 二、程序设计题 1有一个交通工具类vehicle,将它作为基类派生小车类car、卡车类truck和轮船类boat,定义这些类并定义一个虚函数用来显示各类信息。 5.2定义一个shape抽象类,派生出Rectangle类和Circle类,计算各派生类对象的面积Area( )。 5.5某学校对教师每月工资的计算公式如下:固定工资+课时补贴。教授的固定工资为5000元,每个课时补贴50元;副教授的固定工资为3000元,每个课时补贴30元;讲师的固定工资为2000元,每个课时补贴20元。给出教师抽象类及主函数,补充编写程序求若干教师的月工资。 #include using namespace std; class Teacher{ protected: double salary; int workhours; public: Teacher(int wh=0){workhours=wh;} virtual void cal_salary()=0; void print(){cout<cal_salary(); prof.print(); Vice_Prof vice_prof(250); pt=&vice_prof; pt->cal_salary(); vice_prof.print(); Lecture lecture(100); pt=&lecture; pt->cal_salary(); lecture.print (); return 0; }

多继承_虚函数表解析

C++ 虚函数表解析
前言 C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针 指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针 有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可 变的算法。比如:模板技术,RTTI 技术,虚函数技术,要么是试图做到在编译时决议,要么试图 做到运行时决议。 关于虚函数的使用方法,我在这里不做过多的阐述。大家可以看看相关的 C++的书籍。在这篇 文章中,我只想从虚函数的实现机制上面为大家 一个清晰的剖析。 当然,相同的文章在网上也出现过一些了,但我总感觉这些文章不是很容易阅读,大段大段的 代码,没有图片,没有详细的说明,没有比较,没有举一反三。不利于学习和阅读,所以这是我 想写下这篇文章的原因。也希望大家多给我提意见。 言归正传,让我们一起进入虚函数的世界。 虚函数表 对 C++ 了解的人都应该知道虚函数 (Virtual Function) 是通过一张虚函数表 (Virtual Table) 来实现的。简称为 V-Table。在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、 覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了 这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由 为重要了,它就像一个地图一样,指明了实际所应该调用的函数。 这里我们着重看一下这张虚函数表。C++的编译器应该是保证虚函数表的指针存在于对象实例 中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承 的情况下) 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指 。 针,并调用相应的函数。 听我扯了那么多,我可以感觉出来你现在可能比以前更加晕头转向了。 没关系,下面就是实 际的例子,相信聪明的你一看就明白了。 假设我们有这样的一个类: 以下是引用片段: class Base { public: virtual void f() { cout << "Base::f" << endl; } virtual void g() { cout << "Base::g" << endl; } virtual void h() { cout << "Base::h" << endl; } };
按照上面的说法,我们可以通过 Base 的实例来得到虚函数表。 下面是实际例程: 以下是引用片段: typedef void(*Fun)(void);

虚函数的作用

条款14: 确定基类有虚析构函数 有时,一个类想跟踪它有多少个对象存在。一个简单的方法是创建一个静态类成员来统计对象的个数。这个成员被初始化为0,在构造函数里加1,析构函数里减1。(条款m26里说明了如何把这种方法封装起来以便很容易地添加到任何类中,“my article on counting objects”提供了对这个技术的另外一些改进) 设想在一个军事应用程序里,有一个表示敌人目标的类: class enemytarget { public: enemytarget() { ++numtargets; } enemytarget(const enemytarget&) { ++numtargets; } ~enemytarget() { --numtargets; } static size_t numberoftargets() { return numtargets; } virtual bool destroy(); // 摧毁enemytarget对象后 // 返回成功 private: static size_t numtargets; // 对象计数器 }; // 类的静态成员要在类外定义; // 缺省初始化为0 size_t enemytarget::numtargets; 这个类不会为你赢得一份政府防御合同,它离国防部的要求相差太远了,但它足以满足我们这儿说明问题的需要。 敌人的坦克是一种特殊的敌人目标,所以会很自然地想到将它抽象为一个以公有继承方式从enemytarget派生出来的类(参见条款35及m33)。因为不但要关心敌人目标的总数,也要关心敌人坦克的总数,所以和基类一样,在派生类里也采用了上面提到的同样的技巧: class enemytank: public enemytarget { public: enemytank() { ++numtanks; } enemytank(const enemytank& rhs) : enemytarget(rhs) { ++numtanks; }

C++复习题

C++作业题(8) 一.选择填空 (1) 定义重载函数的下列条件中,(C )是错误的。 A. 要求参数个数不同 B. 要求参数类型不同 C. 要求函数返回值类型不同 D. 要求在参数个数相同时,参数类型的顺序不同 (2) 关于下列虚函数的描述中,( C)是正确的。 A. 虚函数是一个static存储类的成员函数 B. 虚函数是一个非成员函数 C. 基类中说明了虚函数后,派生类中可不必将对应的函数说明为虚函数 D. 派生类的虚函数与基类的虚函数应具有不同的类型或个数 (3) 关于纯虚函数和抽象类的描述中,(C )是错误的。 A. 纯虚数是一种特殊的虚函数,它没有具体实现 B. 抽象类中一定具有一个或多个纯虚函数 C. 抽象类的派生类中一定不会再有纯虚函数 D. 抽象类一般作为基类使用,使纯虚函数的实现由其派生类给出 (4) 以下一种类中,( A)不能建立对象。 A. 抽象类 B. 派生类 C. 虚基类 D. 基类 (5)下列函数中不能重载的是( C )。 A)成员函数 B)非成员函数 C)析构函数 D)构造函数 (6)下列描述中,抽象类的特征有( D )。 A)可以说明虚函数 B)可以构造函数重载 C)可以定义友员函数 D)不能说明其对象(7)下列不属于动态联编实现的条件有( D )。 A)要有说明的虚函数。 B)调用虚函数的操作是指向对象的指针或者对象引用:或者是由成员函数调用虚函数。C)子类型关系的确立。 D)在构造函数中调用虚函数。 (8)派生类中对基类的虚函数进行替换时,派生类中说明的虚函数与基类中的被替换的虚

函数之间不要求满足的是( C )。 A)与基类的虚函数具有相同的参数个数。 B)其参数的类型与基类的虚函数的对应参数类型相同。 C)基类必须定义纯虚函数。 D)其返回值或者与基类的虚函数相同,或者都返回指针或引用,并且派生类虚函数所返回的指针或引用的基类型是基类中被替换的虚函数所返回的指针或引用的基类的子类型。(9)下列关于抽象类说法正确的是:( B ) A)抽象类处于继承类层次结构的较下层。 B)抽象类刻画了一组子类的操作通用接口。C)抽象类可以作为类直接使用。 D)抽象类可以直接定义对象。 (10)下列关于虚析构函数说法不正确的是( B )。 A)在析构函数前加上关键字virtual,就说明了虚析构函数。 B)如果一个基类的析构函数说明为虚析构函数,则它的派生类中的析构函数须用virtual 关键字说明后才是虚析构函数。 C)说明虚析构函数的目的在于使用delete删除一个对象时,能保证析构函数被正确地执行。D)设置虚函数后,可以采用动态联编的方式选择析构函数。 (11)编译时多态性通过使用( B )获得。 A)继承 B)虚函数 C)重载函数 D)析构函数 (12)可以使用( A )来阻止基类的成员函数调用派生类中的虚函数。 A)成员名限定 B)指针 C)引用 D)关键字virtual (13)抽象类应该含有( D )。 A)至多一个虚函数 B)至多一个虚函数是纯虚函数 C)至少一个虚函数 D)至少一个虚函数是纯虚函数 (14)一个抽象类可以说明为( A )。 A)指向抽象类对象的指针 B)类成员数据 C)抽象类的对象 D)数组元素(15)对于抽象类的使用需要注意的地方,下列不正确的说法是:( C ) A)抽象类只能用作其它类的基类,不能建立抽象类对象。 B)抽象类不能用作参数类型,函数返回类型或显式转换的类型。

相关主题