C言語基礎|文字列処理と strlen 関数

自作より安全で速い!―strlenで“文字列の長さ”を正しく測って、バグを未然に防ごう。

C言語の文字列は、見た目はただの文字の並びですが、内部では 最後に \0(ナル文字)で終わる配列として扱われます。
この「\0で終わる」というルールがあるからこそ、printf で表示できたり、コピーできたりします。

でも逆に言うと、\0を探しながら処理するのが基本なので、「長さ」を正しく知ることが超重要です。
そこで登場するのが strlen 関数。自作もできますが、実務では基本的に標準ライブラリを使うのが王道です。速くて、広く検証されていて、読み手にも伝わりやすいからです。

文字列処理ライブラリと <string.h>

文字列の処理を助ける標準関数は、主に <string.h> にまとまっています。

文字列系の代表的な関数(入口だけ)

分類関数名ざっくり何をする?
長さstrlen\0 まで数えて長さを返す(\0は数えない)
コピーstrcpy / strncpy文字列をコピーする
連結strcat / strncat文字列を後ろにくっつける
比較strcmp / strncmp辞書順で比較する
検索strchr / strstr文字や部分文字列を探す

※今回は strlen に集中します。

strlen 関数:文字列の長さを求める

仕様(書式)

  • ヘッダ:#include <string.h>
  • 形式:size_t strlen(const char *s);

何をする命令なのか(役割)

  • s が指す文字列について、先頭から \0 までを数えて 文字数を返す
  • \0 自体は長さに含めない

返却値の型 size_t とは?

strlen の返却値型は int ではなく size_t です。

size_t のイメージ

意味ざっくり特徴
int整数負数もある、サイズは処理系依存
size_tサイズ(大きさ)用の非負整数0以上、sizeof の結果と同じ系統の型

サイズを扱う場面では「負にならない」ことが大切なので、size_t が使われます。

サンプルプログラム

入力した単語の「長さ」と「最後の文字」を表示するプログラム例です。

プロジェクト名:chap11-8-1 ソースファイル名:chap11-8-1.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

#include <stdio.h>
#include <string.h>

int main(void)
{
    char word[128];

    printf("単語を入力してください:");
    scanf("%127s", word);

    size_t len = strlen(word);

    printf("入力した単語は %s です。\n", word);
    printf("文字数は %zu です。\n", len);

    if (len > 0) {
        printf("最後の文字は %c です。\n", word[len - 1]);
    }

    return 0;
}

ポイント

  • scanf("%127s", word) で入力上限を付けて安全寄りにしています(\0 の分も考えて127)。
  • strlen の結果は size_t なので、表示は %zu を使います。
  • 最後の文字を取り出すときは word[len - 1]。len が 0 の可能性を考えて if を入れています。

文字列の長さとは何か:\0 が境界線

C言語の文字列は、配列の途中に入る \0 を「終端」として扱います。

図:\0 までが文字列(\0は数えない)

strlen は、この '\0' に当たるまで進んで数えます。だから、\0 が無い(壊れた)配列を渡すと、どこまでも探しに行って危険です。

strlen を使うと何が嬉しい?

strlen を使う場面

やりたいことstrlen の使いどころ
末尾の文字を見たい長さが分かれば添字で取れるword[strlen(word) - 1]
入力の妥当性チェック長すぎ・短すぎを判定len < 3 なら再入力
コピーや連結の安全確認バッファの残りを見積もる目的地サイズ - 現在長さ
ループで走査for の上限に使えるfor (i=0; i<len; i++)

printf と scanf の書式と役割

printf の書式

  • 形式:printf(書式文字列, 引数, ...);
  • 何をする命令?:値を整形して表示する

今回使った主な指定

  • %s:\0 までを文字列として表示
  • %c:1文字を表示
  • %zu:size_t を表示(strlen の結果にぴったり)

scanf の書式

  • 形式:scanf(書式文字列, 格納先, ...);
  • 何をする命令?:入力を読み取り、指定した場所へ書き込む

今回

  • %127s:空白までを文字列として読み、最大127文字まで格納
  • word は配列なので、scanf には word(先頭要素へのポインタとして扱われる)を渡す
    ※int のときに必要だった & は、配列では不要になりやすい、という流れも思い出せます。

自作 strlen も理解しておこう(演習につながる)

strlen と同じ仕様を自作するなら、考え方はシンプルです。

  • 先頭から順に見ていく
  • \0 が来たら終了
  • そこまで数えた回数が長さ

図:ポインタで走査するイメージ

演習問題

演習11-3

次の仕様の関数 my_strlen を作成してください。

  • 引数 s が指す文字列の長さを返す(\0 は数えない)
  • 返却値型は size_t
  • 引数は const char * とする

関数プロトタイプ:

#include <stddef.h>
size_t my_strlen(const char *s);

解答例

プロジェクト名:chap11-8-2 ソースファイル名:chap11-8-2.c

#include <stddef.h>

size_t my_strlen(const char *s)
{
    size_t len = 0;
    while (s[len] != '\0') {
        len++;
    }
    return len;
}

解説

  • s[len] で先頭から順に文字を確認します。
  • '\0' に当たるまで len を増やし続けます。
  • len は 0 以上なので size_t が自然です。
  • const char * にしておくと、関数内で文字を書き換えないことがはっきりします。