Notice
Recent Posts
Recent Comments
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

SYDev

C++ Chapter 04-4 : 클래스와 배열 그리고 this 포인터 본문

Programming Lang/C++

C++ Chapter 04-4 : 클래스와 배열 그리고 this 포인터

시데브 2023. 7. 16. 17:49

객체배열

 객체 기반의 배열은 다음 형태로 선언한다.(SoSimple은 클래스의 이름)

SoSimple arr[10];

 이를 동적으로 할당하는 경우에는 다음의 형태가 된다.

SoSimple * ptrArr = new SoSimple[10];

 이렇게 배열을 선언하면, 열 개의 SoSimple 클래스의 객체가 모여서 배열을 구성하는 형태가 된다. 이렇게 객체 기반 배열을 선언할 때도 생성자는 호출이 되는데, 이 경우에는 생성자에 직접 인자를 전달하지 못한다는 문제점이 생긴다.

 

 그렇기 때문에 위 형태의 배열을 선언하기 위해서는 다음과 같은 형태의 생성자가 선언되어 있어야 한다.

SoSimple() { . . . . }

 이후에 각각의 요소를 원하는 값으로 초기화시키기 위해서는 각각의 객체에 별도로 초기화의 과정을 거쳐야 한다.

 

 이전 예제에서 정의한 Person 클래스를 기반으로 해당 내용을 더 살펴보자.

#include <iostream>
#include <cstring>
using namespace std;

class Person
{
private:
    char * name;
    int age;
public:
    Person(char * myname, int myage)
    {
        int len=strlen(myname)+1;
        name=new char[len];
        strcpy(name, myname);
        age = myage;
    }

    Person()
    {
        name = NULL;
        age = 0;
        cout<<"called Person()"<<endl;
    }

    void SetPersonInfo(char * myname, int myage)
    {
        name = myname;
        age = myage;
    }

    void ShowPersonInfo() const
    {
        cout<<"이름 : "<<name<<", ";
        cout<<"나이 : "<<age<<endl;
    }
    ~Person()
    {
        delete []name;
        cout<<"called destructor!"<<endl;
    }
};

int main(void)
{
    Person parr[3];
    char namestr[100];
    char * strptr;
    int age;
    int len;

    for(int i=0; i<3; i++)
    {
        cout<<"이름 : ";
        cin>>namestr;
        cout<<"나이 : ";
        cin>>age;
        len = strlen(namestr)+1;
        strptr = new char[len];
        strcpy(strptr, namestr);
        parr[i].SetPersonInfo(strptr, age);
    }

    parr[0].ShowPersonInfo();
    parr[1].ShowPersonInfo();
    parr[2].ShowPersonInfo();

    return 0;
}
called Person()
called Person()
called Person()
이름 : 한지수
나이 : 21
이름 : 양은정
나이 : 31
이름 : 이한영
나이 : 34
이름 : 한지수, 나이 : 21
이름 : 양은정, 나이 : 31
이름 : 이한영, 나이 : 34
called destructor!
called destructor!
called destructor!

 

this 포인터의 이해

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

 

 다음 예제를 보면서 this 포인터를 이해해보자.

#include <iostream>
#include <cstring>
using namespace std;

class SoSimple
{
private:
    int num;
public:
    SoSimple(int n) : num(n)
    {
        cout<<"num = "<<num<<", ";
        cout<<"address = "<<this<<endl;
    }

    void ShowSimpleData()
    {
        cout<<num<<endl;
    }

    SoSimple * GetThisPointer()
    {
        return this; // 이 문장을 실행하는 객체의 포인터를 반환
    }
};

int main(void)
{
    SoSimple sim1(100);
    SoSimple * ptr1 = sim1.GetThisPointer(); // sim1 객체의 주소값 저장
    cout<<ptr1<<", ";
    ptr1 ->ShowSimpleData(); // ptr1이 가리키는 객체의 ShowSimpleData 함수를 호출

    SoSimple sim2(200);
    SoSimple * ptr2 = sim2.GetThisPointer(); // sim2 객체의 주소값 저장
    cout<<ptr2<<", ";
    ptr2 ->ShowSimpleData(); // ptr2이 가리키는 객체의 ShowSimpleData 함수를 호출
    return 0;
}
num = 100, address = 0x61fe0c
0x61fe0c, 100
num = 200, address = 0x61fe08
0x61fe08, 200

 예제 코드와 실행 결과에서 보이듯이 this는 객체 자신의 주소값을 의미한다. 

 

this 포인터의 활용

 this 포인터의 활용을 배우기 전에, 다음 클래스를 관찰해보자.

class ThisClass
{
private:
    int num; // 207이 저장됨
public:
    void ThisFunc(int num)
    {
        this -> num = 207;
        num = 105; // 매개변수 값을 105로 변경함
    }
    . . . . .
};

 해당 클래스에서 함수 ThisFunc의 매개변수 num은 클래스 ThisClass의 멤버변수 num과 이름이 같다. 함수 내에서 num은 매개변수 num을 의미하기 때문에 변수의 이름만으로는 멤버변수 num에 접근할 수 없다. 이럴때 this 포인터를 활용하면 멤버변수 num에 접근할 수 있다. 

 

 객체를 참조하는 포인터는 지역변수에 접근이 불가능하기 때문에 이러한 특성을 활용하여 멤버변수와 매개변수의 이름이 같더라도 각각의 변수에 적절하게 접근이 가능하다.

 

 다음 예제를 통해 더 자세히 살펴보자.

#include <iostream>
using namespace std;

class TwoNumber
{
private:
    int num1;
    int num2;
public:
    TwoNumber(int num1, int num2)
    {
        this -> num1 = num1; // this -> num1은 멤버변수
        this -> num2 = num2; // this -> num2은 멤버변수
    }
    /*
    TwoNumber(int num1, int num2)
        :num1(num1), num2(num1) //이니셜라이저에서는 this 포인터 사용 불가능, but 형태가 "멤버변수(매개변수)"
    {
        //empty
    }
    */

   void ShowTwoNumber()
   {
    cout<<this -> num1<<endl;
    cout<<this -> num2<<endl;
   }
};

int main(void)
{
    TwoNumber two(2, 4);
    two.ShowTwoNumber();

    return 0;
}
2
4

 

Self-Reference의 반환

 Self-Reference객체 자신을 참조할 수 있는 참조자를 의미한다. 우리는 this 포인터를 이용하여 자신의 참조에 사용 가능한 참조자의 반환문을 구성할 수 있다. 

 

 이와 관련된 다음 예제를 확인하자.

#include <iostream>
using namespace std;

class SelfRef
{
private:
    int num;
public:
    SelfRef(int n) : num(n)
    {
        cout<<"객체 생성"<<endl;
    }

    SelfRef& Adder(int n) // 객체 자신의 참조의 정보(참조값)을 반환
    {
        num +=n;
        return *this; // 객체 자신의 값
    }

    SelfRef& ShowTwoNumber() // 객체 자신의 참조의 정보(참조값)을 반환
    {
        cout<<num<<endl;
        return *this; // 객체 자신의 값
    }
};

int main(void)
{
    SelfRef obj(3);
    SelfRef &ref = obj.Adder(2); // 함수 Adder의 반환값이 자신의 참조값이기 때문에 ref는 obj를 참조

    obj.ShowTwoNumber();
    ref.ShowTwoNumber();

    ref.Adder(1).ShowTwoNumber().Adder(2).ShowTwoNumber();

    return 0;
}
객체 생성
5
5
6
8

 

참조의 정보(참조값)에 대한 이해

 위에서 언급한 참조의 정보란 무엇일까? 우선 다음 코드를 확인해보자.

int main(void)
{
    int num = 7;
    int &ref = num; // 무엇이 전달된다고 표현할까?
    . . . .

 위 코드에서 ref에 전달하는 것은 무엇이라고 표현해야 할까? 우선 num에 저장된 정수값은 아니다. 그렇기 때문에 대입 연산에 어울리게 다음과 같이 표현하기도 한다.

 

"변수 num을 참조할 수 있는 참조의 정보가 전달된다."

 

  다시 말해 변수 num을 참조할 수 있는 참조값이 참조자 ref에 전달되어, 참조자 ref가 변수 num을 참조하게 되는 것이다.

 


출처 : 윤성우, <윤성우의 열혈 C++ 프로그래밍>, 오렌지미디어, 2010.05.12