8장 오버로딩, friend, 그리고 참조 연산자

2023. 11. 25. 15:07프로그래밍 공부/OOP

8.1 기본 오버로딩 연산자

연산자(operator): 조금 다른 구문을 사용하는 함수 ex)+, -, %, == 등

피연산자(operand): 연산할 때 쓰이는 인수 ex) x, 7

연산자 구문 예시: x + 7 or +(x, 7) or add(x, 7)

연산자에 오버로딩(overloading, 과부하) 정의한 클래스의 피연산자를 처리할 수 있다.

연산자에 오버로딩되는 방법은 함수 이름에 오버로딩되는 방법과 매우 비슷하다. 

 

오버로딩 기본사항

화면 8.1에는 미국 돈의 값을 가진 클래스의 정의가 포함되어 있다.

화면 8.1 vs 화면 7.2에서 정의한 BankAccount 클래스

공통점

달러와 센트 부분에 대한 두 인트와 같은 방식으로 돈의 양을 나타낸다.

private 도움 함수도 동일하다.

생성자와 접근자 및 설정자는 BankAccount 클래스와 유사하다.

 

화면 8.1의 새로운 점

플러스 기호와 마이너스 기호를 오버로드하여 Money 클래스의 두 객체를 더하거나 뺄 수 있도록 했다.

== 부호를 오버로드하여 머니 클래스의 두 객체가 동일한 금액을 나타내는지 비교할 수 있도록 했다.

 

오버로딩 연산자의 특징

연산자 +(및 많은 다른 연산자)를 오버로딩하여 클래스 유형의 인수를 허용할 수 있다.

기호 +를 함수 이름으로 사용한다.

키워드 operator와 함께 + 앞에 온다.

플러스 기호에 대한 연산자 선언(함수 선언) 예시:
const Money operator +( const Money& amount1, const Money& amount2);


피연산자(인수)는 모두 Money 타입의 상수 참조 매개 변수이다.

피연산자는 적어도 하나가 클래스 타입이면 어떤 타입이든 가능하다.

일반적인 경우에 피연산자는 call by value 또는 call by reference 매개 변수일 수 있고 const 수정자를 갖거나 가지지 않을 수 있다.

그러나 효율성의 이유로 일반적으로 클래스에 대한 call by value 대신에 constant call by reference가 사용된다.

이 경우에 반환되는 값은 Money 타입이지만 일반적인 경우에 반환되는 값은 void를 포함하여 어떤 타입이든 될 수 있다.

오버로딩된 이항 연산자 +와 -는 Money 클래스의 멤버 연산자가 아니므로 Money 클래스의 private 멤버에게는 접근할 수 없다.

이것이 오버로딩된 연산자에 대한 정의가 접근자와 설정자를 사용하는 이유이다.

 

연산자 ==는 또한 Money 클래스의 두 개체를 비교하는 데 사용될 수 있도록 오버로딩된다.

반환되는 유형은 bool이므로 ==는 if-else 문과 같은 일반적인 방법으로 비교하는 데 사용될 수 있다.

 

대부분의 연산자를 오버로딩할 수 있지만 모든 연산자를 오버로딩할 수는 없다.

연산자 오버로딩에 대한 한 가지 주요 제한 사항은 적어도 하나의 피연산자가 클래스 유형이어야 한다는 것이다.

예시)

% 연산자를 오버로딩할 때 가능한 피연산자

두 개의 Money 유형의 객체 (O)

Money 유형의 객체와 double형의 객체 (O)

두 개의 double형을 결합 (X)

 

연산자 오버로딩
이항(binary) 연산자: 단순히 인수를 나열할 때 다른 구문을 사용하여 호출되는 함수 ex) +, -, /, % 등

이항 연산자의 경우 인수가 연산자 앞 뒤에 나열되고 함수의 경우 인수가 함수 이름 뒤에 괄호 안에 나열된다.

연산자 정의는 연산자 이름 앞에 예약어 연산자가 포함된다는 점을 제외하고는 함수 정의와 유사하게 작성된다.

+, - 등과 같이 미리 정의된 연산자는 클래스 유형에 대한 새로운 정의를 제공하여 오버로딩이 될 수 있다.

+, -, == 연산자에 오버로딩이 되는 예는 그림 8.1에 나와 있다.

 

디스플레이 8.1 오버로딩 연산자

#include <iostream>
#include <cstdlib>
#include <cmath>
using namespace std;

//Class for amounts of money in U.S. currency
class Money
{
public:
	Money( );
	Money( double amount);
	Money( int theDollars, int theCents);
	Money( int theDollars);
	double getAmount( ) const;
	int getDollars( ) const;
	int getCents( ) const;
	void input( ); //Reads the dollar sign as well as the amount number.
	void output( ) const;
private:
	int dollars; //A negative amount is represented as negative
	             //dollars and
	int cents; //negative cents. Negative $4.50 is represented
	           //as -4 and -50.
               
	int dollarsPart( double amount) const;
	int centsPart( double amount) const;
	int round( double number) const;
};

//For an explanation of a const on a returned type see the
//subsection “Returning by const Value.”
const Money operator +( const Money& amount1, const Money& amount2);

const Money operator -( const Money& amount1, const Money& amount2);

bool operator ==( const Money& amount1, const Money& amount2);

//This is a unary operator and is discussed in the subsection
//“Overloading Unary Operators.”
const Money operator -( const Money& amount);

int main( )
{
	Money yourAmount, myAmount(10, 9);
	cout << "Enter an amount of money: ";
	yourAmount.input( );
	cout << "Your amount is ";
	yourAmount.output( );
	cout << endl;
	cout << "My amount is ";
	myAmount.output( );
	cout << endl;

	if (yourAmount == myAmount)
		cout << "We have the same amounts.\n";
	else
		cout << "One of us is richer.\n";

	Money ourAmount = yourAmount + myAmount;
	yourAmount.output( ); cout << " + "; myAmount.output( );
	cout << " equals "; ourAmount.output(); cout << endl;
    
	Money diffAmount = yourAmount - myAmount;
	yourAmount.output( ); cout << " - "; myAmount.output( );
	cout << " equals "; diffAmount.output(); cout << endl;

	return 0;
}

const Money operator +( const Money& amount1, const Money& amount2)
{
	//Note that we need to use accessor and mutator functions.
	int allCents1 = amount1.getCents( ) + amount1.getDollars( )*100;
	int allCents2 = amount2.getCents( ) + amount2.getDollars( )*100;
	int sumAllCents = allCents1 + allCents2;
	int absAllCents = abs(sumAllCents); //Money can be negative.
	int finalDollars = absAllCents / 100;
	int finalCents = absAllCents % 100;

	if (sumAllCents < 0)
	{
		finalDollars = -finalDollars;
		finalCents = -finalCents;
	}
    
	//If the return statements puzzle you, see the tip entitled
	//“A Constructor Can Return an Object.”
	return Money(finalDollars, finalCents);
}

//Uses cstdlib:
const Money operator -( const Money& amount1, const Money& amount2)
{
	int allCents1 = amount1.getCents( ) + amount1.getDollars( )*100;
	int allCents2 = amount2.getCents( ) + amount2.getDollars( )*100;
	int diffAllCents = allCents1 - allCents2;
	int absAllCents = abs(diffAllCents);
	int finalDollars = absAllCents / 100;
	int finalCents = absAllCents % 100;

	if (diffAllCents < 0)
	{
		finalDollars = -finalDollars;
		finalCents = -finalCents;
	}
    
	return Money(finalDollars, finalCents);
}

bool operator ==( const Money& amount1, const Money& amount2)
{
	return ((amount1.getDollars( ) == amount2.getDollars( ))
	       && (amount1.getCents( ) == amount2.getCents( )));
}

const Money operator -( const Money& amount)
{
	return Money(-amount.getDollars( ), -amount.getCents( ));
}

//If you prefer, you could make these short constructor definitions
//inline function definitions as discussed in Chapter 7 .
Money::Money( ): dollars(0), cents(0)
{ /*Body intentionally empty.*/}

Money::Money( double amount)
               : dollars(dollarsPart(amount)), cents(centsPart(amount))
{ /*Body intentionally empty*/ }

Money::Money( int theDollars)
              : dollars(theDollars), cents(0)
{ /*Body intentionally empty*/}

//Uses cstdlib:
Money::Money( int theDollars, int theCents)
{
	if ((theDollars < 0 && theCents > 0) ||
	    (theDollars > 0 && theCents < 0))
	{
		cout << "Inconsistent money data.\n";
		exit(1);
	}
	dollars = theDollars;
	cents = theCents;
}

double Money::getAmount( ) const
{
	return (dollars + cents*0.01);
}

int Money::getDollars( ) const
{
	return dollars;
}

int Money::getCents( ) const
{
	return cents;
}

//Uses iostream and cstdlib:
void Money::output( ) const
{
	int absDollars = abs(dollars);
	int absCents = abs(cents);
	if (dollars < 0 || cents < 0)
	//accounts for dollars == 0 or cents == 0
		cout << "$-";
	else
		cout << '$';
	cout << absDollars;

	if (absCents >= 10)
		cout << '.' << absCents;
	else
		cout << '.' << '0' << absCents;
}

//For a better definition of the input
//function, see Self-Test Exercise 3 in Chapter 7 .
//Uses iostream and cstdlib:
void Money::input( )
{
	char dollarSign;
	cin >> dollarSign; //hopefully
	if (dollarSign != '$')
	{
		cout << "No dollar sign in Money input.\n";
		exit(1);
	}
    
	double amountAsDouble;
	cin >> amountAsDouble;
	dollars = dollarsPart(amountAsDouble);
	cents = centsPart(amountAsDouble);
}

int Money::dollarsPart( double amount) const
<The rest of the definition is the same as BankAccount::dollarsPart in Display 7.2 .>

int Money::centsPart( double amount) const
<The rest of the definition is the same as BankAccount::centsPart in Display 7.2 .>

int Money::round( double number) const
<The rest of the definition is the same as BankAccount::round in Display 7.2 .>

 

샘플 대화 상자

Enter an amount of money: $123.45
Your amount is $123.45
My amount is $10.09.
One of us is richer.
$123.45 + $10.09 equals $133.54
$123.45 - $10.09 equals $113.36


팁: 생성자는 객체를 리턴할 수 있다

우리는 종종 생성자를 void 함수인 것처럼 생각한다.

그러나 생성자는 특수한 성질을 가진 특수 함수이며, 때때로 그들이 값을 반환하는 것으로 생각하는 것이 더 타당하다.

그림 8.1의 오버로딩된 + 연산자 정의에 있는 return문에 주목하라:
return Money(finalDollars, finalCents);


반환되는 표현은 Money에 대한 생성자의 호출이다.

우리가 때때로 생성자를 void 함수로 생각하지만, 생성자는 객체를 생성하고 클래스의 객체를 반환하는 것으로 생각될 수도 있다.

생성자를 사용하는 것이 불편하다면 이해하기 어렵다면 이 return문이 다음 코드와 동등하다는 것을 아는 것이 도움이 될 수 있다:

 

Money temp;
temp = Money(finalDollars, finalCents);
return temp;


Money(finalDollars, finalCents)와 같은 표현은 때때로 익명의 객체라고 불린다.

익명의 객체(anonymous object): 어떤 변수에 의해서도 이름이 지어지지 않은 객체

생성자는 호출하는 대상으로도 사용할 수 있다:
Money(finalDollars, finalCents).getDollars( )


const 값으로 리턴하기

디스플레이 8.1의 Money 클래스에 대한 오버로딩 연산자에 대한 선언에서 반환되는 유형에 주목하라.

예를 들어, 다음은 디스플레이 8.1에 나타나는 오버로딩 플러스 연산자에 대한 선언이다:
const Money operator +( const Money& amount1, const Money& amount2);


이 절에서는 줄의 맨 앞에 있는 const에 대해 설명한다.

그러나 const에 대해 논의하기 전에, 값을 반환하는 것에 대한 다른 모든 세부사항을 이해했는지 확인해 보겠다.

그러므로, 먼저 const가 오버로딩된 + 연산자의 선언 또는 정의에 나타나지 않는 경우를 생각해 보자.

선언문이 다음과 같이 읽힌다고 가정하자:
Money operator +( const Money& amount1, const Money& amount2);

예를 들어 (m1 + m2) 객체가 반환될 때 (m1 + m2), 여기서 m1 및 m2는 Money 유형이다.

객체는 m1 + m2의 멤버 변수 값을 변경하거나 변경하지 않을 수 있는 멤버 함수를 호출하는 데 사용될 수 있다.

예를 들어,
(m1 + m2).output( );
는 완벽하게 합법적이다. 이 경우 객체(m1 + m2)를 변경 X

 

그러나 다음은 합법적이며 객체 (m1 + m2)의 멤버 변수 값을 변경 O
(m1 + m2).input( );
그래서 어떤 변수와도 연관이 없을 때도 객체는 변경 O

이것을 이해하는 한 가지 방법은 객체에는 멤버 변수가 있으므로

변경될 수 있는 어떤 종류의 변수가 있다는 것에 주목하는 것이다.


이제 모든 것이 디스플레이 8.1과 같다고 가정하자.

즉, Money 타입의 객체를 반환하는 각 연산자의 반환 타입 전에 const가 있다.

예를 들어, 다음은 오버로딩된 플러스 연산자에 대한 선언이다.
디스플레이 8.1에서:
const Money operator +( const Money& amount1, const Money& amount2);
첫 번째 const

const 수식어를 새롭게 사용한 것

= const로 값을 반환(returning a value as const)

= const 값으로 반환(returning by const value)

= 상수 값으로 반환(returning by constant value)

 

이 경우 const 수식어가 의미하는 것은 반환된 개체를 변경할 수 없다는 것이다.

예를 들어, 다음 코드를 생각해 보자:
Money m1(10.99), m2(23.57);
(m1 + m2).output();  -> (O)
출력 호출은 객체(m1 + m2)를 변경하지 않으므로 완벽하게 합법적이다.

그러나 반환된 유형 이전의 const를 사용하면 다음과 같은 컴파일러 오류 메시지가 생성된다:
(m1 + m2).input( ); -> (X)
왜 상수 값으로 반환하려고 할까? 이것은 일종의 자동 오류 검사를 제공한다.

(m1 + m2)를 구성할 때 일반적으로 실수로 변경하고 싶지 않는다.

이 경우 객체 (m1 + m2)를 변경하는 것은 문제가 되지 않지만,

반환된 객체가 기존 객체를 참조하는 것이라면 문제가 발생할 수 있다.

참조는 섹션 8.3에서 다루고 있다.


처음에는 객체를 변경하는 것으로부터 보호하는 것이 너무 과도한 보호로 보일 수 있다
Money m3;
m3 = (m1 + m2);
m3를 변경하고 싶을 수도 있다. 문제 없다. 다음은 완벽하게 합법적이다:
m3 = (m1 + m2);
m3.input( );

m3과 (m1 + m2)의 값은 서로 다른 두 개의 객체이다.

할당 연산자는 m3을 객체 (m1 + m2)와 동일하게 만들지 않는다.

대신에 (m1 + m2)의 멤버 변수 값을 m3의 멤버 변수로 복사한다.

클래스의 객체를 사용하는 경우 기본 할당 연산자는 두 객체를 동일한 객체로 만들지 않고

한 객체에서 다른 객체로 멤버 변수 값을 복사할 뿐이다.

 

기본 유형(int, double, char 등)의 값은 반환된 유형 이전에 const가 있는지 없는지 변경할 수 없다.

반면 클래스 유형의 값, 즉 객체는 멤버 변수가 있으므로 const 수식어가 반환된 개체에 영향을 미치기 때문에 변경할 수 있다.


단항 연산자 오버로딩하기

단항 연산자: 오직 하나의 피연산자(한 인수)를 취하는 연산자.

예시) -(부정), ++(증가 연산자), --(감소 연산자)

예시 문장) x = -y;

 

단항 연산자뿐만 아니라 이항 연산자도 오버로딩시킬 수 있다.

예시)

Money(표시 8.1) 유형에 대한 - 빼기 연산자를 오버로딩시켜

감산/부정 연산자의 단항 연산자와 이진 연산자 버전을 모두 갖도록 했다.


Money amount1(10), amount2(<), amount3;
amount3 = amount1 - amount2;


그러면 화면에 $4.00가 출력된다:
amount3.output( );


amount3 = -amount1;
amount3.output( );


디스플레이 8.1에서 - 마이너스 연산자를 오버로딩한 것과 유사한 방법으로 ++ 및 -- 연산자를 오버로딩할 수 있다.

이 장의 뒷부분에서 ++와 --연산자를 접두사/접미사 위치에서 오버로딩시키는 방법을 설명하겠다.

 

멤버 함수로 오버로딩하기

디스플레이 8.1에서 클래스 외부에 정의된 독립형 함수로 연산자를 오버로딩했다.

또한 연산자를 멤버 연산자(멤버 함수)로 오버로딩할 수도 있다. 이는 디스플레이 8.2에 나와 있다.
이항 연산자가 멤버 연산자로 오버로딩됬을 때 매개변수는 2개가 아니라 1개뿐이라는 점에 유의하자.

호출 객체는 첫 번째 매개변수 역할을 한다.

 

예시)
Money cost(1, 50), tax(0, 15), total;
total = cost + tax;
+: 멤버 연산자로 오버로딩

cost 변수: 호출 객체

tax: +에 대한 단일 인수

 

멤버 연산자 +의 정의는 Display 8.2에 나와 있다.

이 정의에서 다음 행을 참고하자:
int allCents1 = cents + dollars * 100;
cents와 dollars라는 표현은 이 경우 첫 번째 피연산자인 호출 객체의 멤버 변수이다.

이 정의를 다음과 같이 적용하면
cost + tax
그러면 cent는 cost.cents를 의미하고 dollars는 cost. dollars를 의미한다.


첫 번째 피연산자는 호출 객체이므로

대부분의 경우 연산자 선언의 끝과 연산자 정의의 끝에 const 수식어를 추가해야 한다. 


연산자를 멤버 변수로 오버로딩할 때 장점

새로운 세부사항에 익숙해지기 쉽다.

정의가 멤버 변수를 직접 참조할 수 있댜.

접근자와 설정자를 사용할 필요가 없기 때문에

객체 지향 프로그래밍의 정신에 더 가깝고 효율적이다.

 

디스플레이 8.2 멤버로서 오버로딩 연산자

#include <iostream>
#include <cstdlib>
#include <cmath>
using namespace std;
//This is Display 8.1 redone with the overloaded operators as member functions.
//Class for amounts of money in U.S. currency
class Money
{
public:
	Money( );
	Money( double amount);
	Money( int dollars, int cents);
	Money( int dollars);
	double getAmount( ) const;
	int getDollars( ) const;
	int getCents( ) const;
	void input( ); //Reads the dollar sign as well as the amount number.
	void output( ) const;
	//The calling object is the first operand.
	const Money operator +( const Money& amount2) const;
	const Money operator -( const Money& amount2) const;
	bool operator ==( const Money& amount2) const;
	const Money operator -( ) const;
private:
	int dollars; //A negative amount is represented as negative
	             //dollars and
	int cents; //negative cents. Negative $4.50 is represented as
	           //-4 and -50.
               
	int dollarsPart( double amount) const;
	int centsPart( double amount) const;
	int round( double number) const;
};

int main( )
{
	< If the main function is the same as in Display 8.1 , then the screen dialogue
	will be the same as shown in Display 8.1 .>
}

const Money Money:: operator +( const Money& secondOperand) const
{
	int allCents1 = cents + dollars * 100;
	int allCents2 = secondOperand.cents + secondOperand.dollars * 100;
	int sumAllCents = allCents1 + allCents2;
	int absAllCents = abs(sumAllCents); //Money can be negative.
	int finalDollars = absAllCents / 100;
	int finalCents = absAllCents % 100;    
	if (sumAllCents < 0)
	{
		finalDollars = -finalDollars;
		finalCents = -finalCents;
	}
    
	return Money(finalDollars, finalCents);
}

const Money Money:: operator -( const Money& secondOperand) const
<The rest of this definition is Self-Test Exercise 5 .>

bool Money:: operator ==( const Money& secondOperand) const
{
	return ((dollars == secondOperand.dollars)
	        && (cents == secondOperand.cents));
}

const Money Money:: operator -( ) const
{
	return Money(-dollars, -cents);
}

<Definitions of all other member functions are the same as in Display 8.1 .>

 

이것은 멤버 함수로서 오버로딩된 연산자를 디스플레이 8.1을 다시 작성한 것이다.

 

팁: 클래스는 모든 객체에 액세스할 수 있습니다

멤버 함수 또는 연산자를 정의할 때 호출 객체의 모든 private 멤버 변수(또는 함수)에 액세스할 수 있다. 

그러나 그 이상도 허용된다. 

정의 중인 클래스 객체의 모든 private 멤버 변수(또는 private 멤버 함수)에 액세스할 수 있다.

예시)
디스플레이 8.2의 클래스 Money에 대한 더하기 연산자의 정의:
const Money Money:: operator +( const Money&secondOperand) const
{
    int allCents1 = cents + dollars*100;
    int allCents2 = secondOperand.cents + secondOperand.dollars* 100

이 경우 더하기 연산자는 멤버 연산자로 정의되므로

함수 본체의 첫 번째 줄에 있는 변수 cents와 dollars는 호출 객체(첫 번째 피연산자가 됨)의 멤버 변수이다.

그러나 객체 secondOperand의 멤버 변수를 다음 줄과 같이 사용하는 것도 합법이다:
int allCents2 = secondOperand.cents + secondOperand.dollars * 100;
secondOperand는 Money 클래스의 객체이고

이 줄은 Money 클래스의 멤버 연산자의 정의에 있기 때문에 이것은 합법적이다.

 

함수 응용 프로그램 오버로딩( )

함수 호출 연산자( )는 멤버 함수로 오버로딩 되어야 한다.

클래스의 객체를 마치 함수처럼 사용할 수 있게 해준다.

 

예시)

클래스 AClass가 함수 응용 연산자에 오버로딩을 하여

int형의 인수를 하나 가지고

anObject가 AClass의 개체인 경우

anObject(42)가 anObject 객체와 인수 42를 호출하고

오버로딩된 함수 호출 연산자( )를 호출한다.

 

반환되는 형식은 void 또는 다른 형식일 수 있다.

함수 호출 연산자( )는 임의의 수의 인수를 허용한다.

따라서 함수 호출 연산자( )의 오버로딩된 버전을 여러 개 정의할 수 있다.

 

함정: &&, || 및 쉼표 연산자 오버로딩

&&, || 연산자

정상적인 사용: 단락 평가(short-circuit evaluation)

오버로딩: 완전한 평가(perfect evaluation)


쉼표 연산자

정상적인 사용: 왼쪽에서 오른쪽으로 평가(left-to-right)

오버로딩: 그런 보장이 주어지지 않는다.

 

따라서 &&, || 및 쉼표 연산자에 오버로딩이 되지 않는 것이 좋다.

 

8.2 friend 함수와 자동 형변환

자동 형변환을 위한 생성자

클래스 정의에 적절한 생성자가 포함되어 있으면 시스템이 특정 유형의 변환을 자동으로 수행한다.

 

예시)

프로그램에 Money 클래스에 대한 정의가

표시 8.1 또는 표시 8.2에 표시된 것과 같이 포함되어 있으면

다음을 프로그램에 사용할 수 있다:
Money baseAmount(100, 60), fullAmount;
fullAmount = baseAmount + 25;
fullAmount.output( );
출력은 다음과 같다
$125.60

 

앞의 코드는 충분히 단순하고 자연스러워 보일 수 있지만, 한 가지 미묘한 점이 있다.

25 (baseAmount + 25 식에서)는 적절한 유형이 아니다.

그림 8.1

Money형 + Money형 -> 별도의 오버로딩 O

Money형 + int형 -> 별도의 오버로딩 X

 

시스템이 25가 $25.00을 의미한다는 것을 아는 유일한 방법은

int의 단일 인수를 사용하는 생성자를 포함했다는 것이다.

 

시스템이 식을 볼 때 시스템이 이 식을 확인할 수 있다
baseAmount + 25

 

먼저 연산자 +가 Money 유형의 값과 정수의 조합에 대해 오버로딩되었는지 확인한다. 

확인 결과 이러한 오버로딩은 없다.

 

시스템은 다음으로 정수인 단일 인수를 사용하는 생성자가 있는지 확인한다.

단일 정수 인수를 사용하는 생성자를 찾으면 해당 생성자를 사용하여 정수 25를 Money 유형의 값으로 변환한다.

한 인수 생성자는 멤버 변수 dollars가 25이고 멤버 변수 cents가 0인 Money 유형의 개체로 25를 변환해야 한다고 말한다.

즉, 생성자는 25를 $25.00을 나타내는 Money 유형의 개체로 변환한다. 

(생성자의 정의는 디스플레이 8.1에 있다.)
이 형식 변환은 적합한 생성자가 없는 한 작동하지 않는다.

 

클래스 Money에 int형(또는 long 또는 double 등)의 매개 변수가 있는 생성자 X인 경우, 다음 식은 다음과 같다
baseAmount + 25  //ERROR

 

이러한 자동 유형 변환은

+ 및 -와 같은 오버로딩된 숫자 연산자뿐만 아니라

일반 함수의 인수, 멤버 함수의 인수 및 오버로딩된 다른 연산자의 인수에도

정확히 동일한 방식으로 적용된다.

 

함정: 멤버 연산자 및 자동 형변환

이항 연산자를 멤버 연산자로 오버로딩시키면 두 인수는 더 이상 대칭적이지 않다.

하나는 호출 대상이고, 두 번째 "인수"만이 참인 인수이다.

이것은 미적이지 않을 뿐만 아니라 매우 실용적인 단점도 있다.

모든 자동 형식 변환은 두 번째 인수에만 적용된다.

 

따라서 예를 들어, 앞 부분에서 언급했듯이 다음은 합법적일 것이다:
Money baseAmount(100, 60), fullAmount;
fullAmount = baseAmount + 25;
왜냐하면 Money에는 int형 인수가 하나 있는 생성자가 있기 때문에

값 25는 Money 유형의 값으로 자동 변환되는 int 값으로 간주된다.

 

그러나 (디스플레이 8.2와 같이) +를 멤버 연산자로 오버로딩하면

두 인수를 +로 되돌릴 수 없다.

다음은 불법이다,
fullAmount = 25 + baseAmount; -> (X)
25: Money 유형의 인수 가능 but 호출 객체 사용 X

 

반면 +를 비멤버로 오버로딩할 경우(디스플레이 8.1과 같이)

다음은 완벽하게 합법적이다:
fullAmount = 25 + baseAmount; -> (O)
이것은 연산자를 비멤버로 오버로딩시키는 가장 큰 장점이다.

 

연산자를 비멤버로 오버로딩시키면 모든 인수를 자동으로 형 변환할 수 있다.

연산자를 멤버로 오버로딩시키면 접근자와 설정자를 우회하고 멤버 변수에 직접 접근하는 효율성을 얻을 수 있다.

friend 함수로써 오버로딩: 이 두 가지 장점을 모두 제공하는 연산자(그리고 특정 함수)에 오버로딩시키는 방법

 

friend 함수

클래스에 접근자와 설정자의 전체 집합이 있는 경우

접근자와 설정자를 사용하여 비멤버 오버로딩 연산자를 정의할 수 있다

(디스플레이 8.2와는 반대로 디스플레이 8.1에서와 같이). 

그러나 이것이 private 멤버 변수에 대한 접근을 제공할 수 있지만

효율적인 접근을 제공하지 않을 수 있다.

예시)

디스플레이 8.1에 주어진 오버로딩된 덧셈 연산자 +의 정의를 다시 살펴보자.

단지 4개의 멤버 변수를 읽는 것보다

2개의 getCents 호출과 2개의 getDollar 호출의 오버헤드를 발생시켜야 한다.

대안

1) + 연산자를 멤버로서 오버로딩->  첫 번째 피연산자의 자동 형변환 X 

2) + 연산자를 friend로서 오버로딩 -> 멤버 변수에 직접 접근 + 모든 피연산자에 대한 자동 형변환 O


클래스의 friend 함수: 클래스의 멤버 함수는 아니지만, 멤버 함수인 것처럼 클래스의 private 멤버들에게 접근할 수 있는 함수

함수를 friend 함수로 만들려면 클래스 정의에서 그 함수를 friend로 이름 지어야 한다.

예시)

디스플레이 8.3에서 Money 클래스의 정의를 다시 썼다.

이번에는 연산자를 friend로 오버로딩이 되었다.

연산자 또는 함수 선언을 클래스의 정의에 나열하고

키워드 friend를 연산자 또는 함수 선언 앞에 놓음으로써

연산자 또는 함수를 클래스의 friend로 만든다.


friend 연산자 또는 friend 함수의 특징

클래스 정의에 키워드 friend에 의한 선언이 앞에 온다.

friend는 멤버 함수가 아니다.

클래스의 데이터 멤버에 대한 접근 권한이 남다른 일반 함수이다.

friend는 일반 함수와 정확히 동일하게 정의된다.

 

예시)

특히 디스플레이 8.3에 표시된 연산자 정의에는 함수 제목에 Money::라는 한정자가 포함되지 않는다.

또한 함수 정의에서 키워드 friend를 사용하지 않는다.

디스플레이 8.3의 friend 연산자는 디스플레이 8.1의 비친구 비멤버 연산자와 마찬가지로 호출되며,

모든 인수의 자동 형변환 함수는 디스플레이 8.1의 비친구 비멤버 연산자와 같다.

 

가장 일반적인 종류의 friend 함수는 오버로딩 연산자이다.

그러나 어떤 종류의 함수라도 friend 함수로 만들 수 있다. 


함수(또는 오버로딩 연산자)는 둘 이상의 클래스의 friend가 될 수 있다.

여러 클래스의 friend가 되도록 하려면 friend가 되기를 원하는 각 클래스의 friend 함수 선언을 입력하면 된다.

 

연산자를 friend로 오버로딩하는 것은 모든 인수에서 자동 형변환의 실용적인 이점을 제공하며,

연산자 선언은 클래스 정의 내에 있으므로 비멤버 비친구 연산자보다 적어도 약간 더 많은 캡슐화를 제공한다.

 

연산자를 오버로딩하는 세 가지 방법:

비멤버 비친구, 멤버, 그리고 friend.

 

디스플레이 8.3 friend로써 오버로딩 연산자

#include <iostream>
#include <cstdlib>
#include <cmath>
using namespace std;

//Class for amounts of money in U.S. currency
class Money
{
public:
	Money( );
	Money( double amount);
	Money( int dollars, int cents);
	Money( int dollars);
	double getAmount( ) const;
	int getDollars( ) const;
	int getCents( ) const;
	void input( ); //Reads the dollar sign as well as the amount number.
	void output( ) const;
	friend const Money operator +( const Money& amount1, const Money& amount2);
	friend const Money operator -( const Money& amount1, const Money& amount2);
	friend bool operator ==( const Money& amount1, const Money& amount2);
	friend const Money operator -( const Money& amount);
private:
	int dollars; //A negative amount is represented as negative
	             //dollars and
	int cents; //negative cents. Negative $4.50 is represented
	           //as -4 and -50.
	int dollarsPart( double amount) const;
	int centsPart( double amount) const;
	int round( double number) const;
};

int main( )
{
	< If the main function is the same as in Display 8.1 , then the screen dialogue
	will be the same as shown in Display 8.1 .>
}

const Money operator +( const Money& amount1, const Money& amount2)
{
	//Note that friends have direct access to member variables.
	int allCents1 = amount1.cents + amount1.dollars*100;
	int allCents2 = amount2.cents + amount2.dollars*100;
	int sumAllCents = allCents1 + allCents2;
	int absAllCents = abs(sumAllCents); //Money can be negative.
	int finalDollars = absAllCents/100;
	int finalCents = absAllCents%100;
	
	if (sumAllCents < 0)
	{
		finalDollars = -finalDollars;
		finalCents = -finalCents;
	}
    
	return Money(finalDollars, finalCents);
}

const Money operator -( const Money& amount1, const Money& amount2)
<The complete definition is Self-Test Exercise 7 .>

bool operator ==( const Money& amount1, const Money& amount2)
{
	return ((amount1.dollars == amount2.dollars)
	        && (amount1.cents == amount2.cents));
}

const Money operator -( const Money& amount)
{
	return Money(-amount.dollars, -amount.cents);
}

<Definitions of all other member functions are the same as in Display 8.1 .>

int allCents1 = amount1.cents + amount1.dollars*100;
int allCents2 = amount2.cents + amount2.dollars*100;

friend는 멤버 변수에 직접 접근할 수 있다.

 

friend 클래스

함수가 클래스의 friend가 되는 것과 같은 방식으로 클래스은 다른 클래스의 friend가 될 수 있다.

클래스 F가 클래스 C의 friend이면 클래스 F의 모든 멤버 함수는 클래스 C의 friend가 된다.

한 클래스을 다른 클래스의 friend로 만들려면 다른 클래스의 friend 클래스를 friend로 선언해야 한다.
한 클래스가 다른 클래스의 friend인 경우 클래스 정의에서 서로를 참조하는 것이 일반적이다.

이렇게 하려면 이 단락 뒤에 이어지는 개요에 표시된 것처럼

두 번째로 정의된 클래스에 대한 순방향 선언을 포함해야 한다.

순방향 선언(forward declaration): 클래스 정의 뒤에 세미콜론이 오는 표제

F반을 C반의 friend로 삼기를 원하는 경우 설정 방법에 대한 대략적인 개요는 다음과 같다:

class F; //forward declaration
class C
{
public:
...
friend class F;
...
};
class F
{
...

 

friend 함수
클래스의 friend 함수: 해당 클래스의 개체의 멤버 구성원에게 접근할 수 있는 것을 제외하고는 일반 함수

함수를 클래스의 friend로 만들려면 클래스 정의에서 friend 함수에 대한 함수 선언을 나열해야 한다.

함수 선언은 키워드 friend 앞에 있다.

함수 선언은 private 섹션 또는 public 섹션에 배치될 수 있지만

두 경우 모두 public 함수이므로 public 섹션에 나열하는 것이 더 명확하다.


friend 함수를 이용한 클래스정의 구문론

class Class_Name
{
public:
    friend Declaration_for_Friend_Function_1
    friend Declaration_for_Friend_Function_2

    .
    .
    .
    Member_Function_Declarations
private:
    Private_Member_Declarations
};
EXAMPLE
class FuelTank
{
public:
    friend void fillLowest(FuelTank& t1, FuelTank& t2);
    //Fills the tank with the lowest fuel level, or t1 if a tie.
    FuelTank(double theCapacity, double theLevel);
    FuelTank( );

    void input( );
    void output( ) const;
private:
    double capacity;//in liters
    double level;
};

friend 함수는 멤버 함수가 아니다.

friend 함수는 일반 함수와 동일하게 정의되고 호출된다.

friend 함수에 대한 호출에서는 점 연산자를 사용하지 않으며,

friend 함수의 정의에서는 형식 한정자를 사용하지 않는다.

 

함수: friend가 없는 컴파일러

일부 C++ 컴파일러에서 friend 함수는 잘 작동하지 않는 경우가 있다. 

이 때 friend 함수는 클래스의 private 멤버에게 항상 원래대로 접근할 수 있는 것은 아니다.

만약 friend 함수가 작동하지 않는 컴파일러가 있다면,

접근자를 사용하여 비멤버 함수와 오버로딩 연산자를 정의하거나 연산자를 멤버로 오버로딩해야 한다.

 

연산자 오버로딩에 관한 규칙
■ 연산자를 오버로딩할 때 결과적으로 오버로딩된 연산자의 하나 이상의 매개변수(1개의 피연산자)는 클래스 유형이어야 한다.
■ 대부분의 운영자는 클래스의 멤버, 클래스의 친구 또는 비멤버, 비친구로 오버로딩될 수 있다.
■ 클래스의 (정적이지 않은) 멤버로만 오버로딩할 수 있는 연산자는 =, [], -, ()이다.
■ 새로운 연산자를 생성할 수 없다. +, -, *, /, % 등 기존 연산자를 오버로드하는 작업만 수행할 수 있다.
■ 연산자가 취하는 인수의 수는 변경할 수 없다. 예를 들어 %를 오버로드할 때는 이항 연산자에서 단항 연산자로 %를 변경할 수 없으며, 오버로드할 때는 ++를 단항 연산자에서 이항 연산자로 변경할 수 없다.
■ 연산자의 우선순위는 변경할 수 없다. 과부하된 연산자는 일반 버전의 연산자와 동일한 우선순위를 가진다. 예를 들어, x, y, z가 객체이고 연산자 +와 *가 해당 클래스에 대해 오버로딩된 경우에도 x*y + z는 항상 (x*y) + z를 의미한다.
■ 도트 연산자(..), 범위 지정 연산자(::), sizeof, ?: 및 연산자 .*를 오버로드할 수 없다. 이 책에서는 다루지 않는다.
■ 오버로딩된 연산자에는 기본 인수를 사용할 수 없다.

 

8.3 참조 및 더 많은 오버로딩된 연산자

참조

참조(reference): 저장소 위치의 이름

다음과 같이 독립 실행형 참조를 가질 수 있다:
int robert;
int& bob = robert;
이것은 bob을 robert라는 변수의 저장 위치에 대한 참조로 만들어 주고,

bob을 robert라는 변수의 별칭으로 만들어 준다.

bob에 대한 변경 사항은 robert에 대해서도 변경될 것이다.

독립형 참조가 유용한 경우는 몇 가지 있다.

 

참조는 참조별 매개변수 메커니즘을 구현하는 데 사용된다.

 

참조를 반환하면 더 자연스러운 방법으로 특정 연산자를 오버로딩시킬 수 있다.

참조를 반환하는 것은 변수 또는 더 정확하게는 변수에 대한 별칭을 반환하는 것과 같은 것으로 볼 수 있다.

구문적 세부 사항으로 반환 유형에 &를 추가한다.

 

예를 들어,
double& sampleFunction(double& variable);
double &와 같은 유형은 double과 다른 유형이므로 

함수 선언과 함수 정의 모두에서 &를 사용해야 한다. 

 

반환 표현식은 적절한 유형의 변수와 같이 참조가 있는 것이어야 한다. 

X + 5와 같은 표현식이 될 수 없다. 

변수에 별칭을 생성하여 변수를 즉시 파괴하기 때문에 로컬 변수를 반환해서는 안 된다.

함수 정의의 사소한 예는 다음과 같다
double& sampleFunction( double& variable)
{
    return variable;
}

 

예를 들어, 다음 코드는 99를 출력한 다음 42를 출력한다:
double m = 99;
cout << sampleFunction(m) << endl;
sampleFunction(m) = 42;
cout << m << endl;

특정 종류의 오버로딩 연산자를 정의할 때만 참조를 반환한다.


L-Values 및 R-Values
l-value: 할당 연산자의 왼쪽에 나타날 수 있는 것

r-value: 할당 연산자의 오른쪽에 나타날 수 있는 것
함수에 의해 반환되는 개체가 l-value가 되도록 하려면 참조에 의해 반환되어야 한다.

 

팁: 클래스 유형의 멤버 변수 반환하기

클래스 유형의 멤버 변수를 반환할 때 거의 모든 경우에 멤버 값을 const 값으로 반환하는 것이 중요하다.

다음에서 설명한 예제와 같이 const 값으로 반환하지 않는다고 가정한다:
class Employee
{
public:
    Money& getSalary( ) { return salary; }
    ...
private:
    Money salary;
    ...
};
이 예제에서 salary는

Employee 클래스의 일부 접근자를 사용하는 경우를 제외하고는

변경할 수 없는 private 멤버 변수이다.

getSalary 함수는 Money 유형의 salary 변수를 반환한다.

참조하여 salary를 반환하지 않으면 새로운 임시 salary 사본이 생성되어 반환된다.

예제에 표시된 salary에 대한 참조를 반환함으로써 이러한 오버헤드를 방지할 수 있다.

 

그러나 salary가 private로 선언되더라도 이러한 개인 정보는 다음과 같이 쉽게 우회된다:
Employee joe;
(joe.getSalary( )).input( );
이제 joe라는 직원은 원하는 급여를 마음대로 입력할 수 있다.

 

반면에, 다음과 같이, getSalary가 const 값으로 그것의 가치를 반환한다고 가정해자:
class Employee
{
public:
    const Money& getSalary( ) { return salary; }
    . . .
private:
    Money salary;
    . . .
};


이 경우 다음은 컴파일러 오류 메시지를 제공한다.
(joe.getSalary( )).input( );

일반적으로 멤버 함수가 멤버 변수를 반환하고 그 멤버 변수가 어떤 클래스 유형에 속할 때, 

일반적으로 private 멤버 변수에 대한 외부 접근을 피하기 위해 참조로 반환되지 않아야 한다.

효율성을 이유로 참조로 반환하고자 한다면 반환 값에 const를 추가하는 것이

멤버 변수에 대한 접근을 보호하는 데 도움이 될 수 있다.


>> 및 << 오버로딩하기

연산자 >>와 <<은 자신이 정의한 클래스의 객체를 입력하고 출력하는 데 사용할 수 있도록 오버로딩을 할 수 있다. 

 

<< 는 연산자이다
우리가 cout와 함께 사용한 삽입 연산자 <<는 + 또는 -와 매우 유사한 이항 연산자이다.

예시)
cout << "Hello out there.\n";
연산자: <<

첫 번째 피연산자: (라이브러리 iostream으로부터) 미리 정의된 객체 cout

두 번째 피연산자: 문자열 값 "Hello out.\n"

 

cout은 스트림이다

스트림(stream): cin 및 cout 객체 뿐만 아니라 이러한 파일 입출력 객체

미리 정의된 객체 cout은 ostream 유형이다.

<<를 오버로딩하면 cout을 받는 매개변수는 ostream 유형이 된다.

cout을 염두에 두고 생성한 오버로딩은 오버로딩된 <<의 정의에 아무런 변경 없이 파일 출력에도 적용된다.

 

<< 오버로딩

Money 유형의 값 출력 위해 

이전 정의(디스플레이 8.1~디스플레이 8.3)에서는 멤버 함수 출력을 사용했지만

삽입 연산자 <<을 사용하면 더 좋을 것이다.

Money amount(100);
cout << "I have " << amount << " in my purse.\n";

 

다음과 같은 멤버 함수 출력을 사용할 필요가 없는 대신:
Money amount(100);
cout << "I have ";
amount.output( );
cout << " in my purse.\n";


연산자 <<에 오버로딩이 되는 한 가지 문제는 어떤 이 있어야 하는지 결정하는 것이다.
<<가 다음과 같은 표현으로 사용될 때 반환된다:
cout << amount
앞의 식에서 두 피연산자는 cout과 amount 이며, 식을 평가하면 값이 화면에 기록된다.

그러나 << 가 + 또는 - 와 같은 연산자라면, 앞의 식에서도 값을 반환해야 한다.

결국, n1 + n2 와 같은 다른 피연산자가 있는 식에서는 값을 반환한다.

그러나 cout << amount 이 반환되는가?

이 질문에 대한 답을 얻기 위해서는 << 를 포함하는 좀 더 복잡한 식을 살펴보아야 한다.

 

연쇄적인 <<

<<을 사용하여 일련의 표현식을 평가하는 다음 표현식을 생각해 보겠다:
cout << "I have " << amount << " in my purse.\n";

이 식은 아래와 동등하다:
( (cout << "I have ") << amount ) << " in my purse.\n";
이전 식을 이해하려면 어떤 값을 반환해야 할까?

첫 번째로 평가된 것은 하위 표현이다:
(cout << "I have ")
일이 해결된다면, 다음과 같이 계산을 계속할 수 있도록

하위 표현식은 cout을 반환하는 것이 좋다:
( cout << amount ) << " in my purse.\n";
그리고 일이 계속 진행되려면 다음과 같이 계산을 계속할 수 있도록

(cout << amount )은 cout을 반환하는 것이 좋다:
cout << " in my purse.\n";

 

<<는 스트림을 반환한다.
이는 디스플레이 8.4에 나와 있다.

연산자 <<: 첫 번째 인수를 반환한다. ostream 유형(cout 유형)이다.
따라서 오버로딩된 연산자 <<에 대한 선언은 다음과 같다:
class Money
{
public:
    . . .
    friend ostream& operator <<(ostream& outs,
    const Money& amount);

 

디스플레이 8.4 연산자로서 <<

cout << "I have " << amount << " in my purse.\n";
와 같은 뜻의
((cout << "I have ") << amount) << " in my purse.\n";
다음과 같이 평가됩니다:

먼저 (cout << "I have ").")를 평가하고 cout을 반환합니다:
((cout << "I have ") << amount) << " in my purse.\n";
"I have" 문자열이 출력됩니다.
(cout << amount) << " in my purse.\n";

그런 다음 (cout << amount)을 평가하고 cout을 반환합니다:
(cout << amount) << " in my purse.\n";
amount의 값이 출력됩니다.
cout << " in my purse.\n";

그런 다음 cout << " in my purse.\n", 이것은 cout을 평가합니다:
cout << " in my purse.\n";
" in my purse.\n" 문자열이 출력됩니다.
cout;

<< 연산자가 더 이상 없기 때문에 프로세스가 종료됩니다.

 

오버로딩된 연산자 <<의 정의는 멤버 함수 출력과 매우 유사하다.

개요 형태에서 오버로딩된 연산자에 대한 정의:

ostream& operator <<(ostream& outputStream, const Money& amount)
{
    /*This part is the same as the body of
    Money::output which is given in Display 8.1 (except that
    dollars is replaced with amount.dollars
    and cents is replaced by amount.cents).*/
    return outputStream;
}

 

<< 와 >>는 참조를 반환한다.

연산자는 참조를 반환한다.
추출(extraction) 연산자 >>는 우리가 삽입(insertion) 연산자 <<에 대해 설명한 것과 유사한 방식으로 오버로딩된다.

그러나 추출(입력) 연산자 >>에서는 두 번째 인수는 입력값을 수신하는 객체가 될 것이므로

두 번째 매개변수는 일반적인 참조별 매개변수가 되어야 한다.

개요에서 오버로딩된 추출 연산자 >>에 대한 정의:

istream& operator >>(istream& inputStream, Money& amount)
{
    /*This part is the same as the body of
    Money::input, which is given in Display 8.1 (except that
    dollars is replaced with amount.dollars
    and cents is replaced by amount.cents).*/
    return inputStream;
}

 

오버로딩된 연산자 << 및 >>의 전체 정의는 Money 클래스를 다시 작성한 디스플레이 8.5에 나와 있다.


> >또는 <<를 멤버 연산자로 오버로딩 불가능하다.

<< 및 >>가 우리가 원하는 대로 작동하려면

첫 번째 연산자(첫 번째 인수)가 cout 또는 cin(또는 일부 파일 I/O 스트림)이어야 한다.

그러나 Money 클래스의 멤버 연산자로 오버로딩하려면 

첫 번째 연산자가 호출 객체가 되어야 하므로 Money 유형이어야 한다.

따라서 모순이 발생한다.

 

디스플레이 8.5 <<와 >> 오버로딩

#include <iostream>
#include <cstdlib>
#include <cmath>
using namespace std;

//Class for amounts of money in U.S. currency
class Money
{
public:
	Money( );
	Money( double amount);
	Money( int theDollars, int theCents);
	Money( int theDollars);
	double getAmount( ) const;
	int getDollars( ) const;
	int getCents( ) const;
	friend const Money operator +( const Money& amount1, const Money& amount2);
	friend const Money operator -( const Money& amount1, const Money& amount2);
	friend bool operator = =(const Money& amount1, const Money& amount2);
	friend const Money operator -( const Money& amount);
	friend ostream& operator <<(ostream& outputStream, const Money& amount);
	friend istream& operator >>(istream& inputStream, Money& amount);
private:
	//A negative amount is represented as negative dollars
	//and negative cents. Negative $4.50 is represented as
	//-4 and -50.
	int dollars, cents;
    
	int dollarsPart( double amount) const;
	int centsPart( double amount) const;
	int round( double number) const;
};

int main()
{
	Money yourAmount, myAmount(10, 9);
	cout << "Enter an amount of money: ";
	cin >> yourAmount;
	cout << "Your amount is " << yourAmount << endl;
	cout << "My amount is " << myAmount << endl;

	if (yourAmount == myAmount)
		cout << "We have the same amounts.\n";
	else
		cout << "One of us is richer.\n";
        
	Money ourAmount = yourAmount + myAmount;
	cout << yourAmount << " + " << myAmount
	     << " equals " << ourAmount << endl;
         
	Money diffAmount = yourAmount - myAmount;
	cout << yourAmount << " - " << myAmount
	     << " equals " << diffAmount << endl;
	//Since << returns a reference, you can chain
	//<< like this. You can chain >> in a similar way. 
    
	return 0;
}

<Definitions of other member functions are as in Display 8.1.
Definitions of other overloaded operators are as in Display 8.3.>
//In the main function, cout is plugged in for outputStream.
ostream& operator <<(ostream& outputStream, const Money& amount)
{
	int absDollars = abs(amount.dollars);
	int absCents = abs(amount.cents);
    
	if (amount.dollars < 0 || amount.cents < 0)
	//accounts for dollars == 0 or cents == 0
		outputStream << "$-";
	else
		outputStream << '$';
	outputStream << absDollars;
    
	if (absCents >= 10)
		outputStream << '.' << absCents;
	else
		outputStream << '.' << '0' << absCents;
        
	return outputStream; //Returns a reference
}

//For an alternate input algorithm, see Self-Test Exercise 3 in Chapter 7.
//Uses iostream and cstdlib:
//In the main function, cin is plugged in for inputStream.
istream& operator >>(istream& inputStream, Money& amount)
{
	char dollarSign;
	inputStream >> dollarSign; //hopefully
	if (dollarSign != '$')
	{
		cout << "No dollar sign in Money input.\n";
		exit(1);
	}
    
	double amountAsDouble;
	inputStream >> amountAsDouble;
    
	//Since this is not a member operator,
	//you need to specify a calling object
	//for member functions of Money.
	amount.dollars = amount.dollarsPart(amountAsDouble);
	amount.cents = amount.centsPart(amountAsDouble);
    
	return inputStream; //Returns a reference
}

<< 참조를 반환하기 때문에 <<를 이렇게 연결할 수 있다. 
>>를 비슷한 방법으로 체인으로 연결할 수 있다.

 

< 기타 멤버 함수의 정의는 디스플레이 8.1과 같다.
기타 오버로딩 연산자의 정의는 디스플레이 8.3과 같다.>

 

 

ostream& operator <<(ostream& outputStream, const Money& amount)

main 함수에서 cout은 outputStream에 연결되어 있다.

 

return outputStream;

참조를 리턴한다.

 

istream& operator >>(istream& inputStream, Money& amount)

main 함수에서 cin은 inputStream에 연결되어 있다.

 

amount.dollars = amount.dollarsPart(amountAsDouble);
amount.cents = amount.centsPart(amountAsDouble);

멤버 연산자가 아니기 때문에 Money의 멤버 함수에 대한 호출 객체를 지정해야 한다.

 

return inputStream;

참조를 리턴한다.

 

샘플 대화 상자

Enter an amount of money: $123.45
Your amount is $123.45
My amount is $10.09.
One of us is richer.
$123.45 + $10.09 equals $133.54
$123.45 - $10.09 equals $113.36

 

팁: 반환된 값의 사용 방법

함수는 다음과 같은 네 가지 방법으로 T형 값을 반환할 수 있다:
■ 함수 선언 T f( )에서와 같이 일반적인 오래된 값으로;
■ 함수 선언 T f( )에서와 같이 상수 값으로;
■ 함수 선언 T& f( )와 같이 참조로;
■ 함수 선언 const T& f( )에서와 같이 const 참조에 의하여;

 

일반적으로 반환 유형이 단순한 유형일 때는 반환 유형에 const를 사용하지 않는다.

단순한 값을 l-value, 즉 할당문의 왼쪽에서 허용할 때는 참조로 반환하고,

그렇지 않으면 단순한 유형을 일반적인 오래된 값으로 반환한다.


참조에 의한 반환 여부의 결정은 반환된 개체를 l-value로 사용할 수 있는지 여부와 관련이 있다.

l-value로 사용할 수 있는 것, 즉 할당 작업자의 왼쪽에서 사용할 수 있는 것을 반환하려면

참조에 의해 반환해야 하며 반환된 유형에 앰퍼샌드 &를 사용해야 한다.

로컬 변수(또는 기타 수명이 짧은 개체)를 상수가 있거나 없는 참조로 반환하면 문제가 발생할 수 있으므로 피해야 한다.

클래스 유형의 경우 두 반환된 유형 사양 const T와 const T&는 매우 유사하다.

둘 다 반환된 개체에 직접 일부 설정자를 호출하여 반환된 개체를 변경할 수 없음을 의미한다
f().mutator( );
반환된 값은 여전히 할당 연산자를 사용하여 다른 변수에 복사될 수 있으며 

다른 변수에는 설정자가 적용될 수 있다.

만약 const T&와 const T 사이에서 결정할 수 없다면

(앰퍼샌드를 사용하지 않음) const T를 사용하라.
const T&는 const T보다 조금 더 효율적이지만

const T&은 때때로 문제를 일으킬 수 있다.


public 멤버 함수가 private 클래스 멤버 변수를 반환하는 경우, 

반환된 형식에 항상 상수가 있어야 한다.

 

다음 요약이 도움이 될 수 있다. T는 클래스 유형으로 가정한다.


함수 선언 T f( )

값별 단순 반환

l-value로 사용 불가능

반환된 값은 f( ).mutator( )에서와 같이 직접 변경 가능

복사 생성자 호출 O


const T f( )

상수 값으로 반환

반환된 값을 f( ).mutator( )에서와 같이 직접 변경 불가능


T& f( )

참조를 통해 반환

l-value로 사용 가능

반환된 값은 f( ).mutator( )와 같이 직접 변경 가능

복사 생성자 호출 X


const T&f( )

상수 참조로 반환

l-value로 사용 불가능

반환된 값을 f( ).mutator( )에서와 같이 직접 변경 불가능

복사 생성자 호출 X

 

오버로딩 >> 및 <<
입력 및 출력 연산자 >>와 <<는 다른 연산자와 마찬가지로 오버로딩 발생할 수 있다.
연산자가 cin, cout 및 파일 I/O에 대해 예상대로 동작하도록 하려면 

반환되는 값은 입력의 경우 istream 유형, 출력의 경우 ostream 유형이어야 하며

값은 참조로 반환되어야 한다.


선언문

class Class_Name
{
. . .
public:
    . . .
    friend istream& operator >>(istream& Parameter_1, Class_Name& Parameter_2);
    friend ostream& operator <<(ostream& Parameter_3, const Class_Name& Parameter_4);

연산자는 friend일 필요는 없지만 입력 또는 출력 중인 클래스의 멤버일 수는 없다.

정의

istream& operator >>(istream& Parameter_1, Class_Name& Parameter_2)
{
. . .
}
ostream& operator <<(ostream& Parameter_3, const Class_Name& Parameter_4)
{
. . .
}

접근자와 설정자가 충분하다면 >>와 <<비친구(non-friend) 함수으로 오버로딩할 수 있다.

하지만 이들을 friend로 정의하는 것이 자연스럽고 더 효율적이다.

 

할당 연산자

할당 연산자 =를 오버로딩 O -> 멤버 연산자로 오버로딩해야 한다.

할당 연산자 =를 오버로딩 X -> 클래스에 대한 할당 연산자가 자동으로 나타난다.

이 기본 할당 연산자는 클래스의 한 개체에서 다른 개체의 멤버 변수로 멤버 변수 값을 복사한다.

 

증가 및 감소 연산자 오버로딩

접두사와 접미사

증가 연산자 ++와 --는 각각 두 가지 버전으로 구성된다.

접두사(prefix): ++x

접미사(postfix 또는 suffix):x++

 

오버로딩된 연산자의 접두사 버전과 접미사 버전 구별

접두사 형식 오버로딩:

(한 개의 매개변수가 있는 비멤버 연산자 또는 매개변수가 없는 멤버 연산자로서)

++ 연산자를 규칙적인 방식으로 오버로딩시킨다.

접미사 버전 오버로딩:

x++ 또는 x--를 얻으려면 int형의 두 번째 매개변수를 추가한다.

(이것은 컴파일러의 표시일 뿐 x++ 또는 x--를 호출할 때 두 번째 int형 인수를 제공하지 않는다.)


예시)

디스플레이 8.6은 데이터가 정수 쌍인 클래스의 정의를 포함한다.

증가 연산자 ++는 접두사 표기와 접미사 표기 모두에서 작동하도록 정의된다.

접미사 버전의 정의는 디스플레이 8.6과 같이 int 매개 변수를 무시한다.

컴파일러는 a++가 보이면 a를 호출 객체로 하는 IntPair::operator++(int)의 호출로 처리한다.


int 및 char와 같은 단순한 타입의 증가 및 감소 연산자는

접두사 형식의 참조를 통해 그리고

접미사 형식의 값을 통해 반환된다.

클래스 유형에 대해 이러한 연산자를 오버로드할 때

단순한 유형에서 발생하는 일을 에뮬레이션하고 싶다면,

접두사 형식의 참조를 통해 그리고

접미사 형식의 값을 통해 반환된다.

그러나 우리는 증가 또는 감소 연산자를 참조하여 반환하기에는

너무 많은 문제에 대한 문을 열어두고 있으므로

모든 버전의 증가 및 감소 연산자에 대해

항상 단순하게 값을 반환한다.

 

디스플레이 8.6 ++ 오버로딩

#include <iostream>
#include <cstdlib>
using namespace std;

class IntPair
{
public:
	IntPair( int firstValue, int secondValue);
	//You need not give a parameter name in a function or operator declaration.
	//For ++ it makes sense to give no parameter since the parameter is not used.
	IntPair operator++(); //Prefix version
	IntPair operator++( int); //Postfix version
	void setFirst( int newValue);
	void setSecond( int newValue);
	int getFirst( ) const;
	int getSecond( ) const;
private:
	int first;
	int second;
};

int main( )
{
	IntPair a(1,2);
	cout << "Postfix a++: Start value of object a: ";
	cout << a.getFirst( ) << " " << a.getSecond( ) << endl;    
	IntPair b = a++;
	cout << "Value returned: ";
	cout << b.getFirst( ) << " " << b.getSecond( ) << endl;
	cout << "Changed object: ";
	cout << a.getFirst( ) << " " << a.getSecond( ) << endl;

	a = IntPair(1, 2);
	cout << "Prefix ++a: Start value of object a: ";
	cout << a.getFirst( ) << " " << a.getSecond( ) << endl;
	IntPair c = ++a;
	cout << "Value returned: ";
	cout << c.getFirst( ) << " " << c.getSecond( ) << endl;
	cout << "Changed object: ";
	cout << a.getFirst( ) << " " << a.getSecond( ) << endl;
	return 0;
}

IntPair::IntPair( int firstValue, int secondValue)
                      : first(firstValue), second(secondValue)
{/*Body intentionally empty*/}
IntPair IntPair:: operator++(int ignoreMe) //Postfix version
{
	int temp1 = first;
	int temp2 = second;
	first++;
	second++;
	return IntPair(temp1, temp2);
}

IntPair IntPair:: operator++( ) //Prefix version
{
	first++;
	second++;
	return IntPair(first, second);
}

void IntPair::setFirst( int newValue)
{
	first = newValue;
}

void IntPair::setSecond( int newValue)
{
	second = newValue;
}

int IntPair::getFirst( ) const
{
	return first;
}

int IntPair::getSecond( ) const
{
	return second;
}

함수나 연산자 선언에서 매개 변수 이름을 지정할 필요는 없다.
++의 경우 매개 변수가 사용되지 않으므로 매개 변수를 지정하지 않는 것이 합리적이다.

 

샘플 대화 상자

Postfix a++:  Start value of object a: 1 2
Value returned: 1 2
Changed object: 2 3
Prefix ++a:  Start value of object a: 1 2
Value returned: 2 3
Changed object: 2 3

 

배열 연산자[] 오버로딩

클래스의 대괄호 []를 클래스의 객체와 함께 사용할 수 있도록 오버로딩할 수 있다.

할당 연산자의 왼쪽에 있는 식을 []로 사용하려면 참조를 반환하도록 연산자를 정의해야 한다.

[]를 오버로딩할 때 연산자 []는 멤버 함수여야 한다.

 

[]은(는) 멤버 연산자로 오버로딩되므로 []를 사용하는 식의 한 가지는 호출 객체여야 한다.

식 a[2]에서 a는 호출 객체이고 2는 멤버 연산자 []에 대한 인수이다.

[]를 오버로딩할 때 이 "인덱스" 매개 변수는 정수형,

즉 enum, char, short, int, long 또는 이러한 유형 중 하나의 unsigned 버전이어야 한다.


예시)

그림 8.7에서 객체들이 인덱스가 0과 1이 아닌 문자 배열과 같이 행동하는 CharPair라는 클래스를 정의한다.

a[1]과 a[2] 식은 배열 인덱스 변수와 같이 동작한다.

오버로딩 연산자 []의 정의를 보면 참조가 반환되고,

참조가 CharPair 객체 전체가 아니라 멤버 변수에 대한 참조임을 알 수 있다.

멤버 변수가 배열의 인덱스 변수와 유사하기 때문이다.

(그림 8.7의 샘플 코드에서) a[1]을 변경할 때, 먼저 멤버 변수를 변경하려고 한다.


예시)

디스플레이 8.7의 샘플 main 함수에 있는 a[1]과 a[2]를 통해 private 멤버 변수에 접근할 수 있다.

비록 첫 번째와 두 번째가 private 멤버이지만,

코드는 이름으로 첫 번째와 두 번째를 언급하는 것이 아니라

간접적으로 a[1]과 a[2]라는 이름을 사용하기 때문에 합법적이다.

 

디스플레이 8.7 [] 오버로딩

#include <iostream>
#include <cstdlib>
using namespace std;

class CharPair
{
public:
	CharPair( ){ /*Body intentionally empty*/ }
	CharPair( char firstValue, char secondValue)
	                : first(firstValue), second(secondValue)
	{ /*Body intentionally empty*/}

	char& operator[](int index);
private:
	char first;
	char second;
};

int main( )
{
	CharPair a;
	a[1] = 'A';
	a[2] = 'B';
	cout << "a[1] and a[2] are:\n";
	cout << a[1] << a[2] << endl;
    
	cout << "Enter two letters (no spaces):\n";
	cin >> a[1] >> a[2];
	cout << "You entered:\n";
	cout << a[1] << a[2] << endl;
    
	return 0;
}

//Uses iostream and cstdlib:
char& CharPair:: operator[](int index)
{
	if (index == 1) 	    //Note that you return the member variable,
		return first;       //not the entire Pair object, because the
	else if (index == 2)        //member variable is analogous to an indexed
		return second;      //variable of an array.
	else
	{
		cout << "Illegal index value.\n";
		exit(1);
	}
}

멤버 변수가 배열의 인덱싱된 변수와 유사하므로 전체 쌍 개체가 아닌 멤버 변수를 반환한다.

 

샘플 대화 상자

a[1] and a[2] are:
AB
Enter two letters (no spaces):
CD
You entered:
CD

 

L-Value 대 R-Value에 따른 오버로딩

이 책에서는 수행하지 않지만 함수 이름(또는 연산자)을 오버로드하여 

L-Value으로 사용할 때와 R-Value으로 사용할 때 다른 동작을 수행할 수 있다.

(l-값은 할당 문의 왼쪽에서 사용할 수 있음을 의미한다.)

예시)

함수 f가 L-Value으로 사용되는지 또는 R-Value으로 사용되는지에 따라

다른 동작을 수행하려면 다음과 같이 수행할 수 있다:
class SomeClass
{
public:
    int& f( ); // will be used in any l-value invocation
    const int& f( ) const ; // used in any r-value invocation
    ...
};
두 매개변수 목록은 동일해야 한다.

f 의 두 번째 선언에는 const가 두 번 발생한다는 점에 유의하세요.

const의 두 항목을 모두 포함해야 한다.

앰퍼서더와 부호 &도 당연히 필요하다.

 

'프로그래밍 공부 > OOP' 카테고리의 다른 글

10장 포인터와 동적 배열  (1) 2023.11.26
9장 문자열  (1) 2023.11.25
7장 생성자와 다른 도구  (1) 2023.11.24
6장 구조체와 클래스  (1) 2023.11.22
5장 배열  (0) 2023.11.21