プリプロセッサ指令

プリプロセッサ指令

前回のC言語講座の記事ではfor文やwhile文などの繰り返し構文について勉強しました。前回と前々回に登場した文法(条件分岐と繰り返し処理)をひっくるめて制御構文と呼んだりします。繰り返し処理も分岐処理も非常に大切な文法なので理解が不十分な方は次回の記事に入る前に復習をよろしくお願いします。

今回の記事で勉強する項目は、毎回サンプルコードに書かれていた以下の文法についてです。

#include <stdio.h>

ここのところ重めの記事が続いていたので今回は短くまとめていきます。

前回の復習

前回はfor文, while文, do-while文について勉強しました。これらは繰り返し処理を行うことができる構文で基本形はそれぞれ以下のようになります。

for(初期化式; 条件式; 更新式) {
  処理1;
  処理2;
}
while(条件式) {
  処理1;
  処理2;
}
do {
  処理1;
  処理2;
} while(条件式);

条件式が成り立っている間だけ処理1と処理2を実行し、条件式が成り立たなくなったら繰り返し処理の下から処理が再開されます。

無限ループについても少し触れましたね。break文とcontinue文についてはたくさんプログラムを書いて実験して理解した方が早いでしょう!

前回の練習問題の解答例

A問題

岩ちょこ君は毎日歩いて大学に通っています。7日分の歩数が与えられるので合計を求めてください。(for文の中でscanf関数を使ってみてください)

答えは32bit以内におさまります。

入力

a_1 a_2 a_3 a_4 a_5 a_6 a_7

a_iはi日目の歩数を表しており、1以上10000以下であることが保証されています。
出力

t

tには7日間の歩数の合計を出力してください。


入力例1

6000 5600 4349 7995 4597 7543 5999

出力例1

42083

6000 + 5600 + 4349 + 7995 + 4597 + 7543 + 5999の計算結果が答えになります。


入力例2

1 2 10000 3 4000 20 1

出力例2

14027

解答例

#include <stdio.h>

int main() {
  int sum = 0, days = 7;
  int count[7];
  for(int i = 0; i < days; i++) {
    scanf("%d", &count[i]);
    sum += count[i];
  }
  printf("%d\n", sum);
  return 0;
}

わざわざ配列を作らなくても適当な変数を一つ用意してsumにどんどん足し合わせていけば実装できますが、配列の作り方の復習を兼ねて作ってみました。

あえてdaysという変数を作っているのは、このプログラムを『7日間の合計じゃなくて365日の合計に変更します』という事態になった時に、変更しなければならない場所を1箇所にまとめておくために作っています。

B問題

岩ちょこくんは偶数が好きです。非負整数nが与えられるので、n以下の全ての偶数を足し合わせたものを出力してください。

入力

n

出力

sum

sumにはn以下の偶数の和を出力してください。


入力例1

7

出力例1

12

7以下の偶数は2, 4, 6が該当します。なので2 + 4 + 6を計算したものが答えになります。


入力例2

100

出力例2

2550

解答例

#include <stdio.h>

int main() {
  int n, sum = 0;
  scanf("%d", &n);
  for(int i = 0; i <= n; i += 2) {
    sum += i;
  }
  printf("%d\n", sum);
  return 0;
}

更新式を2ずつ増やすように書いてあげれば良さそうです。実際に書いてみたのが上のソースコードになります。

for文の波括弧の中にある処理が1文しかないので、波括弧は省略して書くことができます。

C問題

1×1から9×9までの九九の計算を2重ループを用いて計算してください。(for文の波括弧の中にfor文を入れるとどうなるか実験してみるとわかるかも?)

解答例

#include <stdio.h>

int main() {
  for(int i = 1; i < 10; i++) {
    for(int j = 1; j < 10; j++) {
      printf("%d ", i * j);
    }
    printf("\n");
  }
  return 0;
}

前回の記事で泣く泣く削った内容の2重ループについての問題です。for文の内側に繰り返し処理をしたい内容を書くことができるのですが、そこにfor文を入れてあげることで、2重ループと呼ばれるものができます。同じ原理で3重ループ、4重ループもできますが、実行時間がたくさんかかることになるかもしれないので、4重以上のfor文は書かないように気をつけましょう。

ちなみに、このソースコードを実行すると九九の計算結果の桁数の影響でずれて表示されます。これを揃えるためにはフォーマット指定子についての理解を深める必要があります。この講座の最後の方で別建て記事を書くかもしれません。

どうしても揃えたい人は、とりあえず以下のコードを実行してみてください。いじってみればなんとなく掴めてくると思います(6行目の%2dのところです)。

#include <stdio.h>

int main() {
  for(int i = 1; i < 10; i++) {
    for(int j = 1; j < 10; j++) {
      printf("%2d ", i * j);
    }
    printf("\n");
  }
  return 0;
}

プリプロセッサ指令(命令)

今回の内容はプリプロセッサ指令(ディレクティブ)についてです。プリプロセスというのはコンパイルをする前に行う処理(プロセス)のことを指し、プリプロセッサはプリプロセスを行うソフトウェアのことです。

プリプロセッサに対して指示を出すために用いられる文法ディレクティブ(プリプロセッサ指令)と言います(この辺りの用語をごっちゃにして疑似命令と呼んでいる人も一定数いますね(・_・;))。よくわからない人は、C言語におけるディレクティブは『#』から始まる指示のことだなと考えてもらえれば問題ないです。

以下ではC言語で使われる代表的なディレクティブについて勉強していきます。

#include

#include <stdio.h>

毎回のように書いていたこの部分の説明をします。まずincludeディレクティブを用いることでヘッダーファイルと呼ばれるファイルを読み込むことが出来ます。上のソースコードではstdio.hというヘッダーファイルを読み込んでいることになります。

『それじゃあヘッダーファイルって何?』となるかと思います。ヘッダーファイルにはどんなことが書かれているのかというと、いろんな関数や変数に関する定義が書かれています。例えばstdio.hというヘッダーファイルの中にはprintf関数やscanf関数といったものが呼び出された時にどういった処理をするのかが書かれています。つまりincludeディレクティブの指示文を書き忘れるとprintf関数やscanf関数なんかが使えなくなるわけですね。

実際に忘れた場合にどうなるのかみてみましょう。実験として以下のサンプルコードを実行してみます。

int main() {
  printf("Hi!!\n");
  return 0;
}

コンパイルをしたところ以下のようなエラーが出力されました。

tc1101.c:2:3: warning: implicitly declaring library function 'printf' with type
      'int (const char *, ...)' [-Wimplicit-function-declaration]
  printf("Hi!!\n");
  ^
tc1101.c:2:3: note: include the header <stdio.h> or explicitly provide a
      declaration for 'printf'
1 warning generated.

後半を見るとなんとなく『stdio.hというヘッダーファイルをインクルードしてね』と言っているのが分かります。

このようにヘッダーファイルにはprintf関数やscanf関数などを含め様々な関数の説明が書かれています。そのためヘッダーファイルをインクルードしておかないとコンパイルできないことがわかりましたね。

実は今までのC言語講座で勉強してきた関数はprintf関数とscanf関数だけで、C言語にもともと定義されている関数って少ないんじゃないかと思った方もいるかもしれません。

実際はC言語の標準ライブラリには数百の関数が定義されており、関数の処理を定義しているヘッダーファイルもひとつにまとめずにいくつかに分けられています。なので自分が書いたソースコードの中で使われている標準関数については、関係したヘッダーファイルを必ず読み込んであげる必要があるわけです。

stdio.h以外のヘッダーファイルの例として、時間に関係する関数や定数について定義をしているtime.hというヘッダーファイルや、文字列に関係する関数や定数が定義されているstring.hというヘッダーファイルがあります。

関数とヘッダーファイルのリファレンスを全て暗記できるわけではないので、よく使われる関数についてだけおさえておけば良いでしょう。とは言ってもよく使う標準関数は自然と書いているうちに覚えられるので特に意気込んで勉強しなくても大丈夫です。

ちなみに自作ヘッダーファイルについてはC言語講座の後半で説明する予定です。

#define

defineはincludeの次に有名なC言語のディレクティブです。よくマクロなんて言われています。役割としてはある文字列を違うものに置き換える操作をします。

文字だけだとわかりづらいと思うので、いくつか簡単なサンプルコードを試してもらいます。

#include <stdio.h>
#define MOD 13
int main() {
  int a = 80;
  int b = a % MOD;
  printf("%d ≡ %d (mod %d)\n", a, b, MOD);
  return 0;
}

実行結果

80 ≡ 2 (mod 13)

これはMODという名前の部分を13という数値で置き換えられています。これはMODという名前の定数が初期値13で作られたと考えても表向きは変わりません。なのでC言語における定数はマクロを使って作ることになっています。

あえて変数を使わずに定数を使うのは途中で値が変わらないようにするためです。

どういうことかと言うと、int型の変数を作って最初に初期化をしてそれを定数として用いればわざわざマクロを使わなくてもソースコードはかけます。わざわざ定数だけ別の書き方をしなくてもいいと考えるのが普通でしょう。しかし定数をマクロで行うことによるメリットがあります。

1つ目はマクロを使う時は大文字で書くのが慣例となっています。そのため定数であることが一目で分かります。ただこれに関しては、大文字の名前をつけた変数を定義することで同じことができるのでマクロを使った時のメリットとは言いきれませんね。

2つ目のメリットは定数に代入しようとするとコンパイルエラーが出て、ソースコードの間違いに気づくことが出来ます。マクロはコンパイルする前に実行されて、マクロの処理は特定の文字列の部をそのまま違うものに置き換えるだけなので、例えば上記のtc1102.cのコードで『MOD = 20』と書いても、コンパイルされる時には『13 = 20』に置き換わってしまい、文法上そのようなものが存在しないのでエラーになるわけです。気づかないままコンパイルが通ってしまうよりもコンパイルエラーをみてバグを潰せるのでこれはメリットですね。

ここまではマクロの初歩的な扱いを見てきましたが、マクロを応用することで、簡単な処理をまとめたものを作ることが出来ます。例えば以下のサンプルコードを見てください。

#include <stdio.h>
#define REP(i, n) for(int i = 1; i <= n; i++)

int main() {
  int sum = 0, x = 10;
  REP(i, x) {
    sum += i;
  }
  printf("%d\n", sum);
  return 0;
}

実行結果

55

これはfor文をいちいち書くのが大変なので簡潔に書けるようにマクロを使っています。このようにマクロは定数を定義するためだけではなくコーディングの量を減らすためにも用いられます。ただし可読性(読みやすさ)が下がるので多用は避けた方が良いでしょう

本当は以下のサンプルコードのように変数に括弧をつけておいた方が良いです。

#include <stdio.h>
#define MAX(x, y) (((x) > (y))? (x) : (y))

int main() {
  int a, b;
  scanf("%d", &a);
  scanf("%d", &b);
  int ans = MAX(a, b);
  printf("%d\n", ans);
  return 0;
}

実行結果

12 31 //入力
31

ただ初学者の方にはマクロを使った関数は理解しにくいと思うので後回しにしていただいても構いません。とりあえず定数だけ覚えておけば十分です!

ちなみに関数内で使うだけの定数であればconstを用いることで簡単に宣言することが出来ます。

#ifdef, #ifndef, #else, #endif

これらについては自作ヘッダーファイルで勉強するので詳しい使い方はここでは述べませんが、主なディレクティブを一通り並べておいた方がわかりやすいと判断して一応書いておきました。

ひとつ前で紹介した#defineと組み合わせて使うことになります。詳しくは別記事で紹介するので楽しみにしていてくださいね。

ディレクティブというのはプロセッサに対して命令する際に使われる指示文のことを指す。C言語においてはコンパイルの前にプリプロセスを実行するため、前もってファイルを読み込んだりマクロ置換を行ったりする処理が該当する。

まとめ

今回の記事は比較的コンパクトに収まった気がしています(単に話す内容が減っただけかもしれませんが……)。今回の内容はいつもおまじないで片付けられてしまうディレクティブについて説明してきました。少し高度な部分もあるのでおまじないで片付けられてしまうのも分かりますができれば一通り読んだ上でおまじないにしてもらえればいいなと言うのが私の考えです。

今回の内容をざっとまとめておきます。

  • ディレクティブ(プリプロセッサ指令)というのはプリプロセッサに対する命令を記述できる指示文のこを指す。
  • #includeはヘッダーファイルを読み込むためのディレクティブである。
  • #defineはマクロと呼ばれるもので、文字列を違うものに置き換える処理をする。

次回は自作関数と変数のスコープについて話していく予定です。また次回の記事はボリュームが多そうな上にここ最近の記事の内容も濃かったので、今回の練習問題はなしにします。その分復習にあてていただけると助かります……!(^o^)/

for文【C言語講座#10】

自作関数【C言語講座#12】


最後まで記事を見ていただきありがとうございます。また別の記事でお会いできることを祈っております。

Print Friendly, PDF & Email

C言語カテゴリの最新記事