第7回
制御構造と変数(3)~switch caseによる多枝分岐

ローマ字変換プログラムを作る

ifを使った例として、前回は半角英文字の大文字←→小文字の変換処理を紹介しました。今度は、同じような要領で半角英文字のローマ字をひらがなに変換して表示する処理を作ってみましょう。

母音を“かな”に変換

あまり複雑な仕様にすると本来の目的である制御構造の話題から逸れてしまうため、ここでは『母音』だけに絞ります。つまり
キーボードから『A・I・U・E・O』のどれかが入力されたら
それに対応するローマ字の読み『あ・い・う・え・お』が
表示される
というシンプルな仕様です。

プログラムはリスト1のようになります。関数toromajiが変換処理の本体で、main関数ではscanf関数で半角英文字を1文字受け取って、それをtoromaji関数に渡して対応する「かな」に変換し、printf関数で表示します。

例えば、キーボードから'E'の1文字を受け取ったら、それに対応して「え」という仮名文字が表示されます。

1文字でも文字列

toromaji関数は引数にint型の1文字(1バイトの英文字)を受け取り、それをifで逐一調べて『あ・い・う・え・お』のひらがなに変換します。戻り値はありません。従って、関数の定義行は以下のようになります。
void toromaji(int c) {
ひらがなは多バイト文字なので、Cでは1文字でも文字列として扱われます。関数の戻り値を文字列へのポインタとしても構わないのですが、少し複雑になるため、ここでは関数の外側で以下のようにchar型配列のstrをグローバル変数として用意しておき、そこにtoromaji関数の変換結果であるひらがなを文字列として保存するようにしています。

char str[4];

ifによる判断と文字列のコピー

toromaji関数の内部は、以下のようにifによる条件判定の連続です。
if (c == 'A') strcpy(str, "あ");
if (c == 'I') strcpy(str, "い");
if (c == 'U') strcpy(str, "う");
if (c == 'E') strcpy(str, "え");
if (c == 'O') strcpy(str, "お");
strcpyは、char型配列に文字列をコピーする関数です。String型を持つVisual Basicでは文字列変数に文字列定数(リテラル)を=演算子を使って代入できますが ※1 、文字列をchara型の配列として扱うCでは、配列の各要素に1文字ずつ値をコピーする関数strcpyを使います。

strcpyの書式は以下のようになっています。
strcpy(<コピー先配列>, <コピー元文字列>);
実際にはSet命令を使い“Set StrMsg = "Hello1"”のように記述します。Setは省略できるため、通常は“StrMsg = "Hello1"”のよう記述するのが慣例となっています

リスト1:半角英文字(母音)をローマ字のかなに変換するプログラム(ex0701.cex0701.exe)
#include <stdio.h>
#include <string.h>

/* 関数の宣言 */
void toromaji(int);
/* 文字列をグローバル変数で宣言 */
char str[4];

int main(void)
{
  char c;

  printf("Input Charactor : ");
  scanf("%c", &c);
  toromaji((int)c);
    printf(" --> %s\n", str);
}

void toromaji(int c) {
  if (c == 'A') strcpy(str, "あ");
  if (c == 'I') strcpy(str, "い");
  if (c == 'U') strcpy(str, "う");
  if (c == 'E') strcpy(str, "え");
  if (c == 'O') strcpy(str, "お");
}

無駄な動作をなくす

リスト1では、上に示したようにifによる条件判定を5回連続で実行しています。どの行も、条件が成立した場合(真の場合)に続く1行(strcpy関数による文字列のコピー)を実行するだけなので、このような単純な形になりました。

しかし、この書き方には問題があります。ifに対応するelseがないため、最初のif文で条件が成立した(入力された文字が'A'だった)場合でも、残りのif文をすべて実行してしまうのです。もちろん、どの条件にも合致しないので処理結果に影響はありませんが、実行しなくてもいい命令を実行するため、処理に無駄が生じます。

上記の処理は、本来なら以下のようにif~else if~elseの構造を用いるべきです。
if (c == 'A') strcpy(str, "あ");
else if (c == 'I') strcpy(str, "い");
else if (c == 'U') strcpy(str, "う");
else if (c == 'E') strcpy(str, "え");
else if (c == 'O') strcpy(str, "お");
else strcpy(str, "?");

構造を分かりやすくする

このようにすることで、入力された文字(toromaji関数の引数)が『A・I・U・E・O』以外だった場合、画面に「?」と表示できるようになります。

ifでもelse ifでも実行する命令は1行だけなので、{ }で囲む必要はありません。ただ、{ }がないと見た目に分かりにくい印象もあるので、ソースは長くなりますが、リスト2のようにした方がいいでしょう。

この他、リスト3のようにifのネストを使うという方法もありますが、同じような形式のif文が続いているためかえって煩雑になり、構造が分かりにくくなります。“ { ” と “ } ”の対応関係をチェックするだけでも大変ですね。

いずれにせよ、基本の条件設定が共通している条件判定でifを連続して用いると、本来はシンプルなはずの構造が非常に分かりづらくなる傾向があります。それは、比較元の変数(例では入力された英字1文字を保存した変数c)が1つだけなのに、それぞれのifの条件式で同じ変数を何度も評価しているためです。

リスト2:処理を{ }で囲んで構造を分かりやすくした(ex0702.cex0702.exe)
  if (c == 'A') {
    strcpy(str, "あ");
  } else if (c == 'I') {
    strcpy(str, "い");
  } else if (c == 'U') {
    strcpy(str, "う");
  } else if (c == 'E') {
    strcpy(str, "え");
  } else if (c == 'O') {
    strcpy(str, "お");
  } else {
    strcpy(str, "?");
  }

リスト3:ifをネストすると構造が分かりにくくなる(ex0703.cex0703.exe)
  if (c == 'A') {
    strcpy(str, "あ");
  } else {
    if (c == 'I') {
      strcpy(str, "い");
    } else {
      if (c == 'U') {
        strcpy(str, "う");
      } else {
        if (c == 'E') {
          strcpy(str, "え");
        } else {
           if (c == 'O') {
             strcpy(str, "お");
           } else {
              strcpy(str, "?");
           }
         }
       }
     }
   }