#ifndef CLASS_Hと#define CLASS_Hを.hファイルで使用し、.cppでは使用しないのはなぜですか?


136

私はいつも人々が書くのを見てきました

class.h

#ifndef CLASS_H
#define CLASS_H

//blah blah blah

#endif

問題は、クラス関数の定義を含む.cppファイルに対してもなぜそれを行わないのかということです。

のは、私が持っているとしましょう main.cpp、とmain.cpp含みclass.hclass.hファイルはしないincludeものは、そうはどのようmain.cppにあるか知っていますかclass.cpp


5
「インポート」は、おそらくここで使用する言葉ではありません。含める。
ケイトグレゴリー

5
C ++では、ファイルとクラスの間に1対1の相関関係はありません。必要な数のクラスを1つのファイルに入れることができます(1つのクラスを複数のファイルに分割することもできますが、これはめったに役立ちません)。したがって、マクロはでFILE_Hはなくである必要がありCLASS_Hます。
sbi

1
私のガードのアドバイスを参照してください。

回答:


302

まず、最初のお問い合わせに対処するには:

これを.hファイルに表示すると、次のようになります。

#ifndef FILE_H
#define FILE_H

/* ... Declarations etc here ... */

#endif

これは、ヘッダーファイルが複数回インクルードされるのを防ぐプリプロセッサテクニックです。プロジェクトのコンパイル中に、各.cppファイルが(通常は)コンパイルされます。簡単に言えば、これは、コンパイラが.cppファイルを取得し、#includedそれによってファイルを開き、それらをすべて1つの大規模なテキストファイルに連結してから、構文分析を実行し、最後にそれをいくつかの中間コードに変換し、その他を最適化/実行することを意味しますタスク、最後にターゲットアーキテクチャのアセンブリ出力を生成します。このため、ファイルが#included1つの.cppの下に複数回ある場合ファイルの場合、コンパイラはファイルの内容を2回追加するため、そのファイル内に定義がある場合、変数を再定義したことを示すコンパイラエラーが発生します。コンパイルプロセスのプリプロセッサステップでファイルが処理されると、その内容に初めて到達したときに、最初の2行でFILE_Hプリプロセッサに定義されているかどうかがチェックされます。そうでない場合は、それとディレクティブのFILE_H間のコードを定義して処理を続行します#endif。次にそのファイルの内容がプリプロセッサーによって表示されるとき、チェックFILE_Hはfalseになるため、すぐにスキャンしてまでスキャンし#endif、その後続行します。これにより、再定義エラーが防止されます。

そして、2番目の懸念事項に対処するには:

C ++プログラミングでは、一般的な方法として、開発を2つのファイルタイプに分けます。1つは拡張子が.hで、これを「ヘッダーファイル」と呼びます。これらは通常、関数、クラス、構造体、グローバル変数、typedef、前処理マクロと定義などの宣言を提供します。基本的には、コードに関する情報を提供するだけです。次に、「コードファイル」と呼ばれる.cpp拡張子を付けます。これにより、これらの関数、クラスメンバー、定義が必要な構造体メンバー、グローバル変数などの定義が提供されます。したがって、.hファイルはコードを宣言し、.cppファイルはその宣言を実装します。このため、通常はコンパイル時に各.cppをコンパイルしますオブジェクトにファイルし、それらのオブジェクトをリンクします(1つの.cppファイルに別の.cppファイルが含まれていることはほとんどないため)。

これらの外観がどのように解決されるかは、リンカの仕事です。コンパイラがmain.cppを処理するとき、class.hを含めることにより、class.cppのコードの宣言を取得します。これらの関数や変数がどのように見えるかを知る必要があるだけです(これが宣言によって与えられるものです)。そのため、main.cppファイルをいくつかのオブジェクトファイル(main.objと呼びます)にコンパイルします。同様に、class.cppclass.objにコンパイルされますファイル。最終的な実行可能ファイルを生成するために、リンカーが呼び出され、これら2つのオブジェクトファイルをリンクします。未解決の外部変数または関数の場合、コンパイラーはアクセスが発生する場所にスタブを配置します。次に、リンカはこのスタブを取得して、リストされている別のオブジェクトファイルでコードまたは変数を検索し、見つかった場合は、2つのオブジェクトファイルのコードを出力ファイルに結合し、スタブを関数の最終的な場所に置き換えます。変数。このように、main.cppにであなたのコードは、関数と使用変数を呼び出すことができclass.cpp彼らが宣言され、場合にだけclass.h

これがお役に立てば幸いです。


過去数日間、.hと.cppを理解しようとしていました。この答えにより、C ++を学ぶ時間と興味を節約できました。よく書かれました。ジャスティン、ありがとう!
Rajkumar R

あなたは本当に素晴らしい説明をしました!多分それが画像だったら答えはかなり良いでしょう
アラミン

13

CLASS_Hあるガードを含めます。これは、同じCPPファイル(正確には、同じ変換単位)内に同じヘッダーファイルが複数回(異なるルートを介して)含まれることを回避するために使用されます。これにより、多重定義エラーが発生します。

定義上、CPPファイルの内容は1回しか読み込まれないため、インクルードガードはCPPファイルには必要ありません。

インクルードガードをimport他の言語(Javaなど)のステートメントと同じ機能を持つものとして解釈したようです。しかし、そうではありません。#includeそれ自体はほぼ同等であり、import他の言語です。


2
「同じCPPファイル内」は「同じ翻訳単位内」と読み替えてください。
dreamlax 2010

@dreamlax:良い点-それは私が最初に書くつもりでしたが、私はガードを理解していない誰かが「翻訳単位」という用語に混乱するだけだと考えました。答えを編集して、括弧内に「翻訳単位」を追加します。これは、両方の世界で最高のはずです。
マーティンB

6

そうではありません-少なくともコンパイル段階では。

ソースコードからマシンコードへのc ++プログラムの変換は、次の3つのフェーズで実行されます。

  1. 前処理 -プリプロセッサは、#で始まる行のすべてのソースコードを解析し、ディレクティブを実行します。あなたの場合、ファイルの内容がclass.h行の代わりに挿入され ます#include "class.h。ヘッダーファイルのいくつかの場所にインクルードする可能性がある#ifndefため、プリプロセッサーディレクティブはヘッダーファイルが最初にインクルードされるときにのみ未定義になるため、句は重複した宣言エラーを回避します。
  2. コンパイル -コンパイラは、前処理されたすべてのソースコードファイルをバイナリオブジェクトファイルに変換するようになりました。
  3. リンク -リンカーはオブジェクトファイルをリンクします(そのため、名前が付けられます)。クラスまたはそのメソッドの1つ(class.hで宣言し、class.cppで定義する必要があります)への参照は、オブジェクトファイルの1つでそれぞれのオフセットに解決されます。クラスはclass.cppという名前のファイルで定義する必要がないため、「オブジェクトファイルの1つ」と書きます。プロジェクトにリンクされているライブラリにある可能性があります。

要約すると、宣言はヘッダーファイルを介して共有できますが、宣言と定義のマッピングはリンカーによって行われます。


4

それが宣言と定義の違いです。通常、ヘッダーファイルには宣言のみが含まれ、ソースファイルには定義が含まれます。

何かを使用するには、それが宣言ではなく定義であることを知っていれば十分です。リンカのみが定義を知る必要があります。

そのため、1つ以上のソースファイル内にヘッダーファイルを含めますが、別のソースファイル内にソースファイルを含めません。

また#include、インポートではありません。


3

これはヘッダーファイルに対して行われるため、コンテンツが複数回インクルードされている場合でも(通常は他のヘッダーファイルからインクルードされているため)、コンテンツは各前処理済みソースファイルに1回だけ表示されます。初めてインクルードされるとき、シンボルCLASS_Hincludeガードと呼ばれます)はまだ定義されていないため、ファイルのすべてのコンテンツが含まれます。これを行うとシンボルが定義されるので、それが再びインクルードされる場合、ファイルのコンテンツ(#ifndef/ #endifブロック内)はスキップされます。

(通常)他のファイルには含まれていないため、ソースファイル自体に対してこれを行う必要はありません。

最後の質問についてclass.hは、クラスの定義、およびそのすべてのメンバーの宣言、関連する関数などを含める必要があります。これにより、クラスを使用するのに十分な情報が含まれるファイルが作成されます。関数の実装は、別のソースファイルに入れることができます。それらを呼び出すための宣言だけが必要です。


2

main.cppclass.cppの内容を知る必要はありません。使用する関数/クラスの宣言を知っているだけでよく、これらの宣言はclass.hにあります。

リンカーは、class.hで宣言された関数/クラスが使用される場所と、class.cppでのそれらの実装との間をリンクします。


1

.cppファイルは#include他のファイルに含まれていません(を使用)。したがって、保護を含める必要はありません。Main.cppで実装したクラスの名前とシグネチャがわかるのは、class.cppそれらすべてを指定したからですclass.h。これがヘッダーファイルの目的です。(でclass.h実装するコードを正確に説明するかどうかはあなた次第ですclass.cpp。)の実行可能コードは、リンカーの働きによりclass.cpp、実行可能コードで使用できるようmain.cppになります。


1

.cppロジックの不必要な繰り返しコンパイルを回避するために、ファイルなどのコードのモジュールは一度コンパイルされ、複数のプロジェクトにリンクされることが一般的に予想されます。たとえば、複数のプロジェクトからを使用するようにリンクできるものをg++ -o class.cpp生成class.oしますg++ main.cpp class.o

#includeほのめかしているように、リンカーとして使用することもできますが、より多くのキーストロークと無駄のあるコードではなく、より少ないキーストロークと無駄なコンパイルの繰り返しでコンパイラを適切にリンクする方法を知っていると、それはばかげたことになりますコンパイルの繰り返し...

ただし、ヘッダーファイルは、各モジュールのインターフェイスを提供するため、複数のプロジェクトのそれぞれに含める必要があります。これらのヘッダーがないと、コンパイラーは.oファイルによって導入されたシンボルのいずれも認識しません。

ヘッダーファイルは、これらのモジュールのシンボルの定義を導入するものであることを理解することが重要です。それが実現したら、複数のインクルードがシンボルの再定義を引き起こし(エラーを引き起こす)可能性があるので、そのような再定義を防ぐためにインクルードガードを使用します。


0

これは、Headerfilesにより、クラスに含まれるもの(メンバー、データ構造)を定義し、cppファイルがそれを実装するためです。

そしてもちろん、これの主な理由は、1つの.hファイルを他の.hファイルに複数回含めることができるということですが、これはクラスの複数の定義をもたらすことになり、これは無効です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.