为什么C++中静态变量需要声明两次

8

我有一个名为 filepaths.h 的头文件,它定义了许多静态变量:

#ifndef FILEPATHS_H
#define FILEPATHS_H

class FilePaths {

public:

    static QString dataFolder();
    static QString profileFolder();

private:

    static QString dataFolder_;
    static QString profileFolder_;

};

}
#endif // FILEPATHS_H

我有一个相关的filepaths.cpp文件,最初看起来是这样的:

#include "FilePaths.h"

QString FilePaths::dataFolder() {
    return dataFolder_;
}

QString FilePaths::profileFolder() {
    return profileFolder_;
}

然而,这并没有起作用 - 所有静态变量都会出现“未解决的符号错误”的链接器错误。因此,我以以下方式将这些变量添加到C++文件中:

#include "FilePaths.h"

QString FilePaths::dataFolder_ = "";
QString FilePaths::profileFolder_ = "";

QString FilePaths::dataFolder() {
    return dataFolder_;
}

QString FilePaths::profileFolder() {
    return profileFolder_;
}

这个代码是可以运行的,但我不明白为什么。

这些静态变量为什么需要定义两次?或者说我并没有定义它们而是在初始化?但是为什么需要这样做呢?或者我应该用不同的方式编写我的类?

4个回答

9

一个是定义,另一个是声明。区别在于声明可以出现多次,而对于不在类中的变量,可能根本不需要声明,而定义只能出现一次。

需要单独声明和定义的原因是过时的历史,基本上不必这样做,但它确实是这样的,以使C++与C兼容,后者是在1970年代设计用于编译的。


3
我不认为这很古老。声明表示“某处存在一个名为X的变量/类/函数”,而定义则表示“X驻留在此处,请编译器为其分配存储空间”。能够将声明和定义区分开来,是静态类型语言中允许多个翻译单元的关键,而无需编译器过于“聪明”地猜测你的意图。 - vsekhar
这并不是真正的过时,因为两个标准(即使是现在的C99/C++11)在实际链接器过程上都非常模糊,而在某个cpp文件中的定义将导致结果对象文件中的一个定义,如果不是这种情况,当链接所有代码时,链接器将找不到唯一的引用。 - rubenvb
vsekhar,@rubenvb:我想指出的是,在模板代码中,我们会得到函数和静态属性的多个定义(在目标文件中),但链接器会正确地管理它们。 - Matthieu M.
“古老的”部分恰好是构建模型,其中必须告诉程序的某些位存在于程序的其他位中。例如,Java编译器可以查找它需要知道的所有内容,因此接口可以根据程序员的需求而使用,而不是编译器的需求。 C语言模型有一些优点。其中一些优点仍然存在至今,尽管这是否值得代价还有待商榷。但许多最初的优点与过时的编译时资源限制有关,并且在当今已经不存在。 - Steve Jessop
3
这种做法已经过时,因为程序员需要始终处理它。几乎所有其他编译语言都不需要这种系统,而且反复解析相同的声明非常缓慢。请注意,原文中的“hideously”表示极其严重或可怕的意思。 - Puppy
@vsekhar,我们能把你的评论应用到非静态变量上吗? - Zac

2

来自http://weblogs.asp.net/whaggard/archive/2004/11/05/252685.aspx

你需要在类外部声明它,否则编译器就不知道成员变量应该放在哪个翻译单元(因此是目标文件)中。

因为,就像DeadMG所说的那样,你可以声明多次变量,但只定义一次。我认为这就像函数原型:你可以有任意多个函数原型,但只能有一个带有主体并实际定义函数的。


0

你不需要重复声明它们,声明发生在类头部,而定义——变量实际存在并分配内存的地方——在 .cpp 部分。

但与普通实例变量的区别在于,静态部分仅对每个创建的实例类存在一次。


0
这是因为,每当您声明一个类时,您都在为该类的特定实例声明一个结构,但是在类中静态变量的情况下,它们是可以在创建类的任何对象之前初始化的。 请注意,当我们在内存中声明一个类时,没有保留空间,但是在声明类的对象时会保留空间。不能像“int a = 2;”那样初始化类的成员,但是可以像“static int a = 2;”那样进行初始化。 在类声明中为它们保留空间,并在第二个声明中告知它们。

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