C++を勉強していた時期から苦手だったのがプリプロセッサでした。
インクルートガードに#ifndefやら#defineやら#pragma onceやら…。
C++に限った話ではありませんが、書籍などでは紙面の関係上ソースファイルを大きく分けたりしないためかプリプロセッサの説明などが不十分であるような気もします。
そんな状況の中でも既存のソースコードなどを見ると#if definedや#pragmaが頻繁に登場して本当に辟易することがあります。
今回は復習も兼ねて順番に確認し直そうと思います。
#include
#includeプリプロセッサ・ディレクティブはマクロやクラス定義などが記述されたヘッダーファイルを読み込みます。
1 2 |
#include <iostream> #include "something.h" |
上記の構文はどちらも正しいですが、山かっこ形式と引用符形式では囲まれたヘッダーファイルをどの順番で探し出すかが異なります。
基本的にはコンパイラをインストールした際に付属するヘッダーファイルを検索する場合は山かっこ形式を、コンパイルするソースファイルがあるディレクトリにあるヘッダーファイルを検索する場合は引用符形式で指定します。
ざっくり言えば、iostreamなど標準のヘッダーファイルであれば<>(山かっこ形式)を、それ以外のものは””(引用符形式)で指定すれば大体は問題ありません。
#define
#defineディレクティブは本当に利用の幅が広いです。
基本的にはマクロの定義を行うプリプロセッサとなります。
1 |
#define VERSION 3.14 |
上記のように記載されていれば、コンパイラが「VERSIONと記述された箇所を3.14に置換」します。
つまり、
1 2 3 4 5 6 7 |
#include <iostream> #define VERSION 3.14 void main() { double pi = VERSION; std::cout << pi; } |
と記述されれば、VERSIONが3.14に置換され、double型変数piは3.14で初期化されます。
関数形式マクロ
上記のような使い方以外にも#defineの置換はさまざまな場面で利用されます。
1 2 3 4 5 6 |
#include <iostream> #define MAX(a, b) ((a) > (b) ? (a) : (b)) int main() { std::cout << MAX(1, 2) << std::endl; } |
プリプロセッサにより、MAX(1, 2)は以下のように置換されます。
1 |
std::cout << ((1) > (2) ? (1) : (2)) << std::endl; |
結果、コンソールには[ 2 ]と表示されます。
まとめ
この辺りはまだ簡単に思えますが、#defineが曲者です。
#ifdef, #ifndef, #if defined, #if ! defined, #undef, #演算子, ##演算子と羅列したプリプロセッサは全て#defineと関連してきます。
上手く使えればデバッグ作業やコメントアウトなどに非常に便利ですが、便利すぎていろいろなところで出てくるのでしっかり復習していきます!