열혈 강의 10장
static복습하고 오기. 전역함수로 선언(friend 선언 이해하기)
왜 연산자 오버로딩인가
함수 오버로딩이란 이르이 같아도 전달 인자에 따라 호출되는 함수가 달라지는 것
int a = 1+2; --> 정수형 변수
Point p3 = p1+p2; --> 객체
--> 같은 +인데도 피연산자의 종류에 따라서 다른 +기능을 한다.
1. 객체를 리턴하는 함수
1. AAA& GetName(){ return a; }
2.AAA GetName() { return a; }
--> 차이가 뭐지
class Point {
private:
int x, y;
public:
Point(int _x=0, int _y=0) : x (_x), y(_y) P{}
void show();
Point& operator++();
friend Point& operato--r(Point & _p);
}
void Point::show() {
cout <<x<<" "<<y<<endl;
}
Point& Point::operator++() { --> 왜 레퍼런스로 리턴을 받는가?
x++;
y++;
return *this; --> this는 포인터 *가 붙었기 때문에 자기자신을 리턴
}
Point& operator--(Point& _p){ --> 왜 참조로 리턴을 하는가?
_p.x--;
_p.y--;
return _p;
}
int main(){
Point p(1,2);
++p;
p.show(); ///2,3
--p
p.show(); ///1,2
++(++p); ///3,4
p.show();
--(--p);
p.show(); ///1,2
}
----------------------------------------------------------------------------------문제 제기
Point Point::operator++(){
x++;
y++;
return *this;
}
--> 참조로 리턴을 받지 않는다면?? --> 나 자신을 복사해서 리턴을 한다. --> 리턴과 동시에 새로운 객체를 만들게 된다. (복사본)
--> ++(++p) --> 밑줄 친 부분이 p자기 자신이 아니라 복사본이 오게 된다.
--> 그 결과 p.show()에는 2,3 즉, p는 ++연산을 한번만 하게 되고, 괄호 밖의 ++연산은 p의 복사본에 하게된다.
--> 복사생성자 호출조건 case3 참고
--> 참조로 리턴을 받는다면?? --> ++(++p) --> 밑줄 친 부분에 복사본이 아닌 p의 레퍼런스가 위치하게 된다.
--> 그 결과 p에 ++연산이 두번 실행된다.
--> 함수를 void로 선언하지않고 Point&로 선언하는 이유이다.
check!!
--> p3 = p1 + p1;
p3 = 리턴된 객체 ---> 복사생성자에 의해 멤버 대 멤버 복사가 일어난다.
2. 단항 연산자 오버로딩 전위 후위
++p --> p.operator++();
p++ --> p.operator++(int); --> int는 단순히 구분하기 위한 용도
Point operator++(int){
Point temp(x,y); --> 증가시키기 전의 값을 저장
x++;
y++;
return temp;
} --> 참조를 리턴하지 않는 이유 --> 지역객체이기 때문에 레퍼런스로 리턴할 수 없다.
int main(){
Point p(1,2);
(p++).show(); --> temp를 리턴받았기 때문에 1 2 출력
p.show(); --> 증가된 x, y
}
--> 참조가 불가능 하기 때문에 (p++)++ 는 불가능
3. 교환 법칙
Point p(1,2);
p+3; ---> 가능
3+p; ---> 불가능
가능하게 하려면?
class Point{
private:
int x,y;
public:
Point operator+(int val);
friend Point operator+(int val, Point& p); ---> friend는 연산자 오버로딩 외에는 사용하지 말자.
}
Point::Point operator+(int val){
Point temp(x+val, y+val);
return temp;
}
Point operator(int val, Point& p){ Point operator(int val, Point& p){
Point temp(p.x+val, p.y+val); ----> return p+val; --> 바꾸어주는 기능으로도 구현할 수 있다.
return temp; }
}
전역함수로 선언해줌으로써 해결된다.
4. 임시 객체
Point p(1,2);
Point (1,2); ---> 임시 객체 생성방법 --> 임시 객체는 생성된 후 다음 줄에서 바로 소멸된다.
임시 객체를 만드는 이유는 뭐냐?
-->임시 객체는 생성과 소멸을 최적화 시킨다. 임시 객체를 쓸 수 있는 부분에서는 써주는 것이 좋다.
교환법칙의 예에서 임시 객체를 사용할 수 있는 부분을 찾아보자.
Point Point::operator+(int val){
return Point(x+val, y+val); ---> 소멸이 되기 전에 리턴!!
}
5. 대입 연산자 오버로딩
class Point{ ... };
int main(){
Point b(1,2);
Point a = b ---> AAA a(b); ---> 복사생성자에 의한 대입연산은 생성될 땐 가능하지만
Point a(1,2);
Point b(10,20);
a=b; ----> a(b) ---> 객체 생성 문장이 아님 --> 복사생성자에 의한 대입 연산 불가능
but 디폴트 대입 연산자가 제공되기 때문에 가능하다.
}
------------------------------------------------------------------디폴트 대입연산자 ex
Point& operator=(const Point& p){
x=p.x;
y=p.y;
return *this;
}
대입 연산자의 문제점
class Person{
private:
char* name;
public:
Person(char* _name){
name = new char[strlen(_name)+1];
strcpy(name,_name);
}
Person (const Person& p){
name = new char[strlen(p.name)+1];
strcpy(name,p.name);
}
~Person(){ delete[] name }
};
int main(){ 스택 힙
Person a("ddd"); a ----> ddd
Person b("fff"); b ----> fff
a=b; --------------->> 문제 발생
}
b가 가진값을 a에 복사해준다. a가 b가 가리키는 fff를 가리키게 된다. (복사생성자의 문제와 유사)
디폴트 대입연산자는 얕은 복사가 가지지 않는 또 다른 문제 하나를 더 가지고 있다.
--> 메모리 유출이 발생한다.
--> a가 원래 가리켰던 ddd가 힙에 계속 남아있게 된다.
--> 올바르게 동작하도록 바꿔보자!
1. a가 가리키던 영역을 해제시켜준다.
2. 메모리 할당을 다시해서 b와 같은 문자열을 할당해준다.
Person& operator=(const Person& p){
delete []name; --> 메모리 해제
name = new char[strlen(p.name)+1]; --> 깊은 복사에 의한 할당
strcpy(name,p.name);
return *this;
}
Point!!
--> Point&을 통해 자기자신을 리턴하는 이유 --> p3 = p1 + p2; 와 같은 연산을 수행하기 위해
--> 생성자에서 동적할당을 하게 되면 1. delete
2. 깊은 복사를 하는 복사생성자.
3. 해제, 깊은 복사를 하는 대입 연산자.