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;
}
改成:
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的~
每个有函数声明为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的方法,用基类指针来调用,则通不过编译.
即便如此,楼主的强制转换也没有意义,因为你的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中的函数。
-------------------------------------------------------
对于非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;
}
改成:
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的~
每个有函数声明为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的方法,用基类指针来调用,则通不过编译.
即便如此,楼主的强制转换也没有意义,因为你的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中的函数。
-------------------------------------------------------
对于非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