C言語基礎|文字列の操作

「\0 を見つければ勝ち! C言語の文字列操作は “終端まで走査” が基本ルール」

作る・読むだけから、いよいよ「使いこなす」へ

ここまでで、文字列を配列に入れたり、scanf で読み込んだりできるようになりました。
でも本番はここからです。

  • 文字列の長さを知りたい。
  • 文字列を空にしたい。
  • 特定の文字がどこにあるか探したい。

こういう「文字列の操作」は、C言語では ナル文字 \0 を目印に、先頭から順に走査するのが基本になります。
一見地味ですが、これが分かると自作関数がスラスラ書けるようになりますよ。

文字列の基本ルール(配列の要素数と文字列の長さは別物)

次の宣言を考えます。

char str[6] = "ABC";

配列の要素数と文字列の長さ

図の説明

  • 配列の要素数は 6(箱が6個)
  • 文字列として意味を持つのは \0 の直前まで(この例だと ABC の3文字)
  • 残りの箱があることと、文字列が長いことは別問題です

よく混ざる2つの概念

用語何のこと?この例では
配列の要素数箱の個数6
文字列の長さ\0 の直前までの文字数3

文字列の長さを求める考え方(線形探索そのもの)

文字列の長さは、先頭から見ていって \0 が出た位置で決まります。

走査の流れ(\0 を探す)

len=0 → s[0]='G' なので続ける
len=1 → s[1]='T' なので続ける
len=2 → s[2]='6' なので続ける
len=3 → s[3]='\0' で止まる → 長さは 3

図の説明

  • len は「今見ている位置」兼「数えた文字数」
  • 条件が s[len] が 0 ではない(つまり \0 ではない)間だけ進む
  • 止まった瞬間の len が長さ

サンプルプログラム

文字列の長さを求める自作のプログラムの例です。

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

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

// 文字列の長さを求める(自作)

#include <stdio.h>

// 文字列 s の長さを返す(\0 の直前まで)
int my_strlen(const char s[])
{
    int len = 0;

    while (s[len])
        len++;

    return len;
}

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

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

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

    return 0;
}

実行例

単語を入力してください:Hello
入力した単語は Hello です。
文字数は 5 です。

登場する命令の書式と「何をする命令か」

while の書式と役割

  • 書式
    while (条件式) 文;
  • 何をする?
    条件式が真のあいだ、文を繰り返します。
    今回は「\0 じゃない間は続ける」という走査に使います。

scanf の書式と役割(今回のポイントつき)

  • 書式
    scanf(書式文字列, 格納先, ...);
  • 何をする?
    標準入力から読み取り、格納先に書き込みます。
    %s は空白までを文字列として読み込みます。
  • 安全のためのコツ
    %127s のように 最大入力長を指定すると配列あふれを避けやすいです。
    word が 128 なら、終端\0 のために 127 までが安心です。

printf の書式と役割

  • 書式
    printf(書式文字列, 実引数1, ...);
  • 何をする?
    整形して表示します。%s は文字列、%d は整数です。

const が付く意味(文字列を壊さない宣言)

int my_strlen(const char s[])

const の意味

書き方意味何が嬉しい?
const char s[]s の中身を書き換えません間違って変更するミスを防げる

表の説明
長さを数えるだけなら、文字列の中身を書き換える必要がありません。
なので const を付けると「読むだけ」の意図がはっきりします。

文字列操作での「引数の受け渡し」ルール

文字列は配列なので、関数に渡すときは 配列名だけを渡します。

呼び出し側と呼び出され側

図の説明

  • 呼び出し側は word の名前だけでOK
  • 受け取る側は配列として受け取る
  • 文字列は \0 まで処理すれば終わるので、要素数を別に渡さなくても動きます

演習問題

演習9-4:文字列を空文字列にする関数を作ろう

文字列 s を空文字列にする関数を作成せよ。
void null_string(char s[]);

解答例

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

#include <stdio.h>

void null_string(char s[])
{
    s[0] = '\0';
}

int main(void)
{
    char s[128] = "Sample";

    printf("変更前:%s\n", s);
    null_string(s);
    printf("変更後:%s\n", s);

    return 0;
}

解説

  • 空文字列は、先頭が \0 の文字列です
  • だから s[0] に \0 を入れるだけで「文字列としては終わり」になります
  • それ以降の要素に何が入っていても、文字列としては見えません

演習9-5:文字列の中から文字を探して添字を返そう

文字列 s の中に文字 c が含まれていれば、その最も先頭側の添字を返し、なければ -1 を返せ。
int str_char(const char s[], int c);

解答例

プロジェクト名:chap9-7-3 ソースファイル名:chap9-7-3.c

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

#include <stdio.h>

int str_char(const char s[], int c)
{
    int i = 0;

    while (s[i]) {
        if (s[i] == c)
            return i;
        i++;
    }

    return -1;
}

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

    printf("文字列を入力してください:");
    scanf("%127s", s);

    int pos = str_char(s, 'a');

    if (pos >= 0)
        printf("a は %d 番目にあります。\n", pos);
    else
        puts("a は見つかりませんでした。");

    return 0;
}

解説

  • 先頭から順に見ていく(線形探索)
  • s[i] が \0 になったら終わり
  • 見つけた瞬間に return するので「先頭側」が自然に実現できます