C++ 多态

多态性

      最近在找工作,想着做的项目也不少,但是细细的看自己的代码,尽管有面向对象的概念,有一些设计模式,但是依旧很杂,而且使用了也不知道其原理,比如今天同学说咱们共享内存实现的机制是什么,还有堆之类的问题,顿时感觉自己很不足,于是静下心来好好的研究一下C++中的多态。

如果C++中的代码没有使用多态,那么就是过程化,或者简单的只是将数据与函数的结合,这就根本没有使用到C++的优势。不过话说回来,多态中的虚函数等等都是有一点额外的开销的,汇编语言中将要进行更多的操作。那么多态实现的方式有哪些呢?

      早绑定与晚绑定,晚绑定也成为运行时绑定。首先我们讨论早绑定,函数的重载就是早绑定的情况,前面我们提到,函数的重载是编译器通过名字修饰来实现的,也就是C++的编译器将会结合函数名字以及参数为函数产生一个内部名字,_f_int与_f_char就是两个函数,一个函数的参数为int型,另一个参数为char型。于是在调用的时候就能很好的区分。那么为什么C中不能实现函数的重载呢?因为C中没有这种机制。为什么函数的返回值不能作为重载的一个条件呢?因为C++是基于C的,C中编译之后是忽略返回值的,可想,如果我们声明两个函数,只是返回值的不同,到底调用哪一个呢?且用户在调用代码的时候并不知道函数的声明。

    重写

    派生类对基类的方法进行重写,比如下面这个例子:

//基类
class Animal
{
public:
    void play()
	{
		cout<<"Animal::play"<<endl;
	}
	void speak()
	{
	}
	void addSize(int i)
	{

	}

	~Animal()
	{
		cout<<"~Animal"<<endl;
	}
};

//派生类
class Cat: public   Animal
{
public:
	void play()
	{
		cout<<"Cat::play"<<endl;
	}
	void speak()
	{
		cout<<"I am cat"<<endl;
	}
	void addSize(int i)
	{

	}
	~Cat()
	{
		cout<<"~Cat()"<<endl;
	}
private:
	int age;
};

 //显示
void show(Animal& animal)
{
	animal.play();
	animal.speak();
	animal.addSize(2);
}
 int main()
{
	Cat cat;
	show(cat);
	return 0;
}
输出结果为:Animal::play.

是的,这就是向上类型转换,因为编译器并不知道派生类成员的具体类型信息,所以只能调用本地的方法,那么虚函数中是如何实现的呢?对于下面的代码:


//基类
class Animal
{
public:
virtual void play()//虚函数
{
    cout<<"Animal::play"<<endl;
}
virtual void speak(){}
virtual void addSize(int i){}

~Animal()
{
    cout<<"~Animal"<<endl;
}
};

//派生类
class Cat: public virtual  Animal
{
public:
void play()
{
    cout<<"Cat::play"<<endl;
}
void speak()
{
    cout<<"I am cat"<<age<<endl;
}
 void addSize(int i){}
~Cat()
{
     cout<<"~Cat()"<<endl;
}
private:
int age;
};

class Dog:public Animal
{
public:
void play()
{
    cout<<"Dog::play"<<endl;
}
};

Animal * A[]=
{
      new Dog,
      new Cat,
};

//显示
void show(Animal& animal)
{
       animal.play();
       animal.speak();
       animal.addSize(2);
}

int main()
{
     Animal *an;
     A[0]->play(); 
     return 0;
}

其UML图以及对应的内存布局如下图:


                                     UML 图                        

我们发现有一个vfptr指针,是的,在每个有虚函数的类中,或者继承了该类的子类中,编译器均设置了一个虚函数表来识别每一个函数。通过指针移动来选定函数,虚函数的位置与基类一致,于是才有可能进行函数的调用。如果有如下的代码:

  void sizeManage(Animal& animal);
  {animal.addSize(2);}

具体的汇编语言如下:

push   2
push   animal
mov    bx,word ptr[si]
call   word ptr[bx+4]
add    sp, 4
因为每个函数指针为两个字节长度。所以首先是参数的入栈,然后是将this指针入栈,那么就意味着类内存中其首地址为this指针也就是vptr。我们顺便说说类创建后其内存,首先是虚函数指针,这是一个void*指针,指向虚函数表,然后是成员数据的值。这里我们不得不说说类的大小这一概念,对于一个空类,也就是没有成员变量,成员函数的类,其大小不为空,编译器为了区别不同的类,设置了一个“”成员,所占空间大小为1,如果类中有虚函数,则该位置将被vptr指针占用,此时类的大小为4.上面指令的第三部为将si的值保存在CPU的通用寄存器中,也就是this指针,然后移动4个字节获得函数的地址,于是调用函数,最后移回栈指针,清除入栈的参数。这里我们看到在调用虚函数时需要两条以上的汇编语言,这些过程都在构造函数中完成,inline的代价如果太高了,相信C程序员会不再使用虚函数,所以在写代码时需要进行一个权衡。

      仔细看dog的虚函数表,发现最后一项是基类的函数,也就是说如果子类没有重写基类的函数,那么依旧是原来的函数接口。

      这里要说的是A[0]首先是一个Animal类的对象,所以它拥有自己的虚函数表,但是你会发现因为A[0]本身指向的是Dog的地址,于是虚函数表是一样的。在Cat中的age如果没有对它赋值,那么系统将会赋予-842150451,这是系统的默认值。对于局部变量,不同的编译器给出的初始值不一样,但是对于全局变量如果没有给初始值,那么系统将会对其赋值为0.

虚函数与纯虚函数

       谈及虚函数我们不得不说纯虚函数,其实纯虚函数就类似于一个接口,它关注的是是否有该行为,也就是接口的统一性,而继承者们则要实现这个接口。纯虚函数的好处是什么?当然了为了使用多态特性,因为它设定了不能生成具体的对象就避免了一些不合理对象的产生,比如我们这里的Animal。纯虚函数是如何确保不能实例化?纯虚函数的虚函数表与拥有虚函数的类有所不同,虚基类中虚函数表中只为函数保留了一个位置,但是这个位置具体是什么地址并没有说明,也就是说信息是不完整的,因此当你试图创建一个虚基类的对象,编译器将会告诉你这是不对的。说到这里不得不说编译器的功能强大,为每个类设置VTABLE,初始化VPTR,为虚函数插入代码,准去的识别函数中的参数类型与返回类型。还有一个重要的作用,在Thinking in C++这本书中提及一个概念叫做“对象切片”。如果派生类中存在基类中没有的数据成员,而访问该成员的函数是重写了原来的虚函数,那么在调用的时候是不是会发生一些不美好的事情。比如我们这个程序中的Cat与Animal,而show中参数是传值调用的,在调用cat的speak时,其实调用的将是Animal中的函数,为了防止此类事情,也为了安全。如果有虚基类,因为不允许创建虚基类的实例,就不会存在这个问题。

虚函数与构造函数

      构造函数能否是虚函数呢?答案是否定的,因为如果子类的构造函数中对其类中的数据成员进行初始化,如果它是私有成员变量,基类如何能够去控制该变量呢?显然就失去了封装的特性。构造函数的本质是什么,为该类创建对象,只关心自己的而不会关心其它的父类或者是子类。

虚函数与析构函数

      析构函数中是允许虚函数的,而且如果是基类的话,我们是建议这么做的,因为可能在派生类中有在堆中申请空间,那么没有虚机制的配合,基类将不能调用子类的析构函数,就如我们的Animal无法释放内存,最后内存将会泄漏。

 多继承

         多继承是针对于单继承而言的,可以充分的利用不同的接口,使得类中具有更多的功能,使用的是虚继承,所以一定要注意类的虚继承与虚拟函数之间的区别。比如

      但是存在的问题是二义性,于是虚基类就能够很好的解决该问题,设置Person为虚基类,那么对于同样的成员函数和成员变量,该机制能够保障在Programmer_Author仅有一份拷贝。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值