C++ OOP의 목적은, class 를 int, char 처럼 사용하게 만드는 것이다. 

  1. int a = 1; 과 같은 기본 자료형 사용 방법을 살펴보면,
    int a; 선언과, a = 1; 정의(초기화, 값 할당)가 있다. 그리고 변수명사용한다. 
    C++ OOP의 목적은, class 를 int, char 처럼 사용하게 만드는 것이다.  클래스 자료형도 동일한 개념을 가진다.
  2. 클래스는 대입 연산자 대신, 생성자와 같은 클래스 맴버 함수로 "클래스 맴버 변수"를 초기화한다.
  3. 생성자와 파괴자의 이해는, 단순히 초기화 역할의 이해지만,
    "동적 할당"과 "메모리 해제" 그리고  "깊은 복사", "얇은 복사"를 이해하려면,
    생성자와 파괴자의 호출 시점을 명확하게 머리속에 그릴 수 있어야 한다. 

 

class(1)에서 선언에 해당하는 클래스 이해 시작을

추상화로 "사용자 정의 자료형"을 맴버 변수로 구현하고

은닉화의 필요성을 이해하고, "접근 제어자"와 "맴버 함수"를 구현하는 이유를 이해했다.

 

class(3) 에서 정의(초기화, 값 할당)에 해당하는

생성자파괴자를 이해해야 한다.

 

그리고 사용에 해당하는

"동적 할당"과 "메모리 해제" 그리고  "깊은 복사", "얇은 복사"를 이해로 진행한다.

 

 

1. 생성자와 생성자 호출 시점

더보기

1.1 디폴트 생성자

#include <iostream>

class Tv
{
private:
    bool OnOff;
    int channel;
    int volume;

public:
    Tv() {} // [1] 클래스를 초기화하는 "디폴트 생성자"
            // 생략되어, 명시하지 않아도 클래스 사용시 호출된다. 
};

int main()
{
    Tv lg; //[2] "디폴트 생성자" 가 호출된다.
}

 

1.2 생성자와 파괴자 호출 시점 (1)

#include <iostream>

class MyClass
{
public:
    MyClass()
    {
        std::cout << "**<MyClass> 생성자 호출**" << std::endl;
    }
    ~MyClass()
    {
        std::cout << "**<MyClass> 파괴자 호출**" << std::endl;
    }
};

MyClass globalObj;

int main()
{
    std::cout << "main() 호출" << std::endl;
    std::cout << "main() 종료" << std::endl;
}
// **<MyClass> 생성자 호출**
// main() 호출
// main() 종료
// **<MyClass> 파괴자 호출**

 

생성자와 파괴자는 호출 시점을 확인해야 한다.

 

 

1.3 생성자와 파괴자 호출 시점 (2)

#include <iostream>

class MyClass
{
public:
    MyClass()
    {
        std::cout << "**<MyClass> 생성자 호출**" << std::endl;
    }
    ~MyClass()
    {
        std::cout << "**<MyClass> 파괴자 호출**" << std::endl;
    }
};

void callObj()
{
    std::cout << "  callObj() 함수 호출" << std::endl;
    MyClass globalObj;
    std::cout << "  callObj() 함수 종료" << std::endl;
}

int main()
{
    std::cout << "main() 호출" << std::endl;
    callObj();
    std::cout << "main() 종료" << std::endl;
}
// main() 호출
//   callObj() 함수 호출
// **<MyClass> 생성자 호출**
//   callObj() 함수 종료
// **<MyClass> 파괴자 호출**
// main() 종료

 

생성자와 파괴자는 호출 시점을 확인한다.

 

2. 생성자와 초기화

더보기

2.1 구조체 맴버의 초기화 예시

#include <iostream>

struct Tv
{
private:
    bool OnOff;
    int channel;
    int volume;

public:
    void initializeTvObj() // [1] 구조체 맴버를 초기화하는 함수 정의
    {
        OnOff = false;
        channel = 1;
        volume = 0;
    }
    void printMember()
    {
        std::cout << "OnOff: " << OnOff << std::endl;
        std::cout << "Channel: " << channel << std::endl;
        std::cout << "volume: " << volume << std::endl;
    }
};

int main()
{
    Tv lg;            // [2] int a; 변수 선언과, Tv 클래스 객체 생성은 동일한 동작이다.
    lg.printMember(); // [2.1] 의도하지 않은 값이 들어가 있다. lg 객체 맴버를 초기화하지 않았기 때문에

    // lg.OnOff = false;  // [3] private 접근 제어자로 선언된 객체의 맴버에 직접 접근이 불가능하다.

    lg.initializeTvObj(); // [4] a = 1; 같은 변수 초기화와 동일한 동작을 한다.(값 할당)
    lg.printMember();     // [4.1] 사용자가 초기화 한, 객체 맴버의 값이 출력된다.
}

 

main 함수에서, [2]번 동작처럼 클래스 변수를 선언하고,

[2.1] 번 처럼 객체 변수를 사용하면, 초기화 되지 않았기 때문에, 어떤 값이 출력될지 알 수 없다.

[4]번 동작처럼 특정 값으로 초기화하지 않으면, 의도하지 않은 값이 출력된다. 

 

 

 

2.2 구조체에서 클래스로, 그리고 생성자를 사용한 초기화

#include <iostream>

// [1] struct 키워드를 class 키워드로 변경
class Tv
{
private:
    bool OnOff;
    int channel;
    int volume;
 
public:
    Tv() // [2] 클래스를 초기화하는 "생성자" 함수의 로직 수정
    {
        OnOff = false; // 생성자 함수 호출시, 맴버 변수의 값을 할당한다.
        channel = 1;
        volume = 0;
    }
    void printMember()
    {
        std::cout << "OnOff: " << OnOff << std::endl;
        std::cout << "Channel: " << channel << std::endl;
        std::cout << "volume: " << volume << std::endl;
    }
};
 
int main()
{
    Tv lg; // [3] int a; 변수 선언과 동일한 동작이다. [4] 그리고 a = 1; 동일한 동작인 Tv() 생성자가 호출된다.
    lg.printMember();
}

 

클래스의 경우, int a = 1; 처럼, 선언과 동시에 클래스의 맴버 변수를 초기화하도록 생성자를 사용할 수 있다.

 

 

2.3 기본 생성자와 생성자 오버로

#include <iostream>

class Tv
{
private:
    bool OnOff;
    int channel;
    int volume;

public:
    Tv() // [1] 클래스를 초기화하는 "디폴트 생성자"
    {    // 생략된 형태로, [2]번과 같이 "사용자 정의 생성자" 구현시
    }    // 명시적으로 지금과 같은 기본 생성자를 입력해야한다.

    // Tv() // [2] 동시에 동일한 형태의 함수가 존재 할 수 없다. 
    // {    // [1]번 생성자 함수와 시그니처가 같으면, 컴파일러가 함수를 식별할 수 없다.
    //     OnOff = false;
    //     channel = 1;
    //     volume = 0;
    // }

    Tv(int ch, int vol) // [3] "사용자 정의 생성자", 함수다.
    {
        OnOff = false;
        channel = ch;
        volume = vol;
    }

    void printMember()
    {
        std::cout << "OnOff: " << OnOff << std::endl;
        std::cout << "Channel: " << channel << std::endl;
        std::cout << "volume: " << volume << std::endl;
    }
};

int main()
{
    Tv lg;            // [4] int a; 변수 선언과 동일한 동작, Tv() 기본 생성자가 호출된다.
    lg.printMember(); // [5] 기본 생성자에는, lg 객체의 맴버 변수를 초기화하는 로직이 없다.

    Tv lgMonitor(9999, 200); //[6] 기본 생성자를 오버로딩한, 사용자 정의 생성자 함수를 호출한다.
    lgMonitor.printMember();
}

 

클래스에 구현된 생성자 함수는 2개다. [1]"기본 생성자"와, [3]"사용자 정의 생성자" 실행과 

예제 1.2 에서 사용한 [2] 생성자를 사용과 비교한다.

 

 

3. 생성자, 맴버 이니셜라이저(초기화 리스트)

더보기

 

#include <iostream>
 
int main()
{
    int a = 1;
    int b(2);
    int c{3};
    int *d = new int(4);
    int *e = new int{5};
 
    std::cout << a << std::endl;
    std::cout << b << std::endl;
    std::cout << c << std::endl;
    std::cout << *d << std::endl;
    std::cout << *e << std::endl;
 
    delete d;
    delete e;
    
    std::cout << "f 주소:" << f << "f 값:"<< *f << std::endl;
}

 

 

3.1 클래스 생성자 함수와 디폴트 매개변수 

#include <iostream>

class Color
{
private:
    int r, g, b;

public:
    // Color() // [1] 기본 생성자
    // {
    //     r = 0, g = 0, b = 0;
    // }

    // Color(int r_, int g_, int b_) //[2] 매개변수를 전달받아 맴버변수를 초기화하는 생성자
    // {
    //     r = r_, g = g_, b = b_;
    // }

    Color(int r_ = 0, int g_ = 0, int b_ = 0) // [3] 매개변수에 디폴트 값을 할당한 생성자
    {                                         // [1],[2] 생성자 기능을 동시에 포함한다.
        r = r_, g = g_, b = b_;
    }

    void printRGB();
};

void Color::printRGB()
{
    std::cout << "r:" << r << " g:" << g << " b:" << b << std::endl;
}

int main()
{
    Color black;      // [4] 선언과 동시에 Color 클래스 객체인
    black.printRGB(); // black 이 생성자를 통해 0,0,0 으로 초기화된다.

    Color white(255, 255, 255); // [4] 선언과 동시에 Color 클래스 객체인
    white.printRGB();           // white가 이 생성자를 통해 255,255,255 으로 초기화된다.
}

 

 

3.2 생성자 함수와 맴버 이니셜라이저

#include <iostream>

class Color
{
private:
    int r, g, b;

public:
    // Color(int r_ = 0, int g_ = 0, int b_ = 0) // [3] 매개변수에 디폴트 값을 할당한 생성자
    // {
    //     r = r_, g = g_, b = b_; // 변수 초기화 r(r_), g(g_), b(b_);
    // }

    Color(int r_ = 0, int g_ = 0, int b_ = 0) : r(r_), g(g_), b(b_) // [4] 맴버 이니셜라이저를 적용한 생성자
    {                                                               // 비지니스 로직만 생성자에 작성할 수 있다.
    }

    void printRGB();
};

void Color::printRGB()
{
    std::cout << "r:" << r << " g:" << g << " b:" << b << std::endl;
}

int main()
{
    Color black;      // [4] 선언과 동시에 Color 클래스 객체인
    black.printRGB(); // black 이 생성자를 통해 0,0,0 으로 초기화된다.

    Color white(255, 255, 255); // [4] 선언과 동시에 Color 클래스 객체인
    white.printRGB();           // white가 이 생성자를 통해 255,255,255 으로 초기화된다.
}

 

 

4. 생성자 위임

더보기

4.1 시, 분, 초 출력과 매개변수 위치 문제

#include <iostream>

class Timer
{
private:
    int h, m, s; // [1] 시, 분, 초

public:
    Timer(int h_ = 0, int m_ = 0, int s_ = 0) // [2] 입력받은 시 분 초를 초기화하는 생성자
    {
        h = h_, m = m_, s = s_;
    }

    void printTime();
};

void Timer::printTime()
{
    std::cout << h << "시 "  << m << "분 " << s << "초" << std::endl;
}

int main()
{
    Timer timer; // [4]
    timer.printTime();
}

 

시, 분, 초 값을 입력받는 함수에서, 만약 "초" 단위의 입력만 하고 싶다면 어떻게 해야 하는가?

Timer 생성자에 매개변수를 1개를 입력하면, 시간에 해당하는 값이 매개변수로 전달된다.

 

 

4.2 매개변수 개수를 다르게하는 함수를 여러개 만들어 사용하면, 중복 문제가 발생한다.

#include <iostream>

class Timer
{
private:
    int h, m, s; // [1] 시, 분, 초

public:
    Timer() : h(0), m(0), s(0) 
    {

    }
    Timer(int s_) // [2] 입력받은 초 단위를 초기화하는 생성자
    {
        s = s_;
    }
    Timer(int m_, int s_) // [3] 입력받은 분 초 단위를 초기화하는 생성자
    {
        m = m_;
        s = s_;
    }
    Timer(int h_, int m_, int s_) // [4] 입력받은 시 분 초 단위를 초기화하는 생성자
    {
        h = h_;
        m = m_;
        s = s_;
    }

    void printTime();
};

void Timer::printTime()
{
    std::cout << h << "시 " << m << "분 " << s << "초" << std::endl;
}

int main()
{
    Timer timer1(4); // [5] 에러
    timer1.printTime();
    
    Timer timer2(3,4); // [5] 에러
    timer2.printTime();
    
    Timer timer3(2,3,4); // [5] 에러
    timer3.printTime();
}
// 32758시 626784234분 4초
// 627876040시 3분 4초
// 2시 3분 4초

 

만약 m 맴버 변수를 수정해야 한다면, 모든 Timer 생성자에 포함된 m 변수를 수동으로 찾아가며 고쳐야 한다.

 

 

4.3

#include <iostream>

class Timer
{
private:
    int h, m, s; // [1] 시, 분, 초

public:
    Timer() : h(0), m(0), s(0) {}
    Timer(int s_) : Timer() // [2] 입력받은 초 단위를 초기화하는 생성자
    {
        s = s_;
    }
    Timer(int m_, int s_): Timer(s_) // [3] 입력받은 분 초 단위를 초기화하는 생성자
    {
        m = m_;
    }
    Timer(int h_, int m_, int s_) : Timer(m_, s_) // [4] 입력받은 시 분 초 단위를 초기화하는 생성자
    {
        h = h_;
    }

    void printTime();
};

void Timer::printTime()
{
    std::cout << h << "시 " << m << "분 " << s << "초" << std::endl;
}

int main()
{
    Timer timer;
    timer.printTime();
    
    Timer timer1(4); // 초 단위 출력
    timer1.printTime();
    
    Timer timer2(3,4); // 분, 초 단위 출력
    timer2.printTime();
    
    Timer timer3(2,3,4); // 시, 분, 초 단위 출력
    timer3.printTime();
}