第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);
}