배열 (C++)

배열은 연속된 메모리 영역을 차지하는 동일한 형식의 개체 시퀀스입니다. 기존 C 스타일 배열은 많은 버그의 소스이지만, 특히 이전 코드 베이스에서 여전히 일반적입니다. 최신 C++에서는 이 섹션에 설명된 C 스타일 배열 대신 std::vector 또는 std::array 를 사용하는 것이 좋습니다. 이러한 두 표준 라이브러리 형식 모두 해당 요소를 연속 메모리 블록으로 저장합니다. 그러나 훨씬 더 큰 형식 안전성을 제공하고 시퀀스 내에서 유효한 위치를 가리키도록 보장되는 반복기를 지원합니다. 자세한 내용은 컨테이너를 참조하세요.

스택 선언

C++ 배열 선언에서 배열 크기는 다른 언어와 같이 형식 이름 뒤가 아니라 변수 이름 다음에 지정됩니다. 다음 예제에서는 스택에 할당할 1000 double 배열을 선언합니다. 요소 수는 정수 리터럴로 제공되거나 상수 식으로 제공되어야 합니다. 컴파일러가 할당할 스택 공간의 양을 알고 있기 때문입니다. 런타임에 계산된 값을 사용할 수 없습니다. 배열의 각 요소에는 기본값 0이 할당됩니다. 기본값을 할당하지 않으면 각 요소에는 처음에 해당 메모리 위치에 임의의 값이 포함됩니다.

    constexpr size_t size = 1000;

    // Declare an array of doubles to be allocated on the stack
    double numbers[size] {0};

    // Assign a new value to the first element
    numbers[0] = 1;

    // Assign a value to each subsequent element
    // (numbers[1] is the second element in the array.)
    for (size_t i = 1; i < size; i++)
    {
        numbers[i] = numbers[i-1] * 1.1;
    }

    // Access each element
    for (size_t i = 0; i < size; i++)
    {
        std::cout << numbers[i] << " ";
    }

배열의 첫 번째 요소는 0번째 요소입니다. 마지막 요소는 (n-1) 요소입니다. 여기서 n 은 배열에 포함될 수 있는 요소의 수입니다. 선언의 요소 수는 정수 형식이어야 하며 0보다 커야 합니다. 프로그램에서 보다 큰 (size - 1)아래 첨자 연산자에 값을 전달하지 않도록 하는 것은 사용자의 책임입니다.

크기가 0인 배열은 배열이 마지막 필드 struct 이거나 union Microsoft 확장이 활성화되거나/Za/permissive- 설정되지 않은 경우에만 유효합니다.

스택 기반 배열은 힙 기반 배열보다 더 빠르게 할당하고 액세스할 수 있습니다. 그러나 스택 공간은 제한됩니다. 배열 요소의 수는 너무 커서 스택 메모리를 너무 많이 사용할 수 없습니다. 얼마나 많은 프로그램에 따라 달라 집니다. 프로파일링 도구를 사용하여 배열이 너무 큰지 여부를 확인할 수 있습니다.

힙 선언

스택에 할당하기에 너무 크거나 컴파일 시간에 크기를 알 수 없는 배열이 필요할 수 있습니다. 식을 사용하여 힙에 이 배열을 할당할 수 있습니다 new[] . 연산자는 첫 번째 요소에 대한 포인터를 반환합니다. 아래 첨자 연산자는 스택 기반 배열에서와 동일한 방식으로 포인터 변수에서 작동합니다. 포인터 산술 연산을 사용하여 포인터를 배열의 임의의 요소로 이동할 수도 있습니다. 다음을 보장하는 것은 사용자의 책임입니다.

  • 항상 원래 포인터 주소의 복사본을 유지하므로 배열이 더 이상 필요하지 않을 때 메모리를 삭제할 수 있습니다.
  • 배열 범위를 지나 포인터 주소를 증가 또는 감소하지 않습니다.

다음 예제에서는 런타임에 힙에 배열을 정의하는 방법을 보여줍니다. 아래 첨자 연산자를 사용하고 포인터 산술 연산을 사용하여 배열 요소에 액세스하는 방법을 보여줍니다.

void do_something(size_t size)
{
    // Declare an array of doubles to be allocated on the heap
    double* numbers = new double[size]{ 0 };

    // Assign a new value to the first element
    numbers[0] = 1;

    // Assign a value to each subsequent element
    // (numbers[1] is the second element in the array.)
    for (size_t i = 1; i < size; i++)
    {
        numbers[i] = numbers[i - 1] * 1.1;
    }

    // Access each element with subscript operator
    for (size_t i = 0; i < size; i++)
    {
        std::cout << numbers[i] << " ";
    }

    // Access each element with pointer arithmetic
    // Use a copy of the pointer for iterating
    double* p = numbers;

    for (size_t i = 0; i < size; i++)
    {
        // Dereference the pointer, then increment it
        std::cout << *p++ << " ";
    }

    // Alternate method:
    // Reset p to numbers[0]:
    p = numbers;

    // Use address of pointer to compute bounds.
    // The compiler computes size as the number
    // of elements * (bytes per element).
    while (p < (numbers + size))
    {
        // Dereference the pointer, then increment it
        std::cout << *p++ << " ";
    }

    delete[] numbers; // don't forget to do this!

}
int main()
{
    do_something(108);
}

배열 초기화

루프, 한 번에 하나의 요소 또는 단일 문으로 배열을 초기화할 수 있습니다. 다음 두 배열의 내용은 동일합니다.

    int a[10];
    for (int i = 0; i < 10; ++i)
    {
        a[i] = i + 1;
    }

    int b[10]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

함수에 배열 전달

배열이 함수에 전달되면 스택 기반 배열이든 힙 기반 배열이든 관계없이 첫 번째 요소에 대한 포인터로 전달됩니다. 포인터에는 추가 크기 또는 형식 정보가 없습니다. 이 동작을 포인터 감쇠라고 합니다. 배열을 함수에 전달할 때는 항상 별도의 매개 변수의 요소 수를 지정해야 합니다. 또한 이 동작은 배열이 함수에 전달될 때 배열 요소가 복사되지 않음을 의미합니다. 함수가 요소를 수정하지 못하도록 하려면 매개 변수를 요소에 대한 포인터 const 로 지정합니다.

다음 예제에서는 배열 및 길이를 허용하는 함수를 보여 줍니다. 포인터는 복사본이 아닌 원래 배열을 가리킵니다. 매개 변수가 아니 const므로 함수는 배열 요소를 수정할 수 있습니다.

void process(double *p, const size_t len)
{
    std::cout << "process:\n";
    for (size_t i = 0; i < len; ++i)
    {
        // do something with p[i]
    }
}

배열 매개 변수 p 를 선언하고 정의하여 함수 블록 내에서 읽기 전용으로 const 만듭니다.

void process(const double *p, const size_t len);

동작이 변경되지 않고 이러한 방식으로 동일한 함수를 선언할 수도 있습니다. 배열은 여전히 첫 번째 요소에 대한 포인터로 전달됩니다.

// Unsized array
void process(const double p[], const size_t len);

// Fixed-size array. Length must still be specified explicitly.
void process(const double p[1000], const size_t len);

다차원 배열

다른 배열에서 생성된 배열은 다차원 배열입니다. 이러한 다차원 배열은 여러 개의 대괄호로 묶인 상수 식을 순서대로 배치하여 지정됩니다. 예를 들어 다음 선언을 생각해 볼 수 있습니다.

int i2[5][7];

다음 그림과 같이 5개 행과 7개의 열로 구성된 2차원 행렬에 개념적으로 정렬된 형식 int배열을 지정합니다.

Conceptual layout of a multi dimensional array.
다차원 배열의 개념적 레이아웃

이니셜라이저에 설명된 대로 이니셜라이저 목록이 있는 다차원 배열을 선언할 수 있습니다. 이러한 선언에서 첫 번째 차원의 경계를 지정하는 상수 식을 생략할 수 있습니다. 예를 들면 다음과 같습니다.

// arrays2.cpp
// compile with: /c
const int cMarkets = 4;
// Declare a float that represents the transportation costs.
double TransportCosts[][cMarkets] = {
   { 32.19, 47.29, 31.99, 19.11 },
   { 11.29, 22.49, 33.47, 17.29 },
   { 41.97, 22.09,  9.76, 22.55 }
};

앞의 선언은 세 개의 행과 네 개의 열로 구성된 배열을 정의합니다. 행은 공장을 나타내고 열은 공장에서 출하되는 시장을 나타냅니다. 값은 공장에서 시장까지의 운송 비용입니다. 배열의 첫 번째 차원은 생략되었지만 컴파일러가 이니셜라이저를 검사하여 해당 차원을 채웁니다.

n차원 배열 형식에서 간접 참조 연산자(*)를 사용하면 n-1 차원 배열이 생성됩니다. n이 1인 경우 스칼라(또는 배열 요소)가 생성됩니다.

C++ 배열은 행 중심 순서로 저장됩니다. 행 중심 순서는 마지막 첨자가 가장 빠르게 변경됨을 의미합니다.

예제

다음과 같이 함수 선언에서 다차원 배열의 첫 번째 차원에 대한 경계 사양을 생략할 수도 있습니다.

// multidimensional_arrays.cpp
// compile with: /EHsc
// arguments: 3
#include <limits>   // Includes DBL_MAX
#include <iostream>

const int cMkts = 4, cFacts = 2;

// Declare a float that represents the transportation costs
double TransportCosts[][cMkts] = {
   { 32.19, 47.29, 31.99, 19.11 },
   { 11.29, 22.49, 33.47, 17.29 },
   { 41.97, 22.09,  9.76, 22.55 }
};

// Calculate size of unspecified dimension
const int cFactories = sizeof TransportCosts /
                  sizeof( double[cMkts] );

double FindMinToMkt( int Mkt, double myTransportCosts[][cMkts], int mycFacts);

using namespace std;

int main( int argc, char *argv[] ) {
   double MinCost;

   if (argv[1] == 0) {
      cout << "You must specify the number of markets." << endl;
      exit(0);
   }
   MinCost = FindMinToMkt( *argv[1] - '0', TransportCosts, cFacts);
   cout << "The minimum cost to Market " << argv[1] << " is: "
       << MinCost << "\n";
}

double FindMinToMkt(int Mkt, double myTransportCosts[][cMkts], int mycFacts) {
   double MinCost = DBL_MAX;

   for( size_t i = 0; i < cFacts; ++i )
      MinCost = (MinCost < TransportCosts[i][Mkt]) ?
         MinCost : TransportCosts[i][Mkt];

   return MinCost;
}
The minimum cost to Market 3 is: 17.29

이 함수 FindMinToMkt 는 새 팩터리를 추가해도 코드 변경이 필요하지 않고 다시 컴파일하기만 하면 되도록 작성됩니다.

배열 초기화

클래스 생성자가 있는 개체의 배열은 생성자에 의해 초기화됩니다. 배열의 요소보다 이니셜라이저 목록에 항목이 적으면 나머지 요소에 기본 생성자가 사용됩니다. 클래스에 대해 정의된 기본 생성자가 없는 경우 이니셜라이저 목록이 완료되어야 합니다. 즉, 배열의 각 요소에 대해 하나의 이니셜라이저가 있어야 합니다.

생성자 두 개를 정의하는 Point 클래스를 살펴보세요.

// initializing_arrays1.cpp
class Point
{
public:
   Point()   // Default constructor.
   {
   }
   Point( int, int )   // Construct from two ints
   {
   }
};

// An array of Point objects can be declared as follows:
Point aPoint[3] = {
   Point( 3, 3 )     // Use int, int constructor.
};

int main()
{
}

aPoint의 첫 번째 요소는 Point( int, int ) 생성자를 사용하여 생성되고 나머지 두 요소는 기본 생성자를 사용하여 생성됩니다.

정적 멤버 배열(클래스 선언 외부)의 정의에서 초기화할 수 있는지 여부 const 입니다. 예를 들면 다음과 같습니다.

// initializing_arrays2.cpp
class WindowColors
{
public:
    static const char *rgszWindowPartList[7];
};

const char *WindowColors::rgszWindowPartList[7] = {
    "Active Title Bar", "Inactive Title Bar", "Title Bar Text",
    "Menu Bar", "Menu Bar Text", "Window Background", "Frame"   };
int main()
{
}

배열 요소 액세스

배열 첨자 연산자([ ])를 사용하여 배열의 개별 요소에 액세스할 수 있습니다. 아래 첨자 없이 1차원 배열의 이름을 사용하는 경우 배열의 첫 번째 요소에 대한 포인터로 평가됩니다.

// using_arrays.cpp
int main() {
   char chArray[10];
   char *pch = chArray;   // Evaluates to a pointer to the first element.
   char   ch = chArray[0];   // Evaluates to the value of the first element.
   ch = chArray[3];   // Evaluates to the value of the fourth element.
}

다차원 배열을 사용할 때 식에서 다양한 조합을 사용할 수 있습니다.

// using_arrays_2.cpp
// compile with: /EHsc /W1
#include <iostream>
using namespace std;
int main() {
   double multi[4][4][3];   // Declare the array.
   double (*p2multi)[3];
   double (*p1multi);

   cout << multi[3][2][2] << "\n";   // C4700 Use three subscripts.
   p2multi = multi[3];               // Make p2multi point to
                                     // fourth "plane" of multi.
   p1multi = multi[3][2];            // Make p1multi point to
                                     // fourth plane, third row
                                     // of multi.
}

위의 코드 multi 에서 형식 double의 3차원 배열입니다. 포인터는 p2multi 크기 3 형식 double 의 배열을 가리킵니다. 이 예제에서 배열은 한 개, 두 개 및 세 개의 아래 첨자와 함께 사용됩니다. 문에서처럼 모든 아래 첨자를 지정하는 것이 더 일반적이지만 다음 문에 cout 표시된 것처럼 배열 요소의 특정 하위 집합을 선택하는 것이 cout유용한 경우도 있습니다.

아래 첨자 연산자 오버로드

다른 연산자처럼 사용자가 아래 첨자 연산자([])를 다시 정의할 수 있습니다. 첨자 연산자의 기본 동작은 다음 메서드를 사용하여 배열 이름과 첨자를 결합하는 것입니다.

*((array_name) + (subscript))

포인터 형식을 포함하는 모든 추가와 마찬가지로 크기 조정은 형식의 크기에 맞게 조정되도록 자동으로 수행됩니다. 결과 값은 원본의 n 바이트 array_name가 아니라 배열의 n번째 요소입니다. 이 변환에 대한 자세한 내용은 추가 연산자를 참조하세요.

다차원 배열에서도 다음 메서드를 사용하여 주소가 파생됩니다.

((array_name) + (subscript1 * max2 * max3 * ... * maxn) + (subscript2 * max3 * ... * maxn) + ... + subscriptn))

식의 배열

배열 형식의 식별자가 , address-of(&) 또는 참조 초기화 이외의 sizeof식에 나타나면 첫 번째 배열 요소에 대한 포인터로 변환됩니다. 예를 들면 다음과 같습니다.

char szError1[] = "Error: Disk drive not ready.";
char *psz = szError1;

psz 포인터는 szError1 배열의 첫 번째 요소를 가리킵니다. 배열은 포인터와 달리 수정 가능한 l-값이 아닙니다. 따라서 다음 할당이 잘못되었습니다.

szError1 = psz;

참고 항목

std::array