AF - 4.01.includeディレクティブ /

Time Limit: 0 msec / Memory Limit: 0 KB

前のページ | 次のページ

キーポイント

  • ソースコードを書いたファイルのことをソースファイルという
  • 便利な関数や構造体を書いたソースファイルなどをまとめたものをライブラリという
  • #include <bits/stdc++.h>はSTL(C++の標準ライブラリ)を用いるためのもの
  • #include <ファイル>include(インクルード)ディレクティブという
    • 「指定したファイルの内容をこの命令を書いた部分に展開する」という機能
  • 複数のソースファイルでプログラムを書く際には、ソースファイルとそれに対応するヘッダファイルを用いることが多い
    • ヘッダファイルには関数のプロトタイプ宣言などを書き、ソースファイルにその関数の定義を書く

includeディレクティブ

このページでは、今までプログラムの先頭に書いていた、#include <bits/stdc++.h>の意味を理解できることを目標に説明します。

#includeの機能は複雑であるため、出来る限り簡潔に説明します。 細かい仕様などについてはAPG4bの範囲を越えるので、気になる人は調べてください。


ソースファイル

ソースファイルとはソースコードを記述したコンピュータ上のファイルのことです。 これまで、明示的にはソースファイルを扱ってきませんでしたが、実はAtCoderのコードテストではテキストボックスに入力したソースコードが1つのソースファイルとして処理されています。

なお、AtCoderのコードテストでは複数のソースファイルを扱うことができません
外部サービスのWandboxでは複数のソースファイルを扱うことができるので、必要に応じて利用してみてください。


ライブラリ

便利な関数や構造体を書いたソースファイルなどをまとめたものをライブラリといいます。

STLもライブラリの1つです。 STLはC++の一部として用意されるため標準ライブラリと呼ばれます。


#include <bits/stdc++.h>の意味

これまでC++の言語機能と標準ライブラリをあまり区別せずに紹介してきました。

例えば、C++の関数呼び出しや四則演算などの機能は言語機能ですが、 変数の値を出力するために使っていたcoutendlは、実はSTLによって提供されている機能です。

STLなどのライブラリを利用するためには、プログラム中でライブラリを利用することを明示する必要があります。

そして、これまでプログラムの1行目に書いていた「#include <bits/stdc++.h>」は STLを利用するためのものです。

逆に、#include〜を書かなければcoutendlの機能は利用できないことになります。


includeディレクティブ

C++には#include <ファイル>という記法があります。 これをinclude(インクルード)ディレクティブといいます。

includeディレクティブは「指定したファイルの内容をこの命令を書いた部分に展開する」という機能です。

例えば、ソースファイルa.cppとソースファイルb.cppがあったとして、それぞれの内容が以下のようになっていたとします。

a.cpp
// STLを使うためのincludeディレクティブ
#include <bits/stdc++.h>
using namespace std;

// b.cppの内容が展開される
#include "b.cpp"

int main() {
  // fの定義はb.cppに書かれている
  cout << f(10) << endl;
}
b.cpp
// xを2乗して返す
int f(int x) {
  return x * x;
}

展開された後のa.cppは次のようになります。

(bits/stdc++.hの内容)
using namespace std;

// xを2乗して返す
int f(int x) {
  return x * x;
}

int main() {
  // fの定義はb.cppに書かれている
  cout << f(10) << endl;
}
実行結果
100

Wandboxでの実行例

このようにincludeの機能を用いることによって、別のファイルに記述したプログラムを組み込むことができます。

#include <bits/stdc++.h>は「bits/stdc++.hというファイルの内容を、この部分に展開する」という意味になります。

bits/stdc++.hはSTLの関数や構造体が宣言されているファイルです。これをプログラムの先頭に展開することによって、いろいろな機能が利用できるようになります。

includeディレクティブの<ファイル>"ファイル"の違いについては「細かい話」で説明します。


複数のソースファイルでプログラムを書く

サンプルプログラムや問題の解答プログラムなどのような、規模の小さなプログラムでは1つのソースファイルで十分ですが、 ある程度規模の大きいプログラムや、ライブラリを書く際には1つのソースファイルだけでは開発が大変になってきます。

そのような場合にはソースファイルを複数に分割して記述します。 複数のファイルに分けることで、関連するコード毎にまとめたり、別のプログラムを作る際に再利用しやすくなります。

「ファイルを複数に分け、そのソースファイルをインクルードして使う」ということも可能ですが、いくつかの理由により、 「各ソースファイルについて、対応するヘッダファイルと呼ばれるファイルを作り、ヘッダファイルの方をインクルードする」という方法が一般的です。興味のある人は理由を調べてみてください。

ヘッダファイルを用いた例は「詳しい話」を見てください。

ヘッダファイル

プログラムを複数のソースファイルに分けて書く場合に、ソースファイルとは別にヘッダファイルと呼ばれるファイルが用いられます。

ヘッダファイルは、主に関数のプロトタイプ宣言や構造体の宣言などをまとめたファイルです。 慣習的にファイル名.hファイル名.hppという名前のファイル名にすることが多いです。


細かい話

プログラムが実行されるまでの流れ

C++でソースコードを書いてプログラムが実行されるまでの流れを簡単に説明します。

  1. C++でソースコードを書く
  2. ソースファイルとして保存する
  3. コンパイラ呼ばれるプログラムを用いてソースファイルを実行ファイルに変換する
  4. 実行ファイルを実行する

ソースファイルに書かれているC++のソースコードは人間が読みやすい形で書かれているため、コンピュータが直接解釈して実行するのが困難です。

そこで、ソースコードを実行ファイルというコンピュータが扱いやすい形式のファイルに変換します。 この変換処理のことをコンパイルといい、コンパイルを行うのがコンパイラと呼ばれるプログラムです。

AtCoderのオンラインジャッジやコードテストの環境では、2〜4のステップが自動的に行われるようになっているため、C++のソースコードを直接実行しているような感覚でプログラムを作成することができます。


プリプロセッサ

厳密にはincludeディレクティブはプリプロセッサによって処理されます。

プリプロセッサはコンパイルの前段階の処理を行うもので、ソースコードを書き換えるような処理を行います。

プリプロセッサの処理はコンパイルの直前に行われるため、プログラムの実行速度に影響が出るものではありません。 よって、たくさんのファイルをインクルードしたからといって、プログラムの実行性能が悪くなるようなことはありません。

他にも、defineディレクティブ(定数やマクロ)の展開もプリプロセッサが行います。


includeするファイルの検索

includeディレクティブがある場合に、C++のコンパイラは、ファイル名にマッチするファイルを探します。

上の例では、標準ライブラリをインクルードするときに#include <bits/stdc++.h>のように書き、自分で用意したb.cppをインクルードするときには#include "b.cpp"のように書いていました。

ファイルをインクルードするには次の2通りの書き方ができます。

  1. #include <ファイル>
  2. #include "ファイル"

1つ目の書き方は、標準ライブラリなど、システムにインストールされているライブラリのファイルをインクルードする場合に用います。 この場合、標準で登録されているディレクトリを順番に見ていき、マッチするファイルを探して展開します。

コンパイラのオプションで指定したディレクトリを検索対象に追加することもできます。

2つ目の書き方は、自分で用意したファイルをインクルードする場合に用いることが多いです。 この場合では、初めに「このファイルと同じディレクトリ」の中を探します。 マッチするファイルが見つからなかった場合には、1つ目の書き方と同じ動作になります。

includeディレクティブをどう扱うかは、厳密には処理系によって異なりますが、多くの環境で上のような動作になっています。


bits/stdc++.h

STLを用いるためにbits/stdc++.hというファイルをインクルードしていることを説明しました。

このファイルは、「標準ライブラリに関連する全てのヘッダファイルをインクルードするファイル」です。

実は、STLは複数のファイルに分けられて書かれています。 例えば、入出力関連のcincoutなどの機能はiostreamというヘッダファイルに書かれています。 またstring関連の機能はstringというヘッダファイルに書かれています。

bits/stdc++.hはこれらのSTLのヘッダファイルを全てインクルードするためのヘッダファイルです。

このファイルはGCC(C++コンパイラ)によって用意されているもので、他の環境(ClangやVC++など)では使えません。 bits/stdc++.hの使えない環境では、次で紹介するように必要なヘッダファイルを書き並べる必要があります。

本来は必要な機能に応じてそれぞれincludeディレクティブを書くのが正しい使い方ですが、 簡単なプログラムを書く際にはbits/stdc++.hは便利です。

個別の機能毎にヘッダファイルをインクルードする際は、以下を参考にインクルードするようにしてください。

#include <iostream> // cout, endl, cin
#include <string> // string, to_string, stoi
#include <vector> // vector
#include <algorithm> // min, max, swap, sort, reverse, lower_bound, upper_bound
#include <utility> // pair, make_pair
#include <tuple> // tuple, make_tuple
#include <cstdint> // int64_t, int*_t
#include <cstdio> // printf
#include <map> // map
#include <queue> // queue, priority_queue
#include <set> // set
#include <stack> // stack
#include <deque> // deque
#include <unordered_map> // unordered_map
#include <unordered_set> // unordered_set
#include <bitset> // bitset
#include <cctype> // isupper, islower, isdigit, toupper, tolower

ここで挙げていない機能に関してはcppreference.comなどを参照してください。 cppreference.comでは各機能のページに「ヘッダ <ファイル名> で定義」のように書かれています。


ヘッダファイルを用いる例

以下の例についてコンパイルの流れを説明します。

Wandboxでの実行例

proc.cc(一番左のデフォルトのファイル)

#include <bits/stdc++.h>
using namespace std;

#include "b.h"

int main() {
  cout << f(10) << endl;
}

b.cpp

int f(int x) {
  return x * x;
}

b.h

int f(int x);
実行結果
100

この例は、proc.ccb.cppという2つのソースファイルと、b.hという1つのヘッダファイルからなります。

b.hには、b.cppで定義されている関数fのプロトタイプ宣言が書かれています。

includeディレクティブの展開

プリプロセッサによってincludeディレクティブが展開された後は次のようになっています。

include展開後のproc.cc

(bits/stdc++.hの内容)
using namespace std;

int f(int x);

int main() {
  cout << f(10) << endl;
}

b.cpp

int f(int x) {
  return x * x;
}
それぞれをコンパイルして一時ファイルを生成

次にこれらのソースファイルが別々にコンパイルされ、proc.ob.oのような一時ファイルが作られます。

この段階で、proc.cc側から見ると定義の無い関数fを呼び出していることになります。

リンク

最後にリンクという処理によりproc.ob.oから1つの実行ファイルが作られます。 このリンク時の処理で、定義の無かった関数fb.cppに書かれたfの定義と繋がり、最終的に正しく実行できる実行ファイルになります。


少し複雑な例

3つのソースファイルでコンパイルする少し複雑な例を紹介します。

3つのソースファイルでコンパイルする例

proc.cc

#include <bits/stdc++.h>
using namespace std;

#include "b.h"
#include "c.h"

int main() {
  cout << f(10) << endl;
  cout << g(10) << endl;
}

b.cpp

int f(int x) {
  return x * x;
}

b.h

int f(int x);

c.cpp

#include "b.h"

int g(int x) {
  return f(f(x));
}

c.h

int g(int x);

proc.ccからb.cppで定義されたfと、c.cppで定義されたgを呼び出しています。 c.cppgではb.cppで定義されたfを呼び出しています。

なお、Wandbox上でソースファイルとヘッダファイルを用いて複数ファイルでコンパイルする際には、左側の「Compiler options:」にファイル名を改行区切りで列挙する必要があります。


インクルードガード

ヘッダファイルの中で別のヘッダファイルをインクルードする際に、循環参照の問題が発生することがあります。

例えば、a.hというヘッダファイルの中でb.hをインクルードしていて、b.hの中でa.hをインクルードしているとすると、これらのファイルは相互に参照し合っているため、無限に展開されることになってしまいコンパイルできません。

このような状況を防ぐために、ファイルの展開を一度しか展開しないようにする必要があります。 これをインクルードガードといいます。

インクルードガードを行うには、ヘッダファイルの1行目に#pragma onceを書きます。

この方法はコンパイラによる拡張機能ですが、多くのコンパイラで利用可能です。

インクルードガードを追加したb.hの例

#pragma once
int f(int x);

このような単純な例ではインクルードガードは必要ではありませんが、 常にインクルードガードを書くようにすることでミスを防ぐことができます。


前のページ | 次のページ