文档库 最新最全的文档下载
当前位置:文档库 › 虚函数与虚析构函数

虚函数与虚析构函数

虚函数与虚析构函数
虚函数与虚析构函数

一.简介

虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。假设我们有下面的类层次:

class A

{

public:

virtual void foo() { cout << "A::foo() is called" << endl;}

};

class B: public A

{

public:

virtual void foo() { cout << "B::foo() is called" << endl;}

};

那么,在使用的时候,我们可以:

A * a = new B();

a->foo(); // 在这里,a虽然是指向A的指针,但是被调用的函数(foo)却是B 的!

这个例子是虚函数的一个典型应用,通过这个例子,也许你就对虚函数有了一些概念。它虚就虚在所谓“推迟联编”或者“动态联编”上,一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为“虚”函数。

虚函数只能借助于指针或者引用来达到多态的效果,如果是下面这样的代码,则虽然是虚函数,但它不是多态的:

class A

{

public:

virtual void foo();

};

class B: public A

{

virtual void foo();

};

void bar()

{

A a;

a.foo(); // A::foo()被调用

}

1.1 多态

在了解了虚函数的意思之后,再考虑什么是多态就很容易了。仍然针对上面的类层次,但是使用的方法变的复杂了一些:

void bar(A * a)

{

a->foo(); // 被调用的是A::foo() 还是B::foo()?

}

因为foo()是个虚函数,所以在bar这个函数中,只根据这段代码,无从确定这里被调用的是A::foo()还是B::foo(),但是可以肯定的说:如果a指向的是A类的实例,则A::foo()被调用,如果a指向的是B类的实例,则B::foo()被调用。

这种同一代码可以产生不同效果的特点,被称为“多态”。

1.2 多态有什么用?

多态这么神奇,但是能用来做什么呢?这个命题我难以用一两句话概括,一般的C++教程(或者其它面向对象语言的教程)都用一个画图的例子来展示多态的用途,我就不再重复这个例子了,如果你不知道这个例子,随便找本书应该都有介绍。我试图从一个抽象的角度描述一下,回头再结合那个画图的例子,也许你就更容易理解。

在面向对象的编程中,首先会针对数据进行抽象(确定基类)和继承(确定派生类),构成类层次。这个类层次的使用者在使用它们的时候,如果仍然在需要基类的时候写针对基类的代码,在需要派生类的时候写针对派生类的代码,就等于类层次完全暴露在使用者面前。如果这个类层次有任何的改变(增加了新类),都需要使用者“知道”(针对新类写代码)。这样就增加了类层次与其使用者之间的耦合,有人把这种情况列为程序中的“bad smell”之一。

多态可以使程序员脱离这种窘境。再回头看看1.1中的例子,bar()作为A-B这个类层次的使用者,它并不知道这个类层次中有多少个类,每个类都叫什么,但是一样可以很好的工作,当有一个C类从A类派生出来后,bar()也不需要“知道”(修改)。这完全归功于多态--编译器针对虚函数产生了可以在运行时刻确定被调用函数的代码。

1.3 如何“动态联编”

编译器是如何针对虚函数产生可以再运行时刻确定被调用函数的代码呢?也就是说,虚函数实际上是如何被编译器处理的呢?Lippman在深度探索C++对象模型[1]中的不同章节讲到了几种方式,这里把“标准的”方式简单介绍一下。

我所说的“标准”方式,也就是所谓的“VTABLE”机制。编译器发现一个类中有被声明为virtual的函数,就会为其搞一个虚函数表,也就是VTABLE。VTABLE实际上是一个函数指针的数组,每个虚函数占用这个数组的一个slot。一个类只有一个VTABLE,不管它有多少个实例。派生类有自己的VTABLE,但是派生类的VTABLE与基类的VTABLE有相同的函数排列顺序,同名的虚函数被放在两个数组的相同位置上。在创建类实例的时候,编译器还会在每个实例的内存布局中增加一个vptr字段,该字段指向本类的VTABLE。通过这些手段,编译器在看到一个虚函数调用的时候,就会将这个调用改写,针对1.1中的例子:

void bar(A * a)

{

a->foo();

}

会被改写为:

void bar(A * a)

{

(a->vptr[1])();

}

因为派生类和基类的foo()函数具有相同的VTABLE索引,而他们的vptr 又指向不同的VTABLE,因此通过这样的方法可以在运行时刻决定调用哪个foo()函数。

虽然实际情况远非这么简单,但是基本原理大致如此。

1.4 overload和override

虚函数总是在派生类中被改写,这种改写被称为“override”。我经常混淆“overload”和“override”这两个单词。但是随着各类C++的书越来越多,后来的程序员也许不会再犯我犯过的错误了。但是我打算澄清一下:

override是指派生类重写基类的虚函数,就象我们前面B类中重写了A 类中的foo()函数。重写的函数必须有一致的参数表和返回值(C++标准允许返回值不同的情况,这个我会在“语法”部分简单介绍,但是很少编译器支持这个feature)。这个单词好象一直没有什么合适的中文词汇来对应,有人译为“覆盖”,还贴切一些。

overload约定成俗的被翻译为“重载”。是指编写一个与已有函数同名但是参数表不同的函数。例如一个函数即可以接受整型数作为参数,也可以接受浮点数作为参数。

二. 虚函数的语法

虚函数的标志是“virtual”关键字。

2.1 使用virtual关键字

考虑下面的类层次:

class A

{

public:

virtual void foo();

};

class B: public A

{

public:

void foo(); // 没有virtual关键字!

};

class C: public B // 从B继承,不是从A继承!

{

public:

void foo(); // 也没有virtual关键字!

};

这种情况下,B::foo()是虚函数,C::foo()也同样是虚函数。因此,可以说,基类声明的虚函数,在派生类中也是虚函数,即使不再使用virtual关键字。

2.2 纯虚函数

如下声明表示一个函数为纯虚函数:

class A

{

public:

virtual void foo()=0; // =0标志一个虚函数为纯虚函数

};

一个函数声明为纯虚后,纯虚函数的意思是:我是一个抽象类!不要把我实例化!纯虚函数用来规范派生类的行为,实际上就是所谓的“接口”。它告诉使用者,我的派生类都会有这个函数。

2.3 虚析构函数

析构函数也可以是虚的,甚至是纯虚的。例如:

class A

{

public:

virtual ~A()=0; // 纯虚析构函数

};

当一个类打算被用作其它类的基类时,它的析构函数必须是虚的。考虑下面的例子:

class A

{

public:

A() { ptra_ = new char[10];}

~A() { delete[] ptra_;} // 非虚析构函数

private:

char * ptra_;

};

class B: public A

{

public:

B() { ptrb_ = new char[20];}

~B() { delete[] ptrb_;}

private:

char * ptrb_;

};

void foo()

{

A * a = new B;

delete a;

}

在这个例子中,程序也许不会象你想象的那样运行,在执行delete a的时候,实际上只有A::~A()被调用了,而B类的析构函数并没有被调用!这是否有点儿可怕?

如果将上面A::~A()改为virtual,就可以保证B::~B()也在delete a的时候被调用了。因此基类的析构函数都必须是virtual的。

纯虚的析构函数并没有什么作用,是虚的就够了。通常只有在希望将一个类变成抽象类(不能实例化的类),而这个类又没有合适的函数可以被纯虚化的时候,可以使用纯虚的析构函数来达到目的。

2.4 虚构造函数?

构造函数不能是虚的。

三. 虚函数使用技巧

3.1 private的虚函数

考虑下面的例子:

class A

{

public:

void foo() { bar();}

private:

virtual void bar() { ...}

};

class B: public A

{

private:

virtual void bar() { ...}

};

在这个例子中,虽然bar()在A类中是private的,但是仍然可以出现在派生类中,并仍然可以与public或者protected的虚函数一样产生多态的效果。并不会因为它是private的,就发生A::foo()不能访问B::bar()的情况,也不会发生B::bar()对A::bar()的override不起作用的情况。

这种写法的语意是:A告诉B,你最好override我的bar()函数,但是你不要管它如何使用,也不要自己调用这个函数。

3.2 构造函数和析构函数中的虚函数调用

一个类的虚函数在它自己的构造函数和析构函数中被调用的时候,它们就变成普通函数了,不“虚”了。也就是说不能在构造函数和析构函数中让自己“多态”。例如:

class A

{

public:

A() { foo();} // 在这里,无论如何都是A::foo()被调用!

~A() { foo();} // 同上

virtual void foo();

};

class B: public A

{

public:

virtual void foo();

};

void bar()

{

A * a = new B;

delete a;

}

如果你希望delete a的时候,会导致B::foo()被调用,那么你就错了。同样,在new B的时候,A的构造函数被调用,但是在A的构造函数中,被调用的是A::foo()而不是B::foo()。

3.3 多继承中的虚函数

3.4 什么时候使用虚函数

在你设计一个基类的时候,如果发现一个函数需要在派生类里有不同的表现,那么它就应该是虚的。从设计的角度讲,出现在基类中的虚函数是接口,出现在派生类中的虚函数是接口的具体实现。通过这样的方法,就可以将对象的行为抽象化。

以设计模式[2]中Factory Method模式为例,Creator的factoryMethod()就是虚函数,派生类override这个函数后,产生不同的Product类,被产生的Product类被基类的AnOperation()函数使用。基类的AnOperation()函数针对Product类进行操作,当然Product类一定也有多态(虚函数)。

另外一个例子就是集合操作,假设你有一个以A类为基类的类层次,又用了一个std::vector来保存这个类层次中不同类的实例指针,那么你一定希望在对这个集合中的类进行操作的时候,不要把每个指针再cast回到它原来的类型(派生类),而是希望对他们进行同样的操作。那么就应该将这个“一样的操作”声明为virtual。

现实中,远不只我举的这两个例子,但是大的原则都是我前面说到的“如果发现一个函数需要在派生类里有不同的表现,那么它就应该是虚的”。这句话也可以反过来说:“如果你发现基类提供了虚函数,那么你最好override它”。

附:C++中的虚函数和纯虚函数用法

1.虚函数和纯虚函数可以定义在同一个类(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)是不能被直接调用的。必须被子类继承重载以后,根据要求调用其子类的方法。

以下为一个简单的虚函数和纯虚寒数的使用演示,目的是抛砖引玉!

#include

using std::cout;

using std::endl;

//father class

class Virtualbase

{

public:

virtual void Demon()= 0; //prue virtual function

virtual void Base() {cout<<"this is farther class"<

};

//sub class

class SubVirtual :public Virtualbase

{

public:

void Demon() { cout<<"this is SubVirtual!"<

void Base() { cout<<"this is subclass "<

Virtualbase::Base();}

};

/* instance class and sample */

int main()

{

Virtualbase* inst = new SubVirtual();//multstate pointer

inst->Demon();

inst->Base();

// inst = new Virtualbase();

// inst->Base()

system("pause");

return 0 ;

}

虚函数和纯虚函数

虚函数和纯虚函数 在面向对象的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++虚函数与纯虚函数用法与区别 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"<}; }

纯虚函数

#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()<

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的析构函数根本没有被调用!(注:肯定不会被调用,因为动态联

c++抽象类和纯虚函数

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

实验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. "<

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

虚函数和纯虚函数的作用与区别 虚函数为了重载和多态的需要,在基类中是由定义的,即便定义是空,所以子类中可以重写也可以不写基类中的函数! 纯虚函数在基类中是没有定义的,必须在子类中加以实现,很像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++程序设计模拟试卷(五) 一、单项选择题(本大题共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不是静态函数

虚函数纯虚函数普通函数.docx

C++在继承中虚函数、纯虚函数、普通函数,三者的区别 1.虚函数(impure virtual) C++的帰函数主要作用是“运行时多态〃,父类屮提供虚函数的实现,为子类提供默认的函数实现。 子类可以重写父类的虚函数实现子类的特殊化。 如下就是一个父类中的虚函数: class A {public: virtual void out2(string s) { cout?"A(out2):"?s?endl; } 2.纯虚函数(pure virtual) C++中包含纯虚函数的类,被称为是"抽彖类〃。抽彖类不能使用newtU对彖,只有实现了这个纯虚函数的子类才能new出对象。 C++中的纯虚函数更像是〃只提供申明,没有实现〃,是对子类的约束,是“接口继承〃。 C++中的纯虚函数也是一种“运行时多态〃。 如下而的类包含纯虚函数,就是“抽彖类〃: class A {public: virtual void outl(string s)=0; virtual void out2(string s) { cout?"A(out2):"?s?endl; } }; 百 3.普通函数(no-virtual) 普通函数是静态编译的,没有运行时多态,只会根据指针或引用的“字面值〃类对象,调用自己的普通函数。 普通函数是父类为子类提供的“强制实现〃。 因此,在继承关系屮,子类不应该重写父类的普通函数,因为函数的调用至于类对彖的字面值有关。 4.程序综合实例 心 #inelude using namespace std; class A {public: virtual void outl()=0; ///由子类实现virtual ~A(){};

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* 指针)被删除,而且这个基类

多态性和虚函数

多态性和虚函数

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; }

抽象基类和纯虚函数

抽象基类和纯虚函数 抽象类和具体类 包含纯虚函数的类不能实例化对象,是抽象类 如果抽象类的派生类实现了所有积累中的纯虚函数,则不再是抽象类 抽象类存在的意义是作为其他类的基类,也较抽象基类 构造函数的执行顺序:从上至下 析构函数的执行顺序:从下至上 创建对象时要执行正确的构造函数 撤销对象时要执行正确的析构函数 问题:动态对象的创建和撤销 虚析构函数 动态对象的创建 动态创建的对象没有问题 New classname(···); 动态对象的撤销 Delete 基类指针; 如果基类指针指向的是派生类的对象呢? 析构函数可以声明为虚函数 Delete 基类指针; 程序会根据积累指针指向的对象的类型确定要调用的析构函数 如果基类的析构函数为虚函数,则所派生的类的析构函数都是虚函数 如果要操作具有继承关系的类的动态对象,最好使用虚析构函数 文件和流——支持大量数据的处理:输入,存储 对文件执行的操作只要求我们掌握对几个函数的操作就行 如果说你不懂对文件的操作和处理,你永远也无法选好编程,你的程序永远也写不好,操作系统能够把外设和文件统一管理。 文件可以保存程序的运行结果 文件使程序处理大量的数据成为可能 大型系统的运行需要文件支持 C++将文件看成有序的字节流 文件被打开后,操作系统为该文件的建立的一个缓冲区,或称为一个字节序列,即流 普通文件 二进制文件 文本文件 输入输出设备:键盘,显示器,打印机等 标准输入流(用指针stdin操作) 标准输出流(用指针stdout操作)

C++采用相同的方式操作普通文件和I/O设备 文件的操作 格式化输入输出(文本) 块输入输出(二进制) 文件操作过程 1.建立并打开文件 2.操作文件:读,写 3.关闭文件 打开文件或建立一个新文件 FILE *fopen(const char *filename,const char *mode); filename——路劲及文件名 mode——打开方式 关闭文件 Int fclose(FILE *stream); Stream——要关闭的文件 读写文件——格式化操作(文本文件) Int fscanf(File *stream,·······); Int fprintf(File *stream,·······); 读写文件——快读写方式(二进制文件) size_t fwrite(const void*buffer,size_t size,size_t count,File *stream); size_t fread(const void*buffer,size_t size,size_t count,File *stream);

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; }

2、定义一个Shape类含一个求面积的纯虚函数,由Shape类派生圆类、矩形类和三角形类求面积函数和求周长函数

2、定义一个基类Shap,包含一个求面积的纯虚函数,由Shap类派生圆类、矩形类和三角形类,定义各自的数据成员、构造函数、求面积函数和求周长函数。编写主函数,定义各类对象及基类指针,通过基类指针调用求面积和周长函数,计算各对象的面积和周长。 #include #include class Shape {public: virtual double mianji() const=0;}; class Yuan:public Shape {public: Yuan(double r):radius(r){} double mianji() const {cout<<"圆的面积为:"<<3.14159*radius*radius<

虚函数的作用

条款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)抽象类不能用作参数类型,函数返回类型或显式转换的类型。

实验6多态性与虚函数

[实验目的] 1、了解多态性的概念; 2、了解虚函数的用途及使用方法; 3、了解纯虚函数和抽象类的概念和用法。 [实验要求] 给出以下各实验内容的源程序代码,并把编译、运行过程中出现的问题以及解决方法填入实验报告中,按时上交。 [实验学时] 2学时。 [实验内容] 1、写一个程序,定义抽象基类Shape,由它派生出3个派生类:Circle(圆形)、Square(正方形)、Rectangle(矩形)。利用指针、虚函数printArea()分别输出以上三者的面积,3个图形的数据在定义对象时给定。 [源程序] #include using namespace std; class Shape { public: virtual float area()const=0; virtual void display()const=0; }; class Circle:public Shape { public: Circle(double a):r(a){} virtual float area()const{return 3.14*r*r;} virtual void display()const { cout<<"圆面积"<

class Rectangle:public Shape { public: Rectangle(double a,double b):l(a),w(b){} virtual float area()const{return l*w;} virtual void display()const { cout<<"矩形面积"<display(); m=m+p[i]->area(); }

相关文档