배열이란?
배열은 동일한 데이터 타입의 값들을 순차적으로 저장하는 메모리 블록이다.
- 모든 원소는 연속된 메모리 공간에 저장된다.
- 정적크기 : 크기를 고정해야 하며, 실행중 변경할 수 없다.
- 인덱스는 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;
}
정리
배열의 핵심 개념:
- 연속된 메모리 공간에 같은 타입의 데이터를 저장
- 인덱스를 통한 빠른 접근 (O(1) 시간 복잡도)
- 컴파일 타임에 크기 결정되는 고정 크기
- 배열 이름은 첫 번째 원소의 주소를 나타내는 상수 포인터
배열과 포인터의 관계:
- 배열 이름은 포인터와 유사하지만 변경 불가능한 상수
- 함수에 전달 시 포인터로 전달됨
- arr[i]는 (arr + i)와 동일
주의사항:
- 배열 범위 초과 주의
- 초기화 잊지 말기
- 동적 메모리 할당 시 반드시 delete[]로 해제
- 함수에서 배열 크기 계산 불가능
반응형
'Game DevTip > C++' 카테고리의 다른 글
14. C++ 이동의미론과 rvalue & lvalue (0) | 2025.07.22 |
---|---|
13. C++ 스마트 포인터에 대해서 (1) | 2025.07.19 |
11. C++ 참조자에 대하여 (0) | 2025.07.15 |
10. C++ 포인터 기초 : 메모리 주소의 이해 (1) | 2025.07.15 |
9. 비트 연산과 Bitflag, Bitmask에 대해서 (4) | 2025.07.09 |
댓글