一、多态
c++有三大特性:封装,继承,多态
多态是面向对象程序设计的一个重要特征,多态就是一个东西有多重状态,具有不同功能的函数可以用一个函数名,这样就可以用一个函数名实现不同的功能
静态多态和动态多态
静态多态是利用重载实现的,在程序编译时确定要调用的是哪个函数,也称为编译时多态。动态多态是利用虚函数实现的,在程序执行期间才动态的确定操作所指对的对象,也称为运行时多态
动态多态:当一个基类被继承为不同的派生类时,各派生类可以使用与基类成员相同的成员名,如果在运行时用同一个成员名调用类对象的成员,会调用哪个?通过继承而产生了相关的不同派生类,与基类成员同名的成员在不同的派生类中有不同的含义
二、虚函数
在类的继承层次结构中,在不同的层次中可以出现名字相同、参数个数类型相同的函数,这时系统会根据同名覆盖的原则决定调用的对象
如果你要调用同名的父类中的函数的话,必须在调用使用时注明类域。虚函数的作用就是允许在派生类中重新定义与基类同名的函数,并可以通过基类指针或引用来访问基类和派生类中同名函数
在父类中声明display函数时,在左端加上**关键字virtual**,这样就将父类中的display函数声明为虚函数
class Father
{
public:
vitrual void display()
{
cout<<_father<<endl;
}
};
class Son : public Father
{
public:
void display()
{
cout<<_son<<endl;
}
};
int main()
{
Father f;
Son s;
Father* p=&f;//指向基类对象
p->display();//调用基类中函数
p=&s;//指向派生类对象
p->display();//调用派生类中函数
return 0;
}
基类的指针是用来指向基类对象的,如果用它来指向派生类的对象,则进行指针类型的转化,即基类指针指向的是派生类中从基类中继承的部分。声明为虚函数之后,在派生类中的虚函数取代了基类原来的虚函数,因此在使用基类指针指向派生类对象后,调用虚函数就是调用了派生类的虚函数
注意问题
- 在基类中用virtual声明成员函数是虚函数,类外定义时不需要再加virtual
- 当一个成员函数被声明为虚函数后,在派生类中的同名函数都是虚函数,如果在派生类中没有同名的函数时,那么派生类只是简单的继承基类的虚函数
- 定义一个指向基类对象的指针变量,想要调用谁的虚函数,就让指针指向同一类中需要调用该函数的对象
- 函数重载是在一个类中,虚函数是在基类与派生类之间的,还与重载不同的是,虚函数要求函数的首部完全相同
- 因为虚函数用在类的继承层次的,所以只能将类的成员函数声明为虚函数
- 如果是通过指向基类对象的指针调用派生类中成员函数,则应声明为虚函数
- 一般将析构函数生命为虚函数,就算基类不需要析构函数,也要显示的定义一个函数体为空放入虚析构函数,以保证在释放对象动态分配空间时能得到正确的处理
纯虚函数
在类中将某一成员函数声明为虚函数,只是因为派生类的需求,在基类中预留一个函数名,具体功能留给派生类定义,这时可以将基类中的虚函数声明为纯虚函数
virtual void fun(int x,int y)=0;
纯虚函数没有函数体,这种形式就是告诉编译器,是一个纯虚函数,留在派生类中定义。
含有纯虚函数的类就成为抽象类。抽象类只是一种基本的数据类型,根据需求在派生类定义各种功能。这个类的作用就是为派生类提供一个公共接口,抽象类不能定义对象,但是可以定义指向抽象类的指向变量。
虚函数表
当父类的指针或引用指向父类对象时调用的是父类虚函数,当指向子类对象时调用的是子类的虚函数,这是通过虚函数表实现的,虚函数表是通过一块连续内存来存储虚函数的地址
使用虚函数,系统会有一定的空间开销,当一个类中有虚函数时,编译系统会为该类构造一个虚函数表,是一个指针数组,用来存放每个虚函数入口地址,虚函数表的最后一个元素是一个空指针
每一个虚函数表都是一个函数指针数组,而每一个虚函数表都由一个虚函数表指针维护,这个虚函数表指针叫做_vptr。
单继承模式
class Father
{
public:
Father()
:_f(1)
{}
virtual void fun1()
{}
virtual void fun2()
{}
private:
int _f;
};
class Son:public Father
{
public:
Son()
:_s(2)
{}
virtual void fun1()
{}
virtual void fun3()
{}
virtual void fun4()
{}private :
int _s;
};
typedef void(*FUNC)();
void PrintVTable(int *VTable)
{
cout << "虚函数表地址" << VTable << endl;
for (int i = 0; VTable[i] != 0; ++i)
{
cout << i << ":" << VTable[i]<<endl;
FUNC f = (FUNC)VTable[i];
f();
}
cout << endl;
}
int main()
{
Son a;
PrintVTable((int *)(*(int*)(&a)));
system("pause");
return 0;
}
虚函数表实质为函数指针数组,里面放的是函数指针,指向函数
多继承模式
class A
{
public:
A()
:_a(1)
{}
virtual void fun1()
{
cout<<"A::fun1()"<<endl;
}
virtual void fun2()
{
cout<<"A::fun2()"<<endl;
}
private:
int _a;
};
class B
{
public:
B()
:_b(2)
{}
virtual void fun3()
{
cout<<"B::fun3()"<<endl;
}
virtual void fun4()
{
cout<<"B::fun4()"<<endl;
}
private:
int _b;
};
class C:public A,public B
{
public:
C()
:_c(3)
{}
virtual void fun1()
{
cout<<"C::func1()"<<endl;
}
virtual void fun3()
{
cout<<"C::fun3()"<<endl;
]
virtual void fun5()
{
cout<<"C::fun5()"<<endl;
}
private:
int _c;
};
typedef void(*FUNC) ();
void PrintVTable(int *VTable)
{
cout<<"虚函数表地址"<<VTable<<endl;
for(int i=0;VTable[i]!=0;++i)
{
cout<<i<<":"<<VTable[i]<<" “;
FUNC f = (FUNC)VTable[i];
f();
}
cout<<endl;
}
int main()
{
C c;
PrintVTable((int *)(*(int *)(&c)));
PrintVTable((int *)(*((int *)(&c)+sizeof(A)/4)));
system("pause");
return 0;
]
其中fun5()函数是在C中声明为虚函数的,但是并不为C单独建立虚函数表,而是把fun5()函数的地址写到C中的第一个虚函数表的最后
菱形继承模型
class A
{
public:
A()
:_a(1)
{}
virtual void fun1()
{
cout<<"A::fun1()"<<endl;
}
virtual void fun2()
{
cout<<"A::fun2()"<<endl;
}
private:
int _a;
};
class B:public A
{
public:
B()
:_b(2)
{}
virtual void fun1()
{
cout<<"B::fun1()"<<endl;
}
virtual void fun3()
{
cout<<"B::fun3()"<<endl;
}
private:
int _b;
};
class C:public A
{
public:
C()
:_c(3)
{}
virtual void fun1()
{
cout<<"C::fun1()"<<endl;
}
virtual void fun3()
{
cout<<"C::fun3()"<<endl;
}
private:
int _c;
};
class D:public B,public A
{
public:
D()
:_d(3)
{}
virtual void fun1()
{
cout<<"C::fun1()"<<endl;
}
virtual void fun4()
{
cout<<"C::fun4()"<<endl;
}
private:
int _d;
};
int main()
{
D d;
PrintVTable((int *)(*(int *)(&d)));
system("pause");
return 0;
}
菱形多重继承的对象模型中,先是B中的虚函数表指针,再是B从A继承的成员变量,最后是B的成员变量。接下来是C中的虚函数表指针,最后是C的成员变量。最后是D的成员变量。D中的虚函数fun4()的地址则存放到了第一个虚函数表的最后
由于fun2()函数一直没有被重写,所以还是A的函数,而fun1()函数最终被D中的fun1()函数重载,所以虚函数表中的fun1函数被D中的虚函数fun1()地址覆盖