序言
本文主要内容:虚函数 + 纯虚函数 + 普通函数 + 虚函数纯虚函数的区别
1. 虚函数
什么是虚函数
- 被virtual关键字修饰的成员函数,就是虚函数
为什么要使用虚函数
引入虚函数主要为了实现重写和多态
虚函数主要作用是为了实现“动态多态性”,父类提供虚函数的实现,为子类提供默认的函数实现。子类可以重写父类的虚函数实现子类的特殊化。
说明
- 对于虚函数来说,父类和子类都有各自的版本,由多态方式调用的时候动态绑定。
- 虚函数必须实现,否则在链接时将会报错
2. 纯虚函数
什么是纯虚函数
- virtual returnType function() = 0;
为什么要使用纯虚函数
- 也是为了实现“动态多态性”
- 在很多情况下,基类本身生成对象是不合情理的,动物基类可以派生出老虎、孔雀等,但是动物本身的对象却没有实际意义,所以引入了纯虚函数
什么情况下使用纯虚函数
- 在基类中抽象出一个方法,且该基类只能被继承不能被实例化。
- 这个方法必须在派生类中被实现。
满足以上两点,可考虑声明为纯虚函数。
3. 普通函数
类的普通成员函数。
父类为子类提供了普通函数的强制实现
普通函数是静态编译的,可类内重载但没有运行时多态,只会根据指针或引用的“字面值”类对象,调用自己的普通函数。
4. 虚函数和纯虚函数举例
- [1] 普通函数举例(静态联编)
/* 例1 */
class A
{
public:
void funPrint()
{
cout<<"function in class A"<<endl;
}
};
class B:public A
{
public:
void funPrint() //重定义
{
cout<<"function in class B"<<endl;
}
};
int main()
{
A *p; //基类指针
A a; //基类对象
B b; //派生类对象
p = &a;
p->funPrint();
p = &b;
p->funPrint();
b.funPrint();
b.A::funPrint();
return 0;
}
//输出结果:
function in class A
function in class A
function in class B
function in class A
//原因:
(1)静态联编:编译器在编译时就确定好了函数调用
(2)只是普通函数“重定义”,不管引用的实例是哪个类的,调用的时候系统会调用 “左值” 那个对象所属类的方法
>>> 所以基类A指针p最终调用类A的funPrint()函数,输出两个function in class A
>>> 类B实例调用类B的funPrint()函数,类B加上作用域操作符A::调用类A的funPrint()函数
/* 例2 */
#include <iostream>
using namespace std;
class A
{
public:
virtual void fun1() = 0; //纯虚函数
virtual ~A(){};
virtual void fun2() //虚函数
{
cout<<"fun1 in class A"<<endl;
}
void fun3() //普通成员函数
{
cout<<"fun3 in class A"<<endl;
}
};
class B:public A
{
public:
virtual ~B(){};
void fun1() //纯虚函数实现
{
cout<<"fun1 in class B"<<endl;
}
void fun2() //虚函数重写(覆盖)
{
cout<<"fun2 in class B"<<endl;
}
void fun3() //普通函数重定义
{
cout<<"fun3 in class B"<<endl;
}
};
int main()
{
A *a = new B; //抽象类定义指针
a->fun1();
a->fun2();
a->fun3();
cout<<"*******************"<<endl;
B b; //派生类实例化
b.fun1();
b.fun2();
b.fun3();
delete a; //释放 new 的对象
return 0;
}
//输出结果:
fun1 in class B //纯虚函数在基类中声明在子类中实现
fun2 in class B //动态联编:多态。虚函数在子类中重写,运行时动态绑定
fun3 in class A //静态联编:编译时就确定好了调用哪个函数。调用左值所属类的方法
*******************
fun1 in class B
fun2 in class B //动态联编:多态。虚函数在子类中重写,运行时动态绑定
fun3 in class B //静态联编:调用左值所属类的方法
- [2] 虚函数举例(动态联编)
class A
{
public:
virtual void funPrint()
{
cout<<"function in class A"<<endl;
}
};
class B:public A
{
public:
void funPrint() //或virtual void funPrint(),重写
{
cout<<"function in class B"<<endl;
}
};
int main()
{
A *p; //基类的指针
A a;
B b;
p = &a;
p->funPrint();
p = &b;
p->funPrint();
return 0;
}
//输出结果:
function in class A
function in class B
//原因:
(1)动态联编:根据实例的不同动态决定调用哪个函数
(2)虚函数的唯一目的就是为了实现多态(动态多态性),编译时不确定调用哪个函数,而是动态的决定要调用哪个函数,即“运行时多态”
>>> 所以基类A指针p指向类A的实例a就调用类A的funPrint()函数,
>>> 指向类B的实例b就调用类B的funPrint()函数
[3] 纯虚函数举例(抽象类)
纯虚函数通常存在于抽象类中,在基类中只是声明一个函数但不去实现它,让派生类去实现
class Vehicle
{
public:
virtual void PrintTyre() = 0; //纯虚函数
};
class Car:public Vehicle
{
public:
virtual void PrintTyre()
{
cout<<"Car tyre four"<<endl;
}
};
class Bike:public Vehicle
{
public:
virtual void PrintTyre()
{
cout<<"Bike tyre two"<<endl;
}
};
int main()
{
Car c;
Bike b;
b.PrintTyre();
c.PrintTyre();
return 0;
}
//输出结果:
Car tyre four
Bike tyre two
//原因:
(1)定义vehicle类,但是具体每种交通工具有多少个轮子是不一定的,就定义为纯虚函数、留待派生类中具体实现
5. 虚函数和纯虚函数的联系与区别
联系
虚函数和纯虚函数可以定义在同一个类中
虚函数和纯虚函数都可以在子类中被重写,以多态的形式调用
虚函数和纯虚函数通常存在于抽象基类中,被子类继承,目的是提供一个统一的接口
虚函数和纯虚函数的定义中不能有static标识符,因为static要求编译时即绑定,而虚函数和纯虚函数运行时才绑定(动态多态性)
实现了纯虚函数的子类,该纯虚函数在子类中就变成了虚函数,子类的子类即孙子类可以覆盖(重写)该虚函数,由多态方式调用的时候动态绑定
区别
定义:
虚函数virtual returnType function();
纯虚函数virtual returnType function() = 0;
抽象类:
含有纯虚函数的类称为抽象类,而只含有虚函数的类不能称为抽象类
抽象类不能实例化,但可以定义抽象类的指针和引用(见例2)
使用:
虚函数可以直接使用,也可以子类重写后以多态形式调用
虚函数在基类中只有声明没有定义,是对子类的约束,是“接口继承”,必须在子类中实现该函数才可以使用
Acknowledgements:
http://www.cnblogs.com/xudong-bupt/p/3570304.html
http://www.cnblogs.com/fzhe/archive/2013/01/02/2842513.html
http://blog.chinaunix.net/uid-26851094-id-3327323.html
http://www.cnblogs.com/crazyacking/p/5265634.html(推荐)
http://www.cnblogs.com/bluestorm/archive/2012/08/29/2662350.html
2017.09.28