OOP 실습하며 이해하기.1

정혜창·2024년 12월 30일
0

내일배움캠프

목록 보기
13/43
post-thumbnail

클래스, 템플릿, 컨테이너, 반복자, 맵, Solid원칙, 스마트 포인터 등등 너무 많은 정보가 머리에 한번에 들어오니깐 내가 이것을 이해하고 있는것인지 아니면 추상적으로 머리에 용어만 들어가 있는것인지.. 우선 개념은 아리송하게 알 것 같은데 막상 코드로 어떻게 들어가는지 전혀 모르겠어서 너무 답답했다. 이럴땐 예제를 보면서 무작정 따라하며 배우는게 깨닫는게 있을거라는 마음에 무작정 각 개념들이 적용된 코드들을 찾아보았다. 그리고 무작정 따라하면서 이해하는게 좋을 것 같다고 판단했다. 종국에는 자유롭게 내가 원하는 작은 프로젝트를 만들 수 있는데 까지 빠르게 가는 것이 목표이다.

일단 첫 예제이다.
템플릿, map이 적용된 작은 예제를 한줄 한줄 이해하려고 하며 무작정 따라 적어보았다. 이해하는데만 하루 절반을 쓸 정도로 매우 오래 걸렸다.. 우선 범위 기반 for문, 함수뒤에 붙는 const, static.cast 명시적 형변환, map의 활용을 조금은 이해할 수 있었다.. 눈이 빠질것 같지만 그래도 조금씩 이해를 할 수 있는 것같아 기쁘다.

점점 이해에 가속이 붙을 수 있도록 많은 코드를 보고 실습을 해봐야겠다.

코드 설명란은 실습하면서 공부하며 제가 이해한 내용을 바탕으로 막 적은거라 오류가 있을 수 있습니다.

#include <iostream>
#include <map>
#include <string>
#include <vector>
#include <iomanip>

// 1. 학생 정보를 담는 클래스
class Student {
private:
    std::string name;    // 이름
    int id;              // 학번
    std::vector<float> grades; // 성적 리스트

public:
    Student() : name("Unknown"), id(0), grades() {}
    
    // 생성자
    Student(const std::string& name, int id, const std::vector<float>& grades)
        : name(name), id(id), grades(grades) {
    }

    // 학번 반환
    int getId() const {
        return id;
    }

    // 이름 반환
    std::string getName() const {
        return name;
    }

    // 성적 리스트 반환
    std::vector<float> getGrades() const {
        return grades;
    }

    // 성적 리스트 수정
    void setGrades(const std::vector<float>& newGrades) {
        grades = newGrades;
    }

    // 학생 정보 출력
    void displayInfo() const {
        std::cout << "Name: " << name << ", ID: " << id << ", Grades: ";
        for (float grade : grades) {
            std::cout << grade << " ";
        }
        std::cout << std::endl;
    }
};

// 2. 템플릿 함수: 평균 계산
template <typename T>
float calculateAverage(const std::vector<T>& values) {
    T sum = 0;
    for (const T& value : values) {
        sum += value;
    }
    return static_cast<float>(sum) / values.size();
}

// 3. 메인 프로그램
int main() {
    // 학생 관리 컨테이너 (학번 -> 학생 객체)
    std::map<int, Student> studentMap;

    // 학생 추가
    studentMap[101] = Student("Alice", 101, { 85.0, 90.5, 78.0 });
    studentMap[102] = Student("Bob", 102, { 92.0, 88.5, 79.0 });
    studentMap[103] = Student("Charlie", 103, { 76.5, 80.0, 85.5 });

    // 학생 정보 출력
    std::cout << "All Students:" << std::endl;
    for (const auto& pair : studentMap) {
        pair.second.displayInfo();
    }

    // 특정 학생 검색 (학번으로)
    int searchId = 102;
    std::cout << "\nSearching for student with ID " << searchId << "..." << std::endl;
    if (studentMap.find(searchId) != studentMap.end()) {
        studentMap[searchId].displayInfo();
    }
    else {
        std::cout << "Student with ID " << searchId << " not found." << std::endl;
    }

    // 특정 학생 성적 수정
    std::cout << "\nUpdating grades for student with ID 102..." << std::endl;
    if (studentMap.find(searchId) != studentMap.end()) {
        studentMap[searchId].setGrades({ 95.0, 88.0, 84.0 });
        studentMap[searchId].displayInfo();
    }

    // 전체 학생 성적 평균 계산
    std::cout << "\nCalculating average grades for all students: " << std::endl;
    for (const auto& pair : studentMap) {
        const Student& student = pair.second;
        float avg = calculateAverage(student.getGrades());
        std::cout << "Student " << student.getName() << " (ID: " << student.getId()
            << ") Average: " << std::setprecision(2) << std::fixed << avg << std::endl;
    }

    return 0;
}

코드 설명


  • grades를 () 로 초기화한 이유
    grades()vector<float>의 기본 생성자를 호출하는 코드이다. vector는 기본적으로 비어있는 상태로 초기화된다. 즉, grades()는 빈vector<float>를 생성하는 것과 같다.

  • 매개변수를 받는 생성자가 있음에도 기본생성자를 두는 이유는 map 때문이다. map 과 같은 STL 컨테이너가 객체를 내부적으로 복사하거나 기본 생성자를 호출하는 경우가 있기 때문.
    map과 같은 STL 컨테이너는 데이터를 관리할 때 다음과 같은 동작을 한다.

    • 내부적으로 기본 생성자를 호출해 빈 객체를 생성한 후, 데이터를 추가하거나 수정한다.
    • 어떤 경우에는 객체를 복사하거나, 초기값 어이 생성하는 작업이 필요하기 때문에 기본생성자가 없으면 컴파일 오류가 난다.
  • int getId() const{return Id;}

    • 굳이 바꾸지도 못하는 변수인데 왜 함수를 선언하지 라고 생각 할 수 있지만 '읽기 전용'으로 쓰기 위해서이다. main 실행문에서 id값을 읽어야되는 경우가 있기 때문에 선언한 것.
    • 함수 뒤, 스코프{}로 들어가기전에 const가 있는 것을 볼 수있는데 객체의 상태를 변경하지 않음을 보장하는 키워드 이다. 멤버 변수를 변경하지 않는다.
  • void setGrade(const vector<float> newGrades) { grades = newGrades; }

    • 위의 함수와 헷갈릴 수 있다. '아니 변수 못 바꾼다고 했지 않았나?' 이건 매개변수의 값을 상수로 하겠다는 것이다. gradessetGrade함수 내에서 자유롭게 변경 가능하다. 여기서도 중요한점은 grades멤버 변수가 private내에서 선언되었음에도 변경가능하다는 것이다. 여튼 여기서는 매개변수로 오는 newGrades만 수정이 불가능하다.
  • for (float grade : grades) { ~ }

    • 범위 기반 for문이다. 당황하지말자. gradesvector<float> 형태의 컨테이너이다.
    • 여기서 grade('s'없음)는 grades 벡터의 각 요소를 순차적으로 하나씩 받아오는 변수이다.
    • gradegrades의 현재 요소를 복사한 값이 된다.
    • grades 배열의 첫 번째 요소부터 마지막 요소까지 순차적으로 grade 값에 대입한다.
    • 그 값으로 실행문이 반복적으로 실행된다.
  • 템플릿은 따로 정리해놔서 pass, 굳이 언급하자면 만약 특수화를 하려면 template <>를 써야한다. calculateAverage 함수에서 알게된건 static_cast 이다. 깊게 들어가면 복잡하지만 일단 '임시적인 형 변환' 이라고 생각하면 된다. sum은 T타입으로 계산되지만 결국 static_cast<float>에 의해 임시적으로 floa로 변환 된다. 따라서 return값은 float / int 이므로 무조건 float 값을 반환한다.

  • const auto는 변수의 자료형을 자동으로 추론하겠다는 것이다. 그리고 이후 자료형을 변경할 수 없다고 하는것. 주로 변수 선언할 때 많이 사용하고 매개변수에도 사용이 가능하나 C++14부터 사용 가능하다.

  • setprecision(자릿수), fixed는 헤더파일 <iomanip>로 사용 가능해지며 setprecision만 있으면 정수+소수의 총 자릿수만큼, fixed와 같이 쓰면 소수의 자릿수만큼 표기해준다.

  • studentMapmap(int, Student) 형이므로 studentMap의 각 요소는 pair<const int, Student> 형태이다.

    • for (const auto& piar : studentMap)에서 pairstudentMap의 각 요소를 참조한다. 이때 pairpair<const int, Student>의 참조이다.
    • pair.firststudentMap의 키(int 타입)이고, pair.second는 값(Student 타입)이다. const가 붙어 있으므로 pair를 통해 값을 수정할 수 없으며, 읽기전용이다.(Read only!)
profile
Unreal 1기

0개의 댓글

관련 채용 정보