在有效的C++(第3版),第2项(身高const
,enum
和inline
对#define
),为类特定常量代码段读:
class GamePlayer { private: static const int NumTurns = 5; // constant declaration int scores[NumTurns]; // use of constant ... };
然后,本书(用我自己的话说)static const int NumTurns = 5;
不是一个定义,这通常是C++对类成员所要求的,除非它是一个从不使用地址的静态整数常量.如果上面的常量不是真的,或者编译器因任何原因坚持定义,那么定义应该在实现文件中提供如下:
const int GamePlayer::NumTurns; // definition of NumTurns; see // below for why no value is given
根据这本书(也用我自己的话说),定义中没有给出任何价值,因为它已在声明中给出.
这让我认为我已经知道的声明和定义的定义令人困惑(我在问这个问题之前在Google上仔细检查过):
为什么static const int NumTurns = 5
不是定义?是NumTurns
不是初始化为5
这里的值,是不是当声明和定义一起出现时,它被称为初始化?
为什么static
积分常数不需要定义?
为什么第二个代码片段在没有定义值时被认为是一个定义,但是包含该值的类中的声明仍然不是一个(基本上回到我的第一个问题)?
是不是初始化定义?为什么这里没有违反"唯一定义"规则?
有可能我现在只是在这里混淆自己,所以有人可以从头开始重新教育我:为什么这两行代码声明和定义而不是另一行,那里有任何初始化实例吗?初始化也是定义吗?
信用:代码片段直接从本书中引用.
编辑:附加参考定义和声明之间的区别是什么?
声明引入了标识符和类型
定义实例化并实现
所以,是的......这似乎不是这里发生的事情.
编辑2:我认为编译器有可能通过不将其存储在内存中来优化静态积分常数,并且只是在代码中将其替换为内联.但是如果使用了NumTurns
地址,为什么声明不会自动变成声明+定义,因为实例化已经存在?
编辑3 :(这个编辑与原始问题关系不大,但仍然很突出.我把它放在这里,这样我就不需要复制粘贴到下面每个答案的评论中.请回答我这个问题在评论中.谢谢!)
谢谢你的回答.我的头脑现在更清楚了,但编辑2中的最后一个问题仍然存在:如果编译器在程序中检测到需要定义的条件(例如&NumTurns
在程序中使用),为什么它不会自动重新解释static const int NumTurns = 5;
为声明和定义而不是仅声明?它具有程序中任何其他位置的所有语法定义.
我来自学校的Java背景,并且不需要以上述方式为静态成员制定这样的单独定义.我知道C++是不同的,但我想知道上面为什么会这样.如果从不使用地址,则内联替换的静态积分成员听起来更像是对我的优化而不是基本特征,那么为什么我需要解决它(当条件不是时提供单独的语句作为定义即使原始语句的语法已经足够了,而不是相反的方式(编译器将原始语句作为定义处理时需要有一个语法,因为语法已经足够)?
免责声明:我不是标准的大师.
我建议你阅读以下两点:
http://www.stroustrup.com/bs_faq2.html#in-class
和:
定义和声明之间有什么区别?
为什么static const int NumTurns = 5不是定义?NumTurns在这里没有初始化为值5,并且当声明和定义一起出现时,它不是称为初始化吗?
在高级别中,定义(与声明相对)实例化或实现实体(变量,类,函数).
在变量的情况下,定义使变量在程序存储器上分配.
在函数的情况下 - 定义提供可以编译为汇编指令的指令.*
代码行
static const int NumTurns = 5;
NumTurns
默认情况下不会在程序存储器中分配,所以它只是声明.为了NumTurns
在程序存储器中创建(唯一的)实例,您必须提供所需的定义:
const int MyClass::NumTurns;
Bjarne引述:
如果(并且仅当)它具有类外定义,您可以获取静态成员的地址:
class AE { // ... public: static const int c6 = 7; static const int c7 = 31; }; const int AE::c7; // definition int f() { const int* p1 = &AE::c6; // error: c6 not an lvalue const int* p2 = &AE::c7; // ok // ... }
为什么静态积分常数不需要定义?
如果你想要他们的地址,他们会这样做.
是不是初始化定义?
初始化是在某个实体(原语,对象)中设置初始值的行为.
void someFunc (){ int x; //x is declared, but not initialized int y = 0; //y is declared+ initialized. }
为什么这里没有违反"唯一定义"规则?
为什么会这样?NumTurns
整个代码中仍然有一个.符号MyClas::NumTurns
只出现一次.该定义仅使变量出现在程序存储器中.ODR规则指定任何符号只能声明一次.
编辑:
问题基本归结为"为什么编译器不能自行决定?" 我不是该委员会的成员,所以我不能给予完全合法的答复.
我的聪明猜测是让编译器决定事情不是C++哲学.当你让编译器决定,例如在哪里声明一个静态积分const时,作为开发人员,我可能会引发以下问题:
1)如果我的代码是一个只有标题的库,会发生什么? 我的编译器应该在哪里声明变量?在遇到的第一个cpp文件中?在包含main的文件中?
2)如果编译器决定在哪里声明变量,我是否有任何保证此变量将与其他静态变量(我手动声明)一起声明,从而保持缓存局部性紧张?
一旦我们批准"让竞争对手疯狂"的思维方式,我就会提出越来越多的问题.我认为这是Java和C++之间的根本区别.
Java:让JVM做它的hueristics.
C++:让开发人员进行他的分析.
最终,在C++中,编译器会检查所有内容是否有效并将代码转换为二进制代码,而不是代替开发人员.
*或字节码,如果你使用托管C++(嘘......)
单定义规则的一个后果是类的静态成员只能有一个定义.但是,如果多个编译单元定义它,则程序中的每个编译单元都有一个定义.实际效果是,如果不违反单一定义规则,声明就不能成为定义.在实践中,链接器通常不够智能来解决这样的多个定义.
静态积分常数不需要定义的原因是它不是必需的.如果在类定义中初始化该值,则编译器只需在使用它时替换初始化值.实际上,这意味着不需要该值实际占用程序中的内存位置(只要没有代码计算该常量的地址,在这种情况下就需要定义).
声明,定义,初始化实际上是独立的(尽管是相关的)概念.声明告诉编译器存在某些东西.定义是一种声明导致某些东西存在的声明(因此具有其他声明可见性的代码可以引用它) - 例如,为它分配内存.初始化是给予价值的行为.这种区别实际上发生在语言的其他部分.例如;
#includeint main() { int x; // declaration and definition of x std::cout << x << '\n'; // undefined behaviour as x is uninitialised x = 42; // since x is not yet initialised, this assignment has an effect of initialising it std::cout << x << '\n'; // OK as x is now initialised }
在实践中,初始化可以是声明的一部分,但不是必须的.
编辑回复原始问题中的"编辑3":
C++有一个单独的编译模型.Java的模型依赖于C++模型所没有的功能(更智能的链接器,运行时链接).在C++中,如果一个编译单元看到一个声明但没有定义,编译器只是假定该定义在另一个编译单元中.通常(具有大量构建链),链接器稍后会检测是否存在必要的定义,因此链接阶段会失败.相反,如果每个需要存在定义的编译单元实际上都创建了一个,那么编译器就会破坏"一个定义规则"和一个典型的哑链接器 - 除了其他功能之外,它还不够智能地将重复定义的东西折叠成单个定义 - 会抱怨多重定义的符号.
为什么静态const NumTurns = 5不是定义?NumTurns在这里没有初始化为值5,并且当声明和定义一起出现时,它不是称为初始化吗?
这是错误的问题.声明是通知编译器类型/变量/函数/存在的任何内容,以及它是什么.定义是指示编译器实际分配存储以保持所述实体的位置.
由于成员是在"类"声明中"定义"的(即 - 此时没有创建类的实例),因此这是一个声明.
您为了将此定义称为定义而依赖的等号仅仅是结构成员的默认值,而不是初始化.
为什么静态积分常数不需要定义?
你有点自己回答了那个问题.这是因为编译器可以避免为它们分配任何存储,只需将它们放入使用的代码中即可.
为什么第二个代码片段在没有定义值时被认为是一个定义,但是包含该值的类中的声明仍然不是一个(基本上回到我的第一个问题)?
正如我之前所说,那是因为第二个代码为变量分配存储空间.
是不是初始化定义?为什么这里没有违反"唯一定义"规则?
因为初始化不是定义.
如果编译器在程序中检测到需要定义的条件(例如,程序中使用了NumTurns),为什么不自动重新解释静态const int NumTurns = 5; 作为声明和定义而不是仅声明?
因为定义分配存储.更具体地说,因为如果编译器这样做,那么在不同的编译单元中将存在多个存储分配.这很糟糕,因为在链接期间,链接器只需要一个.链接器看到具有相同范围的同一变量的几个定义,并且不知道它具有相同的值.它所知道的是它无法将它们全部合并到一个位置.
为了避免这个问题,它会让您手动进行定义.您可以在cpp文件中定义它(即 - 不在标头中),从而将分配解析为链接器可以承载的一个特定文件.