객체지향의 기초

윤형찬·2020년 10월 30일
0

C++

목록 보기
9/10
post-thumbnail

객체지향 프로그래밍과 클래스

  • 데이터와 기능이 모여있는 것을 객체(object)라고 한다.
  • 객체를 코드로 구현한 것을 클래스라고 한다.
  • 클래스를 바탕으로 메모리를 할당받아 실제 존재하는 것 하나를 만드는 것을 instanciation 이라고 하고 만들어진 것을 instance 라고 한다.
  • 같은 클래스에서 나왔지만 다른 메모리에 있는 서로 다른 인스턴스여도 서로 멤버에 접근 가능하다.

클래스 생성 예제

class Friend
{
public: // 접근 제어자access specifier (public, private, protected)
    string name;
    string address;
    int age;
    double height;
    double weight;
    
    void print()
    {
        cout << name << " " << address << " " << age << " " << height << " " << height << endl;
    }
};

int main()
{
    Friend jj { "Jack", "seoul", 2, 30, 10 }; // instanciation, instance
    jj.print();
    
    vector<Friend> my_friends;
    my_friends.resize(2);
    
    for(auto &ele : my_friends)
        ele.print();
    
    return 0;
}


캡슐화, 접근 지정자, 접근 함수

캡슐화 encapsulation

  • 복잡한 코드를 깔끔하게 정리하는 것이 더 좋은 프로그래머가 되는 길이다.

접근 지정자 access specifier

  • public : 클래스 밖에 서도 접근 가능
  • private : 클래스 내에서만 접근 가능
  • protected

public 으로 설정하지않으면private이 기본이다.

접근 함수

private 으로 설정된 것을 접근하기 위해서는 접근 함수(access function)을 만들어줘야 한다.

사용 예제

class Date
{
    int m_month;
    int m_day;
    int m_year;
    
public:
    void setDate(const int& month_input, const int& day_input, const int& year_input)
    {
        m_month = month_input;
        m_day = day_input;
        m_year = year_input;
    }
    
    void copyFrom(const Date &original)
    {
        m_month = original.m_month;
        m_day = original.m_day;
        m_year = original.m_year;
    }
    
    void setMonth(const int& month_input)
    {
        m_month = month_input;
    }
    
    //setDay, setYear ... // setters
    
    const int& getDay() // getters
    {
        return m_day;
    }
};

int main()
{
    Date today; // { 8, 4, 2025 ];
    today.setDate(8, 4, 2025);
    today.setMonth(10);
    
    cout << today.getDay() << endl;
    
    Date copy;
    copy.copyFrom(today);
    
    return 0;
}


생성자 Constructor

  • 이 클래스의 인스턴스들은 만들어지자마자 이러한 것들, 속성들을 가지고있어야해, 수행해야해 하는 경우에 사용한다.
  • 생성자는 인스턴스가 만들어질 때(construct 될 때) 자동으로 호출이 되는 함수다.
  • 생성자가 없을 땐 컴파일러가 디폴트 생성자를 만든다.
  • 생성자에 파라미터가 없는 경우에는 함수와 헷갈리지 않기 위해 인스턴스 선언 시에 괄호 ( ) 는 뺀다.

예제

class Fraction
{
private:
    int m_numerator;
    int m_denominator;
    
public:
// 파라미터가 없는 생성자를 만들거나, 생성자 함수에 기본 값을 넣거나
    Fraction(const int& num_in = 1, const int& den_in = 1)
    {
        m_numerator = num_in;
        m_denominator = den_in;
    }
    void print()
    {
        cout << m_numerator << " / " << m_denominator << endl;
    }
};

int main()
{
    Fraction one_thirds(1, 3);
    one_thirds.print();
    
    Fraction no_constructor;
    no_constructor.print();
    
    return 0;
}

1 / 3
1 / 1



생성자 멤버 초기화 목록

사용 예제

class B
{
private:
    int m_b;
    
public:
    B(const int& m_b_in)
    : m_b(m_b_in){}
};

class Something
{
private:
    int     m_i;
    double  m_d;
    char    m_c;
    int     m_arr[5];
    B       m_b;
    
public:
    Something()
    : m_i(1), m_d(3.14), m_c('a'), m_arr{ 1,2,3,4,5 }, m_b(m_i - 1)
    {
//        m_i = 1;
//        m_d = 3.14;
//        m_c = 'a';
    }
    
    void print()
    {
        cout << m_i << " " << m_d << " " <<  m_c << endl;
        for (auto& e:m_arr)
             cout<<e<<" ";
        cout << endl;
    }
};

int main() {
    Something s;
    s.print();
    
    return 0;
}


위임 생성자

  • 생성자가 다른 생성자를 사용하는 것

예제

class Student
{
private:
    int m_id;
    string m_name;
    
public:
    // 위임 생성자 사용
    Student(const string& name_in)
    : Student(0, name_in) {}
    
    Student(const int& id_in, const string& name_in)
    : m_id(id_in), m_name(name_in) {}
    
    void print()
    {
        cout << m_id << " " << m_name << endl;
    }
};

int main()
{
    Student st1(0, "Jack Jack");
    st1.print();
    
    Student st2("Smith");
    st2.print();
    
    return 0;
}


소멸자 destructor

  • 생성자와 반대되는 개념
  • 변수가 영역을 벗어나서 사라질 때 호출이 되는 함수
  • instance 가 메모리에서 해제될 때 내부에서 자동으로 호출된다.
  • 동적할당으로 만들어진 경우에는 영역을 벗어나도 자동으로 메모리가 해제되지 않기 때문에 delete 으로 매모리를 해제할 때만 소멸자가 호출된다.
  • 소멸자를 프로그래머가 직접 호출하는 것은 대부분의 경우 권장하지 않는다.

사용 예제

class Simple
{
private:
    int m_id;
   
public:
    Simple(const int& id_in)
    : m_id(id_in)
    {
        cout << "Constructor " << m_id << endl;
    }
    
    // 소멸자
    ~Simple()
    {
        cout << "Destructor " << m_id << endl;
    }
};

int main()
{
    Simple *s1 = new Simple(0); // 동적 할당
    Simple s2(1);
    
    delete s1;
    
    return 0;
}


this

  • C++은 클래스의 멤버 함수를 호출할 때 어떻게 호출할 객체(인스턴스)를 찾는가? -> this 라는 숨겨진 포인터를 사용한다.
  • this 포인터는 멤버 함수가 호출된 객체의 주소를 가리키는 숨겨진 포인터다.
  • 모든 멤버 함수는 함수가 호출된 객체(인스턴스)를 가리키는 this 포인터를 가지고 있다.
  • this 가 사용되면 좋을 때
    - 멤버 변수와 이름이 같은 매개 변수를 가진 생성자(또는 멤버 함수)가 있는 경우: this를 사용해서 구분할 수 있다
class Something
{
private:
	int data;
public:
	Something(int data)
    	{
             this->data = data;
             // this->data 는 멤버 변수이고
             // data 는 매개 변수
         }
}
- 클래스 멤버 함수가 작업 중이던 객체(인스턴스)를 반환하는 방식이 유용할 때 -> 체이닝 기법

체이닝 기법 예제

class Calc
{
private:
    int m_value;
    
public:
    Calc(int init_value)
    : m_value(init_value)
    {}
    
    Calc& add(int value) { m_value += value; return *this; }
    Calc& sub(int value) { m_value -= value; return *this; }
    Calc& mult(int value) { m_value *= value; return *this; }
    
    void print()
    {
        cout << m_value <<endl;
    }
};

int main()
{
    Calc cal(10);
    cal.add(10).sub(1).mult(2).print();
    // cal.add(10); cal.sub(1); cal.mult(2);
    // cal.print();
    
    return 0;
}


클래스 코드와 헤더 파일

  • 헤더파일을 따로 만들어서 클래스 코드를 정리한다.
  • 사용할 클래스를 헤더파일에 간결하게 정의
  • 헤더파일에 정의한 함수들을 cpp파일을 따로 만들어서 구현

main.cpp

#include "Calc.h"

int main()
{
    Calc cal(10);
    cal.add(10).sub(1).mult(2).print();
    // cal.add(10); cal.sub(1); cal.mult(2);
    // cal.print();
    
    return 0;
}

Calc.h

#pragma once
#include <iostream>

class Calc
{
private:
    int m_value;
    
public:
    Calc(int init_value);
    
    Calc& add(int value);
    Calc& sub(int value);
    Calc& mult(int value);
    
    void print() ;
};

Calc.cpp

#include "Calc.h"

Calc::Calc(int init_value)
: m_value(init_value)
{}

Calc& Calc::add(int value)
{
    m_value += value;
    return *this;
}

Calc& Calc::sub(int value)
{
    m_value -= value;
    return *this;
}

Calc& Calc::mult(int value)
{
    m_value *= value;
    return *this;
}

void Calc::print()
{
    std::cout << m_value << std::endl;
}


친구 함수와 클래스

  • 여러개의 클래스가 복잡하게 돌아가는 프로그램에서는 클래스간의 상호작용을 깔끔하게 정리하기 힘들 수 있다.
  • 객체지향의 기본원칙(캡슐화)을 지키기 위해서는 부가적인 요소들이 발생할수 있는데 이때 friend키워드 친구 클래스 친구 함수를 활용하면 조금더 깔끔하게 정리할 수 있다.

예제

class B; // forward declaration

class A
{
private:
    int m_value = 1;
    
    friend void doSomething(A& a, B& b);
};

class B
{
private:
    int m_value = 2;
    
    friend void doSomething(A& a, B& b);
};

void doSomething(A& a, B& b)
{
    // private 변수를 직접 접근
    cout << a.m_value << " " << b.m_value <<endl;
}

int main() {
    A a;
    B b;
    doSomething(a, b);
    
    return 0;
}

외부에 있는 함수 말고, 다른 클래스가 내 private 에 접근가능하게 해주는 방법

예제

class B; // forward declaration

class A
{
private:
    int m_value = 1;
    
    friend class B;
};

class B
{
private:
    int m_value = 2;
    
public:
    void doSomething(A& a)
    {
        cout << a.m_value << endl;
    }
};

int main() {
    A a;
    B b;
    b.doSomething(a);
    
    return 0;
}

클래스 전부 open 하긴 부담스러우니 함수만 주고 싶다

예제

class A; // forward declaration

class B
{
private:
    int m_value = 2;
    
public:
    void doSomething(A& a);
};

class A
{
private:
    int m_value = 1;
    
    //friend class B;
    friend void B::doSomething(A& a);
};

void B::doSomething(A& a)
{
    cout << a.m_value << endl;
}


int main() {
    A a;
    B b;
    b.doSomething(a);
    
    return 0;
}


익명 개체

  • 객체를 사용할 때 이름이 붙은 변수를 선언하지 않고 바로 사용하는 객체

    //A a();
    //a.print();
    
    A().print();
  • R-value 처럼 사용된다.
  • 재사용은 안된다.
  • 호출될때마다 생성자와 소멸자가 계속 실행된다.

예제

class Cents
{
private:
    int m_cents;
 
public:
    Cents(const int& cents)
    : m_cents(cents)
    {}

    int getCents() const
    {
        return m_cents;
    }
};

Cents add(const Cents& c1, const Cents &c2)
{
    return Cents( c1.getCents() + c2.getCents() );
}

int main()
{
    cout << add(Cents(6), Cents(8)).getCents() << endl;
    
    return 0;
}


클래스 안에 포함된 자료형

  • 클래스를 구현할 때는 그 기능에 맞춰서 특정한 자료형을 특별히 따로 만들어서 사용한다.
  • 그 자료형은 여기저기 사용 될 필요는 없다.
  • 클래스 내에서만 사용되면 된다. (선언)
  • 클래스 안에 포함되어 있는 자료형
  • 중첩 자료형 이라고도 함

예제

class Fruit
{
public:
    enum FruitType
    {
        APPLE, BANANA, CHERRY,
    };
private:
    FruitType m_type;
    
public:
    Fruit(FruitType type) : m_type(type)
    {}
    
    FruitType getType() { return m_type; }
};

int main()
{
    Fruit apple(Fruit::APPLE);
    
    if(apple.getType() == Fruit::APPLE)
        std::cout << "Apple" << std::endl;
    
    return 0;
}


실행 시간 측정하기

코드

#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
#include <chrono>

using namespace std;

class Timer

{
    using clock_t = std::chrono::high_resolution_clock;
    using second_t = std::chrono::duration<double, std::ratio<1>>;
    
    std::chrono::time_point<clock_t> start_time = clock_t::now();
    
public:
    void elapsed()
    {
        std ::chrono::time_point<clock_t> end_time = clock_t::now();
        
        cout << std::chrono::duration_cast<second_t>(end_time - start_time).count() << endl;
    }
};


int main()
{
    random_device rnd_device;
    mt19937 mersenne_engine{ rnd_device() };
    
    vector<int> vec(10);
    for (unsigned int i = 0; i <vec.size(); ++i)
        vec[i] = i;
    
    // vector 안에 숫자 섞기
    std::shuffle(begin(vec), end(vec), mersenne_engine);
    
    
    Timer timer;
    
    // 정렬
    std::sort(begin(vec), end(vec));
    
    // 잰 시간 출력
    timer.elapsed();
    
    for (auto &e : vec) cout << e << " ";
    cout << endl;

    return 0;
}

profile
https://github.com/velmash

0개의 댓글