일반 변수에서 대입 연산( = )을 하면 변수에 있는 값이 그대로 복사된다.
각각 독립적인 공간을 가지므로 한 쪽을 변경해도 다른 한 쪽에 영향을 주지 않는다.
배열의 대입의 경우에도 마찬가지다.
단순하게 값을 복사하는 행위지만, 데이터가 커지고 이 행위가 반복된다면 이 복사비용을 무시할 수 없다.
변수도 각 변수마다 값을 표현하기 위해 사용되는 공간의 크기가 다르다.
이러한 복사 비용 때문에 C++에서는 값을 직접 복사하는 방식 대신 포인터를 통해 변수의 주소를 가리켜서 동일한 데이터에 접근할 수 있도록 해준다.
1. 포인터 변수의 연산
- 포인터의 모든 연산은 주소값과 관련되어 있다.
- 아래의 2가지 연산은 오직 포인터 변수만 가능하다.
1.1 - 변수의 주소값을 담을 수 있다.
- int* p = &a
1.2 - 담고 있는 주소값에 해당되는 메모리에 있는 값을 읽거나 수정할 수 있다.
주소값을 담는데 왜 포인터의 타입이 필요할까
주소값을 따라서 값을 제대로 읽으려면 주소값을 기준으로 몇바이트를 읽을건지 그 이유도 필요하다.
배열의 이름은 배열의 시작주소를 가지고 있다.
배열과 포인터의 값
buf[K] = *(buf+K)
- 포인터 변수에서도 배열 기호 [ ] (배열첨자연산자) 사용 가능.
- *의 영어 이름은 애스터리스크(asterisk)
- 왼쪽에 있는 값을 L-Value / 오른쪽에 있는 값을 R-Value 라고 부른다. a = b 에서 a는 l-value, b는 r-value.
2. 포인터 배열과 배열 포인터
- 포인터 배열 : 포인터를 원소로 갖는 배열.
- e.g. int* ptrArr[4]; 는 크기가 4이고, 각 원소가 int*인 배열.
int a = 10, b = 20, c = 30, d = 40;
int* ptrArr[4];
ptrArr[0] = &a; // a의 주소를 저장
ptrArr[1] = &b; // b의 주소를 저장
ptrArr[2] = &c; // c의 주소를 저장
ptrArr[3] = &d; // d의 주소를 저장
// 포인터를 통해 값에 접근
printf("%d", *ptrArr[0]); // 10 출력
printf("%d", *ptrArr[1]); // 20 출력
- 배열 포인터 : 배열 전체를 가리키는 포인터
-
- e.g. int (*ptr)[5]; 는 크기가 5인 int 배열을 가리키는 포인터.
-
int arr[5] = {10, 20, 30, 40, 50};
int (*ptr)[5] = &arr; // 전체 배열을 가리키는 포인터
// ptr은 arr 전체 배열을 가리킴
// (*ptr)[0]은 arr[0]과 같음
printf("%d", (*ptr)[2]); // 30 출력
일반 포인터는 배열의 첫째 원소를 가리키지만, 배열 포인터는 배열 전체를 가리킨다.
배열 포인터는 주로 2차원 배열에서 많이 쓰인다.
배열 포인터가 1차원 배열에서 유용한 경우는 함수에 배열 전체를 매개변수로 전달할 때이다.
3. 레퍼런스
- 포인터를 사용하면 주소값을 직접 다뤄야 하므로 복잡해질 수 있다.
- 이 문제를 완화하기 위해 C++에서는 변수에 또 다른 이름을 부여하는 '레퍼런스' 문법을 도입했다.
- 레퍼런스는 일반 변수와 거의 동일하게 사용할 수 있다.
- 그러나 내부적으로는 해당 변수를 직접 가리켜주는 역할은 한다.
- 레퍼런스는 특정 변수에 별명을 부여하는 것이다.
- 복잡한 문법 없이도 마치 포인터를 사용하듯이 내가 참조하고 있는 변수의 값을 제어할 수 있다.
- 선언과 동시에 초기화해야 한다. 미리 선언만 해놓는 것은 불가.
- 한번 할당하면 재할당이 불가능하다.
- 선언방법 : 데이터형 뒤에 '&'를 붙이기
#include <stdio.h>
int main() {
int x = 3;
int& y = x;
y = 4;
printf("%d\n",x);
return 0;
}
기존에는 변수를 선언하면 x랑 y가 각각 존재했지만, &를 붙여주면서 레퍼런스를 하면 x와 y가 같이 묶인다. y가 x의 별명이 된다.
4. 포인터와 레퍼런스의 차이
- 선언과 초기화 시점이 다르다.
- 포인터는 NULL값을 가질 수 있지만, 레퍼런스는 항상 다른 변수와 연결되어 있다. -> 포인터보다 안전.
- 포인터는 간접 참조 문법 *, & 을 사용하지만 레퍼런스는 일반 변수와 연산하는 방법이 동일하다.
- 레퍼런스 변수 자체는 포인터처럼 독립적인 메모리 공간을 명시적으로 차지하지 않는다.
정리
- 포인터 는 '주소'를 저장하는 별도의 변수라서 그 변수 자체의 공간을 차지하고, NULL이 될 수도 있고 다른 곳을 가리키게 바꿀 수도 있다. 저수준의 메모리 제어(예: 동적할당)가 필요할 때 좋다.
- 레퍼런스 는 기존 변수의 '별명'과도 같아서, 보통은 따로 신경 쓸 메모리 공간을 차지하지 않고, 한 번 정해지면 바뀌지도 않고 항상 유효한 값을 가리켜야 한다. 더 안전하고 코드 가독성이 좋다.
- 그래서 보통 C++에서는 포인터가 필요한 특수한 상황(동적 할당, NULL값 가능성 등)이 아니면, 더 안전하고 간편한 레퍼런스를 우선적으로 사용하는 경향이 있다.
5. 상수 레퍼런스
- 레퍼런스에 상수 제약을 걸어서 읽기 전용으로 사용할 수 있다.
- 상수 레퍼런스를 사용하면 값을 복사하지 않고도 기존 변수를 보호할 수 있다.
- const int& cref = x; 하면 복사 과정 없이 x의 값을 읽을 수는 있지만 x값을 수정할 수는 없다.
- cref = 200 (불가능) , x=200 (가능).
'C++ 공부' 카테고리의 다른 글
| 기본적인 Class 생성해보기 (0) | 2025.08.21 |
|---|---|
| C++언어 기초 - 4. 객체지향 프로그래밍 (0) | 2025.08.21 |
| C++에서 char 배열과 std::string의 차이점 (0) | 2025.08.21 |
| C++언어 기초 - 3. Class의 개념 (0) | 2025.08.21 |
| C++언어 기초 - 1. 프로그래밍 기초 (C언어와의 차이점) (2) | 2025.08.18 |