質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.37%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

2回答

938閲覧

ファイルの分割について

DMR

総合スコア6

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

1グッド

0クリップ

投稿2020/08/20 15:27

ファイルの分割で #ifdef,#define,#endifの意味が分かりません。初心者に理解できるように教えてください。例文などあれば助かります。

SaitoAtsushi👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

cateye

2020/08/20 16:14

define(英語)の意味は分かりますか?
mingos

2020/08/20 16:30

たぶんインクルードガードの事を言いたいのでしょうが、せめて具体的なコードを示して、 ここが分からないといった質問をしたほうが回答がつきやすいですよ。
guest

回答2

0

ベストアンサー

ファイルの分割と、条件コンパイルは別の話題です。
それぞれがわからないのか、どちらも単独では理解していても組み合わせたときがわからないのか、どうなんでしょう。


ファイルの分割...といってしまうといろいろありますが、
#includeの働きについて言えば、コンパイルの前段階の処理(プリプロセス)として、指定されたファイルを(""と<>でファイルを探す範囲が決められていますが)その場所に展開する、という働きをします。

C

1///aaa.h 2 printf(

C

1///main.h 2#include <stdio.h> 3int main(void){ 4#include "aaa.h" 5 "Hello, World!\n"); 6 retrun 0; 7}

は、コンパイルの前段階でaaa.hが展開されて

c

1#include <stdio.h> //もちろんこちらも展開されますが、長くなるので略 2int main(void){ 3 printf( ///aaa.hが展開された 4 "Hello, World\n"); 5 return 0; 6}

となって処理が進行します。

このような作用を持つため、分割コンパイルに於いては、共通で使用する関数や変数、型の定義をヘッダファイルに書いておくと、それぞれのソースファイルで展開されるのでソースファイルが「完成」する、ということになるものです。


条件コンパイルもプリプロセス命令です。
#defineは、コンパイル前に第一パラメータの文字列を第二パラメータの文字列に置き換えます。この機能を「マクロ」と呼びます(関数型マクロの話はとりあえず棚上げ)
#ifdefあるいは#if definedは、パラメータとされた文字列(シンボル)がマクロとして定義されていなかった場合、#elseまたは#endifに出会うまでの内容を削除してコンパイルに進みます。定義されていた場合に#elseがあれば、#elseの後#endifまでを削除してコンパイルに進みます。#ifndefあるいは#if not definedは、シンボルがマクロ定義されていた場合、#elseまたは#endifに出会うまでの内容を削除してコンパイルに進みます。

実際には、ソース中の文字列の置き換えの意図がなくても、#ifdef(あるいは同類の#if)で使うためだけにマクロを定義するのもよくあることです。

C

1#define AAA 2///AAAを空文字列に置き換える 3//#defineの行をコメントにあるいは削除して動作を比べてみる 4 5#include <stdio.h> 6int main(void){ 7 printf( 8 #ifdef AAA 9 "AAA defined\n" 10 #else 11 "I don't know 'AAA'\n" 12 #endif 13 ); 14 return 0; 15}

とすると、#define AAAの行があればこれは

C

1int main(void){ 2 printf( 3 "AAA defined\n" 4 ); 5 return 0; 6}

としてコンパイルされ、#define AAAがなければ

C

1int main(void){ 2 printf( 3 "I don't know 'AAA'\n" 4 ); 5 return 0; 6}

してコンパイルされます。


これらを組み合わせて、「インクルードガード」というテクニックがしばしば(常識といっていいレベルで)使われます。

C

1typedef int I; 2typedef int I;

はエラーになりますが、例えば、

C

1///a.h 2typedef int I;

C

1///b.h 2#include "a.h" 3extern I num;

C

1///main.c 2#include "a.h" 3#include "b.h" 4//以下略

などとしてしまうと、これはこれまでの説明でおわかりかと思いますが

C

1///main.c 2//a.hの分 3typedef int I; 4//ここからb.h分 5typedef int I; 6extern I num;

となってしまい、エラーが発生します。

ここで、

C

1///a.h 2#ifndef A_H 3#define A_H 4typedef int I; 5#endif

としておけば、#includeが展開されると

C

1///main.c 2//a.hの分 3#ifndef A_H //ここではA_Hは定義されていない 4#define A_H //ここでA_Hが定義される 5typedef int I; 6#endif 7//ここからb.h分 8#ifndef B_H 9#define B_H 10#ifndef A_H //この時点ではA_Hは定義済なので... 11#define A_H //削除される 12typedef int I; //削除される 13#endif 14extern I num; 15#endif

結果として

C

1///main.c 2typedef int I; 3extern I num;

となり、意図しない二重定義を防げる、ということになります。

まぁ、最近では多分#pragma onceが使える環境が多くなっていると思うので、そちらを使っておけばいいのかも知れません。不用意に_A_HとかやっちゃうとこれはC++では予約済シンボル(アンダースコア+大文字で始まるシンボル)だったりするので、何が起こっても文句が言えないです。

投稿2020/08/20 23:07

thkana

総合スコア7703

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

DMR

2020/08/24 15:51

有難うございました。よくわかりました。後、ヘッダファイルを書くときは、using宣言をしないようにするのですか?
thkana

2020/08/24 22:35

https://teratail.com/questions/286946 なんてのも最近あったのでご参考まで。 よくわかっていただけたならわかると思いますが、ヘッダファイル中にグローバルにusingを使ってしまうと、その影響はインクルードしている側に及んでしまいます。インクルードしている側ではusingを使ったという意識はないわけですから、意図せぬトラブルに巻き込まれるかも知れません。
guest

0

DMRさん

恐らくインクルードガードの質問かと推測して回答します。

インクルードガードは同じヘッダが複数回呼ばれた場合
ifndefからendifまでを無視することで
多重定義を防ぐためのイディオムです。

最初に読み込まれた時defineで定数を定義しておけば
二度目からはifndefに引っかかり定義部分が無視されるという仕組みです。
wikibooks

ちなみに貴方がstd::coutで標準出力を行いたい時など
iostreamヘッダーをインクルードするかと思いますが。

c++

1#include <iostream> 2#include <iostream>

と二回立て続けに書いても多重定義は発生しません。
それはiostream内にもインクルードガードが
組み込まれているからだと聞いたことがあります(実際確認してませんごめんなさい)

あと使うコンパイラによるかと思われますが
インクルードガードを書かなくとも

c++

1#pragma once

の一行でそのファイルを1度しか呼び出せなくすることが出来るので
そちらのほうが簡潔かと思われます。

下記に書き方の例を記します。

インクルードガードの例

c++

1// func.hpp 2 3// define定数が定義されていないなら#endifまで実行 4// (最初の一回は必ず下記の処理が読み込まれるが二度目からは 5// FUNC_HPPが定義されているので下記#endifまでは無視される) 6#ifndef FUNC_HPP 7#define FUNC_HPP// define定数定義 8// この中に宣言や定義を書く 9void func(); 10 11#endif

c++

1// func.cpp 2#include "./func.hpp" 3#include <iostream> 4 5// 実装を記載 6void func(){ 7 std::cout << "funcが呼ばれた" << std::endl; 8}

c++

1// main.cpp 2#include "./func.hpp" 3 4int main(){ 5 func();// 関数func呼び出し 6 return 0; 7}

pragma onceの例

c++

1// func.hpp 2 3#pragma once// この一文のみでこのヘッダは一度しか読み込まれない 4 5void func(); 6

c++

1// func.cpp 2#include "./func.hpp" 3#include <iostream> 4 5// 実装を記載 6void func(){ 7 std::cout << "funcが呼ばれた" << std::endl; 8}

c++

1// main.cpp 2#include "./func.hpp" 3 4int main(){ 5 func();// 関数func呼び出し 6 return 0; 7}

投稿2020/08/20 16:39

編集2020/08/20 16:47
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.37%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問