目次
0 はじめに
C 言語では、文字列と配列には多くの類似点があり、呼び出し用の公式ライブラリ関数が多数提供されています。それでは、姉妹の文字列と配列にはどのような密接な関係があるのでしょうか? 今回のキーキャラクターとして、文字列の特徴は何でしょうか?
文字列は文字列定数として現れるか、文字配列に格納されるため、C 言語には明示的な文字列データ型がありません。文字列定数は、プログラムによって変更されない文字列に適しています。他のすべての文字列は、文字配列または動的に割り当てられたメモリに格納する必要があります。
この記事では、誰もがさまざまな状況で最適なライブラリ関数を選択できるように、文字列に対して一般的に使用されるライブラリ関数をいくつか紹介することに重点を置いています。この記事の内容の概要は以下の通りです
1 文字列の基本
文字列は、ビット パターンが all であるバイトで終わるシーケンス0个
または文字です。例えば:多个
0
NUL
char message[] = "hello word";
2 文字列の長さ
文字列の長さは、最後のターミネータを除いた、文字列に含まれる文字数です。これは面接時に試されることが多いです。
文字列の長さは、ライブラリ関数 strlen() を使用して自動的に計算できます。
例えば:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char message[] = "hello word";
printf("字符串的长度为:%d\n",strlen(message));
system("pause");
return 0;
}
出力の印刷:
この関数は符号なしの数値を返すため、この関数を通じて 2 つの文字列の長さを比較する場合は特別な注意を払う必要があることに注意してください。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char message1[] = "hello word";
char message2[] = "hello Shanghai";
//比较方式1
if(strlen(message1) >= strlen(message2))
printf("字符串1更长\n");
else
printf("字符串2更长\n");
//比较方式2
if (strlen(message1) - strlen(message2) >= 0)
printf("字符串1更长\n");
else
printf("字符串2更长\n");
system("pause");
return 0;
}
印刷出力:
符号なしの数値が返されるため、比較方法2の場合、条件判定の結果は常に真となるため、判定結果に誤差が生じます。
3 無制限の文字列関数
いわゆる無制限の文字列関数とは、使用時に文字列 (実パラメータ) の長さを指定する必要がなく、関数がスムーズに実行できることを意味します。
3.1 文字列をコピーする
開発では文字列のコピーがよく行われますが、新しい文字列にコピーすると元の部分が上書きされてしまうので注意が必要です。
例えば:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char message1[] = "hello word";
char message2[] = "hello Shanghai";
int message2_len = strlen(message2);
printf("字符串2的长度为:%d\n", strlen(message2));
strcpy(message2,message1);
printf("字符串2的长度为:%d\n",strlen(message2));
for(int i = 0; i < message2_len; i++)
printf("%c",message2[i]);
system("pause");
return 0;
}
印刷出力:文字列を にコピーした後、実際には長さが異なること
がわかります。これはなぜですか?message1
message2
message2
これは、文字列をコピーするときにターミネータもコピーされるためで、strlen()
関数が処理されると必ず が返されますが、10
出力された結果から判断すると、message2
文字列の残りの部分はまだ保持されています。
長い文字列を短い文字列にコピーすると、コピーする必要のある文字を収容するのに十分なスペースがないため、エラーが報告されることがよくあります。
3.2 接続文字列
文字列を連結するときは、strcat() 関数を使用できます。そのプロトタイプは次のとおりです。
char *strcat(char *dst, char const *src);
例えば:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char message1[] = "hello word";
char message2[] = "hello Shanghai";
strcat(message1,message2);
printf("%s\n", message1);
system("pause");
return 0;
}
印刷出力:
2 つの文字列が直接連結されていることがわかります。新しい文字列の長さの値は、元の 2 つの文字列の長さの合計です。
3.3 関数の戻り値
これらの関数の戻り値は最初のパラメータのコピーである場合があるため、文字列を実際のパラメータとして使用すると最初の要素のアドレスも渡されるため、ネストすることができます。したがって、これらの関数は多くの場合、ネストされた関数と呼ばれます。
例えば:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char message1[] = "hello ";
char message2[] = "word ";
char message3[] = "Shanghai";
strcat(strcat(message1, message2), message3);
printf("%s\n", message1);
system("pause");
return 0;
}
印刷出力:
ただし、プログラムを読みやすくするために、ネストしなくても大丈夫です。
3.4 文字列の比較
文字列比較の場合、一般的に使用されるライブラリ関数は 1 つだけですstrcmp
。そのプロトタイプは次のとおりです。
int strcmp(char const *s1, char const *s2);
この関数の比較ルールは非常に興味深いものです。この関数は、不一致が見つかるまで 2 つの文字列の文字を 1 つずつ比較します。ここには 2 つの状況があります:
- 最初の不一致文字の中で最も高い ASCII ランキングを持つ文字を含む文字列は、より小さい文字列とみなされます。
- 先頭の両方の文字列が等しい場合、短い文字列が小さい文字列とみなされます。
文字列中に特定の不一致文字が見つかった場合、残りの部分を比較せずに比較結果を得ることができます。
以下に示すように、
実際のコードを見てください。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char temp1[] = "hello";
char temp2[] = "hello world";
char temp3[] = "hello worLd";
//字符串temp1和temp2作比较
if(strcmp(temp1,temp2) == 0)
{
printf("temp1 = temp2\n");
}
else if (strcmp(temp1, temp2) > 0)
{
printf("temp1 > temp2\n");
}
else if (strcmp(temp1, temp2) < 0)
{
printf("temp1 < temp2\n");
}
printf("------------------\n");
//字符串temp2和temp3作比较
if (strcmp(temp2, temp3) == 0)
{
printf("temp2 = temp3\n");
}
else if (strcmp(temp2, temp3) > 0)
{
printf("temp2 > temp3\n");
}
else if (strcmp(temp2, temp3) < 0)
{
printf("temp2 < temp3\n");
}
printf("\n");
system("pause");
return 0;
}
印刷する:
4 長さ制限のある文字列関数
一部のライブラリ関数を呼び出すときは、処理する文字列の長さを渡す必要があるため、これらは長さ制限のある文字列関数と呼ばれます。
これらの関数は、予測できない長い文字列がターゲット配列からオーバーフローするのを防ぐ便利なメカニズムを提供します。
いくつかの共通関数があります。
char *strncpy(char *dst, char const *src, size_t len);
char *strncat(char *dst, char const *src, size_t len);
int strncmp(char const *s1, char const *s2, size_t len);
例えば:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char message1[] = "hello ";
char message2[] = "hello Beijing ";
char message3[] = "Shanghai";
char message_all[] = "hello Beijing Shanghai";
if(strncmp(strncpy(strncat(message2, message3, strlen(message3)), message1, strlen(message1)), message_all,strlen(message_all)) == 0)
printf("二者相等\n");
else
printf("二者不相等\n");
system("pause");
return 0;
}
印刷:
この例はあまり適切ではありません。長さは最大値に従って渡されるため、問題の説明も可能です。
5 文字列検索の基本
標準ライブラリには、さまざまな方法を使用して文字列を検索する関数が多数あります。これらのさまざまなツールは、C プログラマに大きな柔軟性を与えます。
5.1 文字列を検索する
文字列内の特定の文字を検索するために使用できるライブラリ関数が 2 つあります。
char *strchr(char const *str, int ch);
char **strrchr(char const *str, int ch);
前者は特定の文字を見つけるために使用されます初め文字を検索するために使用される、それが発生する位置 (アドレスへのポインタを返します)最後にこれが発生する場所 (そのアドレスへのポインタを返します)。
これら 2 つの関数は次のように使用できます。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char message1[] = "hello ";
char message2[] = "hello Beijing ";
char message3[] = "Shanghai";
char message_all[] = "hello Beijing Shanghai";
char *first_site, *last_site;
first_site = strchr(message_all, 'h');
last_site = strrchr(message_all, 'h');
printf("字符串的长度是:%d\n",strlen(message_all));
printf("h第一次出现的位置是:%d\n", first_site - message_all);
printf("h最后一次出现的位置是:%d\n", last_site - message_all);
system("pause");
return 0;
}
出力の印刷:
この関数はターゲット要素の位置の値を返すのではなく、ポインターを返すため、結果を取得するには文字列の最初の要素のポインターとは異なる値を返す必要があることに注意してください。
注: 検索では大文字と小文字が区別されます。
5.2 任意の数文字を検索する
strpbrk
は、ターゲット文字列内の文字列内で最初に出現する文字を検索するために使用される、より一般的な関数です。そのプロトタイプは次のとおりです。
char *strpbrk(char const *str, char const *group);
次のように使用できます。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char message_all[] = "hello Beijing Shanghai";
char *first_site;
first_site = strpbrk(message_all, "abcde");
printf("字符串的长度是:%d\n",strlen(message_all));
printf("abcde第一次出现匹配字符的位置是:%d\n", first_site - message_all);
system("pause");
return 0;
}
印刷出力:
最初に一致した文字が でe
、位置が であることが簡単にわかります1
。
5.3 部分文字列の検索
文字列内の部分文字列を見つけるには、strstr 関数を使用できます。そのプロトタイプは次のとおりです。
char *strstr(char const *s1, char const *s2);
実際の使用例を挙げると、次のようになります。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char message_all[] = "hello Beijing Shanghai";
char *first_site;
first_site = strstr(message_all, "Beijing");
printf("字符串的长度是:%d\n",strlen(message_all));
printf("Beijing第一次出现的位置是:%d\n", first_site - message_all);
system("pause");
return 0;
}
印刷出力:
この検索では、特定の文字や部分的な文字ではなく、すべての文字が一致する必要があることがわかります。
6 高度な文字列検索
次の関数セットは、文字列の先頭から部分文字列を検索して抽出するプロセスを簡素化します。
6.1 文字列プレフィックスを検索する
strspn
およびstrcspn
関数は、文字列内の開始位置で文字列をカウントするために使用され、そのプロトタイプは次のとおりです。
size_t strspn( char const *str, char const *group);
size_t strcspn( char const *str, char const *group);
これら 2 つの関数は要素ポインターを返すのではなく、一致する文字の実際の数を返すことに注意してください。
具体的な使用法については、例を参照してください。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int len1, len2;
char buffer[] = "25,142,330,smith,J,239-4123";
len1 = strspn(buffer, "0123456789");
len2 = strcspn(buffer, ",");
printf("0123456789的起始匹配数是:%d\n", len1);
printf(",的起始不匹配数是:%d\n", len2);
system("pause");
return 0;
}
印刷出力:
上記の例からわかるように、strspn
関数は最初から開始して、検索している文字列に一致する文字が見つからなくなるまで検索します。この例では,
適切ではなくなったため、連続検索の場合は2
1 つだけが適切になります。
strcspn 関数はその逆で、探しているものが一致していません、先頭の 2 と 5 は明らかに一致していませんが、一致していますので、連続検索の場合は適切なものがあり,
ます2
。
6.2 マークの検索
多くの場合、文字列には、互いに分離された複数の個別の部分が含まれます。これらの部分を処理するたびに、まず文字列からそれらの部分を抽出する必要があります。
strtok関数はそのような機能を実現することができます。文字列からトークンと呼ばれる個々の部分を分離します。そして区切り文字を破棄します。そのプロトタイプは次のとおりです。
char *strtok( char *str, char const *sep);
知らせ:
- strtok 関数はタスクを実行するときに、処理する文字列を変更します。ソース文字列を変更できない場合は、コピーを作成し、このコピーを strtok 関数に渡します。
- strtok 関数の最初のパラメータが NULL でない場合、関数は文字列の最初のトークンを検索します。strtok は文字列内の位置も保存します。strtok 関数の最初のパラメータが NULL の場合、関数は以前と同じように保存された位置から開始して、同じ文字列内の次のトークンを検索します。
例えば:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int add = 0;
char buffer[] = "25,142,330,smith,J,239-4123";
char *token = NULL;
for (token = strtok(buffer, ","); token != NULL; token = strtok(NULL, ","))
{
printf("%s\n", token);
add++;
}
printf("--------------------------\n");
printf("add的值为:%d\n",add);
system("pause");
return 0;
}
出力の印刷:
上記の例からわかるように、境界として検出する必要があるマークを使用すると、各ループはすべての分割が完了するまで分割された部分文字列を取得します。6
合計の分割回数。
7 エラーメッセージ
C 言語ライブラリ関数の実行に失敗すると、エラー コード (0 1 2 3 4 5 6 7 8 9...) が表示されます。オペレーティング システムは、外部整数変数を設定することによってerrno
エラー コードを報告します。つまり、エラー コードはエラーの種類に対応しており、strerror
関数はエラー コードの 1 つをパラメータとして受け取り、次のエラー コードへのポインタを返します。エラーを説明する文字列へのポインタ。この関数のプロトタイプは次のとおりです。
char *strerror(int error_number);
例えば:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
for(int i = 0; i < 10; i++)
printf("%s\n",strerror(i));
system("pause");
return 0;
}
印刷出力:異なるオペレーション コードが異なるエラー タイプに対応しており、エラー コードはエラーがないことを示していること
がわかります。0
その他、さまざまなエラーが示されます。この部分だけは理解してください。マスタリングは必要ありません。各オペコードがどのようなエラーを表すかを知る必要はありません。
8文字の操作
標準ライブラリには、個々の文字を操作するための 2 つの関数セットが含まれており、それらのプロトタイプはヘッダー ファイル ctype.h にあります。最初の関数セットは文字列の分類に使用され、2 番目の関数セットは文字の変換に使用されます。
8.1 文字の分類
各分類関数は、文字値を含む整数パラメータを受け入れます。この関数はこの文字をテストし、true または false を表す整数値を返します。次の表に、各関数と true を返すために必要な条件を示します。
関数 | trueを返すために必要な条件 |
---|---|
iscntrl | 制御文字 |
イススペース | 空白文字: スペース、フォーム フィード '\f'、ライン フィード '\n'、キャリッジ リターン '\r'、タブ 't' または垂直タブ '\v' |
平 | 10進数 |
自分自身の数字 | 大文字と小文字の a ~ f を含む 16 進数 |
低い | 小文字 |
上です | 大文字 |
イアルファ | 文字 (大文字または小文字) |
氷のホール | 文字または数字 |
途切れている | 数字や文字ではない任意のグラフィック文字 (印刷可能な記号) |
イスグラフ | 任意のグラフィック文字 |
スプリント | グラフィック文字や空白文字を含む、印刷可能な任意の文字 |
したがって、これらの関数は文字列要素を決定するために使用されます。たとえば、次のようになります。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
int main()
{
char temp[] = "To carry things with great virtue";
for (int i = 0; i < strlen(temp); i++)
{
if (islower(temp[i]))
printf("temp[%d] : %c是小写字母\n", i, temp[i]);
else if (isupper(temp[i]))
printf("temp[%d] : %c是大写字母\n", i, temp[i]);
else if(isspace(temp[i]))
printf("temp[%d] : %c是空格\n", i, temp[i]);
}
printf("\n");
system("pause");
return 0;
}
印刷出力:各要素が大文字、小文字、スペースのいずれであるかが判定されていることが
わかります。temp
8.2 文字変換
変換関数は、大文字を小文字に、または小文字を大文字に変換します。呼び出す関数は 2 つあります。toupper
関数の戻り値対応するパラメータの大文字形式、tolower
関数は戻りますパラメータに対応する小文字の形式。
int tolower(int ch);
int toupper(int ch);
実際の例を挙げてみましょう。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
int main()
{
char temp1[] = "To carry things with great virtue";
char temp2[] = "To carry things with great virtue";
//全转换为大写
for (int i = 0; i < strlen(temp1); i++)
{
if (islower(temp1[i]))
temp1[i] = toupper(temp1[i]);
printf("%c",temp1[i]);
}
printf("\n-----------------------------\n");
//全转换为小写
for (int i = 0; i < strlen(temp2); i++)
{
if (isupper(temp2[i]))
temp2[i] = tolower(temp2[i]);
printf("%c", temp2[i]);
}
printf("\n");
system("pause");
return 0;
}
印刷出力:
ご覧のとおり、文字列内の文字の大文字化を希望に応じて調整できます。
9 メモリ操作
通常、文字列は NUL で終わりますが、途中に NUL を含む文字列や任意の長さのバイト シーケンスを処理したい場合、以前の関数は比較的弱いか、まったく使用できません。ただし、実際の開発でのニーズを満たすために別の関数セットを使用することもできます。以下はそのプロトタイプです。
注: これらの関数は文字列だけでなく、構造または配列処理できる特定のデータ型は、特定の関数によって異なります。
void *memcpy(void *dst, void const *src, size_t length);
void *memmove(void *dst, void const *src, size_t length);
void *memcmp(void const *a, void const *b, size_t length);
void *memchr(void const *a, int ch, size_t length);
void *memset(void *a, int ch, size_t length);
例えば:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE 10
int main()
{
char temp1[] = "hello world";
char temp2[] = "hello world";
char temp3[] = "hello world";
char temp4[] = "hello world";
unsigned int int_array[SIZE];
char *p = NULL;
//复制字符串
memcpy(temp1 + 2, temp1, 5);
memmove(temp2 + 2, temp2, 5);
printf("temp1 = %s\n", temp1);
printf("temp2 = %s\n", temp2);
printf("---------------------------------------\n");
//比较字符串
if(!memcmp(temp1, temp2, 6))
printf("temp1 = temp2\n");
else
printf("temp1 != temp2\n");
printf("---------------------------------------\n");
//查找字符
p = (char *)memchr(temp3, 'e', strlen(temp3));
if(p != NULL)
printf("字符e在temp3中的位置是:%d\n", p - &temp3[0]);
printf("---------------------------------------\n");
//初始化数组
memset(int_array, 0, sizeof(int_array));
for (int i = 0; i < SIZE; i++)
printf("int_array[%d]的值为:%d\t", i, int_array[i]);
printf("\n", sizeof(int));
printf("---------------------------------------\n");
//初始化数组
memset(temp4, 'a', sizeof(temp4) - 1);
printf("字符串temp4为:%s\n", temp4);
system("pause");
return 0;
}
印刷する:
9.1 memcpy と memmove は本当に違いますか?
議論する価値のある疑問があります:
memcpy 関数と memmove 関数は本当に同じですか? 「C とポインタ」、およびインターネット上の多くの意見は次のとおりです: この 2 つは異なります。src と dst が重なっている場合、memcpy には問題が発生しますが、memmove は常に理想的な条件に従って実行できますが、プログラムの結果は「はい、両方とも」ではありません。関数は理想的に機能します。なぜですか?
唯一の説明は、ソフトウェアの実行環境が異なり、プログラムの基盤となるライブラリも異なるため、このような状況が発生します。。ただし、これは、memcpy と memmove の以前のバージョン (つまり、2 つは異なります) に関する調査には影響しません。
まず、重複の意味と、重複がある場合に文字列のコピーで問題が発生する理由を見てみましょう。
上は、プログラムでの文字列コピー操作の概略図です。src 部分文字列と dst 部分文字列の 3 文字が重なっていることがわかりますが、従来の操作方法に従ってコピーすると、次の結果が得られます。
領域が重なると、コピー エラーが発生します。つまり、目的の値が新しい値に置き換えられます。カバー、値がスムーズに取得できなくなってしまい、コピーしたらtemp1
こうなってしまいましたhehehehorld
。これはmemcpy
、以前に文字列がコピーされた方法です。
memmove
次に、この問題をうまく解決する(そして最適化するmemcpy
) 方法を見てみましょう。
ご覧のとおり、コピーの順序が変更されており、今回は後ろから前にコピーされるため、この問題はうまく回避されます。
そこで質問ですが、今回はコピー先が後ろになっていますが、前にあった場合はどうすればよいでしょうか?答えは、コピーの順序も逆になるということです。実行プロセスを見てみましょう:
このとき、取得される値は元の値で上書きされず、期待される結果に従って実行されます。結果は次のとおりですllo w world
。
9.2 memcmp: 単純な比較
memcmp
メモリ領域 a との最初の長さのバイトを比較します。比較方法や戻り値はstrcmp
基本的に同じですので、詳しくは本記事の項を参照してくださいstrcmp
。
9.3 memchr: 単純な検索
memchr は、a の開始位置から開始して最初に出現する文字 ch を検索し、その位置へのポインタを返します。検索方法や戻り値はstrchr
基本的に同じですので、詳しくはこの記事の項を参照してくださいstrchr
。
9.4 memset: 初期化された値は 0 と -1 のみですか?
この関数の導入当初から判断すると、この関数は連続したメモリ領域に(理論上は)同じ値0
を設定できるのですが、実際の開発では基本的にまたはに設定されるのですが、-1
これはなぜでしょうか。
これは、この関数がバイト単位でメモリに値を割り当てるためです。文字列の要素は 1 バイトしか占有しないため、問題なく文字列に値を割り当てるために一般的に使用されますが、配列は異なります。共通の short 、 int 、 long type 配列要素は 1 バイトを超えるため、初期化では期待した結果が得られません。0 に設定すると、各バイトは 0 (-1 の場合、各ビットは 1) になるため、各要素はバイト数に関係なく 0 に初期化されますが、他の値の場合は結果は初期化されません。同じであること、同じであること。次の手順を参照してください。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE 10
int main()
{
unsigned int int_array[SIZE];
//初始化数组
printf("--------------初始化值设为0-------------------------\n");
memset(int_array, 0, sizeof(int_array));
for (int i = 0; i < SIZE; i++)
printf("int_array[%d]的值为:%d\t", i, int_array[i]);
printf("\n");
printf("--------------初始化值设为1-------------------------\n");
memset(int_array, 1, sizeof(int_array));
for (int i = 0; i < SIZE; i++)
printf("int_array[%d]的值为:%d\t", i, int_array[i]);
printf("\n");
system("pause");
return 0;
}
プリントアウト:
これはなぜですか?
これは、memset
値がバイト単位で割り当てられ、int
型データが4
メモリ内で 3 バイトを占めるため、値は4
バイト単位である必要があります。つまり、0x01010101
10 進数に変換すると、正確に になります16843009
。
したがって、一般に、memse
メモリ領域に同じ値を設定する場合は、0
またはに初期化すること-1
が最良の選択です。
10 まとめ
文字列自体はそれほど複雑ではなく、開発を容易にするために多くのライブラリ関数が提供されているので、C言語でそれらのライブラリ関数を使いこなすだけで済みます。特に、一部の関数は数値ではなくポインタを返します; 一部の関数は使用が特殊であることに注意してくださいstrtok
。
-------------------------------------------------- - - - - - - - - - - - 終わり - - - - - - - - - - - - - - ----------------------------------------------