Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- hacking
- Python
- 인공지능
- Web
- ChatGPT
- flask
- 파이썬
- 웹해킹
- mysql
- Shellcode
- 러닝 스칼라
- Javascript
- c
- php
- 백엔드
- hackthissite
- 리눅스
- hackerschool
- c++
- BOF
- Linux
- deep learning
- 딥러닝
- 경제
- BOF 원정대
- 러닝스칼라
- 챗GPT
- backend
- webhacking
- Scala
Archives
- Today
- Total
jam 블로그
[C++] 010. 연산자 오버로딩 본문
728x90
I. 연산자를 오버로딩한다는 것은 어떤 의미인가?
- operator+라는 이름의 함수
- #include <iostream>
- using namespace std;
- class Point
{
private:
int x,y;
public:
Point(int _x=0,int _y=0):x(_x),y(_y){}
void ShowPosition();
void operator+(int val);
}; - void Point::ShowPosition()
{
cout<<x<<" "<<y<<endl;
} - void Point::operator+(int val)
{
x += val;
y += val;
} - int main()
{
Point p(3,4);
p.ShowPosition(); - p.operator +(10);
p.ShowPosition();
return 0;
}
- operator + 에 10을 전달하면 각각의 수에 10을 더해진다 여기서 조금 변형을 해본다.
- int main()
{
Point p(3,4);
p.ShowPosition(); - p+10;
p.ShowPosition();
return 0;
}
위의 소스를 보면 p+10이라고 했는데도 되었다.
- 이유는 위에서 operator + 라고 지정을 해주었기 때문에 +를 쓰면 알아서 operator+로 해석을 한다.
- 이것이 연산자 오버로딩이다.
II. 연산자를 오버로딩하는 두 가지 방법
- 멤버 함수에 의한 연산자 오버로딩
- #include <iostream>
using namespace std;
class Point
{
private:
int x,y;
public:
Point(int _x =0,int _y = 0):x(_x),y(_y){}
void ShowPosition();
Point operator+(const Point& p);
};
Point Point::operator +(const Point& p) cost
{
Point temp(x+p.x,y+p.y);
return temp;
}
void Point::ShowPosition()
{
cout<<x<<" "<<y<<endl;
} - int main()
{
Point p1(1,2);
Point p2(2,1);
Point p3 = p1+p2;
p3.ShowPosition();
return 0;
}
- Point Point::operator+(const Point& p) 이 구문을 보면 operator+ 함수를 Point 객체로 받고 있다. 단 성능 향상을 위해서 레퍼런스로 받고 있으며 전달 인자의 변경을 허용하지 않기 위해서 const를 썼다. 함수 내에서 멤버 변수의 조작을 할 필요가 없으므로 함수도 const를 했다. 안정성을 위한 것이다.
- 전역 함수에 의한 오버로딩
- #include <iostream>
using namespace std;
class Point
{
private:
int x,y;
public:
Point(int _x =0,int _y = 0):x(_x),y(_y){}
void ShowPosition();
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;
}
void Point::ShowPosition()
{
cout<<x<<" "<<y<<endl;
} - int main()
{
Point p1(1,2);
Point p2(2,1);
Point p3 = p1+p2;
p3.ShowPosition();
return 0;
}
이번에는 +연산자를 오버로딩하면서 전역함수로 나타낸 것이다. friend는 private에 있는 변수에 접근하기 위에서 쓴 것이다.
- 객체지향에는 전역이라는 개념이 존재하지 않기 때문에 가급적 멤버함수를 이용하는 것이 좋다. (간결해 진다고 한다.그러나 전역함수를 써야만 하는 경우도 있다.)
오버로딩이 가능한 연산자의 종류
오버로딩이 불가능한 연산
- . .* :: ?: sizeof
연산자 오버로딩에 있어서의 주의사항
- 첫번째 : 본 의도를 벗어난 연산자 오버로딩은 좋지 않다.
두번째 : 연산자 우선 순위와 결합성을 바꿀 수는 없다.
- 세번째 : 디폴트 매개 변수 설정이 불가능하다.
- 네번째 : 디폴트 연산자들의 기본 기능까지 빼앗을 수는 없다.
III. 단항 연산자의 오버로딩
- 증가, 감소 연산자 오버로딩
- #include <iostream>
using namespace std; - class Point
{
private:
int x,y;
public:
Point(int _x=0,int _y=0):x(_x),y(_y){}
void ShowPosition();
Point& operator++();
friend Point& operator--(Point& p);
}; - void Point::ShowPosition()
{
cout<<x<<" "<<y<<endl;
}
Point& Point::operator ++()
{
x++;
y++;
return *this;
} - Point& operator--(Point& p)
{
p.x--;
p.y--;
return p;
} - int main()
{
Point p(1,2);
++p;
p.ShowPosition();
--p;
p.ShowPosition();
++(++p);
p.ShowPosition();
--(--p);
p.ShowPosition(); - return 0;
}
*this가 의미하는 바는?
- 자기 자신을 리턴하기 위해서
x와 y를 증가 시킨후에 자기자신을 리턴을 한 이유는?
- ++p 연산 후에 그 다음 연산을 가능케 하기 위해서
리턴 타입이 Point& 이유는?
만약 리턴 타입이 Point 였다면 1만 증가 하게 된다. 이유는 ++p 연산에 의해서 리턴 되는 것은 p의 참조가 아닌 p의 복사본이기 때문이다.
- 선 연산과 후 연산의 구분
- #include <iostream>
using namespace std; - class Point
{
private:
int x,y;
public:
Point(int _x=0,int _y=0):x(_x),y(_y){}
void ShowPosition();
Point& operator++();
friend Point& operator--(Point& p);
}; - void Point::ShowPosition()
{
cout<<x<<" "<<y<<endl;
}
Point& Point::operator ++()
{
x++;
y++;
return *this;
} - Point& operator--(Point& p)
{
p.x--;
p.y--;
return p;
} - int main()
{
Point p1(1,2);
(p1++).ShowPosition();
Point p2(1,2);
(++p2).ShowPosition();
return 0; - }
위의 소스의 결과값을 찍어보면 원래 나와야할 1,2가 아닌 2,3이 나와 버린다. 왜 그럴까?
- 원래는 연산자의 위치에 따라 의미가 달라지는데 ++p와 p++가 동일하게 해석이 되어 버린다.
- ++ 연산의 경우, 전위 증가와 후위 증가의 형태를 구분 짓기 어려워서, 후위 증가를 위한 함수를 오버로딩할 경우, 키워드 int를 매개변수로 선언을 하면 된다.
- Point Point::operator ++(int)
{
Point temp(x,y); // Point temp(*this)
x++; // ++(*this)
y++; //
return temp;
}
위와 같이 정의를 하면 된다.
- Point temp(*this 이런 형태로 구현하는 것도 좋다. 이는 복사 생성자를 호출하는 형태로 객체를 복사하는 것이다.
- 객체는 지역적으로 선언되어 있는 지역객체이다. 전역 객체가 아닌기 때문에 레퍼런스 형태로 리턴하면 안된다.
IV. 교환 법칙 해결하기
교환 법칙의 적용
- 앞에서 제시한 소스들에서 p+1은 되어도 1+p는 안되었다.
- #include <iostream>
using namespace std; - class Point
{
private:
int x,y;
public:
Point(int _x =0, int _y=0):x(_x),y(_y){}
void ShowPosition();
Point operator+(int val);
friend Point operator+(int val,Point& p);
}; - void Point::ShowPosition()
{
cout<<x<<" "<<y<<endl;
} - Point Point::operator +(int val)
{
Point temp(x+val,y+val);
return temp;
} - Point operator+(int val,Point& p)
{
return p+val;
} - int main()
{
Point p1(1,2);
Point p2 = p1+3;
p2.ShowPosition(); - Point p3 = 3+p2;
p3.ShowPosition(); - return 0;
}
- 이런식으로 소스를 추가하면 p+1 이나 1+p와 같은 교환법칙이 성립하게 된다.
- 임시 객체의 생성
- #include <iostream>
using namespace std; - class AAA
{
char name[20];
public:
AAA(char* _name)
{
strcpy(name,_name);
cout<<name<<" 객체 생성 "<<endl; - }
~AAA()
{
cout<<name<<"객체 소멸"<<endl;
}
}; - int main()
{
AAA aaa("aaaOBJ");
cout<<" ---------------- 임시 --------------------"<<endl;
AAA("temp");
cout<<" ---------------- 임시 --------------------"<<endl;
return 0;
}
실행을 해보면 임시 객체가 생성되었다가, 그 다음 줄로 넘어가면서 바로 소멸된다는 것을 보여주고 있다.
- 이러한 임시 객체를 어디에 쓸까?
- Point Point::operator+(int val) //Point Point::operator+(int val)
- { //{
- Point temp(x+val,y+val); // return Point(x+val,y+val);
- return temp; // }
- }
- 원래 소스에서 주석쪽 소스로 바꾸면 (기능은 같다.) 주석쪽 소스가 임시 객체를 생성하자마자 바로리턴해 주고 있다.
V. cout, cin 그리고 endl의 비밀
- cout, cin 그리고 endl의 구현 이해
- #include <stdio.h>
- namespace mystd
{
char* endl= "\n";
class ostream
{
public:
void operator<<(char*str)
{
printf("%s",str);
}
void operator<<(int i)
{
printf("%d",i);
}
void operator<<(double i)
{
printf("%e",i);
}
};
ostream cout;
} - using namespace mystd;
- int main()
{
cout<<"Hello World\n";
cout<<3.14;
cout<<endl;
cout<<1;
cout<<endl;
return 0;
}
위에서 만든 네임스페이스를 사용해서 출력이 잘된다.
- 하지만 cout<<"hello"<<100<<3.12<<endl; 이렇게 쳐서 컴파일 하면 오류가 난다.
- (((cout<<"hello")<<100) <<3.12)<<endl;이런 식으로 하면 잘 된다. 귀찮기 때문에 소스를 좀 변경해 보자.
- 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;
}
- 위에 처럼 연산자를 오버로딩하고 있는 operator<< 함수는 cout 객체를 반환하면 된다.
VI. 배열의 인덱스 연산자 오버로딩의 예
- 기본 자료형 데이터를 저장할 수 있는 배열 클래스
- #include <iostream>
- using namespace std;
- const int SIZE = 3;
- class Arr
{
private:
int arr[SIZE];
int idx;
public:
Arr():idx(0){}
int GetElem(int i);
void SetElen(int i,int elem);
void AddElem(int elem);
void ShowAllData(); - };
- int Arr::GetElem(int i)
{
return arr[i];
} - void Arr::SetElen(int i, int elem)
{
if (idx <=i)
{
cout<<"존재하지 않는 요소!"<<endl;
return;
}
arr[i] = elem;
}
void Arr::AddElem(int elem)
{
if (idx>=SIZE)
{
cout<<" 용량 초과"<<endl;
return ;
}
arr[idx++] = elem;
}
void Arr::ShowAllData()
{
for (int i=0;i<idx;i++)
{
cout<<"arrr ["<<i<<"] = "<<arr[i]<<endl;
}
} - int main()
{
Arr arr;
arr.AddElem(1);
arr.AddElem(2);
arr.AddElem(3);
arr.ShowAllData(); - arr.SetElen(0,10);
arr.SetElen(1,20);
arr.SetElen(2,30); - cout<<arr.GetElem(0)<<endl;
cout<<arr.GetElem(1)<<endl;
cout<<arr.GetElem(2)<<endl;
return 0;
}
- 위에서 정의한 클래스가 보다 배열다워지려면 인덱스 연산자 [] 를 통한 참조가 가능해야 한다.
- int main()
{
Arr arr;
arr.AddElem(1);
arr.AddElem(2);
arr.AddElem(3);
arr.ShowAllData();
arr[0]=10;
arr[1]=20;
arr[2]=30;
for (int i;i<SIZE;i++)
{
cout<<arr[i]<<endl;
}
return 0;
}
- 위의 소스를 찍어 내려면
- int& Arr::operator[](int i)
{
return arr[i];
}
- 위의 함수를 추가해 주면 된다.
- 객체를 저장할 수 있는 배열 클래스
- #include <iostream>
using namespace std; - /******************* POINT Class*************************/
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);
}; - ostream& operator<<(ostream& os, const Point& p)
{
os<<"["<<p.x<<" ," <<p.y<<"]";
return os;
} - /****************** PointArr Class*********************/
- const int Size = 3;
class PointArr
{
private:
Point arr[Size];
int idx;
public:
PointArr():idx(0){}
void AddElem(const Point& elem);
void ShowAllData();
Point& operator[](int i);
}; - void PointArr::AddElem(const Point &elem)
{
if (idx >= Size)
{
cout<<" 용량 초과"<<endl;
return;
}
arr[idx++] = elem;
}
void PointArr::ShowAllData()
{
for (int i=0;i<idx;i++)
{
cout<<"arr["<<i<<"]="<<arr[i]<<endl;
}
}
Point& PointArr::operator [](int i)
{
return arr[i];
} - int main()
{
PointArr arr;
arr.AddElem(Point(1,1));
arr.AddElem(Point(2,2));
arr.AddElem(Point(3,3));
arr.ShowAllData(); - arr[0] = Point(10,10);
arr[1] = Point(20,20);
arr[2] = Point(30,30); - cout<<arr[0]<<endl;
cout<<arr[1]<<endl;
cout<<arr[2]<<endl;
}
- 메인에서 arr.AddElem(Point(1,1));줄에서 생성하는 Point 객체는 임시 객체의 형태는 띠고 있다.
VII. 반드시 해야만 하는 대입 연산자의 오버로딩
- 오버로딩된 대입 연산자의 존재 : 디폴트 대입 연산자
- #include <iostream>
using namespace std; - 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); - };
- 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;
cout<<p1<<endl;
return 0;
}
메인에서 보면 p1 = p2 이렇게 되어있는데 =연산자를 오버로딩하고 있는 함수는 보이지 않는다.
- 디폴트 대입 연산자가 제공되기 때문이다. 멤버 변수 대 멤버 변수의 복사가 이뤄지고 있음알 수 있기 때문에 밑의 소스와 유사할 것이다.
- 리턴 타입이 Point&형이고 자기자신을 리턴하는 이유는 p1=p2=p3 같은 연산을 허용하기 위해서이다
- 디폴트 대입 연산자의 문제점
- #include <iostream>
using namespace std; - class Person
{
private:
char* name;
public:
Person(char* _name);
Person(const Person& p);
~Person();
friend ostream& operator<<(ostream& os, const Person& p); - };
Person::Person(char* _name)
{
name = new char[strlen(_name)+1];
strcpy(name,_name);
}
Person::Person(const Person &p)
{
name = new char[strlen(p.name)+1];
strcpy(name,p.name);
}
Person::~Person()
{
delete[] name;
} - ostream& operator<<(ostream& os, const Person& p)
{
os<<p.name;
return os;
}
int main()
{
Person p1("lee");
Person p2("hone"); - cout<<p1<<endl;
cout<<p2<<endl;
p1=p2;
cout<<p1<<endl;
return 0;
}
위의 소스를 컴파일후 실행 하면 실행중에 오류가 난다. 왜냐 하면 디폴트 대입 연산자 때문이다.
- 메모리 공간을 동적 할당 했기 때문에 깊은 복사를 하도록 해야하는데 디폴트 대입 생성자는 얕은 복사를 하기 때문이다.
- 또 하나의 문제는 메모리 유출에 관련이 있다.
- Person& Person::operator=(const Person& p)
{
delete []name;
name = new char[strlen(p.name)+1];
strcpy(name,p.name);
return *this;
}
위의 소스를 추가를 해주면 된다.
- 생성자 내에서 메모리 공간을 동적 할당하게 되면, 할당된 메모리를 해제하는용도의 소멸자를 정의해야하며, 복사 생성자와 대입 연산자도 깊은 복사를 하도록 정의해야한다.
'개발 및 관련 자료 > C' 카테고리의 다른 글
[C++] 012. 템플릿(Template) (0) | 2013.05.12 |
---|---|
[C++] 011. string 클래스 디자인 (0) | 2013.05.12 |
[C++] 009. virtual의 원리와 다중 상속 (0) | 2013.05.12 |
[C++] 008. 상속과 다형성 (0) | 2013.05.12 |
[C++] 007. 상속(Inheritance)의 이해 (0) | 2013.05.12 |
Comments