C++ OOP LNK2001 错误

C++ OOP LNK2001 ERROR

如标题所示,我在 C++ 中遇到了一个 OOP 错误,LNK2001 未解决的外部错误。这是我的代码,我哪里出错了? 我正在使用sfml来使用VS2015中的图形。

// OOPExample.hpp
#pragma once
#include <SFML\Graphics.hpp>
#include <SFML\System.hpp>
#ifndef OOPEX_H
#define OOPEX_H

class OOPExample{
public:
    static sf::CircleShape shape;
    static float rot;
    static void draw();
    static void rotate();
};
#endif // OOPEX_H

// OOPExample.cpp

#include "OOPExample.hpp"
#include <SFML\Graphics.hpp>

void OOPExample::rotate() {
    OOPExample::rot += 0.1f;
    return;
};

void OOPExample::draw() {
    OOPExample::shape.setFillColor(sf::Color::Red);
    OOPExample::shape.setRotation(rot);
    return;
};

// Source.cpp

#include <SFML/Graphics.hpp>
#include "OOPExample.hpp"

int main()
{
    sf::RenderWindow window(sf::VideoMode(200, 200), "SFML works!");
    OOPExample oopexample();

    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                window.close();
        }

        window.clear();
        window.draw(oopexample().shape);
        window.display();
    }

    return 0;
}

据我所见,我似乎需要在 OOPExample.hpp 中声明的方法的定义,但我在 OOPExample.cpp 中有那些确切的定义。我是否在 Source.cpp 中错误地实例化了 class?

关于 link 错误的问题。评论突出显示了其中的大部分...这些不是 OOP 错误,而是 link 构建项目时的时间错误。不知道大家有没有编译system-level语言的经验;但学习 compile-link 循环的基础知识以及 link 人员在将最终程序组合在一起时的期望是个好主意。下面是一个如何定义静态成员变量的简单示例。

// class1.h

Class1
{
public:
private:
  static float rotation;
};

// class1.cpp

#include "class1.h"

int Class1::rotation = 5.0f;

请注意 int Class1::rotation = 5.0f; 在程序初始化时发生一次。

我不知道您是否在遵循一些教程,他们是如何创建这个 class 的,但是您的静态成员数量令人担忧。这是一个 OOP 问题。如果你想制作许多 objects/instances 个 OOPExample,你需要了解静态的含义。在 class 的上下文中,当您将 static 关键字应用于变量时,这意味着所有 OOPExample objects 将共享该变量。这使得静态成员变量适用于默认值和给定的数字 class 之类的东西。您可以使用 static int OOPExample::count; 来计算您创建的 OOPExample objects 的数量。我稍后会把它放在一个例子中。

link 错误的原因可能有很多,尤其是缺少定义。 PcAF 在您的问题的评论中强调了一个重要的问题。但您也可能缺少 SFML 库。我依稀记得 SFML 教程,其中包括有关如何 link 在您的环境中使用它们的库的详细说明。在 VS 中,它将位于您的项目属性中的某个位置。如果你在 header 中声明了一些不在实现中的东西(通常是 cpp),你显然会得到类似的错误。您的静态变量就是这种情况,但也适用于函数。

现在你提供的三个文件有很多错误。我对它们进行了编辑以突出一些问题,但它远非完美。我不会以这种方式处理它,因为 sf::CircleShape 已经是一个 object-orientated 实体。它包含您要实现的所有内容。从来没有 over-abstract 问题(我在某个时候也意识到我们正在旋转一个实心圆哈哈)。你真的应该按照建议得到一本好的教科书,并从 ground-up 开始。 SFML 是一个巨大的库,它会分散您对 C++ 基础知识的理解。 OOP 只是 C++ 的一个方面,您需要掌握所有 C++ 基础知识才能有效地使用 OOP。如果这样做,您将拥有最强大的可用抽象机制(在我看来)。

我的编辑如下,但实际上,这只是兔子洞有多深的演示(它变得更糟)。如何实例化 OOPExample 显示在 main.

// OOPExample.h
#ifndef OOPEX_H
#define OOPEX_H

// Only include what you need to. Users of this header should be exposed to as
// little SFML as possible.
#include <SFML/Graphics/CircleShape.hpp>

class OOPExample{
public:
    // Parameterless constructor.
    OOPExample(); // Note, this sets the rotation to the default rotation.
    // One that takes a initial rotation.
    OOPExample(float initial_rotation);

    // Rotate 0.1f (by default) or by user specified amount.
    void rotate(float rotation = 0.1f);

    // window.draw() takes a Drawable as its first argument. Fortunately,
    // CircleShape is a shape which in turn is a Drawable. Notice that we
    // return by constant reference. Callers cannot edit our shape but they
    // get a reference to the sf::CircleShape shape instance so they can read
    // it.
    // const, & (i.e. reference), pointers requires a deep understanding of
    // object ownership, copying by value, by reference, and now of
    // particular interest in C++11, moving.
    const sf::CircleShape &getShape() const;

    // You forgot to declare and define this.
    void setRotation(float rotation);
    // Set the default rotation for all objects created with the
    // parameterless constructor.
    static void setDefaultRotation(float rotation);

    // The destructor.
    virtual ~OOPExample();
private:
    sf::CircleShape shape;
    // sf::CircleShape already has a rotation with getters and setters.
    // Don't over abstract!
    // Our setRotation, rotate functions seem a bit unneccesary.
    //   float rotation;

    // Defaults.
    static sf::CircleShape default_circle;
    static float default_rotation;
    // Count example.
    static int count;
};

#endif // OOPEX_H


// OOPExample.cpp

// Personally, and I know with most other C++ developers, I prefer my header's
// extension to be .h. .hpp usually identifies headers with
// implementations/definitions of classes in the header file itself (necessary
// in some circumstances).
#include "OOPExample.h"

//
// How to initialise static member variables. This happens once at the
// beginning of the program.
//
// All our circles have a default radius of 5.
sf::CircleShape OOPExample::default_circle = sf::CircleShape(5);
// And rotation of 0.
float OOPExample::default_rotation = 0;

int OOPExample::count = 0;

// The definition of the parameterless constructor.
OOPExample::OOPExample()
// A class initialiser list. How we build a new object.
    : shape(default_circle)     // We copy the default circle.
{
    // Do other stuff to construct the object if you need to. For example:
    shape.setFillColor(sf::Color::Red);
    setRotation(default_rotation);

    count++; // We just made another OOPEXample instance.
}

// The definition of a constructor that takes an initial rotation. I just
// realised we are rotating a circle!
OOPExample::OOPExample(float initial_rotation)
    : shape(default_circle)     // We copy the default circle.
{
    // Do other stuff to construct the object if you need to. For example:
    shape.setFillColor(sf::Color::Red);
    // Notice: we used the user provided argument this time.
    setRotation(initial_rotation);

    count++; // We just made another OOPEXample instance.
}

void OOPExample::rotate(float rotation)
{
    shape.rotate(rotation);

    // return; // No need to specify a return for a void.
}

const sf::CircleShape &OOPExample::getShape() const
{
    return shape;
}

void OOPExample::setRotation(float rotation)
{
    shape.setRotation(rotation);
}

void OOPExample::setDefaultRotation(float rotation)
{
    // OOPExample scoping is unnecessary.
    OOPExample::default_rotation = rotation;
}

OOPExample::~OOPExample()
{
    // Do things required for cleanup, i.e. deinit.

    // One OOPExample just reached the end of its lifetime. Either it
    // was deleted or reached the end of the
    // scope (i.e. {}) it was created in.
    count--;
}


// main.cpp

#include "OOPExample.h"

#include <SFML/Graphics.hpp>

int main()
{
    sf::RenderWindow window(sf::VideoMode(200, 200), "SFML works!");

    // Call the default, parameterless, constructor to instantiate an object
    // of OOPExample.
    OOPExample oopexample;
    // Create another with a initial rotation of your choosing.
    OOPExample another_obj(0.5f);

    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                window.close();
        }

        window.clear();
        // The instance object of OOPexample is referred to by oopexample.
        // window.draw(oopexample().shape);
        // This member is now private.
        //window.draw(oopexample.shape);
        window.draw(oopexample.getShape());
        window.display();
    }

    return 0;
}