学习 C++ 并在正确地将 class 接口与实现分开时遇到问题

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

我正在使用 Xcode 学习 C++ 并编写了几个小程序,包括 hangman 游戏,但每次我尝试将 class 分为定义和实现时,我都会遇到麻烦。我做了一个简单的案例来说明我的问题。简短版本似乎我需要在实现文件中指定一个类型,即使它已经在头文件中定义了。在我的示例中,我在第 12 行和第 13 行得到“C++ requires a type specifier for all declarations”。但是,如果我将第 12 行更改为

 int xmlelem::atrb_count = 0;

它收到错误“non-static data member defined out-of-line”。在其他情况下,我会收到一条错误消息,提示我正在尝试重新定义某些内容。我想我在某处缺少一个基本概念。我在看过的几个类似问题中没有看到这个特殊问题。

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
//  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;
}

main.cpp

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

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

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

请记住,您要声明的是 class。 A class 是一个抽象概念。当您这样做 xlemem::atrb_count = 0; 时,您对抽象概念具有具体价值。没有道理,对吧?当您想到狗的一般概念时,您不会想到特定的颜色。任何初始化都应该在构造函数内部完成,因为只有在构造函数中我们才能创建具体的 object.

因此,您应该删除初始化这 2 个属性的第 11 行和第 12 行,并且您的构造函数代码应更改为:

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

请注意,不必将字符串初始化为 ""

让我们看一下(第二个)错误信息。

non-static data member defined out-of-line

错误分为两部分:“non-static 数据成员”和“已定义 out-of-line”。这些是不兼容的,因此必须更改其中之一。另外,只能其中一个改,否则可能运行变成a different problem。确定这两部分中哪一部分适合您的情况。

保持“定义out-of-line”

当行

int xmlelem::atrb_count = 0;

在 namespace 作用域(即既不是函数也不是 class/struct/union 定义中)遇到,它是一个 out-of-line 定义。这个定义告诉编译器在那个地方为 int 保留足够的 space。然后,每当任何 xmlelem 对象访问 atrb_count 成员时,它都会访问这个特定的 space。所以有一个 int 被所有对象共享。

但是,此行为对应于静态成员。为了使声明与实现一致,需要添加关键字static

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

保留“non-static”

non-static 数据成员存储在 class 的每个对象中。每个对象都可以用它的数据副本做它想做的事,而不会影响其他对象。所以告诉编译器在对象外保留 space 是自相矛盾的。只需删除 out-of-line 定义就足以消除错误消息,但大概您希望在某处进行初始化,对吗?

non-static 数据成员的初始化可以在 in-line 或构造函数中完成。移动初始化 in-line 的示例如下。

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

这有时是合理的,但声明的目标是将接口与实现分开。因此,0 的初始值出现在头文件中可能是不可取的,就像上面那样。另一种方法是将初始值移动到构造函数(如果有多个构造函数,则移动到每个构造函数)。

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

(我还冒昧地将 tag_name 的初始化移到了 initialization list 中。)

请记住,如果您有多个构造函数,则需要在每个实际使用默认值的构造函数中执行此操作(对于例外情况,请考虑“复制构造函数”)。重复代码是缺点;由您决定收益是否值得付出代价。