char 型と文字列(文字の配列)を使う問題を、初級から上級まで13問そろえました。文字列の終わりを表す '\0' を意識することが、どの問題でも鍵になります。ブラウザでコードを書いて「採点する」を押すと、本物の gcc でいくつかの入力を試し、出力が正しいかを判定します。
※ 実行と採点では、コードを外部の無料コンパイルサービス Wandbox に送信して gcc で動かします。
strlen は使わず、自分で数えること。「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
'\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; }
'\0' の位置で決まり、'\0' 自身は長さに数えません。scanf("%99s", s) の 99 は「最大99文字まで読む」という指定で、'\0' の分の1つを残して配列からあふれないようにしています。文字: 文字コード」の形で1行に1つ表示せよ。文字コードは10進数で表示する。「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
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' にすれば、長さを先に求めなくても末尾まで回せます。「実行」で入力を与えて動かす/「採点する」で 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] とキャストして渡すのが安全です)。「実行」で入力を与えて動かす/「採点する」で 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; }
== で比べられますが、文字列どうしは == では比べられません(配列の先頭アドレスを比べることになる)。文字列の比較には strcmp を使います。書式 "%99s %c" の %c の前の空白は、文字列のあとの空白を読み飛ばすためのものです(%c は空白も1文字として読んでしまうため)。printf("%s\n", s) で表示すること。「実行」で入力を与えて動かす/「採点する」で 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 にそろえると符号の混在の警告が出ません。回文です」、そうでなければ「回文ではありません」と表示せよ。大文字と小文字は区別する。「実行」で入力を与えて動かす/「採点する」で 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 という形は、フラグ変数で書くのが定番です。長さが奇数のときの真ん中の文字は、自分自身と比べるだけなので調べなくてかまいません。文字: 回数」を、a から z の順に1行に1つ表示せよ。1回も現れなかった文字は表示しない。「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
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' を確かめてから数えます。同じ文字列です」と表示する。比較には strcmp を使うこと。「実行」で入力を与えて動かす/「採点する」で 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 の先頭部分なので、短い方が前です。fgets で読むこと(入力の最後に改行がない場合もある)。「実行」で入力を与えて動かす/「採点する」で 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; }
fgets は読んだ行の最後の改行 '\n' も配列に入れる(入力の最後に改行がなければ入らない)ので、ここでは改行も区切りとして扱っています。scanf("%s") は空白で読むのを止めるので、空白を含む行は読めません。「実行」で入力を与えて動かす/「採点する」で 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)。my_strlen と、文字列 dst の後ろに src をつなげる関数 my_strcat を、<string.h> を使わずに完成させよ。main はテンプレートのまま使う。入力は空白を含まない2つの文字列(それぞれ1文字以上49文字以下)で、つなげた文字列とその長さが表示される。「実行」で入力を与えて動かす/「採点する」で 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 は「この関数は中身を書き換えない」という約束です。アナグラムです」、そうでなければ「アナグラムではありません」と表示せよ。同じ文字列もアナグラムとみなす。「実行」で入力を与えて動かす/「採点する」で 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; }
aab と abb のように回数が違う場合を見逃します。出現回数で比べれば、長さが違う場合(abc と ab)も自然に「違う」と判定できます。2つの文字列をそれぞれ並べ替えてから strcmp で比べる方法もあります。aaabccdddd は a3b1c2d4 になる。1文字だけの部分も個数 1 を付ける。大文字と小文字は別の文字として扱う。「実行」で入力を与えて動かす/「採点する」で 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 以上で崩れます)。