标签:
本节研究虚函数的相关问题;
代码片段
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成员函数的;
虚函数示意图如下
代码片段
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地址示意图如下:
代码片段
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