3

我确定我错过了部分代码。

我有以下代码:

#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()?需要进行哪些更改才能调用它?

4

7 回答 7

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恰好指向 a Student(当然在你的代码中它总是这样,但是想象你在一个接受 a 的函数中Record*,或者正在循环一个包含这样的指针的容器,其中一些是真正Student的 s 而另一些不是)。如果是这样,则返回的指针可用于将其作为Student对象访问,并具有任何可用的额外功能/成员(如果某些Record功能以某种方式隐藏,则可能存在限制)。

这种方法通常不受欢迎,因为它引出了一个问题“如果我们需要知道“级别”而其他派生类型甚至没有级别的概念,为什么我们将其Student视为?”。不过,这样的事情有时会发生。添加一个函数也不理想,如果它是唯一的派生类(其中一个)它会具有有意义的值:这就是所谓的胖接口 - 你会在 C++ 中找到一些关于它们的讨论编程语言,如果你有副本。RecordRecordvirtual levelRecordStudent

(sasha.sochka 的回答是第一个提到该dynamic_cast选项的人 - 请投票)

基类应该有虚拟析构函数

根据克里斯的评论,您应该添加Record

virtual ~Record() { }

这可确保在派生对象为 d 时使用基类指针调用派生类的析构函数实现delete(例如,如果delete r;在 底部添加 a main())。(编译器仍将确保随后调用基类析构函数)。

如果你不这样做,这是未定义的行为,充其量你会发现派生类中添加的任何其他数据成员都没有调用它们的数据成员......因为int这是无害的,但是说它std::string可能会泄漏内存,甚至持有一个锁,以便程序稍后挂起。当然,依赖最佳情况的未定义行为并不是一个好主意;-) 但我认为除非您制作基本的析构函数,否则了解绝对不会发生的事情是有用的virtual

推荐的学生小改进

如果您 make level virtualin ,那么为了让作为基类函数实现的类Record的读者更清楚,如果您有适当的支持 C++11 的编译器,则可以使用关键字:Studentlevel()virtualoverride

int level() override
{
    return level_;
}

如果在基类中找不到匹配的(非const) ,这会给你一个编译器错误virtual int level(),因此它可以避免一些偶尔的故障排除。virtual如果您认为关键字具有文档价值,您也可以重复该关键字(对于 C++03 来说尤其好,其中override' 不是一个选项),但它不会产生任何功能差异 -virtual只要它存在(隐式或显式),函数就会保持) 从基类重写虚函数。

于 2013-07-11T07:31:08.983 回答
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()

如果您希望所有Records 都有一个级别,您可以添加纯虚函数而无需实现。但是如果你这样做,你将无法创建类的实例化对象Record(但你可以实例化它的子类):

class Record { ...
   virtual int level() = 0;
}

另一个问题:你应该在Record类中将你的析构函数标记为虚拟的,因为当你将不会调用delete r构造函数时Student

于 2013-07-11T07:32:44.570 回答
2

因为Record该类对调用的函数一无所知level()

您需要level()在基类中创建一个虚函数。

于 2013-07-11T07:31:17.983 回答
2

在基类中创建虚函数,如

virtual int level() = 0;

一旦在基类 Record 中创建了虚函数level(),student 就有必要level()在学生类中拥有它的功能。目前您在 Record 类中没有虚 level()函数,因此您无法level()像当前那样使用基类 Record 访问 Student 类的函数。

于 2013-07-11T07:31:42.277 回答
1

学生实例通过以下方式向上转换为记录:

Record *r = new Student();

同时持有一个Student实例,r代表Record。r->getType() 函数调用通过 C++ 的多态机制绑定到 student::getType。要调用 level() 函数,您可以:

1、Record类添加虚函数level()。
2.将r向下转换为Student类,如下:
Student *r_new = dynamic_cast<Student>(r); 
r_new->level();
于 2013-07-11T08:46:58.850 回答
1

您在 Record 中缺少虚拟方法 level() 声明。附带说明一下,为了防止资源泄漏,您需要在 Record 中定义虚拟析构函数。

class Record
{
...
public:
  virtual ~Record() {}
  virtual int level() = 0;
  virtual int getType() = 0;
};
于 2013-07-11T07:30:51.307 回答
0

编译时,您应该会收到类似于以下内容的错误消息:

error C2039: 'level' : is not a member of 'Record'

您需要在 Record 类中添加它:

virtual int level()=0;
于 2013-07-11T07:31:53.173 回答