这是知乎上c++虚函数的作用https://www.zhihu.com/question/23971699
CSDN上的c++虚函数表解析表http://blog.csdn.net/haoel/article/details/1948051/
维基百科上对虚函数的解释https://zh.wikipedia.org/wiki/%E8%99%9A%E5%87%BD%E6%95%B0_(%E7%A8%8B%E5%BA%8F%E8%AF%AD%E8%A8%80)
虚函数和基类指针
基类指针不需要经过类型转换就可以指向派生类对象,这是一个重要的事实。但是,基类指针虽然可以获取派生类对象的地址,却只能访问派生类从基类继承的成员。
演示基类指针的移动。Base类派生First_d类,First_d类派生Second_d类
#include
using namespace std;
class Base
{
public:
Base(char xx)
{
x = xx;
}
void who()
{
cout << "Base class:" << x << "\n";
}
protected:
char x;
};
class First_d :public Base
{
public:
First_d(char xx,char yy):Base(xx)
{
y = yy;
}
void who()
{
cout << "First derived class:" << x << "," << y << "\n";
}
protected:
char y;
};
class Second_d :public First_d
{
public:
Second_d(char xx, char yy, char zz):First_d(xx,yy)
{
z = zz;
}
void who()
{
cout << "Second derived class:" << x << "," << y << "," << z << "\n";
}
protected:
char z;
};
int main()
{
Base B_obj('A');
Base *p;
First_d F_obj('T', 'O');
Second_d S_obj('E', 'N', 'D');
p = &B_obj;
p->who();
p = &F_obj;
p->who();
p = &S_obj;
p->who();
((First_d*)p)->who();
F_obj.who();
S_obj.who();
((Second_d*)p)->who();
}
#include
using namespace std;
class Base
{
public:
Base (char xx)
{
x = xx;
}
virtual void who()//说明虚函数
{
cout << "Base class:" << x << "\n";
}
protected:
char x;
};
class First_d:public Base
{
public:
First_d(char xx, char yy) :Base(xx)
{
y = yy;
}
void who()//默认说明虚函数
{
cout << "First derived class" << x << "," << y << "\n";
}
protected:
char y;
};
class Second_d:public First_d
{
public:
Second_d(char xx,char yy,char zz):First_d(xx,yy)
{
z = zz;
}
void who()
{
cout << "Second derived class:" << x << "," << y << "," << z << "\n";
}
protected:
char z;
};
int main()
{
Base B_obj('A');
Base *p;
First_d F_obj('T', 'O');
Second_d S_obj('E', 'N', 'D');
p = &B_obj;
p->who();
p = &F_obj;
p->who();
p = &S_obj;
p->who();
}
Base类中的who()函数冠以关键字virtual被说明为虚函数。之后,派生类相同界面的成员函数who也默认其具有虚特性而可以省略virtual说明符。
定义虚函数时注意点
(1)一旦一个成员函数被说明为虚函数,则不管经历多少次派生类层次,所有界面相同的重载函数都保持虚特性。因为派生类也是基类。
(2)虚函数必须是类的成员函数。不能将虚函数说明为全局(非成员)函数,也不能说明为静态成员函数。因为虚函数的动态联编必须在类层中依靠this指针实现。
(3)不能将友元说明为虚函数,但是虚函数可以是另一个类的友元。
(4)析构函数可以是虚函数,但是构造函数不能是虚函数。
虚函数的重载特性
在一个派生类中重定义基类的虚函数是函数重载的一种特殊形式,它不同于一般函数的重载。当重载一般函数时,函数的返回类型和参数的个数、类型可能不相同,仅要求函数名相同。但重载一个虚函数时,要求函数名、返回类型、参数个数、参数类型和顺序完全相同。
#include
using namespace std;
class A
{
public:
virtual void vf1()
{
cout << "It is virtual function vf1()of A.\n";
}
virtual void vf2()
{
cout << "It is virtual function vf2() of A.\n";
}
virtual void vf3()
{
cout << "It is virtual function vf3()of A.\n";
}
void fun()
{
cout << "It is common member function A::fun.\n";
}
};
class B :public A
{
public:
void vf1()
{
cout << "Virtual function vf1()overloading of B.\n";
}
void vf2(int x)
{
cout << "B::vf2()lose vritual character.The parameter is" << x << endl;
}
//char vf3(){}; 仅返回类型不同,错误重载
void fun()
{
cout << "It is common over loading member function B::fun().\n";
}
};
int main()
{
B b;
A *Ap = &b;//基类指针指向派生类对象
Ap->vf1();//调用B::vf1()
Ap->vf2();//调用A::vf2()
b.vf2(5);//调用B::vf2()
Ap->vf3();//调用A::vf3()
Ap->fun();//调用A::fun()
b.fun();//调用B::fun()
}
main函数用基类指针Ap指向派生类对象b。派生类B中的函数vf1()与基类中的虚函数vf1()具有完全相同的函数原型,保持了虚特性,使得语句:Ap->vf1();调用了派生类的虚函数版本。
派生类函数B::vf2(int)有一个整型参数,与基类函数A::vf2()同名,但函数原型不同,丢失了虚特性,只是一般函数重载。所以语句:Ap->vf2();只能调用基类版本 A::vf2()。为了调用派生类非虚函数的重载版本,要显示使用对象名或类作用域指定: b.vf2(5);或 B::vf2(5);
由于类B对vf3 ()重载仅返回类型不同,因而c++认为是错误的重载。
至于fun ,在基类和派生类中均有定义,且函数原型相同,但它是非虚的,没有动态特性。非虚函数的调用解释依赖于指针类型、引用类型,或者依赖于对象、类型的显式指示。所以语句: Ap->fun(); 调用基类版本,而语句: b.fun(); 调用派生类版本。
虚析构函数
构造函数不能是虚函数。因为建立一个派生类对象时,必须从类层次的根开始,沿着继承路径逐个调用基类的构造函数,直至自身的构造函数,不能“选择性地”调用构造函数。所以虚析构函数没有意义,定义虚析构函数将产生语法错误。
析构函数可以是虚的。虚析构函数用于动态建立类对象时,指引delete 运算符选择正确的析构调用。
普通析构函数在删除动态派生类对象的调用情况。
#include
using namespace std;
class A
{
public:
~A()
{
cout << "A::~A()is called.\n";
}
};
class B :public A
{
public:
~B()
{
cout << "B::~B()is called.\n";
}
};
int main()
{
A*Ap = new B;//用基类指针建立派生类的动态对象
B*Bp2 = new B;//用派生类指针建立派生类的动态对象
cout << "delete first object:\n";
delete Ap;
Ap = NULL;
cout << "delete second object:\n";
delete Bp2;
Bp2 = NULL;
}
程序中,用基类指针Ap建立派生类动态对象,用delete运算删除对象时,只调用基类的析构函数A:: A,不能释放派生类对象自身定义占有的资源。而用派生类指针建立动态对象,能够正确调用B::B和A::~A,释放派生类对象的所有资源,包括派生类从基类继承的那部分。
可见,处理动态分配的类层次结构中的对象时存在一个问题。如果用基类指针指向由new运算建立的派生类对象,而delete运算又显式地作用于指向派生类对象的基类指针,那么,不管基类指针所指对象是何种类型,也不管每个类的析构函数名是否相同,系统都只调用基类的析构函数。
这个问题有一个简单的解决办法。将基类析构函数说明为虚析构函数,就会让所有派生类的析构函数自动成为虚析构函数(即使它们与基类析构函数名字不同)。
基类析构函数在派生类析构函数之后自动执行。
用虚析构函数删除派生类动态对象
using namespace std;
class A
{
public:
virtual~A()//虚析构函数
{
cout << "A::~A()is called.\n";
}
};
class B :public A
{
public:
~B()
{
cout << "B::~B()is called.\n";
}
};
int main()
{
A*Ap = new B;//用基类指针建立派生类的动态对象
B*Bp2 = new B;//用派生类指针建立派生类的动态对象
cout << "delete first object:\n";
delete Ap;
cout << "delete second object:\n";
delete Bp2;
}
从程序运行结果可以看出,定义了基类虚析构函数之后,基类指针指向的派生类动态对象也可以正确地用delete析构。设计类层次结构时,往往不能预知使用它的各种复杂情况,所以为基类提供一个虚析构函数,能够使派生类对象在不同状态下正确调用析构函数。因此,将基类的析构函数说明为虚函数没有坏处。
纯虚函数和抽象类
基类往往用于表达一些抽象的概念。例如,在一个工资系统中,雇员Employee是一个基类,表示所有人员。可以派生出管理人员Manager类,工人Worker类和销售员Sales类。每个派生类具有不同的工资计算方式。基类Employee体现一个抽象的概念,定义一个工资计算函数显然是无意义的。但是我们可以把Employee类的工资计算函数说明虚函数,仅说明一个公共的界面,而由各派生类提供各自的实现版本。在这种情况下,基类的有些函数没有定义是很正常的,但要求派生类必须重定义这些虚函数,以使派生类有意义。为次,c++引入了纯虚函数的概念。
纯虚函数
纯虚函数是在基类中说明的虚函数,它在该基类中没有实现定义,要求所有派生类都必须定义自己的版本。
纯虚函数的说明形式如下:
virtual 类型 函数名(参数表)=0;
其中,“函数名”是纯虚函数名。该函数赋值为0,表示没有实现定义。虚函数的实现在它的派生类中定义。
纯虚函数的使用
#include
using namespace std;
class Figure
{
protected:
double x, y;
public:
void set_dim(double i, double j = 0)
{
x = i;
y = j;
}
virtual void show_area()const = 0;//定义纯虚函数
};
class Triangle :public Figure
{
public:
void show_area()const
{
cout << "Triangle with high" << x << "and base" << y;
cout << "has an area of" << x*0.5*y << "\n";
}
};
class Square :public Figure
{
public:
void show_area()const
{
cout << "Square with dimension" << x << "*" << y;
cout << "has an area of" << x*y << "\n";
}
};
class Cricle :public Figure
{
public:
void show_area()const
{
cout << "Circle with radius" << x;
cout << "has an area of" << 3.14*x*x << "\n";
}
};
int main()
{
Triangle t;
Square s;
Cricle c;
t.set_dim(10.0, 5.0);
t.show_area();
s.set_dim(10.0, 5.0);
s.show_area();
c.set_dim(9.0);
c.show_area();
}
程序中基类Figure派生了Triangle类、Square类和Cricle类。Friangle类的成员函数show_area()是纯虚函数。它的实现在派生类中分别定义。mian函数通过不同的对象调用派生类show_area()函数的不同实现版本。
如果你声明这个类的一个const实例,那么它就只能调用有const修饰的函数。常成员函数
使用const关键字进行说明的成员函数,称为常成员函数。只有常成员函数才有资格操作常量或常对象,没有使用const关键字说明的成员函数不能用来操作常对象。常成员函数说明格式如下:
<类型说明符 <函数名(<参数表) const;
其中,const是加在函数说明后面的类型修饰符,它是函数类型的一个组成部分,因此,在函数实现部分也要带const关键字。
抽象类
抽象类至少有一个纯虚函数。如上面例子Firgure类具有纯虚函数show area(),所以它是抽象类。
如果抽象类的一个派生类没有为继承的纯虚函数定义实现版本,那么,它仍然是抽象类。对应地,定义了纯虚函数实现版本的派生类称为具体类。
对于抽象类的使用,c++有以下限制:(1)抽象类只能用做其他类的类型;(2)抽象类不能建立对象(3)抽象类不能用做参数类型、函数返回类型或显式类型转换。
但是,可以说明抽象类的指针和引用。
对Firgure抽象类的使用:
Firgure x ;//错误,抽象类不能建立对象
Firgure *p;//正确,可以说明抽象类的指针
Firgure f();//错误,抽象类不作为返回类型
void g(Firgure);//错误,抽象类不能作为参数类型
Firgure &h(Firgure);//正确,可以说明抽象类的引用
使用抽象类引用,以不同数制形式输出正整数。
#include
using namespace std;
class Number {
public:
Number(int i)
{
val = i;
}
virtual void Show()const = 0;
protected:
int val;
};
class Hex_type :public Number
{
public:
Hex_type(int i):Number(i){}
void Show()const
{
cout << "Hexadecimal:" << hex << val << endl;
}
};
class Dec_type :public Number
{
public:
Dec_type(int i) :Number(i) {}
void Show()const
{
cout << "Decimal:" << dec << val << endl;
}
};
class Oct_type :public Number
{
public:
Oct_type(int i) :Number(i) {}
void Show()const
{
cout << "Octal:" << oct << val << endl;
}
};
void fun(Number&n)//普通函数定义抽象类的引用参数
{
n.Show();
}
int main()
{
Dec_type n1(50);
fun(n1);
Hex_type n2(50);
fun(n2);
Oct_type n3(50);
fun(n3);
}