C++中变量的声明和定义有什么区别?

20

我的问题源自于学习Scott Meyers的《Effective C++》。在该书第二条中,写道:

要将常量的作用域限制在类内,必须使其成为类成员,并确保最多只有一个常量副本,必须使其成为静态成员。

这是正确的。然后立即给出了以下示例:

class GamePlayer {
private:
    static const int NumTurns = 5;
    int scores[NumTurns];
    ....
  };

以下是有关上面示例的说明:

您在上面看到的是NumTurns的声明,而不是定义。

我的第一个问题是:这个声明的意义是什么?

紧接着下面提到:

通常情况下,C++要求您为使用的任何内容提供定义,但静态且为整数类型(例如 - 整数,字符,布尔值)的类特定常量是一个例外。只要您不获取它们的地址,就可以声明并使用它们而无需提供定义。如果您确实取一个类常量的地址,或者如果您的编译器错误地坚持需要定义,即使您不取地址,您也可以提供一个单独的定义,如下所示: const int GamePlayer :: Numturns; //定义NumTurns

现在为什么这是定义而不是声明?

我理解函数的上下文中的区别,但不理解普通变量的上下文中的区别。此外,有人能否扩展作者对上述引用段落的含义:

... if you do take the address of a class constant, or if your .. 部分的意思是什么?

P.S:我是C++的新手。


3
当然,这本书会解释这个问题...同时,快速的谷歌搜索可以找到这篇文章,很好地解释了这个问题:http://www.cprogramming.com/declare_vs_define.html - Fantastic Mr Fox
@Amxx 是的,说的好。这也是一个格式良好的问题。+1。 - Fantastic Mr Fox
这与odr规则有关,如果一个变量没有被odr使用,则不需要类外定义。 - Shafik Yaghmour
1
https://dev59.com/V3M_5IYBdhLWcg3wXx1N - T.C.
我喜欢将在类中初始化的静态数据成员称为“带初始值的声明”。它们从未被定义。 - David G
我的狗是黑色的,是一只拉布拉多犬。这是一个定义 - 它不是一只狗。这是我的狗斑点。这是一个声明,它是一只真正的狗。它是一只黑色的拉布拉多犬。这是我的狗菲多。另一个声明,另一只黑色的拉布拉多犬。 - Mike
4个回答

8

和函数一样,变量也可以有'纯声明'的声明和实际定义。你可能会感到困惑,因为你可能没有遇到过许多纯变量声明。

int i; // Definition
extern int i, j; // (Re)declares i, and declares j
extern int j = 0; // Defines j (confusing, eh?)

正如您对函数的熟悉,定义是声明,但不是所有声明都是定义。§3.1/2中写道:
声明是定义,除非[...]它在类定义(9.2、9.4)中声明了一个静态数据成员,
因此,在类内静态数据成员声明中,从来不会定义它们声明的变量。然而,有时候,变量的定义不需要出现。在这种情况下,当您可以直接使用它的值而不必要求变量在运行时存在时,就会出现这种情况。
从技术上讲,只要静态数据成员(或任何实体)没有被“odr-used”,就不必定义它。所有实体的“odr-use”均在§3.2/3中定义:
名为ex的可能评估表达式中出现的变量x,如果将lvalue-to-rvalue转换(4.1)应用于x产生不调用任何非平凡函数且如果x是对象,则ex是表达式e的潜在结果集中的一个元素,其中e的lvalue-to-rvalue转换(4.1)被应用,或者e是一个弃值表达式(Clause 5),则通过ex odrued x。
这看起来很复杂,在标准的早期版本中更简单。但是,粗略地说,它大致表示当某个表达式“立即”访问变量的值时,该变量并未被odr-used,并且此访问产生一个常量表达式。Meyers关于“取其地址”的例子只是odr-use的众多例子之一。
对于某个类A及其静态数据成员i,
class A {
    static const int i = 57; // Declaration, not definition
};

const int A::i; // Definition in namespace scope. Not required per se.

2
这里所述:
静态const整型数据成员在类接口中初始化时不是可寻址的变量。它们只是与其关联值的符号名称。由于它们不是变量,因此无法确定它们的地址。请注意,这不是编译问题,而是链接问题。在类接口中初始化的静态const变量不存在作为可寻址实体。
这里所说的“可寻址实体”指的是静态const数据类型的“实例”。没有实例,就没有地址,即它只是一个声明。请注意,明确在源文件中定义的静态变量可以正确链接。
class X
{
    public:
        static int const s_x = 34;
        static int const s_y;
};

int const X::s_y = 12;

int main()
{
    int const *ip = &X::s_x;    // compiles, but fails to link
    ip = &X::s_y;               // compiles and links correctly
} 

“……如果你获取一个类常量的地址,或者如果你的……是上面引用段落中提到的部分?”
意味着如果这样的成员被 odr-used 使用,命名空间作用域中仍需要有定义,但是它不应该有初始化器。
struct X {
    const static int n = 1;
};
const int* p = &X::n; // X::n is odr-used
const int X::n;       // ... so a definition is necessary

2
那个引用不正确。你可以定义一个const整数数据成员并获取其地址。 - Columbo
@Columbo; 它是static const - haccks
抱歉,我删除了静态内容。但是评论仍然有效。http://coliru.stacked-crooked.com/a/1b633f6939183e52 - Columbo
@Columbo; 我很好奇。你有标准的引用吗? - haccks
1
@Columbo - 不行,你不能这样做。我猜你是那个给这个答案投反对票的人。http://coliru.stacked-crooked.com/a/9c5df94daf8a700e - David Hammen
@DavidHammen 如果你没有定义它,当然它不会链接。虽然这个答案中的引用边缘化了一些。但是在访问该网站后,似乎作者确实涵盖了这些变量的定义,而不包括这些内容是令人恼火的。 - Columbo

0
为什么现在是定义而不是声明?
因为这个语句会导致编译器为静态变量生成一个地址。
还有,有人能解释一下作者所说的“如果你获取类常量的地址”是什么意思吗?
当你让一个指针指向一个变量时,你获取了它的地址。

0
简短的回答是:声明表示“这个东西在某个地方存在”,而定义会导致空间被分配。在您的情况下,您已将其声明为静态和常量。编译器可能足够聪明,如果您仅将其用作值,则可以将该使用简单地替换为文字5。它不需要实际在某个地方创建一个变量并将其填充为5,它可以直接在编译时使用5。但是,如果您获取它的地址,则编译器无法再做出这种假设,现在需要将该5放入可寻址的位置中。编译器需要它存在于一个翻译单元中(大致上是一个cpp文件。另请参见一个定义规则),因此现在您必须在某个地方明确声明它。

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