第6回
制御構造と変数(2)~if文の書き方あれこれ

大文字も小文字もまとめて変換

ここまでに紹介した関数は「小文字への変換では引数が大文字であること」と「大文字への変換では引数が小文字であること」を前提としていました。もう少し扱いやすい仕様にしてみましょう。

2つの機能を1つにする

Cのライブラリには、ここで紹介した2つの関数と同じ働きをするtolower(小文字へ変換)とtoupper(大文字へ変換)が用意されています(使用するには、ヘッダファイルctype.hを取り込む必要があります)。

これらを利用して、今度は
引数cが大文字なら小文字へ
小文字なら大文字へ変換して返し
アルファベット以外なら0を返す
という処理を行うchgcase関数を作ってみましょう。リスト6のようになります。

リスト6:大文字は小文字へ、小文字は大文字へと変換する関数chgcase
char chgcase(char c) {
  if ((c >= 'A') && (c <= 'Z')) {
    return (c + 0x20);
  }
  else if ((c >= 'a') && (c <= 'z')) {
    return (c - 0x20);
  }
  else {
    return (0);
  }
}

大文字/小文字の判定関数を使う

引数が大文字か小文字かを判定する関数も、標準ライブラリに収められています。isupperとislowerです。それぞれ、ctype.h内で以下のように宣言されています ※2

int isupper(int c);
int islower(int c);
isupperは引数が大文字のときに0以外(真)、そうでないときには0(偽)を返します。islowerは引数が小文字のときに0以外(真)、そうでないときには0(偽)を返します。どちらも引数がint型である点に注意しましょう。

これらの関数を利用するには、ctype.hを取り込んでおく必要があります。これらの関数を使えば、ifの条件式が簡単に記述できます。2つの関数に合わせて、戻り値と引数をint型にしておきましょう。

同時に、#defineプリプロセッサ司令を使い、偽(0)に_FALSE、真(!0)に_TRUEという記号定数を定義しておきましょう。また、1文字を保持する変数cに対して加減算する定数0x20(大文字と小文字の差)にも、_DIFFERという記号定数を割り当てておくと、ソースが読みやすくなります。

#define _FALSE 0        /* 偽 */
#define _TRUE  !_FALSE  /* 真 */
#define  _DIFFER (0x20)  /* 大文字と小文字の差 */
ソースはリスト7のようになります。これらの関数は、リスト8のようなプログラムで動作をテストできます。

なお、リスト8のアンダーラインの箇所では、char型の変数cをchgcaseの定義に合わせてint型に型変換しています。このように、変数の前に( )で囲んで型を指定する強制的な型変換を「キャスト」と言います。

is~という文字種判定の関数はたくさんあり、実際には、ctype.h内ではもっと複雑な宣言が行われています
リスト7:isupper関数とislower関数を利用したchgcase
#include <ctype.h>

#define _FALSE 0         /* 偽 */
#define _TRUE  !_FALSE   /* 真 */
#define  _DIFFER (0x20)  /* 大文字と小文字の差 */

int chgcase(int c) {
  if (isupper(c) == _TRUE) {
    return (c + _DIFFER);
  }
  else if (islower(c) == _TRUE) {
    return (c - _DIFFER);
  }
  else {
    return (0);
  }
}

リスト8:リスト7の関数をテストするためのプログラム
#include <stdio.h>
#include <ctype.h>

#define _FALSE 0         /* 偽 */
#define _TRUE  !_FALSE   /* 真 */
#define  _DIFFER (0x20)  /* 大文字と小文字の差 */

/* 関数の宣言 */
int chgcase(int c);

int main(void)
{
  char c;

  printf("Input Upper Charactor : ");
  scanf("%c", &c);
  if (chgcase((int)c) == 0) {
    printf("英字を入力してください。\n");
  } else {
    printf(" --> %c\n", chgcase((int)c));
                                ~~~~~
                                  ↑charをintにキャスト
  }
}

/* 関数の定義 */
int chgcase(int c) {
  if (isupper(c) == _TRUE) {
    return (c + _DIFFER);
  }
  else if (islower(c) == _TRUE) {
    return (c - _DIFFER);
  }
  else {
    return (0);
  }
}

比較演算の省略

リスト7では、
if (isupeer(c) == _TRUE)
elseif (islower(c) == _TRUE)
のように条件式でisupperまたはislower関数の戻り値を、#defineプリプロセッサ指令で定義した記号定数_TRUEと等しいかどうか比較演算子==を使って調べています。

が、これら関数の戻り値は『偽の場合に0』となっているため、以下のように書くことができます。

if (isupeer(c))
else if (islower(c))
isupperとislower関数では結果が真のときに0以外の値が返されるため、ifの条件式では比較演算子を使って記号定数_TRUEと比べなくても、単純に関数の呼び出しだけを記述しておけば、戻り値が0のときには偽、それ以外のときには真と判定されます。

すると、リスト7はリスト9のように簡略化できます。記号定数_TRUEと_FALSEの定義も不要になりました。

リスト9:ifの条件式を簡略化したchgcase
#include <ctype.h>

#define  _DIFFER (0x20)  /* 大文字と小文字の差 */

int chgcase(int c) {
  if (isupper(c)) {
    return (c + _DIFFER);
  }
  elseif (islower(c)) {
    return (c - _DIFFER);
  }
  else {
    return (0);
  }
}

あとがき

条件判定と分岐を行うifの書き方には、実に多様なパターンがあります。

条件式や戻り値との比較で記号定数を使ったり、if~else if~elseの組み合わせが複雑になる場合には字下げ(インデント)をうまく使うなど、ソースを読みやすくして間違いを防げるよう工夫しましょう。

hiropの『ちょっと気になる専門用語』~《評価》

ある条件が真か偽かを調べるifなどの説明では、( )内に記述された条件式を『評価する』と表現されます。これは英語の動詞“evaluate”の訳語で、研究社英和辞典では

(~を)査定する、評価する、値踏みする。
と、説明されています。式を実行した結果が真か偽かを調べる動作ですから、まさにその通りです。

が、日本語の『評価』には上述の『査定・値踏み』という意味の他に、

善悪・美醜・優劣などの価値を判じ定めること。
特に、高く価値を定めること。
という意味も含まれています(広辞苑 第四版より)。

プログラミング経験のある人なら、「式を評価する」と言った場合には単純に「値がどのような状態か判断する」ことだと理解できます。しかしプログラミングに慣れていない一般の人なら、後者の『善悪や美醜を判定する』行為や『高く価値を定める』行為までを意味として受け取ってしまい、違和感を感じることもあります。

特に、数式に対して「それを評価」するという表現は、日常の感覚からしてみれば実はかなり「?」なことです。もちろん、プログラミングの専門書ではそれが当たり前の表現なので問題はありません。ただ、初心者向けの入門書では、こういった言葉の問題にもう少し配慮してもよいのではないか……という気がします。