C++中的虚函数以及动态绑定和静态绑定(值得收藏)

  1. 什么叫虚函数:
    ①.父类中的函数声明前加上virtual关键字,子类在重写的时候可加可不加
    ②.父类如果有虚函数,则会产生一个虚函数表,记录虚函数的入口地址,子类在继承父类后,会 将地址一并继承下来,但是在重写父类中的虚函数的时候,会将地址替换成子类的虚函数地址,在调用的时候也会调用子类的函数
    ③.含有纯虚函数的类叫做抽象类,抽象类不能初始化,不能当做返回值,不能当做参数,可以定义指针,因为此时还没有初始化

  2. 动态类型和静态类型
    静态类型:对象在声明时采用的类型,在编译期既已确定;

    动态类型:通常是指一个指针或引用目前所指对象的类型,是在运行期决定的;

    静态绑定:绑定的是静态类型,所对应的函数或属性依赖于对象的静态类型,发生在编译期;

    动态绑定:绑定的是动态类型,所对应的函数或属性依赖于对象的动态类型,发生在运行期;

    只有虚函数才是动态绑定,非虚函数都是静态绑定的,何种绑定方式决定了指向的对象是哪个

    对象的动态类型可以更改,但是静态类型无法更改;

  3. 动态绑定和静态绑定

    动态多态的使用条件就是父类的引用或者指针指向子类对象,以下为引用举例:

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.纯虚函数也是可以有函数体的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值