1. 첨자 연산자란?
1) 기본 배열의 문제점
- 예int arr[5];라고 하면, 숫자 5개를 담을 수 있는 배열이 생긴다.
- 이 배열을 사용할 때 경계를 검사하지 않는다.
⚠️ 예시:
int arr[5];
cout << arr[-1] << endl; // 이상한 값 출력, 프로그램이 위험해질 수 있음
- arr[-1]처럼 존재하지 않는 칸을 참조해도 컴파일은 되고 실행도 된다.
- 하지만 컴퓨터 메모리에서 엉뚱한 값을 읽거나 프로그램이 터질 수 있다.
2) operator[]란?
- [ ]를 사용자가 만든 함수로 바꿔서 예방할 수 있다. → 첨자 연산자 오버로딩
- 즉, arrEx[2]라고 쓰면, 사실 내부적으로는 arrEx 객체의 operator[] 함수가 호출되어 2번째 칸의 위치(참조값)를 가져오는 과정이다.
✅ 핵심:
- operator 뒤에는 반드시 대괄호 []를 붙여야 한다.
- 잘못된 표기: arrEx.operator = 10; → ❌ 컴파일 오류
- 올바른 표기: arrEx[2] = 10; → 3번째 칸 참조 후 값 10 저장
3) 참조형 반환
- operator[]가 참조형(int&)으로 반환되면,
- arrEx[2] = 10;
- 일반 int형으로 반환하면 읽기만 가능, 쓰기는 불가능
4) 배열 클래스 예시
class ExArr {
private:
int* arr; // 실제 숫자들을 저장하는 상자
int arrlen; // 배열 길이
public:
ExArr(int len); // 배열을 만들 때 크기 지정
int& operator[](int idx); // 첨자 연산자
~ExArr(); // 배열 메모리 해제
};
- ExArr arr(5); → 길이 5짜리 배열 생성
- arr[3] = 10; → operator[]가 4번째 칸 참조를 반환 → 값 10 저장
⚠️ 추가 설명:
- 경계 검사: 잘못된 인덱스 접근 막기
- 데이터 유일성: 복사 생성자/대입 연산자를 private으로 두면 안전
2. const 오버로딩
- const는 “이 객체는 수정이 불가하다. 즉, 읽기만 가능”
- const 객체에서 operator[]를 쓰려면 const 버전 필요
int& operator[](int idx); // 일반 객체
int& operator[](int idx) const; // const 객체
- const 함수에서는 내부 데이터를 변경할 수 없다
- 초보자 실수:
const ExArr c_arr(5);
c_arr[0] = 10; // ❌ 컴파일 에러
- 해결: const 버전 operator[] 정의 후 사용 ✅
3. 객체를 저장하는 배열
1) 객체 대입 기반
- 배열 안에 기본 자료형 대신 객체를 넣음
- 예시:
arr[0] = PTR(3,4);
- 과정:
1️⃣ arr[0] → operator[] 호출 → 참조 반환
2️⃣ PTR& 반환 → 내부 좌표 값 접근
3️⃣ 대입 연산자 = → 값 복사 - 장점: 참조형 반환 덕분에 읽기/쓰기 모두 가능
- ⚠️ 주의: 객체가 크면 복사 비용 발생
2) 포인터 기반 배열
- 배열 안에 객체 포인터를 저장
- 포인터로 동적 생성 → new/delete 사용 가능
typedef PTR* P_ptr;
P_ptr* arr; // PTR* 배열 (이중 포인터)
- 과정:
- 사용 후 delete arr[i]; 필수 ⚠️
- 장점:
💻예제 코드
#include <iostream>
#include <cstdlib>
using namespace std;
class PTR {
private:
int xpos, ypos;
public:
PTR(int x=0, int y=0);
friend ostream& operator<<(ostream& os, const PTR& pos);
};
PTR::PTR(int x,int y):xpos(x),ypos(y){}
ostream& operator<<(ostream& os, const PTR& pos){
os << '[' << pos.xpos << ", " << pos.ypos << ']' << endl;
return os;
}
typedef PTR* P_ptr;
class ExPTRArray{
private:
P_ptr* arr;
int arrlen;
ExPTRArray(const ExPTRArray& arr);
ExPTRArray& operator=(const ExPTRArray& arr);
public:
ExPTRArray(int len) : arrlen(len) { arr = new P_ptr[len]; }
P_ptr& operator[](int idx){
if(idx<0||idx>=arrlen){ cout<<"index 범위 초과"<<endl; exit(1); }
return arr[idx];
}
P_ptr& operator[](int idx) const{
if(idx<0||idx>=arrlen){ cout<<"index 범위 초과"<<endl; exit(1); }
return arr[idx];
}
int GetArrLen() const { return arrlen; }
~ExPTRArray() { delete[] arr; }
};
int main(){
ExPTRArray arr(3);
for(int i=0;i<3;i++)
arr[i] = new PTR(i+i+3,i+i+4);
for(int i=0;i<arr.GetArrLen();i++)
cout<<*(arr[i]);
for(int i=0;i<3;i++)
delete arr[i];
return 0;
}
실행 결과
[3,4]
[5,6]
[7,8]
✅ 마무리 정리
1️⃣ operator[] 참조 반환 → 읽기/쓰기 모두 가능
2️⃣ 경계 검사 → 잘못된 인덱스 접근 방지
3️⃣ const 버전 operator[] → const 객체에서도 안전
4️⃣ 객체 배열 대입 → 내부 데이터 안전하게 복사 가능
5️⃣ 포인터 배열 관리 → new/delete로 안전한 메모리 관리
6️⃣ 복사 생성자/대입 연산 차단 → 포인터 안전 보장
7️⃣ 참조형 반환 → 읽기, 쓰기, 대입 연산 모두 안전
8️⃣ 동적 객체 생성 → 배열 크기 자유, 메모리 관리 가능
'Language > C++' 카테고리의 다른 글
| 📌 Chapter 11 - string (0) | 2025.10.11 |
|---|---|
| 📌 Chapter 10 - 나머지 연산자 오버로딩 (0) | 2025.09.16 |
| 📌 Chapter 10 - 기본 대입 연산자 오버로딩 (0) | 2025.09.08 |
| 📌 Chapter 10 – 연산자 오버로딩 (0) | 2025.09.03 |
| 📌 Chapter 09 - 가상 (0) | 2025.09.02 |