본문 바로가기

프로그램언어/C++

클래스2

생성자 함수

생성자 함수는 오브젝트가 생성되어질 때 자동으로 호출되어지는 함수를 말한다.  생성자 함수가 제공되는 가장 큰 목적은 오브젝트가 생성될 때 각 범버변수에 초기값을 부여하기 위함이다.

 

- 생성자의 이름은 클래스의 이름과 같아야 한다.

- 생성자 함수의 접근 지정자는 public으로 해야 한다.

- 객체 생성시에 딱 한번 호출된다.

- 생성자도 디폴트값을 설정할 수 있다.

- 생성자도 일종의 함수이므로 오버로딩이 가능하다.

- 반환( return)형이 선언되어 있지 않고 실제로도 반환하지 않는다.

 

생성자 호출하기

#include <iostream>

using namespace std;

 

class TestClass

{

private:

    int x;

    int y;

public:

    TestClass() // 생성자 1

    {

        x = 0;

        y = 0;

    }

    TestClass(int n) // 생성자 2

    {

        x = n;

        y = 0;

    }

    TestClass(int n, int n2) // 생성자 3

    {

        x = n;

        y = n2;

    }

    void Show() const

    {

        cout << "X : "<< x << ", y : " << y << endl; 

    }

};

int main(void)

{

    TestClass test1; // 생성자 1 호출

    test1.Show();

 

    TestClass test2(100); // 생성자 2 호출

    test2.Show();

 

    TestClass test3(100, 100); // 생성자 3 호출

    test3.Show();

 

    return 0;

}

실행 결과

X : 0, y : 0

X : 100, y : 0

X : 100, y : 100

 

생성자 매개변수와 디폴트값설정

#include <iostream>

using namespace std;

 

class TestClass

{

private:

    int x;

    int y;

public:

    TestClass(int n = 0, int n2 = 0) // 디폴트 생성자

    {

         x = n;

         y = n2;

    }

    void Show() const

    {

        cout << "X : "<< x << ", y : " << y << endl;

    }

};

int main(void)

{

    TestClass test1;

    test1.Show();

 

    TestClass test2(150);

    test2.Show();

 

    TestClass test3(300, 600);

    test3.Show();

 

    return 0;

}

실행 결과

X : 0, y : 0

X : 150, y : 0

X : 300, y : 600

 

동적 메모리 할당

#include <iostream>
using namespace std;

class TestClass
{
private:
    int x;
    int y;
public:
    TestClass(int n = 0, int n2 = 0)  // 디폴트 생성자
    {
        x = n;
        y = n2;
    }

    void Show() const
    {
        cout << "X : "<< x << ", y : " << y << endl;
    }

};


int main(void)
{
    TestClass* test1 = new TestClass;
    test1->Show(); // 포인터를 통한 간접 접근

    TestClass* test2 = new TestClass(50);
    test2->Show();

    TestClass* test3 = new TestClass(50, 350);
    test3->Show();

    delete test1, test2, test3;

    return 0;
}
실행 결과

X : 0, y : 0

X : 50, y : 0

X : 50, y : 350

 

생성자의 활용

#include <iostream>

using namespace std;

 

class FruitSeller

{

private:

    int applePrice;

    int numOfApples;

    int myMoney;

public:

    FruitSeller(int price, int num, int money)

    {

        applePrice = price;

        numOfApples = num;

        myMoney = money;

    }

    int SaleApples(int money)

    {

        int num = money / applePrice;

        numOfApples -= num;

        myMoney += money;

        return num;

    }

    void ShowSalesResult() const

    {

        cout << "남은 사과: " << numOfApples << endl;

       cout << "판매 수익: " << myMoney << endl << endl;

    }

};

 

class FruitBuyer

{

private:

    int myMoney;

    int numOfApples;

public:

    FruitBuyer(int money)

    {

        myMoney = money;

        numOfApples = 0;

    }

    void BuyApples(FruitSeller& seller, int money)

    {
        // 메시지 전달(Message Passing)

        numOfApples += seller.SaleApples(money); 

        myMoney -= money;

    }

    void ShowBuyResult() const

    {

        cout << "현재 잔액: " << myMoney << endl;

        cout << "사과 개수: " << numOfApples << endl << endl;

    }

};

 

int main(void)

{

    FruitSeller seller(1000, 20, 0);

    FruitBuyer buyer(5000);

    buyer.BuyApples(seller, 2000);

 

    cout << "과일 판매자의 현황: " << endl;

    seller.ShowSalesResult();

    cout << "과일 구매자의 현황: " << endl;

    buyer.ShowBuyResult();

 

    return 0;

}

실행 결과

과일 판매자의 현황:

남은 사과: 18

판매 수익: 2000

 

과일 구매자의 현황:

현재 잔액: 3000

사과 개수: 2

 

복사 생성자

같은 클래스의 레퍼런스 타입을 인자로 받습니다.

- 인자를 레퍼런스 타입으로 받지 않으면 복사 생성자에 넘겨진 인자가 call by value방식으로 넘어가 매개변수에 복사됩니다. 복사 생성자는 Array클래스 타입을 복사할 때 호출되는 함수인데 이 함수를 실행하는 중에 Array 클래스 타입을 복사해야 한다면 자가당착에 빠지게 되는 것입니다. 결국 프로그램은 무한루프를 돌게 됩니다.

 

복사 생성자는 정의하지 않으면 컴파일러에 위해 자동으로 만들어 진다.

- 자동으로 만들어지는 복사 생성자는 위에 정의된 것처럼 모든 멤버 변수를 일대일로 복사하는 기능을 수행합니다.

 

복사 생성자가 자동으로 만들어진다고 해서 너무 믿고 맡겨서는 안됨

- 멤버변수중 포인터가 있는 경우에는 특히 주의해야 합니다. Array 클래스만 하더라도 pData가 가리키고 있는 메모리 공간을 통째로 복사해야 완전한 복사가 이루어지는 것이지, pData의 포인터만 복사한다고 복사가 된 것은 아닙니다. 이럴 때는 복사 생성자가 컴파일러에 의해 자동으로 만들어지게 두지 말고 다음과 같이 직접 정의해야 합니다.

 

 

Array::Array(const Array &data)

{

    maxsize = data.maxsize;  //크기를 같게 설정

    pData = new int [maxsize];  //크기만큼 메모리 공간 할당

    memcpy(pData, data.pData, maxsize);  //데이터 영역 복사

}

복사 생성자는 클래스의 인스턴스를 선언할 때뿐만 아니라, 클래스의 인스턴스를 인자로 넘겨주면서 함수를 호출할 때나 함수의 리턴 값으로 클래스의 인스턴스를 리턴할 때와 같이 클래스의 인스턴스가 통째로 복사될 때 호출된다.

 

#include <iostream>

using namespace std;

 class MyClass {

 private:

    int num1;

    int num2;

 public:

    MyClass(int a, int b) {

        num1 = a;

        num2 = b;

    }

   void ShowData() {

        cout << "num1: " << num1 << " num2: " << num2 << endl;

    }

};

int main() {

    MyClass mc1(50, 40);

    MyClass mc2 = mc1;

    mc2.ShowData();

    return 0;

}

결과를 보시면 mc1 객체 내의 num1num2 멤버 변수의 값과 같음을 알 수 있습니다. 마치, 멤버별 복사가 이루어진것처럼 말이죠. 생각해보면, MyClass 객체를 인수로 받는 생성자를 구현하지 않았음에도, 오류가 나지않고 정상적인 결과를 출력합니다. 이는, 우리가 따로 복사 생성자를 정의하지 않아도 디폴트 생성자처럼, 디폴트 복사 생성자가 컴파일러에 의해 삽입됩니다. 아래와 같이 말이죠.

MyClass(int a, int b)

{

    num1 = a;
    num2 = b;

}

MyClass(const MyClass& mc) // 디폴트 복사 생성자의 형태

{

    num1 = mc.num1;

    num2 = mc.num2;

}

위와 같이 멤버별 복사가 이루어지는 방식을 가르켜 '얕은 복사(Shallow Copy)'라고 합니다.

 

얕은 복사의 문제점 깊은 복사로 해결
#include <iostream>
using namespace std;

class MyClass 
{
private:
    char* str;
public:
    MyClass(const char* aStr) 
    {
        str = new char[strlen(aStr) + 1];
        strcpy(str, aStr);
    }
    ~MyClass()
    {
        delete[]str;
        cout << "~MyClass() called!" << endl;
    }
    void ShowData()
    {
        cout << "str: " << str << endl;
    }
};

int main() {
    MyClass mc1("MyClass!");
    MyClass mc2 = mc1;
    mc1.ShowData();
    mc2.ShowData();
    return 0;
}
실행 결과

에러 발생

#include <iostream>

using namespace std;

class MyClass{

private:

    char *str;

public:

    MyClass(const char *aStr) {

        str = new char[strlen(aStr)+1];

        strcpy(str, aStr);

    }

    MyClass(const MyClass& mc){

        str = new char[strlen(mc.str)+1];

        strcpy(str, mc.str);

    }

    ~MyClass() {

        delete []str;

        cout << "~MyClass() called!" << endl;

    }

    void ShowData(){

        cout << "str: " << str << endl;

    }

};

int main() {

    MyClass mc1("MyClass!");

    MyClass mc2 = mc1;

    mc1.ShowData();

    mc2.ShowData();

    return 0;

}
실행 결과

 

왼쪽소스 코드를 한번 컴파일해보면, "~MyClass() called!"단한번만 출력되고 오류가 발생합니다. 생각해보면, mc1 선언과 동시에 생성자 내에서 str를 메모리에 할당합니다. 그리고 mc2 선언시에 디폴트 복사 생성자가 호출되고, 메모리를 할당하지 않고 str의 포인터만 복사합니다. 그런 뒤에, mc2 객체가 먼저 소멸되고 mc2의 소멸자가 호출되고 str를 메모리 공간에서 해제시킵니다. 그리고 mc1 소멸자가 호출되어 str 포인터가 가르키고 있는 메모리 공간을 해제하려 하나, 이미 mc2소멸자에 의해 해제되었으므로 오류가 발생합니다.

 

 

 

 

 

소멸자 함수

소멸자 함수는 오브젝트가 메모리에서 없어질 때 자동적으로 호출되어지는 함수를 말한다.

 

- 소멸자 함수는 클래스이름과 동일한 이름으로 함수 앞에 ~(틸드)기호가 들어 간다.

- 소멸자 함수도 생성자 함수처럼 접근 지정자는 public 으로 선언해야 한다.

- 반환( return)형이 선언되어 있지 않고 실제로도 반환하지 않는다.

- 소멸자 함수는 클래스에서 한 개밖에 선언할 수 없다.

- 소멸자 함수는 매개변수를 가질 수 없다.

 

 

#include <iostream>

using namespace std;

 

class TestClass

{

private:

    int *x;

    int *y;

public:

    TestClass()

    {

        x = new int;

        y = new int;

        *x = 0;

        *y = 0;

    }

    TestClass(int n)

    {

        x = new int;

        y = new int;

        *x = n

        *y = 0;

    }

    TestClass(int n, int n2)

    {

        x = new int;

        y = new int;

        *x = n

        *y = n2

    }

    void Show() const

    {

        cout << "X : "<< *x << ", y : " << *y << endl;

    }

    ~TestClass()

    {

        cout << "소멸자실행 << endl;

        delete x;

        delete y;

    }

};

 

int main(void)

{

    TestClass test1;

    test1.Show();

 

    TestClass test2(50);

    test2.Show();

 

    TestClass test3(20, 50);

    test3.Show();

 

    return 0;

}

실행 결과

X : 0, y : 0

X : 50, y : 0

X : 20, y : 50

소멸자 실행

소멸자 실행

소멸자 실행

 

 

this 포인터 변수

this 포인터 변수란? 클래스 자료형 내에 존재하는 멤버 함수들은 오브젝트의 시작주소 값을 기억할 수 있는 숨겨진 매개변수인 this라는 클래스형 주소변수를 하나씩 가지고 있는데 이를 this포인터 변수라 한다.

 

- this포인터 변수에는 호출하는 오브젝트의 시작주소 값이 자동으로 기억 된다.

- this포인터 변수는 멤버함수의 숨겨진 매개변수로서 우리가 직접 선언할 수 없고 단지 this라는 이름을 이용하여 호출하는 오브젝트 속의 멤버변수나 오브젝트 자체의 공간을 통제할 뿐이다.

 

this포인터 변수를 사용하는 이유

동일한 클래스 자료형을 여러 개의 오브젝트로 만들어 생성해도 메모리 상에는 하나만 만들어진다. 이때 멤버함수들을 각 오브젝트에서 공유하여 사용하게 되는데 어떤 오브젝트에서 호출되어 사용하는지를 알아야 하기 때문에 this포인터로 각 오브젝트들의 시작주소를 참조하여 사용해야 정확하게 처리할 수 있다.

 

#include <iostream>

  using namespace std;

 

class TestClass

{

    int age;

    double height;

public:

    TestClass()

    {

        age = 0;

        height = 0;

    }

    void set(int a, double b)

    {

        age = a

        height = b

    }

};

void disp()

{

    cout << "나이 : " << age << ", : " << height;

}

 

int main(void)

{

    TestClass test1;

    test1.set(13, 142.5);

    test1.disp();

    return 0;

}

실행 결과
나이 : 13, 키 : 142.5

#include <iostream>

using namespace std;

 

class TestClass

{

    int age;

    double height;

public:

    TestClass() {

        this->age = 0;

        this->height = 0;

    }

    void set(int a, double b) {

        this->age = a;

        this->height = b;

    }

    void disp() {

      cout << "나이 : " << this->age << ", : " << this->height << endl;;

    }

    TestClass get() {

        this->age++;

        return *this;

    }

};

int main(void) {

    TestClass test1;

    test1.set(13, 142.5);

    test1.disp();

 

    TestClass test2;

    test2 = test1.get();

    test2.disp();

 

    return 0;

}
실행 결과
나이 : 13, 키 : 142.5
나이 : 14, 키 : 142.5

 

'프로그램언어 > C++' 카테고리의 다른 글

클래스3  (0) 2020.08.07
클래스2 문제  (0) 2020.08.07
클래스1 - 문제  (0) 2020.08.06
클래스1  (0) 2020.08.03
C++ 시작하기  (0) 2019.10.22