수색…


소개

배열은 다른 유형의 정렬 된 값 컬렉션 ( "요소")을 나타내는 파생 데이터 유형입니다. 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] = 값; / * 인덱스 n에 값을 설정합니다. * /
  • 값 = arr [n]; / * 인덱스 n에서 값을 얻는다. * /

비고

왜 우리는 배열이 필요한가?

배열은 개체를 자체 중요성을 가진 집계로 구성하는 방법을 제공합니다. 예를 들어, C 문자열은 문자 배열 ( char s)이고 "Hello, World!"와 같은 문자열입니다. 각 문자에 내재되어 있지 않은 집합체로서의 의미를가집니다. 마찬가지로, 배열은 수학적 벡터와 행렬뿐만 아니라 여러 종류의 목록을 나타 내기 위해 일반적으로 사용됩니다. 게다가 요소를 그룹화하는 방법이 없어도 별도의 변수를 통해 개별적으로 처리해야합니다. 그것은 다루기 힘들뿐만 아니라 다양한 길이의 콜렉션을 쉽게 수용하지 못합니다.

배열은 대부분의 컨텍스트에서 암시 적으로 포인터로 변환됩니다 .

sizeof 연산자, _Alignof 연산자 (C2011) 또는 단항 & (address-of) 연산자의 피연산자 또는 (다른) 배열을 초기화하는 데 사용되는 문자열 리터럴로 표시되는 경우를 제외하고는 배열이 암시 적으로 ( "쇠퇴") 첫 번째 요소에 대한 포인터. 이 암시 적 변환은 배열 subscripting 연산자 ( [] )의 정의와 밀접하게 결합됩니다. expression arr[idx]*(arr + idx) 와 같은 것으로 정의됩니다. 또한 포인터 연산은 교환 가능하므로 *(arr + idx)*(idx + arr) 과 동일하며 idx[arr] 와 같습니다. idx 또는 arr 이 포인터 (또는 포인터로 쇠퇴하는 배열)이고 다른 하나가 정수이고 정수가 배열에 유효한 인덱스 인 경우 해당 표현식은 모두 유효하며 동일한 값으로 평가됩니다 포인터가 가리키는 포인터.

특별한 경우로서, 즉 관찰 &(arr[0]) 와 동등하다 &*(arr + 0) 하는 단순화하는 arr . 모든 표현식은 포인터가 마지막으로 붕괴 될 때마다 교환 가능합니다. 이것은 단순히 배열이 첫 번째 요소에 대한 포인터로 붕괴한다는 것을 다시 표현합니다.

대조적으로, 주소 연산자가 T[N] ( &arr ) 유형의 배열에 적용되면 그 결과는 T (*)[N] 유형이고 전체 배열을 가리 킵니다. 이는 적어도 pointed-to 유형의 크기로 정의되는 포인터 산술과 관련하여 첫 x 째 h 열 요소에 대한 포인터와 구별됩니다.

함수 매개 변수는 배열이 아닙니다 .

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

foo 의 첫 번째 선언은 매개 변수 a 대해 배열과 유사한 구문을 사용하지만이 구문을 사용하여 함수 매개 변수가 해당 매개 변수를 배열의 요소 유형에 대한 포인터 로 선언한다는 것을 선언합니다. 따라서 foo() 대한 두 번째 서명은 첫 번째 서명과 의미 적으로 동일합니다. 이것은 함수 호출 에 대한 인수로 나타나는 포인터에 대한 배열 값의 감쇠에 해당합니다. 즉, 변수와 함수 매개 변수가 같은 배열 유형으로 선언 된 경우 해당 변수의 값은 함수 호출에서의 사용에 적합합니다. 매개 변수와 연관된 인수.

배열 선언 및 초기화

1 차원 배열을 선언하는 일반적인 구문은 다음과 같습니다.

type arrName[size];

여기서 type 은 구조와 같은 내장 유형 또는 사용자 정의 유형 일 수 arrName 은 사용자 정의 식별자이고 size 는 정수 상수입니다.

배열 (이 경우 10 개의 int 변수의 배열)을 선언하는 것은 다음과 같이 수행됩니다 :

int array[10];

이제는 불확정 값을 유지합니다. 선언하는 동안 값을 0으로 유지하려면 다음을 수행하십시오.

int array[10] = {0};

배열에는 또한 초기화 기가있을 수 있습니다.이 예제는 10 int 의 배열을 선언합니다. 여기서 처음 3 개의 int1 , 2 , 3 값을 가지며 다른 모든 값은 0입니다.

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

위의 초기화 방법에서 목록의 첫 번째 값은 배열의 첫 번째 멤버에 할당되고 두 번째 값은 배열의 두 번째 멤버에 할당됩니다. 목록 크기가 배열 크기보다 작 으면 위 예제처럼 배열의 나머지 구성원은 0으로 초기화됩니다. 지정된 목록 초기화 (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 */

길이가 0 인 배열을 선언 할 수 없습니다.

C99 C11

Variable Length Array (가변 길이 어레이) (VLA)는 C99에 추가되었으며 C11에서는 선택 사항입니다. 그것들은 일반적인 배열과 동일합니다. 중요한 차이점은 다음과 같습니다. 컴파일시 길이를 알 필요가 없습니다. VLA에는 자동 저장 기간이 있습니다. VLA에 대한 포인터 만 정적 저장 기간을 가질 수 있습니다.

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

중대한:

VLA는 잠재적으로 위험합니다. 위의 예제에서 배열 vla 가 스택에 사용 가능한 공간보다 많은 공간을 필요로하면 스택이 오버플로됩니다. 따라서 VLA의 사용은 스타일 가이드와 책 및 연습 문제로 인해 종종 권장되지 않습니다.

배열 내용 지우기 (제로)

때로는 초기화가 완료된 후에 배열을 0으로 설정해야합니다.

#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;
}

위의 루프의 일반적인 단축키는 <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으로 만드는 세 번째 옵션이 가능합니다.

 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 연산자의 피연산자 인 경우는 소수의 예외 중 하나입니다. 결과 포인터는 그 자체가 배열이 아니며, 파생 된 배열의 길이에 대한 정보를 가지고 있지 않습니다. 따라서 포인터가 함수에 전달 될 때와 같이 포인터와 함께 해당 길이가 필요하면 별도로 전달해야합니다.

예를 들어, 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 ). 이는 int *input 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 은 다음 경고를 내 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 ... */

두 사람도 마찬가지입니다.

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 요소 배열에 메모리 블록을 할당합니다. 메모리는 후자에 의해 모두 0으로 초기화됩니다.

성공의 경우 메모리는 free() 호출에 의해 해제됩니다.

배열을 효율적으로 반복 행 우선 순서로 반복

C의 배열은 연속적인 메모리 덩어리로 볼 수 있습니다. 보다 정확하게는 배열의 마지막 차원은 인접한 부분입니다. 우리는이 행을 주요 순서 라고 부릅니다. 이것을 이해하고 이후의 캐시 오류를 방지하기 위해 캐시되지 않은 데이터에 액세스 할 때 캐시 오류가 전체 캐시 라인을 캐시로로드한다는 사실을 알기 때문에 array[0][0] 으로 차원 10000x10000에 액세스하면 잠재적으로 array[0][1] 로부터 sizeof(type)*10000 바이트 떨어져 있기 때문에 두 번째 캐시 결함을 생성하는 바로 직후에 array[1][0] 액세스하는 것은 캐시에 있지만 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;
    }
}

같은 맥락에서, 이것이 하나의 차원과 여러 개의 인덱스를 가진 배열을 다루는 경우 (인덱스 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 데이터 유형 ( int , float 등)이 될 수 있으며 arrayName 은 유효한 C 식별자가 될 수 있습니다. 2 차원 배열은 m 행과 n 열이있는 테이블로 시각화 할 수 있습니다. : 순서 C로 중요합니다. int a[4][3] 배열은 int a[3][4] 배열과 동일하지 않습니다. C는 행 - 주요 언어이므로 행의 수가 가장 먼저옵니다.

3 행 4 열을 포함하는 2 차원 배열 a 는 다음과 같이 표시 할 수 있습니다.

테이블로 2D 배열의 시각적 레이아웃

따라서 배열 a 모든 요소는 a[i][j] 형식의 요소 이름으로 식별됩니다. 여기서 a 는 배열의 이름이고, i 는 어떤 행을 나타내며 j 는 어떤 열을 나타냅니다. 행과 열은 0으로 인덱싱된다는 점을 상기하십시오. 이는 하위 2-D 행렬에 대한 수학 표기법과 매우 유사합니다.

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];

위의 명령문은 배열의 세 번째 행에서 네 번째 요소를 취합니다. 중첩 루프를 사용하여 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}} 
};

생성되는 배열의 대부분은 하나 또는 두 개의 차원이 될 수 있지만 모든 차원의 배열을 가질 수 있습니다.

포인터를 사용하여 배열 반복하기

#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가 사용되는 거의 모든 장소 에서처럼 배열 a 는 첫 번째 요소에 대한 포인터로 쇠퇴 합니다.

그런 다음 ++p 는 포인터 p 에 대해 포인터 연산을 수행하고 배열 요소를 통해 하나씩 순회하며 *p 로 역 참조하여 참조합니다.

다차원 배열을 함수에 전달

다차원 배열은 함수에 전달할 때 일차원 배열과 동일한 규칙을 따릅니다. 그러나 decay-to-pointer, 연산자 우선 순위 및 다차원 배열 (배열 배열 대 포인터 배열)을 선언하는 두 가지 방법의 조합은 그러한 함수의 선언을 직관적이지 않게 만들 수 있습니다. 다음 예제에서는 다차원 배열을 전달하는 올바른 방법을 보여줍니다.

#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