3.연산자(operator) 오버로딩

본장에서는  class의 특징중의 하나인 연산자 오버로딩에 대해서 설명을 합니다. 본책에서는 오버로딩과 연산자 오버로딩을 분리하였습니다. 그이유는 연산자 오버로딩부분이 실제 오버로딩과 기능은 같으나 일반적으로 그 사용법등이 다소 차이가 있다고 생각하기 때문입니다. 연산자 오버 로딩이란 클래스와 클래스사이에 기존에 사용하였던 연산자를 새로운 방식으로 사용하는 것을 말합니다.

본장을 통해서 C++의 또하나의 막강한 기능을 배우시게 될것입니다.

연산자 오버로딩이란?

연산자는 보통 +,-,*,/,= 같은 산술연산자와 형전환등에 사용하는 casting연산등과 &&,||,^ 등의 집한 연산자를 이야기 하는데 클래스끼리 이런 연산이 가능하며 또한 클래스에서 이런 연산을 이용하여 새로운 데이터를 만들 수 있다는 것입니다. 예를 든다면 전까지 사용하였던 CNamecard의 변수끼리 연산자를 사용하여 계산을 할수 있다는 것입니다.

CNamecard data1,data2,data3;

:

data3=data1 + data2;

위의 예를 보면 다소 의아해 할것입니다. 이름,전화번호,주소가 있는 데이터를 +한다는 것은 무슨 의미인가? 하고 말입니다. 이때 사용하는 + 라는 연산을 일반적개념으로 사용하는 것이 아니라 클래스 내에서 새로 정의하여 사용할수 있다는 것입니다. 기존에 일반 방식으로 사용하였던 연산방법이 클래스내에서는 다른 방식으로 전환 된다는 것을 연산자 오버로딩이라고 합니다. 인간 세계에서 예를 든다면 결혼식 할 때 주례에서 이런 말을 하죠. □ 이제 신랑 신부는 둘이 하나가 되어...□

여기에서 둘이 합쳐서 하나가 된다는 의미인데 결국 신랑 + 신부 = 하나 라는의미입니다. 이게 일반 산수에서는 가능하지 않는 이야기입니다. 그렇다고 주례도중에 이말이 잘못되었다고 이야기하는 사람 하나도 없습니다. 여기에서 신랑 + 신부에서 덧셈의 의미는 산수에서 생각하는 덧셈이 아니라 마음을 합치라는 덧셈입니다.

클래스에서도 마찬가지입니다. 덧셈이나 뺄셈등을 클래스의 성격에 맟추어서 하나의 형식을 정하고 이것에 의해서 처리를 할수 있다는 것입니다.

연산자를 오버로딩할 경우 보통 다음과 같은 형식으로 설정합니다.

[형] operator [연산자] (인자들...);

예를 든다면 박스 좌표를 가지고 있는 CRectangle라는 클래스를 만들고 이클래스에서 증가 연산자 += 이라는 연산자를 사용하고자 할 경우 다음과 같이 정의 할수 있습니다.

class CRectangle{

:

CRectangle operator += (CRectangle data);

}

위의 예에서 operator + 는 CRectangle 형의 data라는 인자를 받아서 어떤 처리를 한다음 이것을 CRectangle라는 형으로 리턴하라는 의미입니다.

위와 같이 설정을 하였다면 다음과 같이 사용을 할 경우 +=는 클래스에 정의한 방식대로 계산됩니다.

CRectangle data1,data2;

data1+=data2; //+= 연산자는 CRectangle에 설정된 연산 방식에 의거한다.

본항목에서의 요점은 바로 연산자를 클래스 내에서 정의할수 있다는 것입니다.

그것을 꼭기억하시기 바랍니다.

연산자 오버로딩 사용하기

박스 영역을 설정하는 좌표 reft,top,right,bottom을 윈도우에서는 주로 많이 사용하는 좌표입니다. SDK에서는 RECT라는 구조체로 이좌표를 사용했습니다. 좀더 진보적인 방법으로 클래스를 이용하여 CRectangle라는 구조체를 만들어 봅시다.

다음은 CRectangle라는 구조체를 만든 예입니다.

class CRectangle

{

protected:

int left;

int right;

int top;

int bottom;

public:

int GetLeft(){ return left;}

int GetRight(){ return right;}

int GetBottom(){ return bottom;}

int GetTop(){ return top;}

CRectangle(int x1,int y1,int x2,int y2);//생성자

CRectangle operator += (CRectangle data);

}

CRectangle::CRectangle(int x1,int y1,int x2,int y2 )

{

left=x1;

top=y1;

right=x2;

bottom=y2;

}

CRectangle CRectangle::operator += (CRectangle rect)

{

left+=rect.GetLeft();

right+=rect.GetRight();

bottom+=rect.GetBottom();

top+=rect.GetTop();

return *this;

}

생성자 CRectangle는 4개의 좌표를 받아서 left,top,right,bottom에 설정하는 함수입니다. 다음 operator += 함수는 인자로 들어온 rect의 left,top,right,bottom의 값을 본함수의 left,right,bottom,top에 더해주고 return *this를 해줍니다.

+= 연산자가 어떤 것을 하는것인지는 알겠는데 return *this의 의미를 모르실것입니다. 지금까지 사용하지 않은 새로운 문장이기 때문입니다. this라는 문장은 바로 자기 자신을 의미합니다. 위의 +=연산자를 이용하여 하나의 예를 들어 보면서 설명을 하겠습니다.

CRectangle data1(100,10,10,0);

CRectangle data2(100,100,200,200);

data1+=data2;

생성자에 인자를 넘겨줄경우에 정적일경우에는 위와 같은 방법으로 설정해줍니다. 만일 동적일 경우 즉 포인터를 이용하여 메모리에 할당할 경우에는 다음과 같이 합니다.

CRectangle *data1= new CRectangle(100,10,10,0);

data1+=data2;라는 문장을 보면 += 연산자에 인자를 data2를 넘겨주었습니다. 이렇게 넘겨줄 때 바로 수행되는 연산자 함수는 data1에 설정된 함수입니다. 그렇기 때문에 data1안에 있는 100,10,10,0에 100,100,200,200값을 더하게 됩니다. 더한 값은 200,110,210,200 이됩니다. 이렇게된후 return *this를 했기 때문에 자기 자신인 바로 data1이라는 변수가 리턴이 되는 것입니다. 이값이 다시 data1에 설정이 되므로 data1에는 200,110,210,200입니다. return *this라는 의미는 현재 자신을 이야기 합니다.  this는 C++에서 많이 사용하는 문장입니다. 때로는 자기 자신을 포인터로 넘겨줄경우도 있기 때문입니다. 즉 클래스 내부에서 자신을 포인터로 넘겨줄수가 있는 것입니다. 이부분을 많은 분들이 참 이해를 못하는 것 같습니다. 매일 매일 this개념을 사용하고 있으면서도 말입니다. 예를 들어 봅시다. 집에 전화를 하고 보통 뭐라고 합니까? □ 나야!□ 이것은 일반적인 말입니다. 필자의 사부님 (한양대학교 전자공학과 김회율 교수님)도 전화를 걸면 □음 난데..□ 이럽니다. 여기서 나라는 것이 누굽니까? 말안해도 우리는 알지 않습니까? 나라고 이야기 하는 사람 내부의 목청에서 □나야□ 라고 말합니다. 즉 맴버 함수가 이야기를 하면서 바로 나를 표현합니다. 마찬가지로 this라는 것은 바로 현재 클래스의 변수 □나□ 라는의미입니다. this라는 의미가 클래스 형이 아닙니다. data라는 CRectangle 형을 가진 변수에서 this를 썼다면 data라는 의미입니다. □나야□ 할 때 내가 사람이라는 형을 가르키는게 아니지 않습니까? 바로 □사람의 형을 가진 이상엽이야□ 라는 의미인것처럼 this라는 의미도 같은 것입니다.

연산자 오버로딩 사용예제 CpEx04

CpEx04는 = 연산자와 +=연산자를 오버로딩하여 사용한 예입니다. 먼저 소스를 보시겠습니다.

(프로그램 소스)

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

class CRectangle{

protected:

int left;

int right;

int bottom;

int top;

public:

int GetLeft(){ return left;}

int GetRight(){ return right;}

int GetBottom(){ return bottom;}

int GetTop(){ return top;}

CRectangle(int,int,int,int);

CRectangle operator = (CRectangle rect);

CRectangle operator += (CRectangle rect);

};

CRectangle::CRectangle(int x1,int y1,int x2,int y2 )

{

left=x1;

top=y1;

right=x2;

bottom=y2;

}

CRectangle CRectangle::operator = (CRectangle rect)

{

left=rect.GetLeft();

right=rect.GetRight();

bottom=rect.GetBottom();

top=rect.GetTop();

return *this;

}

CRectangle CRectangle::operator += (CRectangle rect)

{

left+=rect.GetLeft();

right+=rect.GetRight();

bottom+=rect.GetBottom();

top+=rect.GetTop();

return *this;

}

void main()

{

CRectangle rect(0,0,100,100);

CRectangle rect2(0,0,0,0);

char temp[80];

int x1,y1,x2,y2;

printf("rect:%d %d %d %d\n",rect.GetLeft(),

rect.GetRight(),rect.GetBottom(),

rect.GetTop());

printf("새로운값:");

gets(temp);

sscanf(temp,"%d %d %d %d",&x1,&y1,&x2,&y2);

rect=CRectangle(x1,y1,x2,y2);

printf("더할값:");

gets(temp);

sscanf(temp,"%d %d %d %d",&x1,&y1,&x2,&y2);

rect2=CRectangle(x1,y1,x2,y2);

rect+=rect2;

printf("rect:%d %d %d %d",rect.GetLeft(),

rect.GetRight(),rect.GetBottom(),

rect.GetTop());

gets(temp);

}

(프로그램 소스끝)

rect에는 0,0,100,100 이라는 사각형 좌표가 설정되어 있으며 rect2에는 0,0,0,0이라는 좌표가 설정되어 있습니다. 먼저 rect의 좌표값을 출력한후 gets(temp)라는 함수에의해 temp안에 4개의 좌표값을 받습니다. 이때 입력할때는 숫자 4개를 공백을 띄어서 입력해야합니다. 그이유는 다음 문장에 있습니다. 다음문장에서 temp안에 있는 데이터에서 4개의 정수를 sscanf로 받기 때문입니다.

그다음 gets(temp)로 또다시 숫자 4개를 받고 이숫자를 rect2에 설정합니다. 그런 후에 +=연산자에 의해서 두값을 받는 것입니다. 이때 +=연산자는 오버라이딩 된 연산자로써 operator += (CRectangle rect) 함수가 실행됩니다. 여기에서 주목해야 할 문장이 있습니다. 이문장은 다음과 같습니다.

rect=CRectangle(x1,y1,x2,y2);

위의 문장은 다음 두문장을 합친것입니다.

CRectangle data1(x1,y1,x2,y2);

rect=data1;

위의 예에서 data1를 생략하고 바로 CRectangle를 사용한것입니다. 여기에서 = 라는 연산자가 사용됩니다. 이연산자가 사용되면 바로  오버라이딩 된 operator = (CRectangle rect) 함수가 실행됩니다. 결국 rect에는 x1,y1,x2,y2가 설정되는 것입니다. 그림1은 CpEx04의 출력 결과입니다.

(그림 1)CpEx04 출력 결과

문자열 사용 클래스 만들기

프로그램에서 문자열을 사용하는 예가 매우 많습니다. 문자를 데이터로 설정하고 문자열의 크기를 얻고 문자열에 새로운 데이터를 넣고 등등 말입니다. 이때마다 우리는 strcpy또는 strcat또는 strstr이라는 함수를 사용하였고 해당 문자를 저장할 변수를 정적으로 설정하거나 또는 동적으로 설정하여 메모리를 할당하는등 사실 복잡한 방법을 사용하였습니다. 이제 문자를 컨트롤 하는 하나의 클래스를 만들어서 이클래스의 맴버 함수를 이용하여 문자를 컨트롤 하도록 해보겠습니다.

문자열 컨트롤 클래스를 CStringData로 설정하였고 다음가 같은 클래스를 정의하였습니다.

class CStringData{

protected:

char *buff;

int size;

public:

CStringData();//생성자

~CStringData();//소멸자

void PrintData();//문자열을 화면에 출력한다.

void GetInput();//문자열을 콘솔에서 입력받는다.

int GetSize(){return size;}//문자열 크기를 리턴한다.

char *GetString(){return buff;}//문자열을 리턴한다.

CStringData operator = (CStringData data); //대입 연산자 오버로딩

CStringData operator += (CStringData data);//증가 연산자 오버로딩

operator const char* ();//const char* 연산자 오버로딩

};

문자열을 컨트롤 할려면 이외에 많은 클래스의 맴버 변수가 필요하나 간단하게 예를 보이기 위해서 위의 함수만 설정하였습니다.

먼저 생성자와 소멸자를 보겠습니다.

CStringData::CStringData()

{

buff=NULL;

size=0;

}

CStringData::~CStringData()

{

if(buff)

delete buff;

}

생성자는 buff를 NULL로 하고 size=0으로 하였습니다. 소멸자는 만일 buff가 NULL이 아니라면 즉 메모리가 할당 되어 있다면 이 메모리를 해제 하는 내용을 담았습니다.

PrintData함수는 단순히 화면에 데이터를 출력하는 내용을 담았습니다.

void CStringData::PrintData()

{

printf("%s\n",buff);

}

위와 같이 하면 buff에 데이터가 들어있을 경우 그 내용을 출력하게 됩니다.

GetInput함수는 콘솔모드에서 데이터를 입력받는 함수입니다. 이함수는 다음과 같이 설정합니다.

void CStringData::GetInput()

{

char temp[80];

gets(temp);

size=strlen(temp);

buff=new char[size];

strcpy(buff,temp);

}

먼저 temp라는 변수를 만들고 gets라는 함수에 의해서 temp에 입력 받은 문자열을 넣고 strlen함수에 의해서 문자열의 크기를 얻은다음 그크기만큼 문자열을 buff에 저장합니다.

GetSize는 size를 리턴하며 GetBuff 입력된 문자열을 리턴하는 함수입니다.

이제 본장의 주제가 담긴 연산자 오버로딩 함수 3개를 보겠습니다. 먼저 대입연산자입니다.

CStringData CStringData::operator = (CStringData data)

{

size=data.GetSize();//데이터의 크기를 알고

buff= new char[size];//그크기만큼 메모리를 할당한후

strcpy(buff,data.GetString());//data안의 문자열을 복사한다.

return *this;//복사한 데이터를 리턴한다.

}

인자로 넘겨준 data의 GetSize함수에 의해서 크기를 얻고 buff에 그크기만큼 메모리를 할당합니다. 이 버퍼에 data의 문자열을 복사하고 자신을 리턴합니다.

다음은 증가연산자입니다.

CStringData CStringData::operator += (CStringData data)

{

char temp[512];

strcpy(temp,buff);//기존의 데이터를 temp에 복하하고

size+=data.GetSize();//현재 크기에 data의 크기만큼 더해주고

buff=new char[size];//새로 buff를 메모리에 할당

strcpy(buff,temp);//temp를 복사하고

strcat(buff,data.GetString());//data의 문자열을 복사한다.

return *this;

}

증가연산자 += 이 문자열에서의 의미는 strcat와 같을것입니다. 즉 현재의 문자열에 data안에 있는 문자열을 넣는것입니다. 이렇게 하기 위해서는 먼저 temp를 만들어 놓고 이곳에 기존의 문자열을 복사한다음 buff를 data에 있는 문자와 현재 설정된 문자를 합친 크기만큼 확대한 후에 temp에 있는 데이터를 buff에 넣은후에 이곳에 data안에 있는 문자열을 넣는것입니다.

이제 operator const char *()입니다. char *()는 리턴값이 없습니다. 리턴값이 없는 것이 아니라 char* 형태로 리턴을 한다는 의미입니다. 사칙연산이나 집합연산자와 달리 형을 이용안 연잔자는 자신의 형을 그대로 사용할수 있습니다. char *()의 의미는 바로 현재의 클래스의 데이터를 문자열 포인터로 전환하자는 의미입니다.

이함수는 다음과 같이 설정됩니다.

CStringData::operator const char *()

{

return buff;

}

만일 현재 문자열을 숫자로 넘긴다면 어떻게 사용할수 있을까요?

다음과 같을 것입니다.

CStringData::operator int ()

{

return atoi(buff);

}

지금까지 설명한 클래스를 이용하여 하나의 예제를 만든 것이 CpEx05입니다. CpEx05의 main함수는 다음과 같습니다.

void main()

{

char temp[512];

CStringData *data= new CStringData;

CStringData *data2= new CStringData;

printf("문자열1:");

data->GetInput();//data에 문자열을 받고

printf("문자열2:");

data2->GetInput();//data2에 문자열을 받고

*data += *data2;//data에 data2의 넣은다음

printf("문자열1+문자열2:");

data->PrintData();//data를 출력한다.

//char* 로 리턴하는 연산

sprintf(temp,"%s",data->operator const char*());

printf("%s\n",temp);

}

위의 내용에서 *data += *data2;로 사용한 것은 data와 data2가 포인터 이기 때문입니다. 또한 += 연산자는 설정되는 인자가 포인터가 아닙니다. 그렇기 때문에 CStringData* 의 값을 CStringData로 전환하기 위해서 앞에 □*□를 사용한 것입니다. 그림 2은 CpEx05출력 결과입니다.

(그림 2) CpEx05 출력 결과