第26回
データ構造(5)~ポインタを使った文字列処理関数を作る

文字列を扱う関数を作る

以下に紹介する関数は、Cの標準ライブラリに収録されているものと同じ名前ですが、内部の動作や使用結果はまったく同じではありません。また、対象とする文字列は1バイト(半角)文字の英数記号の集合という前提です。日本語などの2バイト文字は対象としていません(日本語の扱いについては、回を追って取り上げる予定です)。

strlen~文字列の長さ(文字数、バイト数)を調べる(リスト1)

・宣言 unsigned long int strlen(const char *);
・機能 引数に文字列へのポインタを受け取り、そのバイト数を返します。

戻り値はunsigned long int(符号なし長整数)型としています。単純に"int"型とすると、処理系やプラットフォームによってshortまたはlongとして扱われ、ソースコードとしては曖昧な表現となります。文字の長さ(文字数)のように正の整数であることが明白な値は符号なし(unsigned)と宣言しておく方が確実です。

こういった『何らかの数』を扱う処理のために、ANSI-C ※1 では以下のようにして"size_t"型を定義しています。
typedef unsigned long int size_t;

size_t型は型の占有するバイト数を調べるsizeof演算子の戻り値などに用いられ、stdio.h、stdlib.hなどのヘッダファイルで宣言されています。

処理の内容は、リストのコメント(/*~*/)を読んでいただければお分かりでしょう。whileループで引数の示す文字列の終端('\0')まで1文字ずつポインタを進め、その回数をカウンタ変数iで数えています。

whileループ内でポインタsをインクリメントしている部分は、以下のようにwhileの条件式にまとめることもできます。
while (*s++ != '\0') {
  i++;
}
ANSI(American National Standards Institute:米国規格協会)の定めたCの標準規格

リスト1:strlen関数
unsigned long int strlen(const char * s)
{
  unsigned long int i;        /* カウンタ */

  i = 0;        /* カウンタを初期化 */
  while (*s != '\0') {    /* 文字列の終端まで繰り返す */
    s++;                  /* ポインタを進める */
    i++;                  /* カウンタを加算 */
  }
  return (i);             /* カウンタを返す */
}

strcpy~文字列のコピー(リスト2~4)

・宣言 char * strcpy(const char *, char *);
・機能 引数に文字列を示す2つのポインタを採り、第1引数の示す文字列を第2引数のポインタが示す場所にコピーします。

戻り値はコピー後の文字列を示すポインタ(第2引数)とします。

コピー元をs、コピー先をdとしています。sはsource(元、出所)、dはdestination(宛先、目的地)の略です。sの示す文字列を1文字ずつdの示す場所にコピーし、dにコピーされた最後の文字の次(文字列の終端)にNULLを代入します。

リスト2のwhileループでは、処理の流れが分かるよう冗長な書き方をしています。
while (*s != '\0') {
  *d = *s;
  d++;
  s++;
}

リスト3では、sからdへの値の代入とポインタのインクリメントとを1行にまとめています。
while (*s != '\0') {
  *d++ = *s++;
      ↑ 1文字コピーしてポインタを進める
}

さらにリスト4では、それらすべての処理をwhileの条件式の中に記述し、{ }内の記述をなくしています。
while ((*d++ = *s++) != '\0') {
}

さすがに、ここまで簡略化するとソースが読みにくくなります。C独特の書き方に慣れてしまえばどうということはありませんが、できればリスト3程度の簡略化で済ませておいた方が分かりやすいソースとなるでしょう。

リスト2:strcpy関数~冗長な書き方
char * strcpy(const char *s, char *d)
{
  char *r;
  r = d;
  while (*s != '\0') {    /* コピー元の終端まで繰り返す */
    *d = *s;              /* 1文字コピー */
    d++;                  /* コピー先のポインタを進める */
    s++;                  /* コピー元のポインタを進める */
  }
  *d = '\0';              /* 終端のNULLをセット */
                          /* このときdは最後の文字の次を示している */
  return (r);             /* 本来なら「失敗時にNULLを返す」ようにするべきだが省略 */
}

リスト3:strcpy関数~やや冗長な書き方
char * strcpy(const char *s, char *d)
{
  char *r;
  r = d;
  while (*s != '\0') {
    *d++ = *s++;          /* 1文字コピーしてポインタを進める */
  }
  *d = '\0';
  return (r);             /* 本来なら「失敗時にNULLを返す」ようにするべきだが省略 */
}

リスト4:strcpy関数~シンプルな書き方
char * strcpy(const char *s, char *d)
{
  char *r;
  r = d;
  while ((*d++ = *s++) != '\0') {
  /* 条件式の中でコピーとポインタのインクリメントを行う */
  }
  *d = '\0';
  return (r);             /* 本来なら「失敗時にNULLを返す」ようにするべきだが省略 */
}

strcat~文字列の連結(リスト5)

・宣言 char * strcat(char *, const char *)
・機能 引数に文字列を示す2つのポインタを採り、第1引数の示す文字列の末尾に第2引数の示す文字列を連結します。

戻り値は連結先のポインタ(第1引数)とします。

仮引数名はs1とs2とし、s1の示す文字列の最後にs2の示す文字列を連結します。処理の最後にs1を戻り値として返すため、処理の中でs1の値を書き換えてはいけません。そのため、char *型の作業用ポインタpを用意し、それにs1の値を代入して処理を行います。

もしs1をそのまま使って処理を行うと、whileループでs1を進めていった結果、戻り値であるs1の値は処理前と異なり、文字列の最後尾(NULL)を指した状態となってしまいます。

リスト5:strcat関数
char * strcat(char *s1, const char *s2)
{
  char *p;   /* 作業用のポインタ */
  p = s1;    /* s1の値を受け取る */

  /* p(s1)の末尾までポインタを進める */
  while (*p != '\0') {
    p++;
  }
  /* p(s1)の末尾にs2をコピー */
  while (*s2 != '\0') {
    *p++ = *s2++;
  }
  *p = '\0';
  return (s1);
}