Programming Language/C++

[C++] 클래스(Class) (1)

lumana 2024. 1. 30. 03:53

클래스(Class) (1)


  • 특정 객체를 생성하기 위해 변수와 메소드를 정의하는 일종의 틀

  • 자료를 저장하고 자료를 처리할 수 있음

  • 특정한 용도를 수행하기 위한 변수와 함수를 모아 둔 틀

  • int a, b, c에서 int가 틀이라면 a, b, c 는 틀을 이용하여 찍어낸 변수(객체)

  • 객체(오브젝트) : 틀을 이용하여 찍어낸 개체(변수, 메모리 상의 공간)

접근제어 지시자(access modifier)

  • 객체지향 프로그래밍 언어에서 클래스의 멤버(필드, 메서드)에 대한 접근 권한을 제어하기 위해 사용되는 키워드나 지시자

  • 래스의 내부 구조를 보호하고, 데이터 은닉(data encapsulation)을 지원하여 클래스의 캡슐화를 강화(캡슐화 : 외부에서 접근을 제한하고 내부의 상세 구현을 숨김)

  • private / public / protected 3가지가 존재

class TV {
   private:
    bool powerOn;
    int channel;
    int volume;
    // 함수를 사용하여 0 이상의 수만 사용하거나 최대 최소 범위를 설정하자.

   public:
    void on() {
        powerOn = true;
        cout << "TV를 켰습니다." << endl;
    }
    void off() {
        powerOn = false;
        cout << "TV를 껐습니다. " << endl;
    }
    void setVolume(int vol) {
        if (vol >= 0 && vol <= 100) {
            volume = vol;
            cout << "볼륨 : " << volume << endl;
        }
    }
    void setChannel(int ch) {
        if (ch >= 1 && ch <= 999) {
            channel = ch;
            cout << "채널 : " << channel << endl;
        }
    }
    // 그런데 그냥 ss.volume = 300 해버리면 막을 방법이 없음. --> private 지시자를 사용하자.
};

void EX1() {
    TV lg;
    lg.on();
    //lg.volume = 50(private 으로 인해 직접적으로 접근 불가)
    lg.setChannel(100);
    lg.setVolume(33);
    lg.off();
}
  • class의 경우 접근제어 지시자를 사용하지 않으면 기본적으로 private 이고, struct의 경우에는 public이다.

this 포인터

  • 자신이 소속되어 있는 객체의 주소
#include <iostream>
using namespace std;

class MyClass {
   public:
    // 객체 하나 하나 마다 함수를 따로 만드는게 아니라, 하나의 함수를 만들어 놓고, 각각의 개체에따라 호출되는 것이다.
    void PrintThis() {  // 보이지 않는 매개변수로 this를 항상 가지고 있다.
        cout << "나의 주소는 " << this << " 입니다." << endl;
    }
    void PrintThis2(MyClass *ptr) {  // MyClass 객체 b에서 printthis2를 수행하면 b의 주소가 매개변수로 넘어가 실행되는 것을 표현한 함수
        cout << "나의 주소는 " << ptr << " 입니다." << endl;
    }
};

void EX1() {
    MyClass a, b;
    cout << "a의 주소는 " << &a << endl;
    cout << "b의 주소는 " << &b << endl;
    a.PrintThis();
    b.PrintThis();
    a.PrintThis2(&a);
    b.PrintThis2(&b);
}

  • 모든 함수에서는 보이지 않는 매개변수로 this를 항상 가지고 있다.

  • 따라서 PrintThis2와 같이 자신이 속한 객체의 주소를 매개변수로 받는 함수를 만들지 않는다.

생성자와 소멸자

  • 생성자 : 객체가 생성될 때 자동으로 호출되는 함수

  • 소멸자 : 객체가 소멸될 때 자동으로 호출되는 함수

#include <iostream>
using namespace std;

class MyClass {
   public:       // 아무것도 안적어도 빈 생성자와 소멸자가 있음.
    MyClass() {  // 생성자
        cout << "생성자 호출" << '\n';
    }
    ~MyClass() {  // 소멸자
        cout << "소멸자 호출" << '\n';
    }
};

MyClass globalobj; // global

int main() {
    cout << "main function started" << '\n';
    cout << "main function end" << '\n';
}
  • 생성자 -> main함수 시작 -> main함수 종료 -> 소멸자

  • 생성자와 소멸자를 명시적으로 안 적어도 빈 생성자와 소멸자가 있음

#include <iostream>
using namespace std;

class MyClass {
   public:       // 아무것도 안적어도 빈 생성자와 소멸자가 있음.
    MyClass() {  // 생성자
        cout << "생성자 호출" << '\n';
    }
    ~MyClass() {  // 소멸자
        cout << "소멸자 호출" << '\n';
    }
};

void testlocalobj() {
    cout << "testlocalobj started" << '\n';
    MyClass localobj;
    cout << "testlocalobj end" << '\n';
}

int main() {
    cout << "main function started" << '\n';
    testlocalobj();
    cout << "main function end" << '\n';
}
  • 지역변수는 순서대로 생성자 호출되고, 지역변수가 속한 함수가 끝나면, 소멸자가 호출됨.

생성자의 대표적인 사용 예시

  • 생성자

    • 멤버 변수 초기화
  • 소멸자

    • 메모리 해제
#include <iostream>
using namespace std;

class Complex {
   public:
    Complex() {
        real = 0;
        imag = 0;
    }
    Complex(double real_, double imag_) {
        real = real_;
        imag = imag_;
    }
    double GetReal() {
        return real;
    }
    double GetImag() {
        return imag;
    }
    void SetReal(double real_) {
        real = real_;
    }
    void SetImag(double imag_) {
        imag = imag_;
    }

   private:
    double real;
    double imag;
};

int main(void) {
    Complex c1;
    Complex c2 = Complex(2, 3);  // 이 코드랑 아래코드랑 같은 값을 갖는 표현.
    Complex c3(2, 3);
    Complex c4 = {2, 3};
    Complex c5 = Complex{2, 3};
    Complex c6{2, 3};
    cout << "c1 = " << c1.GetReal() << " , " << c1.GetImag() << '\n';
    cout << "c2 = " << c2.GetReal() << " , " << c2.GetImag() << '\n';
    cout << "c3 = " << c3.GetReal() << " , " << c3.GetImag() << '\n';
}
  • 생성자와 소멸자 모두 오버라이딩 가능

  • c1이 생성될 때 Complex() 생성자 실행

  • c2와 c3의 경우 omplex(double real_, double imag_) 생성자 실행

  • c4, c5, c6처럼 중괄호를 이용하여 초기화 할 수 있음

복소수를 다루는 class(예시)

#include <iostream>
using namespace std;

// 생성자 : 멤버 변수 초기화

// 복소수(real, imag)

class Complex {
   public:
    // 생성자도 default 매개변수를 이용할 수 있음
    /* 
    Complex(double real_ = 0, double imag_ = 0) {
        real = real_;
        imag = imag_;
    } 
    */
    Complex() : real(0), imag(0) {} // { real = 0, imag = 0} 과 같음 
    Complex(double real, double imag) : real(real), imag(imag) {}  // 괄호 real은 매개변수, 밖의 real은 멤버의 real.
    double GetReal() {
        return real;
    }
    double GetImag() {
        return imag;
    }
    void SetReal(double real_) {
        real = real_;
    }
    void SetImag(double imag_) {
        imag = imag_;
    }

   private:
    double real;
    double imag;
};

int main(void) {
    int a(5);
    Complex c1;
    Complex c2 = Complex(2, 3);  // 이 코드랑 아래코드랑 같은 값을 갖는 표현.
    Complex c3(2, 3);
    Complex c4 = {2, 3};
    Complex c5 = Complex{2, 3};
    Complex c6{2, 3};
    cout << "c1 = " << c1.GetReal() << " , " << c1.GetImag() << '\n';
    cout << "c2 = " << c2.GetReal() << " , " << c2.GetImag() << '\n';
    cout << "c3 = " << c3.GetReal() << " , " << c3.GetImag() << '\n';
}
  • 생성자도 디폴트 매개변수를 이용할 수 있다

  • 중괄호안에서 멤버변수를 초기화 하는 코드를 멤버변수(매개변수) 의 형태로 나타낼 수 있다

    • 이 때 멤버변수의 이름과 매개변수의 이름이 같아도 올바르게 저장된다

    • 콜론 다음 내용을 먼저 실행하고, 괄호 안의 코드를 실행한다

생성자 위임

  • ex) Time 클래스(시간, 분, 초를 멤버 변수로 가짐)를 만들어 각가 5초, 5분, 2시간 37분 54초의 정보를 담는 객체들을 만든다고 해보자

      class Time {
      public:
          Time(5) // 5초
          Time(5,0) //5분
          Time(2, 37, 54); // 2시간 37분 54초
  • 이런식으로 구현하고 싶은데 알다시피 default parameter를 사용하면 왼쪽부터 대입하여 Time(5,0)과 Time(5)는 5시간이 되버린다

  • 생성자를 매개변수 1개일땐 초로, 2개일땐 분, 초, 3개일때 시간 분 초 로 만들기 위해 각각의 생성자를 선언해야 한다

  • 이 때 생성자가 겹치는 걸 줄이기 위해 생성자 위임을 사용한다

      #include <iostream>
      using namespace std;
    
      class Time {
      public:
          Time() : h(0), m(0), s(0) {}
          // 콜론 : 다음 내용을 먼저 실행하고, 괄호 안의 코드를 실행.
          Time(int s_) : Time() {
              s = s_;
          }
          Time(int m_, int s_) : Time(s_) {
              m = m_;
          }
          Time(int h_, int m_, int s_) : Time(m_, s_) {
              h = h_;
          }
    
      private:
          int h;
          int m;
          int s;
      };
    
      int main(void) {
          Time t1;
          Time t2(5);
          Time t3(3, 10);
          Time t4(2, 42, 15);
      }
    • 생성자에서 콜론 다음 내용을 먼저 실행하고, 괄호 안의 코드를 실행한 다는 점을 염두해 두고 코드를 보면 멤버 변수가 생성자를 통해 초기화 되는 과정을 파악할 수 있다.

참조) 두들낙서 C/C++ 강좌