码迷,mamicode.com
首页 > 其他好文 > 详细

【核心基础】虚函数

时间:2015-04-10 22:31:12      阅读:158      评论:0      收藏:0      [点我收藏+]

标签:

      本节研究虚函数的相关问题;


虚函数表

无继承

代码片段

class Animal {
public:
  Animal(int age) :
   _age(age)
  {
  }
  
  virtual void f()
  {
    cout << "Animal::f " << _age << endl;
  }
  
  virtual void g()
  {
    cout << "Animal::g " << _age << endl;
  }
private:
  int _age;
};
  
int main(void)
{
  Animal a(10);
  cout << "Animal size: "<< sizeof(a) << endl;

  cout << "_age address: "<< ((int*)&a+1) << " value: " << *((int*)&a+1)<< endl;
  cout << "virtual fun address: "<< (int*)&a << endl;
  cout << "virtual fun[1] address: "<< (int*)*(int*)&a << endl;
  
  cout << "virtual fun[2] address: "<< ((int*)*(int*)&a+1) << endl;
  
  typedef void (*Fun)(Animal*);
  Fun pf = (Fun)*((int*)(*(int*)&a));
  pf(&a);
  
  typedef void (*Fun2)();
  Fun2 pg = (Fun2)*((int*)(*(int*)&a)+1);
  pg();
}

输出如下:

Animal size: 8
_age address: 0xbf8347d4 value: 10
virtual fun address: 0xbf8347d0
virtual fun[1] address: 0x8048c18
virtual fun[2] address: 0x8048c1c
Animal::f 10
Animal::g 134515314

说明几点:

(1)在GCC中(vptr放在对象头部),因此Animal的结构体大小为8,第一个字节存放的虚函数指针(vptr),第二个字节存放的是_age数据;

(2)所有的Animal的对象的vptr都是一样的,vptr为Animal虚函数表的指针,虚函数表存放各个虚函数的函数地址;

(3)在void (*Fun)(Animal*);中我们在pf函数参数中传入a地址,通过pf函数指针也是可以进行Animal成员函数的;

虚函数示意图如下

技术分享


单继承(无virtual覆盖)

代码片段

class Animal {
public:
  Animal(int age) :
   _age(age)
  {
  }
  
  virtual void f()
  {
    cout << "Animal::f " << _age << endl;
  }
  
  virtual void g()
  {
    cout << "Animal::g " << _age << endl;
  }
private:
  int _age;
};

class Dog : public Animal {
public:
  Dog(int age, int id) :
   Animal(age), _id(id)
  {
  }
  
private:
  int _id;
};

Dog d(10, 20);
Animal a(10);
  
int main(void)
{
  cout << "Dog ---------------- " << endl;
  cout << "Dog size: "<< sizeof(d) << endl;

  cout << "_age address: "<< ((int*)&d+1) << " value: " << *((int*)&d+1)<< endl;
  cout << "_id address: "<< ((int*)&d+2) << " value: " << *((int*)&d+2)<< endl;
  cout << "virtual fun address: "<< (int*)&d << endl;
  cout << "virtual fun[1] address: "<< (int*)*(int*)&d << endl;
  
  cout << "virtual fun[2] address: "<< ((int*)*(int*)&d+1) << endl;
  
  typedef void (*Fun)(Animal*);
  Fun pf = (Fun)*((int*)(*(int*)&d));
  pf(&d);
  
  typedef void (*Fun2)();
  Fun2 pg = (Fun2)*((int*)(*(int*)&d)+1);
  pg();
  
  
  cout << "Aniaml ---------------- " << endl;
  cout << "virtual fun address: "<< (int*)&a << endl;
  cout << "virtual fun[1] address: "<< (int*)*(int*)&a << endl;
  
  cout << "virtual fun[2] address: "<< ((int*)*(int*)&a+1) << endl;
  
  Animal c(10);
  
  cout << "virtual fun address: "<< (int*)&c << endl;
  cout << "virtual fun[1] address: "<< (int*)*(int*)&c << endl;
  cout << "virtual fun[2] address: "<< ((int*)*(int*)&c+1) << endl;
}
输出如下:

Dog ---------------- 
Dog size: 12
_age address: 0x804a338 value: 10
_id address: 0x804a33c value: 20
virtual fun address: 0x804a334
virtual fun[1] address: 0x8048f00
virtual fun[2] address: 0x8048f04
Animal::f 10
Animal::g 14073600
Aniaml ---------------- 
virtual fun address: 0x804a340
virtual fun[1] address: 0x8048f10
virtual fun[2] address: 0x8048f14
virtual fun address: 0xbfed4890
virtual fun[1] address: 0x8048f10
virtual fun[2] address: 0x8048f14
地址示意图如下:

技术分享

单继承(有virtual覆盖)

代码片段

class Animal {
public:
  Animal(int age) :
   _age(age)
  {
  }
  
  virtual void f()
  {
    cout << "Animal::f " << _age << endl;
  }
  
  virtual void g()
  {
    cout << "Animal::g " << _age << endl;
  }
private:
  int _age;
};

class Dog : public Animal {
public:
  Dog(int age, int id) :
   Animal(age), _id(id)
  {
  }
  
  void f()
  {
    cout << "Dog::f hello" << endl;
  }
  
  virtual void h()
  {
    cout << "Dog::h hello" << endl;
  }
private:
  int _id;
};

地址示意图如下:

技术分享


虚函数与重载

代码片段(提示:此代码段有陷阱)

#include <iostream>
using namespace std;

class Point
{
public:
  Point(int x = 1, int y = 3) :
      _x(x), _y(y)
  {
  }

  virtual void print()
  {
    cout << "Point::print" << endl;
  }

private:
  int _x, _y;
};

class Point3D: public Point
{
public:
  void print() const
  {
    cout << "Point3D::print" << endl;
  }

private:
  int _z;
};

void print(Point a, Point* b, Point& c)
{
  a.print();
  (*b).print();
  b->print();
  c.print();
}

int main(void)
{
  Point3D point;
  print(point, &point, point);

  return 0;
}

说明几点:

(1)void print(Point a, Point* b, Point& c)函数中的,4个输出均为Point::print;

(2)请注意Point::print() 表示传入的this为一个指向常量的指针,this本身是一个常量指针,因此传入函数的是一个指向常量的常量指针;而对于print中a,b,c并不发生virtual机制,对于a,发生切割,表现为ADT行为,一定调用的是Point::print() 函数,对于b,c指向地址空间只是Point3D的中Point部分指针的不同,主要表现为所寻址的object的不同,只能解释Point所覆盖范围的部分,由于Point::print()和Point32::print()  const不是同一函数,因此不会发生virtual调用,只会Point::print() const;

(3)注意如果将将print函数修改为void print(Point a, const Point* b, const Point& c),此例子是无法编译通过的,因为b和c调用所传入的this指针均为const * Point const this;而对于Point::print()不为const成员函数,故将发生将指向常量的指针转换成普通指针的编译错误;

(4)修改print函数如下,此时,p输出的内容为Point3D::print;主要原因此时p所指向的地址空间为整个Point3D的地址空间,是一个显式的向下类型转换,无论如何都会变成一个Point3D类型指针的;请注意p转换成const Point3D*类型,否则不能编译通过,主要是因为Point3D提供的print函数为const函数;

void print(Point a, Point* b, Point& c)
{
  a.print();
  (*b).print();
  b->print();
  c.print();
  
  const Point3D* p = static_cast<const Point3D *>(b);
	p->print();
}

(5)修改main函数如下,此时并不会出现段错误,原因是在p->print()在调用Point3D中的void print() const将被解释成xx_print_xx(const Point3D * const p),而由于在此函数xx_print_xx中p并没有访问相关的成员变量,如果访问将会出现段错误;

int main(void)
{
  Point3D* p = NULL;
  p->print();

  return 0;
}

虚函数与默认实参

代码片段如下

#include <iostream>
using namespace std;

class A {
public:
   virtual int X(int x = 1) {
     return x;
   }
};


class B : public A {
public:
  int X(int x = 5) {
     cout << x << endl;
     return x;
  }
  
};

int main(void) {
  B b;
  cout << b.X() << endl;
  
  cout << "------------------------\n";
  A* a = &b;
  cout << a->X() << endl;
  return 0;
}
说明几点:

(1)对于B b并不会发生多态调用,因为C++中只有通过指针和引用才能进行动态调用,就因此b.X()就编译时已经确定好了,如果B中未重新定义X(int x=5),那么根据域的查找原则,可以调用父类的X();

(2)对于a->X()其实也是调用的B中的X(),X()中cout会执行,但是虚函数的默认实参是由本次调用的静态类型A决定的,也就是x会等于1;

(3)假设将A类去除掉virtual,修改为如下:

class A {
public:
   int X(int x = 1) {
     return x;
   }
};
(3.1)那么a->X()将会调用父类的X(),而不会发生动态调用;因为对非虚函数的调用是编译时决定的;名字查找,首先确定静态类型,找到与静态类型对应的类(或父类)中找到,找到以后,判断调用是否是虚函数,若是函数,编译器产生的代码将在运行时决定该虚函数的哪个版本否则将是一次常规的函数调用;而本例对应后一种情况,不会发生虚函数调用;

虚函数与构造(析构)函数

代码片段如下

#include <iostream>
using namespace std;

class A
{
public:
  A()
  {
    cout << "----------\n";
    X();
    cout << "A ctor\n";
  }

  virtual void X()
  {
    cout << "A::X()" << endl;
  }

  virtual ~A()
  {
    cout << "----------\n";
    X();
    cout << "A dtor\n";
  }
};

class B : public A
{
public:
  void X()
  {
    cout << "B::X()" << endl;
  }

  ~B()
  {
    cout << "----------\n";
    X();
    cout << "B dtor\n";
  }

};

int main(void)
{
  A* a = new B;
  delete a;
  return 0;
}
说明几点:

(1)A的析构函数,应该声明为virtual,因为delete a时将会执行A的析构函数,如果A的析构函数不是virtual,将不会发生动态调用,使得B对象处于半死状态;所以A的析构函数应该声明为析构函数,这样首先调用B的析构函数,继而调用A的析构函数

(2)在A的构造函数和析构函数调用虚函数,并不会发生动态调用;因为执行到A的构造函数时,B的除A的其他部分还未初始化;而执行到A的析构函数时,B除A的其他部分已经销毁掉了;因此执行父类中虚函数时,整个对象处于未完成状态;而编译器将会将不会进行动态调用,而是执行所属类型的对应版本;


【核心基础】虚函数

标签:

原文地址:http://blog.csdn.net/skyuppour/article/details/44983823

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!