一个C ++头文件如何包含实现?

好的,不是C / C ++专家,但是我认为头文件的目的是声明函数,然后C / CPP文件定义实现。

然而,今天晚上回顾一些C ++代码,我发现这在一个类的头文件中…

public: UInt32 GetNumberChannels() const { return _numberChannels; } // <-- Huh?? private: UInt32 _numberChannels; 

那么为什么在头文件中有一个实现呢? 它是否与const关键字有关? 这是内联一个类的方法吗? 这样做的好处是什么,而在CPP文件中定义实现呢?

更新

我看到了很多类似的答案,但是讽刺的是他们其中一个在这里解释const关键字(尽pipe有人在这个问题的评论中做过)。由于所有的答案基本上都是关于内联的说法,第一个答案也解释那个const词将得到投票。

好的,不是C / C ++专家,但是我认为头文件的目的是声明函数,然后C / CPP文件定义实现。

头文件的真正目的是在多个源文件之间共享代码。 它通常用于将声明与实现分开以实现更好的代码pipe理,但这不是必需的。 可以编写不依赖头文件的代码,并且可以编写仅由头文件组成的代码(STL和Boost库就是这样的例子)。 请记住,当预处理器遇到#include语句时,它会将语句replace为被引用文件的内容, 编译器只会看到完成的预处理代码。

所以,例如,如果你有以下文件:

foo.h中:

 #ifndef FooH #define FooH class Foo { public: UInt32 GetNumberChannels() const; private: UInt32 _numberChannels; }; #endif 

Foo.cpp中:

 #include "Foo.h" UInt32 Foo::GetNumberChannels() const { return _numberChannels; } 

Bar.cpp:

 #include "Foo.h" Foo f; UInt32 chans = f.GetNumberChannels(); 

预处理程序分别parsingFoo.cpp和Bar.cpp,并生成以下代码, 编译器随后parsing:

Foo.cpp中:

 class Foo { public: UInt32 GetNumberChannels() const; private: UInt32 _numberChannels; }; UInt32 Foo::GetNumberChannels() const { return _numberChannels; } 

Bar.cpp:

 class Foo { public: UInt32 GetNumberChannels() const; private: UInt32 _numberChannels; }; Foo f; UInt32 chans = f.GetNumberChannels(); 

Bar.cpp编译成Bar.obj并包含调用Foo::GetNumberChannels()的引用。 Foo.cpp编译成Foo.obj并包含Foo::GetNumberChannels()的实际实现。 编译之后, 链接器会匹配.obj文件并将它们链接在一起以生成最终的可执行文件。

那么为什么在头文件中有一个实现呢?

通过在方法声明中包含方法实现,它被隐式声明为内联(也可以明确地使用实际的inline关键字)。 指示编译器应该内联一个函数只是一个暗示,不能保证函数实际上会被内联。 但是,如果是这样的话,那么无论内联函数从哪里调用,函数的内容都直接复制到调用站点,而不是生成一个CALL语句来跳转到函数中,并在退出时跳回到调用者。 如果可能的话,编译器可以考虑周围的代码并进一步优化复制的代码。

它是否与const关键字有关?

不能, const关键字只是向编译器表明,该方法不会改变它在运行时被调用的对象的状态。

这样做的好处是什么,而在CPP文件中定义实现呢?

如果使用得当,它可以使编译器通常生成更快,更好的机器码。

在头文件中实现一个函数是完全有效的。 唯一的问题是打破一个定义规则。 也就是说,如果包含来自多个其他文件的头文件,将会出现编译器错误。

但是,有一个例外。 如果你声明一个函数是内联的,那么这个函数就可以免于一个定义规则。 这就是在这里发生的事情,因为在类定义中定义的成员函数是隐式的内联的。

内联本身是编译器的一个暗示,一个函数可能是内联的一个好候选。 也就是说,将任何调用扩展到函数的定义中,而不是简单的函数调用。 这是一个优化,它将生成的文件的大小换成更快的代码。 在现代编译器中,除了对一个定义规则的影响之外,大多忽略为函数提供内联提示。 而且,编译器总是可以自由地内联任何它认为合适的函数,即使它没有被声明为inline (显式或隐式)。

在你的例子中,在参数列表之后使用const表示成员函数不会修改被调用的对象。 在实践中,这意味着由此指向的对象以及所有类成员的扩展将被视为const 。 也就是说,试图修改它们会产生一个编译时错误。

由于是在类声明中定义的成员函数,所以它被隐式地声明为 inline 。 这并不意味着编译器必须内联它,但这意味着你不会破坏这个定义规则 。 这与const *完全无关。 这也与函数的长度和复杂性无关。

如果它是一个非成员函数,那么你将不得不显式声明它为inline

 inline void foo() { std::cout << "foo!\n"; } 

*有关const更多信息,请参阅成员函数的末尾。

据我所知,有两种方法可以在头文件中安全的实现。

  • 内联方法 – 将它们的实现复制到使用它们的位置,所以双精度链接器错误没有问题。
  • 模板方法 – 它们实际上是在模板实例化的时刻编译的(例如,当有人inputtypes代替模板时),所以再次不存在双重定义问题的可能性。

我相信,你的榜样适合第一种情况。

即使在普通的C中,也可以将代码放在头文件中。 如果你这样做,你通常需要声明它是static ,否则包含相同头文件的多个.c文件将导致“多重定义的函数”错误。

预处理器在文本中包含一个包含文件,所以包含文件中的代码成为源文件的一部分(至less从编译器的angular度来看)。

C ++的devise者希望能够实现具有良好数据隐藏的面向对象编程,所以他们期望看到许多getter和setter函数。 他们不想要一个不合理的performance惩罚。 所以,他们devise了C ++,使得getters和setter不仅可以在头文件中声明,而且可以实现,所以他们会内联。 你展示的这个函数是一个getter,当这个C ++代码被编译时,就不会有任何的函数调用。 取出该值的代码将被编译到位。

有可能使一个没有头文件/源文件区分的计算机语言,但是只有编译器能够理解的实际“模块”。 (C ++没有这样做;他们只是build立在成功的C源代码文件模型之上,并且包含头文件)。如果源文件是模块,那么编译器就可以将代码从模块中提取出来,然后内联该代码。 但是C ++的实现方式比较简单。

保持类头文件中的实现工作,因为我相信你知道如果你编译你的代码。 const关键字可以确保您不会更改任何成员,从而在方法调用期间保持实例不可变 。