ユーニックス総合研究所

  • home
  • archives
  • c-string-array

C言語の文字列の配列の使い方

  • 作成日: 2021-12-01
  • 更新日: 2024-03-13
  • カテゴリ: C言語

C言語の文字列の配列の使い方

C言語では文字列を扱うことができます。
そしてC言語ではこの文字列を配列として扱うことができます
この記事ではC言語の文字列の配列について具体的に解説します

文字列の配列はC言語のプログラミングで色々なシーンで使われます。
複数の文字列を扱いたいという需要はプログラミングで多く、C言語の文字列の配列もこの需要を満たすものです。
C言語の文字列の配列には「文字列定数の配列」、「文字配列の配列」、「動的な文字配列の配列」、「静的な文字列の配列」の4種類があり、これらの使い方を押させておくとはかどります。

今回は具体的に↓のコンテンツを解説していきます。

  • 文字列の配列とは?
  • 文字列の配列の使いどころ
  • 文字列の配列のハマりどころ
  • 文字列の配列の種類
  • 文字列定数のポインタ配列の定義方法
  • 文字配列の配列の定義方法
  • 動的な文字配列の配列の定義方法
  • 文字列の配列をインデックスで参照する
  • 文字列の配列に番兵を持たせる
  • 文字列の配列をfor文で参照する

C言語や他の言語を扱うYoutubeも公開しています。
興味がある方は以下のリンクからご覧ください。

Youtubeの当チャンネル

関連記事

目が覚めるC言語のdo-while文の使い方【ループ処理、初心者向け】
状態遷移による文字列パースのテクニック【Python】
明快!C言語のcontinue文の使い方
文字列の描画 - Rustで作るWindowsアプリ
改行付きの文字列を描画する - Rustで作るWindowsアプリ

文字列の配列とは?

文字列の配列とは、文字列が要素となっている配列のことを言います
配列の要素を参照すると文字列を参照できる。
これが文字列の配列です。

文字列の配列は普通の配列と比べて定義方法が異なっています。
そのため文字列の配列の定義方法を知っておくのはC言語のスキル的に有用なことです。
また、一度定義方法を覚えればスラスラと書けるようにもなるので、この機会に覚えてみてください。

C言語の配列とは?その美しさを解説します

文字列の配列の使いどころ

文字列の配列の使いどころはどこでしょうか?
文字列の配列はこんな時に使われます。

  • 文字列を複数使いたいとき

文字列を複数使いたい時に使う

たとえば商品のブランドを文字列として定義しておきたい場合があります。
そのブランド名はプログラム中で参照されます。
そういう時はブランドの一覧としてブランド名の文字列の配列を定義しておけば、便利に使えます。

文字列の配列にしておけば、配列の添え字でブランド名にアクセスできます
それからfor文で回すことだってできます

文字列の配列としてブランド名を定義しておけば、ブランド名が変更されたときはその文字列の配列の定義を変更するだけで対応できます
他の部分では添え字を使ってブランド名を参照しているので、それらの部分の処理でブランド名の変更に対応する必要はありません。

また動的な文字列の配列にすればソートしてブランド名を整列させることもできます。
このように文字列の配列は文字列を複数使いたい時に有用です。

文字列の配列のハマりどころ

文字列の配列のハマりどころはどこでしょうか?
文字列の配列でバグりやすいところは?
それは↓になります。

  • 文字列定数と文字配列で扱いが異なる

文字列定数と文字配列で扱いが異なる

C言語の文字列には大きく分けて2つの種類があります。
それは「文字列定数」と「文字配列」です。

このうち文字列定数は変更不可能な文字列です。
文字配列は変更可能な文字列です。

C言語の文字列の配列にはこれらの文字列の配列を定義することができます。
しかし文字列定数と配列と文字配列の配列とでは扱いが異なるので注意が必要です。

文字列の配列の種類

C言語の文字列の配列の種類はどんなものがあるのでしょうか?
それぞれの文字列の配列の違いは?
文字列の配列の種類は↓の4種類になります。

  • 文字列定数のポインタ配列
  • 文字配列の配列
  • 動的な文字配列の配列
  • 静的な文字列の配列

文字列定数のポインタ配列

文字列定数のポインタ配列とは、文字列の配列の一種です。
この配列は文字列定数を格納している配列です。

文字列定数は処理系によりますがテキスト・セグメントというメモリ領域に保存されることが多い文字列で、この領域の文字列は変更することができません
そのため文字列定数のポインタ配列は基本的に値を変更すると何が起こるかわからないため変更することができません。
つまり定数としての特徴を持っています。

文字列定数のポインタ配列は定数として扱える文字列の配列です。

C言語の配列とポインタについて

文字配列の配列

文字配列の配列とは、データが変更可能な配列のことです。
この配列の文字列は、要素数が変更可能なメモリ領域に確保されています。
そのため定義後に値を変更することが可能です。

文字列定数の配列と比べて、文字配列の配列はデータの変更が効くのでその分ゆうずうが利く配列と言えます。
またプログラムの中で動的に文字列の値を変更する処理がある場合はこの文字配列の配列を使うことになるでしょう。

動的な文字配列の配列

動的な文字配列の配列とは、動的なメモリ確保で確保された文字配列の配列のことを言います。
この配列は配列の要素数が動的に決定されます。
また、メモリが動的に確保されているのでポインタ変数として持ち運びが可能なのも特徴です。

動的メモリの確保を行うため、普通の配列と比べてコードは複雑になりますが、その分拡張性がある優れた配列です。
例えば配列の要素を動的に変更することも出来ます。
ただしメモリの解放漏れがある場合はメモリリークなどのバグになるため、一長一短と言えます。

大規模開発ではこの動的な文字配列の配列はモジュール化して使うことが多いです。
開発が大きくなりそうだったら専用のモジュールを定義して使いましょう。

関連記事
C言語の動的配列のリサイズ方法
C言語の静的なメモリと動的なメモリ

静的な文字列の配列

静的な文字列の配列とは、↓の配列を静的に定義する配列のことです。

  • 文字列定数のポインタ配列
  • 文字配列の配列

staticと言うキーワードでこれらの配列を定義することで、これらの配列は静的なメモリ領域に確保されます。
具体的にはデータセグメントと呼ばれるメモリ領域に確保されます。

静的な定義の配列は、プログラムが開始して終了するまでメモリにアクセスすることができるので、寿命が長い配列です。
しかし、スレッドセーフの問題などもあるためこれも一長一短な配列と言えます。

関連記事
C言語の静的なメモリと動的なメモリ

文字列定数のポインタ配列の定義方法

文字列定数のポインタ配列の定義方法です。
↓のように定義します。

#include <stdio.h>  

int main(void) {  
    const char *ary[3] = {  
        "Hello",  
        "Good",  
        "World",  
    };  

    printf("%s\n", ary[0]);  // Hello  
    printf("%s\n", ary[1]);  // Good  
    printf("%s\n", ary[2]);  // World  

    return 0;  
}  

↑の場合、aryというのが配列です。
この配列はconst char型のポインタを持つ配列です。
const char型のポインタとはつまり文字列定数のことです。

↑のようにary[3]とやって配列の要素数を指定します。
この配列は3要素を持つ配列です。
そしてイコール(=)の後にブレースを書いて、文字列を代入します。
文字列は「Hello」「Good」「World」の3つです。
それぞれ「Hello」は配列の0番目の要素に、「Good」は配列の1番目の要素に、「World」は配列の2番目の要素に保存されます。

保存されているのは文字列定数のアドレスであることに注意してください。
文字列の実体が保存されているわけではありません。

文字列定数の配列の場合、配列の要素数は省略することができます。

#include <stdio.h>  

int main(void) {  
    const char *ary[] = {  
        "Hello",  
        "Good",  
        "World",  
    };  

    printf("%s\n", ary[0]);  // Hello  
    printf("%s\n", ary[1]);  // Good  
    printf("%s\n", ary[2]);  // World  

    return 0;  
}  

配列の要素数を省略した場合は代入する文字列の数によって動的に要素数が決定されます。
こちらのほうが使い勝手が良いかと思います。

文字配列の配列の定義方法

文字配列の配列は↓のように定義します。

#include <stdio.h>  

int main(void) {  
    // 要素数が20の文字配列を2要素分確保する  
    char ary[2][20] = {  
        "Hello",  
        "World",  
    };  

    puts(ary[0]);  // Hello  
    puts(ary[1]);  // World  

    return 0;  
}  

↑の場合、aryが文字配列の配列です。
このaryは要素数が20ある文字配列を2つ分要素として確保しています。
ary[2]2が配列の要素数で、ary[2][20]20が文字配列の要素数です。

このように定義した配列の文字配列は変更することができます。
この辺が文字列定数の配列と違う点です。

また配列の要素数は省略することができます。

#include <stdio.h>  

int main(void) {  
    // 要素数が20の文字配列をn要素分確保する  
    char ary[][20] = {  
        "Hello",  
        "World",  
    };  

    puts(ary[0]);  // Hello  
    puts(ary[1]);  // World  

    return 0;  
}  

↑の場合、配列はaryですが、要素数を省略しています。
この場合も配列の要素数は定義によって動的に決定されます。

動的な文字配列の配列の定義方法

動的な文字配列の配列は↓のように定義します。

#include <stdio.h>  
#include <stdlib.h>  

int main(void) {  
    // 配列を動的に確保する  
    size_t capa = 3;  // 配列の要素数  
    char **ary = calloc(capa, sizeof(char *));  

    // 配列の各要素を動的に確保する  
    for (size_t i = 0; i < capa; i += 1) {  
        size_t elem_capa = 20;  // 文字配列の要素数  
        ary[i] = calloc(elem_capa, sizeof(char));  
        snprintf(ary[i], elem_capa, "Hello %d", i);  
    }  

    // 出力と解放  
    for (size_t i = 0; i < capa; i += 1) {  
        printf("%s\n", ary[i]);  // 出力  
        // Hello 0  
        // Hello 1  
        // Hello 2  
        free(ary[i]);  // 要素のメモリの解放  
    }  
    free(ary);  // 配列のメモリの解放  

    return 0;  
}  

動的な配列のメモリ確保は↑のように多少めんどうです。
最初にcapaという変数に配列の要素数を入れておきます。
capacapacity(容量)のcapaです。

そして次にcalloc()関数で配列のメモリを確保します。
calloc()は第1引数に要素数、第2引数に要素のバイト数を指定します。
char **aryというポインタのポインタに確保したメモリを代入します。
calloc()の第2引数にはポインタのサイズを指定しておきます。

次に配列の要素を動的に確保します。
for文でcapaの数だけループを回します。
そしてelem_capaのサイズのメモリをcalloc()で確保して、配列の要素に代入します。
それとあわせてsnprintf()で要素の値を初期化しておきます。

次にfor文を回して要素の値を出力します。
それと同時に配列の要素のメモリをfree()で解放します。
最後にfree()ary自体を解放します。

これでメモリの確保と解放は完了です。
今回は手抜きで要素の値の出力とメモリの解放を同じfor文でやってますが、これは別々にしたほうがいいでしょう。

配列の伸縮方法

動的な配列の伸縮方法です。
↓のように行います。

#include <stdio.h>  
#include <stdlib.h>  

int main(void) {  
    // 配列を動的に確保する  
    size_t capa = 3;  // 配列の要素数  
    char **ary = calloc(capa, sizeof(char *));  

    // 配列の各要素を動的に確保する  
    for (size_t i = 0; i < capa; i += 1) {  
        size_t elem_capa = 20;  // 文字配列の要素数  
        ary[i] = calloc(elem_capa, sizeof(char));  
        snprintf(ary[i], elem_capa, "Hello %d", i);  
    }  

    // 配列の要素数を増やす(伸縮)  
    size_t new_capa = capa + 2;  // 新しい配列の要素数  
    size_t byte = sizeof(char *);  // 要素1つのバイト数  
    size_t size = new_capa * byte;  // 全体のバイト数  
    ary = realloc(ary, size);  // メモリを伸縮する  

    for (size_t i = capa; i < new_capa; i += 1) {  
        size_t elem_capa = 20;  
        ary[i] = calloc(elem_capa, sizeof(char));  
        snprintf(ary[i], elem_capa, "Hello %d", i);  
    }  

    capa = new_capa;  // capaを伸縮後のnew_capaで更新  

    // 出力と解放  
    for (size_t i = 0; i < capa; i += 1) {  
        printf("%s\n", ary[i]);  // 出力  
        // Hello 0  
        // Hello 1  
        // Hello 2  
        free(ary[i]);  // 要素のメモリの解放  
    }  
    free(ary);  // 配列のメモリの解放  

    return 0;  
}  

動的な配列の伸縮にはrealloc()関数を使います。
realloc()は第1引数に既存メモリのポインタ、第2引数に再確保するバイト数を指定します。
capaの値から新しくnew_capaという伸縮後の配列の要素数を表す変数を作ります。
そして要素のバイト数ととこのnew_capaをかけて全体のバイト数を算出します。
そしてrealloc()でメモリを再確保して、ポインタaryを更新します。

ちなみに今回はエラー処理は省略しています。
ちなみにrealloc()は再確保したメモリ領域を0クリアしません

for文でcapaからnew_capaまで回して要素のメモリを確保して初期化します。
これで伸縮後のメモリの初期化が完了しました。

あとはいつも通り出力と解放を行います。

関連記事
C言語の動的配列のリサイズ方法

静的な文字列の定義方法

静的な文字列の配列を定義するには文字列定数の配列や文字配列の配列にstaticをつけるだけです。

#include <stdio.h>  

int main(void) {  
    // 静的な文字列定数の配列  
    static const char *ary1[] = {  
        "Hello",  
        "World",  
    };  

    // 静的な文字配列の配列  
    static char ary2[][20] = {  
        "Hello",  
        "World",  
    };  

    puts(ary1[0]);  // Hello  
    puts(ary1[1]);  // World  

    puts(ary2[0]);  // Hello  
    puts(ary2[1]);  // World  

    return 0;  
}  

↑のように定義された配列はプログラムが起動して終了するまでメモリが有効になります。
そのため関数の中で定義された静的な配列は、関数が終了してもそのメモリが変更されたままになります。

文字列の配列をインデックスで参照する

文字列の配列の各要素はインデックス(添え字)で参照することができます
↓のようにary[0]ary[1]で配列の要素にアクセスします。

#include <stdio.h>  

int main(void) {  
    // 要素数が20の文字配列を3要素分確保する  
    char ary[][20] = {  
        "Hello",  
        "Good",  
        "World",  
    };  

    puts(ary[0]);  // Hello  
    puts(ary[1]);  // Good  
    puts(ary[2]);  // World  

    return 0;  
}  

範囲外のアクセスはセグフォになったりしてプログラムがクラッシュする場合がありますので注意が必要です
たとえば↑の配列aryの場合、要素数は3つです。
そのためary[10]のように範囲外のアクセスをするとセグフォになる場合があります。
ただし環境によってはならない場合もあるので注意が必要です。

🦝 < C言語の闇

文字列の配列に番兵を持たせる

文字列定数のポインタ配列や動的な文字配列の配列にはNULLポインタの番兵を持たせることができます。

#include <stdio.h>  

int main(void) {  
    // 文字列定数を3要素分確保する  
    const char *ary[] = {  
        "Hello",  
        "World",  
        NULL,  
    };  

    printf("%s\n", ary[0]);  // Hello  
    printf("%s\n", ary[1]);  // World  
    printf("%p\n", ary[2]);  // (nil)  

    return 0;  
}  

↑のように配列の末尾に保存されるNULLポインタが番兵です。
番兵とは配列の中に保存するしんがりみたいなものです。
for文などで配列を回す時にこの番兵があるとカウント変数を使わなくて済みます。

文字列定数の配列を定義するときはこのNULLポインタ番兵を入れておくクセを付けておくと良いです。
役には立っても害になることはほとんどありません。

関連記事
C言語の終端を表すEOF, NULL, ナル文字について

文字列の配列をfor文で参照する

文字列の配列をfor文で参照するにはどうしたらいいのでしょうか?
for文で文字列の配列を回すには?
具体的に↓の2つについて見ていきたいと思います。

  • 文字列定数のポインタ配列をfor文で参照する
  • 文字配列の配列をfor文で参照する

関連記事
C言語のfor文の書き方【繰り返し文】

文字列定数のポインタ配列をfor文で参照する

文字列定数のポインタ配列をfor文で参照する場合です。
NULLポインタ番兵を使う場合は↓のようにコードを書くことが出来ます。

#include <stdio.h>  

int main(void) {  
    // 要素数が20の文字配列を3要素分確保する  
    const char *ary[] = {  
        "Hello",  
        "World",  
        NULL,  
    };  

    for (const char **p = ary; *p; p += 1) {  
        printf("%s\n", *p);  
        // Hello  
        // World  
    }  

    return 0;  
}  

↑の場合、aryは文字列定数の配列で、配列の末尾にはNULLポインタが保存されています。
これを↑のようにfor文で回すと、カウント変数を使わなくても配列の要素を参照することができます
これはNULLポインタ番兵がループの終了条件で役に立ってくれるからです。

番兵を使ったループの実行はC言語では非常によく行われます。
そのためこの使い方をマスターしておくのは得策と言えます。

🦝 < C言語特有やね

文字配列の配列をfor文で参照する

文字配列の配列をfor文で参照するには↓のようにコードを書きます。

#include <stdio.h>  

#define CAPA 3  

int main(void) {  
    // 要素数が20の文字配列を3要素分確保する  
    char ary[CAPA][20] = {  
        "Hello",  
        "Good",  
        "World",  
    };  

    for (size_t i = 0; i < CAPA; i += 1) {  
        printf("%s\n", ary[i]);  
        // Hello  
        // Good  
        // World  
    }  

    return 0;  
}  

↑のようにfor文で配列の要素数だけ回します。
そしてカウント変数で配列を参照するとその配列の要素を参照できます。

配列の要素数はマクロでCAPAと定義してあります。
このCAPAを配列の要素数の定義に使うことができます。

おわりに

今回はC言語の文字列の配列について解説しました。
文字列の配列はC言語では比較的に良く使われるデータ構造です。
文字列の配列の使い方をマスターしておけばきっと役に立つはずです。
この記事があなたの何かの参考になれば幸いです。

🦝 < 文字列の配列をぶん回そう

🐭 < ぶんぶん回る文字列