연산자 오버로딩
1.연산자 오버로딩의 의미와 방법
(1)연산자오버로딩의 의미
-기본자료형 - int, 대입연산,사칙연산가능
-사용자정의형(구조체) - 대입연산,사칙연산 불가능
사용자정의형으로도 기본자료형처럼 쓸 수도록 하기 위해서 등장한 것이 연산자오버로딩이다.
cf)C++에 대한 전문가의 평가 : 문법을 너무 남용하는 측면이 있는 문법요소 :friend, 연산자오버로딩
(2)오버로딩의 방법
<1>멤버함수에 의한 오버로딩
class Point
{
private:
int x,y;
public:
Point(int _x = 0,int _y= 0):x(_x),y(_y){}
void ShowPosition()
{
cout<<x<<" "<<y<endl;
}
void operator+ (int val)
{
x += val;
y += val;
}
Point operator+(const Point& p)
{
Point temp(x + p.x , y + p.y);
return temp;
}
};
//"연산자만으로 함수가 호출되는 것을 허용하겠다."
int main()
{
Point p(3,4);
p.ShowPosition();
p.operator+(10);
p.ShowPosition();
Point p1(1,2);
Point p2(2,1);
Point p3 = p1 + p2; //operator+함수를 찾는다.
//함수로 존재한다.멤버함수에 연산자오버로딩이 존재한다. p1.operator(p2);로 변환된다.
p3.ShowPosition();
return 0;
}
<2>전역함수에 의한 오버로딩
class Point
{
private:
int x,y;
public:
Point(int _x = 0,int _y= 0):x(_x),y(_y){}
void ShowPosition()
{
cout<<x<<" "<<y<endl;
}
void operator+ (int val)
{
x += val;
y += val;
}
friend Point operator+(const Point& p1,const Point& p2);
};
Point operator+(const Point& p1,const Point& p2)
{
Point temp(p1.x + p2.x , p1.y + p2.y);
return temp;
}
//"연산자만으로 함수가 호출되는 것을 허용하겠다."
int main()
{
Point p1(1,2);
Point p2(2,1);
Point p3 = p1 + p2; //operator+함수를 찾는다.함수를 찾는다.
//전역에 +연산자오버로딩이 존재한다. operator(p1,p2);로 변환된다.
p3.ShowPosition();
return 0;
}
2.단항 연산자의 오버로딩
(1)연산자 오버로딩 방법
++p
멤버함수라면==> p.operator++();
전역함수라면==>operator++(p);
class Point
{
private:
int x,y;
public:
Point(int _x = 0,int _y= 0):x(_x),y(_y){}
void ShowPosition()
{
cout<<x<<" "<<y<endl;
}
Point& operator++() //참조를 반환하는 이유 : ++(++p);와 같은 연산을 허용하기 위해서
//만약에 반환값이 Point라면 ++p의 반환값이 p의 복사본p2가 리턴되고 , p복사본 p2에 operator++가 호출된다.
//원본p는 한번만 증가하게되고 복사본만이 두 번 증가하게 된다.따라서 값에 의한 반환은 문제가 있다.
//코드의 안정성을 위해서 void 반환 코드는 쓰지말자.
{
x++;
y++;
return *this;
}
friend Point operator+(const Point& p1,const Point& p2);
};
Point& operator--(Point& p)
{
p.x--;
p.y--;
return p;
}
int main()
{
Point p(1,2);
++p;
p.ShowPosition();
--p;
p.ShowPosition();
++(++p); //p.operator++()으로 반환값으로 p의 참조가 반환한다. 다시 ++p의 형태가 된다.
//다시 p.operator++()으로 반환값으로 p를 반환한다; 따라서 x,y값이 두 번 증가한다.
p.ShowPosition();
--(--p);//operator--(p)는 p의 참조를 반환하고
//p의 참조에 다시 operator--(p)가 수행되어 x,y값이 각가 두 번 감소한다.
p.ShowPosition();
return 0;
}
(2)선 연산과 후 연산의 구분
++p ==> p.operator++();
p++ ==> p.operator++(int); //int 형 ++연산자가 앞에 붙였느냐 뒤에 붙였느냐 구분하기 위해서 쓰인다.
--p ==> p.operator--();
p-- ==> p.operator--(int);
class Point
{
private:
int x,y;
public:
Point(int _x = 0, int _y = 0):x(_x),y(_y){}
void ShowPosition()
{
cout<<x<< " " <<y<<endl;
}
Point& operator++() //선연산
{
x++;
y++;
return *this;
}
Point operator++(int) //후연산
{
Point temp(x,y); //지역객체이므로 참조로 리턴할 수 없다.
x++;
y++;
return temp; //증가하기 이전의 복사본을 리턴한다.
}
};
int main()
{
Point p1(1,2);
(p1++).ShowPosition(); //1,2
p1.ShowPosition();//2,3
Point p2(1,2);
(++p2).ShowPosition();//2,3
return 0;
}
(3)연산자오버로딩의 용도
-STL, 대입연산자
3.교환법칙 해결
class Point
{
private:
int x,y;
public:
Point(int _x = 0, int _y = 0):x(_x),y(_y){}
void ShowPosition()
{
cout<<x<< " " <<y<<endl;
}
Point operator+(int val)
{
Point temp(x + val , y + val);
return temp;
}
friend Point operator+(int val,Point& p);
};
//첫번째 방법
Point operator+(int val , Point& p)
{
Point temp(p.x + val , p.y + val);
return temp;
}
//두 번째 방법 (순서 바꾸기)
Point operator+(int val , Point& p)
{
return p + val;
}
int main()
{
Point p1(1,2);
Point p2 = p1 + 3; //p1.operator+(3);
Point p3 = 3+ p1; //compiler error 멤버버전을 호출한다면3.operator+(p);이므로 문제가 있다.
//이런 교환법칙이 적용되도록 하려면 전역함수로 만들면 가능하다. operator+(3,p);가 될 것이다.
//전역함수로만 해결가능하다.
p2.ShowPosition();
return 0;
}
4.임시객체의 생성
#include <iostream>
using std::endl;
using std::cout;
class AAA
{
char name[20];
public:
AAA(char* _name)
{
strcpy(name,_name);
cout<<name<<" 객체 생성"<<endl;
}
~AAA()
{
cout<<name<<" 객체 소멸"<<endl;
}
};
int main()
{
AAA aaa("aaa obj");
cout<<"임시 객체 생성 전 "<<endl;
AAA("Temp obj");//객체는 생성되지만 이름이 없다.다음 줄로 넘어가면서 바로 소멸된다.
int a = 3 + 4;
cout<<"임시 객체 생성 후"<<endl;
return 0;
}
-생성방법 : Point (3,4);
-효과 : 생성과 소멸이 컴파일러에 의해서 최적화된다.
-임시 객체 생성의 적용
Point operator+(int val , Point& p)
{
Point temp(p.x + val , p.y + val);
return temp;
}
===>
Point operator+(int val , Point& p)
{
return Point (p.x + val , p.y + val); //임시 객체 생성후 바로 리턴
}
5.cout,cin,endl의 원리 이해
#include <stdio.h>
namespace mystd
{
char *endl ="\n";
class ostream
{
ostream& operator<<(char* str)
{
printf("%s",str);
return *this;
}
ostream& operator<<(int i)
{
printf("%d",i);
return *this;
}
ostream& operator(double i)
{
printf("%e",i);
return *this;
}
};
ostream cout; //ostream 객체 생성
}
using mystd::cout;
using mystd::endl;
int main()
{
cout<<"Hello World"<<endl<<3.14<<endl;
return 0;
}
<< , >>연산자의 오버로딩
#include <iostream>
using std::cout;
using std::endl;
using std::ostream;
class Point
{
private :
int x,y;
public:
Point( int _x = 0, int _y = 0):x(_x),y(_y) {}
friend ostream& operator<<(ostream& os, const Point& p); //Point클래스의 privat멤버에 접근하기 위해서 friend로 선언한다.
};
ostream& operator<<(ostream& os, const Point p)
{
os<<"["<<p.x<<", "<<p.y<<"]"<<endl;
return os;
}
int main()
{
Point p(1,3);
cout<<p<<"hello"<<endl; //cout.operator<<("hello")는 불가능하다. 표준라이브러리를 건드려야 하므로.
return 0;
}
6.인덱스 연산자 []와 오버로딩
arr[i] ==> arr.operator[](i)
Arr arr;//객체 생성 내부에 배열이 선언되어 있어야 한다.
int& operator[](int i) //참조를 반환해야 arr[1] = 10;같이 값을 저장할 수 있다.
{
return arr[i];
}
7.대입연산자 오버로딩
(1)디폴트 대입 연산자
-멤버 대 멤버 복사
-첫번째 case
A a = b; //A a(b);로 묵시적으로 변환된다. (디폴트 복사생성자)
-두번째 case
A a;
B b;
a = b; //a(b);(x) -객체생성 문장이 아니다. a.operator=(b);로 해석된다 .(디폴트대입연산자)
두가지 case를 혼동해서는 안된다.
class Point
{
private :
int x,y;
public:
Point( int _x = 0, int _y = 0):x(_x),y(_y) {}
friend ostream& operator<<(ostream& os, const Point& p); //Point클래스의 privat멤버에 접근하기 위해서 friend로 선언한다.
//디폴트 대입연산자를 구현해보면 아래와 같다.
Point& operator=(const Point &p)
{
//멤버 대 멤버 복사를 한다.
x= p.x ;
y= p.y;
return *this;
}
};
ostream& operator<<(ostream& os, const Point p)
{
os<<"["<<p.x<<", "<<p.y<<"]"<<endl;
return os;
}
int main()
{
Point p1(1,3);
Point p2(10,30);
cout<<p1<<endl;
cout<<p2<<endl;
p1 = p2; //p1.operator=(p2);와 동일하다. 디폴트 대입연산자가 호출된 것이다.
cout<<p1<<endl;
return 0;
}
(2)대입연산자를 정의할 필요한 경우
class Person
{
private:
char* name;
public:
Person(char* _name)
{
name = new char[str(_name) + 1];
strcpy(name ,_name);
}
~Person()
{
if(name) delete name;
}
Person(const Person &p)
{
name = new char[str(p.name) + 1];
strcpy(name ,p.name);
}
//아래와 같이 대입연산자를 정의해주면 문제가 해결된다.
Person& operator=(const Person& p)
{
delete []name; //이전 메모리 할당 영역 삭제(1단계)
//다시 메모리를 할당한다(2단계)
name = new char[strlen(p.name) + 1];
//복사를 한다.(3단계)
strcpy(name,p.name);
return *this;
}
};
int main()
{
Person p1("kkk"); //내부적으로 동적메모리 할당이 일어난다.
Person p2("hhh"); //내부적으로 동적메모리 할당이 일어난다.
cout<<p1<<endl;
p1 = p2; //얕은 복사(shallow copy) ,디폴트 대입연산자가 호출된다.
//p1.operator=(p2); p2의 할당메모리는 포인터를 잃어버려서 메모리 누출이 발생한다.
cout<<p1<<endl;
}