Topic 1: 포인터의 이해 🎯➡️
🎯 학습 목표
- 포인터의 기본 개념과 메모리 구조를 이해할 수 있다
- 포인터 선언, 초기화, 역참조 방법을 익힐 수 있다
- 포인터와 변수의 차이점을 명확히 구분할 수 있다
- 실제 프로그램에서 포인터를 안전하게 사용할 수 있다
🏠 메모리와 주소의 개념
프로그램을 실행하면 컴퓨터의 **메모리(RAM)**에 데이터가 저장됩니다. 메모리는 마치 아파트처럼 각각 고유한 주소를 가지고 있습니다! 🏢
실생활 비유: 아파트 주소 시스템
// 실생활 비유
// 101호: "김철수"가 살고 있음
// 102호: "이영희"가 살고 있음
// 103호: "박민수"가 살고 있음
// 컴퓨터 메모리도 동일한 원리!
int value1 = 100; // 메모리 주소 0x1000에 100 저장
int value2 = 200; // 메모리 주소 0x1004에 200 저장
int value3 = 300; // 메모리 주소 0x1008에 300 저장
🔍 포인터란 무엇인가?
**포인터(Pointer)**는 다른 변수의 메모리 주소를 저장하는 특별한 변수입니다.
마치 친구의 집 주소를 적어놓은 메모지와 같습니다! 📝
포인터의 핵심 개념
#include <iostream>
using namespace std;
int main() {
// 일반 변수 선언
int number = 42;
// 포인터 변수 선언 및 초기화
int* ptr = &number; // ptr은 number의 주소를 저장
cout << "=== 포인터 기본 개념 ===" << endl;
cout << "number의 값: " << number << endl; // 42
cout << "number의 주소: " << &number << endl; // 예: 0x7fff5fbff6ac
cout << "ptr의 값 (주소): " << ptr << endl; // 예: 0x7fff5fbff6ac
cout << "ptr이 가리키는 값: " << *ptr << endl; // 42
return 0;
}
📝 포인터 선언과 초기화
기본 문법
// 포인터 선언 문법
데이터타입* 포인터변수명;
데이터타입 *포인터변수명; // 공백 위치는 상관없음
// 예시들
int* intPtr; // int를 가리키는 포인터
double* doublePtr; // double을 가리키는 포인터
char* charPtr; // char를 가리키는 포인터
포인터 초기화 방법들
#include <iostream>
using namespace std;
int main() {
int value = 100;
// 방법 1: 선언과 동시에 초기화
int* ptr1 = &value;
// 방법 2: 선언 후 초기화
int* ptr2;
ptr2 = &value;
// 방법 3: nullptr로 초기화 (안전한 초기화)
int* ptr3 = nullptr;
ptr3 = &value;
cout << "모든 포인터가 같은 주소를 가리킵니다:" << endl;
cout << "ptr1: " << ptr1 << endl;
cout << "ptr2: " << ptr2 << endl;
cout << "ptr3: " << ptr3 << endl;
// 모두 같은 값을 출력
cout << "\n가리키는 값들:" << endl;
cout << "*ptr1: " << *ptr1 << endl; // 100
cout << "*ptr2: " << *ptr2 << endl; // 100
cout << "*ptr3: " << *ptr3 << endl; // 100
return 0;
}
🔄 호출 방식 비교: Call-by-Value vs Call-by-Address
포인터의 핵심은 변수의 주소를 다른 함수에 전달하여 원본 데이터를 직접 수정할 수 있다는 점입니다!
📚 호출 방식의 종류
- Call by Value (값에 의한 호출): 복사본을 전달 → 원본 변경 불가
- Call by Address (주소에 의한 호출): 포인터로 주소를 전달 → 원본 변경 가능
🔍 실습 예제: 두 변수 교환하기
#include <iostream>
using namespace std;
// 1. Call by Value - 원본이 바뀌지 않음
void swapByValue(int a, int b) {
cout << "[함수 내부] 교환 전: a=" << a << ", b=" << b << endl;
int temp = a;
a = b;
b = temp;
cout << "[함수 내부] 교환 후: a=" << a << ", b=" << b << endl;
}
// 2. Call by Address - 포인터로 원본 변경 가능
void swapByAddress(int* a, int* b) {
cout << "[함수 내부] 교환 전: *a=" << *a << ", *b=" << *b << endl;
int temp = *a; // a가 가리키는 값
*a = *b; // a가 가리키는 곳에 b가 가리키는 값 저장
*b = temp; // b가 가리키는 곳에 temp 저장
cout << "[함수 내부] 교환 후: *a=" << *a << ", *b=" << *b << endl;
}
int main() {
cout << "=== 호출 방식 비교 실험 ===" << endl;
// 실험용 변수들
int x1 = 10, y1 = 20;
int x2 = 10, y2 = 20;
cout << "\n🔸 초기 상태:" << endl;
cout << "x1=" << x1 << ", y1=" << y1 << endl;
cout << "x2=" << x2 << ", y2=" << y2 << endl;
// 1. Call by Value 테스트
cout << "\n❌ Call by Value (값 전달):" << endl;
swapByValue(x1, y1);
cout << "[메인함수] 호출 후: x1=" << x1 << ", y1=" << y1 << " (변경 안됨!)" << endl;
// 2. Call by Address 테스트
cout << "\n✅ Call by Address (주소 전달):" << endl;
swapByAddress(&x2, &y2); // 주소를 전달
cout << "[메인함수] 호출 후: x2=" << x2 << ", y2=" << y2 << " (성공적으로 교환!)" << endl;
return 0;
}
🎯 왜 포인터가 필요한가?
#include <iostream>
using namespace std;
// 포인터 없이는 불가능한 작업들
void demonstratePointerNeed() {
cout << "=== 포인터가 필요한 이유 ===" << endl;
// 1. 여러 값을 동시에 반환하고 싶을 때
int dividend = 17, divisor = 5;
int quotient, remainder;
// 포인터로 여러 결과를 받아올 수 있음
divide(dividend, divisor, "ient, &remainder);
cout << dividend << " ÷ " << divisor << " = "
<< quotient << " 나머지 " << remainder << endl;
}
// 몫과 나머지를 동시에 계산하는 함수
void divide(int dividend, int divisor, int* quotient, int* remainder) {
*quotient = dividend / divisor;
*remainder = dividend % divisor;
}
int main() {
demonstratePointerNeed();
return 0;
}
💻 포인터 연산과 활용
포인터 연산의 종류
#include <iostream>
using namespace std;
int main() {
cout << "🧮 === 포인터 연산 예제 === 🧮" << endl;
int numbers[] = {10, 20, 30, 40, 50};
int* ptr = numbers; // 배열의 첫 번째 요소 주소
cout << "배열: ";
for (int i = 0; i < 5; i++) {
cout << numbers[i] << " ";
}
cout << endl << endl;
// 포인터를 이용한 배열 순회
cout << "포인터를 이용한 순회:" << endl;
for (int i = 0; i < 5; i++) {
cout << "ptr + " << i << ": ";
cout << "주소=" << (ptr + i) << ", ";
cout << "값=" << *(ptr + i) << endl;
}
cout << endl;
// 포인터 증감 연산
cout << "포인터 증감 연산:" << endl;
int* current = numbers;
cout << "초기 위치: *current = " << *current << endl;
current++; // 다음 요소로 이동
cout << "current++ 후: *current = " << *current << endl;
current += 2; // 2칸 앞으로 이동
cout << "current += 2 후: *current = " << *current << endl;
current--; // 이전 요소로 이동
cout << "current-- 후: *current = " << *current << endl;
return 0;
}
🎯 실습: 포인터로 배열 수정하기
#include <iostream>
using namespace std;
// 배열의 모든 값을 2배로 만드는 함수
void doubleValues(int* arr, int size) {
cout << "함수에서 배열 값들을 2배로 만듭니다..." << endl;
for(int i = 0; i < size; i++) {
arr[i] = arr[i] * 2; // 포인터로 원본 배열 수정
}
}
// 배열 출력 함수
void printArray(int* arr, int size, const char* label) {
cout << label << ": ";
for(int i = 0; i < size; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
int main() {
cout << "=== 포인터로 배열 수정하기 ===" << endl;
int numbers[5] = {1, 2, 3, 4, 5};
printArray(numbers, 5, "수정 전");
// 배열의 주소를 함수에 전달 (Call by Address)
doubleValues(numbers, 5);
printArray(numbers, 5, "수정 후");
return 0;
}
🎮 포인터로 값 증가시키기
#include <iostream>
using namespace std;
// 숫자를 증가시키는 함수 (포인터로 원본 수정)
void increaseValue(int* number, int increment) {
cout << "함수 내부: " << *number << "에 " << increment << "을 더합니다." << endl;
*number += increment;
cout << "함수 내부: 결과값 = " << *number << endl;
}
// 점수를 두 배로 만드는 함수
void doubleScore(double* score) {
cout << "점수 " << *score << "를 두 배로 만듭니다." << endl;
*score *= 2;
if (*score > 100.0) {
*score = 100.0; // 최대 100점
cout << "최대 점수 100점으로 조정되었습니다." << endl;
}
}
int main() {
cout << "=== 포인터로 값 수정하기 ===" << endl;
// 정수 값 증가 예제
int number = 42;
cout << "\n1. 정수 값 증가:" << endl;
cout << "원래 값: " << number << endl;
increaseValue(&number, 10); // 주소 전달
cout << "메인에서 확인: " << number << endl;
// 실수 값 두 배 예제
double score = 85.5;
cout << "\n2. 점수 두 배로 만들기:" << endl;
cout << "원래 점수: " << score << endl;
doubleScore(&score); // 주소 전달
cout << "최종 점수: " << score << endl;
return 0;
}
⚠️ 포인터 사용 시 주의사항
1. 초기화되지 않은 포인터
#include <iostream>
using namespace std;
int main() {
// ❌ 위험한 코드
int* dangerousPtr; // 초기화되지 않은 포인터
// cout << *dangerousPtr; // 프로그램 크래시 가능!
// ✅ 안전한 코드
int* safePtr = nullptr; // nullptr로 초기화
if (safePtr != nullptr) {
cout << *safePtr; // 안전한 접근
} else {
cout << "포인터가 nullptr입니다!" << endl;
}
return 0;
}
2. 널 포인터 체크
#include <iostream>
using namespace std;
// 안전한 포인터 사용 함수
void safePointerUsage(int* ptr) {
// 항상 nullptr 체크!
if (ptr == nullptr) {
cout << "⚠️ 널 포인터입니다! 처리할 수 없습니다." << endl;
return;
}
cout << "✅ 안전하게 접근: " << *ptr << endl;
}
int main() {
int value = 42;
int* validPtr = &value;
int* nullPtr = nullptr;
cout << "=== 안전한 포인터 사용 예제 ===" << endl;
safePointerUsage(validPtr); // 정상 작동
safePointerUsage(nullPtr); // 안전하게 처리
return 0;
}
3. 포인터와 배열의 경계
#include <iostream>
using namespace std;
int main() {
int numbers[5] = {10, 20, 30, 40, 50};
int* ptr = numbers;
cout << "=== 배열 경계 확인 ===" << endl;
// ✅ 안전한 접근
for (int i = 0; i < 5; i++) {
cout << "numbers[" << i << "] = " << *(ptr + i) << endl;
}
cout << "\n⚠️ 위험한 접근 예시 (주석 처리됨):" << endl;
// ❌ 위험한 코드 (배열 경계 초과)
// cout << *(ptr + 10); // 메모리 접근 오류 가능!
cout << "배열 경계를 항상 확인하세요!" << endl;
return 0;
}
🧪 퀴즈 타임!
🤔 퀴즈 1: 포인터와 주소 연산자
다음 코드의 출력 결과는?
int x = 100;
int* ptr = &x;
cout << x << " ";
cout << &x << " ";
cout << ptr << " ";
cout << *ptr;
정답: 100 [주소값] [같은주소값] 100
- x: 변수의 값 (100)
- &x: 변수 x의 주소
- ptr: x의 주소를 저장 (&x와 같음)
- *ptr: ptr이 가리키는 값 (100)
🤔 퀴즈 2: Call by Address 효과
다음 함수 호출 후 x의 값은?
void changeValue(int* ptr) {
*ptr = 999;
}
int x = 100;
changeValue(&x);
정답: 999
포인터로 주소를 전달했기 때문에 원본 변수 x가 변경됩니다.
🤔 퀴즈 3: 포인터 vs 일반 변수
다음 중 올바른 설명은?
- 포인터는 다른 변수의 값을 저장한다
- 포인터는 다른 변수의 주소를 저장한다
- &는 값을 가져오는 연산자다
- *는 주소를 가져오는 연산자다
정답: 2번
- 포인터는 주소를 저장하는 변수입니다
- &는 주소를 가져오는 연산자
- *는 값을 가져오는 연산자(역참조)
🎯 연습 문제
연습 1: 간단한 배열 최대값 찾기
배열에서 포인터를 사용해 최대값을 찾는 함수를 만들어 보세요.
💡 힌트
int* findMax(int* arr, int size) {
// 첫 번째 요소를 최대값으로 가정
// 나머지 요소들과 비교
// 더 큰 값의 주소를 반환
}
연습 2: 포인터로 두 수 교환
포인터를 이용해 두 변수의 값을 교환하는 함수를 작성하세요.
💡 힌트
void swap(int* a, int* b) {
// temp 변수 사용
// *a와 *b를 교환
}
🎉 마무리
포인터는 C++의 강력한 기능이지만 신중하게 사용해야 하는 도구입니다!
오늘 배운 내용:
- 🏠 메모리와 주소의 개념
- 🎯 포인터의 기본 원리와 문법
- 🔄 포인터를 이용한 값 교환과 함수 매개변수
- 💻 포인터 연산과 배열 접근
- 🛍️ 실제 프로젝트에서의 포인터 활용
- ⚠️ 안전한 포인터 사용법
다음 단계:
- Topic 2에서는 동적 메모리 할당(new/delete)을 학습합니다
- 런타임에 메모리를 할당하고 해제하는 방법을 배우게 됩니다
- 메모리 누수 방지와 스마트 포인터도 다루게 됩니다
포인터를 제대로 이해했다면, 이제 메모리를 자유자재로 다룰 수 있는 C++ 프로그래머가 되어가고 있습니다! 💪
Last updated on