2015-10-20 99 views
3

我有自定义堆栈实现的例子。据我了解,.h文件应该只包含声明cpp文件应该只包含实现。我在cplusplus.com/stack_example上找到了一个自定义堆栈的例子,它看起来像下面这样。什么是头文件和C++中的实现文件?

Stack.h文件

#ifndef _STACK_H_ 
#define _STACK_H_ 

#include <iostream> 
#include "Exception.h" 

template <class T> 
class Stack { 
    public: 
     Stack():top(0) { 
      std::cout << "In Stack constructor" << std::endl; 
     } 
     ~Stack() { 
      std::cout << "In Stack destructor" << std::endl; 
      while (!isEmpty()) { 
       pop(); 
      } 
      isEmpty(); 
     } 

     void push (const T& object); 
     T pop(); 
     const T& topElement(); 
     bool isEmpty(); 

    private: 
     struct StackNode {    // linked list node 
      T data;      // data at this node 
      StackNode *next;   // next node in list 

      // StackNode constructor initializes both fields 
      StackNode(const T& newData, StackNode *nextNode) 
       : data(newData), next(nextNode) {} 
     }; 

     // My Stack should not allow copy of entire stack 
     Stack(const Stack& lhs) {} 

     // My Stack should not allow assignment of one stack to another 
     Stack& operator=(const Stack& rhs) {} 
     StackNode *top;     // top of stack 

}; 

现在我有问题。这个.h文件显然揭示了一些实现细节。构造函数和析构函数都在.h文件中执行执行。根据我的理解,这些应该在.cpp文件中执行。此外,还有struct StackNode这也是.h文件中实现。这甚至有可能在.cpp文件中实现,只能在头文件中声明它?作为一般规则,如果这些文件位于.cpp实施文件中,这会不会更好?编码这个东西的最好方法是什么,以便遵循C++规则?

+5

你把事情需要由多个[翻译单元]可以使用头文件(http://en.wikipedia.org/wiki/Translation_unit)。如果你想隐藏实现细节,可以考虑使用[the pimpl idiom](http://c2.com/cgi/wiki?PimplIdiom)或其他不透明的结构指针。 –

回答

3

有什么必须是在头文件中,除了标准库头再没标准规则。我的意思是,理论上讲,我们可以完全避免头文件和.cpp文件中的复制和粘贴声明。

这就是说,这更多的是一个常识和经验的问题。你会根据你的意愿和你的需要把东西放入标题或.cpp。我们可以列出一些使用案例:

  • 模板(如在你的例子)声明实现通常去头。有关更多信息,请参阅this thread

  • 将函数的声明在一个头,当你想/想他们会在使用超过一个翻译单元

  • 当你别不将函数的声明在头如果您希望函数为inline d并且希望该函数在不同的翻译单元中使用,那么您将它的定义放在一个标题中(前面有如果我是inline关键字T的一个非成员函数)

  • 把一个类声明(又名向前声明)的头,当你想/想要的那个类型的对象将是访问 *从不同的翻译单位

  • 把类的声明,当你想/只想要一个翻译单元将要访问该类的水箱内

  • do把类定义(即全班接口)中,当你认为/头想要的那个类型的对象将是*在不同的翻译单位

    创建
  • 把一个类定义在头,当你想要的该类型的对象将只在一个翻译单元创建

    如果你想定义一个全局变量,你很难把定义在一个头文件(除非你想包括头只是一个单一的翻译
  • 单位)

  • 如果你正在开发一个图书馆,你就会把这些函数和类声明要为用户提供进入头文件

  • 如果你正在开发一个图书馆,你会找到一种方法,投入.cpp文件的实现细节你不想让公众(如@Joachim Pileborg建议,见the pimpl idiom

  • 你平时想用用指令在头部声明或,因为他们会污染那些翻译单位将#include

  • 当它是可能的,你希望其他#include头到你的;你肯定喜欢转发声明,你需要使你的程序编译什么

  • 最后,粗略地讲,你把水箱内的少的东西,更快的文件进行编译;并且让我明确地指出,你想让你的文件快速编译!


注意

上面我已经说过主要的类和函数,但一般大拇指这些规则是有效的枚举和typedef声明。

在头从列表中缺少成员函数的定义,因为它里面类定义函数定义的问题,而不是.h VS .cpp文件

*随着访问我的意思是通过指针或参考使用,相对于创建的

+0

这是一个很有前途的列表,但是(当前)省略了最有趣的例子之一 - 类/结构定义。 –

+0

@TonyD这是最糟糕的,我在类定义*和声明*之间混淆,所以答案是误导。我试图澄清这一点。请通过编辑或添加更多的内容免费改进当前列表。 –

4

头文件与源文件没有根本的区别。

原则上,标头可以包含与源文件相同的代码结构。按照惯例,唯一的区别是头文件和源文件的区别在于头文件应该是其他文件的文件,我们通常不会使用源文件(尽管我们可以,如果我们觉得冒险的话)。

现在重要的是如果一个文件通过多个源文件得到#include d会发生什么情况。请注意,这基本上等同于将相同的代码复制粘贴到多个.cpp文件。在这种情况下,某些事情可能会让你陷入麻烦。

特别是,如果最终在两个不同的源文件中出现两个相同符号的定义,那么您的程序格式不正确(根据the one-definition rule),链接器可能会拒绝链接您的程序。

对于不同的源文件,这通常不是问题,除非你在不同的上下文中意外地重用了一个名称。一旦头文件进入图片(这只是复制粘贴的代码),事情开始看起来不同。您仍然可以在头文件中定义一个定义,但是如果该头文件被多个源文件拉入,您自己会违背单定义规则。

因此,约定是只将东西放入头文件中,可安全地跨多个源文件进行复制。这包括声明,内联函数定义和模板。

这里的关键实现可能是头文件是一个相当古老的工具,用于在源文件之间共享代码。因此,他们严重依赖于用户足够聪明,不要搞砸了。

3

究竟是什么进入头文件?

与您学习的内容和您的设计无关,请将尽可能少的内容放入标题中。标题是使用实现细节所需的最少量东西。与大头文件相比,简单头文件通常也更容易使用代码,并且其包含的编译时间更短。

当然,C++头文件与源文件没有区别,它是根据您的技能将源代码编写到不同的文件中以使您的项目更易于使用和理解。

有些情况下你不得不把东西放在标题中(例如使用模板),如果你不这样做,最好把东西放入标题&源文件中。

编译器将转换为二进制ANY源文件(header或cpp),唯一的必要条件是至少有一些“可编译”代码(函数/方法的主体,即使为空)。

一个谁做的区别是程序员:

如果你把一个头的功能不能被编译的签名,你也来编译函数体,以实际上使得它可运行的程序(如果你没有身体的某个地方,你会得到链接错误)

Header.h

#pragma once 

// by including this file you are actually promising that 
// you will later add also the body of this function. 
int function(int a); 

Source.cpp

#include <liba> 
#include <libb> 
#include "Header.h" 

// by compiling this file AND linking into your binaries 
// you satisfy the promise by providing the real function 
int function(int a){ 
    return liba::fun(a)+libb::fun(b); 
} 

是不是有用?是。你所包含的每个文件都会增加编译时间并为你的代码添加依赖(如果你改变了一个头文件,你必须重新编译你的程序,这样你也可能会遇到编译错误,但是如果你把东西分成两个文件,你可以有更简单的头文件和你可以在你的函数内改变一些小东西,而不必重新编译大量文件:在大型项目中编译时间是一个真正的问题)。

以下的2个例子是等价的(产生相同组件):

的main.cpp

#include "Header.h" 
int main(){ 

    return function(3); 
} 

你仅包括1个文件


MAIN2。 cpp

#include "Source.cpp" //note source.cpp here! 
int main(){ 

    return function(3); 
} 

要包括Source.cppHeader.hlibalibb(甚至更多)的文件,用结果4X慢编译时间。


当然头还可以让你通过避免重复的定义有一个更简单的生活编程。

双列入

#include "Header.h" 
#include "Header.h" //bad, but allowed! 
int main(){ 

    return function(3); 
} 

双列入

#include "Source.cpp" 
#include "Source.cpp" //error! redefinition (not compile) 
int main(){ 

    return function(3); 
} 

来自不同地方的双重涵

file1.cpp

#include "Source.cpp" // Bad! include Header.h 

file2.cpp

#include "Source.cpp" //error symbol already define!