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

大文字⇔小文字の変換処理

文字には文字コードという番号が振られています。そのため、文字を数値型として扱えるCでは、文字同士の演算ができます。

大文字と小文字の“差”

コンピュータでは、文字も文字コードという数値で識別されます。ご存じのように、アルファベットと記号はASCIIなどいくつかの規格に従って、1文字に対して1バイトの文字コードが割り当てられています ※1

日本語のようにたくさんの文字種を扱う言語では、JISコードなど1文字を2バイトの数値で表す仕組みが適用されます。現在では、これらを1つにまとめたUnicodeが一般的です。

どのような場合も、いわゆる半角文字のアルファベットには、
小文字のコードは大文字のコードより
0x20(16進数の“20”)大きい
という規則性があります。

このことを応用すれば、アルファベットの
大文字→小文字 / 小文字→大文字
という変換が簡単にできます。

表1:ASCIIコード表
  0 1 2 3 4 5 6 7
0 NUL DLE SP
1 SOH DC1
2 EXT DC2
3 EOT DC3
4 EOT DC4
5 ENQ NAK
6 ACK SYN
7 BEL ETB
8 BS CAN
9 HT EM
A LF SUB
B VT ESC
C FF FS
D CR GS
E SO RS ~
F SI US _ DEL

アルファベットと記号は128種類以内で収まるため、ASCIIコードでは1文字を7ビットで表現できます。JISなど日本の規格では、これに半角のカタカナを加えて8ビット(1バイト)で表現できるようにしています

文字コードの加減算

例えば'A'のコードは0x41、'a'のコードは0x61なので、以下のような計算が成り立ちます。

char c;
c = 'A' + 0x20;
こうすると、char型の変数cには'a'を表す0x61が代入されます(0x41 + 0x20 = 0x61)。逆に
c = 'a' - 0x20;
とすれば、変数cには'A'を表す0x41が代入されます(0x61 - 0x20 = 0x41)。

では、char型の引数cを小文字に変換する関数tolowrを作ってみましょう。単純に考えれば、リスト1のようなソースコードなります。同じように、引数cを大文字に変換する関数touprもリスト1に掲げておきます。

これら関数の動作を確認するには、リスト2のようなプログラムを作ります。tolowr関数を呼び出しているアンダーラインの箇所をtouprに書き換えれば、toupr関数のテストもできます。

リスト1:大文字を小文字に変換するtolowr関数と小文字を大文字に変換するtoupr関数
/* 大文字を小文字に変換 */
char tolower(char c) {
  return (c + 0x20);
}

/* 小文字を大文字に変換 */
char toupr(char c) {
  return (c - 0x20);
}

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

/* 関数の宣言 */
char tolowr(char c);    /* 大文字を小文字に変換 */
char toupr(char c);     /* 小文字を大文字に変換 */

int main(void)
{
  char c;

  printf("Input Charactor : ");
  scanf("%c", &c);
  printf(" --> %c\n", tolowr(c));
                      ~~~~~~
                         ↑大文字への変換ならここをtouprに書き換える

/* ---- 関数の定義 ---- */
/* 大文字を小文字に変換 */
char tolowr(char c) {
  return (c + 0x20);
}

/* 小文字を大文字に変換 */
char toupr(char c) {
  return (c - 0x20);
}

引数のチェック処理を追加

リスト1の関数は、単純に大文字と小文字の差である0x20を加減算するだけなので、あまり良い仕様ではありません。tolower関数の引数cが小文字だったとき、それに対して0x20を加算してしまうため、とんでもない値が返ってくることになります。toupr関数の引数が大文字だった場合も同様で、大文字の'A'から0x20を減算すれば0x21となって'!'が返されます。

もちろん、関数を呼び出す前に引数をチェックしておけば問題はありませんが、仕様としては不親切です。以下のような形とするのが安全でしょう。

tolowr:引数が大文字のときは小文字に変換し
        それ以外のときは0を返す
toupr :引数が小文字のときは大文字に変換し
        それ以外のときは0を返す
このような仕様とするには、引数が大文字か小文字か、あるいはそれ以外かを調べて、処理を切り替えなければなりません。ここでifを使います。すると、ソースはリスト3のようになります。

これらの関数の動作は、リスト4のようなプログラムでテストできます。

リスト3:引数を調べて大文字⇔小文字の変換を行うtolowr関数とtoupr関数
/* 大文字を小文字に変換 */
char tolowr(char c) {
  if ((c >= 'A') && (c <= 'Z')) { -------- (1)
    return (c + 0x20);
  } else {
    return (0);
  }
}

/* 小文字を大文字に変換 */
char toupr(char c) {
  if ((c >= 'a') && (c <= 'z')) { -------- (2)
    return (c - 0x20);
  } else {
    return (0);
  }
}

リスト4:リスト3の関数をテストするためのプログラム
/* 関数の宣言 */
char tolowr(char c);    /* 大文字を小文字に変換 */
char toupr(char c);     /* 小文字を大文字に変換 */

int main(void)
{
  char ci, co;    /* ci : 入力文字 / co : 出力文字  */

  printf("Input Upper Charactor : ");
  scanf("%c", &c);
  co = tolowr(ci);
       ~~~~~~
         ↑大文字への変換ならここをtouprに書き換える
  if (co == 0) {
    printf("大文字を入力してください。\n");
  } else {
    printf(" --> %c\n", co);
  }
}

/* ---- 関数の定義 ---- */
/* 大文字を小文字に変換 */
char tolowr(char c) {
  if ((c >= 'A') && (c <= 'Z')) {
    return (c + 0x20);
  } else {
    return (0);
  }
}

/* 小文字を大文字に変換 */
char toupr(char c) {
  if ((c >= 'a') && (c <= 'z')) {
    return (c - 0x20);
  } else {
    return (0);
  }
}

文字の比較演算と論理演算

先のリスト3の(1)の箇所では、変数cの値を
if ((c >= 'A') && (c <= 'Z'))
として
文字'A'(0x41)以上であり
なおかつ(&&)
文字'Z'(0x5A)以下である
かどうかを調べています。これは
変数cの値が'A'~'Z'の範囲
──つまり大文字かどうかを調べていることになります。

同様に(2)では
if ((c >= 'a') && (c <= 'z'))
として
文字'a'(0x61)以上であり
なおかつ(&&)
文字'z'(0x7A)以下である
かどうかを調べています。これは
変数cの値が'a'~'z'の範囲
──つまり小文字かどうかを調べていることになります。

このように、C言語では文字(1文字)を数値として加減算や比較演算などの対象にできます。

ifのネストでも可能

このような条件判定は、リスト5のようにifのネストを使っても可能です。

しかし、リストを見れば分かるように
変数cが'A'~'Z'の範囲かどうか
を調べていることが見通しにくくなります。

1つの変数に対して2つの条件を判定する場合、リスト3のように2つの条件式を論理演算子(この場合は「かつ」を表す&&)でつなぐ方が、『何を何と比較しているのか』が分かりやすく、さらにソースもすっきりとして読みやすくなります。

リスト5:ifのネストを使ったtolowr関数
char tolowr(char c) {
  if (c >= 'A') {
    if (c <= 'Z') {
      return (c + 0x20);
    } else {
      return (0);
    }
  } else {
    return (0);
  }
}