C++中使用#include和预编译指令来实现分文件继承

28
我是新来的Stack Overflow用户,正在自学C++,但我仍然是个初学者。在完成了我使用的一本书的大部分内容之后(可能被认为已经过时和/或不是很好的书),我决定通过尝试自己编写代码来加强一些概念,只在需要时参考书籍,但我似乎卡住了。我试图解决的概念是继承、多态、抽象数据类型(ADT)以及将我的类的代码分离到头文件(.h)和C++文件(.cpp)中。提前对这堵长城说声抱歉,我只是想在必要的地方清晰明确。
所以,我的目标是创建简单的形状类,其中适用的类会继承其他类。我有四个类:myPoly、myRectangle、myTriangle和mySquare。如果我正确理解了这个概念,myPoly应该是一个ADT,因为其中一个方法是纯虚函数(area方法),因为创建myPoly对象不是我希望我的类的用户做的事情。myRectangle和myTriangle都派生自myPoly,而mySquare又从myRectangle派生出来。我还包括了计划测试我的类的测试程序。我正在使用Code::Blocks 10.05,并在构建test.cpp程序时不断收到以下错误:
undefined reference to 'myPoly::myPoly()'

我遇到了42个类似的错误,都是关于myPoly类的方法。当我试图构建myRectangle和myTriangle的.cpp文件时,也会出现这种情况。通过我对这个小项目遇到的问题进行的研究,我感觉我的包含保护或者#include语句有问题,某些内容没有被正确地包含或者被重复包含了很多次。起初,我将myPoly的.cpp文件提供给了myRectangle和myTriangle,但在一些地方读到,包含myPoly的.h文件更有效率,并且可以自动包含它的.cpp文件。如果有人能够提供一些见解,那将不胜感激。我还记得,在包含语句中使用引号与使用尖括号是不同的。下面是我为这个小项目创建的九个文件。大部分注释都是我自己的小笔记或提醒。
//Practice with inheritance, polymorphism, and Abstract Data Types
//header file for Polygon class

#ifndef MYPOLY_H
#define MYPOLY_H

class myPoly
{
    public:
        //constructor
        //const reference pass because the values w and h don't change and reference avoid the time it takes to copy large
        //  objects by value (if there were any)
        myPoly();
        myPoly(const float & w, const float & h);

        //destructor
        virtual ~myPoly();

        //accessors
        float getWidth();
        float getHeight();
        void setWidth(const float & w);
        void setHeight(const float & h);

        virtual float area() = 0;

    private:
        float width, height;
};

#endif

myPoly.cpp

//Practice with inheritance, polymorphism, and Abstract Data Types
//implementation file for myPoly class

#include "myPoly.h"

//constructor
myPoly::myPoly()
{
    setWidth(10);
    setHeight(10);
}

myPoly::myPoly(const float & w, const float & h)
{
    setWidth(w);
    setHeight(h);
}

//destructor
myPoly::~myPoly() {}

//accessors
float myPoly::getWidth() {return width;}
float myPoly::getHeight() {return height;}

void myPoly::setHeight(const float & w) {width = w;}
void myPoly::setWidth(const float & h) {height = h;}

//pure virtual functions have no implementation
//area() is handled in the header file

myRectangle.h

//Practice with inheritance, polymorphism, and Abstract Data Types
//declaration file for myRectangle class

#ifndef MYRECTANGLE_H
#define MYRECTANGLE_H

#include "myPoly.h"

class myRectangle : public myPoly
{
    public:
        //constructor
        myRectangle();
        myRectangle(const float & w, const float & h);

        //destructor
        ~myRectangle();

        //this doesn't need to be virtual since the derived class doesn't override this method
        float area();
};

#endif

myRectangle.cpp

//Practice with inheritance, polymorphism, and Abstract Data Types
//implementaion file for the myRectangle class

//get a vauge compiler/linker error if you have virtual methods that aren't implemented (even if it ends up being just
//  a 'stub' method, aka empty, like the destructor)

#include "myRectangle.h"

myRectangle::myRectangle()
{
    setWidth(10);
    setHeight(10);
}

myRectangle::myRectangle(const float & w, const float & h)
{
    setWidth(w);
    setHeight(h);
}

myRectangle::~myRectangle()
{
}

float myRectangle::area()
{
    return getWidth() * getHeight();
}

myTriangle.h

//Practice with inheritance, polymorphism, and Abstract Data Types
//declaration file for myTriangle class

#ifndef MYTRIANGLE_H
#define MYTRIANGLE_H

#include "myPoly.h"

//imagine the triangle is a right triangle with a width and a height
//  |\
//  | \
//  |  \
//  |___\

class myTriangle : public myPoly
{
    public:
        //constructors
        myTriangle();
        myTriangle(const float & w, const float & h);

        //destructor
        ~myTriangle();

        //since nothing derives from this class it doesn't need to be virtual and in turn neither does the destructor
        float area();
};

#endif

myTriangle.cpp

//Practice with inheritance, polymorphism, and Abstract Data Types
//implementation file for myTriangle class

#include "myTriangle.h"

myTriangle::myTriangle()
{
    setWidth(10);
    setHeight(10);
}

myTriangle::myTriangle(const float & w, const float & h)
{
    setWidth(w);
    setHeight(h);
}

myTriangle::~myTriangle()
{
}

float myTriangle::area()
{
    return getWidth() * getHeight() / 2;
}

mySquare.h

//Practice with inheritance, polymorphism, and Abstract Data Types
//declaration file for mySquare class

#ifndef MYSQUARE_H
#define MYSQUARE_H

#include "myRectangle.cpp"

class mySquare : public myRectangle
{
    public:
        //constructors
        mySquare();
        //explicity call the myRectangle constructor within this implementation to pass w as width and height
        mySquare(const float w);

        //destructor
        ~mySquare();
};

#endif

mySquare.cpp

//Practice with inheritance, polymorphism, and Abstract Data Types
//implementation file for mySquare class

#include "mySquare.h"

mySquare::mySquare()
{
    setWidth(10);
    setHeight(10);
}

mySquare::mySquare(const float w)
{
    myRectangle::myRectangle(w, w);
}

mySquare::~mySquare()
{
}

test.cpp

//Practice with inheritance, polymorphism, and Abstract Data Types
//main class that uses my shape classes and experiments with inheritance, polymorphism, and ADTs

#include "myRectangle.cpp"
//#include "mySquare.cpp"
#include "myTriangle.cpp"

#include <iostream>

int main()
{
    myPoly * shape = new myRectangle(20,20);

    return 0;
}

我非常好奇为什么我会得到这些错误或者为什么我的做法可能不被认为是最佳实践,而不只是得到一行代码让我的错误消失。


3
快速浏览后,我注意到 MySquare.h 应该包括 MyRectangle.h,而不是 MyRectangle.cpp - StarPilot
谢谢你指出了这个问题。我会确保在我的代码中进行更改。 - RIBdot
4个回答

11

您的预编译指令看起来很好。如果有问题,您很可能会收到编译器错误,包括文件和行号信息。您发布的错误似乎更像是链接器错误。

然而,您的代码存在一个“问题”。通常情况下,您应该只#include.h文件,而不是.cpp文件。

现在让我们来解决这个问题:我本人对Code::Blocks不熟悉。但我希望能够提供一些通用信息,以指导您朝正确的方向前进。我过去使用的一些编译器默认情况下允许我编译单个C++文件并运行程序。要编译具有多个文件的程序,我必须创建一个项目。(大多数现代编译器都强制您从头开始创建项目。)考虑到这一点,我建议您查看如何在Code::Blocks中为您的程序创建一个项目。


感谢您的输入。我将所有文件移动到Code::Blocks项目中,并重新编译了test.cpp文件,未定义的引用错误消失了。我知道您说您不熟悉Code::Blocks,但您是否知道将这些文件放入项目中与单独使用它们时有何不同?项目是否向链接器或编译器提供了一些信息,而我的文件在单独使用时没有提供? - RIBdot
我做了一些搜索,如果我理解阅读内容的正确性,请有人纠正我,我认为当我尝试仅构建test.cpp时,在所有文件都在一个项目中之前,构建命令仅编译了test.cpp,并未将.o文件链接到任何其他.o文件。通过将所有文件移动到项目中,我相信该项目执行正确的链接器命令来编译我的所有九个文件并适当地链接它们的.o文件,类似于w00te的第二个点建议的几个答案。 - RIBdot
@RIBdot,我无法比你更好地解释了。这很可能是创建项目时发生的事情。虽然我不熟悉Code::Blocks,但这是我熟悉的所有编译器都会做的事情。 - Code-Apprentice

5

从代码角度来看(至少我查看的内容),它看起来非常不错,但是:

有两件事情需要考虑:

  1. 不要直接包含cpp文件。例如,在mySquare.h中,#include "myRectangle.cpp" 应该是 #include "myRectangle.h"。您需要包含在头文件中提供的接口/声明,以告诉程序如何制作类,而不仅仅是函数定义。

  2. 其次,请确保使用所有对象文件进行编译。我不知道Code Blocks,但如果您使用g ++或类似的东西,则需要对所有文件执行g ++ main.cpp myPoly.cpp mySquare.cpp等。例如,如果忘记了myPoly.cpp,则可能会发生此类错误,因为其函数的任何定义都不会被包括。


感谢您的输入。我的test.cpp文件是否也只应包含头文件而不是.cpp文件? - RIBdot
我想我实际上回答了自己的问题。我遵循了Code Guru在几个答案中留下的建议,将所有类移动到一个项目中。这给了我一个“多重定义错误”,针对myRectangle类。所以我去了我的test.cpp文件,并开始跟踪myRectangle被包含超过一次的地方,结果改变“#include myRectnalge.cpp”为“#include myRectangle.h”解决了多重定义错误。 - RIBdot

3

实际上一切看起来都很好。问题可能只是没有在链接程序时包含myPoly.obj。我不熟悉Code::Blocks(尽管我知道它相当受欢迎),但我假设如果您只是点击test.cpp并选择“运行”,那么Code::Blocks将尝试从那个源文件构建一个程序。您需要在每个要构建的程序中包含所有相关的源文件。


谢谢您的回答。我相信现在已经执行了正确的链接器命令,因为我按照Code-Guru的建议将所有文件移动到Code::Blocks中的一个项目中。 - RIBdot

-2
除了其他人说的之外,还有一点需要注意:你没有正确地使用继承...

当你这样做时

class Poly
{
   Poly();
   ~Poly();
}

class Rect : public Poly()
{
   Rect();
   ~Rect();
}

你需要按照以下方式声明子类的构造函数:
Rect::Rect() : Poly()
{

}

在父元素构造完成之后才能构造子元素。


3
不会的。如果你不显式调用超类构造函数,那么默认构造函数会自动调用。你的更改增加了混乱,并且根本没有改变生成的代码。 - Ernest Friedman-Hill
实际上,如果你不明确调用父类的构造函数,代码会变得含糊不清。程序员有可能会忘记默认父类构造函数正在被调用,一些初始化等操作可能已经完成或没有完成。这将使未来的调试变得更加困难。 - gibertoni

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接