最近我试图解决C ++继承和多态,但我有一些问题对我没用。 我在单独的文件中有2个头文件和一个带有实现的cpp文件。我的代码的简短摘要如下:
#ifndef MANDEL_H_
#define MANDEL_H_
class Mandel{
public:
virtual void compute("various arguments") = 0;
//dummy destructor, I must have one or compile is sad and I dunno why
virtual ~Mandel();
private:
virtual int compute_point("various arguments") = 0;
};
#endif
这是我的“祖父”标题“Mandel.h”。现在转移到“父亲”标题。下一个标题指定了一些特定于Mandel的白色和黑色实现的变量,称为“Black_White_Mandel.h”:
#ifndef BLACK_WHITE_MANDEL_H_
#define BLACK_WHITE_MANDEL_H_
#include "Mandel.h"
class Black_White_Mandel: public Mandel {
protected:
int max_iterations; //a specific variable of this Black_White Version
};
#endif
现在,在一个名为White_Black_Mandel_Imp1.cpp的单独文件中执行Black_White_Mandel标头:
#include <iostream>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include "Mandel.h"
#include "Black_White_Mandel.h"
using namespace std;
//constructor
Black_White_Mandel::Black_White_Mandel(){
max_iterations = 255;
}
//destructor
Black_White_Mandel::~Black_White_Mandel(){}
int Black_White_Mandel::compute_point("various arguments") {
//code and stuff
return 0;
}
void Black_White_Mandel::compute("various arguments") {
//code and stuff
}
因此,Mandel.h必须实现2个函数,因为它们是虚拟的并且“= 0”。在White_Black_Mandel_Imp1.cpp中,当我实现编译器疯狂的那些函数时。它说函数没有在White_Black_Mandel.h中定义,尽管如此,它们在Mandel.h中定义。因此,通过继承,White_Black_Mandel_Imp1.cpp应该知道它有从Mandel.h实现这些函数的义务。
我不明白,我的一个朋友说我的White_Black_Mandel.h文件应该是Mandel.h的精确副本,但还有一些额外的东西,但这对我来说真的很愚蠢,这没有任何意义。
我做错了什么?
答案 0 :(得分:7)
虽然您的祖先类中有2个纯虚方法,但这并不意味着它们的原型可以在子类中使用。
您必须在您的子类中声明原型:
class Black_White_Mandel: public Mandel {
public:
virtual void compute("various arguments")
protected:
int max_iterations; //a specific variable of this Black_White Version
private:
virtual int compute_point("various arguments");
};
virtual
关键字是可选的,但知道该方法确实是虚拟的很有用。你没有被迫在这个特定的子类中实现它们,你可以避免指定任何东西,但你仍然有两个必须实现的纯虚方法,所以你将无法实例化这个子类的任何对象(你将有无论如何在层次结构树中实现它们。)
虚拟析构函数是必需的,否则在类似的情况下:
Base *derived = new Derived();
delete derived;
编译器无法调用正确的析构函数。
答案 1 :(得分:4)
将compute
和compute_point
的原型添加到Black_White_Mandel
。
在某些情况下,您从具有纯虚函数的基类继承并且未实现所有这些函数:您的派生类将保持抽象,并且需要继承自另一个类等,直到所有纯虚函数实施。
E.g。
class A {
virtual void foo() = 0;
virtual void bar() = 0;
};
class B : public A {
virtual void foo() {};
};
class C : public B {
virtual void bar() {};
};
class D : public A {
virtual void foo() {};
virtual void bar() {};
};
上面唯一可实例化的类是C
和D
。
答案 2 :(得分:3)
White_Black_Mandel_Imp1.cpp应该知道它有义务
它不会也不应该。它也可以决定是一个抽象类,在这种情况下它可以单独留下这些函数。
答案 3 :(得分:3)
必须在实现类中提供声明的原因是,派生类使用不同的返回类型覆盖虚函数是合法的,只要新的返回类型为< em> covariant 与原始返回类型。例如,您的基类可以返回BaseReturnedObject&
,但您的派生类可以选择返回DerivedReturnObject&
。如果派生类中没有声明,编译器就不知道方法的返回类型是什么。它不能假设它与基数相同,因此编译器需要原型。
有关使用协变返回类型的覆盖规则,请参阅this question。
答案 4 :(得分:0)
想象一下,你的“祖父”课程中有4种方法,所有方法都是纯虚拟的。现在,您打算在“父”类中实现两个,在“子”类中实现两个。顺便说一下,这个特殊的术语并不理想,但我坚持认为它是因为你开始使用它。
当您定义类时(在class whatever {
和};
之间的.h文件中,您告诉编译器您打算覆盖基类(术语中的祖父类)中的哪些函数。您可以将实现放在标题中,也可以将其放在.cpp文件中。但是您必须指定要覆盖的4个函数(如果有)中的哪一个。
如果你没有在类定义中提到一个特定的继承函数(正如你所说的那样在头文件中),那么当需要编译实现(.cpp文件)时,编译器会说 < em>“什么?你从来没有告诉我你打算覆盖这个功能!” 你的回答似乎是 “老兄,如果我不覆盖它,它是纯虚拟的我无法实例化对象!“ ,但你知道吗?编译器对此并不在意。它不知道你是否想要创建一个可实例化的对象,如果你打算进一步继承,或者甚至真的认为该函数是基类中的纯虚函数。它只知道是,它正在编译一个实现文件,它遇到了一个你在类定义中没有告诉它的函数。
因此,当您编写class something {
... };
类定义时,请确保列出您要为该类实现的所有函数,无论您是否继承它们。不要列出您继承的功能,并且不会实现。如果你继承的是一个接口(所有的功能都是纯虚拟的)那么是的,如果你要全部实现它们,你必须在课堂上列出它们。那是因为语言没有为这种情况留出特殊情况。这完全不等同于“我的标题必须是祖父类头文件的精确副本”,我认为如果你在祖父类中有更多的功能,并且没有实现它们,你会更清楚地看到它所有在儿童班。