Array declaration
배열 타입의 객체를 선언합니다.
목차 |
구문
배열 선언은 선언자 가 다음과 같은 형태를 갖는 모든 단순 선언입니다
noptr-declarator
[
expr
(선택 사항)
]
attr
(선택 사항)
|
|||||||||
| noptr-declarator | - |
모든 유효한
declarator
이지만, 만약
*
,
&
, 또는
&&
로 시작하는 경우 괄호로 둘러싸여야 함 (그렇지 않으면 전체 선언자가
pointer declarator
또는
reference declarator
로 처리됨).
|
| expr | - | integral constant expression (C++14 이전) converted constant expression of type std::size_t (C++14 이후) , 0보다 큰 값으로 평가됨 |
| attr | - | (C++11 이후) attributes 목록 |
T a
[
N
]
;
형태의 선언은
a
를
T
타입의 객체
N
개가 연속적으로 할당된 배열
객체
로 선언합니다. 배열의 요소는
0
부터 …,
N
-
1
까지 번호가 매겨지며,
첨자 연산자 []
를 사용하여
a
[
0
]
, …,
a
[
N
-
1
]
와 같이 접근할 수 있습니다.
배열은 모든 기본 타입 ( void 제외), 포인터 , 멤버 포인터 , 클래스 , 열거형 , 또는 알려진 경계를 가진 다른 배열(이 경우 배열을 다차원 배열이라고 함)로부터 생성될 수 있습니다. 다시 말해, 알려지지 않은 경계를 가진 배열 타입을 제외한 객체 타입만이 배열 타입의 요소 타입이 될 수 있습니다. 불완전한 요소 타입을 가진 배열 타입 또한 불완전한 타입입니다.
|
가능성 있는
제약 조건
(C++20부터)
|
(C++11부터) |
참조 배열이나 함수 배열은 존재하지 않습니다.
배열 타입에 cv-qualifiers 를 적용하면(typedef나 템플릿 타입 조작을 통해) 한정자가 요소 타입에 적용되지만, 요소가 cv-qualified 타입인 모든 배열 타입은 동일한 cv-qualification을 가진 것으로 간주됩니다.
// a와 b는 동일한 const-qualified 타입 "5개의 const char 배열"을 가짐 typedef const char CC; CC a[5] = {}; typedef char CA[5]; const CA b = {};
new[]-표현식 과 함께 사용될 때, 배열의 크기는 0일 수 있습니다; 이러한 배열은 요소를 가지지 않습니다:
int* p = new int[0]; // p[0] 또는 *p에 접근하는 것은 정의되지 않은 동작입니다 delete[] p; // 여전히 정리 작업이 필요합니다
할당
배열 타입의 객체는 전체적으로 수정될 수 없습니다: 비록 그것들이 lvalues (예: 배열의 주소를 취할 수 있음)일지라도, 대입 연산자의 좌변에 나타날 수 없습니다:
int a[3] = {1, 2, 3}, b[3] = {4, 5, 6}; int (*p)[3] = &a; // 정상: a의 주소를 취할 수 있음 a = b; // 오류: a는 배열임 struct { int c[3]; } s1, s2 = {3, 4, 5}; s1 = s2; // 정상: 암시적으로 정의된 복사 할당 연산자가 // 배열 타입의 데이터 멤버를 할당할 수 있음
배열-포인터 변환
배열 타입의 좌측값과 우측값에서 포인터 타입의 우측값으로 암시적 변환 이 존재합니다: 이는 배열의 첫 번째 요소를 가리키는 포인터를 생성합니다. 이 변환은 배열이 예상되지 않지만 포인터가 예상되는 컨텍스트에서 배열이 나타날 때마다 사용됩니다:
#include <iostream> #include <iterator> #include <numeric> void g(int (&a)[3]) { std::cout << a[0] << '\n'; } void f(int* p) { std::cout << *p << '\n'; } int main() { int a[3] = {1, 2, 3}; int* p = a; std::cout << sizeof a << '\n' // 배열의 크기를 출력 << sizeof p << '\n'; // 포인터의 크기를 출력 // 배열은 허용되지만 포인터는 허용되지 않는 경우, 배열만 사용 가능 g(a); // 정상: 함수가 참조로 배열을 받음 // g(p); // 오류 for (int n : a) // 정상: 범위 기반 for 루프에서 배열 사용 가능 std::cout << n << ' '; // 배열의 요소들을 출력 // for (int n : p) // 오류 // std::cout << n << ' '; std::iota(std::begin(a), std::end(a), 7); // 정상: begin과 end는 배열을 받음 // std::iota(std::begin(p), std::end(p), 7); // 오류 // 포인터는 허용되지만 배열은 허용되지 않는 경우, 둘 다 사용 가능: f(a); // 정상: 함수가 포인터를 받음 f(p); // 정상: 함수가 포인터를 받음 std::cout << *a << '\n' // 첫 번째 요소 출력 << *p << '\n' // 동일 << *(a + 1) << ' ' << a[1] << '\n' // 두 번째 요소 출력 << *(p + 1) << ' ' << p[1] << '\n'; // 동일 }
다차원 배열
배열의 원소 타입이 다른 배열일 때, 그 배열을 다차원 배열이라고 합니다:
// 2개의 배열로 이루어진 배열, 각 배열은 3개의 int를 가짐 int a[2][3] = {{1, 2, 3}, // 2 × 3 행렬로 볼 수 있음 {4, 5, 6}}; // 행 우선 배치 방식
배열-포인터 변환(decay)이 적용될 때, 다차원 배열은 첫 번째 요소(예: 첫 번째 행이나 첫 번째 평면)에 대한 포인터로 변환된다는 점에 유의하십시오: 배열-포인터 변환은 한 번만 적용됩니다.
int a[2]; // 2개의 int를 가지는 배열 int* p1 = a; // a는 a의 첫 번째 요소를 가리키는 포인터로 decay됨 int b[2][3]; // 3개의 int를 가지는 2개의 배열의 배열 // int** p2 = b; // 오류: b는 int**로 decay되지 않음 int (*p2)[3] = b; // b는 b의 첫 번째 3-요소 행을 가리키는 포인터로 decay됨 int c[2][3][4]; // 4개의 int를 가지는 3개의 배열의 2개의 배열의 배열 // int*** p3 = c; // 오류: c는 int***로 decay되지 않음 int (*p3)[3][4] = c; // c는 c의 첫 번째 3 × 4-요소 평면을 가리키는 포인터로 decay됨
경계를 알 수 없는 배열
만약 expr 이 배열 선언에서 생략되면, 선언된 타입은 "T의 알려지지 않은 경계의 배열"이며, 이는 일종의 불완전 타입 입니다. 단, 집합체 초기화 와 함께 사용되는 선언에서는 예외입니다:
extern int x[]; // x의 타입은 "크기를 알 수 없는 int 배열"입니다 int a[] = {1, 2, 3}; // a의 타입은 "3개의 int 배열"입니다
배열 요소가 알려지지 않은 경계의 배열일 수 없기 때문에, 다차원 배열은 첫 번째 차원을 제외한 다른 차원에서 알려지지 않은 경계를 가질 수 없습니다:
extern int a[][2]; // 정상: 알 수 없는 경계를 가진 2개 int 배열의 배열 extern int b[2][]; // 오류: 배열이 불완전한 요소 타입을 가짐
동일한 범위 내에서 해당 개체의 선행 선언이 있고 그 범위에서 경계가 지정된 경우, 생략된 배열 경계는 이전 선언에서와 동일하게 간주되며, 이는 클래스의 정적 데이터 멤버 정의에도 유사하게 적용됩니다:
extern int x[10]; struct S { static int y[10]; }; int x[]; // OK: 경계는 10 int S::y[]; // OK: 경계는 10 void f() { extern int x[]; int i = sizeof(x); // 오류: 불완전한 객체 타입 }
알려지지 않은 경계를 가진 배열에 대한 참조자와 포인터는 형성될 수 있으나, 초기화하거나 할당할 수 없습니다 (C++20 이전) 초기화하거나 할당할 수 있습니다 (C++20 이후) 알려진 경계를 가진 배열 및 배열 포인터로부터. C 프로그래밍 언어에서는 알려지지 않은 경계를 가진 배열에 대한 포인터가 알려진 경계를 가진 배열에 대한 포인터와 호환되므로, 양방향으로 변환 및 할당이 가능합니다.
extern int a1[]; int (&r1)[] = a1; // 정상 int (*p1)[] = &a1; // 정상 int (*q)[2] = &a1; // 오류 (C에서는 정상) int a2[] = {1, 2, 3}; int (&r2)[] = a2; // 정상 (C++20부터) int (*p2)[] = &a2; // 정상 (C++20부터)
알 수 없는 경계를 가진 배열에 대한 포인터는 포인터 산술 에 참여할 수 없으며 첨자 연산자 의 왼쪽에 사용될 수 없지만, 역참조는 가능합니다.
배열 rvalues
배열은 함수에서 값으로 반환될 수 없으며 대부분의 캐스트 표현식의 대상이 될 수 없지만, 배열 prvalue 는 타입 별칭을 사용하여 brace-initialized functional cast 를 통해 배열 임시 객체를 구성함으로써 형성될 수 있습니다.
|
클래스 prvalue와 마찬가지로, 배열 prvalue는 평가 시 temporary materialization 을 통해 xvalue로 변환됩니다. |
(since C++17) |
배열 xvalue 는 클래스 rvalue의 배열 멤버에 직접 접근하거나 std::move 를 사용하거나 rvalue 참조를 반환하는 다른 캐스트나 함수 호출을 통해 직접 형성될 수 있습니다.
#include <iostream> #include <type_traits> #include <utility> void f(int (&&x)[2][3]) { std::cout << sizeof x << '\n'; } struct X { int i[2][3]; } x; template<typename T> using identity = T; int main() { std::cout << sizeof X().i << '\n'; // 배열의 크기 f(X().i); // 정상: xvalue에 바인딩 // f(x.i); // 오류: lvalue에 바인딩할 수 없음 int a[2][3]; f(std::move(a)); // 정상: xvalue에 바인딩 using arr_t = int[2][3]; f(arr_t{}); // 정상: prvalue에 바인딩 f(identity<int[][3]>{{1, 2, 3}, {4, 5, 6}}); // 정상: prvalue에 바인딩 }
출력:
24 24 24 24 24
결함 보고서
다음의 동작 변경 결함 보고서들은 이전에 발표된 C++ 표준에 소급 적용되었습니다.
| DR | 적용 대상 | 게시된 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 393 | C++98 |
알려지지 않은 경계를 가진 배열에 대한 포인터나 참조를
함수 매개변수로 사용할 수 없었음 |
허용됨 |
| CWG 619 | C++98 |
생략된 경우 배열의 경계를 이전 선언으로부터
추론할 수 없었음 |
추론 허용됨 |
| CWG 2099 | C++98 |
배열 정적 데이터 멤버의 경계는 초기화자가 제공되더라도
생략할 수 없었음 |
생략 허용됨 |
| CWG 2397 | C++11 | auto 를 요소 타입으로 사용할 수 없었음 | 허용됨 |
참고 항목
|
C documentation
for
Array declaration
|