简体   繁体   English

学习 C++ 并在将类接口与实现分离时遇到问题

[英]Learning C++ and having a problem correclty separating class interface from implementation

I'm learning C++ using Xcode and have written several small programs including a hangman game but I'm having trouble every time I try to separate a class into definition and implementation.我正在使用 Xcode 学习 C++,并编写了几个小程序,包括一个刽子手游戏,但是每次我尝试将一个类分为定义和实现时都会遇到麻烦。 I made a simple case that shows my problem.我做了一个简单的案例来说明我的问题。 Short version is it seems that I need to specify a type in the implementation file even though it is already defined in the header file.简短版本似乎我需要在实现文件中指定一个类型,即使它已经在头文件中定义。 I get " C++ requires a type specifier for all declarations " on lines 12 and 13 in my example.在我的示例中,我在第 12 和 13 行得到“ C++ requires a type specifier for all declarations ”。 But if I change line 12, for example, to但是,例如,如果我将第 12 行更改为

 int xmlelem::atrb_count = 0;

it gets the error " non-static data member defined out-of-line ".它得到错误“ non-static data member defined out-of-line ”。 In other cases I have got an error saying that I was trying to redefine something.在其他情况下,我收到一个错误,说我试图重新定义某些东西。 I think I'm missing a fundamental concept somewhere.我想我在某处遗漏了一个基本概念。 I did not see this particular issue in the handful of similar questions I looked at.在我查看的一些类似问题中,我没有看到这个特定问题。

xmlelem.hpp xmlelem.hpp

//  xmlelem.hpp
//  learn header
//
//

#ifndef xmlelem_hpp
#define xmlelem_hpp

#include <stdio.h>
#include <string>

#endif /* xmlelem_hpp */

class xmlelem {
    
private:
    int atrb_count;
    std::string tag_name;
    
public:
    xmlelem(std::string tag);
    void add_atrib();
    std::string output();
};

xmlelem.cpp xmlelem.cpp

//  xmlelem.cpp
//  learn header
//.
//

#include "xmlelem.hpp"
#include "string"
#include <iostream>

// line 11
xmlelem::atrb_count = 0;
xmlelem::tag_name = "";

xmlelem::xmlelem(std::string tag){
    tag_name = tag;
}

void  xmlelem::add_atrib(){
    atrb_count++;
}

std::string xmlelem::output(){
    std::string build = "<";
    build = build + tag_name + " " + std::to_string(atrb_count);
    build = build + ">";
    return build;
}

and main.cppmain.cpp

//  main.cpp
//  learn header
//
//

#include <iostream>
#include "xmlelem.hpp"
using namespace std;

int main(){
    xmlelem clip("test)");
    std::cout << clip.output() << " test \n";
}

Remember that you are declaring a class.请记住,您正在声明一个类。 A class is an abstract concept.类是一个抽象的概念。 When you do this xlemem::atrb_count = 0;当你这样做时xlemem::atrb_count = 0; , you are having a concrete value on an abstract concept. ,您对抽象概念具有具体价值。 Doesn't make sense, right?没有意义,对吧? You don't think of a particular color when you think of the general concept of dog.当您想到狗的一般概念时,您不会想到特定的颜色。 Any initiliazations should be done inside the constructor, because only in the constructor is that we create a concrete object .任何初始化都应该在构造函数内部完成,因为只有在构造函数中我们才能创建一个具体的对象

Therefore, you should eliminate lines 11 and 12 where you initialize these 2 attributes and your constructor code should be changed to:因此,您应该删除初始化这 2 个属性的第 11 行和第 12 行,并且您的构造函数代码应更改为:

xmlelem::xmlelem(std::string tag){
    tag_name = tag;
    atrb_count = 0;
}

Note that it isn't necessary to initialize a string to "" .请注意,没有必要将字符串初始化为""

Let's take a look at the (second) error message.让我们来看看(第二个)错误信息。

non-static data member defined out-of-line

There are two parts to the error: "non-static data member" and "defined out-of-line".错误有两部分:“非静态数据成员”和“定义外”。 These are incompatible, so one of them must be changed.这些是不兼容的,因此必须更改其中之一。 Furthermore, only one of them should be changed, or else you may run into a different problem .此外,应更改其中一个,否则您可能会遇到不同的问题 Decide which of the two parts is correct for your situation.确定这两个部分中的哪一个适合您的情况。

Keep "defined out-of-line"保持“定义的脱线”

When the line当线

int xmlelem::atrb_count = 0;

is encountered at namespace scope (that is, in neither a function nor a class/struct/union definition), it is an out-of-line definition.在命名空间范围内遇到(即,既不是在函数也不是在类/结构/联合定义中),它是一个外部定义。 This definition tells the compiler to reserve, right at that spot, enough space for an int .这个定义告诉编译器在那个位置为int保留足够的空间。 Then whenever any xmlelem object accesses the atrb_count member, it will access this particular space.然后,每当任何xmlelem对象访问atrb_count成员时,它都会访问该特定空间。 So there is one int shared by all objects.所以所有对象共享一个int

However, this behavior corresponds to a static member.但是,此行为对应于静态成员。 To make the declaration agree with the implementation, the keyword static needs to be added.为了使声明与实现一致,需要添加关键字static

class xmlelem {
    
private:
    static int atrb_count;
    /* rest of the class definition */
};

Keep "non-static"保持“非静态”

A non-static data member is stored inside each object of the class.非静态数据成员存储在类的每个对象中。 Each object can do what it wants with its copy of the data without impacting other objects.每个对象都可以用它的数据副本做它想做的事,而不会影响其他对象。 So telling the compiler to reserve space outside the objects is contradictory.所以告诉编译器在对象外保留空间是矛盾的。 Simply removing the out-of-line definition is enough to get rid of the error message, but presumably you wanted that initialization to occur somewhere, right?简单地删除外部定义就足以消除错误消息,但大概您希望初始化发生在某处,对吗?

The initialization of non-static data members can be done either in-line or in a constructor.非静态数据成员的初始化可以在线或在构造函数中完成。 An example of moving the initialization in-line is the following.在线移动初始化的示例如下。

class xmlelem {
    
private:
    int atrb_count = 0;
    /* rest of the class definition */
};

This is sometimes reasonable, but the stated goal was to separate the interface from the implementation.这有时是合理的,但声明的目标是将接口与实现分开。 Therefore, it might be undesirable for the initial value of 0 to appear in the header file, as it does in the above.因此,可能不希望头文件中出现初始值0 ,就像上面那样。 The alternative is to move the initial value to the constructor (to each constructor, if you had more than one).另一种方法是将初始值移至构造函数(移至每个构造函数,如果您有多个)。

xmlelem::xmlelem(std::string tag) :
    atrb_count(0),
    tag_name(tag)
{
}

(I've also taken the liberty of moving the initialization of tag_name into the initialization list .) (我还冒昧地将tag_name初始化移动到了初始化列表中。)

Remember, if you have more than one constructor, this needs to be done in each constructor that actually utilizes the default value (for an exception, think "copy constructor").请记住,如果您有多个构造函数,则需要在实际使用默认值的每个构造函数中完成此操作(对于例外情况,请考虑“复制构造函数”)。 Repeated code is a drawback;重复代码是一个缺点; it is up to you to decide if the gains are worth the cost.由您决定收益是否值得付出代价。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM