본문 바로가기

프로그래밍 언어들/C++

CHAPTER04 - 클래스의 완성-2

1. 이니셜라이저를 통한 참조자인 멤버변수 초기화

const 변수와 마찬가지로 '참조자'도 선언과 동시에 초기화가 이루어져야 합니다.

따라서 아래와 같이 이니셜라이저를 통해 초기화가 가능합니다.

class Second
{
private:
    int #
public:
    Second( int &n) : num(n) {}
};


2. 디폴트 생성자(Default Constructor)

객체가 되기 위해서는 반드시 하나의 생성자가 호출되어야 한다. 그리고 이러한 기준에 예외를 두지 않기 위해

생성자를 정의하지 않는 클래스에는 C++ 컴파일러에 의해서 디폴트 생성자라는 것이 자동으로 삽입된다.

즉, 생성자를 선언하지 않으면 다음과 같은 정의와 완전히 동의하다.

class Second
{
private:
    int #
public:
    Second() {} // default constroctor
};


따라서, 모든 객체는 한 번의 생성자 호출을 동반한다. 이는 new 연산자를 이용한 객체의 생성에도 해당한다.

class Second
{
private:
    int #
public:
    Second() {} // default constroctor
};

int main( void)
{
    Second *s = new Second;

    return 0;
}


그러나, C언어의 malloc 함수를 대신 이용하면 생성자는 호출되지 않는다.

Second *s = (Second*)malloc(sizeof(Second));


malloc 함수 호출 시, 실제로는 AAA의 클래스의 크기정보만 바이트 단위로 전달되기 때문에 생성자가 호출되지 않는다.

따라서 객체를 동적으로 할당하려는 경우에는 반드시 new 연산자를 이용해야 한다.


3. 생성자 불일치

만약 선언한 생성자가 디폴트 생성자의 형태(매개변수가 없는)일 경우에는 디폴트 생성자가 삽입되지 않기 때문에

기본 형태로는 객체 생성이 불가능하다.

class Second
{
private:
    int #
public:
    Second( int &n) :num(n) {}
};

int main( void)
{
    Second s; // (X)
    Second *sp = new Second; // (X)

    return 0;
}


위의 형태를 가능하게 하려면, 디폴트 생성자를 추가해야 한다.


4. private 생성자

앞에 보인 생성자들은 모두 public으로 선언되었다. 객체의 생성이 클래스의 외부에서 진행되기 때문이다.

그러나 클래스 내부에서 객체를 생성한다면, 생성자가 'private'로 선언 되어도 된다.

class First
{
private:
    int num;
public:
    First( void) :num(0) {}
    First& createinitobj( int n) const
    {
        First *f = new First(n);

        return *f;
    }
private:
    First( const int n) : num(n) {}
};


위 예제는 힙 영역에 생성된 객체를 참조의 형태로 반환한다. 따라서,

"힙에 할당된 메모리 공간은 변수로 간주하여, 참조자를 통한 참조가 가능하다."라는 사실을 확인시켜 준다.

또한 'private' 으로 선언된 생성자를 통해서 객체의 생성이 가능함을 보였다.


5. 소멸자(Destructor)
소멸자는 객체소멸시 반드시 호출되는 함수이다.

- 클래스의 이름 앞에 '~'가 붙은 형태이다.

- 반환형이 선언되어 있지 않으며, 실제로 반환하지 않는다.

- 매개변수는 void형으로 선언되어야 하기 때문에 오버로딩 및 디폴트 값 설정이 불가능하다.
생성자와 마찬가지로, 프로그래머가 직접 정의하지 않으면, 디폴트 소멸자가 삽입된다.

즉, 다음의 클래스는 그 아래의 예제와 같다.

class First
{
};
class First
{
public:
    First() {}
    ~First() {}
};


소멸자는 보통 생성자에서 할당한 리소스의 소멸에 사용된다.

예를 들어 생성자 내에서 new 연산자를 할당해 놓은 메모리가 있다면 소멸자에서는 delete 연산자를 통해

이 메모리 공간을 소멸한다.

class First
{
    char *myname;
public:
    First( char *name)
    {
        int len = strlen(name) + 1;
       
        myname  = new char[len];

        strcpy( myname, name);
    }
    ~First()
    {
        delete []myname;
    }
};


6. 객체 배열
C언어 구조체 배열과 유사한 형태이다. 하지만 배열을 선언하는 경우에도 생성자는 호출이 된다.

단, 배열의 선언과정에서는 호출할 생성자를 별도로 명시하지 못한다.( 생성자에 인자를 전달하지 못한다)

즉, 객체를 배열로 생성하려면 디폴트 생성자가 반드시 정의되어 있어야 한다.

각각의 요소를 임의의 값으로 초기화시키길 원한다면, 일일이 초기화의 과정을 별도로 거쳐야 한다.

class Person
{
private:
    char *myname;
public:
    Person( const char *name) {}
    Person() {} // it's necessary to declair the array of obj
    void setname( char *name)
    {
        myname  = name;
    }
};

int main( void)
{
    Person p[3];
    char name[20];
    char *strptr;
    int len;

    // we have to initailize the elements of the array
    for( int i = 0 ; i < 3 ; i ++)
    {
        cin >> name;
        len = strlen(name);

        strptr  = new char[len];
        strcpy_s( strptr, len, name);

        p[i].setname( strptr);
    }
    return 0;
}


7. 객체 포인터 배열

객체 배열이 객체로 이뤄진 배열이라면, 객체 포인터 배열은 객체의 주소 값 저장이 가능한 포인터 변수로 이뤄진 배열이다.

즉, 배열의 각 원소에 동적할당을 하면서 생성자를 통한 초기화가 가능하다.

class Person
{
private:
    char *myname;
public:
    Person( const char *name)
    {
        int len = strlen(name);

        myname  = new char[len];
        strcpy_s( myname, len, name);
    }
    Person() {} // it's necessary to declair the array of obj
};

int main( void)
{
    Person *p[3];
    char name[20];

    for( int i = 0 ; i < 3 ; i ++)
    {
        cin >> name;

        p[i]    = new Person(name);

    }
    return 0;
}


※ 객체 배열과 객체 포인터 배열 중 한 가지를 선택해야 한다. 저장의 대상을 객체로 하느냐,

객체의 주소로 하느냐( 스택 or 힙)를 결정해야 한다.


8. This 포인터
멤버함수 내에서는 'this'라는 이름의 포인터를 사용할 수 있는데, 이는 객체 자신을 가르키는 용도로 사용된다.

class Simple
{
private:
    int num;
public:
    Simple( const int n) : num(n) {}
    Simple* gethispointer( void)
    {
        return this;
    }
};

int main( void)
{
    Simple s(100);
    Simple *p   = s.gethispointer();

    return 0;
}


즉, 'this' 포인터는 객체 자신의 주소 값을 의미한다.


(1) This 포인터의 활용

this 포인터를 이용하면, 멤버변수와 매개변수의 이름을 달리하기 위해서 고민할 필요가 없다.

class Simple
{
private:
    int num;
public:
    Simple( const int num)
    {
        this->num   = num;
    }
};


9. Self-Reference

객체 자신을 참조할 수 있는 참조자를 의미한다. 'this' 포인터를 이용하여, 객체가 자신의 참조에 사용할 수 있는

참조자의 반환문을 구성할 수 있다.

class Simple
{
private:
    int num;
public:
    Simple( const int n) : num(n) {}
    Simple& adder( const int n)
    {
        num += n;
        return *this;
    }
    Simple& showtwonumber(void)
    {
        cout << num << endl;
        return *this;
    }
};

int main( void)
{
    Simple s(8);
    Simple &ref = s.adder(2);

    s.showtwonumber();
    ref.showtwonumber();

    s.adder(2).showtwonumber().adder(3).showtwonumber();
    return 0;
}


this 포인터가 가르키는 값(객체의 주소)를 통해 참조자를 구성할 수 있다.

즉, s와 ref가 같은 공간을 지칭하는 것이다. 그리고 맨 아래 출력문의 구성을 보면,

adder 함수가 this 포인터를 통해 객체의 주소를 반환하고, 그것을 이용하여 수를 출력한다.

그리고 또 거기서 반환되는 객체 주소를 통해 adder를 호출, 그리고 showtwonumber를 호출한다.


출처 : 윤성우 열혈 C++ 프로그래밍