programing

C++에서 어레이를 어떻게 사용합니까?

jooyons 2023. 5. 20. 10:46
반응형

C++에서 어레이를 어떻게 사용합니까?

C++는 C에서 상속된 어레이로, 사실상 모든 곳에서 사용됩니다.가 발생하기쉬운 를 제공합니다.std::vector<T>C++98 이후 및 C++11 이후). 따라서 어레이에 대한 필요성은 C만큼 자주 발생하지 않습니다.그러나 레거시 코드를 읽거나 C로 작성된 라이브러리와 상호 작용할 때는 배열이 어떻게 작동하는지 확실히 파악해야 합니다.

이 FAQ는 다음과 같이 5개 부분으로 나뉩니다.

  1. 유형 수준의 배열 및 요소 액세스
  2. 어레이 생성 및 초기화
  3. 할당 및 매개 변수 전달
  4. 다차원 배열 및 포인터 배열
  5. 어레이를 사용할 때 일반적으로 발생하는 문제

이 FAQ에서 중요한 내용이 누락되었다고 생각되는 경우 답변을 작성하고 여기에 추가로 링크하십시오.

템플릿 "array"가 "array"를 의미합니다.std::arrayC 선언자 구문에 대한 기본 지식을 가정합니다. 사용유 오시십하의에의 하십시오.new그리고.delete아래에서 설명한 바와 같이 예외에 직면할 경우 매우 위험하지만, 이는 또 다른 FAQ의 주제입니다.


(참고: 이것은 Stack Overflow의 C++ FAQ 항목입니다.만약 당신이 이 양식으로 FAQ를 제공하는 아이디어를 비판하고 싶다면, 이 모든 것을 시작한 메타에 게시하는 것이 그것을 할 수 있는 장소가 될 것입니다.이 질문에 대한 답변은 애초에 FAQ 아이디어가 시작된 C++ 채팅방에서 모니터링되므로 아이디어를 제안한 사람들이 답변을 읽을 가능성이 매우 높습니다.)

유형 수준의 배열

배열 유형은 다음과 같이 표시됩니다.T[n]T요소 유형 및n는 양의 크기로 배열에 포함된 요소의 수입니다.어레이 유형은 요소 유형 및 크기의 제품 유형입니다.두 성분 중 하나 또는 둘 다 다를 경우, 다음과 같은 유형을 얻을 수 있습니다.

#include <type_traits>

static_assert(!std::is_same<int[8], float[8]>::value, "distinct element type");
static_assert(!std::is_same<int[8],   int[9]>::value, "distinct size");

크기는 유형의 일부입니다. 즉, 크기가 다른 어레이 유형은 서로 전혀 관련이 없는 호환되지 않는 유형입니다. sizeof(T[n])는 와동합다니등다에 합니다.n * sizeof(T).

배열 대 포인터 붕괴

일 "결은한" 사이의 T[n]그리고.T[m]즉, 두 유형 모두 암시적으로 변환될 수 있습니다.T*그리고 이 변환의 결과는 배열의 첫 번째 요소에 대한 포인터입니다.나 즉, 어든서a입니다.T*입력해야 . 를 할 수 있습니다.T[n]컴파일러는 자동으로 해당 포인터를 제공합니다.

                  +---+---+---+---+---+---+---+---+
the_actual_array: |   |   |   |   |   |   |   |   |   int[8]
                  +---+---+---+---+---+---+---+---+
                    ^
                    |
                    |
                    |
                    |  pointer_to_the_first_element   int*

이러한 변환을 "어레이-포인터 붕괴"라고 하며, 이는 혼란의 주요 원인입니다.의 크기는 더 에 이됩니다.T*) Pro: 유형 수준에서 배열의 크기를 잊으면 포인터가 모든 크기의 배열의 첫 번째 요소를 가리킬 수 있습니다.반대: 배열의 첫 번째(또는 다른) 요소에 대한 포인터를 지정하면 해당 배열이 얼마나 큰지 또는 배열의 경계를 기준으로 포인터가 정확히 가리키는 위치를 감지할 수 없습니다.포인터는 매우 멍청합니다.

배열이 포인터가 아닙니다.

컴파일러는 유용하다고 판단될 때마다, 즉 배열에서 작업이 실패하지만 포인터에서 성공할 때마다 배열의 첫 번째 요소에 대한 포인터를 자동으로 생성합니다.결과 포인터 값은 단순히 배열의 주소이기 때문에 배열에서 포인터로 변환하는 것은 사소한 일입니다.포인터는 배열 자체의 일부(또는 메모리의 다른 부분)로 저장되지 않습니다.배열이 포인터가 아닙니다.

static_assert(!std::is_same<int[8], int*>::value, "an array is not a pointer");

배열이 첫 번째 요소에 대한 포인터로 붕괴되지 않는 한 가지 중요한 맥락은 다음과 같습니다.&연산자가 적용됩니다.그런경에우,에경,&연산자는 첫 번째 요소에 대한 포인터뿐만 아니라 전체 배열에 대한 포인터를 제공합니다.이 경우 (주소)은 동일하지만 배열의 첫 번째 요소에 대한 포인터와 전체 배열에 대한 포인터는 완전히 다른 유형입니다.

static_assert(!std::is_same<int*, int(*)[8]>::value, "distinct element type");

다음 ASCII 기술은 이러한 차이를 설명합니다.

      +-----------------------------------+
      | +---+---+---+---+---+---+---+---+ |
+---> | |   |   |   |   |   |   |   |   | | int[8]
|     | +---+---+---+---+---+---+---+---+ |
|     +---^-------------------------------+
|         |
|         |
|         |
|         |  pointer_to_the_first_element   int*
|
|  pointer_to_the_entire_array              int(*)[8]

첫 번째 요소에 대한 포인터가 단일 정수(작은 상자로 표시됨)만 가리키는 반면 전체 배열에 대한 포인터는 8개의 정수(큰 상자로 표시됨)의 배열을 가리킵니다.

같은 상황이 수업에서 발생하고 어쩌면 더 분명할 수도 있습니다.개체에 대한 포인터와 첫 번째 데이터 멤버에 대한 포인터는 동일한 값(동일한 주소)을 가지지만 완전히 다른 유형입니다.

구문에 않은 , C 의 괄호가 .int(*)[8]필수 요소:

  • int(*)[8]8개의 정수 배열에 대한 포인터입니다.
  • int*[8]각각의 유형 요소인 8개의 포인터 배열입니다.int*.

요소 액세스

C++는 배열의 개별 요소에 액세스할 수 있는 두 가지 구문 변형을 제공합니다.그들 중 어느 것도 다른 것보다 우월하지 않으며, 당신은 두 가지 모두에 익숙해야 합니다.

포인터 산술

인터지정이 주어집니다.p은 " 의첫번요식소인을째배열"입니다.p+i배열의 i번째 요소에 대한 포인터를 산출합니다.나중에 포인터를 참조 해제하면 개별 요소에 액세스할 수 있습니다.

std::cout << *(x+3) << ", " << *(x+7) << std::endl;

한다면x배열을 표시합니다. 배열과 정수를 추가하는 것은 의미가 없지만(배열에 더하기 연산이 없음) 포인터와 정수를 추가하는 것은 다음과 같습니다.

   +---+---+---+---+---+---+---+---+
x: |   |   |   |   |   |   |   |   |   int[8]
   +---+---+---+---+---+---+---+---+
     ^           ^               ^
     |           |               |
     |           |               |
     |           |               |
x+0  |      x+3  |          x+7  |     int*

(암묵적으로 생성된 포인터에는 이름이 없으므로 다음과 같이 기록했습니다.x+0식별하기 위해.)

반면에, 만약에,x배열의 첫 번째(또는 다른) 요소에 대한 포인터를 나타내며, 다음에 배열-벡터 간 붕괴가 필요하지 않습니다. 왜냐하면i추가할 항목이 이미 있습니다.

   +---+---+---+---+---+---+---+---+
   |   |   |   |   |   |   |   |   |   int[8]
   +---+---+---+---+---+---+---+---+
     ^           ^               ^
     |           |               |
     |           |               |
   +-|-+         |               |
x: | | |    x+3  |          x+7  |     int*
   +---+

참로묘사경우된고,경우,▁the▁note▁in▁that로된사,x포인터 변수입니다(옆의 작은 상자로 표시 가능).x), 은 포인터(의 표현)를 반환하는 도 있습니다.T*).

인덱싱 연산자

구문 이후후 이후로*(x+i)서툴지만 구문인 C++을 합니다.x[i]:

std::cout << x[3] << ", " << x[7] << std::endl;

덧셈은 교환적이기 때문에 다음 코드는 정확히 동일합니다.

std::cout << 3[x] << ", " << 7[x] << std::endl;

인덱싱 연산자의 정의는 다음과 같은 흥미로운 동등성으로 이어집니다.

&x[i]  ==  &*(x+i)  ==  x+i

하만지,&x[0]일반적으로 와 동등하지 않습니다.x전자는 포인터이고 후자는 배열입니다.컨텍스트가 어레이 대 포인터 붕괴를 트리거할 때만 가능합니다.x그리고.&x[0]번갈아 쓰임새다예:

T* p = &array[0];  // rewritten as &*(array+0), decay happens due to the addition
T* q = array;      // decay happens due to the assignment

첫 번째 줄에서 컴파일러는 포인터에서 포인터로의 할당을 감지하며, 이는 사소한 성공입니다.두 번째 줄에서는 배열에서 포인터로의 할당을 감지합니다.이것은 의미가 없기 때문에(그러나 포인터에 대한 포인터 할당은 의미가 있으므로), 어레이 대 포인터 붕괴는 평소와 같이 시작됩니다.

범위

유형의 입니다.T[n]가지다n 요소, 인된요에서 0n-1 요가없습다가 .n그럼에도 불구하고 반오픈 범위(시작이 포함되고 끝이 배타적인 경우)를 지원하기 위해 C++는 (존재하지 않는) n번째 요소에 대한 포인터의 계산을 허용하지만, 해당 포인터의 참조를 취소하는 것은 불법입니다.

   +---+---+---+---+---+---+---+---+....
x: |   |   |   |   |   |   |   |   |   .   int[8]
   +---+---+---+---+---+---+---+---+....
     ^                               ^
     |                               |
     |                               |
     |                               |
x+0  |                          x+8  |     int*

예를 들어 배열을 정렬하려는 경우 다음 두 가지 모두 동일하게 잘 작동합니다.

std::sort(x + 0, x + n);
std::sort(&x[0], &x[0] + n);

제하는것불법다니입은공다니▁▁to▁is를 제공하는 것은 입니다.&x[n]이것이 와 동등하기 때문에 두 번째 주장으로서.&*(x+n) 하위 표현 식표현express*(x+n)C++에서 정의되지 않은 동작을 기술적으로 호출합니다(C99에서는 호출되지 않음).

또한간제수있다습니할공히를 제공할 수도 .x제1변론으로서이것은 제 취향에 약간 맞지 않고, 또한 컴파일러에게 템플릿 인수 추론을 조금 더 어렵게 만듭니다. 이 경우 첫 번째 인수는 배열이지만 두 번째 인수는 포인터이기 때문입니다. (다시, 배열 대 포인터 붕괴가 시작됩니다.)

프로그래머는 종종 다차원 배열과 포인터 배열을 혼동합니다.

다차원 배열

대부분의 프로그래머는 명명된 다차원 배열에 익숙하지만, 많은 프로그래머는 다차원 배열도 익명으로 만들 수 있다는 사실을 알지 못합니다.다차원 배열은 종종 "배열 배열" 또는 "진정한 다차원 배열"이라고도 합니다.

명명된 다차원 배열

명명된 다차원 배열을 사용하는 경우 컴파일 시 모든 차원을 알아야 합니다.

int H = read_int();
int W = read_int();

int connect_four[6][7];   // okay

int connect_four[H][7];   // ISO C++ forbids variable length array
int connect_four[6][W];   // ISO C++ forbids variable length array
int connect_four[H][W];   // ISO C++ forbids variable length array

메모리에서 명명된 다차원 배열은 다음과 같습니다.

              +---+---+---+---+---+---+---+
connect_four: |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+

위와 같은 2D 그리드는 유용한 시각화에 불과합니다.C++의 관점에서 메모리는 "평평한" 바이트 시퀀스입니다.다차원 배열의 요소는 행-주요 순서로 저장됩니다. 그은것,connect_four[0][6]그리고.connect_four[1][0]기억 속의 이웃들입니다. 실은.connect_four[0][7]그리고.connect_four[1][0]동일한 요소를 나타냅니다!, 할 수 . 즉, 다차배사열을 1차원 배열입니다.

int* p = &connect_four[0][0];
int* q = p + 42;
some_int_sequence_algorithm(p, q);

익명의 다차원 배열

익명의 다차원 배열을 사용하는 경우 컴파일 시 첫 번째 차원을 제외한 모든 차원을 알아야 합니다.

int (*p)[7] = new int[6][7];   // okay
int (*p)[7] = new int[H][7];   // okay

int (*p)[W] = new int[6][W];   // ISO C++ forbids variable length array
int (*p)[W] = new int[H][W];   // ISO C++ forbids variable length array

메모리에서 익명의 다차원 배열은 다음과 같습니다.

              +---+---+---+---+---+---+---+
        +---> |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |
      +-|-+
   p: | | |
      +---+

어레이 자체는 여전히 메모리에서 단일 블록으로 할당됩니다.

포인터 배열

다른 수준의 간접비를 도입하여 고정 폭의 제한을 극복할 수 있습니다.

명명된 포인터 배열

다음은 길이가 다른 익명 배열로 초기화된 5개 포인터의 명명된 배열입니다.

int* triangle[5];
for (int i = 0; i < 5; ++i)
{
    triangle[i] = new int[5 - i];
}

// ...

for (int i = 0; i < 5; ++i)
{
    delete[] triangle[i];
}

그리고 기억 속에서의 모습은 다음과 같습니다.

          +---+---+---+---+---+
          |   |   |   |   |   |
          +---+---+---+---+---+
            ^
            | +---+---+---+---+
            | |   |   |   |   |
            | +---+---+---+---+
            |   ^
            |   | +---+---+---+
            |   | |   |   |   |
            |   | +---+---+---+
            |   |   ^
            |   |   | +---+---+
            |   |   | |   |   |
            |   |   | +---+---+
            |   |   |   ^
            |   |   |   | +---+
            |   |   |   | |   |
            |   |   |   | +---+
            |   |   |   |   ^
            |   |   |   |   |
            |   |   |   |   |
          +-|-+-|-+-|-+-|-+-|-+
triangle: | | | | | | | | | | |
          +---+---+---+---+---+

이제 각 라인이 개별적으로 할당되므로 2D 어레이를 1D 어레이로 볼 수 없습니다.

익명 포인터 배열

다음은 길이가 다른 익명 배열로 초기화되는 5개(또는 다른 개수의) 포인터의 익명 배열입니다.

int n = calculate_five();   // or any other number
int** p = new int*[n];
for (int i = 0; i < n; ++i)
{
    p[i] = new int[n - i];
}

// ...

for (int i = 0; i < n; ++i)
{
    delete[] p[i];
}
delete[] p;   // note the extra delete[] !

그리고 기억 속에서의 모습은 다음과 같습니다.

          +---+---+---+---+---+
          |   |   |   |   |   |
          +---+---+---+---+---+
            ^
            | +---+---+---+---+
            | |   |   |   |   |
            | +---+---+---+---+
            |   ^
            |   | +---+---+---+
            |   | |   |   |   |
            |   | +---+---+---+
            |   |   ^
            |   |   | +---+---+
            |   |   | |   |   |
            |   |   | +---+---+
            |   |   |   ^
            |   |   |   | +---+
            |   |   |   | |   |
            |   |   |   | +---+
            |   |   |   |   ^
            |   |   |   |   |
            |   |   |   |   |
          +-|-+-|-+-|-+-|-+-|-+
          | | | | | | | | | | |
          +---+---+---+---+---+
            ^
            |
            |
          +-|-+
       p: | | |
          +---+

변환

배열 대 포인터 붕괴는 자연스럽게 배열 및 포인터 배열로 확장됩니다.

int array_of_arrays[6][7];
int (*pointer_to_array)[7] = array_of_arrays;

int* array_of_pointers[6];
int** pointer_to_pointer = array_of_pointers;

그러나 다음에서 암시적인 변환은 없습니다.T[h][w]T**는 배열의 첫 에 대한 .h에 대한 T(각각 원래 2D 배열에서 선의 첫 번째 요소를 가리키지만) 해당 포인터 배열은 아직 메모리의 어느 곳에도 존재하지 않습니다.이러한 변환을 수행하려면 필요한 포인터 배열을 수동으로 생성하고 채워야 합니다.

int connect_four[6][7];

int** p = new int*[6];
for (int i = 0; i < 6; ++i)
{
    p[i] = connect_four[i];
}

// ...

delete[] p;

이렇게 하면 원래 다차원 배열의 보기가 생성됩니다.대신 복사본이 필요한 경우 추가 어레이를 생성하고 직접 데이터를 복사해야 합니다.

int connect_four[6][7];

int** p = new int*[6];
for (int i = 0; i < 6; ++i)
{
    p[i] = new int[7];
    std::copy(connect_four[i], connect_four[i + 1], p[i]);
}

// ...

for (int i = 0; i < 6; ++i)
{
    delete[] p[i];
}
delete[] p;

과제

특별한 이유 없이 어레이를 서로 할당할 수 없습니다.사용하다std::copy대신:

#include <algorithm>

// ...

int a[8] = {2, 3, 5, 7, 11, 13, 17, 19};
int b[8];
std::copy(a + 0, a + 8, b);

이는 더 큰 어레이의 슬라이스를 더 작은 어레이로 복사할 수 있기 때문에 실제 어레이 할당보다 더 유연합니다. std::copy는 일반적으로 최대 성능을 제공하기 위해 기본 유형에 특화되어 있습니다. 것 같지는 않습니다.std::memcpy더 나은 성능을 제공합니다.확실하지 않으면 측정합니다.

어레이를 직접 할당할 수는 없지만 어레이 멤버를 포함하는 구조체 및 클래스를 할당할 수 있습니다.이는 배열 멤버가 컴파일러에서 기본값으로 제공하는 할당 연산자에 의해 멤버별로 복사되기 때문입니다.고유한 구조체 또는 클래스 유형에 대해 할당 연산자를 수동으로 정의하는 경우 어레이 멤버에 대한 수동 복사로 돌아가야 합니다.

매개 변수 전달

배열은 값으로 전달할 수 없습니다.포인터 또는 참조로 전달할 수 있습니다.

포인터로 전달

배열 자체는 값으로 전달될 수 없으므로 일반적으로 첫 번째 요소에 대한 포인터는 값으로 전달됩니다.이를 "pass by pointer"라고 합니다.이 포인터를 통해 어레이의 크기를 검색할 수 없으므로 어레이의 크기를 나타내는 두 번째 매개 변수(클래식 C 솔루션) 또는 어레이의 마지막 요소(C++ 반복기 솔루션) 뒤를 가리키는 두 번째 포인터를 전달해야 합니다.

#include <numeric>
#include <cstddef>

int sum(const int* p, std::size_t n)
{
    return std::accumulate(p, p + n, 0);
}

int sum(const int* p, const int* q)
{
    return std::accumulate(p, q, 0);
}

를 구적대안다같음이매개변선수수있다로 선언할 .T p[]그리고 그것은 정확히 같은 것을 의미합니다.T* p 매개 변수 목록의 컨텍스트에서만 사용할 수 있습니다.

int sum(const int p[], std::size_t n)
{
    return std::accumulate(p, p + n, 0);
}

당신은 컴파일러가 다시 쓰는 것이라고 생각할 수 있습니다.T p[]T *p 매개 변수 목록의 컨텍스트에서만 사용할 수 있습니다.이 특별한 규칙은 배열과 포인터에 대한 전체 혼란에 부분적으로 책임이 있습니다.다른 모든 맥락에서 무언가를 배열 또는 포인터로 선언하는 것은 큰 차이를 만듭니다.

안타깝게도 컴파일러가 자동으로 무시하는 배열 매개 변수의 크기를 제공할 수도 있습니다.즉, 컴파일러 오류에서 알 수 있듯이 다음 세 가지 서명이 정확히 동일합니다.

int sum(const int* p, std::size_t n)

// error: redefinition of 'int sum(const int*, size_t)'
int sum(const int p[], std::size_t n)

// error: redefinition of 'int sum(const int*, size_t)'
int sum(const int p[8], std::size_t n)   // the 8 has no meaning here

참조로 전달

어레이는 참조를 통해 전달될 수도 있습니다.

int sum(const int (&a)[8])
{
    return std::accumulate(a + 0, a + 8, 0);
}

이 경우 어레이 크기가 중요합니다.8개 요소의 배열만 허용하는 함수를 작성하는 것은 거의 쓸모가 없기 때문에 프로그래머들은 보통 템플릿과 같은 함수를 작성합니다.

template <std::size_t n>
int sum(const int (&a)[n])
{
    return std::accumulate(a + 0, a + n, 0);
}

이러한 함수 템플릿은 정수에 대한 포인터가 아닌 실제 정수 배열로만 호출할 수 있습니다.어레이의 크기는 모든 크기에 대해 자동으로 유추됩니다.n템플릿에서 다른 함수가 인스턴스화됩니다.요소 유형과 크기 모두에서 추상화되는 매우 유용한 함수 템플릿을 작성할 수도 있습니다.

어레이를 사용할 때 흔히 발생하는 문제입니다.

5.1 피트폴:신뢰 유형 - 안전하지 않은 연결.

좋습니다. 글로벌(번역 단위 외부에서 액세스할 수 있는 범위 변수)이 Evil™이라는 말을 들었거나 스스로 알게 되었습니다.하지만 그들이 얼마나 진정한 Evil™인지 알고 계셨습니까?[main.cpp] 및 [numbers.cpp] 두 개의 파일로 구성된 아래 프로그램을 생각해 보십시오.

// [main.cpp]
#include <iostream>

extern int* numbers;

int main()
{
    using namespace std;
    for( int i = 0;  i < 42;  ++i )
    {
        cout << (i > 0? ", " : "") << numbers[i];
    }
    cout << endl;
}

// [numbers.cpp]
int numbers[42] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

Windows 7(윈도우 7)에서는 MinGW g++ 4.4.1 및 Visual C++ 10.0과 모두 잘 컴파일되고 연결됩니다.

유형이 일치하지 않기 때문에 프로그램을 실행하면 프로그램이 중단됩니다.

Windows 7(윈도우 7) 충돌 대화 상자

공식적인 설명: 이 프로그램은 정의되지 않은 행동(UB)을 가지고 있습니다. 따라서 프로그램을 중단시키는 대신 그냥 매달리거나, 아마도 아무것도 하지 않거나, 미국, 러시아, 인도, 중국, 스위스 대통령에게 위협적인 이메일을 보낼 수 있습니다. 그리고 여러분의 코에서 비강 데몬이 날아가게 만들 수 있습니다.

설명: 실상의설에서:main.cpp배열은 배열과 동일한 주소에 배치된 포인터로 처리됩니다.의 경우 는 첫 번째 32비트 실행 파일이 다음과 같다는 합니다.int배열의 값은 포인터로 처리됩니다.즉, 인main.cpp그자리의 numbers변수가 다음을 포함하거나 포함하는 것으로 나타납니다.(int*)1이로 인해 프로그램이 주소 공간의 맨 아래에 있는 메모리에 액세스하게 되며, 이는 일반적으로 예약되어 트랩을 발생시킵니다.결과: 충돌이 발생합니다.

컴파일러는 이 오류를 진단하지 않을 권리가 충분히 있습니다. C++11 § 3.5/10이 선언에 대한 호환 가능한 유형의 요구사항에 대해 언급하고 있기 때문입니다.

[N3290 §3.5/10]
유형 ID에 대한 이 규칙을 위반하면 진단이 필요하지 않습니다.

같은 단락에서 허용되는 변동에 대해 자세히 설명합니다.

배열 개체에 대한 선언은 주 배열 바인딩(8.3.4)의 유무에 따라 다른 배열 유형을 지정할 수 있습니다.

이렇게 허용되는 변형에는 이름을 한 변환 단위의 배열로 선언하거나 다른 변환 단위의 포인터로 선언하는 것이 포함되지 않습니다.

Pitfall : (5.2 Pitfall : 조기최화적(화(memset친구).

미작성

5.3 Pitfall: Cidiom을 사용하여 요소의 수를 얻습니다.

깊은 C 경험을 바탕으로 …을 쓰는 것은 당연합니다.

#define N_ITEMS( array )   (sizeof( array )/sizeof( array[0] ))

로 로.array pointer한 곳, 식 필 한 식 로 소 를 터sizeof(a)/sizeof(a[0])라고도 쓸 수 있습니다.sizeof(a)/sizeof(*a)같은 뜻이고, 어떻게 쓰든 배열의 숫자 요소를 찾는 시디옴입니다.

주요 함정: Cidiom은 안전하지 않습니다.예를 들어, 코드는 …

#include <stdio.h>

#define N_ITEMS( array ) (sizeof( array )/sizeof( *array ))

void display( int const a[7] )
{
    int const   n = N_ITEMS( a );          // Oops.
    printf( "%d elements.\n", n );
}

int main()
{
    int const   moohaha[]   = {1, 2, 3, 4, 5, 6, 7};

    printf( "%d elements, calling display...\n", N_ITEMS( moohaha ) );
    display( moohaha );
}

를 포를에 니다전합달로 합니다.N_ITEMS따라서 잘못된 결과를 초래할 가능성이 높습니다.에서 32비트 …윈도우즈 7(윈도우즈 7)에서 32비트 실행 파일을 생성합니다.

7개의 요소, 호출 디스플레이...
요소 1개.

  1. 는 컴일러다씁니다를 다시 씁니다.int const a[7]int const a[].
  2. 는 컴일러다씁니다를 다시 씁니다.int const a[]int const* a.
  3. N_ITEMS따라서 포인터로 호출됩니다.
  4. 파일의 32비트 파일입니다.sizeof(array)는 4으)ㄹ 수 있습니다. (으)ㄹ 수 있습니다.
  5. sizeof(*array)는 와동합다니등다에 합니다.sizeof(int)의 경우4.32 트실입경 4니다입니다.

런타임에 이 오류를 감지하려면 다음을 수행합니다.

#include <assert.h>
#include <typeinfo>

#define N_ITEMS( array )       (                               \
    assert((                                                    \
        "N_ITEMS requires an actual array as argument",        \
        typeid( array ) != typeid( &*array )                    \
        )),                                                     \
    sizeof( array )/sizeof( *array )                            \
    )

7개의 요소, 호출 디스플레이...
실패: ( argument", = (&*a), typeidN_ITES"!= typeid(&*a), runtime_inion), .cpp, 16.

이 응용 프로그램은 런타임에 비정상적인 방법으로 종료하도록 요청했습니다.
자세한 내용은 응용 프로그램의 지원 팀에 문의하십시오.

런타임 오류 탐지는 탐지하지 않는 것보다 낫지만 프로세서 시간을 약간 낭비하고 프로그래머 시간을 훨씬 더 낭비할 수 있습니다.컴파일 시 탐지 기능이 향상되었습니다!C++98로 로컬 유형의 어레이를 지원하지 않는 것이 좋다면 다음과 같이 할 수 있습니다.

#include <stddef.h>

typedef ptrdiff_t   Size;

template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }

#define N_ITEMS( array )       n_items( array )

이 정의를 g++로 첫 번째 완전한 프로그램으로 대체하여 컴파일하면서 저는 …을 얻었습니다.

M:\count> g++ compile_time_detection.cpp
compile_time_message.cpp:'display( 'Void display("Void display")"
: ' compile_time_proxy.cpp:14: 파일: 'n_proxy(constint*&)' 호출에 대한 .

M:\count> _

작동 방식: 어레이는 다음을 참조하여 전달됩니다.n_items따라서 첫 번째 요소에 대한 포인터로 붕괴되지 않으며 함수는 형식에 의해 지정된 요소의 수를 반환할 수 있습니다.

C++11을 사용하면 로컬 유형의 배열에도 사용할 수 있으며 배열의 요소 수를 찾는 안전한 C++ 관용구입니다.

C& : 5.4 C++ & C++14 사용하기 : aconstexpr배열 크기 함수입니다.

C++11 이상에서는 당연하지만, 당신이 보게 될 위험! C++03 기능을 대체하는 것.

typedef ptrdiff_t   Size;

template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }

와 함께

using Size = ptrdiff_t;

template< class Type, Size n >
constexpr auto n_items( Type (&)[n] ) -> Size { return n; }

는 여서중변사것입다니는용하화는요기한을 입니다.constexpr이 함수는 컴파일 시간 상수를 생성할 수 있습니다.

예를 들어, C++03 함수와 대조적으로, 이러한 컴파일 시간 상수는 다른 것과 같은 크기의 배열을 선언하는 데 사용될 수 있습니다.

// Example 1
void foo()
{
    int const x[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 4};
    constexpr Size n = n_items( x );
    int y[n] = {};
    // Using y here.
}

그러나 다음을 사용하여 이 코드를 고려합니다.constexpr버전:

// Example 2
template< class Collection >
void foo( Collection const& c )
{
    constexpr int n = n_items( c );     // Not in C++14!
    // Use c here
}

auto main() -> int
{
    int x[42];
    foo( x );
}

: 2015년 과 함께 MinGW-645.1.0과 됩니다.-pedantic-errors그리고 gcc.godbolt.org/, 의 온라인 컴파일러를 사용하여 clang 3.0 및 clang 3.2를 사용하여 테스트하지만 clang 3.3, 3.4.1, 3.5.0, 3.5.1, 3.6(rc1) 또는 3.7(실험)은 테스트하지 않습니다.또한 윈도우즈 플랫폼에서 중요한 것은 Visual C++ 2015로 컴파일되지 않는다는 것입니다.그 이유는 다음에서 참조 사용에 대한 C++11/C++14 문입니다.constexpr식:

C++11 C++14 $5.19/2 nine th의 dash

조건식 e의 평가가 없는 한 핵심 상수 표현식입니다.e추상 기계의 규칙(1.9)에 따라 다음 식 중 하나를 평가합니다.

  • 참조가 이전 초기화를 가지지 않는 한, 참조 유형의 변수 또는 데이터 멤버를 참조하는 id-표현식.
    • 상수 식을 사용하여 초기화됩니다.
    • e의 평가 내에서 수명이 시작된 객체의 비정적 데이터 멤버입니다.

사람은 항상 더 장황하게 쓸 수 있습니다.

// Example 3  --  limited

using Size = ptrdiff_t;

template< class Collection >
void foo( Collection const& c )
{
    constexpr Size n = std::extent< decltype( c ) >::value;
    // Use c here
}

하지만 이것은 실패합니다.Collection원시 배열이 아닙니다.

어레이가 아닐 수 있는 컬렉션을 처리하려면 오버로드 가능성이 필요합니다.n_items함수, 그러나 컴파일 시간 사용을 위해서는 배열 크기의 컴파일 시간 표현이 필요합니다.그리고 C++11과 C++14에서도 잘 작동하는 고전적인 C++03 솔루션은 함수가 값이 아닌 함수 결과 유형을 통해 결과를 보고하도록 하는 것입니다.예를 들어 다음과 같습니다.

// Example 4 - OK (not ideal, but portable and safe)

#include <array>
#include <stddef.h>

using Size = ptrdiff_t;

template< Size n >
struct Size_carrier
{
    char sizer[n];
};

template< class Type, Size n >
auto static_n_items( Type (&)[n] )
    -> Size_carrier<n>;
// No implementation, is used only at compile time.

template< class Type, size_t n >        // size_t for g++
auto static_n_items( std::array<Type, n> const& )
    -> Size_carrier<n>;
// No implementation, is used only at compile time.

#define STATIC_N_ITEMS( c ) \
    static_cast<Size>( sizeof( static_n_items( c ).sizer ) )

template< class Collection >
void foo( Collection const& c )
{
    constexpr Size n = STATIC_N_ITEMS( c );
    // Use c here
    (void) c;
}

auto main() -> int
{
    int x[42];
    std::array<int, 43> y;
    foo( x );
    foo( y );
}

한반유선택형에 대한 반환 static_n_items이 코드는 사용되지 않습니다.std::integral_constant 냐하면과 함께std::integral_constant는 과는다같직표접현다니됩이음과결▁a▁as로 직접 표현됩니다.constexpr값, 원래 문제를 다시 소개합니다.에 에.Size_carrier클래스 1은 함수가 배열에 대한 참조를 직접 반환하도록 할 수 있습니다.하지만, 모든 사람들이 그 구문에 익숙하지는 않습니다.

에 대해서: 이의 일부를 " " " " " " " " " 라고 입력합니다.constexpr- 참조로 인한 오류 문제는 컴파일 시간의 선택을 일정하게 명시적으로 만드는 것입니다.

웁스-the-there-a-reference-in-in-in-reference-in-your-sour-s ▁oops▁hopefully참다.constexpr문제는 C++17로 해결될 것이지만, 그때까지 같은 매크로.STATIC_N_ITEMS위는 클랑 및 Visual C++ 컴파일러에 대한 휴대성을 제공하여 유형 안전을 유지합니다.

범위를 이름 접두사를 하는 것이 를 들어, " " " " " " " " " " " " " " " " " " " " " 를 사용하는 것이 좋습니다.MYLIB_STATIC_N_ITEMS.

어레이 생성 및 초기화

다른 C++ 객체와 마찬가지로 배열은 명명된 변수에 직접 저장되거나(그러면 크기는 컴파일 시간 상수여야 하며, C++는 VLA를 지원하지 않습니다) 힙에 익명으로 저장되고 포인터를 통해 간접적으로 액세스할 수 있습니다(그래야만 런타임에 크기를 계산할 수 있습니다).

자동 배열

제어 흐름이 정적이 아닌 로컬 배열 변수의 정의를 통과할 때마다 자동 배열("스택"에 상주하는 배열)이 생성됩니다.

void foo()
{
    int automatic_array[8];
}

초기화는 오름차순으로 수행됩니다. 유형에 .T:

  • 한다면TPOD입니다(예:int위의 예에서는) 초기화가 수행되지 않습니다.
  • 않으면 그지 경않우, 의기생성의 생성자입니다.T모든 요소를 초기화합니다.
  • 한다면T액세스 가능한 기본값 편집기를 제공하지 않으며 프로그램이 컴파일되지 않습니다.

또는 괄호로 둘러싸인 쉼표로 구분된 목록인 배열 이니셜라이저에서 초기 값을 명시적으로 지정할 수 있습니다.

    int primes[8] = {2, 3, 5, 7, 11, 13, 17, 19};

이 경우 배열 이니셜라이저의 요소 수가 배열 크기와 같기 때문에 크기를 수동으로 지정하는 것은 중복됩니다.컴파일러에서 자동으로 추론할 수 있습니다.

    int primes[] = {2, 3, 5, 7, 11, 13, 17, 19};   // size 8 is deduced

크기를 지정하고 더 짧은 배열 이니셜라이저를 제공할 수도 있습니다.

    int fibonacci[50] = {0, 1, 1};   // 47 trailing zeros are deduced

이 경우 나머지 요소는 0 초기화됩니다.C++은 빈 배열 이니셜라이저(모든 요소가 0 초기화됨)를 허용하는 반면, C89는 허용하지 않습니다(최소 하나의 값이 필요함).또한 어레이 초기화자는 어레이를 초기화하는 데만 사용할 수 있으며 나중에 할당에 사용할 수 없습니다.

정적 배열

배열 세그먼트"에 살고 있는 ")은 정어레이에는다같음어다변적정어로니수입레로 된 로컬 입니다.static네임스페이스 범위의 키워드 및 배열 변수("글로벌 변수"):

int global_static_array[8];

void foo()
{
    static int local_static_array[8];
}

네임스페이스 범위의 변수는 암시적으로 정적입니다. 추가static정의에 대한 키워드는 완전히 다른,이상 사용되지 않는 의미를 가집니다.)

다음은 정적 어레이가 자동 어레이와 다르게 작동하는 방법은 다음과 같습니다.

  • 어레이 이니셜라이저가 없는 정적 어레이는 추가 초기화 전에 제로 초기화됩니다.
  • 정적 POD 어레이는 정확히 한 번 초기화되며, 일반적으로 초기화 값은 실행 파일에 구워집니다. 이 경우 런타임 시 초기화 비용이 들지 않습니다.그러나 이것이 항상 가장 공간 효율적인 솔루션은 아니며 표준에서 요구하는 것도 아닙니다.
  • 정적 비POD 어레이는 제어 흐름이 처음 정의를 통과할 때 초기화됩니다.로컬 정적 배열의 경우, 함수가 호출되지 않으면 이러한 현상이 발생하지 않을 수 있습니다.

위의 내용 중 어레이에만 해당되는 것은 없습니다.이러한 규칙은 다른 종류의 정적 개체에도 동일하게 잘 적용됩니다.)

배열 데이터 멤버

어레이 데이터 멤버는 자신의 소유 개체가 생성될 때 생성됩니다.안타깝게도 C++03은 멤버 이니셜라이저 목록에서 어레이를 초기화할 수 있는 수단을 제공하지 않으므로, 초기화는 다음과 같은 할당으로 위장해야 합니다.

class Foo
{
    int primes[8];

public:

    Foo()
    {
        primes[0] = 2;
        primes[1] = 3;
        primes[2] = 5;
        // ...
    }
};

또는 생성자 본문에서 자동 배열을 정의하고 요소를 복사할 수 있습니다.

class Foo
{
    int primes[8];

public:

    Foo()
    {
        int local_array[] = {2, 3, 5, 7, 11, 13, 17, 19};
        std::copy(local_array + 0, local_array + 8, primes + 0);
    }
};

C++0x에서는 균일초기화 덕분에 멤버 초기화 목록에서 배열을 초기화할 수 있습니다.

class Foo
{
    int primes[8];

public:

    Foo() : primes { 2, 3, 5, 7, 11, 13, 17, 19 }
    {
    }
};

이 솔루션은 기본 생성자가 없는 요소 유형에서 작동하는 유일한 솔루션입니다.

동적 배열

동적 배열에는 이름이 없으므로, 동적 배열에 액세스하는 유일한 방법은 포인터를 통해 액세스하는 것입니다.이름이 없기 때문에 앞으로는 "익명 배열"이라고 부르겠습니다.

은 서익명배다통해생성다니됩음에을을 통해 됩니다.malloc그리고 친구들.은 C++ 익명다사생용성다니됩여를 됩니다.new T[size]익명 배열의 첫 번째 요소로 포인터를 반환하는 구문:

std::size_t size = compute_size_at_runtime();
int* p = new int[size];

다음 ASCII 그림은 런타임에 크기가 8로 계산되는 경우의 메모리 레이아웃을 나타냅니다.

             +---+---+---+---+---+---+---+---+
(anonymous)  |   |   |   |   |   |   |   |   |
             +---+---+---+---+---+---+---+---+
               ^
               |
               |
             +-|-+
          p: | | |                               int*
             +---+

익명 배열은 별도로 저장해야 하는 추가 포인터 때문에 명명된 배열보다 더 많은 메모리가 필요합니다. (무료 저장소에 대한 추가 오버헤드도 있습니다.)

여기에서는 어레이 대 포인터 붕괴가 발생하지 않습니다.평가는 하고 있지만,new int[size]실제로 표현의 결과인 정수 배열을 생성합니까?new int[size]이미 정수 배열 또는 알 수 없는 크기의 정수 배열에 대한 포인터가 아닌 단일 정수(첫 번째 요소)에 대한 포인터입니다.정적 유형 시스템에서는 배열 크기가 컴파일 시간 상수여야 하므로 이는 불가능합니다.(따라서 사진에 정적 유형 정보가 있는 익명 배열에 주석을 달지 않았습니다.)

요소의 기본값과 관련하여 익명 배열은 자동 배열과 유사하게 동작합니다.일반적으로 익명 POD 배열은 초기화되지 않지만 값 초기화를 트리거하는 특수 구문이 있습니다.

int* p = new int[some_computed_size]();

세미콜론 바로 앞에 있는 괄호의 뒤에 있는 쌍을 기록합니다.C++0x는 규칙을 단순화하고 일관된 초기화 덕분에 익명 어레이의 초기 값을 지정할 수 있습니다.

int* p = new int[8] { 2, 3, 5, 7, 11, 13, 17, 19 };

익명 배열 사용을 완료한 경우 시스템에 다시 배포해야 합니다.

delete[] p;

각 익명 배열을 한 번만 정확하게 해제한 다음 이후에는 다시는 해당 배열을 만지지 않아야 합니다.해제하지 않으면 메모리 누수가 발생하고(또는 더 일반적으로 요소 유형에 따라 리소스 누출), 해제를 여러 번 시도하면 정의되지 않은 동작이 발생합니다.가 아닌 형식 delete(또는)free delete[]배열을 해제하는 것도 정의되지 않은 동작입니다.

언급URL : https://stackoverflow.com/questions/4810664/how-do-i-use-arrays-in-c

반응형