1.虚函数
定义:
用virtual修饰的成员函数称为虚函数;
重写(覆盖):
当在子类中定义了一个与父类完全相同的虚函数时,则称这个子类的函数重写(或覆盖)了父类的函数;
例:
#include<iostream>
using namespace std;
class Person
{
public:
virtual void BuyTicket()
{
cout<<"Price:全票"<<endl;
}
};
class Student
{
public:
virtual void BuyTicket()
{
cout<<"Price:半票"<<endl;
}
};
int main()
{
Person p;
Student s;
cout<<"Person:"<<endl;//成人买全票
p.BuyTicket();
cout<<"Student:"<<endl;//学生买半票
s.BuyTicket();
return 0;
}
总结:
- 完全相同指函数名、参数列表和返回值相同;
- 存在一种特殊情况:协变,子类虚函数和父类虚函数的返回值分别为子类指针和父类指针;
- 只有类的成员函数才能定义为虚函数;
- 静态成员函数不能定义为虚函数,因为static成员函数不属于任何对象;
- 如果在类外定义虚函数,只能在声明函数时加virtual,类外定义函数时不能加virtual;
- 构造函数不能为虚函数,虽然可以将operator=定义为虚函数,但最好不要这样做,因为使用时容易引起混淆;
- 内联函数不能为虚函数,如果内联函数被virtual修饰,计算机会忽视inline将它变成纯粹的虚函数;
- 不要在构造函数和析构函数里面调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会发生未定义的行为;
- 最好把基类的析构函数定义为虚函数;(如果没有定义为虚函数,当基类指针指向派生类,并且删除指针时,会析构基类而不会析构派生类,造成内存泄漏)
2.虚表
定义:
存放虚函数地址的表
例:
#include<iostream>
using namespace std;
typedef void (*Fun)();
class Base
{
public:
virtual void a()
{
cout<<"a()"<<endl;
}
virtual void b()
{
cout<<"b()"<<endl;
}
virtual void c()
{
cout<<"c()"<<endl;
}
int _i;
};
int main()
{
Base b;
b._i = 1;
Fun pFun = NULL;
cout<<sizeof(b)<<endl;
cout<<"虚表指针:"<<(int*)(&b)<<endl;
cout<<"a()地址:"<<(int*)*(int*)(&b)<<endl;//打印a()地址
pFun = (Fun)*((int*)*(int*)(&b));
pFun();
cout<<"b()地址:"<<(int*)*(int*)(&b)+1<<endl;//打印b()地址
pFun = (Fun)*((int*)*(int*)(&b)+1);
pFun();
cout<<"c()地址:"<<(int*)*(int*)(&b)+2<<endl;//打印c()地址
pFun = (Fun)*((int*)*(int*)(&b)+2);
pFun();
return 0;
}
运行结果(vs2008):
在内存中查看:
b对象模型结构:
3.虚表在继承中的情况
(1)一般继承,无虚函数覆盖
代码:
class Base
{
public:
virtual void f1()
{
}
virtual void h1()
{
}
int _b;
};
class Derive:public Base
{
public:
virtual void f2()
{
}
virtual void h2()
{
}
int _d;
};
实例对象Base b;Derive d;模型:
(2)一般继承,有虚函数覆盖
代码:
class Base
{
public:
virtual void f()
{
}
virtual void h1()
{
}
int _b;
};
class Derive:public Base
{
public:
virtual void f()
{
}
virtual void h2()
{
}
int _d;
};
实例对象Base b;Derive d;模型:
(3)多继承,无虚函数覆盖
代码:
class Base1
{
public:
virtual void f1()
{
}
int _b1;
};
class Base2
{
public:
virtual void g2()
{
}
int _b2;
};
class Derive:public Base1,public Base2
{
public:
virtual void h3()
{
}
int _d;
};
实例对象Base1 b1;Base2 b2;Derive d;模型:
(4)多继承,有虚函数覆盖
代码:
class Base1
{
public:
virtual void f1()
{
}
virtual void g1()
{
}
int _b1;
};
class Base2
{
public:
virtual void f2()
{
}
virtual void g2()
{
}
int _b2;
};
class Derive:public Base1,public Base2
{
public:
virtual void f1()//覆盖Base1的函数f1()
{
}
virtual void g2()//覆盖Base2的函数g2()
{
}
int _d;
};
实例对象Base1 b1;Base2 b2;Derive d;模型:
(5)总结
1.单继承
子类和父类有各自的虚表,子类虚表拷贝父类虚表中的内容,并会更新构成覆盖的函数地址;
2.多继承
子类中包含多个虚表(取决于继承的个数),各自拷贝父类虚表中的内容,并更新构成覆盖的函数地址,对于子类中没有构成覆盖的虚函数,将其地址添加到最先继承类的虚表中;
4.纯虚函数
定义:
在虚函数后面赋值0;
例:
class A
{
virtual void func() = 0; // 纯虚函数
protected :
string _a ;
};
class B : public A
{};
注意:
(1)纯虚函数没有函数体;
(2)最后面的“=0”并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是虚函数”;
(3)这是一个声明语句,最后有分号;
(4)纯虚函数只有函数的名字而不具备函数的功能,不能被调用。
(5)纯虚函数的作用是在基类中为其派生类保留一个函数的名字,以便派生类根据需要对他进行定义。如果在基类中没有保留函数名字,则无法实现多态性。
(6)如果在一个类中声明了纯虚函数,在其派生类中没有对其函数进行定义,则该虚函数在派生类中仍然为纯虚函数。
5.抽象类(接口类)
定义:
含有纯虚函数的类称为抽象类(接口类),抽象类不能实例化出对象;
注意:
(1)凡是包含纯虚函数的类都是抽象类;
(2)抽象类不能实例化出对象;
(3)纯虚函数在派生类中重新定义以后,派生类才能实例化出对象;
抽象类/接口类的作用:
抽象类的存在,使得子类必须重写虚函数才能实例化出对象;