학생 성적 관리 시스템 (STL 활용)
#include #include #include #include #include using namespace std;// 학생 ID별 과목 점수 관리 (map 활용)map> studentScores;// 과목별 학생 점수 관리 (vector 활용)map>> subjectScores;// 특정 과목의 점수 정렬을 위한 set (점수 내림차순, ID 오름차순)map, greater>>> sortedSubjectScores;// 학생 성적 추가 (점수 갱신 포함)void addScore(int id, string subject, int score) { if (score 100) { cout first; cout = minScore && p.second > choice; i..
2025.02.10
Unreal Engine에서 Tick 사용법
Tick이란?Unreal Engine에서 Tick은 매 프레임마다 호출되는 함수.Tick 함수는 주로 매 프레임마다 변화하는 로직을 처리하는 데 사용. 예: 캐릭터의 위치 업데이트, 물리 연산, UI 업데이트 등.Tick의 호출 시점과 속도기본적으로 Tick은 매 프레임마다 호출됨.프레임률에 따라 속도가 결정. (예: 60 FPS일 경우 한 번의 Tick은 약 0.0167초(1/60초))프레임률에 따라 Tick의 속도는 변할 수 있으며, 이를 통해 게임의 성능을 조정할 수 있다.Tick을 언제 사용하는가?게임의 상태가 매 프레임마다 업데이트되어야 할 때 사용.예: 캐릭터 이동, 물리 계산, 이벤트 발생 등.게임 루프의 일부로 지속적인 업데이트가 필요한 경우 사용.Tick을 어떻게 사용하나?Tick은 AA..
2025.01.08
챕터 3-2: Unreal Engine 기본 개념
수업 목표Unreal Engine의 개념과 구조를 이해하고 프로젝트 생성 방법 학습.Unreal Engine의 주요 기능(UE_LOG, Random 등) 활용 방법 익히기.언리얼 엔진 특징실시간 렌더링: 뛰어난 기술로 고품질 비주얼 구현 가능.블루프린트: 프로그래밍 지식 없이 게임 로직 제작 가능.다양한 플랫폼 지원: PC, 콘솔, 모바일 등 모두 지원.활성화된 커뮤니티: 튜토리얼과 포럼이 잘 활성화됨.언리얼 엔진 설치하기(생략) 환경셋업Visual Studio에서 " C++을 사용한 게임 개발" 도구 및 기능 설치Unreal Engine editor에서 라이브 코딩 활성화 해지(VS와 충돌 방지) Actor게임 내 배치 가능한 모든 객체를 의미.예: 비행기, RPG 주인공 등. C++클래스로 Actor..
2025.01.07
no image
챕터 3-1: 디자인 패턴
디자인 패턴이란?반복적으로 등장하는 개발 문제를 해결하기 위한 일반화된 솔루션. 디자인 패턴의 분류생성 패턴(Creational Patterns)객체 생성과 관련된 패턴.예: 싱글톤 패턴구조 패턴(Structural Patterns)객체 간의 관계를 구성하는 패턴.예: 데코레이터 패턴행동 패턴(Behavioral Patterns)객체 간의 상호작용을 정의하는 패턴.예: 옵저버 패턴싱글톤 패턴 (Singleton Pattern)목표객체를 오직 하나만 생성하고 전역적으로 접근 가능하도록 함.주요 개념생성자를 private으로 설정하여 외부에서 객체 생성을 제한.getInstance() 메서드를 통해 유일한 인스턴스에 접근.코드 예제#include using namespace std;class Airplane..
2025.01.06
챕터 2-4: 객체지향적 설계
1. 객체지향 설계의 중요성오픈소스 이해를 돕고 학습 효율 증가구현 시간 단축 및 유지보수 용이기능 변경에 유연함2. 응집도(Cohesion)정의: 클래스 내 모듈들이 얼마나 관련있는지높은 응집도: 관련된 기능만 포함낮은 응집도: 무관한 기능 섞임예시: 높은 응집도class PizzaDelivery: def calculate_route(self): # 배달 경로 계산 pass def estimate_time(self): # 배달 예상 시간 측정 pass def respond_to_customer(self): # 고객 문의 대응 pass3. 결합도(Coupling)정의: 모듈 간 의존성 정도낮은 결합도: 변경이 다른..
2025.01.03
no image
챕터 2-3 : STL 기초
더보기목표STL의 구성요소를 이해하고 효율적인 코드를 작성할 수 있다.컨테이너와 알고리즘을 활용하여 생산성을 높이는 프로그래밍 방식을 학습한다.STL의 개요C++ 표준 라이브러리로 컨테이너, 알고리즘, 반복자 등을 포함.템플릿 기반으로 다양한 데이터 타입에 대해 사용 가능.동적 메모리 관리를 내부적으로 처리.반복자를 통해 컨테이너 구현 방식과 관계없이 동일한 문법으로 접근 가능.컨테이너데이터를 저장하고 관리하기 위한 자료구조.사용 목적에 따라 다양한 형태 제공(배열형, 연결 리스트형, 키-값 쌍 등).템플릿으로 구현되어 타입에 상관없이 사용 가능.메모리 관리를 자동으로 처리.반복자 지원으로 내부 구현 방식과 관계없이 데이터 접근 가능.1. 벡터(Vector)동적 배열 형태의 컨테이너로 크기가 자동으로 조..
2025.01.02
no image
챕터 2-2 : 템플릿
1. 함수 오버로딩함수 오버로딩이란?C++에서 동일한 이름의 함수를 정의할 수 있는 기능.함수 이름뿐 아니라 매개변수의 타입과 개수를 기준으로 구분.C언어와 달리, C++은 함수 이름 + 매개변수 정보로 판단. 함수 오버로딩이 되는 조건매개변수 타입이 다를 경우.매개변수 개수가 다를 경우.예제: 매개변수 타입이 다른 경우void display(int value);void display(double value);void display(string value);​display(10) 호출 시: int 버전 실행.display(3.14) 호출 시: double 버전 실행.display("Hello!") 호출 시: string 버전 실행.예제: 매개변수 개수가 다른 경우void printSum(int a);vo..
2024.12.31
no image
챕터 2-1 : 자원 관리하기
1. 스택 메모리 일반 변수는 대부분 스택 메모리를 사용한다.스택 메모리는 변수의 생존 주기가 끝나면 자동으로 메모리를 회수한다.사용자가 별도로 메모리를 관리할 필요가 없다.(장점)변수의 생존 주기는 선언된 위치부터 가장 가까운 마침 괄호( } )까지다.#include using namespace std;void nestedFunction() { int b = 20; // 지역 변수 'b', stack 메모리에서 관리됨 cout  BUT, 단점: 스택은 메모리 영역 자체가 크지 않다. 생존 영역을 벗어나면 자동으로 해지 된다. 2. 힙 메모리힙 메모리는 위의 단점을 해결:선언 시 new 연산자를, 해제시 delete 연산자를 사용한다. 스택처럼 자동으로 해지하지 않는다. (리스크가 존재함) ..
2024.12.30

학생 성적 관리 시스템 (STL 활용)

언리얼 거토
|2025. 2. 10. 10:29

 

#include <iostream>
#include <map>
#include <vector>
#include <set>
#include <algorithm>

using namespace std;

// 학생 ID별 과목 점수 관리 (map 활용)
map<int, map<string, int>> studentScores;

// 과목별 학생 점수 관리 (vector 활용)
map<string, vector<pair<int, int>>> subjectScores;

// 특정 과목의 점수 정렬을 위한 set (점수 내림차순, ID 오름차순)
map<string, set<pair<int, int>, greater<pair<int, int>>>> sortedSubjectScores;

// 학생 성적 추가 (점수 갱신 포함)
void addScore(int id, string subject, int score) {
    if (score < 0 || score > 100) {
        cout << "점수는 0~100 사이여야 합니다!\n";
        return;
    }

    // 기존 점수 갱신
    studentScores[id][subject] = score;

    // 과목별 점수 리스트에서 학생 ID가 있으면 갱신
    bool found = false;
    for (auto& p : subjectScores[subject]) {
        if (p.first == id) {
            p.second = score;
            found = true;
            break;
        }
    }
    if (!found) {
        subjectScores[subject].push_back({ id, score });
    }

    // set에 점수 갱신 (기존 값 삭제 후 새 값 추가)
    sortedSubjectScores[subject].erase({ score, id });
    sortedSubjectScores[subject].insert({ score, id });

    cout << id << "번 학생 " << subject << " 점수 등록 완료: " << score << "점\n";
}

// 특정 학생의 전체 성적 조회
void viewStudentScores(int id) {
    if (studentScores.find(id) == studentScores.end()) {
        cout << "학생 " << id << "의 성적 정보가 없습니다.\n";
        return;
    }

    cout << "학생 " << id << " 성적 목록:\n";
    for (auto& it : studentScores[id]) {
        cout << "- " << it.first << ": " << it.second << "점\n"; // 과목명: 점수
    }
}

// 전체 학생의 과목별 평균 점수 출력
void printAverageScores() {
    if (subjectScores.empty()) {
        cout << "등록된 성적이 없습니다.\n";
        return;
    }

    cout << "과목별 평균 점수:\n";
    for (auto& subject : subjectScores) {
        double total = 0;
        int count = subject.second.size();
        for (auto& p : subject.second) {
            total += p.second;
        }
        double avg = total / count;
        cout << "- " << subject.first << ": " << avg << "점\n";
    }
}

// 특정 과목의 최고 점수 학생 조회 (동점자 포함)
void highestScoreStudent(string subject) {
    if (sortedSubjectScores.find(subject) == sortedSubjectScores.end() || sortedSubjectScores[subject].empty()) {
        cout << subject << " 과목의 성적 데이터가 없습니다.\n";
        return;
    }

    int maxScore = sortedSubjectScores[subject].begin()->first;
    cout << subject << " 최고 점수: " << maxScore << "점 (학생 ID: ";

    bool first = true;
    for (auto& p : sortedSubjectScores[subject]) {
        if (p.first == maxScore) {
            if (!first) cout << ", ";
            cout << p.second;
            first = false;
        }
        else {
            break;
        }
    }
    cout << ")\n";
}

// 특정 과목에서 점수 구간에 해당하는 학생 조회
void searchScoreRange(string subject, int minScore, int maxScore) {
    if (subjectScores.find(subject) == subjectScores.end()) {
        cout << subject << " 과목의 성적 데이터가 없습니다.\n";
        return;
    }

    cout << subject << "에서 " << minScore << " ~ " << maxScore << "점 학생 목록:\n";
    bool found = false;
    for (auto& p : subjectScores[subject]) {
        if (p.second >= minScore && p.second <= maxScore) {
            cout << "- 학생 ID: " << p.first << ", 점수: " << p.second << "점\n";
            found = true;
        }
    }
    if (!found) cout << "해당 점수 구간에 있는 학생이 없습니다.\n";
}

// 메뉴 시스템
void menu() {
    while (true) {
        cout << "\n학생 성적 관리 시스템\n";
        cout << "1. 학생 성적 추가\n";
        cout << "2. 학생 성적 조회\n";
        cout << "3. 과목별 평균 점수 출력\n";
        cout << "4. 과목별 최고 점수 학생 조회\n";
        cout << "5. 특정 점수 구간 학생 조회\n";
        cout << "6. 종료\n";
        cout << "선택: ";

        int choice;
        cin >> choice;

        if (choice == 1) {
            int id, score;
            string subject;
            cout << "학생 ID, 과목, 점수를 입력하세요 (예: 1001 C++ 85): ";
            cin >> id >> subject >> score;
            addScore(id, subject, score);
        }
        else if (choice == 2) {
            int id;
            cout << "조회할 학생 ID를 입력하세요: ";
            cin >> id;
            viewStudentScores(id);
        }
        else if (choice == 3) {
            printAverageScores();
        }
        else if (choice == 4) {
            string subject;
            cout << "조회할 과목을 입력하세요: ";
            cin >> subject;
            highestScoreStudent(subject);
        }
        else if (choice == 5) {
            string subject;
            int minScore, maxScore;
            cout << "조회할 과목과 점수 구간을 입력하세요 (예: C++ 80 90): ";
            cin >> subject >> minScore >> maxScore;
            searchScoreRange(subject, minScore, maxScore);
        }
        else if (choice == 6) {
            cout << "프로그램을 종료합니다.\n";
            break;
        }
        else {
            cout << "올바른 숫자를 입력하세요!\n";
        }
    }
}

int main() {
    menu();
    return 0;
}

1.  코드 개요

기능 설명
학생 성적 추가 학생 ID, 과목, 점수를 저장 (동일 과목 입력 시 점수 갱신)
학생 성적 조회 특정 학생의 모든 성적을 조회
과목별 평균 점수 출력 각 과목의 평균 점수를 계산 후 출력
과목별 최고 점수 학생 조회 특정 과목에서 최고 점수를 받은 학생 ID 조회
특정 점수 구간 학생 조회 특정 과목에서 점수가 지정된 범위에 속하는 학생 조회

 

2. STL 컨테이너 선택 이유

컨테이너사용 목적설명

컨테이너 사용 목적 설명
map<int, map<string, int>> 학생별 성적 관리 학생 ID별로 과목과 점수를 저장 (studentScores)
map<string, vector<pair<int, int>>> 과목별 성적 관리 과목별로 학생 ID와 점수를 저장 (subjectScores)
map<string, set<pair<int, int>,
greater<pair<int, int>>>>
과목별 성적 정렬 점수 내림차순 + 학생 ID 오름차순 정렬 (sortedSubjectScores)

3. 코드 상세 설명

(1) 학생 성적 저장을 위한 자료구조

// 학생 ID별 과목 점수 관리 (학생 기준 조회)
map<int, map<string, int>> studentScores;

// 과목별 학생 점수 관리 (과목 기준 조회)
map<string, vector<pair<int, int>>> subjectScores;

// 특정 과목의 점수 정렬을 위한 set (점수 내림차순, ID 오름차순)
map<string, set<pair<int, int>, greater<pair<int, int>>>> sortedSubjectScores;
 

studentScores

  • 학생 ID별로 여러 과목과 점수를 저장하는 map
  • 예: studentScores[1001]["C++"] = 85;

subjectScores

  • 과목별로 학생 ID와 점수를 저장하는 map
  • 예: subjectScores["C++"].push_back({1001, 85});

sortedSubjectScores

  • 특정 과목의 점수를 내림차순(큰 값 → 작은 값)으로 정렬하는 set
  • 예: sortedSubjectScores["C++"].insert({85, 1001});

(2) 학생 성적 추가 (addScore)

void addScore(int id, string subject, int score) {
    if (score < 0 || score > 100) {
        cout << "점수는 0~100 사이여야 합니다!\n";
        return;
    }

    // 기존 점수 갱신
    studentScores[id][subject] = score;

    // 과목별 점수 리스트에서 학생 ID가 있으면 갱신
    bool found = false;
    for (auto& p : subjectScores[subject]) {
        if (p.first == id) {
            p.second = score;
            found = true;
            break;
        }
    }
    if (!found) {
        subjectScores[subject].push_back({id, score});
    }

    // set에 점수 갱신 (기존 값 삭제 후 새 값 추가)
    sortedSubjectScores[subject].erase({score, id});
    sortedSubjectScores[subject].insert({score, id});

    cout << id << "번 학생 " << subject << " 점수 등록 완료: " << score << "점\n";
}
  • 입력된 점수가 0~100 사이인지 검사
  • 학생 ID 기준으로 점수 갱신 (studentScores)
  • 과목별 성적 데이터 갱신 (subjectScores)
  • 기존 점수를 삭제하고 새로운 점수를 추가 (sortedSubjectScores)

(3) 특정 학생의 전체 성적 조회 (viewStudentScores)

void viewStudentScores(int id) {
    if (studentScores.find(id) == studentScores.end()) {
        cout << "학생 " << id << "의 성적 정보가 없습니다.\n";
        return;
    }

    cout << "학생 " << id << " 성적 목록:\n";
    for (auto& it : studentScores[id]) {
        cout << "- " << it.first << ": " << it.second << "점\n";
    }
}
  • 학생 ID가 존재하는지 확인
  • 학생 ID가 있으면 해당 학생의 모든 과목과 점수를 출력
  • 없으면 "데이터 없음" 메시지 출력

(4) 과목별 평균 점수 출력 (printAverageScores)

void printAverageScores() {
    if (subjectScores.empty()) {
        cout << "등록된 성적이 없습니다.\n";
        return;
    }

    cout << "과목별 평균 점수:\n";
    for (auto& subject : subjectScores) {
        double total = 0;
        int count = subject.second.size();
        for (auto& p : subject.second) {
            total += p.second;
        }
        double avg = total / count;
        cout << "- " << subject.first << ": " << avg << "점\n";
    }
}
  • 등록된 성적이 있는지 확인
  • 각 과목별로 모든 점수를 더하고 평균 계산
  • 평균 점수를 출력

(5) 특정 과목의 최고 점수 학생 조회 (highestScoreStudent)

void highestScoreStudent(string subject) {
    if (sortedSubjectScores.find(subject) == sortedSubjectScores.end() || sortedSubjectScores[subject].empty()) {
        cout << subject << " 과목의 성적 데이터가 없습니다.\n";
        return;
    }

    int maxScore = sortedSubjectScores[subject].begin()->first;
    cout << subject << " 최고 점수: " << maxScore << "점 (학생 ID: ";

    bool first = true;
    for (auto& p : sortedSubjectScores[subject]) {
        if (p.first == maxScore) {
            if (!first) cout << ", ";
            cout << p.second;
            first = false;
        }
        else {
            break;
        }
    }
    cout << ")\n";
}

 

  • 해당 과목이 존재하는지 확인
  • 최고 점수를 찾고, 해당 점수를 받은 학생 ID 출력
  • 동점자도 함께 출력

4. 실행 예시

학생 성적 관리 시스템
1. 학생 성적 추가
2. 학생 성적 조회
3. 과목별 평균 점수 출력
4. 과목별 최고 점수 학생 조회
5. 특정 점수 구간 학생 조회
6. 종료
선택: 1
학생 ID, 과목, 점수를 입력하세요 (예: 1001 C++ 85): 1001 C++ 85
1001번 학생 C++ 점수 등록 완료: 85점

선택: 3
과목별 평균 점수:
- C++: 85점

선택: 4
조회할 과목을 입력하세요: C++
C++ 최고 점수: 85점 (학생 ID: 1001)

선택: 6
프로그램을 종료합니다.

'TIL > C++' 카테고리의 다른 글

Unreal Engine에서 Tick 사용법  (0) 2025.01.08
챕터 3-2: Unreal Engine 기본 개념  (2) 2025.01.07
챕터 3-1: 디자인 패턴  (0) 2025.01.06
챕터 2-4: 객체지향적 설계  (1) 2025.01.03
챕터 2-3 : STL 기초  (1) 2025.01.02

Unreal Engine에서 Tick 사용법

언리얼 거토
|2025. 1. 8. 23:00

 

Tick이란?

  • Unreal Engine에서 Tick은 매 프레임마다 호출되는 함수.
  • Tick 함수는 주로 매 프레임마다 변화하는 로직을 처리하는 데 사용.
    • 예: 캐릭터의 위치 업데이트, 물리 연산, UI 업데이트 등.
  • Tick의 호출 시점과 속도
    • 기본적으로 Tick은 매 프레임마다 호출됨.
    • 프레임률에 따라 속도가 결정. (예: 60 FPS일 경우 한 번의 Tick은 약 0.0167초(1/60초))
    • 프레임률에 따라 Tick의 속도는 변할 수 있으며, 이를 통해 게임의 성능을 조정할 수 있다.
  • Tick을 언제 사용하는가?
    • 게임의 상태가 매 프레임마다 업데이트되어야 할 때 사용.
    • 예: 캐릭터 이동, 물리 계산, 이벤트 발생 등.
    • 게임 루프의 일부로 지속적인 업데이트가 필요한 경우 사용.
  • Tick을 어떻게 사용하나?
    • Tick은 AActor 클래스의 함수로 구현.
      • 예를 들어, AMyActor2::Tick(float DeltaTime)와 같이 정의.
    • 매 프레임마다 호출되며, DeltaTime을 인자로 받아 프레임 간 시간 차이를 사용 가능.
  • Tick의 속도 조절 방법
    • PrimaryActorTick.TickInterval을 설정하여 Tick 간의 간격을 조정.
    • 예를 들어, PrimaryActorTick.TickInterval = 1.0f;로 설정하면, 1초에 한 번만 Tick을 호출.
  • Tick을 멈추는 방법
    • PrimaryActorTick.bCanEverTick = false;로 설정하여 Tick을 비활성화.
    • SetActorTickEnabled(true/false) <- 동적으로 Tick을 활성/비활성
    • 예를 들어, 일정 조건을 만족했을 때 Tick을 멈추고 싶을 때 사용.
  • Tick 사용 예시
    • AMyActor2 클래스에서 Tick을 사용하여 이동 로직과 이벤트를 발생시키는 예시
void AMyActor2::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
    
    // 이동 및 이벤트 발생
    int32 XStep = Step();
    int32 YStep = Step();
    NewPosition.X = CurrentPosition.X + XStep;
    NewPosition.Y = CurrentPosition.Y + YStep;
    
    // 이동 거리와 이벤트를 로그에 출력
    UE_LOG(LogTemp, Display, TEXT("Position: (%f, %f)"), NewPosition.X, NewPosition.Y);
    createEvent(50);

    CurrentPosition = NewPosition;
    CurrentStep++;

    // 모든 이동이 끝나면 Tick 비활성화
    if (CurrentStep >= 10)
    {
        PrimaryActorTick.bCanEverTick = false;
    }
}

 

 

Tick 속도 문제 해결하기

  • TickInterval을 Tick의 속도를 조절할 수 있다. 또는 DeltaTime을 이용해 프레임 독립적인 이동을 구현할 수도 있다.
  • 예를 들어, 이동을 DeltaTime에 비례하도록 하면 다양한 FPS에서도 일정한 속도로 이동할 수 있다.

'TIL > C++' 카테고리의 다른 글

학생 성적 관리 시스템 (STL 활용)  (0) 2025.02.10
챕터 3-2: Unreal Engine 기본 개념  (2) 2025.01.07
챕터 3-1: 디자인 패턴  (0) 2025.01.06
챕터 2-4: 객체지향적 설계  (1) 2025.01.03
챕터 2-3 : STL 기초  (1) 2025.01.02

챕터 3-2: Unreal Engine 기본 개념

언리얼 거토
|2025. 1. 7. 20:09

수업 목표

  • Unreal Engine의 개념과 구조를 이해하고 프로젝트 생성 방법 학습.
  • Unreal Engine의 주요 기능(UE_LOG, Random 등) 활용 방법 익히기.

언리얼 엔진 특징

  • 실시간 렌더링: 뛰어난 기술로 고품질 비주얼 구현 가능.
  • 블루프린트: 프로그래밍 지식 없이 게임 로직 제작 가능.
  • 다양한 플랫폼 지원: PC, 콘솔, 모바일 등 모두 지원.
  • 활성화된 커뮤니티: 튜토리얼과 포럼이 잘 활성화됨.

언리얼 엔진 설치하기

(생략)

 

환경셋업

  • Visual Studio에서 " C++을 사용한 게임 개발" 도구 및 기능 설치
  • Unreal Engine editor에서 라이브 코딩 활성화 해지(VS와 충돌 방지)

Actor

  • 게임 내 배치 가능한 모든 객체를 의미.
  • 예: 비행기, RPG 주인공 등.

C++클래스로 Actor 생성(한글 이름X) -> Visual Studio 자동 생성 -> UE_LOG 추가 -> 액터 배치(드래그) -> 출력로그 확인 

 

코드 예제: 369 게임

int Start = 1; 
int End = 30;

for (int i = Start; i <= End; ++i)
{
    FString Number = FString::FromInt(i);
    bool bIsClap = false;

    for (TCHAR Char : Number)
    {
        if (Char == '3' || Char == '6' || Char == '9')
        {
            bIsClap = true;
            break;
        }
    }

    if (bIsClap)
    {
        UE_LOG(LogTemp, Warning, TEXT("짝"));
    }
    else
    {
        UE_LOG(LogTemp, Warning, TEXT("%d"), i);
    }
}

 

코드 예제: 랜덤 숫자 합계 출력

int MinValue = 1;
int MaxValue = 100;

int RandomNumber1 = FMath::RandRange(MinValue, MaxValue);
int RandomNumber2 = FMath::RandRange(MinValue, MaxValue);
int RandomNumber3 = FMath::RandRange(MinValue, MaxValue);

int Sum = RandomNumber1 + RandomNumber2 + RandomNumber3;

UE_LOG(LogTemp, Warning, TEXT("Generated Numbers: %d, %d, %d"), RandomNumber1, RandomNumber2, RandomNumber3);
UE_LOG(LogTemp, Warning, TEXT("Sum of Numbers: %d"), Sum);

UE_LOG 사용법

  • 구조
    1. 카테고리: 로그를 분류하는 태그.
    2. 심각성: 로그의 중요도(색상으로 구분).
    3. 내용: 출력할 메시지.
      • 디버깅과 정보 확인에 매우 유용
  • 예시
UE_LOG(LogTemp, Log, TEXT("Game has started."));
UE_LOG(LogTemp, Display, TEXT("Welcome, %s!"), *PlayerName);
UE_LOG(LogTemp, Warning, TEXT("Player health is below maximum: %d"), PlayerHealth);
UE_LOG(LogTemp, Error, TEXT("No ammo left!"));

Unreal Engine + Visual Studio 사용 시 유의사항

프로젝트 관리

  • Unreal은 .uproject 파일 사용, Visual Studio의 .sln 파일은 사용하지 않음.
  • Unreal Editor나 Ctrl + Shift + B로 빌드.
  • Generated.h와 같은 자동 생성된 파일은 수정하지 않음.

Unreal에서의 코드 작성

  • 문자열은 TEXT() 매크로로 감싸기 (예: TEXT("Hello")).
  • 출력은 std::cout 대신 UE_LOG 사용.
  • Unreal 스타일 가이드에 맞게 UCLASS, UPROPERTY, UFUNCTION 매크로 사용.

헤더 파일과 소스 파일

클래스 선언은 헤더 파일에, 정의는 CPP 파일에 작성.

  • Unreal에서는 클래스를 헤더 파일에 선언하고, 메서드 구현은 CPP 파일에서 하는 것이 일반적.
  • 클래스 선언은 헤더 파일에, 정의는 CPP 파일에 작성.
    • Unreal에서는 클래스를 헤더 파일에 선언하고, 메서드 구현은 CPP 파일에서 하는 것이 일반적.
// Header file (.h)
UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()

public:
    void MyMethod();
};

// Source file (.cpp)
void AMyActor::MyMethod() 
{
    // method implementation
}
  • 헤더 파일에서의 선언은 클래스 인터페이스 제공.
  • CPP 파일에서 메서드 정의로 구현. 이렇게 하면 코드가 더 깔끔하고 컴파일 성능도 향상됨.

디버깅 및 로그

  • Breakpoints를 설정하여 Unreal Editor와 Visual Studio에서 디버깅 가능.
  • Saved/Logs 폴더에서 Crash 로그 확인.

기타 팁

  • Unreal의 API 문서를 활용하여 함수와 클래스 사용법 확인.
  • 작은 기능 추가 후 바로 Unreal Editor에서 핫 리로드로 테스트.

기타의 기타

  • 두 점 사이의 거리는 **FVector::Dist()**로 계산.
float Distance = FVector::Dist(PointA, PointB);
  • 다국어 지원 시 FText 사용.
FText WelcomeMessage = NSLOCTEXT("MyGame", "Welcome", "Welcome to the Game!");
  • 위치나 방향은 FVector, 문자열은 FString 사용.
FVector Location = FVector(0.0f, 0.0f, 0.0f);
FString PlayerStatus = FString::Printf(TEXT("Player: %s, Health: %d"), *PlayerName, Health);

'TIL > C++' 카테고리의 다른 글

학생 성적 관리 시스템 (STL 활용)  (0) 2025.02.10
Unreal Engine에서 Tick 사용법  (0) 2025.01.08
챕터 3-1: 디자인 패턴  (0) 2025.01.06
챕터 2-4: 객체지향적 설계  (1) 2025.01.03
챕터 2-3 : STL 기초  (1) 2025.01.02

챕터 3-1: 디자인 패턴

언리얼 거토
|2025. 1. 6. 19:53

디자인 패턴이란?
반복적으로 등장하는 개발 문제를 해결하기 위한 일반화된 솔루션.

 

디자인 패턴의 분류

  1. 생성 패턴(Creational Patterns)
    • 객체 생성과 관련된 패턴.
    • 예: 싱글톤 패턴
  2. 구조 패턴(Structural Patterns)
    • 객체 간의 관계를 구성하는 패턴.
    • 예: 데코레이터 패턴
  3. 행동 패턴(Behavioral Patterns)
    • 객체 간의 상호작용을 정의하는 패턴.
    • 예: 옵저버 패턴

싱글톤 패턴 (Singleton Pattern)

목표

  • 객체를 오직 하나만 생성하고 전역적으로 접근 가능하도록 함.

주요 개념

  1. 생성자를 private으로 설정하여 외부에서 객체 생성을 제한.
  2. getInstance() 메서드를 통해 유일한 인스턴스에 접근.

코드 예제

#include <iostream>
using namespace std;

class Airplane {
private:
    static Airplane* instance; // 유일한 인스턴스를 가리키는 정적 포인터
    int positionX;             // X 좌표
    int positionY;             // Y 좌표

    Airplane() : positionX(0), positionY(0) { // private 생성자
        cout << "Airplane Created at (" << positionX << ", " << positionY << ")" << endl;
    }

public:
    // 복사 방지: 복사 생성자와 대입 연산자 삭제
    Airplane(const Airplane&) = delete;
    Airplane& operator=(const Airplane&) = delete;

    // 유일한 인스턴스 반환
    static Airplane* getInstance() {
        if (instance == nullptr) { // 아직 생성되지 않았다면
            instance = new Airplane();
        }
        return instance;
    }

    void move(int deltaX, int deltaY) {
        positionX += deltaX;
        positionY += deltaY;
        cout << "Airplane moved to (" << positionX << ", " << positionY << ")" << endl;
    }

    void getPosition() const {
        cout << "Airplane Position: (" << positionX << ", " << positionY << ")" << endl;
    }
};

Airplane* Airplane::instance = nullptr;

int main() {
    Airplane* airplane = Airplane::getInstance();
    airplane->move(10, 20);
    airplane->getPosition();

    Airplane* sameAirplane = Airplane::getInstance();
    sameAirplane->move(-5, 10);
    sameAirplane->getPosition();

    return 0;
}

코드 설명

  • static 포인터로 유일한 객체를 관리.
  • getInstance()에서 처음 호출 시 객체를 생성하고 이후에는 동일한 객체를 반환.
  • 장점: 자원을 효율적으로 관리.
  • 단점: 멀티스레드 환경에서는 동기화 필요.

데코레이터 패턴 (Decorator Pattern)

목표

  • 객체의 기능을 동적으로 확장하거나 변경할 수 있도록 함.

주요 개념

  1. 기본 컴포넌트: 핵심 기능을 가진 클래스.
  2. 데코레이터 클래스: 기존 객체를 감싸 추가 기능을 제공.

코드 예제

#include <iostream>
#include <string>
using namespace std;

// 기본 피자 클래스
class Pizza {
public:
    virtual ~Pizza() {}
    virtual string getName() const = 0;
    virtual double getPrice() const = 0;
};

class BasicPizza : public Pizza {
public:
    string getName() const { return "Basic Pizza"; }
    double getPrice() const { return 5.0; }
};

class PizzaDecorator : public Pizza {
protected:
    Pizza* pizza;
public:
    PizzaDecorator(Pizza* p) : pizza(p) {}
    virtual ~PizzaDecorator() { delete pizza; }
};

class CheeseDecorator : public PizzaDecorator {
public:
    CheeseDecorator(Pizza* p) : PizzaDecorator(p) {}
    string getName() const { return pizza->getName() + " + Cheese"; }
    double getPrice() const { return pizza->getPrice() + 1.5; }
};

class PepperoniDecorator : public PizzaDecorator {
public:
    PepperoniDecorator(Pizza* p) : PizzaDecorator(p) {}
    string getName() const { return pizza->getName() + " + Pepperoni"; }
    double getPrice() const { return pizza->getPrice() + 2.0; }
};

int main() {
    Pizza* pizza = new BasicPizza();
    pizza = new CheeseDecorator(pizza);
    pizza = new PepperoniDecorator(pizza);

    cout << "Pizza: " << pizza->getName() << endl;
    cout << "Price: $" << pizza->getPrice() << endl;

    delete pizza;
    return 0;
}

코드 설명

  • PizzaDecorator가 Pizza 객체를 감싸 동적으로 기능 추가.
  • 장점: 기존 코드를 수정하지 않고도 기능 확장 가능.

옵저버 패턴 (Observer Pattern)

목표

  • 객체 상태가 변경되면, 이를 관찰하는 다른 객체들에게 자동으로 알림.

주요 개념

  1. Subject: 상태를 관리하며, 관찰자를 등록 및 관리.
  2. Observer: Subject의 상태 변경을 감지.

코드 예제

#include <iostream>
#include <vector>
using namespace std;

class Observer {
public:
    virtual void update(int data) = 0;
};

class ExcelSheet {
private:
    vector<Observer*> observers;
    int data;
public:
    void attach(Observer* observer) {
        observers.push_back(observer);
    }
    void setData(int newData) {
        data = newData;
        for (Observer* obs : observers) obs->update(data);
    }
};

class BarChart : public Observer {
public:
    void update(int data) {
        cout << "BarChart updated with data: " << data << endl;
    }
};

int main() {
    ExcelSheet sheet;
    BarChart barChart;
    sheet.attach(&barChart);

    sheet.setData(5);
    return 0;
}

코드 설명

  • ExcelSheet에서 데이터 변경 시 모든 Observer가 알림을 받음.
  • 장점: 느슨한 결합으로 객체 간 관계 관리.

'TIL > C++' 카테고리의 다른 글

Unreal Engine에서 Tick 사용법  (0) 2025.01.08
챕터 3-2: Unreal Engine 기본 개념  (2) 2025.01.07
챕터 2-4: 객체지향적 설계  (1) 2025.01.03
챕터 2-3 : STL 기초  (1) 2025.01.02
챕터 2-2 : 템플릿  (1) 2024.12.31

챕터 2-4: 객체지향적 설계

언리얼 거토
|2025. 1. 3. 20:43

1. 객체지향 설계의 중요성

  • 오픈소스 이해를 돕고 학습 효율 증가
  • 구현 시간 단축 및 유지보수 용이
  • 기능 변경에 유연함

2. 응집도(Cohesion)

  • 정의: 클래스 내 모듈들이 얼마나 관련있는지
  • 높은 응집도: 관련된 기능만 포함
  • 낮은 응집도: 무관한 기능 섞임

예시: 높은 응집도

class PizzaDelivery:
    def calculate_route(self):
        # 배달 경로 계산
        pass

    def estimate_time(self):
        # 배달 예상 시간 측정
        pass

    def respond_to_customer(self):
        # 고객 문의 대응
        pass

3. 결합도(Coupling)

  • 정의: 모듈 간 의존성 정도
  • 낮은 결합도: 변경이 다른 모듈에 영향 없음
  • 높은 결합도: 변경 시 다수의 모듈 수정 필요

예시: 낮은 결합도

class PaymentProcessor:
    def process_payment(self, payment_method):
        # 결제 처리 (의존성 최소화)
        payment_method.pay()

class CreditCard:
    def pay(self):
        # 카드 결제
        print("Paying with Credit Card")

processor = PaymentProcessor()
processor.process_payment(CreditCard())

4. SOLID 원칙

  • SRP: 하나의 클래스는 하나의 책임만
  • OCP: 확장에는 열림, 변경에는 닫힘
  • LSP: 부모 클래스는 자식으로 대체 가능
  • ISP: 클라이언트에 맞는 인터페이스 분리
  • DIP: 추상화에 의존, 구체화 피하기

4.1 SRP (단일 책임 원칙)

  • 정의: 클래스는 하나의 책임만 가져야 함.
  • 이유: 변경 이유를 하나로 제한해 유지보수 용이.

예시:

class Order:
    def add_item(self, item):
        # 주문 항목 추가
        pass

class Invoice:
    def generate_invoice(self, order):
        # 송장 생성
        pass

2. OCP (개방-폐쇄 원칙)

  • 정의: 확장에는 열려 있고, 변경에는 닫혀 있어야 함.
  • 이유: 기존 코드를 수정하지 않고 새로운 기능 추가 가능.

예시:

class Discount:
    def apply(self, price):
        return price

class SeasonalDiscount(Discount):
    def apply(self, price):
        return price * 0.9  # 10% 할인

class Cart:
    def __init__(self, discount: Discount):
        self.discount = discount

    def calculate_total(self, price):
        return self.discount.apply(price)

cart = Cart(SeasonalDiscount())
print(cart.calculate_total(100))

3. LSP (리스코프 치환 원칙)

  • 정의: 자식 클래스는 부모 클래스를 대체할 수 있어야 함.
  • 이유: 상속 구조에서 일관성 유지.

예시:

class Bird:
    def fly(self):
        print("I can fly!")

class Sparrow(Bird):
    pass

def let_bird_fly(bird: Bird):
    bird.fly()

sparrow = Sparrow()
let_bird_fly(sparrow)  # Sparrow도 Bird처럼 행동

4. ISP (인터페이스 분리 원칙)

  • 정의: 클라이언트에 불필요한 인터페이스를 강요하지 말아야 함.
  • 이유: 특정 요구에 맞춘 인터페이스 분리로 코드 유연성 증가.

예시:

class Printer:
    def print_document(self):
        pass

class Scanner:
    def scan_document(self):
        pass

class MultiFunctionDevice(Printer, Scanner):
    def print_document(self):
        print("Printing document")

    def scan_document(self):
        print("Scanning document")

5. DIP (의존성 역전 원칙)

  • 정의: 고수준 모듈은 저수준 모듈에 의존하지 않고, 둘 다 추상화에 의존해야 함.
  • 이유: 변경에 강한 구조 설계 가능.

예시:

class NotificationService:
    def send(self, message):
        pass

class EmailService(NotificationService):
    def send(self, message):
        print(f"Email sent: {message}")

class NotificationManager:
    def __init__(self, service: NotificationService):
        self.service = service

    def notify(self, message):
        self.service.send(message)

email_service = EmailService()
manager = NotificationManager(email_service)
manager.notify("Hello, Dependency Inversion!")

'TIL > C++' 카테고리의 다른 글

챕터 3-2: Unreal Engine 기본 개념  (2) 2025.01.07
챕터 3-1: 디자인 패턴  (0) 2025.01.06
챕터 2-3 : STL 기초  (1) 2025.01.02
챕터 2-2 : 템플릿  (1) 2024.12.31
챕터 2-1 : 자원 관리하기  (0) 2024.12.30

챕터 2-3 : STL 기초

언리얼 거토
|2025. 1. 2. 20:00

 

더보기

목표

  • STL의 구성요소를 이해하고 효율적인 코드를 작성할 수 있다.
  • 컨테이너와 알고리즘을 활용하여 생산성을 높이는 프로그래밍 방식을 학습한다.

STL의 개요

  • C++ 표준 라이브러리로 컨테이너, 알고리즘, 반복자 등을 포함.
  • 템플릿 기반으로 다양한 데이터 타입에 대해 사용 가능.
  • 동적 메모리 관리를 내부적으로 처리.
  • 반복자를 통해 컨테이너 구현 방식과 관계없이 동일한 문법으로 접근 가능.

컨테이너

  • 데이터를 저장하고 관리하기 위한 자료구조.
  • 사용 목적에 따라 다양한 형태 제공(배열형, 연결 리스트형, 키-값 쌍 등).
  • 템플릿으로 구현되어 타입에 상관없이 사용 가능.
  • 메모리 관리를 자동으로 처리.
  • 반복자 지원으로 내부 구현 방식과 관계없이 데이터 접근 가능.


1. 벡터(Vector)

  • 동적 배열 형태의 컨테이너로 크기가 자동으로 조정됨.
  • 임의 접근 가능하며 삽입과 삭제는 주로 맨 끝에서 수행하는 것이 효율적.
  • 배열과 달리 크기가 고정되지 않으며 다양한 메서드 제공.

1.1 벡터 선언 방법

  • 기본 생성 및 초기화
vector<int> vec1;                // 빈 벡터 생성
vector<int> vec2(5, 10);         // 크기 5, 모든 원소가 10으로 초기화
vector<int> vec3 = {1, 2, 3, 4, 5}; // 특정 값으로 초기화

 

  • 다른 벡터 복사 및 대입
vector<int> vec3 = {1, 2, 3, 4, 5};
vector<int> vec4(vec3);          // vec3의 복사본 생성
vector<int> vec5 = vec3;         // 복사 대입

  • 2차원 벡터 선언
vector<vector<int>> vec2D(3, vector<int>(4, 7)); // 3x4 행렬, 모든 원소가 7로 초기화

1.2 벡터 주요 메서드(동작)

  • push_back: 끝에 원소 추가
vector<int> vec;
vec.push_back(10);
vec.push_back(20);
vec.push_back(30);
// 출력: 10 20 30
  • pop_back: 끝에서 원소 제거
vector<int> vec = {10, 20, 30};
vec.pop_back(); // 마지막 요소(30) 제거
  • size: 현재 크기 확인
vector<int> vec = {10, 20, 30};
cout << vec.size(); // 출력: 3
  • erase: 특정 위치 원소 제거
vector<int> vec = {10, 20, 30, 40};
vec.erase(vec.begin() + 1); // 두 번째 원소(20) 제거

 


2. 맵

핸드폰 연락처를 생각해봅시다.

  • 연락처를 검색할 때 이름을 기준으로 전화번호를 찾는 방식과 유사.
  • 맵은 키(Key)를 기반으로 값(Value)을 저장하며, 배열이 인덱스를 사용하는 것과 유사한 역할 수행.

2.1 맵(Map)의 특징

  1. 키-값(Key-Value) 쌍으로 이루어진 컨테이너
    • 배열의 인덱스처럼 키를 사용해 값에 접근.
  2. 키 기준 자동 정렬
    • 사용자가 별도 정렬 작업을 하지 않아도 항상 정렬된 상태 유지.
  3. 중복된 키 허용 불가
    • 동일 키를 가진 데이터는 하나만 저장 가능.

2.2 맵 선언 방법

  • 맵 선언 시 두 가지 타입(키와 값)이 필요하며, 이 둘은 같아도 되고 달라도 됨.

코드 예제: 다양한 선언 방법

#include <map>
#include <vector>
using namespace std;

int main() {
    // 1. 기본 선언
    map<string, int> studentScores;

    // 2. 선언과 동시에 초기화
    map<int, string> idToName = {{1, "Alice"}, {2, "Bob"}, {3, "Charlie"}};

    // 3. 키: char, 값: double
    map<char, double> gradeToGPA;

    // 4. 키: int, 값: vector<int>
    map<int, vector<int>> studentMarks;

    // 5. 키: pair<int, int>, 값: string
    map<pair<int, int>, string> coordinateToName;

    return 0;
}

 

2.3 맵의 주요 동작

  • 키 기준 자동 정렬
    • 맵은 삽입된 데이터를 키 값 기준으로 자동 정렬.
map<int, string> myMap;
myMap[5] = "E";
myMap[2] = "B";
myMap[8] = "H";
myMap[1] = "A";

for (auto it = myMap.begin(); it != myMap.end(); ++it) {
    cout << it->first << ": " << it->second << endl;
}
// 출력: 1: A, 2: B, 5: E, 8: H
  • insert로 원소 추가
map<int, string> myMap;
myMap.insert(make_pair(1, "Apple"));
myMap.insert(make_pair(2, "Banana"));
  • find로 특정 키 검색
map<int, string> myMap = {{1, "Apple"}, {2, "Banana"}};
auto it = myMap.find(2);
if (it != myMap.end()) {
    cout << "Found: " << it->first << ": " << it->second << endl;
} else {
    cout << "Key not found!" << endl;
}
  • size로 크기 확인
cout << "Map size: " << myMap.size() << endl;
  • clear로 모든 원소 삭제
myMap.clear();
cout << "Map size after clear: " << myMap.size() << endl;
  • 맵 복사
    • 복사 생성자, 대입 연산자, insert 사용 가능.
map<int, string> originalMap = {{1, "Apple"}, {2, "Banana"}};
map<int, string> copiedMap1(originalMap); // 복사 생성자
map<int, string> copiedMap2 = originalMap; // 대입 연산자
map<int, string> copiedMap3;
copiedMap3.insert(originalMap.begin(), originalMap.end()); // insert 사용

알고리즘

C++ STL(Standard Template Library)은 컨테이너뿐만 아니라 이를 효과적으로 활용할 수 있는 다양한 알고리즘을 제공한다. 이 알고리즘들은 반복자(iterator)를 기반으로 동작하며, 컨테이너의 내부 구현을 알 필요 없이 동일한 방식으로 사용할 수 있다는 점이 큰 장점이다.

STL 알고리즘은 다음과 같은 작업을 지원한다:

  • 정렬(Sort): 데이터를 정렬하여 특정 기준에 맞게 배치.
  • 탐색(Find): 특정 값을 찾아 위치를 반환.
  • 수치 연산(Numeric): 데이터의 합계, 곱셈 등.
  • 변환(Transform): 데이터를 변환하여 새로운 결과 생성.

이를 통해 C++ 개발자는 반복적인 작업을 간결하고 효율적으로 구현할 수 있다. 이번 섹션에서는 가장 자주 사용되는 Sort(정렬)Find(탐색) 알고리즘을 중심으로 살펴보겠다.


1. Sort

정렬은 STL에서 가장 널리 사용되는 알고리즘 중 하나로, 컨테이너 내부의 데이터를 특정 기준에 따라 정렬한다. 오름차순이 기본 동작이며, 필요 시 사용자 정의 정렬 기준을 지정할 수 있다.

(1) 기본 타입 배열 정렬

  • 정렬 기준 없음 (오름차순 기본값):
#include <iostream>
#include <algorithm>
using namespace std;

int main() {
    int arr[] = {5, 2, 9, 1, 5, 6};
    int size = sizeof(arr) / sizeof(arr[0]);

    sort(arr, arr + size);

    for (int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
    return 0;
}
  • 정렬 기준 있음 (내림차순):
#include <iostream>
#include <algorithm>
using namespace std;

bool compare(int a, int b) {
    return a > b; // 내림차순 정렬
}

int main() {
    int arr[] = {5, 2, 9, 1, 5, 6};
    int size = sizeof(arr) / sizeof(arr[0]);

    sort(arr, arr + size, compare);

    for (int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
    return 0;
}

(2) 벡터 정렬

벡터 역시 배열과 동일한 방식으로 정렬 가능하다.

  • 오름차순 정렬:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    vector<int> vec = {5, 2, 9, 1, 5, 6};
    sort(vec.begin(), vec.end());

    for (int num : vec) {
        cout << num << " ";
    }
    return 0;
}
  • 사용자 정의 정렬 기준:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

bool compare(int a, int b) {
    return a > b; // 내림차순
}

int main() {
    vector<int> vec = {5, 2, 9, 1, 5, 6};
    sort(vec.begin(), vec.end(), compare);

    for (int num : vec) {
        cout << num << " ";
    }
    return 0;
}

(3) 사용자 정의 타입 정렬

사용자 정의 클래스나 구조체도 정렬할 수 있으며, 정렬 기준은 비교 함수로 정의한다.

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

class Person {
private:
    string name;
    int age;

public:
    Person(string name, int age) : name(name), age(age) {}
    string getName() const { return name; }
    int getAge() const { return age; }
};

bool compareByAgeAndName(const Person& a, const Person& b) {
    if (a.getAge() == b.getAge()) {
        return a.getName() < b.getName(); // 이름 기준
    }
    return a.getAge() < b.getAge(); // 나이 기준
}

int main() {
    vector<Person> people = {
        Person("Alice", 30),
        Person("Bob", 25),
        Person("Charlie", 35),
        Person("Alice", 25)
    };

    sort(people.begin(), people.end(), compareByAgeAndName);

    for (const Person& person : people) {
        cout << person.getName() << " (" << person.getAge() << ")" << endl;
    }
    return 0;
}

2. Find

STL의 find 함수는 컨테이너에서 특정 값을 검색한다. 값을 찾으면 해당 위치를 나타내는 반복자를 반환하며, 찾지 못하면 컨테이너의 끝 반복자를 반환한다.

(1) 벡터에서 값 찾기

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    vector<int> vec = {10, 20, 30, 40, 50};
    auto it = find(vec.begin(), vec.end(), 30);

    if (it != vec.end()) {
        cout << "값 30이 벡터에서 발견됨, 위치: " << (it - vec.begin()) << endl;
    } else {
        cout << "값 30이 벡터에 없음" << endl;
    }
    return 0;
}

(2) 배열에서 값 찾기

#include <iostream>
#include <algorithm>
using namespace std;

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int size = sizeof(arr) / sizeof(arr[0]);
    auto it = find(arr, arr + size, 4);

    if (it != arr + size) {
        cout << "값 4가 배열에서 발견됨, 위치: " << (it - arr) << endl;
    } else {
        cout << "값 4가 배열에 없음" << endl;
    }
    return 0;
}

(3) 문자열에서 문자 찾기

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

int main() {
    string str = "hello world";
    auto it = find(str.begin(), str.end(), 'o');

    if (it != str.end()) {
        cout << "문자 'o'가 문자열에서 발견됨, 위치: " << (it - str.begin()) << endl;
    } else {
        cout << "문자 'o'가 문자열에 없음" << endl;
    }
    return 0;
}

반복자 (Iterators)

  • **반복자(iterator)**는 C++에서 컨테이너의 원소를 순차적으로 탐색하는 객체로, 컨테이너의 구현에 관계없이 알고리즘을 동일하게 적용할 수 있게 해준다. 이를 통해 코드의 재사용성과 효율성을 높일 수 있다.

1. 순방향 반복자 (Forward Iterator)

  • 기능: 컨테이너의 원소를 처음부터 끝까지 순차적으로 탐색.
  • 시작 위치: begin()
  • 끝 위치: end()
  • 특징: end()는 탐색 범위의 끝을 나타내며, 찾을 수 없을 경우 반환된다.

예시 1: 벡터에서 짝수만 출력

#include <vector>
#include <iostream>
using namespace std;

int main() {
    vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // 순방향 반복자로 짝수만 출력
    for (auto it = numbers.begin(); it != numbers.end(); ++it) {
        if (*it % 2 == 0) {  // 짝수 판별
            cout << *it << " ";  // 출력
        }
    }
    return 0;
}
// 출력: 2 4 6 8 10

예시 2: 맵에서 키-값 쌍 출력

#include <map>
#include <iostream>
using namespace std;

int main() {
    map<string, int> scores = {{"Alice", 90}, {"Bob", 85}, {"Charlie", 88}};

    // 순방향 반복자를 사용해 키-값 쌍 출력
    for (auto it = scores.begin(); it != scores.end(); ++it) {
        cout << it->first << ": " << it->second << endl;  // 키와 값 출력
    }
    return 0;
}
// 출력:
// Alice: 90
// Bob: 85
// Charlie: 88

2. 역방향 반복자 (Reverse Iterator)

  • 기능: 컨테이너의 원소를 끝에서부터 처음까지 순차적으로 탐색.
  • 시작 위치: rbegin()
  • 끝 위치: rend()
  • 특징: rend()는 첫 번째 원소 이전을 나타내며, 뒤에서부터 찾지 못한 경우 반환된다.

예시 1: 벡터에서 짝수만 출력 (역방향)

#include <vector>
#include <iostream>
using namespace std;

int main() {
    vector<int> numbers = {10, 15, 20, 25, 30};

    // 역방향 반복자로 짝수만 출력
    for (auto it = numbers.rbegin(); it != numbers.rend(); ++it) {
        if (*it % 2 == 0) {  // 짝수 판별
            cout << *it << " ";  // 출력
        }
    }
    return 0;
}
// 출력: 30 20 10

예시 2: 맵에서 값이 88 이상인 항목만 출력 (역방향)

#include <map>
#include <iostream>
using namespace std;

int main() {
    map<string, int> scores = {{"Alice", 90}, {"Bob", 85}, {"Charlie", 88}};

    // 역방향 반복자로 값이 88 이상인 항목만 출력
    for (auto it = scores.rbegin(); it != scores.rend(); ++it) {
        if (it->second >= 88) {  // 값이 88 이상인 항목만 출력
            cout << it->first << ": " << it->second << endl;  // 출력
        }
    }

    return 0;
}
// 출력:
// Charlie: 88
// Alice: 90

3. find()와 역방향 반복자

  • 기능: find() 함수는 지정된 값을 찾는 데 사용되며, 역방향 반복자와 함께 사용할 경우 뒤에서부터 탐색이 진행된다.

예시 1: 역방향에서 문자열 찾기

#include <vector>
#include <string>
#include <algorithm>
#include <iostream>
using namespace std;

int main() {
    vector<string> words = {"apple", "banana", "cherry", "date"};
    string target = "banana";

    // 역방향에서 "banana" 찾기
    auto it = find(words.rbegin(), words.rend(), target);

    if (it != words.rend()) {
        cout << "Word \"" << target << "\" found at reverse index " 
             << distance(words.rbegin(), it) << " (from the back)" << endl;
        cout << "Word \"" << target << "\" found at forward index " 
             << distance(words.begin(), it.base()) - 1 << " (from the front)" << endl;
    } else {
        cout << "Word \"" << target << "\" not found." << endl;
    }

    return 0;
}
// 출력:
// Word "banana" found at reverse index 2 (from the back)
// Word "banana" found at forward index 1 (from the front)

↓ 숙제: 반복자를 화용하여 각 컨테이너를 순회하기

더보기
  • 숙제 설명아래 코드스니펫에 작성된 초기화된 벡터와 맵을 순방향 반복자와 역방향 반복자를 활용해서 출력하는 코드를 작성하세요
  • STL 컨테이너는 반복자를 통해 거의 동일한 코드로 내부 구현에 의존하지 않고 순회할수 있다는 것을 배웠습니다.

#include <iostream>
#include <vector>
#include <map>

using namespace std;

int main() {
    // 벡터와 맵 데이터 정의
    vector<int> vec = { 10, 20, 30, 40, 50 };
    map<string, int> mp = {
        {"Alice", 90},
        {"Bob", 85},
        {"Charlie", 95}
    };

    // 문제: 아래 부분을 완성하세요

    return 0;
}

 

답:

#include <iostream>
#include <vector>
#include <map>

using namespace std;

int main() {
    // 벡터와 맵 데이터 정의
    vector<int> vec = { 10, 20, 30, 40, 50 };
    map<string, int> mp = {
        {"Alice", 90},
        {"Bob", 85},
        {"Charlie", 95}
    };

    // 문제: 아래 부분을 완성하세요

    // 벡터 출력
    cout << "Vector:\n";
    cout << "[Forward]: ";
    for (vector<int>::const_iterator it = vec.begin(); it != vec.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;

    cout << "[Backward]: ";
    for (vector<int>::const_reverse_iterator rit = vec.rbegin(); rit != vec.rend(); ++rit) {
        cout << *rit << " ";
    }
    cout << endl;

    // 맵 출력
    cout << "\nMap:\n";
    cout << "[Forward]: ";
    for (map<string, int>::const_iterator it = mp.begin(); it != mp.end(); ++it) {
        cout << "(" << it->first << ", " << it->second << ") ";
    }
    cout << endl;

    cout << "[Backward]: ";
    for (map<string, int>::const_reverse_iterator rit = mp.rbegin(); rit != mp.rend(); ++rit) {
        cout << "(" << rit->first << ", " << rit->second << ") ";
    }
    cout << endl;

    return 0;
}

'TIL > C++' 카테고리의 다른 글

챕터 3-1: 디자인 패턴  (0) 2025.01.06
챕터 2-4: 객체지향적 설계  (1) 2025.01.03
챕터 2-2 : 템플릿  (1) 2024.12.31
챕터 2-1 : 자원 관리하기  (0) 2024.12.30
C++ 추가 자료  (1) 2024.12.27

챕터 2-2 : 템플릿

언리얼 거토
|2024. 12. 31. 18:00

1. 함수 오버로딩

함수 오버로딩이란?

  • C++에서 동일한 이름의 함수를 정의할 수 있는 기능.
  • 함수 이름뿐 아니라 매개변수의 타입개수를 기준으로 구분.
  • C언어와 달리, C++은 함수 이름 + 매개변수 정보로 판단. <- 시그니쳐

함수 오버로딩이 되는 조건

  • 매개변수 타입이 다를 경우.
  • 매개변수 개수가 다를 경우.

예제: 매개변수 타입이 다른 경우

void display(int value);
void display(double value);
void display(string value);​
  • display(10) 호출 시: int 버전 실행.
  • display(3.14) 호출 시: double 버전 실행.
  • display("Hello!") 호출 시: string 버전 실행.

예제: 매개변수 개수가 다른 경우

void printSum(int a);
void printSum(int a, int b);
void printSum(int a, int b, int c);
  • printSum(10) 호출: 매개변수 1개 버전 실행.
  • printSum(10, 20) 호출: 매개변수 2개 버전 실행.
  • printSum(10, 20, 30) 호출: 매개변수 3개 버전 실행.

함수 오버로딩이 불가능한 경우

  • 반환 타입만 다를 경우: 컴파일 에러
int getValue();
double getValue(); // 반환 타입만 다름 -> 오류.
  • 디폴트 매개변수로 모호한 경우
void printMessage(string message = "Default");
void printMessage(); // 호출 모호 -> 오류.
  • 포인터와 배열로만 차이 나는 경우
void process(int* arr);
void process(int arr[]); // 포인터와 배열 구분 불가 -> 오류.

 

함수 오버로딩 호출 순서

 

1. 정확히 일치하는 타입을 먼저 찾음.

2. 암묵적 타입 변환이 가능한 함수로 이동.

 

예제: 호출 순서

#include <iostream>
using namespace std;

void process(int value) {
    cout << "(int) 호출" << value << endl;
}

void process(int value, int value2) {
    cout << "(int, int) 호출" << value * value2 << endl;
}

void process(float value) {
    cout << "(float) 호출" << value << endl;
}
 void process(float value, float value2) {
    cout << "(float, float) 호출" << value * value2 << endl;
}


int main() {

    process(10);    //(int) 호출
    process(3, 4.3);//(int, int) 호출

    return 0;
}

2. 템플릿 (Template)

템플릿이란?

  • 타입에 구애받지 않는 일반화된 코드 작성을 위한 문법.
  • 문법:
template <typename T>
  • T는 타입 파라미터로, 실제 타입으로 대체됨.
  • 함수나 클래스를 타입에 상관없이 정의 가능.

템플릿 함수 예제

  • 두 수 더하기
template <typename T>
T add(T x, T y) {
    return x + y;
}

int main() {
    cout << add(3, 4) << endl;         // 정수형 덧셈
    cout << add(3.3, 4.2) << endl;     // 실수형 덧셈
    return 0;
}
  • 최댓값 구하기
template <typename T>
T getMax(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    cout << getMax(10, 20) << endl;     // 정수형
    cout << getMax(3.5, 2.7) << endl;   // 실수형
    cout << getMax('a', 'z') << endl;   // 문자형
    return 0;
}
  • 배열 합 구하기
template <typename T>
T sumArray(T arr[], int size) {
    T sum = 0;
    for (int i = 0; i < size; ++i) {
        sum += arr[i];
    }
    return sum;
}

int main() {
    int intArr[] = {1, 2, 3, 4, 5};
    double doubleArr[] = {1.1, 2.2, 3.3, 4.4};
    cout << sumArray(intArr, 5) << endl;     // 정수형 배열
    cout << sumArray(doubleArr, 4) << endl;  // 실수형 배열
    return 0;
}

템플릿 활용 예제

  • 값 교환하기
template <typename T>
void swapValues(T &a, T &b) {
    T temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 10, y = 20;
    swapValues(x, y);  // 정수형
    cout << "x = " << x << ", y = " << y << endl;

    double p = 3.14, q = 2.71;
    swapValues(p, q);  // 실수형
    cout << "p = " << p << ", q = " << q << endl;
    return 0;
}
  • 값 비교하기
template <typename T>
bool isEqual(T a, T b) {
    return a == b;
}

int main() {
    cout << boolalpha;
    cout << isEqual(10, 10) << endl;   // 정수 비교
    cout << isEqual(3.5, 2.7) << endl;  // 실수 비교
    cout << isEqual('a', 'b') << endl;  // 문자 비교
    return 0;
}

템플릿 클래스 예제

  • 배열 관리 클래스
template <typename T>
class MyArray {
private:
    T* arr;
    int capacity;
    int size;

public:
    MyArray(int cap) : capacity(cap), size(0) {
        arr = new T[capacity];
    }

    ~MyArray() {
        delete[] arr;
    }

    void push(T value) {
        if (size < capacity) {
            arr[size] = value;
            size++;
        } else {
            cout << "배열이 가득 찼습니다!" << endl;
        }
    }

    void pop() {
        if (size > 0) {
            size--;
        } else {
            cout << "배열이 비어 있습니다!" << endl;
        }
    }

    void print() const {
        for (int i = 0; i < size; i++) {
            cout << arr[i] << " ";
        }
        cout << endl;
    }
};

int main() {
    MyArray<int> intArr(5);
    intArr.push(10);
    intArr.push(20);
    intArr.print();  // 정수형 배열

    MyArray<double> doubleArr(3);
    doubleArr.push(3.14);
    doubleArr.push(2.71);
    doubleArr.print();  // 실수형 배열
    return 0;
}

 

↓숙제: 오버로딩 된 함수 템플릿으로 변경하기

더보기

숙제 설명

  • 오버로딩된 함수를 템플릿으로 변경 해봅시다.
  • 우리는 타입이 동일한 두개의 인자를 받고 이를 더해서 반환하는 템플릿 함수를 구현해야 합니다.
  • 기존에 제공된 오버로딩 된 함수를 분석해서 하나의 템플릿 함수로 구현하시면 됩니다.
  • 템플릿 함수로 변경해야 할 오버로딩 된 함수
#include <iostream>

using namespace std;

//아래 3개의 함수를 하나의 템플릿 함수로 통합하세요
int add(int a, int b) {
    return a + b;
}

double add(double a, double b) {
    return a + b;
}

std::string add(const std::string& a, const std::string& b) {
    return a + b;
}


//아래 테스트 코드는 변역하지 마세요
int main() {
    // 정수 더하기
    cout << "3 + 5 = " << add(3, 5) << endl;

    // 실수 더하기
    cout << "2.5 + 4.3 = " << add(2.5, 4.3) << endl;

    // 문자열 합치기
    cout << "\"Hello, \" + \"World!\" = " << add(string("Hello, "), string("World!")) << endl;

    // 아래 코드는 컴파일 에러가 발생해야 함
    // cout << add(true, false) << endl;

    return 0;
}

 

답:

template<typename T>
T add(T a, T b) {
    return a + b;
}

'TIL > C++' 카테고리의 다른 글

챕터 2-4: 객체지향적 설계  (1) 2025.01.03
챕터 2-3 : STL 기초  (1) 2025.01.02
챕터 2-1 : 자원 관리하기  (0) 2024.12.30
C++ 추가 자료  (1) 2024.12.27
챕터 1-3 : 객체지향 프로그래밍  (1) 2024.12.26

챕터 2-1 : 자원 관리하기

언리얼 거토
|2024. 12. 30. 16:03

1. 스택 메모리

 

  • 일반 변수는 대부분 스택 메모리를 사용한다.
  • 스택 메모리는 변수의 생존 주기가 끝나면 자동으로 메모리를 회수한다.
    • 사용자가 별도로 메모리를 관리할 필요가 없다.(장점)
  • 변수의 생존 주기는 선언된 위치부터 가장 가까운 마침 괄호( } )까지다.
#include <iostream>
using namespace std;

void nestedFunction() {
    int b = 20;  // 지역 변수 'b', stack 메모리에서 관리됨
    cout << "nestedFunction: b = " << b << endl;
}  // 'b'는 nestedFunction() 종료 후 소멸됨

void functionExample() {
    int a = 10;  // 지역 변수 'a', stack 메모리에서 관리됨
    cout << "functionExample: a = " << a << endl;

    for (int i = 0; i < 3; ++i) {  // 반복문 내에서 매번 생성되는 지역 변수 'i'
        int temp = i * 10;  // 반복문 내에서만 유효한 'temp'
        cout << "Iteration " << i << ": temp = " << temp << endl;
    }  // 반복문이 끝날 때마다 'temp'는 소멸됨

    nestedFunction();  // 중첩 함수 호출
}  // 'a'는 functionExample() 종료 후 소멸됨

int main() {
    functionExample();  // functionExample() 호출
    return 0;
}

 

  • BUT, 단점:
    • 스택은 메모리 영역 자체가 크지 않다.
    • 생존 영역을 벗어나면 자동으로 해지 된다.

 

2. 힙 메모리

힙 메모리는 위의 단점을 해결:

  • 선언 시 new 연산자를, 해제시 delete 연산자를 사용한다.
  • 스택처럼 자동으로 해지하지 않는다. (리스크가 존재함)
  • 생존주기는 사용자가 선언한 순간부터 해지하기 전 까지이다.

#include <iostream>
using namespace std;

void handleDynamicMemory() {
    int size;
    cout << "배열 크기를 입력하세요: ";
    cin >> size;  // 사용자로부터 배열 크기 입력받기

    if (size > 0) {
        int* arr = new int[size];  // 배열 크기만큼 동적 할당
        for (int i = 0; i < size; ++i) {
            arr[i] = i * 10;  // 배열 초기화
            cout << "arr[" << i << "] = " << arr[i] << endl;
        }
        delete[] arr;  // 동적 할당한 배열 메모리 해제
    } else {
        cout << "잘못된 크기입니다!" << endl;
    }

    int* ptr = new int(20);  // 정수 하나 동적 할당
    cout << "값: " << *ptr << endl;
    delete ptr;  // 동적 할당한 메모리 해제
}

int main() {
    handleDynamicMemory();
    return 0;
}

 

3. Dangling Pointer

 

  • 비유 설명:
    • 어떤 식당을 가려고 하는데, 이미 그 식당이 철거된 상황이라 가게에 도착했을 때 허탕을 치는 상황.
  • C++에서의 유사한 문제:
    • 메모리 해지 후, 해지된 메모리 영역에 대한 정보가 특별히 알림 없이 그대로 남아 있을 수 있음.
  • 위험성:
    • 해지된 메모리 영역을 참조하면, 예상치 못한 결과를 초래할 수 있음.
    • 이런 포인터를 **Dangling Pointer**라고 부름.

 

#include <iostream>
using namespace std;

void func() {
    int* ptr = new int(30);  // 정수 30에 대한 메모리 할당
    int* ptr2 = ptr;         // 같은 메모리를 가리키는 두 번째 포인터

    cout << "삭제 전 값: " << *ptr << endl;

    delete ptr;              // 첫 번째 해제

    // 아래 라인을 주석 해제하면 이중 해지 오류가 발생합니다
    // delete ptr;            // 두 번째 해지 (이중 해지 오류)

    cout << "첫 번째 삭제 후 값: " << *ptr2 << endl;  // Dangling Pointer 사용
}

int main() {
    func();
    return 0;
}

 

↓ 숙제 1: 메모리 누수 발생 코드 분석, 보완 

더보기
#include <iostream>
using namespace std;

class MyClass {
private:
    int* ptr;

public:
    // 생성자
    MyClass() {
        ptr = new int(10); // 동적 메모리 할당
        cout << "메모리 할당 완료!" << endl;
    }

    // 소멸자
    ~MyClass() {
      
    }

    void print() const {
        cout << "값: " << *ptr << endl;
    }
};

int main() {
    MyClass obj;
    obj.print();

    // main 함수 종료
    return 0;
}

 

답: 

 ~MyClass() {
        delete ptr;  // 동적 메모리 해제 (ptr이 가리키는 메모리)
        cout << "메모리 해제 완료!" << endl;
    }

main 함수 종료 시, obj의 소멸자가 호출되어 메모리 해제가 이루어진다. 그러나 소멸자에서 delete ptr;가 없으면, ptr이 가리키는 동적 메모리가 해제되지 않아 메모리 누수가 발생한다. 동적 메모리를 할당할 때는 반드시 소멸자에서 메모리를 해제해야 한다.


4. 스마트 포인터(unique_ptr & shared_ptr)

 

  • Heap은 여러 가지 장점이 있지만, 메모리를 직접 관리해야 하는 부담이 있음.
  • Dangling Pointer 방지: 자동으로 메모리 관리가 되어 Dangling Pointer가 발생하지 않도록 하는 방식이 필요함.
  • C++의 스마트 포인터: 스마트 포인터는 레퍼런스 카운터를 이용해 메모리를 자동으로 관리함.
  • 스마트 포인터의 원리:
    • delete를 직접 호출하지 않고,
    • 자신을 참조하는 포인터의 개수가 0이 되면 자동으로 메모리를 해제함.

 

4.1 unique_ptr

 

  • 레퍼런스 카운터가 최대 1인 스마트 포인터.
  • 소유권을 한 번에 하나의 객체만 가질 수 있음, 안전하게 관리
  • 복사 불가능: unique_ptr은 복사나 대입이 불가능하며, 이를 시도하면 컴파일 에러가 발생함.
  • 소유권 이전 가능: move를 사용하여 소유권을 다른 unique_ptr로 이전할 수 있음.
  • 자동 메모리 해제: unique_ptr은 범위를 벗어나면 자동으로 메모리를 해제하여 관리 용이.

 

#include <iostream>
#include <memory>  // unique_ptr 사용

using namespace std;

class MyClass {
public:
    MyClass(int val) : value(val) {
        cout << "MyClass 생성: " << value << endl;
    }
    ~MyClass() {
        cout << "MyClass 소멸: " << value << endl;
    }
    void display() const {
        cout << "값: " << value << endl;
    }

private:
    int value;
};

int main() {
    // 1. 기본적인 unique_ptr 사용
    unique_ptr<int> ptr1 = make_unique<int>(10);  
    cout << "ptr1의 값: " << *ptr1 << endl;

    // 2. unique_ptr 소유권 이동
    unique_ptr<int> ptr2 = move(ptr1);  
    if (!ptr1) {
        cout << "ptr1은 이제 비어 있습니다." << endl;
    }
    cout << "ptr2의 값: " << *ptr2 << endl;

    // 3. 일반 클래스에서 unique_ptr 사용
    unique_ptr<MyClass> myObject = make_unique<MyClass>(42);  
    myObject->display();

    unique_ptr<MyClass> newOwner = move(myObject);
    if (!myObject) {
        cout << "myObject는 이제 비어 있습니다." << endl;
    }
    newOwner->display();

    return 0;
}

 

  • 기본 사용법:
    • unique_ptr<int> ptr1 = make_unique<int>(10);
      unique_ptr을 사용하여 동적 메모리 할당.
    • *ptr1로 값을 출력하고, 소유권 이동은 move를 통해 처리.
  • 소유권 이동 (move):
    • unique_ptr은 소유권을 이동할 수 있으며, move(ptr1)로 ptr1의 소유권을 ptr2로 이동.
    • 이동 후 ptr1은 비어있게 됨.
  • 클래스에서 사용:
    • unique_ptr<MyClass>를 사용하여 객체를 관리하고, 범위를 벗어나면 자동 메모리 해제가 이루어짐.

 

4.2 shared_ptr

 

  • 여러 포인터가 동일한 메모리를 공유하는 상황에서 유용
    • 레퍼런스 카운트가 여러 개 존재할 수 있는 스마트 포인터.
    • 복사 및 대입이 가능하고, use_count()로 참조 카운트를 확인할 수 있음. 
  • reset()을 통해 포인터를 초기화하고 참조 카운트를 조정할 수 있음.
  • 범위를 벗어나면 자동 해제됨.
#include <iostream>
#include <memory>  // shared_ptr 사용
using namespace std;

class MyClass {
public:
    MyClass(int val) : value(val) {
        cout << "MyClass 생성: " << value << endl;
    }
    ~MyClass() {
        cout << "MyClass 소멸: " << value << endl;
    }
    void display() const {
        cout << "값: " << value << endl;
    }
private:
    int value;
};

int main() {
    // 1. 기본적인 shared_ptr 사용
    shared_ptr<int> ptr1 = make_shared<int>(10);
    cout << "ptr1의 참조 카운트: " << ptr1.use_count() << endl;  // 출력: 1

    // 2. 복사 및 참조 카운트 증가
    shared_ptr<int> ptr2 = ptr1;
    cout << "ptr2 생성 후 참조 카운트: " << ptr1.use_count() << endl;  // 출력: 2

    // 3. reset을 통한 참조 카운트 감소
    ptr2.reset();
    cout << "ptr2 해제 후 참조 카운트: " << ptr1.use_count() << endl;  // 출력: 1

    // 4. 클래스에서 shared_ptr 사용
    shared_ptr<MyClass> obj1 = make_shared<MyClass>(42);
    shared_ptr<MyClass> obj2 = obj1;
    cout << "obj1과 obj2의 참조 카운트: " << obj1.use_count() << endl;  // 출력: 2

    obj2->display();  // 출력: 값: 42

    // 5. obj2 해제 후 obj1이 객체를 유지
    obj2.reset();
    cout << "obj2 해제 후 obj1의 참조 카운트: " << obj1.use_count() << endl;  // 출력: 1

    return 0;
}

 

기본 사용법

  • shared_ptr<int> ptr1 = make_shared<int>(10);
    shared_ptr을 사용해 동적 메모리 할당.
  • use_count()로 참조 카운트를 확인할 수 있으며, 참조 카운트의 변화에 따라 리소스 공유 상태를 알 수 있음.

복사 및 참조 공유

  • shared_ptr은 복사 가능하며, 여러 포인터가 하나의 리소스를 공유함.
    • 예: shared_ptr<int> ptr2 = ptr1;
  • 복사 후 참조 카운트가 증가하고, 동일 객체를 여러 포인터가 공유하게 됨.

reset()으로 참조 카운트 관리

  • reset()을 사용하여 shared_ptr을 초기화하고, 참조 카운트를 조정할 수 있음.
    • 예: ptr2.reset(); — ptr2를 해제하고 ptr1의 참조 카운트를 유지.

클래스에서의 사용

  • shared_ptr은 클래스를 관리할 때도 동일하게 사용됨. 여러 포인터가 동일한 객체를 공유하고, 참조 카운트에 따라 메모리가 자동으로 해제됨.
  • 객체를 관리하는 클래스에서 shared_ptr을 사용할 때, 포인터가 소멸하면 객체도 자동으로 해제됨.

5. 얕은 복사와 깊은 복사

얕은 복사(Shallow Copy)

  • 얕은 복사는 대입 연산자를 사용하여 두 포인터가 동일한 메모리 위치를 공유하는 방식.
    • 예: int* B = A; — B는 A가 가리키는 메모리 영역을 공유.
  • 이 방식에서 A가 메모리를 해제하면 B는 더 이상 유효한 메모리를 가리키지 않게 되어 Dangling Pointer가 발생할 수 있음.
  • Dangling Pointer는 해제된 메모리를 참조하게 되므로 Undefined Behavior를 일으킬 수 있음.

#include <iostream>
using namespace std;

int main() {
    int* A = new int(30);  // A가 30을 가리키는 메모리 할당
    int* B = A;            // B는 A가 가리키는 메모리 공유

    cout << "A의 값: " << *A << endl; // 출력: 30
    cout << "B의 값: " << *B << endl; // 출력: 30

    delete A;  // A가 메모리 해제

    cout << "B의 값 (dangling): " << *B << endl; // 위험: 정의되지 않은 동작
    return 0;
}

 

 

깊은 복사(Deep Copy)

  • 깊은 복사는 독립적인 메모리 영역을 새로 할당하고, 그 내용만 복사하는 방식.
    • 예: int* B = new int(*A); — B는 A의 값을 복사하여 독립된 메모리 영역을 관리.
  • 이 방식에서는 A가 메모리를 해제하더라도 B는 여전히 독립적으로 메모리를 관리하므로 Dangling Pointer가 발생하지 않음.
  • 각 포인터가 독립적인 메모리 공간을 할당받기 때문에, 안전하게 메모리 관리를 할 수 있음. 
#include <iostream>
using namespace std;

int main() {
    int* A = new int(30);  // A가 30을 가리키는 메모리 할당
    int* B = new int(*A);  // B는 A의 값을 복사하여 독립된 메모리 할당

    cout << "A의 값: " << *A << endl; // 출력: 30
    cout << "B의 값: " << *B << endl; // 출력: 30

    delete A;  // A의 메모리 해제
    cout << "B의 값 (깊은 복사 후): " << *B << endl; // 출력: 30

    delete B;  // B의 메모리 해제
    return 0;
}

 

↓ 숙제2: 스마트 포인터를 활용한 로그분석기 구현

더보기
  • 요구사항은 아래와 같습니다.
    • 로그 메시지는 중요도에따라(Info,Warning,Error)로 분류되어 기록될 수 있습니다. 로그 앞에 중요도가 태그가 붙어 표시됩니다.
      • [Info] 사용자 메시지
      • [Warning]사용자 메시지
      • [Error] 사용자 메시지
    • 로그 기록기는 단 하나의 인스턴스만 존재해야 하며, unique_ptr를 통해 이를 보장해야 합니다.
    • 지금까지 기록된 로그의 총 개수를 출력할 수 있어야 합니다.
  • 로그 기록기는 프로그램에서 발생한 중요한 사건을 기록합니다. 로그 기록기의 몇가지 기능을 구현해봅시다.

[로그 출력 예시]

[INFO]: System is starting.

[WARNING]: Low disk space.

[ERROR]: Unable to connect to the server.

Total logs recorded: 3

Logger instance destroyed.

 

답: 

#include <iostream>
#include <memory> // unique_ptr를 사용하기 위해 포함
#include <string> // std::string 사용을 위해 포함

using namespace std;

class Logger {
private:
    int logCount; // 총 로그 개수를 저장하는 변수

public:
    // 생성자: logCount를 0으로 초기화
    Logger() : logCount(0) {}

    // 일반 정보 로그: 로그 메시지를 출력하고 logCount 증가
    void logInfo(const string& message) {
        logCount++;
        cout << "[INFO]: " << message << endl;
    }

    // 경고 로그: 로그 메시지를 출력하고 logCount 증가
    void logWarning(const string& message) {
        logCount++;
        cout << "[WARNING]: " << message << endl;
    }

    // 에러 로그: 로그 메시지를 출력하고 logCount 증가
    void logError(const string& message) {
        logCount++;
        cout << "[ERROR]: " << message << endl;
    }
    //const를 사용하여 전달된 문자열이 수정되지 않음을 보장하며, 참조를 사용해 복사를 방지하여 성능을 최적화.

    // 총 로그 개수를 출력
    void showTotalLogs() {
        cout << "Total logs recorded: " << logCount << endl;
    }

    // 소멸자: Logger 객체가 소멸될 때 호출되며 로그를 출력
    ~Logger() {
        cout << "Logger instance destroyed." << endl;
    }
};

int main() {
    // Logger 인스턴스를 unique_ptr로 관리
    // unique_ptr는 스마트 포인터로, 동적 메모리를 자동으로 관리하며 복사가 불가능
    unique_ptr<Logger> logger = make_unique<Logger>();

    // 다양한 로그 기록
    logger->logInfo("System is starting.");       // 일반 정보 로그
    logger->logWarning("Low disk space.");       // 경고 로그
    logger->logError("Unable to connect to the server."); // 에러 로그

    // 총 로그 개수 출력
    logger->showTotalLogs();

    // unique_ptr은 복사가 불가능하므로 아래 코드는 에러 발생:
    // unique_ptr<Logger> anotherLogger = logger;

    return 0;
}

'TIL > C++' 카테고리의 다른 글

챕터 2-3 : STL 기초  (1) 2025.01.02
챕터 2-2 : 템플릿  (1) 2024.12.31
C++ 추가 자료  (1) 2024.12.27
챕터 1-3 : 객체지향 프로그래밍  (1) 2024.12.26
챕터 1-2 : Class 개념  (0) 2024.12.24