C++ Chapter 9.1 : 연산자 오버로딩 시작하기
카테고리: Cpp
태그: Cpp Programming
인프런에 있는 홍정모 교수님의 홍정모의 따라 하며 배우는 C++ 강의를 듣고 정리한 필기입니다. 😀
🌜 [홍정모의 따라 하며 배우는 C++]강의 들으러 가기!
chapter 9. 연산자 오버로딩 : 연산자 오버로딩 시작하기
🔔연산자 오버로딩 소개
연산자 오버로딩이란?
int main()
{
out << 1 + 3 << endl;
cout << 1 * 3 << endl;
int a = 3;
cout << ++a << endl;
}
우리는 int, float, bool 같은 기본 자료형을 가진 데이터들을 쉽게 피연산자로서 C++ 에서 제공하는 연산자로 연산시킬 수 있다.
class A
{
public:
int data_a = 3;
int data_b = 10;
};
int main()
{
A object1;
A object2;
cout << object1 + object2 << endl; // 👈 💥에러 !
}
A 라는 클래스가 있다고 할 때 A 타입의 두 객체끼리 +
덧셈 연산자를 사용하여 덧셈을 하려고 하면 에러가 난다. 객체는 일반 int나 float 같은 기본 자료형처럼 단 하나의 값으로 이루어져 있는게 아닌 이것 저것 여러 멤버들을 담고 있는 캡슐 같은 것이기 때문이다. 객체 참조 변수 object1, object2끼리 어떻게 덧셈 해야할지 알 길이 없다.
class A
{
public:
int data_a = 10;
};
int add(const A & a, const A & b)
{
return a.data_a + b.data_a;
}
int main()
{
A object1;
A object2;
cout << add(object1, object2) << endl; // 👈 20 출력
}
물론 위와 같이 A 타입의 두 객체를 매개변수로 받아 덧셈을 하고 결과를 리턴해해주는 함수 int add(const A & a, const A & b) 를 정의할 수도 있다.
class A
{
public:
int data_a = 10;
};
int operator + (const A & a, const A & b)
{
return a.data_a + b.data_a;
}
int main()
{
A object1;
A object2;
cout << object1 + object2 << endl; // 👈 20 출력
}
int operator + (const A & a, const A & b)
{
return a.data_a + b.data_a;
}
cout << object1 + object2 << endl; // 이제 가능 !
(리턴타입) operator (연산자) (연산자가 받는 인자)
이때 위와 같이 두 피연산자를 A 타입의 객체로 받는 덧셈 연산자 +
를 정의할 수도 있다. 이처럼 C++에선 +
, -
, <<
, []
등등 이런 연산자에 특정 타입의 객체가 피연산자로 들어올 시 어떤 행동을 하게끔 미리 정의할 수 있다. 이를 연산자 오버로딩 이라고 한다.
string 문자열 객체 또한 +
가 오버로딩 되어 있기 때문에 피연산자로 string 문자열이 들어간 문자열1 + 문자열2
을 연산해주어 문자열1에 문자열2를 붙일 수 있는 것이다. 이는 string 클래스에 구현되어 있다.
오버로딩 할 수 있는 연산자
사용자가 새로운 연산자를 만들 수는 없고 C++ 에서 제공하는 기본 연산자들만 오버로딩할 수 있다.
- 산술 연산자
+
,-
,*
,/
,%
+=
같은 복합 연산자도 가능하다.A + A
피연산자 2개를 필요로 한다.
- 입출력 연산자
<<
,>>
std::cout << A
std::cint >> A
ostream << A
. 이렇게 피연산자 2개를 필요로 한다.istream >> A
. 이렇게 피연산자 2개를 필요로 한다.
- 단항 연산자
+
,-
,!
!A
피연산자 1개를 필요로 한다.
- 비교 연산자
>=
,==
,>
,!=
A == A
피연산자 2개를 필요로 한다.
- 증감 연산자
++
,--
++A
피연산자 1개를 필요로 한다.
- 첨자 연산자
[]
A[5]
피연산자 2개를 필요로 한다. (A와 5)
- 괄호 연산자
()
A(5)
피연산자 2개를 필요로 한다. (A와 5)
- 논리 연산자
&&
,||
A && A
피연산자 2개를 필요로 한다.
- 멤버 선택 연산자 중
->
,*
- 형변환 연산자
(자료형)
,자료형()
- 대입 연산자
=
- 비트 연산자
- 쉬프트 :
<<
>>
&
,|
,^
- 쉬프트 :
- 포인터 관련 연산자
*
, 주소&
,->
->*
- 메모리 연산자
new
,new[]
,delete
,delete[]
오버로딩 할 수 없는 연산자
아래 연산자들은 오버로딩이 불가능하다.
?:
조건연산자::
범위 지정 연산자sizeof
크기 연산자.
멤버 선택 연산자.*
포인터로 멤버 선택할 때 연산자
🔔 일반 전역 함수로 오버로딩
class A
{
public:
int data = 10;
};
int operator + (const A & a, const A & b) // ⭐전역함수
{
return a.data + b.data;
}
int main()
{
A object1;
A object2;
cout << object1 + object2 << endl; // 👈 20 출력
}
+
연산자를 오버로딩 한 int operator + (const A & a, const A & b) 을 전역 함수로 만들었다. 전역 함수이기 때문에, 예를 들어 피연산자가 2개가 필요할 때 2개의 매개변수가 필요하다.
class Cents
{
public:
int m_cents;
Cents(int cents = 0) { m_cents = cents; }
};
void operator + (const Cents & c1, const Cents & c2)
{
cout << c1.m_cents + c2.m_cents << endl;
}
int main()
{
Cents cents1(5);
Cents cents2(7);
cents1 + cents2; // 12 출력
}
- void operator + (const Cents & c1, const Cents & c2)
- 전역 함수다.
- Cents 타입의 객체를 2 개 받아서
- 두 객체의 멤버 변수 m_cents 끼리 더하고
- 이를 출력한다.
- main 에서
cents1 + cents2;
해주기만 해도 위 내용이 실행된다.
전역 함수 사용시 접근 하려는 멤버가 private
일 때 : friend
사용하기
class Cents
{
private:
int m_cents;
public:
Cents(int cents = 0) { m_cents = cents; }
int getCents() const { return m_cents; }
int& getCents() { return m_cents; }
};
void operator + (const Cents & c1, const Cents & c2)
{
// cout << c1.m_cents + c2.m_cents << endl; 전역 함수(클래스 외부)이므로 private 멤버를 사용할 수 없다.
cout << c1.getCents() + c2.getCents() << endl;
}
int main()
{
Cents cents1(5);
Cents cents2(7);
cout << cents1.getCents() + cents2.getCents() << endl;
cents1 + cents2; // 오버로딩한 + 호출
}
- m_cents 가
private
멤버이기 때문에- 클래스 외부에 있는 전역 함수인
+
연산자 오버로딩 내부 에서 직접 접근하여 사용할 수가 없다. - 따라서 멤버 함수 int getCents() const 를 사용하여
private
멤버인 m_cents 를 리턴 받았다.
- 클래스 외부에 있는 전역 함수인
+
연산자 오버로딩 내에서의 getCents() 은 int getCents() const 이다.- 인수가 Const 객체로 들어왔기 때문
- main 함수에서의 getCents() 은 int& getCents() 이다.
class Cents
{
private:
int m_cents;
public:
Cents(int cents = 0) { m_cents = cents; }
int getCents() const { return m_cents; }
int& getCents() { return m_cents; }
friend void operator + (const Cents & c1, const Cents & c2); // Cents의 친구로 지정.
};
void operator + (const Cents & c1, const Cents & c2)
{
cout << c1.m_cents + c2.m_cents << endl; // 이제 직접적으로 접근 가능!
// cout << c1.getCents() + c2.getCents() << endl;
}
int main()
{
Cents cents1(5);
Cents cents2(7);
cout << cents1.getCents() + cents2.getCents() << endl;
cents1 + cents2; // 오버로딩한 + 호출
}
- 그러나 위의 코드처럼 전역함수인
+
연산자 오버로딩를 Cents 클래스의friend
로 지정하면- 이제 전역함수
+
연산자 오버로딩 내에서 getCents() 를 호출할 필요 없이 - 바로
private
멤버인 m_cents 에 접근이 가능하다.
- 이제 전역함수
friend void operator + (const Cents & c1, const Cents & c2);
class Cents
{
private:
int m_cents;
public:
Cents(int cents = 0) { m_cents = cents; }
int getCents() const { return m_cents; }
int& getCents() { return m_cents; }
friend void operator + (const Cents & c1, const Cents & c2)
{
cout << c1.m_cents + c2.m_cents << endl;
}
};
- 위 연산자 오버로딩이 Cents 클래스 안에 구현되어 있어 멤버 함수라고 착각할 수도 있으나 전역 함수다!
- 전역 함수인데 Cents 클래스의
friend
로 지정될 때 같이 함수의 바디도 구현해준 것 뿐이다.
- 전역 함수인데 Cents 클래스의
- 이처럼
friend
+ 함수 프로토타입; 으로 하고 바디는 전역 범위에서 정의할 수도 있고 - 클래스 내에서
friend
+ 함수 바디까지 다 정의; 로 할 수도 있다. 👈 멤버 함수 아니고 전역 함수라는 것에 주의하기!
🔔 멤버 함수로 오버로딩
왼쪽 피연산자가 멤버 함수를 호출하는 객체가 되고 오른쪽 피연산자는 인수가 된다.
class A
{
private:
int data = 10;
public:
int operator + (const A & a) // ⭐멤버 함수
{
return data + a.data;
}
};
int main()
{
A object1;
A object2;
cout << object1 + object2 << endl; // 👈 20 출력
}
+
연산자를 오버로딩 한 int operator + (const A & a, const A & b) 을 A클래스의 멤버 함수로 만들었다.- 멤버 함수이기 때문에, 예를 들어 피연산자가 2개가 필요해도 매개변수 1개만 있으면 된다.
- 피연산자 나머지 하나는 자기 자신(this)로 사용하면 되기 때문!
- return data + a.data;
- data 는 this->data 나 마찬가지다. A타입 객체인 자기 자신의 data 값.
- a.data는 매개변수로 받은 A타입 객체인 a의 data 값.
- 멤버 함수이므로
private
멤버를 내부에서 자유롭게 접근할 수 있다.
- 멤버 함수이기 때문에, 예를 들어 피연산자가 2개가 필요해도 매개변수 1개만 있으면 된다.
class Cents
{
private:
int m_cents;
public:
Cents(int cents = 0) { m_cents = cents; }
void operator + (const Cents & c2)
{
cout << m_cents + c2.m_cents << endl;
}
};
int main()
{
Cents cents1(5);
Cents cents2(7);
cents1 + cents2; // 12 출력
}
- void operator + (const Cents & c2)
- Cents클래스의 멤버 함수다.
- 자기 자신(this)과 매개 변수로 받은 객체(c2)를 더하고
- 이를 출력한다.
- main 에서
cents1 + cents2;
해주기만 해도 위 내용이 실행된다.- 이때
cents1
입장에서cents2
를 매개변수로 받아 위 멤버 함수가 실행 되는 것.
- 이때
🔔 전역 함수 vs 멤버 함수 작동 비교
cents1 + cents2 + cents3;
위 코드가 +
오버로딩이 전역 함수일 때, 멤버 함수일 때 각각 어떻게 동작할지 살펴 보자.
이때, 오버로딩 된 연산자가 객체로 리턴 되는게 있어야 위와 같이 chaining이 가능하다.
전역 함수
class Cents
{
private:
int m_cents;
public:
Cents(int cents = 0) { m_cents = cents; }
friend Cents operator + (const Cents & c1, const Cents & c2);
};
Cents operator + (const Cents & c1, const Cents & c2)
{
return Cents(c1.m_cents + c2.m_cents);
}
cents1 + cents2
- Cents 타입의 두 매개변수를 받아 오버로딩 된 + 연산을 실행한다.
- 덧셈 결과를 Cents 타입으로 리턴한다.
(cents1 + cents2) + cents3
cents1 + cents2
의 덧셈 결과도 Cents 타입이니- 이 덧셈 결과와 cents3 , 두 매개변수를 받아 오버로딩 된 + 연산을 실행한다.
- 덧셈 결과를 Cents 타입으로 리턴한다.
멤버 함수
class Cents
{
private:
int m_cents;
public:
Cents(int cents = 0) { m_cents = cents; }
Cents operator + (const Cents & c2)
{
return Cents(m_cents + c2.m_cents);
}
};
cents1 + cents2
- 자기 자신 : cents1
- 인수 1개 : cents2
- 덧셈 결과를 Cents 타입으로 리턴한다.
(cents1 + cents2) + cents3
- 자기 자신 : (cents1 + cents2) 덧셈 결과
- 인수 1개 : cents3
- 덧셈 결과를 Cents 타입으로 리턴한다.
🔔 연산자 우선순위
연산자를 오버로딩해도 연산자의 우선순위는 그대로 유지된다. 내가 오버로딩한 +
연산자는 여전히 *
보다 우선순위가 낮다. 따라서 연산자 순위에 있어 좀 애매하다면 오버로딩 하지 말고 따로 add같은 일반 함수를 만들어 쓰는 것이 더 낫다.
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글남기기