第25回
データ構造(4)~文字列をポインタで扱う

文字列の大文字/小文字変換

関数とするにせよ、マクロとしてインライン展開するにせよ、先に紹介した処理はあくまで「1文字ずつの変換処理」です。文字列をまとめて大文字または小文字に変換するには、処理の対象を配列にしなければなりません。

配列で処理する

まず、文字列を配列として引数に受け取り、それらをまとめて大文字に変換する関数を作ってみます。引数の文字列は「すべて小文字」という前提です。

ソースはリスト4のようになります。

まず、大文字~小文字のコードの差分である0x20に"_DEF"という記号定数を定義しておきます。その上でカウンタとして変数iを使い、whileループの中で添字を1ずつ増加しながら、要素から1つずつ差分の_DEF(0x20)を減算していきます。

whileの条件式で
(str[i] != NULL)
としているため、要素が文字列終端のNULL(\0)になればループを抜けて処理を終えます。なお要素から"_DEF"を減算している
str[i] = str[i] - _DEF;
という箇所は
(str[i]) -= _DEF;
としても構いません。ここでは処理が分かりやすいように、冗長な記述としました。

文字列をすべて小文字に変換するのも、これと同じ要領です。リスト5のようになります。★マークの行で要素に差分の"_DEF"を加算しているところだけが異なります。

リスト4:文字列をすべて大文字に変換する関数(配列版)
#define _DEF 0x20 ---------------- 大文字と小文字の差分

void strupper(char str[])
{
  int i =0; ---------------------- 添字のカウンタ

  while(str[i] != NULL) { -------- 終端まで繰り返す
    str[i] = str[i] - _DEF;
    i++;
  }
}

リスト5:文字列をすべて小文字に変換する関数(配列版)
#define _DEF 0x20

void strlower(char str[])
{
  int i =0;

  while(str[i] != NULL) {
    str[i] = str[i] + _DEF; ---- ★
    i++;
  }
}

ポインタで処理する

次に、同じ処理をポインタを使って記述してみましょう。小文字を大文字に変換する関数は、リスト6のようになります。

ソースはかなりシンプルになりました。引数はchar型のポインタで、それの示す文字列を直接変換するため戻り値はありません。

void strupper(char * s)
呼び出し側で文字列──char型配列の名前を引数とすれば、その先頭アドレスがこの関数の仮引数sに渡されます。

関数内部ではwhileループで*s(ポインタの示す1文字)がNULLでない間、処理が繰り返されます。
while (*s != NULL) {

繰り返される処理は、以下のような内容です。

(1)*s(1文字)から差分の_DEFを減算する。
(2)その結果を*sに代入する。
(3)s(ポインタ)を1つ進める

これをソースコードにすると、以下のようになります。
*s = *s - _DEF;
s++;

減算と代入には-=演算子を使えるので、さらに以下のように書き換えることができます。
*s -= _DEF;
s++;

演算子の優先順位を利用すると、上の2行は以下のように1行にまとめられます。
*s++ -= _DEF;

ポインタのインクリメントと代入

上記の式は一見すると分かりにくいようですが、以下のような動作を表しています。

(1)*sを評価する(*sの示す値を取り出す)。
(2)s++を評価する(ポインタを1つ進める──インクリメント)。

このとき、(1)で評価された*sの「s」は(2)でインクリメントされる前であることに注意してください。

(3)(1)で評価した*sに対して_DEFの減算と代入を行う。

この時点で、インクリメント前の「*s」に対して"_DEF"との減算結果が代入され、ポインタ「s」はインクリメントされた状態となっています。

この書き方はCの簡略記法として多用されるため、1つのパターンとして覚えておくといいでしょう。複雑な動作をシンプルに表すことのできる「いかにもC」といった感じの記述法です。

同じ要領で文字列を小文字に変換する関数は、リスト7のようになります。

リスト6:文字列をすべて大文字に変換する関数(ポインタ版)
#define _DEF 0x20

void strupper(char * s)
{
  while (*s != NULL) {
    *s++ -= _DEF;
  }
}

リスト7:文字列をすべて小文字に変換する関数(ポインタ版)
#define _DEF 0x20

void strlower(char * s)
{
  while (*s != NULL) {
    *s++ += _DEF;
  }
}

値を返す方が自然

なお、ここでは2つの関数の戻り値をvoidとして型のない扱いとしました。ポインタとして受け取った文字列を関数の内部で処理するため、これでも構いませんが、文字列を処理する関数では、処理後の文字列へのポインタを戻り値とするのが慣習のようになっているため、リスト8のようにする方が親切でしょう。

関数の型をconst char *としてchar型ポインタを返すように定義し、最後に
return(ret);
として、最初に保存しておいたポインタ(char型配列の先頭アドレス)を返します。このとき
return (s);
とすると、whileループ内でインクリメントされたポインタ(文字列終端を示すポインタ)が返されてしまいます。

リスト8:大文字への変換で文字列へのポインタを返す
const char * strupper(char * s)
{
  char * ret;
  ret = s;    /* 引数のアドレスを保存 */
  while (*s != NULL) {
    *s++ -= _DEF;
  }
  return (ret);
}