C言語 文字・文字列の練習問題

char 型と文字列(文字の配列)を使う問題を、初級から上級まで13問そろえました。文字列の終わりを表す '\0' を意識することが、どの問題でも鍵になります。ブラウザでコードを書いて「採点する」を押すと、本物の gcc でいくつかの入力を試し、出力が正しいかを判定します。

この練習問題の使い方
  1. 問題文と実行例を読み、下の欄にコードを書く
  2. 「▶ 実行」で自分で入力を与えて動かしてみる
  3. 「✔ 採点する」で、用意したいくつかの入力に対して出力が正しいかを判定
  4. 行き詰まったら「ヒント」、解けたら「模範解答と解説」で確認
採点のルール
  • 出力は実行例と同じ形で表示する。「数を入力してください」のような入力を促すメッセージは表示しない
  • 行末の空白と、最後の空行の有無は区別しない
  • 書いたコードはこのブラウザに自動保存されます

※ 実行と採点では、コードを外部の無料コンパイルサービス Wandbox に送信して gcc で動かします。

全13問(初級 4・中級 5・上級 4)

文字・文字列の練習問題(13問)

S01 初級

問1. 文字列の長さを数える

空白を含まない文字列(1文字以上99文字以下、英数字と記号のみ)を入力し、その長さ(文字数)を表示せよ。strlen は使わず、自分で数えること。
実行例(下線が入力)
hello 長さ: 5
コードを書く
「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
C の文字列は、最後の文字の次にナル文字 '\0' が入っています。添字 len を 0 から増やしていき、s[len] が '\0' になったところで止めれば、そのときの len が長さです。
#include <stdio.h>

int main(void) {
    char s[100];
    scanf("%99s", s);

    int len = 0;
    while (s[len] != '\0') {
        len++;
    }
    printf("長さ: %d\n", len);
    return 0;
}
解説:配列の大きさ(100)と文字列の長さは別物です。長さは '\0' の位置で決まり、'\0' 自身は長さに数えません。scanf("%99s", s) の 99 は「最大99文字まで読む」という指定で、'\0' の分の1つを残して配列からあふれないようにしています。
S02 初級

問2. 文字コードを表示

空白を含まない文字列(1文字以上99文字以下、英数字と記号のみ)を入力し、1文字ずつ「文字: 文字コード」の形で1行に1つ表示せよ。文字コードは10進数で表示する。
実行例(下線が入力)
ABC A: 65 B: 66 C: 67
コードを書く
「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
char 型の値は、中身は文字コード(整数)です。同じ s[i] を %c で表示すれば文字、%d で表示すれば数値になります。
#include <stdio.h>

int main(void) {
    char s[100];
    scanf("%99s", s);

    for (int i = 0; s[i] != '\0'; i++) {
        printf("%c: %d\n", s[i], s[i]);
    }
    return 0;
}
解説:文字の '0' の文字コードは 48 で、数値の 0 とは違います。この「'0'〜'9' は連続した番号」(C の規格で保証)、「'A'〜'Z'、'a'〜'z' もそれぞれ連続」(ASCII の性質)を使うと、次の問題のような変換ができます。ループの条件を s[i] != '\0' にすれば、長さを先に求めなくても末尾まで回せます。
S03 初級

問3. 大文字と小文字の入れ替え

空白を含まない文字列(1文字以上99文字以下、英数字と記号のみ)を入力し、英大文字は小文字に、英小文字は大文字に変えて表示せよ。それ以外の文字はそのまま表示する。
実行例(下線が入力)
Hello123 hELLO123
コードを書く
「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
'a' <= c && c <= 'z' なら小文字です。ASCII では小文字と大文字の文字コードの差は一定なので、c - 'a' + 'A' で大文字になります。
#include <stdio.h>

int main(void) {
    char s[100];
    scanf("%99s", s);

    for (int i = 0; s[i] != '\0'; i++) {
        if ('a' <= s[i] && s[i] <= 'z') {
            s[i] = s[i] - 'a' + 'A';
        } else if ('A' <= s[i] && s[i] <= 'Z') {
            s[i] = s[i] - 'A' + 'a';
        }
    }
    printf("%s\n", s);
    return 0;
}
解説:else if を if にすると、小文字→大文字にしたあと、同じ文字がすぐ大文字→小文字に戻されてしまいます。<ctype.h> の toupper / tolower を使っても書けます(引数は (unsigned char)s[i] とキャストして渡すのが安全です)。
S04 初級

問4. 文字の出現回数

空白を含まない文字列(1文字以上99文字以下)と1つの文字 c を空白区切りで入力し、文字列の中に c が何個あるかを表示せよ。大文字と小文字は区別する。
実行例(下線が入力)
banana a 個数: 3
コードを書く
「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
s[i] != '\0' の間ループして、s[i] == c のときに数えます。1文字を比べるときは == で比べられます。
#include <stdio.h>

int main(void) {
    char s[100];
    char c;
    scanf("%99s %c", s, &c);

    int count = 0;
    for (int i = 0; s[i] != '\0'; i++) {
        if (s[i] == c) count++;
    }
    printf("個数: %d\n", count);
    return 0;
}
解説:1文字(char)は == で比べられますが、文字列どうしは == では比べられません(配列の先頭アドレスを比べることになる)。文字列の比較には strcmp を使います。書式 "%99s %c" の %c の前の空白は、文字列のあとの空白を読み飛ばすためのものです(%c は空白も1文字として読んでしまうため)。
S05 中級

問5. 文字列を逆順にする

空白を含まない文字列(1文字以上99文字以下)を入力し、文字の並びを逆にして表示せよ。表示だけでなく、配列の中身そのものを逆順に入れ替えてから printf("%s\n", s) で表示すること。
実行例(下線が入力)
hello olleh
コードを書く
「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
長さを len とすると、s[0] と s[len - 1]、s[1] と s[len - 2]、… と両端から交換していきます。交換は len / 2 回で終わりです。
#include <stdio.h>
#include <string.h>

int main(void) {
    char s[100];
    scanf("%99s", s);

    size_t len = strlen(s);
    for (size_t i = 0; i < len / 2; i++) {
        char tmp = s[i];
        s[i] = s[len - 1 - i];
        s[len - 1 - i] = tmp;
    }
    printf("%s\n", s);
    return 0;
}
解説:交換を len 回まで続けると、半分を過ぎたところからもう一度入れ替わって元に戻ります。相手の位置は len - i ではなく len - 1 - i です(s[len] は '\0' なので、これを先頭に持ってくると空の文字列になります)。strlen の戻り値は size_t 型なので、添字も size_t にそろえると符号の混在の警告が出ません。
S06 中級

問6. 回文判定

空白を含まない文字列(1文字以上99文字以下)を入力し、前から読んでも後ろから読んでも同じ(回文)なら「回文です」、そうでなければ「回文ではありません」と表示せよ。大文字と小文字は区別する。
実行例(下線が入力)
level 回文です
コードを書く
「実行」で入力を与えて動かす/「採点する」で 6 個のテストを自動で試します
前問と同じく両端から内側へ向かって、s[i] と s[len - 1 - i] を比べます。1か所でも違えば回文ではないので、フラグを 0 にして break します。
#include <stdio.h>
#include <string.h>

int main(void) {
    char s[100];
    scanf("%99s", s);

    size_t len = strlen(s);
    int is_pal = 1;
    for (size_t i = 0; i < len / 2; i++) {
        if (s[i] != s[len - 1 - i]) {
            is_pal = 0;
            break;
        }
    }

    if (is_pal) {
        printf("回文です\n");
    } else {
        printf("回文ではありません\n");
    }
    return 0;
}
解説:「全部一致したら回文」は、ループの中で if (一致) 回文です と表示してしまうと間違いです。1か所でも違えば NG、最後まで違いがなければ OK という形は、フラグ変数で書くのが定番です。長さが奇数のときの真ん中の文字は、自分自身と比べるだけなので調べなくてかまいません。
S07 中級

問7. アルファベットの出現回数

英小文字だけからなる文字列(1文字以上99文字以下)を入力し、文字列に現れた文字ごとに「文字: 回数」を、a から z の順に1行に1つ表示せよ。1回も現れなかった文字は表示しない。
実行例(下線が入力)
banana a: 3 b: 1 n: 2
コードを書く
「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
26個の数を数える配列 int cnt[26] = {0}; を用意します。文字 c は c - 'a' で 0〜25 の番号になるので、cnt[s[i] - 'a']++; と数えられます。
#include <stdio.h>

int main(void) {
    char s[100];
    scanf("%99s", s);

    int cnt[26] = {0};
    for (int i = 0; s[i] != '\0'; i++) {
        cnt[s[i] - 'a']++;
    }

    for (int k = 0; k < 26; k++) {
        if (cnt[k] > 0) {
            printf("%c: %d\n", 'a' + k, cnt[k]);
        }
    }
    return 0;
}
解説:「文字 → 添字」の変換(s[i] - 'a')と「添字 → 文字」の変換('a' + k)を組み合わせるのがポイントです。入力に英小文字以外が混じると添字が 0〜25 を外れて範囲外に書き込むので、実用では 'a' <= s[i] && s[i] <= 'z' を確かめてから数えます。
S08 中級

問8. 辞書順に並べる

空白を含まない2つの文字列(それぞれ1文字以上99文字以下、英数字のみ)を空白区切りで入力し、辞書順(文字コード順)で前のものを1行目、後のものを2行目に表示せよ。2つが同じなら「同じ文字列です」と表示する。比較には strcmp を使うこと。
実行例(下線が入力)
banana apple apple banana
コードを書く
「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
strcmp(a, b) は、a が前なら負の値、同じなら 0、a が後なら正の値を返します。a が後なら、作業用の配列と strcpy を使って a と b の中身を入れ替えてから表示します。
#include <stdio.h>
#include <string.h>

int main(void) {
    char a[100], b[100];
    scanf("%99s %99s", a, b);

    int r = strcmp(a, b);
    if (r == 0) {
        printf("同じ文字列です\n");
        return 0;
    }
    if (r > 0) {
        char tmp[100];
        strcpy(tmp, a);
        strcpy(a, b);
        strcpy(b, tmp);
    }
    printf("%s\n%s\n", a, b);
    return 0;
}
解説:strcmp の戻り値は −1 や 1 とは限らないので、== 1 ではなく > 0・< 0 で判定します。配列は a = b; のように代入できないので、中身のコピーには strcpy を使います。比較は文字コード順なので、大文字(65〜90)は小文字(97〜122)より前に来ます(Zoo が apple より前)。また app は apple の先頭部分なので、短い方が前です。
S09 中級

問9. 単語の数を数える

英数字と空白からなる1行の文章(200文字以下)を入力し、単語の数を表示せよ。単語は空白で区切られた文字の並びとし、空白は連続することも、行の先頭や末尾にあることもある。空白しかない行なら単語数は 0 とする。行は fgets で読むこと(入力の最後に改行がない場合もある)。
実行例(下線が入力)
I have a pen 単語数: 4
コードを書く
「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
「今、単語の中にいるか」を表すフラグ in_word を使います。空白(または改行)なら in_word = 0、それ以外の文字で in_word が 0 なら単語の始まりなので、数を 1 増やして in_word = 1 にします。
#include <stdio.h>

int main(void) {
    char s[256];
    if (fgets(s, sizeof s, stdin) == NULL) {
        s[0] = '\0';
    }

    int count = 0, in_word = 0;
    for (int i = 0; s[i] != '\0'; i++) {
        if (s[i] == ' ' || s[i] == '\n') {
            in_word = 0;
        } else if (!in_word) {
            in_word = 1;
            count++;
        }
    }
    printf("単語数: %d\n", count);
    return 0;
}
解説:「空白の数 + 1」で数えると、空白が連続したり先頭・末尾にあったりすると数が合いません。単語の始まりの数を数えるのが確実です。fgets は読んだ行の最後の改行 '\n' も配列に入れる(入力の最後に改行がなければ入らない)ので、ここでは改行も区切りとして扱っています。scanf("%s") は空白で読むのを止めるので、空白を含む行は読めません。
S10 上級

問10. シーザー暗号

整数 K(0 ≤ K ≤ 25)と、空白を含まない文字列(1文字以上99文字以下、英数字と記号のみ)を空白区切りで入力する。英字をアルファベット順に K 文字後ろへずらして表示せよ(z の次は a に戻る)。大文字は大文字、小文字は小文字のままずらし、英字以外はそのまま表示する。
実行例(下線が入力)
3 abcxyz defabc
コードを書く
「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
小文字なら、まず s[i] - 'a' で 0〜25 の番号にし、K を足して 26 で割った余りをとり、'a' を足して文字に戻します。大文字も同じ考え方で 'A' を基準にします。
#include <stdio.h>

int main(void) {
    int k;
    char s[100];
    scanf("%d %99s", &k, s);

    for (int i = 0; s[i] != '\0'; i++) {
        if ('a' <= s[i] && s[i] <= 'z') {
            s[i] = 'a' + (s[i] - 'a' + k) % 26;
        } else if ('A' <= s[i] && s[i] <= 'Z') {
            s[i] = 'A' + (s[i] - 'A' + k) % 26;
        }
    }
    printf("%s\n", s);
    return 0;
}
解説:s[i] + k だけだと、'z' を超えたときに { などの記号になってしまいます。いったん 0〜25 の番号に直してから % 26 するのが、輪のように回る計算の定番です。K=13 で2回暗号化すると元に戻ります(ROT13)。
S11 上級

問11. strlen と strcat を自作する

文字列の長さを返す関数 my_strlen と、文字列 dst の後ろに src をつなげる関数 my_strcat を、<string.h> を使わずに完成させよ。main はテンプレートのまま使う。入力は空白を含まない2つの文字列(それぞれ1文字以上49文字以下)で、つなげた文字列とその長さが表示される。
実行例(下線が入力)
abc def abcdef 長さ: 6
コードを書く
「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
my_strcat は2段階です。(1) dst の '\0' の位置を探す(そこが書き込み開始位置)。(2) そこから src を1文字ずつコピーし、最後に '\0' も書き込む。
#include <stdio.h>

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

void my_strcat(char *dst, const char *src) {
    int i = my_strlen(dst);
    int j = 0;
    while (src[j] != '\0') {
        dst[i] = src[j];
        i++;
        j++;
    }
    dst[i] = '\0';
}

int main(void) {
    char a[100], b[50];
    scanf("%49s %49s", a, b);
    my_strcat(a, b);
    printf("%s\n", a);
    printf("長さ: %d\n", my_strlen(a));
    return 0;
}
解説:最後の dst[i] = '\0'; を忘れると、つなげた後ろに何が続くか分からず、printf がゴミを表示したり配列の外まで読み進めたりします。また、つなげる側の配列 a は両方の長さ + 1 文字分の大きさが必要です(ここでは 49 + 49 + 1 ≤ 100)。標準の strcat も大きさを確かめてくれないので、呼ぶ側が責任を持ちます。引数の const は「この関数は中身を書き換えない」という約束です。
S12 上級

問12. アナグラム判定

英小文字だけからなる2つの文字列(それぞれ1文字以上99文字以下)を空白区切りで入力し、一方の文字を並べ替えるともう一方になる(アナグラム)なら「アナグラムです」、そうでなければ「アナグラムではありません」と表示せよ。同じ文字列もアナグラムとみなす。
実行例(下線が入力)
listen silent アナグラムです
コードを書く
「実行」で入力を与えて動かす/「採点する」で 6 個のテストを自動で試します
各文字の出現回数が全部同じならアナグラムです。int cnt[26] = {0}; に、1つ目の文字列の文字は +1、2つ目の文字列の文字は −1 していき、最後に26個すべてが 0 かを調べます。
#include <stdio.h>

int main(void) {
    char a[100], b[100];
    scanf("%99s %99s", a, b);

    int cnt[26] = {0};
    for (int i = 0; a[i] != '\0'; i++) {
        cnt[a[i] - 'a']++;
    }
    for (int i = 0; b[i] != '\0'; i++) {
        cnt[b[i] - 'a']--;
    }

    int ok = 1;
    for (int k = 0; k < 26; k++) {
        if (cnt[k] != 0) {
            ok = 0;
            break;
        }
    }

    if (ok) {
        printf("アナグラムです\n");
    } else {
        printf("アナグラムではありません\n");
    }
    return 0;
}
解説:「a の文字がすべて b に含まれるか」だけを調べると、aab と abb のように回数が違う場合を見逃します。出現回数で比べれば、長さが違う場合(abc と ab)も自然に「違う」と判定できます。2つの文字列をそれぞれ並べ替えてから strcmp で比べる方法もあります。
S13 上級

問13. ランレングス圧縮

英字だけからなる文字列(1文字以上99文字以下)を入力し、同じ文字が続く部分を「文字と続いた個数」に置き換えて表示せよ。たとえば aaabccdddd は a3b1c2d4 になる。1文字だけの部分も個数 1 を付ける。大文字と小文字は別の文字として扱う。
実行例(下線が入力)
aaabccdddd a3b1c2d4
コードを書く
「実行」で入力を与えて動かす/「採点する」で 6 個のテストを自動で試します
添字 i の文字 c を覚え、s[i] == c の間 i と個数を増やします。違う文字(または '\0')に来たら「c と個数」を表示し、そこから次のかたまりを数え始めます。
#include <stdio.h>

int main(void) {
    char s[100];
    scanf("%99s", s);

    int i = 0;
    while (s[i] != '\0') {
        char c = s[i];
        int count = 0;
        while (s[i] == c) {
            count++;
            i++;
        }
        printf("%c%d", c, count);
    }
    printf("\n");
    return 0;
}
解説:内側の while (s[i] == c) は、c が英字なので '\0' に来れば必ず止まり、配列の外には出ません。s[i] == s[i + 1] を比べる書き方もできますが、最後のかたまりを表示し忘れやすいので注意します。個数は2桁以上にもなるので、%d で表示します('0' + count だと 10 以上で崩れます)。

復習する講座

← 練習問題の一覧へ