질문 : C ++에서 배열을 어떻게 사용합니까?
거의 모든 곳에서 사용되는 C에서 C ++ 상속 배열. C ++는 사용하기 쉽고 오류가 덜 발생하는 추상화를 제공하므로 ( std::vector<T>
C ++ 98 이후 및 std::array<T, n>
C ++ 11 이후) 배열이 필요하지 않습니다. C 에서처럼 자주 발생합니다. 그러나 레거시 코드를 읽거나 C로 작성된 라이브러리와 상호 작용할 때 배열의 작동 방식을 확실히 파악해야합니다.
이 FAQ는 다섯 부분으로 나뉩니다.
- 유형 수준의 배열 및 요소 액세스
- 배열 생성 및 초기화
- 할당 및 매개 변수 전달
- 다차원 배열 및 포인터 배열
- 배열 사용 시 일반적인 함정
이 FAQ에서 중요한 것이 누락되었다고 생각되면 답변을 작성하고 여기에 추가 부분으로 링크하십시오.
다음 텍스트에서 "array"는 클래스 템플릿 std::array
아니라 "C 배열"을 의미합니다. C 선언자 구문에 대한 기본 지식이 있다고 가정합니다. new
및 delete
의 수동 사용은 예외에 직면하여 매우 위험하지만 다른 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
'프로그래밍 언어 > C++' 카테고리의 다른 글
최신 C ++ 11 / C ++ 14 / C ++ 17 및 향후 C ++ 20에서 열거 형 문자열 (0) | 2021.06.27 |
---|---|
C ++에서 'struct'와 'typedef struct'의 차이점 (0) | 2021.06.26 |
C ++에서 문자열을 토큰화 하는 방법 (0) | 2021.06.23 |
dash를 제외한 문자열에서 영숫자가 아닌 모든 문자를 제거하는 방법 (0) | 2021.06.17 |
C ++에서 클래스와 구조체 사용시기 (0) | 2021.06.15 |