ほとんどの言語は自分の長さを知っている string 型を差し出します。Cはそうしません。Cにおける文字列は、1つの規則が付いた単なる char の配列です。テキストのあとに値ゼロのバイトが続き、それは '\0' と書かれヌル終端文字と呼ばれます。C言語の文字列に関する他のすべてが、このたった1つの約束事から導かれます。
文字列とは char 配列と '\0'
リテラルから char 配列を初期化して文字列を宣言します。
この配列は3バイトではなく4バイトに見えます。
name: +-----+-----+-----+------+
| 'A' | 'd' | 'a' | '\0' |
+-----+-----+-----+------+
添字: 0 1 2 3
sizeof(name) は 4 です。コンパイラが文字を数え、終端文字のぶんを1つ足し、配列の大きさを決めてくれました。この余分な1バイトのせいで char name[3] = "Ada"; は起こるべくして起こるバグです - テキストは収まりますが終端文字が収まらないので、その先の何ものも文字列がどこで終わるか分からなくなります。
あとで中身を変える余地が欲しいときは、自分でサイズを与えて余裕を残しましょう。
char name[32] = "Ada"; /* 3文字、終端文字、そして28バイトの余り */
使われないバイトは初期化子によってゼロで埋められますが、これは無害です。
終端文字が重要な理由
Cのどの関数も長さを受け取りません。printf("%s", s) はアドレスを1つ渡され、'\0' に出会うまで前へ進みながらバイトを表示します。ですから終端文字のない char 配列は文字列ではなく、それを文字列として扱えば、たまたま後ろに続くメモリを読むことになります。
覚えるべき規則です。sizeof は記憶領域を、strlen はテキストを教えてくれます。 char buf[32] = "Ada" について sizeof buf は 32、strlen(buf) は 3 です。両者は違う問いに答えており、それらを取り違えることはC言語で最もよくあるバグの1つです。
文字列リテラルと char 配列
この2行は似て見えますが、まったく違う振る舞いをします。
char a[] = "hello"; /* 自分が所有する配列。リテラルから初期化される */
char *p = "hello"; /* リテラルそのものを指すポインタ */
a はテキストの自分専用のコピーを保持する6バイトの配列です。変更できます。p はプログラムの読み取り専用の文字列データを指しています。そのリテラルは共有されており、p を通じた書き込みは未定義動作で、コンパイル時ではなく実行時にクラッシュするのが普通です。
ポインタがリテラルを指すときは必ず const char * と書きましょう。そうすればコンパイラがその書き込みをコンパイル時に拒否し、クラッシュになるのを防ぎます。この2つの選択は実のところポインタと配列についての問いで、ポインタと配列で扱います。
もう1つ知っておく価値のある違い。sizeof a は 6 ですが、sizeof p はテキストの長さに関係なくポインタの大きさ(多くのシステムで8)です。
文字列の表示と読み取り
%s は文字列を、%c は1文字を表示します。
入力には gets をまったく使わないでください - 安全に使う方法がないため言語から削除されました。バッファサイズを取りあふれさせない fgets を使いましょう。
fgets はあなたが押した改行を残しますが、それは文字列に欲しいものではまずありません。strcspn の行は最初の '\n' を見つけて終端文字で上書きします - それを取り除く簡潔で標準的な方法です。
文字列を1文字ずつ歩く
終わりが数えられるのではなく印されているので、慣用的なループは文字そのものを調べます。
条件 text[i] != '\0' はしばしば単に text[i] と短く書かれます。'\0' はゼロなので偽だからです。どちらも正しいのですが、学んでいる間は明示的な形のほうが読みやすいでしょう。
添字の代わりにポインタで歩くこともでき、これも同じくらい慣用的なCです。
toupper を呼ぶ前の unsigned char へのキャストは飾りではありません。<ctype.h> の関数は負の値に対して未定義であり、素の char は127を超えるバイトに対して負になりうるからです。
= で文字列をコピーすることはできない
これが新参者の誰もがぶつかる壁です。
char a[10];
a = "hello"; /* エラー: 配列型の式への代入 */
char b[10] = "hi";
char c[10];
c = b; /* エラー - 同じ理由 */
配列名は書き換え可能な値ではないので、= にできることがありません。この省略記法が効く唯一の場所は宣言時の初期化であり、それはコンパイラが行う別の操作です。
実行時にコピーするには、<string.h> の strcpy でバイトをコピーします。
同じ理屈が、== が文字列を比較しない理由も説明します。if (a == b) は2つのアドレスを比較するので、同一のテキストを保持する別々の配列に対して偽になります。代わりに strcmp(a, b) == 0 を使いましょう。これらの関数とより安全な親戚は文字列関数で扱います。
文字列の配列
文字列のリストは、ポインタの配列(読むだけの固定テキストの場合)か、2次元の char 配列(各項目が変更可能でなければならない場合)のどちらかです。
ポインタの配列は読み取り専用リテラルへの3つのアドレスを保持します - 小さいのですが、変更できません。char[3][16] の形は自分のメモリを48バイト確保するので、各行を書き換えられます。
よくある間違い
- 終端文字の1バイトを忘れる。
char s[5] = "hello"には'\0'の余地がありません。strlen + 1で大きさを取りましょう。 strlenのつもりでsizeofを使う。 ポインタに対するsizeofはテキストの長さではなくポインタの大きさを返します。- リテラルを指す
char *を通じて書き込む。 そうしたポインタはconst char *と宣言しましょう。 ==で比較する。 それはアドレスの比較です。strcmpを使いましょう。scanf("%s", buf)で読む。 サイズの上限がなく平然とあふれます。fgetsを選びましょう。
よくある質問
C言語で文字列はどう宣言しますか?
char の配列としてです:char name[] = "Ada";。Cに組み込みの文字列型はありません - この配列は 'A'、'd'、'a' と、終わりを示す4番目の隠れたバイト '\0' を保持しています。あとでテキストを変える余地が欲しければ、配列に明示的なサイズを与えましょう:char name[32] = "Ada";。
C言語のヌル終端文字とは何ですか?
文字列がどこで終わるかを示すバイト '\0'(値はゼロ)です。標準のどの関数も - printf("%s")、strlen、strcpy - そのバイトが見つかるまで前へ読み進めます。それを持たない char 配列は文字列ではなく、それらの関数に渡すとメモリの末尾を越えて読むことになります。
なぜC言語では = で文字列を代入できないのですか?
配列は = でコピーできる値ではないからです。char a[10]; a = "hi"; はコンパイルできません。代わりに <string.h> の strcpy(a, "hi") で文字をコピーするか、宣言時に初期化しましょう - char a[10] = "hi"; - これが省略記法の効く唯一の場所です。
C言語の char * と char [] の違いは何ですか?
char s[] = "hi" はテキストの自分専用のコピーを保持する書き換え可能な配列を作ります。char *p = "hi" は文字列リテラルへのポインタを作り、それは読み取り専用メモリにあります - 読むのは構いませんが、それを通じて書くとクラッシュします。変更するつもりなら配列を、読むだけならポインタを使いましょう。