導入

C 言語には別の文字列型がありません。文字列は文字配列、つまり char 型の配列として扱われます。たとえば、文字列「Hello」は配列 {'H', 'e', 'l', 'l', 'o'} として扱われます。

コンパイラは配列に連続したメモリを割り当て、すべての文字は隣接するメモリ ユニットに格納されます。 C 言語は文字列の最後に、バイナリの '0' で構成されるバイトを自動的に追加し、文字列の終わりを示すために '\0' 文字として書き込まれます。文字 \0 は文字 0 とは異なります。前者の ASCII コードは 0 (バイナリ形式 00000000) であり、後者の ASCII コードは 48 (バイナリ形式 00110000) です。したがって、文字列「Hello」に実際に格納される配列は {'H', 'e', 'l', 'l', 'o', '\0'} となります。

すべての文字列の最後の文字は \0 です。この利点は、C 言語がメモリ内の文字列を読み取る前に文字列の長さを知る必要がないことです。文字が \0 であることが判明する限り、その文字列が認識されることになります。終わりました。

文字ローカル文字列[10];

上記の例では、文字列として扱うことができる 10 メンバーの文字配列を宣言しています。位置は \0 用に予約する必要があるため、最大 9 文字の文字列のみを収容できます。

配列形式で文字列を記述するのは非常に面倒です。 C 言語には、二重引用符で囲まれた文字が自動的に文字配列として扱われる短縮方法が用意されています。

{'H''e''l''l''o''\0'}

// と同等
"こんにちは"

上記 2 つの文字列の書き込み方法は同等であり、内部の保存方法も同じです。二重引用符で囲まれた文字列の場合、終了文字「\0」を自分で追加する必要はありません。C 言語によって自動的に追加されます。

二重引用符には文字列が含まれ、一重引用符には文字が含まれることに注意してください。 「Hello」を一重引用符で囲むと、コンパイラはエラーを報告します。

// エラーを報告する
'こんにちは'

一方、二重引用符内の文字が 1 つしかない場合でも (「a」 など)、文字「'a' ではなく文字列 (2 バイトとして保存) として処理されます ( 1ワードフェスティバルとして保存されます)。

文字列の内部に二重引用符が含まれている場合は、二重引用符をバックスラッシュでエスケープする必要があります。

「彼女は「その通りです」と答えました。」

バックスラッシュは、改行 (\n)、タブ (\t) などの他の特殊文字を表すこともできます。

「こんにちは、世界!\n」

文字列が長すぎる場合は、改行が必要な最後にバックスラッシュ (\) を使用して 1 行を複数行に分割できます。

"こんにちは\
世界"

上の例では、最初の行の末尾にあるバックスラッシュにより、文字列が 2 行に分割されます。

上記の書き方の欠点の 1 つは、インデントを含める場合、2 行目を先頭形式で書かなければならないことです。インデントも文字列に含まれます。この問題を解決するために、C 言語では、複数の文字列リテラルを結合することができます。これらの文字列間にギャップがない場合、またはスペースのみがある場合、C 言語はそれらを自動的に結合します。

chargreeting[50] = "こんにちは、""お元気ですか""今日は!";
// と同等
chargreeting[50] = "こんにちは、今日の調子はどうですか!";

この新しい書き方では、複数行の文字列の結合がサポートされています。

char 挨拶[50] = "こんにちは、"
  "元気ですか"
  "今日!";

printf() はプレースホルダー %s を使用して文字列を出力します。

printf("%s\n", "hello world")

文字列変数の宣言

文字列変数は、文字配列として、または文字配列を指すポインターとして宣言できます。

//書き方その1
char s[14] = "こんにちは、世界!";

//書き方2
char* s = "こんにちは、世界!";

上記の書き方はどちらも文字列変数 s を宣言しています。 1つ目の書き方をすると、文字配列の長さはコンパイラが自動的に計算できるため、宣言時に文字配列の長さを省略できます。

char s[] = "こんにちは、世界!";

上記の例では、コンパイラは配列 s の長さを 14 として指定します。これは、次の文字列をちょうど収容できる長さです。

文字配列の長さは、文字列の実際の長さよりも大きくなる場合があります。

char s[50] = "こんにちは";

上の例では、文字配列 s の長さは 50 ですが、文字列 "hello" の実際の長さはわずか 6 (終了記号 \0 を含む) なので、44 個の空き位置は次のようになります。 \0として初期化されます。

文字配列の長さは、文字列の実際の長さより小さくすることはできません。

char s[5] = "こんにちは";

上の例では、文字列配列 s の長さは 5 であり、文字列 "hello" の実際の長さである 6 よりも短くなります。この場合、コンパイラはエラーを報告します。最初の 5 文字だけを記述し、最後の終了記号 \0 を省略すると、後続の文字列関連のコードでエラーが発生する可能性があるためです。

文字ポインターと文字配列、文字列変数を宣言するこれら 2 つの方法は基本的に同等ですが、2 つの違いがあります。

最初の違いは、ポインタが指す文字列が C 言語内で定数として扱われ、文字列自体を変更できないことです。

char* s = "こんにちは、世界!";
s[0] = 'z'; // エラー

上記のコードは、ポインターを使用して文字列変数を宣言し、文字列の最初の文字を変更します。この書き方は間違っており、予期せぬ結果を招き、実行中にエラーが報告される可能性があります。

配列を使用して文字列変数を宣言する場合、そのような問題は発生せず、配列の任意のメンバーを変更できます。

char s[] = "こんにちは、世界!";
s[0] = 'z';

文字列がポインタとして宣言されている場合は変更できないのに、配列として宣言されている場合は変更できるのはなぜですか?その理由は、システムが文字列リテラルをメモリの定数領域に保存し、ユーザーがこの領域を変更することはできないためです。ポインタとして宣言された場合、ポインタ変数に格納される値は定数領域を指すメモリ アドレスとなるため、ユーザーはこのアドレスを介して定数領域を変更できません。ただし、配列として宣言された場合、コンパイラは配列に別のメモリを割り当て、文字列リテラルはコンパイラによって文字配列として解釈され、この新しく割り当てられたメモリに文字ごとに書き込まれます。

文字列をポインタとして宣言した後に変更してはならないことをユーザーに通知するには、宣言時に const 指定子を使用して文字列が読み取り専用になるようにすることができます。

const char* s = "こんにちは、世界!";

上記の文字列がポインタとして宣言されている場合、文字列が変更できないようにするために const 指定子が使用されます。変更すると、コンパイラは必ずエラーを報告します。

2 番目の違いは、ポインター変数が他の文字列を指すことができることです。

char* s = "こんにちは";
s = 「世界」;

上の例では、文字ポインタは別の文字列を指すことができます。

ただし、文字配列変数は別の文字列を指すことはできません。

char s[] = "こんにちは";
s = "ワールド"; // エラーレポート

上記の例では、文字配列の配列名は初期化中に常に文字列アドレスを指し、変更できません。

同じ理由で、文字配列を宣言した後は、文字配列に直接文字列を代入することはできません。

文字 s[10];
s = "abc"; // エラー

上記の例では、文字列を文字配列変数に直接割り当てることができないため、エラーが報告されます。その理由は、文字配列の変数名が指す配列にバインドされており、別のアドレスを指すことができないためです。

配列変数を別の配列に代入できないのはなぜですか?その理由は、配列変数のアドレスは変更できないためです。つまり、コンパイラが配列変数にアドレスを割り当てると、そのアドレスは配列変数にバインドされ、このバインディング関係は変化しないからです。 C 言語では、配列変数が変更不可能な左辺値であること、つまり、代入演算子を使用して再代入できないことも規定されています。

再割り当てしたい場合は、C 言語がネイティブに提供する strcpy() 関数を使用して、文字列のコピーを通じて割り当てを完了する必要があります。これを実行した後、配列変数のアドレスは変更されません。つまり、 strcpy() は、配列変数に新しいアドレスを指すのではなく、元のアドレスに新しい文字列を書き込むだけです。

文字 s[10];
strcpy(s, "abc");

上記の例では、strcpy() 関数は文字列 abc を変数 s にコピーします。この関数の詳細な使用法は後で紹介します。

strlen()

strlen() 関数は、末尾の null 文字 \0 を除いた文字列の長さをバイト単位で返します。この関数のプロトタイプは次のとおりです。

// 文字列.h
size_t strlen(const char* s);

そのパラメータは文字列変数であり、非常に長い文字列でない限り、通常は size_t 型の符号なし整数として扱われます。以下に使用例を示します。

char* str = "こんにちは";
int len = strlen(str);

strlen() のプロトタイプは標準ライブラリの string.h ファイルに定義されており、使用する際にはヘッダファイル string.h をロードする必要があります。

#include <stdio.h>
#include <文字列.h>

int main(void) {
  char* s = "こんにちは、世界!";
  printf("文字列の長さは %zd 文字です。\n", strlen(s));
}

文字列の長さ (strlen()) と文字列変数の長さ (sizeof()) は 2 つの異なる概念であることに注意してください。

char s[50] = "こんにちは";
printf("%d\n", strlen(s)); // 5
printf("%d\n", sizeof(s)); // 50

上記の例では、文字列の長さは 5、文字列変数の長さは 50 です。

この関数を使用しない場合は、文字列の末尾の\0を判断して文字列の長さを自分で計算することができます。

int my_strlen(char *s) {
  int カウント = 0;
  while (s[カウント] != '\0')
    カウント++;
  戻り数;
}

strcpy()

文字列をコピーする場合、代入演算子を使用して文字列を文字配列変数に直接代入することはできません。

文字 str1[10];
文字 str2[10];

str1 = "abc"; // エラーレポート
str2 = str1; // エラーレポート

文字列をコピーおよび書き込む上記の 2 つの方法はどちらも間違っています。配列の変数名は固定アドレスであるため、別のアドレスを指すように変更することはできません。

文字ポインタの場合、代入演算子 (=) は文字列をコピーするのではなく、あるポインタのアドレスを別のポインタにコピーするだけです。

char* s1;
char* s2;

s1 = "abc";
s2 = s1;

上記のコードを実行すると、文字列 s1 の内容を s2 にコピーするのではなく、2 つのポインター変数 s1s2 が同じ文字列を指すようになります。

C 言語には strcpy() 関数が用意されています。この関数は、ある文字列の内容を別の文字列にコピーするために使用されます。これは文字列の代入と同等です。この関数のプロトタイプは、string.h ヘッダー ファイルで定義されます。

strcpy(char dest[], const char source[])

strcpy() は 2 つのパラメータを受け入れます。最初のパラメータは宛先文字列配列で、2 番目のパラメータはソース文字列配列です。文字列をコピーする前に、最初のパラメータの長さが 2 番目のパラメータ以上であることを確認する必要があります。そうしないと、エラーは報告されませんが、最初の文字列変数の境界がオーバーフローし、予測できない結果が生じます。 2 番目のパラメータの const 指定子は、この関数が 2 番目の文字列を変更しないことを示します。

#include <stdio.h>
#include <文字列.h>

int main(void) {
  char s[] = "こんにちは、世界!";
  文字 t[100];

  strcpy(t, s);

  t[0] = 'z';
  printf("%s\n", s); // "こんにちは!"
  printf("%s\n", t); // "ゼロ、ワ​​ールド!"
}

上記の例では、変数 s の値を変数 t にコピーし、それを 2 つの異なる文字列に変換します。一方を変更しても、もう一方には影響しません。また、変数 't' の長さは 's' より大きく、コピー後の余分な位置 (エンドマーク '\0' 以降の位置) はすべてランダムな値です。

strcpy() は文字配列への代入にも使用できます。

文字 str[10];
strcpy(str, "abcd");

上記の例では、文字配列変数を文字列「abcd」に割り当てます。

strcpy() の戻り値は、最初のパラメータを指す文字列ポインタ (すなわち、char*) です。

char* s1 = "獣";
char s2[40] = "できる限り最高の自分になりましょう。";
char* ps;

ps = strcpy(s2 + 7, s1);

put(s2); // 野獣になろう
put(ps); // 獣

上記の例では、文字列 beasts2 の 7 番目の位置からコピーされ、それ以前の位置は変更されません。これにより、「beast」の末尾にあるヌル文字が一緒にコピーされるため、「s2」以降のコンテンツが切り捨てられます。 strcpy() は、コピーの開始位置を指すポインタを返します。

strcpy() の戻り値のもう 1 つの用途は、複数の文字配列に値を連続的に代入することです。

strcpy(str1, strcpy(str2, "abcd"));

上の例では、strcpy() を 2 回呼び出して、2 つの文字列変数の割り当てを完了しています。

さらに、strcpy() の最初のパラメータは、宣言後に初期化されていない文字ポインタではなく、宣言された配列であることが最善です。

char* str;
strcpy(str, "hello world"); // エラー

上記のコードには何か問題があります。 strcpy() は文字列をポインタ変数 str に割り当てますが、str は初期化されておらず、ランダムな場所を指すため、文字列は任意の場所にコピーできます。

strcpy() が必要なく、文字列を自分でコピーする場合は、次のコードを使用できます。

char* strcpy(char* dest, const char* ソース) {
  char*ptr = 宛先;
  while (*dest++ = *source++);
  ptr を返します。
}

int main(void) {
  文字 str[25];
  strcpy(str, "ハローワールド");
  printf("%s\n", str);
  0を返します。
}

上記のコードでは、キー行は while (*dest++ = *source++) です。これは、source の各文字を順番に dest に割り当て、 に遭遇するまで次の位置に移動するループです。 \0 では、ループの判定条件が成立しなくなり、ループから抜け出します。このうち、*dest++ という式は、*(dest++) と同等です。つまり、最初に dest のアドレスが返され、次にインクリメント操作が実行されて次の位置に移動し、*dest 現在の位置に値を割り当てることができます。

strcpy() 関数は、ターゲット文字列の長さがソース文字列のコピーを収容するのに十分であるかどうかをチェックしないため、セキュリティ リスクがあり、書き込みオーバーフローが発生する可能性があります。オーバーフローが発生しないという保証がない場合は、代わりに strncpy() 関数を使用することをお勧めします。

strncpy()

strncpy()strcpy() とまったく同じように使用されますが、3 番目のパラメータがある点が異なります。このパラメータは、ターゲット文字列変数の境界を超えないようコピーする最大文字数を指定するために使用されます。

char*strncpy(
  char* 宛先、
  char* ソース、
  サイズ_t n
);

上記のプロトタイプでは、3 番目のパラメーター「n」はコピーされる文字の最大数を定義します。最大文字数に達してもコピー元の文字列が完全にコピーされていない場合は、コピー先の文字列の末尾にターミネータ「\0」が存在しないことに注意してください。ソース文字列の文字数が「n」文字未満の場合、「strncpy()」は「strcpy()」とまったく同じように動作します。

strncpy(str1, str2, sizeof(str1) - 1);
str1[sizeof(str1) - 1] = '\0';

上記の例では、文字列 str2str1 にコピーされますが、コピーの長さは最大でも str1 から 1 を引いた長さであり、str1 に残っている最後のビットは、文字列の終了マークを書き込むために使用されます。文字列「\0」。これは、strncpy()\0 自体を追加しないためです。コピーされた文字列フラグメントに終了マークが含まれていない場合は、手動で追加する必要があります。

strncpy() を使用して文字列の一部をコピーすることもできます。

文字 s1[40];
char s2[12] = "ハローワールド";

strncpy(s1, s2, 5);
s1[5] = '\0';

printf("%s\n", s1); // こんにちは。

上記の例では、最初の 5 文字のみがコピーされるように指定されています。

strcat()

strcat() 関数は文字列を連結するために使用されます。これは 2 つの文字列を引数として受け取り、2 番目の文字列のコピーを最初の文字列の末尾に追加します。この関数は最初の文字列を変更しますが、2 番目の文字列は変更しません。

この関数のプロトタイプは、string.h ヘッダー ファイルで定義されます。

char* strcat(char* s1, const char* s2);

strcat() の戻り値は、最初のパラメータを指す文字列ポインタです。

char s1[12] = "こんにちは";
char s2[6] = "世界";

strcat(s1, s2);
put(s1); // "ハローワールド"

上の例では、strcat() を呼び出した後、文字列 s1 の値が変更されたことがわかります。

strcat() の最初の引数の長さは、2 番目の引数文字列の追加に対応できる十分な長さでなければならないことに注意してください。そうしないと、連結された文字列が最初の文字列の境界を越えて隣接するメモリ ユニットに書き込まれてしまうため、代わりに次の strncat() を使用することをお勧めします。

strncat()

strncat() は 2 つの文字列を接続するために使用されます。使い方は strcat() とまったく同じですが、追加する最大文字数を指定するために 3 番目のパラメーターが追加される点が異なります。追加プロセス中に、指定された文字数に達するか、ソース文字列内でヌル文字 \0 が検出されると、その文字は追加されなくなります。そのプロトタイプは string.h ヘッダー ファイルで定義されます。

char*strncat(
  const char* dest、
  const char* ソース、
  サイズ_t n
);

strncat() は最初の引数、つまりターゲット文字列ポインタを返します。

連結された文字列がターゲット文字列の長さを超えないようにするために、strncat() は通常次のように記述されます。

strncat(
  str1、
  str2、
  sizeof(str1) - strlen(str1) - 1
);

strncat() は常にスプライシング結果の末尾にヌル文字 \0 を自動的に追加するため、3 番目のパラメータの最大値は、str1 の可変長から str1 の文字列長を引いたものでなければなりません。次に「1」を減算します。以下に使用例を示します。

char s1[10] = "月曜日";
char s2[8] = "火曜日";

strncat(s1, s2, 3);
put(s1); // "月曜日火曜日"

上の例では、s1 の可変長は 10、文字長は 6 です。 2 を減算した後、1 を減算して 3 を取得します。これは、s1 がさらに 3 文字まで追加できることを示しており、結果は次のようになります。は「月曜日火曜日」です。

strcmp()

2 つの文字列を比較したい場合、それらを直接比較することはできません。C 言語には strcmp() 関数が用意されています。

strcmp() 関数は、2 つの文字列の内容を比較するために使用されます。この関数のプロトタイプは次のとおりで、string.h ヘッダー ファイルで定義されています。

int strcmp(const char* s1, const char* s2);

辞書の順序に従って、2 つの文字列が同じである場合、戻り値は '0' になり、's1' が 's2' より小さい場合、'strcmp()' の戻り値は 0 より小さくなります。が s2 より大きい場合、戻り値は 0 より大きくなります。

以下に使用例を示します。

// s1 = 明けましておめでとうございます
// s2 = 明けましておめでとうございます
// s3 = ハッピーホリデー

strcmp(s1, s2) // 0
strcmp(s1, s3) // 0より大きい
strcmp(s3, s1) // 0未満

strcmp() は文字列ではなく文字列の比較にのみ使用されることに注意してください。文字は小さな整数であるため、等価演算子 (==) を使用して直接比較できます。したがって、文字型 (char) の値を strcmp() にパラメータとして入れないでください。

strncmp()

strcmp() は文字列全体を比較するため、C 言語には指定された位置のみを比較する strncmp() 関数が用意されています。

この関数は、比較する文字数を指定する 3 番目のパラメーターを追加します。そのプロトタイプは string.h ヘッダー ファイルで定義されます。

int strncmp(
  const char* s1、
  const char* s2、
  サイズ_t n
);

戻り値は strcmp() と同じです。 2 つの文字列が同じである場合、戻り値は 0 になります。s1 が s2 より小さい場合、strcmp() の戻り値は 0 より小さくなります。 ` の場合、戻り値は 0 より大きくなります。

以下に例を示します。

char s1[12] = "こんにちは世界";
char s2[12] = "こんにちは C";

if (strncmp(s1, s2, 5) == 0) {
  printf("みんなこんにちは。\n");
}

上の例では、2 つの文字列の最初の 5 文字のみを比較します。

sprintf()、snprintf()

sprintf() 関数は printf() に似ていますが、データをディスプレイに出力するのではなく文字列に書き込むために使用されます。この関数のプロトタイプは、stdio.h ヘッダー ファイルで定義されます。

int sprintf(char* s, const char* 形式, ...);

sprintf() の最初のパラメータは文字列ポインタ変数で、残りのパラメータは printf() と同じです。つまり、2 番目のパラメータはフォーマット文字列で、次のパラメータは変数のリストです。書かれる。

char first[6] = "こんにちは";
char last[6] = "世界";
文字 s[40];

sprintf(s, "%s %s", first, last);

printf("%s\n", s); // こんにちは。

上の例では、sprintf() は出力を「hello world」に結合し、それを変数 s に入れます。

sprintf() の戻り値は、変数に書き込まれた文字数です (末尾の null 文字 \0 はカウントしません)。エラーが発生した場合は、負の値が返されます。

sprintf() には重大なセキュリティ上のリスクがあります。書き込まれた文字列が長すぎてターゲット文字列の長さを超えた場合でも、sprintf() はそれを書き込み、オーバーフローを引き起こします。書き込む文字列の長さを制御するために、C言語には別の関数snprintf()が用意されています。

snprintf() には、sprintf() よりも 1 つだけ多くのパラメータ n があります。これは、変数に書き込まれる文字列が n - 1 文字を超えないように制御するために使用され、ヌル文字 \0 は残りの位置には が書き込まれます。以下はそのプロトタイプです。

int snprintf(char*s, size_t n, const char* 形式, ...);

snprintf() は常に自動的に文字列の末尾に null 文字を書き込みます。指定された最大文字数を超える文字を書き込もうとすると、 snprintf() は n - 1 文字を書き込み、最後の位置を null 文字に残します。

以下に例を示します。

snprintf(s, 12, "%s %s", "hello", "world");

上記の例では、 snprintf() の 2 番目のパラメータは 12 です。これは、書き込まれる文字列の最大長が 12 を超えないことを意味します (末尾の null 文字を含む)。

snprintf() の戻り値は、フォーマット文字列に書き込まれた文字数です (末尾のヌル文字 \0 はカウントしません)。 n が十分に大きい場合、戻り値は n より小さくなるはずですが、場合によってはフォーマット文字列の長さが n よりも大きい場合、戻り値は n より大きくなりますが、実際には実際に変数に書き込まれるのは n-1 文字です。エラーが発生した場合は、負の値が返されます。したがって、戻り値が負ではなく、「n」より小さい場合にのみ、完全なフォーマット文字列が変数に書き込まれたことを確認できます。

文字列配列

配列の各メンバーが文字列の場合は、2 次元の文字配列として実装する必要があります。各文字列自体は文字配列であり、複数の文字列が配列を形成します。

char 平日[7][10] = {
  "月曜日""火曜日""水曜日""木曜日""金曜日""土曜日""日曜日"
};

上記の例は、合計 7 つの文字列を含む文字列配列であるため、最初の次元の長さは 7 です。このうち、最も長い文字列の長さは 10 (終了ターミネータ \0 を含む) であるため、2 次元の長さは一律 10 に設定されます。

最初の次元の長さはコンパイラが自動的に計算できるため、省略できます。

char 平日[][10] = {
  "月曜日""火曜日""水曜日""木曜日""金曜日""土曜日""日曜日"
};

上記の例では、2 次元配列の最初の次元の長さは、後続の代入に基づいてコンパイラによって自動的に計算できるため、記述する必要はありません。

配列の 2 番目の次元の長さは一律 10 に設定されますが、ほとんどのメンバーの長さが 10 未満であるため、これはスペースの無駄です。解決策は、配列の 2 番目の次元を文字配列から文字ポインタに変更することです。

char* 平日[] = {
  "月曜日""火曜日""水曜日""木曜日""金曜日""土曜日""日曜日"
};

上記の文字列配列は実際には 1 次元配列であり、そのメンバーは 7 つの文字ポインターであり、各ポインターは文字列 (文字配列) を指します。

文字列配列をトラバースする方法は次のとおりです。

for (int i = 0; i < 7; i++) {
  printf("%s\n", 平日[i]);
}

作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0