文字列関数

文字列関数は工具箱のレンチやドライバーのようなものです——各ツールが特定の問題を解決します。コピー、連結、比較、検索。マスターすれば、テキスト処理に自信が持てます。

string.hの概要

<string.h>はC標準ライブラリの文字列操作の中核となるヘッダファイルで、コピー、連結、比較、検索の関数を提供します。使用前にインクルードが必要です。

C
#include <string.h>

strcpyとstrncpy

strcpy

strcpyは元の文字列を宛先配列にコピーします。\0も含みます。

C
char dest[20];
strcpy(dest, "Hello World");
printf("%s\n", dest);

プロトタイプ:char *strcpy(char *dest, const char *src);

⚠️ 注意: strcpyは宛先バッファのサイズをチェックしません。元の文字列が宛先配列より大きいと境界外書き込みになります。使用前に宛先配列が十分大きいことを確認してください。

strncpy

strncpyはコピーする最大バイト数を制限し、より安全です。

C
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は元の文字列を宛先文字列の末尾に追加します。

C
char buf[50] = "Hello";
strcat(buf, " World");
printf("%s\n", buf);

プロトタイプ:char *strcat(char *dest, const char *src);

宛先文字列の末尾の既存の\0は上書きされ、連結結果の後に新しく\0が置かれます。

⚠️ 注意: 宛先配列は連結結果を収める十分なスペースが必要です。strcatはバッファサイズをチェックしません。

strncat

strncatは追加する最大文字数を制限し、自動的に\0を追加します。

C
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文字ずつ比較します。

C
int result = strcmp("abc", "abd");

戻り値の意味:

C
if (strcmp(s1, s2) == 0) {
    printf("Equal\n");
}
⚠️ 注意: 文字列の比較にはstrcmpを使い、==は絶対に使わないでください。==はポインタのアドレスを比較し、文字列の内容を比較しません。

strncmp

strncmpは最初のn文字だけを比較します。

C
if (strncmp(str, "GET ", 4) == 0) {
    printf("GET request\n");
}

プレフィックスの比較や比較範囲の制限に便利です。

文字列配列を辞書順にソートします。

C
#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;
}
▶ 試してみよう
TEXT
alice
bob
charlie
david
eve

strlen

strlenは文字列の有効長(\0を含まない)を返します。

C
char s[] = "Hello";
printf("%zu\n", strlen(s));
printf("%zu\n", sizeof(s));

出力:5と6。strlen\0まで数え、sizeof\0を含みます。

strlen\0を見つけるまで文字列を走査するため、時間計算量はO(n)です。同じ文字列の長さを複数回使う場合は、結果をキャッシュしてください。

C
size_t len = strlen(s);
for (size_t i = 0; i < len; i++) {
}

ループ条件の中で毎回strlenを呼び出すのではなく、このように一度取得した値を使い回します。

strchrとstrrchr

strchrは文字列中の文字の最初の出現を見つけます。

C
const char *p = strchr("Hello World", 'o');
if (p != NULL) {
    printf("Found: %s\n", p);
}

出力:"o World"。その文字へのポインタを返し、見つからなければNULLを返します。

strrchrは文字の最後の出現を見つけます。

C
const char *p = strrchr("Hello World", 'o');
if (p != NULL) {
    printf("Last occurrence: %s\n", p);
}

出力:"orld"。

strstr

strstrは文字列中の部分文字列の最初の出現を見つけます。

C
const char *p = strstr("Hello World", "World");
if (p != NULL) {
    printf("Substring at: %s\n", p);
}

出力:"World"。見つからなければNULLを返します。

すべての出現を見つける:

C
const char *text = "abababab";
const char *p = text;
while ((p = strstr(p, "ab")) != NULL) {
    printf("Position %ld\n", (long)(p - text));
    p++;
}

見つけるたびにポインタを1つ進め、後続の出現を探し続けます。

文字列関数の独自実装

標準関数の内部動作を理解すれば、文字列操作の本質が把握できます。

独自strlen

C
size_t my_strlen(const char *s) {
    size_t len = 0;
    while (s[len] != '\0') {
        len++;
    }
    return len;
}

独自strcpy

C
char *my_strcpy(char *dest, const char *src) {
    char *d = dest;
    while ((*d++ = *src++) != '\0') {
    }
    return dest;
}

この簡潔な形式:*d++ = *src++は文字をコピーしてから両方のポインタを進め、\0がコピーされるまで続けます。

独自strcmp

C
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とテスト:

C
#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;
}
▶ 試してみよう
TEXT
Hello, World!

まずdestの末尾の\0を見つけ、その位置からsrcの内容をコピーします。

❓ よくある質問

Q なぜstrncpyは自動的に\0を追加しないのですか?
A strncpyは元々固定長バッファ(ファイル名フィールドなど)を埋めるために設計されました。nより短い場合は\0で埋めますが、nバイトに達した場合は\0を追加しません。より安全なstrcpyとして使う場合は、手動で\0を追加する必要があります。
Q strcmpの具体的な数値は意味がありますか?
A 正、負、ゼロのいずれかだけを確認すべきで、具体的な値に依存しないでください。実装によって異なる差を返します。移植性のあるコードでは==0<0>0のみを使います。
Q strcatは複数回の連結で非効率ですが、どうすればよいですか?
A strcatは毎回destの先頭から末尾まで走査してから追加するため、複数回の連結では全体の効率がO(n^2)になります。現在の末尾位置を手動で追跡し、そこから直接追加すると効率的です。
Q 文字列関数を自分で実装する意味は何ですか?
A 基本原理の理解、面接のよくある質問、標準ライブラリが利用できない組込み開発などです。ただし実際のプロジェクトでは標準ライブラリ関数を優先してください。十分にテストされ最適化されています。

📖 まとめ

📝 練習問題

  1. 文字列中のすべての大文字を小文字に変換する関数void str_tolower(char *s)を書いてください。
  2. 文字s中に文字chが何回出現するかを数える関数int str_count_char(const char *s, char ch)を書いてください。
  3. string.hの関数を一切使わずに、my_strstrで部分文字列検索を実装してください。
100%