构造函数不能为虚函数,因为虚函数依赖虚函数表,构造函数尚未执行时,指向虚函数表的指针还不存在,因此显然构造函数无法成为虚函数。另外子类名字与父类不同,类的构造函数无法继承,更不能通过指针或引用去调用,因此跟虚函数也沾不上边。但是构造函数能否调用虚函数?按理说,构造函数没执行完,虚函数表都没有呢还。
先来一个最基本的代码:
#include <stdio.h>
class A
{
public:
A(){}
~A(){}
virtual void print(){printf("Base\n");}
};
class B:public A
{
public:
B(){}
~B(){}
void print(){printf("B\b");}
};
class C:public A
{
public:
C(){}
~C(){}
void print(){printf("C\b");}
};
int main()
{
A a;
B b;
C c;
A *p=NULL;
int choice;
printf("Input choice=");
scanf("%d", &choice);
switch(choice)
{
case 1:
p=&a;
break;
case 2:
p=&b;
break;
case 3:
p=&c;
break;
default:
p=&a;
}
p->print();
getchar();
return 0;
}
结果:
在构造函数里调用虚函数:
class A
{
public:
A()
{
print();
}
~A(){}
virtual void print(){printf("Base\n");}
};
class B:public A
{
public:
B():A()
{
print();
}
~B(){}
void print(){printf("B\b");}
};
class C:public A
{
public:
void print(){printf("C\b");}
C() :A()
{
print();
}
~C(){}
};
class A
{
public:
A()
{
print();
}
~A(){}
virtual void print(){printf("Base\n");}
};
class B:public A
{
public:
B():A()
{
print();
}
~B(){}
void print(){printf("B\b");}
};
class C:public A
{
public:
void print(){printf("C\b");}
C() :A()
{
print();
}
~C(){}
}; int main()
{
A a;
printf("\n");
B b;
printf("\n");
C c;
return 0;
}
但是有几次只输出了Base,Base,Base。
参考资料:
http://dev.yesky.com/441/2033941.shtml
这里给出的解释是说,构造函数需要调用基类的构造函数,然后再执行本身的构造部分,所以呢,当本身的那部分还没开始的时候,编译器只认为这是一个基类对象。于是,就会调用基类对应的函数了!面试的时候只想着按指针来找,结果杯具了。考官gg说,你回去跑跑看。幸好后来让我写对象模型写对了。
基类构造期间,虚函数从来不会向下匹配(go down)到派生类。取而代之的是,那个对象的行为就好像它的类型是基类。非正式地讲,基类构造期间,虚函数禁止。 这个表面上看起来匪夷所思的行为存在一个很好的理由。因为基类的构造函数在派生类构造函数之前执行,当基类构造函数运行时,派生类数据成员还没有被初始化。如果基类构造期间调用的虚函数向下匹配(go down)到派生类,派生类的函数理所当然会涉及到本地数据成员,但是那些数据成员还没有被初始化。这就会为未定义行为和悔之晚矣的调试噩梦开了一张通行证。调用涉及到一个对象还没有被初始化的部分自然是危险的,所以 C++ 告诉你此路不通。
在实际上还有比这更多的更深层次的原理。在派生类对象的基类构造期间,对象的类型是那个基类的。不仅虚函数会解析到基类,而且语言中用到运行时类型信息(runtime type information)的配件(例如,dynamic_cast和 typeid),也会将对象视为基类类型。在我们的例子中,当 Transaction 构造函数运行初始化 BuyTransaction 对象的基类部分时,对象的类型是 Transaction。C++ 的每一个配件将以如下眼光来看待它,并对它产生这样的感觉:对象的 BuyTransaction 特有的部分还没有被初始化,所以安全的对待它们的方法就是视若无睹。在派生类构造函数运行之前,一个对象不会成为一个派生类对象。
同样的原因也适用于析构过程。一旦派生类析构函数运行,这个对象的派生类数据成员就被视为未定义的值,所以 C++ 就将它们视为不再存在。在进入基类析构函数时,对象就成为一个基类对象,C++ 的所有配件——虚函数,dynamic_casts 等——都如此看待它。