关于虚函数的问题

时间:2021-12-28 18:38:26
在如下代码中:
class A
{
public:
  void fn(void){ cout << "A\n" ; }
};

class B:public A
{
public:
  void fn(void){ cout << "B\n" ;}
};

void main()
{
  A* p = (A*)new B;
  p -> fn();
  delete p;
}
运行结果为A。
如果在A类的fn函数前加上virtual关键字,运行结果为B,请问这是为什么?

12 个解决方案

#1


最起码基类(A)中的函数不加virtual,那就不是虚函数了~ 
改成:
class A
{
public:
virtual void fn(void){ cout << "A\n" ; }
};

class B:public A
{
public:
void fn(void){ cout << "B\n" ;}
};

void main()
{
A* p = new B; // 还有这里没必要 强制转换~
p -> fn();
delete p;
}

#2


如果问具体是为什么:这里简单的解释一下〉

每个有函数声明为virtual的class,都会有一个虚函数表(vtable),vtable中维护着各个虚函数的指针,平且class中 编译器会自动 附加一个 vptr的成员 来指向 这个 vtable,派生类会自动复制基类的一份vtable,但当 派生类 中改写了基类虚函数时,派生类的vtable就会被更新了,使其中的虚函数指针指向新的 派生类中的虚函数地址,调用虚函数时编译器会先去相应的类对象
中找到vptr,然后找到vtable中相应的虚函数的指针,接着去调用它

以上面为例:A* p = new B;当通过指针调用虚函数时;p -> fn():由于p指向的是class
B 的对象, 编译器将通过 B类的vptr找到 B类的vtable中 的 fn的指针,而由于fn是改写了
基类A的虚函数,所以 这个指针实际是指向 B中的 fn的~

#3


如果不使用virtual 则函数是在程序运行之前完成的捆绑,因此编译器只知首类A的地址时并不知道要调用的正确的函数。而使用了virtual 则采用的是晚绑定,是在程序运行时根据对象的类型进行绑定,只有这样的才能正确使用函数。也才能真正的实现多态性。

#4


楼主的程序应该编译通不过的,没有virtual的类不允许被继承.
即便如此,楼主的强制转换也没有意义,因为你的fn方法没有多态,而基类指针指向派生类对象的时候,本身就只能调用基类中出现过的东西,cout<<B的fn在A类中没有出现过,调用不到. 如果你在B类中新增一个叫fm的方法,用基类指针来调用,则通不过编译.

#5


楼主的程序应该编译通不过的,没有virtual的类不允许被继承.
=========================
这个从何而来的~

#6


运行结果为A。
-------------------------------------------------------
对于非virtual函数,编译器根据指针的静态类型(就是声明时的类型)决定调用哪一个函数;p的静态类型为A*,所以调用class A中的函数。


如果在A类的fn函数前加上virtual关键字,运行结果为B
-------------------------------------------------------
对于virtual函数,运行时根据对象的实际类型决定调用哪一个函数;p所指向的对象的实际类型是B,所以调用class B中的函数。

#7


对于初学者不用了解这么多原理。
只要记住,对于有虚函数的类的指针,
通过指针访问成员函数时,对于虚函数(存在一个虚表),指针访问的是其指向对象的成员函数。
对于非虚函数,指针访问的是其定义类型的成员函数。

精简一下讲,就是对虚函数的访问由其所指对象的类型决定,而对于非虚函数的访问,由指针本身的类型决定。

#8


车神同志可以看出是看过不少书的,只可惜都是浅尝辄止,这样学的似是而非出来乱讲还真能害人啊~~

#9


另外补充一点,基类指针只能访问基类中才有的函数,对于派生类新添加成员是访问不了的。

#10


楼主的程序在vc6.0中可通过编译,运行结果为A  大家评论时不要先入为主嘛

#11


呵呵,这里认为楼主代码不可编译的只有一位而以吧。

#12


当C++编译器在编译的时候,如果发现A类的fn方法是虚函数,这个时候C++就会采用迟绑定技术(late binding)的技术,在运行时,依据对象的类型(在程序中我们传递的是B类对象的地址)来确认调用哪一个函数,这种能力叫做C++的多态性,如果我们不把A类的fn方法设置为virtual,那么此时就是一个早期绑定,那么在编译的时候就确定了该调用哪个函数.总之就是子类有的就调用子类的,子类没有的就调用父类的,都没有就编译出错哈 :)P

#1


最起码基类(A)中的函数不加virtual,那就不是虚函数了~ 
改成:
class A
{
public:
virtual void fn(void){ cout << "A\n" ; }
};

class B:public A
{
public:
void fn(void){ cout << "B\n" ;}
};

void main()
{
A* p = new B; // 还有这里没必要 强制转换~
p -> fn();
delete p;
}

#2


如果问具体是为什么:这里简单的解释一下〉

每个有函数声明为virtual的class,都会有一个虚函数表(vtable),vtable中维护着各个虚函数的指针,平且class中 编译器会自动 附加一个 vptr的成员 来指向 这个 vtable,派生类会自动复制基类的一份vtable,但当 派生类 中改写了基类虚函数时,派生类的vtable就会被更新了,使其中的虚函数指针指向新的 派生类中的虚函数地址,调用虚函数时编译器会先去相应的类对象
中找到vptr,然后找到vtable中相应的虚函数的指针,接着去调用它

以上面为例:A* p = new B;当通过指针调用虚函数时;p -> fn():由于p指向的是class
B 的对象, 编译器将通过 B类的vptr找到 B类的vtable中 的 fn的指针,而由于fn是改写了
基类A的虚函数,所以 这个指针实际是指向 B中的 fn的~

#3


如果不使用virtual 则函数是在程序运行之前完成的捆绑,因此编译器只知首类A的地址时并不知道要调用的正确的函数。而使用了virtual 则采用的是晚绑定,是在程序运行时根据对象的类型进行绑定,只有这样的才能正确使用函数。也才能真正的实现多态性。

#4


楼主的程序应该编译通不过的,没有virtual的类不允许被继承.
即便如此,楼主的强制转换也没有意义,因为你的fn方法没有多态,而基类指针指向派生类对象的时候,本身就只能调用基类中出现过的东西,cout<<B的fn在A类中没有出现过,调用不到. 如果你在B类中新增一个叫fm的方法,用基类指针来调用,则通不过编译.

#5


楼主的程序应该编译通不过的,没有virtual的类不允许被继承.
=========================
这个从何而来的~

#6


运行结果为A。
-------------------------------------------------------
对于非virtual函数,编译器根据指针的静态类型(就是声明时的类型)决定调用哪一个函数;p的静态类型为A*,所以调用class A中的函数。


如果在A类的fn函数前加上virtual关键字,运行结果为B
-------------------------------------------------------
对于virtual函数,运行时根据对象的实际类型决定调用哪一个函数;p所指向的对象的实际类型是B,所以调用class B中的函数。

#7


对于初学者不用了解这么多原理。
只要记住,对于有虚函数的类的指针,
通过指针访问成员函数时,对于虚函数(存在一个虚表),指针访问的是其指向对象的成员函数。
对于非虚函数,指针访问的是其定义类型的成员函数。

精简一下讲,就是对虚函数的访问由其所指对象的类型决定,而对于非虚函数的访问,由指针本身的类型决定。

#8


车神同志可以看出是看过不少书的,只可惜都是浅尝辄止,这样学的似是而非出来乱讲还真能害人啊~~

#9


另外补充一点,基类指针只能访问基类中才有的函数,对于派生类新添加成员是访问不了的。

#10


楼主的程序在vc6.0中可通过编译,运行结果为A  大家评论时不要先入为主嘛

#11


呵呵,这里认为楼主代码不可编译的只有一位而以吧。

#12


当C++编译器在编译的时候,如果发现A类的fn方法是虚函数,这个时候C++就会采用迟绑定技术(late binding)的技术,在运行时,依据对象的类型(在程序中我们传递的是B类对象的地址)来确认调用哪一个函数,这种能力叫做C++的多态性,如果我们不把A类的fn方法设置为virtual,那么此时就是一个早期绑定,那么在编译的时候就确定了该调用哪个函数.总之就是子类有的就调用子类的,子类没有的就调用父类的,都没有就编译出错哈 :)P