記事一覧

第11問

配列に格納された整数値から10の倍数を見つけて表示するプログラムを作りました。このプログラムは正しいものですが、少しだけ改善できる点があります。

どこが改善できるか分かりますか?
main.c
#include <stdio.h>
#include <stdlib.h>

int main(void) {
  int values[10] = { 5, 3, 14, -6, 20, 372, 1000, 18 };

  for (int i=0; i<10; i++) {
    if (values[i] % 10 == 0) {
      printf("values[%d] is a multiple of %d.\n", i, 10);
    }
  }

  return EXIT_SUCCESS;
}
実行結果
values[4] is a multiple of 10.
values[6] is a multiple of 10.
values[8] is a multiple of 10.
values[9] is a multiple of 10.
最初のほうで、要素数が10の配列を初期化しているのが分かるかしら?
はい。5とか3とかバラバラの整数が10個……ん?8個しかないですよ?
この場合、残りは0を指定したのと同じ意味になるのよ。
なるほど。そうしたら、配列の最後の2つには0が入るんですね。
そういうこと。じゃあ、forループのところにある数字の10は何を指していると思う?
これも配列の要素数……ですよね。
そうね。if文の条件式にある10はどう?
ええと、こっちは10の倍数かどうかを判定するための10ですかね。
うん。ちゃんと解読できたようね。
はい、なんとか……。でも少しややこしかったです。
もう少し意味が分かりやすい書き方がないか考えてみましょう。

第10問の答え

答え

コマンドライン引数が必ず与えられる前提になっているのが間違い!
  if (strstr(argv[1], text) != 0) {
    ……
コマンドライン引数の数をチェックするのが正解!
  if (
    (argc >= 2) &&
    (strstr(argv[1], text) != 0)
  ) {
    ……

解説

このプログラムの問題点は、コマンドライン引数の数をチェックしていないことでした。最初のif文で、いきなりargv[1]にアクセスしていますね。

  if (strstr(argv[1], text) != 0) {
    ……

1つ目のコマンドライン引数が必ずあると思って処理してしまっているため、コマンドライン引数が1つも与えられなかったときにクラッシュしてしまいます。

修正の方法はいくつか考えられますが、例えばこのようにすればクラッシュを避けられるでしょう。

  if (
    (argc >= 2) &&
    (strstr(argv[1], text) != 0)
  ) {
    ……

コマンドライン引数が1つはあることをargcで確認してから、argv[1]にアクセスするよう手直ししました。

これって、ケアレスミスですよね?
そうね。
こういうミスを防ぐ方法って何かあるんですか?
それは難しいんじゃないかしら。人間だもの。
ええっ!じゃあ、どうすれば……。
ミスしたところがないか、確認する時間をつくるのがいいわね。

プログラミングをする以上、完全にミスを防ぐことはできません。今回の問題は単純なミスですが、何かに気を取られて同じような間違え方をしてしまうこともあるでしょう。それは、しかたのないことです。ミスしないように頑張るよりも、あとからミスに気付くことができる方法を考えるほうが生産的かもしれません。

例えば、コードレビューの時間をとる方法があります。プログラムを作ったら、「期待どおりに動かないケースはないかな」と考えてみましょう。100%とはいかないまでも、ある程度のミスを早期発見できるはずです。

ここがポイント!
プログラムにミスがないか、確認する時間をとりましょう!

修正後のプログラム

main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[]) {
  char text[] = "hello";

  if (
    (argc >= 2) &&
    (strstr(argv[1], text) != 0)
  ) {
    printf("You said %s.\n", argv[1]);

    return EXIT_SUCCESS;

  } else {
    printf("Say %s.\n", text);

    return EXIT_FAILURE;
  }
}
実行結果
↓コマンドライン引数に「hello」が含まれる場合(「hello-world」など):
You said hello-world.

↓コマンドライン引数に「hello」が含まれない場合:
Say hello.

第10問

コマンドラインから受け取ったあいさつをオウム返しするプログラムを作りました。実行してみると、おおむね期待通りの表示が得られました。でも、このプログラムには大きな問題があります。

どのように修正すべきか分かりますか?
main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[]) {
  char text[] = "hello";

  if (strstr(argv[1], text) != 0) {
    printf("You said %s.\n", argv[1]);

    return EXIT_SUCCESS;

  } else {
    printf("Say %s.\n", text);

    return EXIT_FAILURE;
  }
}
期待される実行結果
↓コマンドライン引数に「hello」が含まれる場合(「hello-world」など):
You said hello-world.

↓コマンドライン引数に「hello」が含まれない場合:
Say hello.
strstr()って何をする関数ですか?
これはね、1つ目の文字列に2つ目の文字列と同じものが含まれるかチェックする関数よ。
ということは……。argv[1]はコマンドライン引数だから……。
うんうん。
コマンドライン引数にhelloが含まれてたらオウム返しするんですね。
そういうことね。
で、それ以外のときはSay hello.と表示して、EXIT_FAILUREを返すから失敗ってわけですね!
そういうことなんだけど、ちゃんと動かないのよ。
プログラムが期待どおりに動かない条件を考えてみましょう。

第09問の答え

答え

main()の戻り値が数値になっている!
  return 0; /* 成功 */
main()の戻り値にはEXIT_SUCCESSなどを使うほうがベター!
  return EXIT_SUCCESS;

解説

main()の戻り値は、OSに返却される値です。この値によって、OSはプログラムが正常に処理を終えたのかどうかを判断します。ほとんどの場合、値の意味は次のように決まっています。

  • 0のとき:すべての処理に成功した(正常終了)
  • 0以外のとき:何らかの処理に失敗した(異常終了)

正常終了したときは、main()に次のように書いても問題になることは少ないでしょう。

  return 0;

厳密に言えば、OSに返却する値の意味はOSに依存します。そのため、ポータビリティ(どの環境でもうまく動くこと)を考えれば、数値をじかに書くやり方は望ましいとはいえません。

そこでC言語には、標準で利用できる次の2つの値が用意されています。

  • EXIT_SUCCESS:正常終了を意味する値
  • EXIT_FAILURE:異常終了を意味する値

これらは、<stdlib.h>をインクルードすると使えるようになります。実際のところは「EXIT_SUCCESSとは0のことだ」と思ってほぼ間違いないのですが、次のように書いたほうがプログラムの意図が明確になりますね。

  return EXIT_SUCCESS;
なるべく標準的な書き方をしたほうが、プログラムを作ったときの気持ちが伝わりやすいって感じですかね。
そうそう。気持ちを伝えるって大事なことでしょ?
大事ですねー。
ここがポイント!
標準的な書き方を心がければ、プログラムの意図はもっと伝わりやすくなる!

修正後のプログラム

main.c
#include <stdio.h>
#include <stdlib.h>

void Greet(const char *pName) {
  printf("Hello, %s!\n", pName);
}

int main(int argc, char *argv[]) {
  if (argc < 2) {
    puts("No input.");

    return EXIT_FAILURE;
  }

  for (int i=1; i<argc; i++) {
    Greet(argv[i]);
  }

  return EXIT_SUCCESS;
}
実行結果
↓コマンドライン引数が「Leo Yuki」の場合:
Hello, Leo!
Hello, Yuki!

↓コマンドライン引数なしの場合:
No input.

第09問

コマンドラインから名前を受け取って、あいさつを表示するプログラムを作りました。プログラムを実行すると期待どおりに表示されましたが、あと少し改善できる部分があります。

どこが改善できるか分かりますか?
main.c
#include <stdio.h>

void Greet(const char *pName) {
  printf("Hello, %s!\n", pName);
}

int main(int argc, char *argv[]) {
  if (argc < 2) {
    puts("No input.");

    return 1; /* 失敗 */
  }

  for (int i=1; i<argc; i++) {
    Greet(argv[i]);
  }

  return 0; /* 成功 */
}
実行結果
↓コマンドライン引数が「Leo Yuki」の場合:
Hello, Leo!
Hello, Yuki!

↓コマンドライン引数なしの場合:
No input.
これは、第04問の変形ですね。
そうね。コマンドライン引数で1つ以上名前を指定すると……
ひとりひとりの名前であいさつを表示します!
それじゃあ、名前を1つも指定しなかった場合は……
「失敗」になって、プログラムを終了します!
あいさつを表示できれば「成功」、できなければ「失敗」ということね。
ちゃんと表示されてるのに、まだ改善できるところなんてあるのかなぁ。
これまでに出てきたプログラムと見比べてみましょう。

第08問の答え

答え

標準関数を自分で宣言しているのが間違い!
long strlen(char *s);
標準関数はインクルードして使うのが正解!
#include <string.h>

解説

標準関数はすべて、規格で形式が決められています。strlen()の場合はこうです。

size_t strlen(const char *s);

問題のプログラムでは、違う形式で宣言してしまっていますね。これがエラーの原因です。

つまり、正しい形式で宣言すればエラーが出なくなるわけですね。
まぁ、それはそうなんだけれども……。

標準関数は、そもそも自分で宣言する必要がありません。対応するヘッダーファイルを、ただインクルードすればよいのです。strlen()のような文字列を操作する関数は、<string.h>に宣言があります。使い方を忘れてしまっても、リファレンスを確認すればしっかり書いてあるはずです。

なるほど。言われてみれば当たり前って感じがしますね。
でしょ。
はい。こういう間違いはしない気がします!
ふふふ。そう願いたいわね。

実はこれ、あるベテランのソフトウェアエンジニアが本当にやっていた間違いをベースにした出題でした。その人は、わざわざコンパイラのオプションでエラーをねじ伏せておいて、「なぜクラッシュするんだ」と悩んでいました。そのくせ自信があるものだから、リファレンスもろくに読まないわけです。間違えるはずがないと思い込んでいると、深みにハマッて抜け出せなくなることもあるんですね。

ここがポイント!
標準関数は、しっかり使い方を調べてから使いましょう!

修正後のプログラム

main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
  char textA[] = "Hello!";
  char textB[] = "Hi!";

  if (strlen(textA) > strlen(textB)) {
    printf("%s is longer then %s\n", textA, textB);
  }

  return EXIT_SUCCESS;
}
実行結果
Hello! is longer then Hi!

自分にあったツールを揃えておく

ユキ先輩、設計力と実装力をもっと身に付けたいと思っているんですが、それにはやっぱりひたすらコードを書くのがいいんでしょうか?
そうね、いろいろなプログラムを書いて経験を重ねるのももちろん必要だけど、それ以外にも考えておくといいことがあるわね。
そうなんですね!教えてください!

良い仕事をしたければ、まずは良い道具を揃えることが大切です。

野球の選手でも、料理人でも、マジシャンでも、まげわっぱ作りの職人でも、良い仕事をする人は道具を大事にしているのではないでしょうか。

使いやすく質の良い道具は、ソフトウェアエンジニアとしての仕事の質を向上させてくれます。例えば、以下のようなものがひととおり揃っていると、作業効率も上がるでしょう。

  • プログラムを書いたり実行したりするのに、十分な処理速度があるパソコン
  • 手に馴染むキーボードやマウス、長時間座っていても疲れにくいイス
  • 使いやすいテキストエディターやIDE(軽快に動く、使用するプログラミング言語の仕様に合わせて色分けしてくれる、など)
  • チームで共同作業をするのに便利なクラウドツール(共有ストレージ、プロジェクト管理ツール、など)

会社から支給される備品やチームで導入しているツールを自分の好みによって変えるのは難しいこともありますが、なるべくストレスを感じずに作業できるように工夫してみましょう。

僕は肩がこらないように、アームレストを使ってますよ。あと、その日にやることを自分専用のToDoリストに入れて毎朝チェックするようにしています!
とても良いことね!
ここがポイント!
自分にあった道具や方法を揃えておくと、効率的に仕事が進められます。できる範囲で工夫してみよう!

ほかにも、誰も持っていないような自分だけの道具を用意しておくことは、とても大切です。ソフトウェアエンジニアなら、自作のツールやスクリプト、サブルーチンやクラスライブラリなどです。ちょっとした気分転換の方法なども、設計のヒラメキを得るためのツールといえるかもしれません。

もちろん、どのようなツールが使いやすいかは、人によって異なります。普段は周りの流儀に合わせるタイプの人でも、皆それなりに自分のスタイルをもっているものです。

変数命名法一つをとってみても、好みは千差万別でしょう。試しに、あなたの周りにいる開発者にも聞いてみると、案外おもしろいかもしれませんよ?

ちなみにユキ先輩は、どんな命名法が好みなんですか?
じゃあ、次からは私が普段から使っている命名法について紹介するわね。
やったー!お願いしま〜す!(ワクワク)

第08問

文字列の長さを比較するプログラムを作ろうとしています。でも、コンパイルエラーで動きません。

何が間違っているか分かりますか?
main.c
#include <stdio.h>
#include <stdlib.h>

long strlen(char *s);

int main(void) {
  char textA[] = "Hello!";
  char textB[] = "Hi!";

  if (strlen(textA) > strlen(textB)) {
    printf("%s is longer then %s\n", textA, textB);
  }

  return EXIT_SUCCESS;
}
期待される実行結果
Hello! is longer then Hi!
手前のほうで宣言しているstrlen()が、文字列の長さを数える関数ってことですよね。
そうね。
宣言があるからmain()から呼び出せるはずだと思うんですけど……。
でもエラーになるのよ。
strlen()は標準関数だったような……。

第07問の答え

答え

この書き方だと引数が「不定」になってしまう!
void SayHello();
引数が「ない」ときは、こう書くのが正解!
void SayHello(void);

解説

今回のプログラムは、第06問と似ていますね。サブルーチンSayHello()を手前で宣言して、main()から呼び出せるようにしています。ただし、SayHello()には引数がありません。

こういう場合、引数リストを省略して()と書くと、C言語では引数が「不定」という扱いになってしまいます。引数が「ない」ときは、そのことを(void)と明示しなければなりません。

そのため、SayHello()の宣言は、正しくはこう書きます。

void SayHello(void);

定義するときも同様で、こうです。

void SayHello(void) {
  ……
}

さらに、main()も同様です。

int main(void) {
  ……
}
質問でーす!引数が「不定」の関数って、どういうときに使うんですか?
それがねぇ、使わないのよ。
ええっ!
引数が「不定」っていうのはね、古いバージョンのコンパイラの仕様なの。
昔の書き方ってことですか?
そうそう。古いプログラムが動かなくなってしまわないように、昔の書き方も許されているということね。

なおC++では、引数リストを()と省略すると、(void)と同じように引数「なし」の意味になります。C言語よりも新しいプログラミング言語なので、歴史的な「しがらみ」も少ないというわけです。

ここがポイント!
引数「なし」の関数は(void)と書こう!

修正後のプログラム

main.c
#include <stdio.h>
#include <stdlib.h>

void SayHello(void);

int main(void) {
  SayHello();

  return EXIT_SUCCESS;
}

void SayHello(void) {
  puts("Hello!");
}
実行結果
Hello!

第07問

短いテキストを表示するプログラムを作りました。期待通りの表示にはなったのですが、コンパイル時に警告が出てしまいます。

どこを修正すべきか分かりますか?
main.c
#include <stdio.h>
#include <stdlib.h>

void SayHello();

int main() {
  SayHello();

  return EXIT_SUCCESS;
}

void SayHello() {
  puts("Hello!");
}
期待される実行結果
Hello!
これって、ハローワールドをサブルーチンにしただけですよね。
そうよ。でも警告が出るのよね。
うーん、すごく単純なプログラムだと思うんだけどなぁ……。
C++のコンパイラでは、同じプログラムでも警告されません。