-
什么叫虚函数:
①.父类中的函数声明前加上virtual关键字,子类在重写的时候可加可不加
②.父类如果有虚函数,则会产生一个虚函数表,记录虚函数的入口地址,子类在继承父类后,会 将地址一并继承下来,但是在重写父类中的虚函数的时候,会将地址替换成子类的虚函数地址,在调用的时候也会调用子类的函数
③.含有纯虚函数的类叫做抽象类,抽象类不能初始化,不能当做返回值,不能当做参数,可以定义指针,因为此时还没有初始化 -
动态类型和静态类型
静态类型:对象在声明时采用的类型,在编译期既已确定;动态类型:通常是指一个指针或引用目前所指对象的类型,是在运行期决定的;
静态绑定:绑定的是静态类型,所对应的函数或属性依赖于对象的静态类型,发生在编译期;
动态绑定:绑定的是动态类型,所对应的函数或属性依赖于对象的动态类型,发生在运行期;
只有虚函数才是动态绑定,非虚函数都是静态绑定的,何种绑定方式决定了指向的对象是哪个
对象的动态类型可以更改,但是静态类型无法更改;
-
动态绑定和静态绑定
动态多态的使用条件就是父类的引用或者指针指向子类对象,以下为引用举例:
class Animal
{
public:
virtual void speak()
函数前加上virtual关键字,那么编译器在编译的时候就不能确定函数调用,实现地址晚绑定,在运行的时
候才会确定地址,而不是编译时期
{
cout << "动物在说话" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
void Dospeak(Animal &animal) 动态多态的使用条件就是父类的引用或者指针指向子类对象
这里的animal他的静态类型是Animal类,而动态类型根据传入参数的不同而不同,如果传入的是Cat类那么动态
类型就是Cat类。因为speak是个虚函数,而虚函数采用的是动态绑定,所以决定了animal指向的是Cat类对象,
因此会输出“小猫在说话”。如果把virtual去掉,普通函数采用的是静态绑定,也就是编译阶段就确定好了地址,
因此animal会绑定静态类型也就是Animal类,则会调用Animal类的函数,输出“动物在说话”
{
animal.speak();
}
int main()
{
Cat cat;
Dospeak(cat); 函数的输出为“小猫在说话”,如果把父类中的virtual去掉就会输出“动物在说话”
return 0;
}
动态多态的使用条件就是父类的引用或者指针指向子类对象,以下为指针举例:
class A
{
public:
/*virtual*/ void func(){ std::cout << "A::func()\n"; }
};
class B : public A
{
public:
void func(){ std::cout << "B::func()\n"; }
};
class C : public A
{
public:
void func(){ std::cout << "C::func()\n"; }
};
下面分析说明:
C* pc = new C(); //pc的静态类型是它声明的类型C*,动态类型也是C*;
B* pb = new B(); //pb的静态类型和动态类型也都是B*;
A* pa = pc; //pa的静态类型是它声明的类型A*,动态类型是pa所指向的对象pc的类型C*;
pa = pb; //pa的动态类型可以更改,现在它的动态类型是B*,但其静态类型仍是声明时候的A*;
C *pnull = NULL; //pnull的静态类型是它声明的类型C*,没有动态类型,因为它指向了NULL;
pa->func(); //A::func() pa的静态类型是A*,不管其指向的是哪个子类,都是直接调用A::func();
pc->func(); //C::func() pc的动、静态类型都是C*,因此调用C::func();
pnull->func(); //C::func() 为什么空指针也可以调用函数,因为这在编译期就确定了
如果把C中的函数声明去掉,那么:
pc->func(); //A::func() pc在类C中找不到func的定义,因此到其基类中寻找;
如果为A中的void func()函数添加virtual特性,其他不变
pa->func(); //B::func() 因为有了virtual虚函数特性,pa的动态类型指向B*,因此先在B中查找
pc->func(); //C::func() pc的动、静态类型都是C*,因此也是先在C中查找;
pnull->func(); //空指针异常,因为是func是virtual函数,因此对func的调用只能等到运行期才能确定,
//然后才发现pnull是空指针;
发生多态,动态类型决定调用哪个函数,不发生多态,静态类型决定调用哪个函数
另外,在virtual函数中,要注意默认参数的使用。当缺省参数和virtual函数一起使用的时候一定要谨慎,绝不重新定义继承而来的缺省参数值!举个例子:
class E
{
public:
virtual void func(int i = 0)
{
std::cout << "E::func()\t"<< i <<"\n";
}
};
class F : public E
{
public:
virtual void func(int i = 1)
{
std::cout << "F::func()\t" << i <<"\n";
}
};
void test2()
{
F* pf = new F();
E* pe = pf;
pf->func(); //F::func() 1 正常,就该如此;
pe->func(); //F::func() 0 为什么呢,因为缺省参数值是静态绑定,即此时i的值使用的是基类A中
//的缺省参数值,其值在编译阶段已经绑定,
}
4.在子类构造和析构期间,编译器将它看做是基类,而不是继承类,因此如果构造函数和析构函数是虚函数,他们是无法实现多态的,不会动态绑定。
5.纯虚函数也是可以有函数体的