目录
1.虚函数的引入
2.虚函数作用
3.关于虚函数的几点说明
4.纯虚函数
5.抽象类
6.虚基类
先看如下程序,程序后有进一步的解释。如果读者对程序不懂请先复习基础知识。
//
// VirtualFun.cpp
// virtual
//
// Created by 刘一丁 on 2019/8/26.
// Copyright © 2019年 LYD. All rights reserved.
//
//file1.cpp
#include
using namespace std;
class Base //基类 Base
{
public:
Base(int x, int y)
{ a = x; b = y; }
//virtual void show() //采用虚函数解决同名函数调用问题
void show() //基类成员函数 show()
{
cout << "base++++++" << endl;
cout << a << " " << b << endl;
}
private:
int a, b;
};
class Derived:public Base //派生类 Derived
{
public:
Derived(int x, int y, int z):Base(x, y)
{ c = z; }
void show() //派生类成员函数 show()
{
cout << "Derived+++++++" << endl;
cout << " c= " << c << endl;
}
private:
int c;
};
int main()
{
Base mb(60, 60), *pc; //定义基类对象及指向基类的指针(简称基类指针)
Derived mc(10, 20, 30); //定义派生类对象
pc = &mb; //基类指针指向基类对象 mb
pc-> show();
pc = &mc; //基类指针指向派生类对象 mc
pc-> show();
return 0;
}
//output:
//base++++++++
//60 60
//base++++++++
//10 20
注:
C++中规定:基类的对象指针可以指向它公有派生的对象,但是当其指向公有派生类对象时,它只能访问派生类中从基类继承来的成员,而不能访问公有派生类中定义的成员,解决这个问题的方法就是引入虚函数。
虚函数通常和指针连用,来体现动态的多态性。使用指针的目的是为了表达一种动态的特性,即当指针指向不同对象(基类或派生类对象)时,分别调用不同类的成员函数。
在上文中虽然基类指针 pc 已经指向了派生类对象 mc,但是它所调用的成员函数 show() 仍然是其基类对象的 show()。解决方法有三种。
①mc.show();
②((Derived*)pc)-> show();
③将基类中的同名函数声明为虚函数,即在对应成员函数前加上关键字 virtual,并在派生类中被重载。
虚函数更该方法即是代码中注释的部分,virtual void show();函数输出结果如下:
base++++++++
60 60
Derived+++++
c = 30
(1)C++规定,如果在派生类中,没有用 virtual 显式地给出虚函数声明,这是系统就会遵循以下规则来判断一个成员函数是否是虚函数。
①该函数与基类的虚函数有相同的名称。
②该函数与基类的虚函数有相同的参数个数及相同的对应参数类型。
③该函数与基类的虚函数有相同的返回类型或者满足赋值兼容规则的指针、引用型的返回类型。
(2)通过虚函数来使用多态性机制,派生类必须从他的基类公有派生(赋值兼容规则)。
(3)虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态成员函数,因为虚函数调用哪个要靠特定的对象来决定该激活哪个函数。
(4)构造函数不能是虚函数,通常把析构函数声明为虚函数(请读者自行百度)。
基类往往表示一种抽象的概念,它并不与具体的事物相联系。这时在基类中将某一成员函数定义为虚函数,并不是基类本身的要求,而是考虑到派生类的需要,在基类中预留了一个函数名,具体功能留给派生类根据需要去定义。
纯虚函数是在声明虚函数时被“初始化”为 0 的函数。例如
virtual void show() = 0;
纯虚函数的作用是在基类中为其派生类保留一个函数的名字,以便派生类根据需要对他进行重新定义。纯虚函数没有函数体,它最后面的“ = 0” 并不表示函数的返回值为 0,它只起形式上的作用,告诉编译系统“这是纯虚函数”。纯虚函数不具备函数的功能,不能被调用。
//
// VirtualFun.cpp
// Virtual
//
// Created by 刘一丁 on 2019/8/26.
// Copyright © 2019年 LYD. All rights reserved.
//
//file1.cpp
#include
using namespace std;
class Circle
{
public:
void set(int x)
{ r = x; }
virtual void show() = 0; //定义纯虚函数
protected:
int r;
};
class Area:public Circle
{
public:
void show()
{ cout << "Area is " << 3.14 * r * r << endl; }
};
class Perimeter:public Circle
{
public:
void show()
{ cout << "Primeter is " << 2 * 3.14 * r << endl; }
};
int main()
{
Circle *ptr;
Area ob1;
Perimeter ob2;
ob1.set(10);
ob2.set(10);
ptr = &ob1;
ptr-> show();
ptr = &ob2;
ptr-> show();
return 0;
}
//output:
//Area is 314
//Primeter is 62.8
如果一个类至少有一个纯虚函数,那么就称该类为抽象类。上述程序中定义的类 circle 就是一个抽象类。
①抽象类只能作为其他类的基类来使用,不能建立抽象类的对象。
②不允许从具体类派生出抽象类。
③抽象类不能用作函数的参数类型、函数的返回类型或显示转换的类型。
虚基类是对多继承来说的。如果一个类有多个直接基类,而这些直接基类又有一个共同的基类,则在最底层的派生类中会保留这个间接的共同基类数据成员的多份同名成员。
//
// VirtualClass.cpp
// VirtualClass
//
// Created by 刘一丁 on 2019/8/31.
// Copyright © 2019年 LYD. All rights reserved.
//
//file1.cpp
#include
using namespace std;
class base
{
public:
base()
{
a = 5;
cout << "base a = " << a << endl;
}
protected:
int a;
};
class base1:public base
{
public:
base1()
{
a = a + 10;
cout << "base1 a = " << a << endl;
}
};
class base2:public base
{
public:
base2()
{
a = a + 20;
cout << "base2 a = " << a << endl;
}
};
class derived:public base1, public base2
{
public:
derived()
{
cout << "base1::a = " << base1::a << endl; //明确指出调用 base1 的数据成员 a
cout << "base2::a = " << base2::a << endl; //明确指出调用 base2 的数据成员 a
}
};
int main()
{
derived obj;
return 0;
}
//output:
//1.base a = 5
//2.base1 a = 15
//3.base a = 5
//4.base2 a = 25
//5.base1::a = 15
//6.base2::a = 25
//注:在访问这些同名成员时,必须在派生类对象名后增加直接基类名
//使其唯一地标识一个成员,以免产生二义性。
上述程序中,在定义 derived 对象 obj 时,首先调用 base1 的构造函数,在调用 base2 的构造函数,最后调用 derived 构造函数。在调用 base1 构造函数时又会先调用 base 的构造函数,所以会输出 1-2 语句;在调用 base2 构造函数时又会先调用 base 的构造函数所以输出 3-4 语句。 虽然在类 base1 和 base2 中没有定义数据成员 a ,但是他们在调用基类 base 构造函数时会分别继承基类的数据成员 a。这样就在 base1 和 base2 中同时存在着同名的数据成员 a ,他们都是类 base 成员的拷贝。但是类 base1 和类 base2 中的数据成员 a 分别具有不同的存储单元,可以存放不同的数据(在这里可以这样理解,base1 公有继承了 base ,所以相当于 base1 中也有protected 类型的数据成员 a )。下图显示了其中的层次关系。
由于在类derived 中同时存在着类 base1 和类base2 的数据成员 a ,因此在 derived 的构造函数中输出 a 的值,必须加上“类名::”,指出是那一个数据成员 a ,否则就会出现二义性。例如
class derived:public base1, public base2
{
public:
derived()
{ cout << "derived a = " << a << endl; } //错误,二义性。
};
要想解决这个问题就需要用到虚基类。即将共同的基类base 声明为虚基类。这就要求从 base 派生新类时,使用关键字 virtual 将类 base 声明为虚基类。声明形式如下:
class 派生类名:virtual 继承方式 类名
{
...
}
用虚基类重写上述代码如下:
//
// VirtualClass.cpp
// VirtualClass
//
// Created by 刘一丁 on 2019/8/31.
// Copyright © 2019年 LYD. All rights reserved.
//
//file1.cpp
#include
using namespace std;
class base
{
public:
base()
{
a = 5;
cout << "base a = " << a << endl;
}
protected:
int a;
};
class base1:virtual public base
{
public:
base1()
{
a = a + 10;
cout << "base1 a = " << a << endl;
}
};
class base2:virtual public base
{
public:
base2()
{
a = a + 20;
cout << "base2 a = " << a << endl;
}
};
class derived:public base1, public base2
{
public:
derived()
{ cout << "derived a = " << a << endl; }
};
int main()
{
derived obj;
return 0;
}
//output:
//base a = 5
//base1 a = 15
//base2 a = 35
//derived a = 35
在上述程序中,从类 base 派生出来 base1 和类 base2 时,使用了关键字 virtual,把类 base 声明为类 base1 和 base2 的虚基类。这样,从类 base1 和 base2 派生出的类 derived 只继承基类 base 一次,也就是说,基类 base 的数据成员 a 只保留一份。具体层次关系如下图。