C言語/配列
配列の基本概念
[編集]配列とは何か
[編集]配列は、同じデータ型の複数の値を1つの変数名で扱うことができるデータ構造です。配列の各要素は番号付けされており、この番号をインデックスと呼びます。配列の各要素にはインデックスを使ってアクセスできます。
メモリ上の配置
[編集]配列の要素は、メモリ上に連続して配置されています。つまり、1つの配列の各要素は、隣接する場所に格納されています。このため、配列の先頭アドレスと要素のサイズがわかれば、任意の要素のアドレスを計算できます。
配列と変数の違い
[編集]変数は1つの値を格納するための記憶領域ですが、配列は複数の値を格納する記憶領域です。また、配列の要素は番号付けされているため、インデックスを使ってアクセスできます。
配列の宣言と初期化
[編集]配列の宣言
[編集]配列を宣言するには、データ型、配列名、配列のサイズを指定する必要があります。
データ型 配列名[サイズ];
例えば、要素数が5のint
型の配列を宣言するには、以下のようにします。
int ary[5];
配列の初期化
[編集]配列を初期化するには、要素に値を割り当てます。初期化子リストを使って、配列の宣言と同時に初期化することもできます。
データ型 配列名[] = {値1, 値2, 値3, ...};
例えば、要素数が5のint
型の配列を初期化するには、以下のようにします。
int ary[] = {10, 20, 30, 40, 50};
配列の操作
[編集]配列要素へのアクセス
[編集]配列の要素にアクセスするには、配列名とインデックスを使います。インデックスは0から始まります。
配列名 [ インデックス ]
例えば、arr
配列の3番目の要素にアクセスするには、以下のようにします。
ary[2] = 100; // ary[2]の値を100に設定
ループを使った配列操作
[編集]配列の要素を処理するには、ループを使うのが一般的です。for
ループは配列操作に便利です。
for (int i = 0; i < sizeof 配列名 / sizeof *配列名; i++) { // 配列名[i]を使って各要素にアクセス }
配列の使用例
[編集]基本的な使用例
[編集]配列は様々な用途で使用できます。代表的な使用例を以下に示します。
- 複数の値を1つの変数で扱う
- 関数の引数として複数の値を渡す
- 関数の戻り値として複数の値を返す
- データ構造の実装
多次元配列
[編集]二次元配列の概念
[編集]二次元配列は、配列の配列です。つまり、各要素が配列になっている配列のことです。二次元配列は、行と列のインデックスを使ってアクセスします。
二次元配列の宣言と初期化
[編集]二次元配列を宣言するには、行と列のサイズを指定します。
データ型 配列名[行サイズ][列サイズ];
例えば、3行4列のint
型の二次元配列を宣言するには、以下のようにします。
int ary[3][4];
初期化子リストを使って、二次元配列を初期化することもできます。
int ary[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} };
二次元配列の操作
[編集]二次元配列の要素にアクセスするには、行と列のインデックスを使います。
ary[行インデックス][列インデックス]
例えば、arr
配列の2行3列目の要素にアクセスするには、以下のようにします。
ary[1][2] = 100; // ary[1][2]の値を100に設定
配列とポインタ
[編集]配列名とポインタの関係
[編集]配列名は、実際には配列の先頭要素のアドレスを指すポインタ定数です。したがって、配列名と配列の先頭要素のアドレスは同じ値を持ちます。
ポインタを使った配列操作
[編集]ポインタを使うと、配列の要素にアクセスできます。ポインタ演算子を使って、ポインタを配列の要素に合わせることができます。
*(arr + i) // ary[i]と同じ
文字列と文字配列
[編集]文字列の概念
[編集]C言語には文字列型は存在しませんが、null
終端文字(\0
)で終わる文字配列を文字列として扱うことができます。
文字配列の操作
[編集]文字配列は通常の配列と同様に操作できますが、文字列関数を使うと便利です。string.h
ヘッダファイルに定義されている関数を使って、文字列の操作ができます。
配列の制約と注意点
[編集]メモリ管理
[編集]配列のサイズは、コンパイル時に決まります。つまり、実行時に配列のサイズを変更することはできません。動的にサイズを変更したい場合は、動的メモリ割り当てを使う必要があります。
可変長配列
[編集]VLA(Variable Length Array)は、C99標準で導入された可変長配列で、配列のサイズを実行時に決定できるという点で、従来の配列とは異なります。
VLAについて補足すると、以下のような特徴があります。
- 関数の中でのみ使用可能
- 配列のサイズを実行時に決定できる
- 自動変数として割り当てられ、関数終了時に自動的に解放される
- 可読性が高く、動的メモリ割り当てに比べてコードが簡潔になる
例えば、以下のようにVLAを使うことができます。
#include <stdbool.h> #include <stdio.h> // エラトステネスのふるいを実行して、n未満の素数を表示する関数 void sieve_of_eratosthenes(int n) { if (n < 2) { printf("2未満の素数はありません。\n"); return; } // VLAを使用して配列を宣言 bool is_prime[n]; for (int i = 0; i < n; i++) { is_prime[i] = true; } // エラトステネスのふるいアルゴリズム for (int p = 2; p * p < n; p++) { // is_prime[p]がtrueの場合は素数 if (is_prime[p]) { // pの倍数を素数ではないとマーク for (int i = p * p; i < n; i += p) { is_prime[i] = false; } } } // 素数を表示 printf("n未満の素数: "); for (int p = 2; p < n; p++) { if (is_prime[p]) { printf("%d ", p); } } printf("\n"); } int main() { int n; printf("nの値を入力してください: "); scanf("%d", &n); sieve_of_eratosthenes(n); return 0; }
このように、VLAを使えば実行時に配列のサイズを決めることができるので、より動的なプログラミングが可能になります。ただし、VLAにもいくつかの制限があり、関数の中でのみ使用可能で、大きなサイズを指定するとスタックオーバーフローの危険があることに注意が必要です。
境界チェックの重要性
[編集]配列のインデックスが範囲外になると、プログラムの動作は未定義になります。したがって、配列操作では必ず境界チェックを行うことが重要です。
章末問題
[編集]理解度チェック
[編集]- 配列とは何か、その特徴を説明しなさい。
- 以下の配列宣言と初期化は正しいか。間違っている場合は理由を説明しなさい。
int ary[5] = {1, 2, 3, 4, 5, 6};
- 以下のコードの出力は何か。
int ary[] = {10, 20, 30, 40, 50}; for (int i = 0; i < 5; i++) { printf("%d ", *(arr + i)); }
- 3行4列の二次元配列を宣言し、初期化するコードを書きなさい。
- 文字列
"Hello, World!"
を格納する文字配列を宣言し、初期化するコードを書きなさい。