C++ 초급 - 5. 포인터와 참조 (Pointers and References) (1 - 포인터 기본 개념 (*, &, nullptr))

2025. 2. 12. 18:37프로그래밍 언어/C++

📌 5. 포인터와 참조 (Pointers and References)

C++에서 포인터(Pointer)와 참조(Reference)메모리 주소를 직접 다루는 기능을 제공한다.
포인터와 참조를 사용하면 효율적인 메모리 관리와 객체 제어가 가능하지만,
올바르게 사용하지 않으면 메모리 누수(Memory Leak)나 프로그램 오류가 발생할 수 있다.

C++11에서는 스마트 포인터(Smart Pointer)가 도입되어 메모리 관리를 자동화할 수 있다.
이 단원에서는 포인터와 참조를 다루며, 동적 메모리 할당 및 스마트 포인터의 활용까지 학습한다.


📌 5.1 포인터 기본 개념 (*, &, nullptr)

포인터(Pointer)메모리 주소를 저장하는 변수이다.
일반 변수는 값을 저장하지만, 포인터는 변수의 주소를 저장하며,
이를 통해 동적 메모리 할당과 객체 직접 제어가 가능하다.

C++에서 포인터를 이해하면 메모리 관리, 배열, 동적 객체 제어, 함수 전달 등 다양한 기능을 효과적으로 활용할 수 있다.


📌 1. 포인터 연산자

포인터를 다룰 때 사용하는 주요 연산자는 다음과 같다.

연산자  설명
* (역참조 연산자) 포인터 변수를 선언하거나, 포인터가 가리키는 값을 참조할 때 사용
& (주소 연산자) 변수의 메모리 주소를 가져올 때 사용
nullptr (C++11 이상) 포인터가 아무것도 가리키지 않음을 나타내는 값 (이전에는 NULL 사용)

💡 포인터 개념 예제

#include <iostream>

int main() {
    int num = 10;
    int* ptr = &num;  // 포인터 ptr에 num의 주소 저장

    std::cout << "num의 값: " << num << std::endl;
    std::cout << "num의 주소: " << &num << std::endl;
    std::cout << "포인터 ptr이 가리키는 주소: " << ptr << std::endl;
    std::cout << "ptr을 역참조한 값: " << *ptr << std::endl;

    return 0;
}

🔹 출력 결과 (주소 값은 다를 수 있음)

num의 값: 10
num의 주소: 0x7ffcbf1a
포인터 ptr이 가리키는 주소: 0x7ffcbf1a
ptr을 역참조한 값: 10

💡 설명

  • int* ptr = &num; → ptr은 num의 주소를 저장하는 포인터.
  • ptr을 출력하면 num의 메모리 주소가 나옴.
  • *ptr을 출력하면 ptr이 가리키는 값(즉, num)을 가져옴.

📌 2. 포인터 변수 선언 및 초기화

포인터 변수를 선언할 때는 저장할 데이터 타입을 명시해야 한다.

(1) 포인터 선언 및 초기화

int* p1;   // 정수를 가리키는 포인터
double* p2; // 실수를 가리키는 포인터
char* p3;  // 문자를 가리키는 포인터
  • 포인터가 가리킬 변수의 타입과 포인터의 타입이 반드시 일치해야 한다.
  • 포인터 변수를 선언할 때 초기화하지 않으면 쓰레기 값을 가질 수 있으므로 주의해야 한다.

(2) 포인터 초기화 방법

int num = 42;
int* p1 = &num;  // 변수의 주소로 초기화
int* p2 = nullptr;  // 안전한 초기화 (C++11 이상)

📌 3. nullptr을 사용한 안전한 포인터 초기화

C++에서는 초기화되지 않은 포인터를 사용하면 오류가 발생할 가능성이 높다.
C++11 이전에는 NULL을 사용했으나, C++11부터 nullptr가 도입되어 더 안전한 방식으로 초기화 가능하다.

💡 예제: 안전한 포인터 초기화

#include <iostream>

int main() {
    int* ptr1 = nullptr;  // C++11 이상에서 안전한 초기화
    int* ptr2 = NULL;  // C++98 방식 (사용 가능하지만 권장되지 않음)

    if (ptr1 == nullptr) {
        std::cout << "ptr1은 현재 아무것도 가리키지 않습니다." << std::endl;
    }

    return 0;
}

🔹 출력 결과

ptr1은 현재 아무것도 가리키지 않습니다.

💡 왜 nullptr이 NULL보다 좋은가?

  • nullptr은 명확한 포인터 전용 타입을 가지므로, 정수와 혼동될 위험이 없음.
  • NULL은 내부적으로 0으로 정의되어 있어, 정수와 비교 시 문제가 발생할 가능성이 있음.

📌 4. 포인터를 활용한 배열 및 문자열 처리

포인터는 배열과 밀접한 관계가 있으며, 배열 요소를 처리하는 데 유용하게 사용된다.

(1) 포인터와 배열의 관계

배열의 이름 자체가 첫 번째 요소의 주소를 의미하므로,
포인터를 사용하면 배열을 보다 효율적으로 다룰 수 있다.

💡 예제: 배열과 포인터

#include <iostream>

int main() {
    int arr[3] = {10, 20, 30};
    int* ptr = arr;  // 배열 이름은 첫 번째 요소의 주소

    std::cout << "배열 요소 출력 (포인터 사용): " << std::endl;
    for (int i = 0; i < 3; i++) {
        std::cout << *(ptr + i) << " ";  // 포인터 연산 사용
    }

    return 0;
}

🔹 출력 결과

배열 요소 출력 (포인터 사용): 
10 20 30

💡 설명

  • int* ptr = arr; → 배열 이름은 첫 번째 요소의 주소.
  • *(ptr + i) → 포인터 연산을 사용하여 배열 요소 접근.

(2) 포인터를 활용한 문자열 처리

문자열(char 배열)은 포인터를 이용하면 더욱 효율적으로 관리할 수 있다.

💡 예제: 문자열과 포인터

#include <iostream>

int main() {
    char str[] = "Hello";
    char* ptr = str;  // 첫 번째 문자의 주소 저장

    std::cout << "문자열 출력 (포인터 사용): ";
    while (*ptr != '\0') {  // NULL 문자('\0')까지 반복
        std::cout << *ptr;
        ptr++;  // 다음 문자로 이동
    }

    return 0;
}

🔹 출력 결과

문자열 출력 (포인터 사용): Hello

💡 설명

  • 포인터를 이용하여 문자열을 한 글자씩 읽어가는 방식으로 처리.
  • *ptr → 현재 가리키는 문자 출력.
  • ptr++ → 포인터를 다음 문자로 이동.

📌 5. 정리

개념  설명
포인터 기본 개념 포인터는 메모리 주소를 저장하는 변수
포인터 연산자 * → 포인터 선언 및 역참조, & → 주소 가져오기, nullptr → 포인터 초기화
포인터 초기화 **nullptr(C++11 이상)**를 사용하여 안전하게 초기화
포인터와 배열 배열 이름은 첫 번째 요소의 주소를 의미
포인터와 문자열 포인터를 사용하면 문자열을 효율적으로 관리 가능