虚函数

一. 什么是虚函数

1. 虚函数是面向对象编程中函数的一种特定形态,是C++中用于实现多态的一种有效机制

2. 虚函数用virtual修饰函数名,虚函数的作用是在程序的运行阶段动态的选择合适的成员函数,在定义了虚函数之后,可以在基类的派生类中对虚函数进行重定义,在派生类中重定义的函数与基类虚函数具有相同的函数返回值、函数参数列表、函数名等。如果派生类中没有重定义基类的虚函数则它是直接继承基类的虚函数

3. 定义虚函数的一般格式

class <类名>{

public:

        virtual 函数返回值类型 函数名(参数列表);

private:

}

4. 虚函数实现多态例子

#include<queue>
#include<stack>
#include<iostream>
#include<algorithm>
using namespace std;

//类A
class A{
public:
	//类A的虚函数
	virtual void Print(void){
	     cout<<"class A"<<endl;
	}
private:
};

//类B继承类A
class B:public A{
public:
	//类B中重写A的虚函数
	virtual void Print(void){
	     cout<<"class B"<<endl;
	}
private:
};

int main(){
	//test多态
	A *a = new A();
	B *b = new B();
	a->Print(); //输出:class A
	a = b;  //让基类指针指向子类对象指针
	a->Print(); //输出: class B
	getchar();
	return 0;
}
/*
解析如下:
1. 类A中定义了一个虚函数Print,类B继承了类A之后重写了虚函数Print
2. 在main函数中,基类指针第一次调用Print函数是调用的是类A的Print
   当基类指针指向子类B的指针对象后,再调用Print则调用的是类B的Print
3. 通过基类指针指向不同子类对象实现动态的调用虚函数,就是多态的实现机制
*/

二. 使用虚函数应该注意哪些问题

1. 只需要在声明虚函数的类中使用关键字virtual,而在类外实现虚函数的时候是不用加上virtual的

2. 将基类的某一成员函数声明为虚函数之后,派生类中的同名函数自动成为虚函数

3. 如果声明了某个成员函数为虚函数,则在该类中不能出现这个成员函数同名并且返回值、参数列表相同的非虚函数,在该类的派生类中也是不能出现这种同名的非虚函数

4. 全局函数、类的静态成员函数、构造函数是不能声明为虚函数的

5. 析构函数可以定义为虚函数,将基类的析构函数定义为虚函数之后,当利用delete删除一个指向子类对象的基类指针之后,会先去析构子类对象再去析构基类对象。如果基类的析构函数没有声明为虚函数则只会调用基类的析构函数而无法调用子类的析构函数,可能会造成内存泄露

三. 虚函数的实现机制

1. 虚函数是通过一张虚函数表来实现的,在有虚函数的类的实例中都有一个指针专门用来指向这张虚函数表。所以当用基类的指针来操作一个子类对象的时候,这个虚函数表可以用来指明实际要调用的函数

2. C++的编译器保证了指向虚函数表的指针位于实例化对象的最前面位置,通过这个指针得到这张虚函数表,这样就可以遍历虚函数表中的函数指针,并调用相应的虚函数

3. 虚函数表的各表项为指向对应虚函数的指针,存储的就是函数指针

4. 如果一个类中含有虚函数,则编译器会为此类生成一个虚函数表,并且会为该类插入一指针指向这个虚函数表。当调用这个类的构造函数的时候,编译器会把指针指向对应的虚函数表。比如当基类指针赋给子类指针对象的时候则把基类指针对象的指针指向子类指针对象的虚函数,这样就可以实现调用子类的虚函数,从而实现多态

四. 虚函数表

(1)子类继承基类虚函数,但是没有覆盖

//基类
class A{
public:
      virtual void F(void){}
      virtual void H(void){}
};

//子类
class B:public A{
	  virtual void G(void){}
}; 

   实际的虚函数表示这样的,虚函数表前面是基类虚函数在是子类的虚函数

  (2)子类继承基类虚函数,并且覆盖了基类虚函数

//基类
class A{
public:
      virtual void F(void){}
      virtual void H(void){}
};

//子类
class B:public A{
	  virtual void F(void){}
	  virtual void G(void){}
}; 

     这个时候实际的虚函数表是这样的,子类覆盖了基类的虚函数在虚函数表中替换基类同名函数位置,然后是基类虚函数,然后是子类自定义虚函数,如下图所示。

     

    
   (3)子类同时继承多个基类

例如子类D同时继承了基类A、B、C

//基类
class A{
public:
      virtual void F(void);
};
class B{
public:
	  virtual void G(void);
};
class C{
public:
      virtual void H(void);
};

//子类
class D:public A,B,C{
public:
      virtual void K(void);
}; 

实际的虚函数表是这样的,这个时候D类对象有三个指针指向了这三个虚函数表,按照继承的类从上到下,每个虚函数表内部和单继承是一样的道理。

这个时候求sizeof(D) = 12,因为有三个指针指向三个虚函数表,每个指针4个字节。

时间: 2024-08-16 15:04:11

虚函数的相关文章

class-关于const虚函数问题,求解

问题描述 关于const虚函数问题,求解 class Base { public: virtual void print() const = 0; }; class Test : public Base { public: void print() {cout << "Test::print()" << endl;} void print() const { cout << "Test::print() const" <&l

有关基类指针调用类的虚函数时所遇到的问题

问题描述 有关基类指针调用类的虚函数时所遇到的问题 下列输出中其中一个第2处为什么无法输出相应字符串?并且当类中的输出字符串改为输出int 类型age时又为什么可以? 问题1:问题2: 解决方案 忽视了作用域

java和c++比较(1)--虚函数和指针

c++|比较|函数    1.java中有虚函数,但是没指针,那也许会有这样的问题,那java是怎么实现多态的? java中可以肯定是有虚函数的,而且我们如果不申明为final形他默认就是虚函数.不用vitual申明. "In Java, you do not need to declare a method as virtual. Dynamic binding is the default behavior. If you do not want a method to be virtual

C#中虚函数,抽象,接口的简单说明

函数 虚函数:由virtual声明,它允许在派生类中被重写,要重写方法,必须先声名为virtual public class myclass { public virtual int myint() { 函数体: } } class myclass1:myclass { public override int myint() { 函数体1: } } 抽象类.抽象函数:由abstract声明,在抽象类中可以定义抽象方法,抽象方法基本没有执行代码,派生类必须重写它,提供其执行代码 public ab

C++中的纯虚函数(pure virtual) 和抽象基类(abstract base class)

纯虚函数(pure virtual), 是一个基类中的方法, 仅仅是声明, 而不包括定义, 是一个泛化概念(general concept); 是把相应的虚函数, 末尾添加"=0",该虚函数就变为纯虚函数, 可以不用添加定义; 如果是其他虚函数, 即使不使用, 也必须定义(define); 包含纯虚函数的基类, 是抽象基类(abstract base class),不能定义(define)对象(object), 仅可以作为继承使用; 代码: /* * CppPrimer.cpp * *

C++中虚函数(virtual function) 的 使用范围

虚函数(virtual function) 在 基函数(base function) 和 派生函数(derived function) 之间可以覆写(override); 如果派生函数有与虚函数同名的函数, 则会隐藏虚函数, 但是虚函数还会在; 由于派生对象和基对象可以转换, 可以只使用基对象部分; 派生对象的虚函数会覆写基对象的虚函数; 代码: /* * cppprimer.cpp * * Created on: 2013.11.13 * Author: Caroline */ /*eclip

C++中如何在容器中使用继承和虚函数

容器不支持混合类型, 如果直接把派生类对象, 存入基类容器中, 则无法使用派生-基转换(derived-base conversion); 因为转换只能发生在指针和引用 过程中, 不能发生在 对象直接赋值, 如果是直接转换, 则会产生截断(sliced down); 即派生类部分被切除, 只留下基类部分; 所以存入容器中的派生类 输出为基类部分 的虚函数; 如果想在容器中, 进行继承, 则需要使用指针, 包括智能指针(如:shared_ptr<>), 则会输出派生类的覆写(override)版

C++:虚函数(virtual function)缺省参数的异常表现及正确使用

避免定义基类(base class)虚函数的缺省参数, 如果基类虚函数包含缺省参数, 派生类不要重新定义, 会产生异常; 虚函数(virtual)是动态绑定(dynamically bound),缺省参数是静态绑定(statically bound). 如果派生类(derived class)重新定义缺省值, 实例化, 执行函数正常; 但在动态绑定时, 会出现,执行基类的缺省参数, 却是派生类的行为, 这样的异常举动. talk is cheap, show you the code. 注意观察

C++:阻止派生类(derived class)重新定义虚函数(final&amp;amp;sealed)

阻止派生类重新定义虚函数, 在java和C#中可以很简单的实现(finale&sealed), 但在C++中则需要使用public继承+复合(composition)的方法; 注意: 使用私有继承(private), 无法实现此要求. 如: /************************************************* File: test.cpp Copyright: C.L.Wang Author: C.L.Wang Date: 2014-04-11 Descripti

C++:类的虚函数及虚继承所占的空间

char占用一个字节, 但不满足4的倍数时, 会进行数据对齐, 使用4的倍数的字节(byte). GCC: GCC中, 无论是虚函数还是虚继承, 都需要将指针存储在虚函数表(virtual function table), 占用4个字节. 继承会继承基类的数据, 和虚函数表, 即继承基类的空间. 代码: /* * test.cpp * * Created on: 2014.04.20 * Author: Spike */ /*eclipse cdt, gcc 4.8.1*/ #include <