프로그래밍 언어/C++

C ++에서 배열 사용 방법

Rateye 2021. 6. 25. 10:46
728x90
반응형
질문 : C ++에서 배열을 어떻게 사용합니까?

거의 모든 곳에서 사용되는 C에서 C ++ 상속 배열. C ++는 사용하기 쉽고 오류가 덜 발생하는 추상화를 제공하므로 ( std::vector<T> C ++ 98 이후 및 std::array<T, n> C ++ 11 이후) 배열이 필요하지 않습니다. C 에서처럼 자주 발생합니다. 그러나 레거시 코드를 읽거나 C로 작성된 라이브러리와 상호 작용할 때 배열의 작동 방식을 확실히 파악해야합니다.

이 FAQ는 다섯 부분으로 나뉩니다.

  1. 유형 수준의 배열 및 요소 액세스
  2. 배열 생성 및 초기화
  3. 할당 및 매개 변수 전달
  4. 다차원 배열 및 포인터 배열
  5. 배열 사용 시 일반적인 함정

이 FAQ에서 중요한 것이 누락되었다고 생각되면 답변을 작성하고 여기에 추가 부분으로 링크하십시오.

다음 텍스트에서 "array"는 클래스 템플릿 std::array 아니라 "C 배열"을 의미합니다. C 선언자 구문에 대한 기본 지식이 있다고 가정합니다. newdelete 의 수동 사용은 예외에 직면하여 매우 위험하지만 다른 FAQ 의 주제입니다.

답변

배열 유형은 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* 로 변환 될 수 있으며이 변환의 결과는 배열의 첫 번째 요소에 대한 포인터입니다. T* 가 필요한 모든 곳에서 T[n] 제공 할 수 있으며 컴파일러는 해당 포인터를 자동으로 제공합니다.

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

이 변환은 "배열-포인터 감쇠"로 알려져 있으며 혼란의 주요 원인입니다. 배열의 크기는 더 이상 유형 ( T* )의 일부가 아니므로이 프로세스에서 손실됩니다. 장점 : 유형 수준에서 배열의 크기를 잊어 버리면 포인터가 모든 크기의 배열의 첫 번째 요소를 가리킬 수 있습니다. 단점 : 배열의 첫 번째 (또는 다른) 요소에 대한 포인터가 주어지면 해당 배열이 얼마나 큰지 또는 배열의 경계를 기준으로 포인터가 정확히 가리키는 위치를 감지 할 방법이 없습니다. 포인터는 매우 어리 석습니다 .

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

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] int* 유형의 각 요소 인 8 개의 포인터 배열입니다.

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*
   +---+

설명 된 경우 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] 유형의 배열에는 0 부터 n-1 까지 인덱싱 된 n 요소가 있습니다. n 요소가 없습니다. 그러나 반 개방 범위 (시작은 포함 하고 끝은 제외 )를 지원하기 위해 C ++는 (존재하지 않는) n 번째 요소에 대한 포인터 계산을 허용하지만 해당 포인터를 역 참조하는 것은 불법입니다.

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

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

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

&x[n] 을 두 번째 인수로 제공 &*(x+n) 과 동일하고 하위 표현식 *(x+n) 기술적 으로 C ++에서 정의되지 않은 동작 을 호출하므로 C99에서는 제공되지 않습니다. ).

x 를 첫 번째 인수로 제공 할 수도 있습니다. 그것은 내 취향에 비해 너무 간결하며 컴파일러에서 템플릿 인수 추론을 조금 더 어렵게 만듭니다.이 경우 첫 번째 인수는 배열이지만 두 번째 인수는 포인터이기 때문입니다. (다시, 어레이-포인터 붕괴가 시작됩니다.)

출처 : https://stackoverflow.com/questions/4810664/how-do-i-use-arrays-in-c
728x90
반응형