首先让我向您展示简单的C ++代码示例。
#include <iostream>
using namespace std;
class Person{
string m_name;
public:
virtual void saySomething(){
cout << "I am a person.";
}
};
class Student:public Person{
public:
void saySomething(){
cout << "I am a student." << endl;
}
};
class CSStudent:public Student{
public:
void saySomething(){
cout << "I am a CS Student." << endl;
}
};
int main(){
Person* p = new Person();
Student* s = new Student();
CSStudent* c = new CSStudent();
((Student*)c)->saySomething();
return 0;
}
在我的例子中,Student是Person的派生类。 此外,CSStudent是Student的派生类。 我知道虚拟关键字可以确定它是否是派生类。
问题1。
((学生*)C) - &GT; saySomething();
为什么我会得到“我是CS学生。”? 我期待“我是学生。”因为我只为Person指定虚拟关键字。
问题2。
我看到一个将虚拟关键字放在基本案例中的示例。 此外,我看到了一个将虚拟关键字放入所有基本案例和派生类的示例。这两者有什么区别?
问题3。
虚拟功能在运行时确定。 那么非虚函数呢?是在编译时确定的吗?
答案 0 :(得分:3)
对于第一个问题,即使你将c
转换为基于相同基类的另一个类,它仍然是原始类(即CSStudent
)。虚拟函数(几乎总是)通过跳转表实现。因此c
对象有一个表,其中包含c
对象中实际函数的地址。此表不会更改,因为您重新转换了类型。
对于第二个问题,没有区别。如果在基类中将成员函数标记为virtual
,则它在所有子类中也将是虚拟的。只是有些人更喜欢在子类virtual
中标记函数。它实际上可能是好的,因为那样你就不必检查基类来查看哪些函数是虚函数而哪些函数不是虚函数。
对于第三个问题。是的,使用如上所述的表计算运行时的虚函数,而在编译时确定非虚函数。
答案 1 :(得分:2)
问题1。
((学生*)C) - &GT; saySomething();
为什么我会得到“我是CS学生。”?我期待“我是学生。”因为我只为Person指定虚拟关键字。
在C ++中,如果基类指定函数是virtual
,那么对于具有相同签名的成员函数,在任何派生类中都不需要virtual
关键字。如果你愿意,你可以包括它(这通常是很好的做法),但它是可选的。
问题2。
我在网页上看到一些示例,其中提到虚拟关键字仅基于案例。另外,我看到了一些示例,它提到了所有基本案例和派生类的虚拟关键字。这两者有什么区别?
如上所述:没有区别。如果基类说具有特定签名的方法是虚拟的,那么这也适用于所有派生类。
问题3。
虚拟功能在运行时确定。那么非虚函数呢?是在编译时确定的吗?
是。如果您的saySomething()
函数是非虚拟的,则调用将在编译时解析,而转换为Student*
意味着您将获得Student
版本的{{1} }}
答案 2 :(得分:1)
为什么我得到&#34;我是CS学生。&#34;?我期待&#34;我是学生。&#34;以来 我只为Person指定虚拟关键字。
在C ++中,如果在基类中指定virtual关键字,那么该方法也将在任何子类中自动变为虚拟。 (我不会试图推测为什么C ++以这种方式工作,但确实如此)
我看到一个将虚拟关键字放在基本案例中的示例。此外,我看到了一个将虚拟关键字放入所有基本案例和派生类的示例。这两者之间的区别是什么?
功能上没有区别。我认为风格上的后者在自我记录方面要好一些。
虚拟功能在运行时确定。怎么样非虚拟的 功能?是在编译时确定的吗?
正确。特别是,为非虚方法调用的方法实现将仅通过调用它的指针类型来确定,而不是通过动态查找适当的子类方法。