文字列関数
文字列関数は工具箱のレンチやドライバーのようなものです——各ツールが特定の問題を解決します。コピー、連結、比較、検索。マスターすれば、テキスト処理に自信が持てます。
string.hの概要
<string.h>はC標準ライブラリの文字列操作の中核となるヘッダファイルで、コピー、連結、比較、検索の関数を提供します。使用前にインクルードが必要です。
#include <string.h>
strcpyとstrncpy
strcpy
strcpyは元の文字列を宛先配列にコピーします。\0も含みます。
char dest[20];
strcpy(dest, "Hello World");
printf("%s\n", dest);
プロトタイプ:char *strcpy(char *dest, const char *src);
strcpyは宛先バッファのサイズをチェックしません。元の文字列が宛先配列より大きいと境界外書き込みになります。使用前に宛先配列が十分大きいことを確認してください。
strncpy
strncpyはコピーする最大バイト数を制限し、より安全です。
char dest[6];
strncpy(dest, "Hello World", sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';
printf("%s\n", dest);
プロトタイプ:char *strncpy(char *dest, const char *src, size_t n);
strncpyは最大nバイトをコピーします。元の文字列がnより短い場合、残りの位置は\0で埋められます。元の文字列がnバイト以上の場合、自動的には\0を追加しません。したがって手動で追加する必要があります。
strncpyを使う際は常に\0のためにスペースを確保し、コピー後に手動で\0を追加してください。
strcatとstrncat
strcat
strcatは元の文字列を宛先文字列の末尾に追加します。
char buf[50] = "Hello";
strcat(buf, " World");
printf("%s\n", buf);
プロトタイプ:char *strcat(char *dest, const char *src);
宛先文字列の末尾の既存の\0は上書きされ、連結結果の後に新しく\0が置かれます。
strcatはバッファサイズをチェックしません。
strncat
strncatは追加する最大文字数を制限し、自動的に\0を追加します。
char buf[10] = "Hi";
strncat(buf, " World!", sizeof(buf) - strlen(buf) - 1);
printf("%s\n", buf);
プロトタイプ:char *strncat(char *dest, const char *src, size_t n);
最大n文字を追加し、その後自動的に\0を追加します。null終端を保証するため、strncpyより安全です。
strcmpとstrncmp
strcmp
strcmpは2つの文字列を辞書順で1文字ずつ比較します。
int result = strcmp("abc", "abd");
戻り値の意味:
< 0:s1がs2より小さい= 0:s1とs2は等しい> 0:s1がs2より大きい
if (strcmp(s1, s2) == 0) {
printf("Equal\n");
}
strcmpを使い、==は絶対に使わないでください。==はポインタのアドレスを比較し、文字列の内容を比較しません。
strncmp
strncmpは最初のn文字だけを比較します。
if (strncmp(str, "GET ", 4) == 0) {
printf("GET request\n");
}
プレフィックスの比較や比較範囲の制限に便利です。
例
文字列配列を辞書順にソートします。
#include <stdio.h>
#include <string.h>
void sort_strings(char arr[][32], int n) {
int i, j;
char temp[32];
for (i = 0; i < n - 1; i++) {
for (j = 0; j < n - 1 - i; j++) {
if (strcmp(arr[j], arr[j + 1]) > 0) {
strcpy(temp, arr[j]);
strcpy(arr[j], arr[j + 1]);
strcpy(arr[j + 1], temp);
}
}
}
}
int main(void) {
char names[5][32] = {
"david",
"alice",
"charlie",
"bob",
"eve"
};
int i;
sort_strings(names, 5);
for (i = 0; i < 5; i++) {
printf("%s\n", names[i]);
}
return 0;
}
alice
bob
charlie
david
eve
strlen
strlenは文字列の有効長(\0を含まない)を返します。
char s[] = "Hello";
printf("%zu\n", strlen(s));
printf("%zu\n", sizeof(s));
出力:5と6。strlenは\0まで数え、sizeofは\0を含みます。
strlenは\0を見つけるまで文字列を走査するため、時間計算量はO(n)です。同じ文字列の長さを複数回使う場合は、結果をキャッシュしてください。
size_t len = strlen(s);
for (size_t i = 0; i < len; i++) {
}
ループ条件の中で毎回strlenを呼び出すのではなく、このように一度取得した値を使い回します。
strchrとstrrchr
strchrは文字列中の文字の最初の出現を見つけます。
const char *p = strchr("Hello World", 'o');
if (p != NULL) {
printf("Found: %s\n", p);
}
出力:"o World"。その文字へのポインタを返し、見つからなければNULLを返します。
strrchrは文字の最後の出現を見つけます。
const char *p = strrchr("Hello World", 'o');
if (p != NULL) {
printf("Last occurrence: %s\n", p);
}
出力:"orld"。
strstr
strstrは文字列中の部分文字列の最初の出現を見つけます。
const char *p = strstr("Hello World", "World");
if (p != NULL) {
printf("Substring at: %s\n", p);
}
出力:"World"。見つからなければNULLを返します。
すべての出現を見つける:
const char *text = "abababab";
const char *p = text;
while ((p = strstr(p, "ab")) != NULL) {
printf("Position %ld\n", (long)(p - text));
p++;
}
見つけるたびにポインタを1つ進め、後続の出現を探し続けます。
文字列関数の独自実装
標準関数の内部動作を理解すれば、文字列操作の本質が把握できます。
独自strlen
size_t my_strlen(const char *s) {
size_t len = 0;
while (s[len] != '\0') {
len++;
}
return len;
}
独自strcpy
char *my_strcpy(char *dest, const char *src) {
char *d = dest;
while ((*d++ = *src++) != '\0') {
}
return dest;
}
この簡潔な形式:*d++ = *src++は文字をコピーしてから両方のポインタを進め、\0がコピーされるまで続けます。
独自strcmp
int my_strcmp(const char *s1, const char *s2) {
while (*s1 == *s2) {
if (*s1 == '\0') return 0;
s1++;
s2++;
}
return (unsigned char)*s1 - (unsigned char)*s2;
}
1文字ずつ比較し、等しければ続け、差異や\0が見つかれば停止します。差を返して順序を示します。unsigned charへのキャストは、負の文字値が比較結果に影響しないようにします。
例
独自strcatとテスト:
#include <stdio.h>
char *my_strcat(char *dest, const char *src) {
char *d = dest;
while (*d != '\0') {
d++;
}
while ((*d++ = *src++) != '\0') {
}
return dest;
}
int main(void) {
char buf[50] = "Hello";
my_strcat(buf, ", ");
my_strcat(buf, "World!");
printf("%s\n", buf);
return 0;
}
Hello, World!
まずdestの末尾の\0を見つけ、その位置からsrcの内容をコピーします。
❓ よくある質問
\0を追加しないのですか?strncpyは元々固定長バッファ(ファイル名フィールドなど)を埋めるために設計されました。nより短い場合は\0で埋めますが、nバイトに達した場合は\0を追加しません。より安全なstrcpyとして使う場合は、手動で\0を追加する必要があります。strcmpの具体的な数値は意味がありますか?==0、<0、>0のみを使います。strcatは複数回の連結で非効率ですが、どうすればよいですか?strcatは毎回destの先頭から末尾まで走査してから追加するため、複数回の連結では全体の効率がO(n^2)になります。現在の末尾位置を手動で追跡し、そこから直接追加すると効率的です。📖 まとめ
strcpy/strncpyは文字列をコピーします。strncpyはより安全ですが手動での\0終端が必要ですstrcat/strncatは文字列を連結します。strncatは自動的に\0を追加しますstrcmp/strncmpで文字列を比較します。文字列の内容の比較に==は絶対に使わないでくださいstrchr/strrchrは文字を検索し、strstrは部分文字列を検索します- 独自実装は基本原理の理解に役立ちます。実際のプロジェクトでは標準ライブラリを使いましょう
📝 練習問題
- 文字列中のすべての大文字を小文字に変換する関数
void str_tolower(char *s)を書いてください。 - 文字
s中に文字chが何回出現するかを数える関数int str_count_char(const char *s, char ch)を書いてください。 - string.hの関数を一切使わずに、
my_strstrで部分文字列検索を実装してください。



