Skip to Content
💻 코리아IT아카데미 신촌 - 프로그래밍 학습 자료
C++ 프로그래밍Unit 7: 포인터Topic 1: 포인터의 기본 개념

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

포인터의 핵심은 변수의 주소를 다른 함수에 전달하여 원본 데이터를 직접 수정할 수 있다는 점입니다!

📚 호출 방식의 종류

  1. Call by Value (값에 의한 호출): 복사본을 전달 → 원본 변경 불가
  2. 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, &quotient, &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 일반 변수

다음 중 올바른 설명은?

  1. 포인터는 다른 변수의 값을 저장한다
  2. 포인터는 다른 변수의 주소를 저장한다
  3. &는 값을 가져오는 연산자다
  4. *는 주소를 가져오는 연산자다

정답: 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