C++ 입문: 네임스페이스, 함수 오버로딩, 기본값 매개변수 및 참조

C++의 입출력 방식은 C 언어와 다릅니다. C++에서는 <iostream> 헤더와 std 네임스페이스를 사용하여 coutcin을 이용합니다.

#include <iostream>
using namespace std;

int main() {
    cout << "Hello, World!" << endl;
    return 0;
}

네임스페이스 (Namespace)

네임스페이스는 이름 충돌을 방지하기 위해 사용됩니다. 전역 공간에 너무 많은 변수, 함수, 클래스가 존재하면 이름이 중복될 수 있는데, 네임스페이스는 이러한 충돌을 줄여줍니다. C 언어에서는 이러한 이름 충돌 문제가 발생하기 쉽습니다.

#include <stdio.h>

int rand = 10; // stdlib.h의 rand 함수와 이름 충돌

int main() {
    // 컴파일 오류: rand는 이미 선언되었습니다.
    // printf("%d\n", rand);
    return 0;
}

네임스페이스 정의

namespace 키워드를 사용하여 네임스페이스를 정의합니다.

namespace MyNamespace {
    int value = 10;
    int add(int a, int b) {
        return a + b;
    }
}

네임스페이스는 전역, 함수 지역, 클래스 등 다양한 범위(scope) 내에서 독립적으로 작용합니다. C++ 표준 라이브러리는 std라는 네임스페이스 안에 있습니다.

네임스페이스 사용

네임스페이스 멤버에 접근하려면 네임스페이스이름::멤버이름 형식을 사용합니다.

#include <stdio.h>

namespace UserData {
    int count = 5;
    int calculateSum(int x, int y) {
        return x + y;
    }
}

int main() {
    printf("%d\n", UserData::count);
    printf("%d\n", UserData::calculateSum(3, 7));
    return 0;
}

중첩 네임스페이스 사용:

namespace Outer {
    namespace Inner {
        int data = 100;
    }
}

int main() {
    printf("%d\n", Outer::Inner::data);
    return 0;
}

여러 파일에 걸쳐 동일한 이름의 네임스페이스를 정의하면 자동으로 병합됩니다.

네임스페이스 멤버 접근 방법

  1. 명시적 지정: NamespaceName::member (권장)
  2. using 선언: using NamespaceName::member; (특정 멤버만 사용)
  3. using namespace: using namespace NamespaceName; (모든 멤버 사용, 이름 충돌 위험)
#include <iostream>

namespace Example {
    int var1 = 10;
    int var2 = 20;
}

int main() {
    // 1. 명시적 지정
    std::cout << Example::var1 << std::endl;

    // 2. using 선언
    using Example::var2;
    std::cout << var2 << std::endl;

    // 3. using namespace (주의: 프로젝트에서는 권장하지 않음)
    using namespace Example;
    std::cout << var1 << std::endl; // var1은 이미 선언되었으므로 충돌 가능성 있음

    return 0;
}

C++ 입출력 (iostream)

iostream은 표준 입출력 스트림 라이브러리입니다.

  • std::cin: 표준 입력 스트림 객체.
  • std::cout: 표준 출력 스트림 객체.
  • std::endl: 줄바꿈 문자를 출력하고 버퍼를 플러시합니다.
  • <<: 스트림 삽입 연산자 (output).
  • >>: 스트림 추출 연산자 (input).

C++ 입출력은 형식 지정이 필요 없고, 사용자 정의 타입도 지원합니다.

#include <iostream>

int main() {
    int num;
    double pi;
    char initial;

    std::cout << "정수, 실수, 문자를 입력하세요: ";
    std::cin >> num >> pi >> initial;

    std::cout << "입력된 값: " << num << ", " << pi << ", " << initial << std::endl;

    // 입출력 속도 향상 (경쟁 프로그래밍 등에서 유용)
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    return 0;
}

기본값 매개변수 (Default Parameter)

함수 선언 시 매개변수에 기본값을 지정할 수 있습니다. 함수 호출 시 해당 매개변수에 대한 실인수가 제공되지 않으면 기본값이 사용됩니다.

  • 전체 기본값: 모든 매개변수에 기본값 지정.
  • 부분 기본값: 일부 매개변수에 기본값 지정. 이때, 부분 기본값은 오른쪽부터 순차적으로 지정해야 합니다.
#include <iostream>

// 전체 기본값
void printValues(int a = 1, int b = 2, int c = 3) {
    std::cout << "a: " << a << ", b: " << b << ", c: " << c << std::endl;
}

// 부분 기본값 (b와 c에 기본값 지정)
void processData(int x, int y = 10, int z = 20) {
    std::cout << "x: " << x << ", y: " << y << ", z: " << z << std::endl;
}

int main() {
    printValues();         // 1, 2, 3
    printValues(10);       // 10, 2, 3
    printValues(10, 20);   // 10, 20, 3
    printValues(10, 20, 30); // 10, 20, 30

    processData(100);       // 100, 10, 20
    processData(100, 200);  // 100, 200, 20
    // processData(100, , 300); // 불가능: 부분 기본값은 순차적으로 지정해야 함

    return 0;
}

함수 선언과 정의가 분리될 경우, 기본값은 함수 선언부에 지정해야 합니다.

함수 오버로딩 (Function Overloading)

동일한 범위 내에서 동일한 이름의 함수를 여러 개 정의하는 기능입니다. 단, 함수의 매개변수 목록(개수 또는 타입, 타입 순서)이 달라야 합니다. 반환 타입만 다른 것은 오버로딩으로 인정되지 않습니다.

#include <iostream>

// 매개변수 타입이 다른 오버로딩
int calculate(int a, int b) {
    std::cout << "Using int calculate(int, int)" << std::endl;
    return a + b;
}

double calculate(double a, double b) {
    std::cout << "Using double calculate(double, double)" << std::endl;
    return a + b;
}

// 매개변수 개수가 다른 오버로딩
void display() {
    std::cout << "Using void display()" << std::endl;
}

void display(int value) {
    std::cout << "Using void display(int)" << std::endl;
}

// 매개변수 타입 순서가 다른 오버로딩
void printInfo(int id, const char* name) {
    std::cout << "ID: " << id << ", Name: " << name << std::endl;
}

void printInfo(const char* name, int id) {
    std::cout << "Name: " << name << ", ID: " << id << std::endl;
}

int main() {
    calculate(5, 10);
    calculate(5.5, 10.1);

    display();
    display(100);

    printInfo(1, "Alice");
    printInfo("Bob", 2);

    return 0;
}

참조 (Reference)

참조는 기존 변수에 대한 별명(alias)입니다. 새로운 변수를 만드는 것이 아니라, 기존 변수를 참조하는 것입니다. 참조 변수는 원본 변수와 동일한 메모리 공간을 공유합니다.

  • 참조는 정의 시 반드시 초기화해야 합니다.
  • 하나의 변수는 여러 참조를 가질 수 있습니다.
  • 참조는 한 번 초기화되면 다른 변수를 참조하도록 변경할 수 없습니다 (실제로는 복사/할당이 일어납니다).
#include <iostream>

int main() {
    int original = 10;
    int& ref1 = original; // ref1은 original의 별명
    int& ref2 = original; // ref2도 original의 별명

    ref1 = 20; // original의 값이 20으로 변경됩니다.
    std::cout << "Original: " << original << ", Ref1: " << ref1 << ", Ref2: " << ref2 << std::endl;

    int another = 30;
    ref1 = another; // 이것은 ref1이 another를 참조하게 되는 것이 아니라, ref1(original)에 another의 값을 복사하는 것입니다.
    std::cout << "Original: " << original << ", Ref1: " << ref1 << ", Another: " << another << std::endl;

    std::cout << "Address of original: " << &original << std::endl;
    std::cout << "Address of ref1: " << &ref1 << std::endl; // original과 동일한 주소
    std::cout << "Address of ref2: " << &ref2 << std::endl; // original과 동일한 주소

    return 0;
}

참조의 활용

  • 참조를 이용한 매개변수 전달: 함수 호출 시 객체 복사를 피하여 효율성을 높입니다. 포인터 전달과 유사한 효과를 내지만, 문법이 더 간결합니다.
  • 참조를 이용한 반환: 함수에서 객체 자체를 반환할 때 복사를 피할 수 있습니다. (주의: 함수 내 지역 변수를 참조로 반환하면 안 됩니다.)
#include <iostream>
#include <vector>

// 참조를 이용한 값 교환
void swapValues(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

// 참조를 이용한 벡터 요소 추가 (효율성 증대)
void pushBackElement(std::vector<int>& vec, int value) {
    vec.push_back(value);
}

// 참조를 반환하는 함수 (주의: 함수 내 지역 변수를 반환하지 않도록 함)
int& getElementByIndex(std::vector<int>& vec, size_t index) {
    return vec[index];
}

int main() {
    int x = 10, y = 20;
    std::cout << "Before swap: x=" << x << ", y=" << y << std::endl;
    swapValues(x, y);
    std::cout << "After swap: x=" << x << ", y=" << y << std::endl;

    std::vector<int> data = {1, 2, 3};
    pushBackElement(data, 4);
    for (int val : data) {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    getElementByIndex(data, 0) = 100; // 참조 반환을 이용한 값 변경
    std::cout << "First element after modification: " << data[0] << std::endl;

    return 0;
}

포인터 변수에 대한 참조를 사용하여 이중 포인터 없이 함수 내에서 포인터 자체를 변경할 수 있습니다.

#include <iostream>

struct Node {
    int data;
    Node* next;
};

// 이중 포인터를 사용하지 않고 헤드 포인터 변경
void insertNode(Node*& head, int value) {
    Node* newNode = new Node{value, nullptr};
    newNode->next = head;
    head = newNode;
}

int main() {
    Node* listHead = nullptr;
    insertNode(listHead, 10);
    insertNode(listHead, 20);

    Node* current = listHead;
    while (current) {
        std::cout << current->data << " -> ";
        current = current->next;
    }
    std::cout << "NULL" << std::endl;

    // 메모리 해제 (간략화)
    while(listHead) {
        Node* temp = listHead;
        listHead = listHead->next;
        delete temp;
    }

    return 0;
}

태그: C++ 네임스페이스 함수 오버로딩 기본값 매개변수 참조

6월 1일 10:06에 게시됨