サーチ…


前書き

配列は、別の型の順序付けられた値(「要素」)を表す派生データ型です。 Cの大部分の配列は、いずれかの型の固定数の要素を持ち、その表現は、要素をギャップまたはパディングなしでメモリに連続して格納します。 Cは、要素が他の配列である多次元配列、およびポインタの配列を許します。

Cは、実行時にサイズが決定される動的に割り当てられた配列をサポートします。 C99以降では、可変長配列またはVLAがサポートされています。

構文

  • タイプ名[長さ]; / *名前 'name'と長さ 'length'の 'type'の配列を定義します。 * /
  • int arr [10] = {0}; / *配列を定義し、すべての要素を0に初期化する* /
  • int arr [10] = {42}; / *配列を定義し、最初の要素を42に初期化し、残りを0に初期化します。* /
  • int arr [] = {4,2,3,1}; / *長さ4の配列を定義して初期化する* /
  • arr [n] = value; / *インデックスnの値を設定します。 * /
  • 値= arr [n]; / *インデックスnの値を取得します。 * /

備考

なぜ配列が必要なのでしょうか?

配列は、独自の意義を持ってオブジェクトを集合体に編成する方法を提供します。たとえば、C文字列は文字の配列( char s)で、 "Hello、World!"などの文字列です。キャラクターに個々に固有のものではない集合体としての意味を持ちます。同様に、配列は数学的なベクトルや行列、そして多くの種類のリストを表すためによく使われます。さらに、要素をグループ化する何らかの方法がなくても、別々の変数を介してなど、個々に個別に対処する必要があります。それは扱いにくいだけでなく、さまざまな長さのコレクションを容易に受け入れることができません。

配列は、ほとんどのコンテキストで暗黙的にポインタに変換されます

sizeof演算子、 _Alignof演算子(C2011)、単項演算子( & _Alignof of)、または(他の)配列の初期化に使用される文字列リテラルのオペランドとして表示される場合を除いて、配列は暗黙的に(最初の要素へのポインタ。この暗黙の変換は、配列添字演算子( [] )の定義に密接に関連しています。式arr[idx]*(arr + idx)と等価であると定義されています。さらに、ポインタ演算は可換性であるため、 *(arr + idx)*(idx + arr)と等価であり、 idx[arr]と等価です。 idxまたはarrいずれかがポインタ(またはポインタに崩壊する配列)であり、もう一方が整数であり、整数が配列への有効なインデックスである場合、それらの式はすべて有効であり、同じ値に評価されますこれにポインタが指す。

特殊なケースとして、 &(arr[0])&*(arr + 0)と等価であることにarr 。これはarr簡単になります。これらの表現はすべて、最後のポインタが消えたところであればどこでも交換可能です。これは、配列が最初の要素へのポインタに崩壊することを単純に再現します。

対照的に、アドレス演算子がT[N]型( すなわち &arr )の配列に適用された場合、結果の型はT (*)[N]あり、配列全体を指します。これは少なくともポインター型のサイズに関して定義されたポインター算術に関して、最初の配列要素へのポインターとは異なります。

関数のパラメータは配列ではありません

void foo(int a[], int n);
void foo(int *a, int n);

fooの最初の宣言では、パラメータaに対して配列のような構文が使用されますが、このような構文は、関数パラメータがそのパラメータを配列の要素型へのポインタとして宣言するために使用されます。したがって、 foo() 2番目の署名は、最初の署名と意味的に同じです。これは、変数と関数のパラメータは同じ配列型で宣言されている場合は、その変数の値として関数呼び出しでの使用に適しているような、彼らは関数呼び出しの引数として現れるポインタへの配列値の減衰に対応します引数に関連付けられた引数。

配列の宣言と初期化

1次元配列を宣言するための一般的な構文は次のとおりです。

type arrName[size];

typeは構造体などの組み込み型またはユーザー定義typeできますarrNameはユーザー定義の識別子で、 sizeは整数定数です。

配列(この場合は10個のint変数の配列)を宣言するには、次のようにします。

int array[10];

現在は不確定な値を保持しています。宣言時にゼロ値を保持するには、次のようにします。

int array[10] = {0};

アレイはまた、初期化子を持つことができ、この例では10のアレイを宣言int 「第3の、 int sは値が含まれています」 123 、他のすべての値はゼロになります。

int array[10] = {1, 2, 3};

上記の初期化の方法では、リストの最初の値は配列の最初のメンバーに割り当てられ、2番目の値は配列の2番目のメンバーに割り当てられます(以下同様)。リストのサイズが配列のサイズよりも小さい場合、上の例のように、配列の残りのメンバーはゼロに初期化されます。指定リストの初期化(ISO C99)により、配列メンバーの明示的な初期化が可能です。例えば、

int array[5] = {[2] = 5, [1] = 2, [4] = 9}; /* array is {0, 2, 5, 0, 9} */

ほとんどの場合、コンパイラは配列の長さを推測できますが、これは大括弧を空白のままにすることで実現できます。

int array[] = {1, 2, 3}; /* an array of 3 int's */
int array[] = {[3] = 8, [0] = 9}; /* size is 4 */

ゼロの長さの配列を宣言することはできません。

C99 C11

C99では可変長アレイ(VLA)を追加し、C11ではオプションとしました。これらは、通常の配列と同じですが、重要な違いが1つあります。コンパイル時に長さを知る必要はありません。 VLAには自動保存期間があります。 VLAへのポインタだけが静的記憶期間を持つことができます。

size_t m = calc_length(); /* calculate array length at runtime */
int vla[m];               /* create array with calculated length */

重要:

VLAは潜在的に危険です。上記の例の配列vlaがスタック上に使用可能な領域より多くの領域を必要とする場合、スタックはオーバーフローします。 VLAの使用はスタイルガイドや本や練習ではしばしば推奨されません。

配列の内容を消去する(ゼロ)

場合によっては、初期化が完了した後に配列をゼロに設定する必要があります。

#include <stdlib.h> /* for EXIT_SUCCESS */

#define ARRLEN (10)

int main(void)
{
  int array[ARRLEN]; /* Allocated but not initialised, as not defined static or global. */

  size_t i;
  for(i = 0; i < ARRLEN; ++i)
  {
    array[i] = 0;
  }

  return EXIT_SUCCESS;
}

上記のループの一般的なmemset()は、 <string.h> memset()を使用することです。以下のようにarrayを渡すと、最初の要素へのポインタへの減衰が行われます。

memset(array, 0, ARRLEN * sizeof (int)); /* Use size explicitly provided type (int here). */

または

memset(array, 0, ARRLEN * sizeof *array); /* Use size of type the pointer is pointing to. */

この例のように、 array 配列の最初の要素へのポインタではなく( 配列の長さが重要である理由を参照)、配列を0にする3番目のオプションが可能です:

 memset(array, 0, sizeof array); /* Use size of the array itself. */

配列の長さ

配列は宣言の範囲内で知られている固定長です。それにもかかわらず、配列の長さを計算することは可能であり、時には便利です。特に、配列の長さがイニシャライザから自動的に決定されるとき、コードをより柔軟にすることができます:

int array[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

/* size of `array` in bytes */
size_t size = sizeof(array);

/* number of elements in `array` */
size_t length = sizeof(array) / sizeof(array[0]); 

しかし、配列が式に現れるほとんどのコンテキストでは、最初の要素へのポインタに自動的に変換されます(「崩壊する」)。配列がsizeof演算子のオペランドである場合は、少数の例外の1つです。結果として生じるポインタはそれ自身が配列ではなく、それが導出された配列の長さに関する情報を持ちません。したがって、ポインターが関数に渡されたときなど、その長さがポインターと一緒に必要な場合は、別々に伝達する必要があります。

たとえば、 int配列の最後の要素を返す関数を記述したいとします。上記から引き続き、次のように呼び出すことができます。

/* array will decay to a pointer, so the length must be passed separately */
int last = get_last(array, length);

関数は次のように実装できます:

int get_last(int input[], size_t length) {
    return input[length - 1];
}

特に、パラメータinputの宣言は配列の宣言に似ていinput、実際にはinputをポインタintとして宣言していますinputint *inputとして宣言するのと全く同じです。次元が与えられても同じことが当てはまります。これは、配列が関数の実際の引数(関数呼び出し式に現れるときにポインタに崩壊する)であることができず、ニーモニックとして見ることができるため可能です。

配列のサイズをポインタから決定しようとするのは非常に一般的なエラーですが、これはうまくいきません。こんなことしないで:

int BAD_get_last(int input[]) {
    /* INCORRECTLY COMPUTES THE LENGTH OF THE ARRAY INTO WHICH input POINTS: */
    size_t length = sizeof(input) / sizeof(input[0]));

    return input[length - 1];  /* Oops -- not the droid we are looking for */
}

実際には、その特定のエラーは非常に一般的であり、一部のコンパイラはそれを認識して警告します。たとえば、 clangは次の警告を発するでしょう:

warning: sizeof on array function parameter will return size of 'int *' instead of 'int []' [-Wsizeof-array-argument]
        int length = sizeof(input) / sizeof(input[0]);
                           ^
note: declared here
int BAD_get_last(int input[])
                     ^

値を配列で設定する

配列の値へのアクセスは、通常、角かっこで行います。

int val;
int array[10];

/* Setting the value of the fifth element to 5: */
array[4] = 5;

/* The above is equal to: */
*(array + 4) = 5;

/* Reading the value of the fifth element: */
val = array[4];

+演算子に対するオペランドの副作用が交換可能( - >可換法)なので、以下は同等です:

*(array + 4) = 5;
*(4 + array) = 5;

同様に、次のステートメントは同等です。

array[4] = 5;
4[array] = 5; /* Weird but valid C ... */

これらの2人も同様です:

val = array[4];
val = 4[array]; /* Weird but valid C ... */

Cは境界チェックを実行せず、宣言された配列の外側の内容にアクセスすることは定義されていません( 割り当てられたチャンクを越えるメモリへのアクセス )。

int val;
int array[10];

array[4] = 5;    /* ok */
val = array[4];  /* ok */
array[19] = 20;  /* undefined behavior */
val = array[15]; /* undefined behavior */

配列とアクセス配列要素を定義する

#include <stdio.h>
 
#define ARRLEN (10)

int main (void) 
{

   int n[ ARRLEN ]; /* n is an array of 10 integers */
   size_t i, j; /* Use size_t to address memory, that is to index arrays, as its guaranteed to 
                   be wide enough to address all of the possible available memory. 
                   Using signed integers to do so should be considered a special use case, 
                   and should be restricted to the uncommon case of being in the need of 
                   negative indexes. */
 
   /* Initialize elements of array n. */         
   for ( i = 0; i < ARRLEN ; i++ ) 
   {
      n[ i ] = i + 100; /* Set element at location i to i + 100. */
   }
   
   /* Output each array element's value. */
   for (j = 0; j < ARRLEN ; j++ ) 
   {
      printf("Element[%zu] = %d\n", j, n[j] );
   }
 
   return 0;
}

ユーザー定義のサイズで配列を割り当て、ゼロ初期化する

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


int main (void)
{
  int * pdata;
  size_t n;

  printf ("Enter the size of the array: ");
  fflush(stdout); /* Make sure the prompt gets printed to buffered stdout. */

  if (1 != scanf("%zu", &n)) /* If zu is not supported (Windows?) use lu. */
  {
    fprintf("scanf() did not read a in proper value.\n");
    exit(EXIT_FAILURE);
  }

  pdata = calloc(n, sizeof *pdata);
  if (NULL == pdata) 
  {
    perror("calloc() failed"); /* Print error. */
    exit(EXIT_FAILURE);
  }

  free(pdata); /* Clean up. */

  return EXIT_SUCCESS;
}

このプログラムは、標準入力から符号なし整数値をスキャンし、 calloc()関数を呼び出してint型のn要素の配列にnメモリブロックをcalloc()ます。メモリは、後者によってすべてゼロに初期化されます。

成功の場合、メモリはfree()呼び出しによって解放されます。

効率的かつ行優先順序で配列を反復する

Cの配列は連続したメモリのチャンクとみなすことができます。より正確には、配列の最後の次元は連続した部分です。これを行優先順位と呼びます。その後のキャッシュの障害を防ぐために、キャッシュされていないデータにアクセスする際に次元10000x10000のアレイにアクセスなぜ、私たちが見ることができるキャッシュにこのキャッシュ障害が完全なキャッシュラインをロードしているという事実を理解するarray[0][0] 潜在的にロードしarray[0][1]キャッシュではなく、アクセスarray[1][0] 、それがあるため、二次キャッシュフォールトを生成した直後sizeof(type)*10000バイトから離れるarray[0][0]従って確かではありません同じキャッシュライン上にある。このように反復するのは、非効率的な理由です。

#define ARRLEN 10000
int array[ARRLEN][ARRLEN];

size_t i, j;
for (i = 0; i < ARRLEN; ++i)
{
    for(j = 0; j < ARRLEN; ++j)
    {
        array[j][i] = 0;
    }
}

このように反復する方が効率的です:

#define ARRLEN 10000
int array[ARRLEN][ARRLEN];

size_t i, j;
for (i = 0; i < ARRLEN; ++i)
{
    for(j = 0; j < ARRLEN; ++j)
    {
        array[i][j] = 0;
    }
}

同じように、これは、1次元と複数のインデックスを持つ配列を扱うとき(インデックスiとjを単純化するためにここでは2次元としましょう)、配列を次のように繰り返し処理することが重要です:

#define DIM_X 10
#define DIM_Y 20

int array[DIM_X*DIM_Y];

size_t i, j;
for (i = 0; i < DIM_X; ++i)
{
    for(j = 0; j < DIM_Y; ++j)
    {
        array[i*DIM_Y+j] = 0;
    }
}

または、3次元とインデックスi、j、およびkを使用して、

#define DIM_X 10
#define DIM_Y 20
#define DIM_Z 30

int array[DIM_X*DIM_Y*DIM_Z];

size_t i, j, k;
for (i = 0; i < DIM_X; ++i)
{
    for(j = 0; j < DIM_Y; ++j)
    {
        for (k = 0; k < DIM_Z; ++k)
        {
            array[i*DIM_Y*DIM_Z+j*DIM_Z+k] = 0;
        }
    }
}

あるいは、より一般的な方法では、 N1 x N2 x ... x Nd要素、 d次元、およびn1、n2、...、ndと表記されるインデックスを持つ配列がある場合、オフセットはのように計算されます

式

写真/数式: https : //en.wikipedia.org/wiki/Row-major_order

多次元配列

C言語では、 多次元配列が可能です。ここには、多次元配列宣言の一般的な形式があります。

type name[size1][size2]...[sizeN];

たとえば、次の宣言は3次元(5 x 10 x 4)整数配列を作成します。

int arr[5][10][4];

2次元配列

多次元配列の最も単純な形式は2次元配列です。 2次元配列は、本質的に、1次元配列のリストである。次元mxnの2次元整数配列を宣言するには、次のように記述できます。

type arrayName[m][n];

typeは任意の有効なCデータ型( intfloatなど)で、 arrayNameは任意の有効なC識別子です。 2次元配列は、 mn列の表として視覚化することができます。 :順序は重要Cにアレイない int a[4][3]配列と同じではないint a[3][4] 。 C は行主言語であるため、 行数が先頭になります。

3行4列の2次元配列a 、次のように示すことができます。

テーブルとしての2D配列のビジュアルレイアウト

したがって、配列aすべての要素は、 a[i][j]という形式の要素名で識別されa[i][j] 。ここでaは配列の名前、 iは行、 jはどの列を表します。行と列のインデックスはゼロであることを思い出してください。これは、添字2次元行列の数学的記法と非常によく似ています。

2次元配列の初期化

多次元配列は、各行に括弧で囲まれた値を指定することによって初期化することができます。次の例では、各行に4つの列がある3行の配列を定義しています。

int a[3][4] = {  
   {0, 1, 2, 3} ,   /*  initializers for row indexed by 0 */
   {4, 5, 6, 7} ,   /*  initializers for row indexed by 1 */
   {8, 9, 10, 11}   /*  initializers for row indexed by 2 */
};

目的の行を示すネストされた中カッコはオプションです。次の初期化は、前の例と同じです。

int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};

ネストされた中カッコを使用して配列を作成する方法はオプションですが、読みやすく分かりやすくするために強く推奨されています。

2次元配列要素へのアクセス

2次元配列内の要素は、添え字、すなわち配列の行インデックスおよび列インデックスを使用してアクセスされます。例えば ​​-

int val = a[2][3];

上記のステートメントは、配列の3番目の行から4番目の要素を取得します。ネストされたループを使って2次元配列を扱った次のプログラムを確認しましょう:

#include <stdio.h>
 
int main () {

   /* an array with 5 rows and 2 columns*/
   int a[5][2] = { {0,0}, {1,2}, {2,4}, {3,6},{4,8}};
   int i, j;
 
   /* output each array element's value */
   for ( i = 0; i < 5; i++ ) {

      for ( j = 0; j < 2; j++ ) {
         printf("a[%d][%d] = %d\n", i,j, a[i][j] );
      }
   }
   
   return 0;
}

上記のコードをコンパイルして実行すると、次の結果が生成されます。

a[0][0]: 0
a[0][1]: 0
a[1][0]: 1
a[1][1]: 2
a[2][0]: 2
a[2][1]: 4
a[3][0]: 3
a[3][1]: 6
a[4][0]: 4
a[4][1]: 8

3次元配列:

3D配列は基本的に配列の配列の配列です。配列または2D配列のコレクションであり、2D配列は1D配列の配列です。

表の集合としての2D配列の視覚的レイアウト

3Dアレイメモリマップ:

メモリ内に連続して配置された3D配列

3D配列の初期化:

double cprogram[3][2][4]={ 
{{-0.1, 0.22, 0.3, 4.3}, {2.3, 4.7, -0.9, 2}},
 {{0.9, 3.6, 4.5, 4}, {1.2, 2.4, 0.22, -1}},
 {{8.2, 3.12, 34.2, 0.1}, {2.1, 3.2, 4.3, -2.0}} 
};

任意の次元数の配列を持つことができますが、作成される配列の大半は1次元または2次元です。

ポインタを使用して配列を反復する

#include <stdio.h>
#define SIZE (10)
int main()
{
    size_t i = 0;
    int *p = NULL;
    int a[SIZE];
    
    /* Setting up the values to be i*i */
    for(i = 0; i < SIZE; ++i) 
    {
        a[i] = i * i;
    }
    
    /* Reading the values using pointers */
    for(p = a; p < a + SIZE; ++p) 
    {
        printf("%d\n", *p);
    }

    return 0;
}

ここで、最初のforループ条件のpの初期化では、配列a 、そのような配列変数が使用されているほぼすべての場所と同様に、最初の要素へのポインタに減衰します。

次いで、 ++pポインタにポインタ演算を行いpおよび配列の要素を1つずつを歩く、そしてそれらを逆参照することによってそれらを指す*p

多次元配列を関数に渡す

多次元配列は、関数に渡すときに、1次元配列と同じ規則に従います。しかし、ディケイ・ツー・ポインター、演算子優先順位、および多次元配列(配列の配列対ポインターの配列)を宣言する2つの異なる方法の組み合わせは、そのような関数の宣言を非直感的にする可能性があります。次の例は、多次元配列を渡す正しい方法を示しています。

#include <assert.h>
#include <stdlib.h>

/* When passing a multidimensional array (i.e. an array of arrays) to a
   function, it decays into a pointer to the first element as usual.  But only
   the top level decays, so what is passed is a pointer to an array of some fixed
   size (4 in this case). */
void f(int x[][4]) {
    assert(sizeof(*x) == sizeof(int) * 4);
}

/* This prototype is equivalent to f(int x[][4]).
   The parentheses around *x are required because [index] has a higher
   precedence than *expr, thus int *x[4] would normally be equivalent to int
   *(x[4]), i.e. an array of 4 pointers to int.  But if it's declared as a
   function parameter, it decays into a pointer and becomes int **x, 
   which is not compatable with x[2][4]. */
void g(int (*x)[4]) {
    assert(sizeof(*x) == sizeof(int) * 4);
}

/* An array of pointers may be passed to this, since it'll decay into a pointer
   to pointer, but an array of arrays may not. */
void h(int **x) {
    assert(sizeof(*x) == sizeof(int*));
}

int main(void) {
    int foo[2][4];
    f(foo);
    g(foo);

    /* Here we're dynamically creating an array of pointers.  Note that the 
       size of each dimension is not part of the datatype, and so the type 
       system just treats it as a pointer to pointer, not a pointer to array
       or array of arrays. */
    int **bar = malloc(sizeof(*bar) * 2);
    assert(bar);
    for (size_t i = 0; i < 2; i++) {
        bar[i] = malloc(sizeof(*bar[i]) * 4);
        assert(bar[i]);
    }

    h(bar);
    
    for (size_t i = 0; i < 2; i++) {
        free(bar[i]);
    }
    free(bar);
}

も参照してください

関数への配列の受け渡し



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow