我确定错过了部分代码。
我有以下代码:
#include <iostream>
using namespace std;
class Record
{
private:
int age;
string name;
public:
virtual int getType()=0;
};
class Student: public Record
{
private:
int level_;
public:
Student()
{
level_=1;
};
~Student() {};
int getType()
{
return 1;
}
int level()
{
return level_;
}
};
int main (int argc, char ** argv)
{
Record *r = new Student();
cout <<"tuype " << r->getType();
cout <<"Class " << r->level();
}
问题是:为什么我无法调用r->level()
?调用它需要进行哪些更改?
答案 0 :(得分:8)
更改记录以使level()
虚拟
您写道:
Record *r = new Student();
在该行之后,编译器认为r
是指向Record
或某个Record
派生类(它是)的指针,但它只是知道为Record
指定的接口。 virtual
中没有level()
Record
个功能,因此您无法通过Student
界面访问Record
级功能。只需将此功能添加到Record
,您就可以了:
virtual int level() { return 0; } // Student may override implementation
或
virtual int level() = 0; // Student MUST override implementation
另一种方法:检查记录*是否针对学生
我上面说...
virtual
中没有level()
Record
个功能,因此您无法通过Student
界面访问Record
级功能。
...并展示如何添加到Record
界面,但另一种方法是再次访问Student
界面,如:
if (Student* p = dynamic_cast<Student*>(r))
std::cout << "Level " << p->level() << '\n';
第一行检查Record* r
是否恰好指向Student
(当然在你的代码中总是如此,但想象你在一个接受Record*
的函数中,或者循环遍历这些指针的容器,其中一些是真正的Student
而其他的不是。如果是这样,返回的指针可用于以Student
对象的形式访问它,并且可以使用任何额外的功能/成员(如果某些Record
功能以某种方式隐藏,则可能存在限制)。 / p>
这种方法通常不受欢迎,因为它引出了问题&#34;如果我们需要知道&#34;等级&#34;为什么我们将Student
视为Record
?和其他Record
- 派生类型甚至没有水平的概念?&#34;。不过,有时会发生类似的事情。将virtual
level
函数添加到Record
并不理想,如果Student
是(唯一的)唯一派生类,它将具有有意义的价值:那就是所谓的胖接口 - 如果你有副本,你可以在C ++编程语言中找到它们的一些讨论。
(sasha.sochka的回答是第一个提到dynamic_cast
选项 - 请upvote)
基类应具有虚拟析构函数
根据克里斯的评论,您应该添加到Record
:
virtual ~Record() { }
这确保了派生对象使用基类指针delete
时调用派生类的析构函数实现(例如,如果在底部添加delete r;
main()
)。 (编译器仍然会确保之后调用基类析构函数。)
未定义的行为如果您不这样做,并且您最多会发现派生类中添加的任何其他数据成员都没有将其数据成员调用...对于int
无害的std::string
,但是对于virtual
来说,它可能会泄漏内存,甚至可能会锁定以便程序稍后挂起。当然,依靠最佳案例的未定义行为并不是一个好主意;-)但我认为除非你制作基础析构函数,否则理解什么是绝对不会有用的{{h} {1}}。
推荐学生的小改进
如果您在level
中提出virtual
Record
,那么Student
类的读者会更清楚level()
是virtual
的实现{1}}来自基类的函数,如果你有一个适当的支持C ++ 11的编译器,你可以使用override
关键字:
int level() override
{
return level_;
}
如果它无法在基类中找到匹配的(非const
)virtual int level()
,则会出现编译错误,因此可以避免一些偶然的故障排除。如果你觉得它有文档价值,你也可以重复virtual
关键字(对于override
不能选择的C ++ 03特别好),但它不能做任何功能差异 - 只要它(隐式或显式地)覆盖基类中的虚函数,函数就会保持virtual
。
答案 1 :(得分:3)
您无法调用r->level()
,因为您正在尝试调用类Record中不存在的函数。在您的具体情况下,r
指向的数据不仅是Record
而且是Student
,因此您可以选择:
Student *r = new Student();
cout <<"tuype " << r->getType();
cout <<"Class " << r->level()
或
Record *r = new Student();
cout <<"tuype " << r->getType();
cout <<"Class " << dynamic_cast<Student*>(r)->level()
如果您希望所有Record
都具有级别,则可以添加纯虚函数而无需实现。但是,如果你这样做,你将无法创建类Record
的即时对象(但你可以实现它的子类):
class Record { ...
virtual int level() = 0;
}
另一个问题:您应该将Record
类中的析构函数标记为虚拟,因为何时delete r
Student
的构造函数将不会被调用
答案 2 :(得分:2)
因为Record
类对名为level()
您需要在基类中创建虚拟level()
函数。
答案 3 :(得分:2)
在基类中创建虚拟函数,如
virtual int level() = 0;
在基类Record中创建虚函数level()
后,学生必须在学生班中使用它的函数level()
。目前,在Record类中没有虚拟level()
函数,因此您无法使用基类Record来访问Student类的level()
函数,就像您当前所做的那样。
答案 4 :(得分:1)
您在Record中缺少虚拟方法level()声明。另外,为防止资源泄漏,您需要在Record中定义虚拟析构函数。
class Record
{
...
public:
virtual ~Record() {}
virtual int level() = 0;
virtual int getType() = 0;
};
答案 5 :(得分:1)
学生实例被上传到记录:
Record *r = new Student();
在持有Student实例时,代表Record作为Record。 r-&gt; getType()函数调用绑定到student :: getType,带有C ++的Polymorphism机制。 要调用level()函数,您可以:
1. Add a virtual function level() to Record class. 2. downcasting r to Student class, as follows:
Student *r_new = dynamic_cast<Student>(r);
r_new->level();
答案 6 :(得分:0)
编译时,您应该收到类似于以下内容的错误消息:
error C2039: 'level' : is not a member of 'Record'
您需要在Record类中添加它:
virtual int level()=0;