본문 바로가기
Game DevTip/C++

12. C++ 배열에 대해서

by LIKE IT.라이킷 2025. 7. 16.

배열이란?

배열은 동일한 데이터 타입의 값들을 순차적으로 저장하는 메모리 블록이다. 

 

- 모든 원소는 연속된 메모리 공간에 저장된다. 

- 정적크기 : 크기를 고정해야 하며, 실행중 변경할 수 없다.

- 인덱스는 0부터 시작한다.(항상)

- 동일 타입 : 모든 원소는 같은 데이터 타입을 가져야 한다. 

- 인덱스 접근 : O(1) 시간에 임의의 원소에 접근이 가능하다. 

 

배열 선언

#include <iostream>
using namespace std;

int main() {
    // 1. 크기만 지정 (초기화 X - 쓰레기 값)
    int scores[5];

    // 2. 선언과 동시에 초기화
    int values[3] = {1, 2, 3};

    // 3. 크기 생략 가능 (자동 결정)
    float rates[] = {0.5, 0.7};  // 크기 2

    // 4. 부분 초기화 (나머지는 0으로 초기화)
    int arr[5] = {10, 20};  // {10, 20, 0, 0, 0}

    // 5. 모든 원소를 0으로 초기화
    int zeros[5] = {0};     // {0, 0, 0, 0, 0}
    int empty[5] = {};      // C++11 이후, 모든 원소 0으로 초기화

    return 0;
}

 

배열 요소 접근 

배열 요소는 인덱스를 통해서 접근합니다. 

#include <iostream>
using namespace std;

int main() {
    int scores[5] = {85, 92, 78, 96, 88};

    // 요소 접근
    scores[0] = 100;       // 첫 번째 요소에 값 저장
    int x = scores[2];     // 세 번째 요소 값 읽기 (78)

    cout << "첫 번째 점수: " << scores[0] << endl;    // 100
    cout << "세 번째 점수: " << x << endl;           // 78

    // 배열 전체 출력
    for (int i = 0; i < 5; i++) {
        cout << "scores[" << i << "] = " << scores[i] << endl;
    }

    return 0;
}

 

중요한 점은 scores[i]는 *(scores + i)와 같습니다. 

 

 

배열의 메모리 구조 

int arr[5] = {10, 20, 30, 40, 50};

 

인덱스값주소 (예시)

0 10 0x1000
1 20 0x1004
2 30 0x1008
3 40 0x100C
4 50 0x1010
  • int형이 4바이트이므로 주소가 4씩 증가
  • 메모리상에 연속적으로 배치됨
#include <iostream>
using namespace std;

int main() {
    int arr[5] = {10, 20, 30, 40, 50};

    cout << "배열 원소들의 메모리 주소:" << endl;
    for (int i = 0; i < 5; i++) {
        cout << "arr[" << i << "] 주소: " << &arr[i]
             << ", 값: " << arr[i] << endl;
    }

    // 주소 차이 확인
    cout << "주소 차이: " << (&arr[1] - &arr[0]) << " 개 (4 bytes)" << endl;

    return 0;
}

 

 

 

배열의 메모리 구조

#include <iostream>
using namespace std;

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

    // 배열 크기 계산
    int size = sizeof(arr) / sizeof(arr[0]);

    cout << "배열 크기: " << size << endl;                    // 8
    cout << "전체 배열 크기: " << sizeof(arr) << " bytes" << endl;      // 32
    cout << "한 원소 크기: " << sizeof(arr[0]) << " bytes" << endl;    // 4

    return 0;
}

 

공식: sizeof(arr) / sizeof(arr[0])

  • sizeof(arr): 배열 전체 크기 (바이트 단위)
  • sizeof(arr[0]): 한 요소의 크기

 

배열 이름과 포인터의 관계

arr 배열의 첫 번째 요소 주소
&arr[0] 첫 번째 요소 주소
*arr 첫 번째 요소 값
arr[i] *(arr + i)와 동일

 

#include <iostream>
using namespace std;

int main() {
    int arr[3] = {1, 2, 3};
    int* ptr = arr;  // arr은 &arr[0]과 같음

    // 주소 비교
    cout << "arr: " << arr << endl;
    cout << "&arr[0]: " << &arr[0] << endl;
    cout << "ptr: " << ptr << endl;
    cout << "세 주소가 모두 같음!" << endl << endl;

    // 동일한 접근 방법들
    cout << "arr[2] = " << arr[2] << endl;
    cout << "*(arr + 2) = " << *(arr + 2) << endl;
    cout << "ptr[2] = " << ptr[2] << endl;
    cout << "*(ptr + 2) = " << *(ptr + 2) << endl;
    cout << "모든 값이 같음!" << endl;

    return 0;
}

 

 

 

배열 이름과 포인터의 차이점

메모리 주소 변경 불가능 (상수) 가능 (ptr++ 가능)
크기 정보 전체 배열 크기 계산 가능 크기 정보 없음
할당 시점 컴파일 시 (정적 할당) 실행 시 (동적 할당 가능)
연산 가능 여부 산술 연산 불가 산술 연산 가능
#include <iostream>
using namespace std;

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int* ptr = arr;

    // 포인터 변경 가능
    ptr = ptr + 1;  // 가능: 두 번째 원소를 가리킴
    cout << "*ptr = " << *ptr << endl;  // 2

    // 배열 이름 변경 불가능
    // arr = arr + 1;  // 오류! 배열 이름은 상수

    // 포인터 산술 연산
    ptr++;  // 세 번째 원소로 이동
    cout << "*ptr = " << *ptr << endl;  // 3

    ptr--;  // 다시 두 번째 원소로
    cout << "*ptr = " << *ptr << endl;  // 2

    return 0;
}

 

 

 

크기 비교 예시

#include <iostream>
using namespace std;

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int* ptr = arr;

    cout << "sizeof(arr): " << sizeof(arr) << endl;  // 20 (5 * 4)
    cout << "sizeof(ptr): " << sizeof(ptr) << endl;  // 8 (64비트 시스템 기준)

    cout << "배열 원소 개수: " << sizeof(arr) / sizeof(arr[0]) << endl;  // 5
    // cout << sizeof(ptr) / sizeof(ptr[0]);  // 오류! 포인터로는 크기 계산 불가

    return 0;
}

중요:

  • sizeof(arr)는 배열 전체 크기를 바이트 단위로 반환
  • sizeof(ptr)는 포인터 변수 자체의 크기 (주소값 저장 공간)

 

배열과 포인터의 관계

선언 예 int arr[3] int* ptr
저장 대상 여러 값 (정적 메모리) 한 주소값 (동적도 가능)
값 접근 arr[i], *(arr + i) ptr[i], *(ptr + i)
주소 연산 불가능 (arr++ 불가) 가능 (ptr++, ptr--)
함수 전달 시 배열 이름 → 포인터로 전달됨 포인터 그대로 전달
크기 정보 컴파일러가 전체 크기 인식 가능 포인터는 크기 정보 없음

 

 

 

함수와 배열

배열을 함수에 전달 할시 포인터로 전달해야 한다. 

#include <iostream>
using namespace std;

// 배열을 매개변수로 받는 함수 (여러 방법)
void printArray1(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
}

void printArray2(int* arr, int size) {  // 위와 동일
    for (int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
}

// 배열 수정하는 함수
void doubleArray(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        arr[i] *= 2;
    }
}

int main() {
    int numbers[5] = {1, 2, 3, 4, 5};

    cout << "원본 배열: ";
    printArray1(numbers, 5);

    doubleArray(numbers, 5);
    cout << "2배 후: ";
    printArray2(numbers, 5);

    return 0;
}

 

함수에서 배열 크기를 구할 수 없는 이유

함수 매개변수로 전달된 배열은 포인터가 되므로, sizeof는 포인터 크기만 반환하게 된다. 

#include <iostream>
using namespace std;

void wrongSizeFunction(int arr[]) {
    // 오류: 함수 내에서 배열 크기를 구할 수 없음
    int size = sizeof(arr) / sizeof(arr[0]);  // 잘못된 결과 (포인터 크기 / int 크기)
    cout << "함수 내에서 계산한 크기: " << size << endl;  // 2 (8/4)
}

int main() {
    int numbers[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // 올바른 방법: 크기를 직접 계산
    int size = sizeof(numbers) / sizeof(numbers[0]);
    cout << "실제 배열 크기: " << size << endl;  // 10

    wrongSizeFunction(numbers);

    return 0;
}

 

 

다차원 배열

2차원 배열

#include <iostream>
using namespace std;

int main() {
    // 2차원 배열 선언과 초기화
    int matrix[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };

    // 2차원 배열 출력
    cout << "2차원 배열:" << endl;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            cout << matrix[i][j] << "\\t";
        }
        cout << endl;
    }

    // 특정 원소 접근
    cout << "matrix[1][2] = " << matrix[1][2] << endl;  // 7

    // 2차원 배열 크기
    int rows = sizeof(matrix) / sizeof(matrix[0]);
    int cols = sizeof(matrix[0]) / sizeof(matrix[0][0]);
    cout << "행 수: " << rows << ", 열 수: " << cols << endl;

    return 0;
}

 

 

2차원 배열과 포인터

#include <iostream>
using namespace std;

int main() {
    int matrix[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };

    // 2차원 배열의 각 행은 1차원 배열의 포인터
    cout << "각 행의 주소:" << endl;
    for (int i = 0; i < 3; i++) {
        cout << "matrix[" << i << "]: " << matrix[i] << endl;
    }

    // 포인터를 이용한 접근
    cout << "matrix[1][2] = " << *(*(matrix + 1) + 2) << endl;  // 7

    return 0;
}

 

 

동적 배열(new/delete)

정적배열의 한계

#include <iostream>
using namespace std;

int main() {
    // 컴파일 타임에 크기가 결정되어야 함
    const int SIZE = 100;
    int arr[SIZE];  // 크기가 고정됨

    // 다음은 표준 C++에서 불가능:
    // int n;
    // cin >> n;
    // int arr[n];  // 일부 컴파일러에서만 지원 (VLA)

    return 0;
}

동적 메모리 할당

#include <iostream>
using namespace std;

int main() {
    int size;
    cout << "배열 크기를 입력하세요: ";
    cin >> size;

    // 동적 메모리 할당
    int* arr = new int[size];

    // 배열 사용
    for (int i = 0; i < size; i++) {
        arr[i] = i * 2;
        cout << arr[i] << " ";
    }
    cout << endl;

    // 메모리 해제 (중요!)
    delete[] arr;

    return 0;
}

동적 2차원 배열

#include <iostream>
using namespace std;

int main() {
    int rows, cols;
    cout << "행과 열의 수를 입력하세요: ";
    cin >> rows >> cols;

    // 2차원 동적 배열 할당
    int** matrix = new int*[rows];
    for (int i = 0; i < rows; i++) {
        matrix[i] = new int[cols];
    }

    // 배열 사용
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            matrix[i][j] = i * cols + j + 1;
            cout << matrix[i][j] << "\\t";
        }
        cout << endl;
    }

    // 메모리 해제
    for (int i = 0; i < rows; i++) {
        delete[] matrix[i];
    }
    delete[] matrix;

    return 0;
}

 

 

실용적인 배열 예제

성적 관리 시스템

#include <iostream>
using namespace std;

int main() {
    const int STUDENT_COUNT = 5;
    int scores[STUDENT_COUNT];

    // 성적 입력
    cout << "5명의 학생 성적을 입력하세요:" << endl;
    for (int i = 0; i < STUDENT_COUNT; i++) {
        cout << "학생 " << (i + 1) << ": ";
        cin >> scores[i];
    }

    // 통계 계산
    int sum = 0;
    int max = scores[0];
    int min = scores[0];

    for (int i = 0; i < STUDENT_COUNT; i++) {
        sum += scores[i];
        if (scores[i] > max) max = scores[i];
        if (scores[i] < min) min = scores[i];
    }

    double average = (double)sum / STUDENT_COUNT;

    // 결과 출력
    cout << "\\n=== 성적 통계 ===" << endl;
    cout << "평균: " << average << endl;
    cout << "최고점: " << max << endl;
    cout << "최저점: " << min << endl;

    return 0;
}

단순 정렬 (버블 정렬)

#include <iostream>
using namespace std;

void bubbleSort(int arr[], int size) {
    for (int i = 0; i < size - 1; i++) {
        for (int j = 0; j < size - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                // 두 원소 교환
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

int main() {
    int numbers[] = {64, 34, 25, 12, 22, 11, 90};
    int size = sizeof(numbers) / sizeof(numbers[0]);

    cout << "정렬 전: ";
    for (int i = 0; i < size; i++) {
        cout << numbers[i] << " ";
    }
    cout << endl;

    bubbleSort(numbers, size);

    cout << "정렬 후: ";
    for (int i = 0; i < size; i++) {
        cout << numbers[i] << " ";
    }
    cout << endl;

    return 0;
}

배열 관련 주의사항

배열 범위 초과 (Array Out of Bounds)

#include <iostream>
using namespace std;

int main() {
    int arr[5] = {1, 2, 3, 4, 5};

    // 위험한 코드 - 배열 범위 초과
    // cout << arr[10] << endl;  // 정의되지 않은 동작
    // arr[10] = 100;            // 메모리 손상 가능

    // 안전한 코드 - 범위 검사
    int index = 10;
    if (index >= 0 && index < 5) {
        cout << arr[index] << endl;
    } else {
        cout << "배열 범위를 벗어났습니다!" << endl;
    }

    return 0;
}

배열 초기화 주의사항

#include <iostream>
using namespace std;

int main() {
    int arr1[5];           // 쓰레기 값 - 위험!
    int arr2[5] = {0};     // 모든 원소 0으로 초기화 - 안전
    int arr3[5] = {};      // C++11 이후, 모든 원소 0으로 초기화

    // 지역 배열은 자동으로 초기화되지 않음
    cout << "초기화하지 않은 배열 (쓰레기 값):" << endl;
    for (int i = 0; i < 5; i++) {
        cout << "arr1[" << i << "] = " << arr1[i] << endl;
    }

    return 0;
}

포인터 배열 vs 배열 포인터

포인터 배열

#include <iostream>
using namespace std;

int main() {
    // 포인터 배열: 포인터들을 저장하는 배열
    int a = 10, b = 20, c = 30;
    int* ptrs[3] = {&a, &b, &c};  // 포인터들의 배열

    cout << "포인터 배열:" << endl;
    for (int i = 0; i < 3; i++) {
        cout << "ptrs[" << i << "] = " << ptrs[i]
             << ", 값: " << *ptrs[i] << endl;
    }

    // 문자열 배열 (포인터 배열의 활용)
    const char* names[3] = {"Alice", "Bob", "Charlie"};

    cout << "\\n문자열 배열:" << endl;
    for (int i = 0; i < 3; i++) {
        cout << "names[" << i << "] = " << names[i] << endl;
    }

    return 0;
}

배열 포인터

#include <iostream>
using namespace std;

int main() {
    // 배열 포인터: 배열을 가리키는 포인터
    int arr[5] = {1, 2, 3, 4, 5};
    int (*ptr)[5] = &arr;  // 5개 원소를 가진 배열을 가리키는 포인터

    cout << "배열 포인터:" << endl;
    cout << "ptr = " << ptr << endl;
    cout << "*ptr = " << *ptr << endl;  // 배열의 첫 번째 원소 주소
    cout << "**ptr = " << **ptr << endl;  // 첫 번째 원소의 값

    // 배열 포인터를 통한 접근
    for (int i = 0; i < 5; i++) {
        cout << "(*ptr)[" << i << "] = " << (*ptr)[i] << endl;
    }

    return 0;
}

Range-based for 반복문 (C++11 이후)

#include <iostream>
using namespace std;

int main() {
    int numbers[] = {10, 20, 30, 40, 50};

    // Range-based for (읽기 전용)
    cout << "배열 원소들: ";
    for (int num : numbers) {
        cout << num << " ";
    }
    cout << endl;

    // Range-based for (수정 가능)
    for (int& num : numbers) {
        num *= 2;  // 각 원소를 2배로 만들기
    }

    cout << "2배 후: ";
    for (const int& num : numbers) {
        cout << num << " ";
    }
    cout << endl;

    return 0;
}

정리

배열의 핵심 개념:

  1. 연속된 메모리 공간에 같은 타입의 데이터를 저장
  2. 인덱스를 통한 빠른 접근 (O(1) 시간 복잡도)
  3. 컴파일 타임에 크기 결정되는 고정 크기
  4. 배열 이름은 첫 번째 원소의 주소를 나타내는 상수 포인터

배열과 포인터의 관계:

  • 배열 이름은 포인터와 유사하지만 변경 불가능한 상수
  • 함수에 전달 시 포인터로 전달
  • arr[i]는 (arr + i)와 동일

주의사항:

  • 배열 범위 초과 주의
  • 초기화 잊지 말기
  • 동적 메모리 할당 시 반드시 delete[]로 해제
  • 함수에서 배열 크기 계산 불가능
반응형

댓글