학습목표

  • 연산자 오버로딩
  • 프렌드 함수
  • 출력을 위한 << 연산자 오버로딩
  • 상태 멤버
  • rand() 함수를 이용한 무작위 값 생성
  • 클래스와 관련된 자동 데이터형 변환과 데이터형 캐스트
  • 클래스 변환 함수

11.1 연산자 오버로딩

연산자 오버로딩(operator overloading) : 연산자들에 다중적인 의미 부여

  • C++에서는 연산자 오버로딩을 사용자 정의 데이터형에도 적용할 수 있음
  • 연산자 오버로딩을 위해서는 operatorop(argument-list)의 형식을 가진 연산자 함수(operator function)라는 특별한 함수를 사용해야함
  • 이때의 op는 적법한 C++ 연산자여야하며, 같은 클래스의 객체들끼리 오버로딩된 연산자를 사용할 경우 해당 연산자가 연산자 함수로 대체됨


11.2 잠깐의 휴식 : 연산자 오버로딩 예제

// mytime0.h

#ifndef MYTIME0_H_
#define MYTIME0_H_

class Time
{
	private:
		int hours;
		int minutes;
	public:
		Time();
		Time(int h, int m = 0);
		void AddMin(int m);
		void AddHr(int h);
		void Reset(int h = 0, int m = 0);
		Time Sum(const Time & t) const;
		void Show() const;
};

#endif
// mytime0.cpp

#include <iostream>
#include "mytime0.h"

Time::Time()
{
	hours = minutes = 0;
}

Time::Time(int h, int m)
{
	hours = h;
	minutes = m;
}

void Time::AddMin(int m)
{
	minutes += m;
	hours += minutes / 60;
	minutes %= 60;
}

void Time::AddHr(int h)
{
	hours += h;
}

void Time::Reset(int h, int m)
{
	hours = h;
	minutes = m;
}

Time Time::Sum(const Time & t) const
{
	Time sum;
	sum.minutes = minutes + t.minutes;
	sum.hours = hours + t.hours + sum.minutes / 60;
	sum.minutes %= 60;
	return sum;
}

void Time::Show() const
{
	std::cout << hours << "시간, " << minutes << "분";
}

Sum() 함수에서는 새로운 Time 객체를 만들고, 이 함수를 호출한 함수가 해당 객체를 사용할 수 있도록 복사본 개체를 리턴함
이 때 만약 리턴형이 참조라면 참조는 생성된 객체 자신이 되지만 이는 지역변수이기 때문에 함수 종료시 파괴되어 참조가 어긋나게됨

// usetime0.cpp

#include <iostream>
#include "mytime0.h"

int main()
{
	using std::cout;
	using std::endl;
	Time planning;
	Time coding(2, 40);
	TIme fixing(5, 55);
	TIme total;

	cout << "planning time = ";
	planning.Show()
	cout << endl;

	cout << "coding time = ";
	coding.Show()
	cout << endl;

	cout << "fixing time = ";
	fixing.Show()
	cout << endl;

	total = coding.Sum(fixing);
	cout << "coding.Sum(fixing) = ";
	total.Show();
	cout << endl;
	return 0;
}

덧셈 연산자의 추가

// mytime1.h

#ifndef MYTIME1_H_
#define MYTIME1_H_

class Time
{
	private:
		int hours;
		int minutes;
	public:
		Time();
		Time(int h, int m = 0);
		void AddMin(int m);
		void AddHr(int h);
		void Reset(int h = 0, int m = 0);
		Time operator+(const Time & t) const;
		void Show() const;
};

#endif
// mytime1.cpp

#include <iostream>
#include "mytime1.h"

Time::Time()
{
	hours = minutes = 0;
}

Time::Time(int h, int m)
{
	hours = h;
	minutes = m;
}

void Time::AddMin(int m)
{
	minutes += m;
	hours += minutes / 60;
	minutes %= 60;
}

void Time::AddHr(int h)
{
	hours += h;
}

void Time::Reset(int h, int m)
{
	hours = h;
	minutes = m;
}

Time Time::operator+(const Time & t) const
{
	Time sum;
	sum.minutes = minutes + t.minutes;
	sum.hours = hours + t.hours + sum.minutes / 60;
	sum.minutes %= 60;
	return sum;
}

void Time::Show() const
{
	std::cout << hours << "시간, " << minutes << "분";
}

Sum()operator+()로 바꾸어 오버로딩된 덧셈 연산자를 사용

// usetime1.cpp

#include <iostream>
#include "mytime1.h"

int main()
{
	using std::cout;
	using std::endl;
	Time planning;
	Time coding(2, 40);
	Time fixing(5, 55);
	Time total;

	cout << "planning time = ";
	planning.Show();
	cout << endl;

	cout << "coding time = ";
	coding.Show();
	cout << endl;

	cout << "fixing time = ";
	fixing.Show();
	cout << endl;

	total = coding + fixing;
	cout << "coding + fixing = ";
	total.Show();
	cout << endl;

	Time morefixing(3, 28);
	cout << "more fixing time = ";
	morefixing.Show();
	cout << endl;
	total = morefixing.operator+(total);
	cout << "morefixing.operator+(total) = ";
	total.Show();
	cout << endl;

	return 0;
}

operator+()역시 객체에 의해 호출할 수 있으며, total = coding.operator+(fixing);과 같이 Sum()에서 쓰던 것과 동일한 문법을 사용할 수 있음
그러나 연산자 오버로딩을 활용하여 total = coding + fixing의 형식으로도 사용할 수 있음
이 때 연산자 왼쪽의 객체가 호출한 객체, 오른쪽 객체가 매개변수로 전달되는 객체가 됨

두 개 이상의 객체의 연산에도 적용 가능함
t4 = t1 + t2 + t3;t4 = t1.operator+(t2 + t3);와 같고, 이는 t4 = t1.operator+(t2.operator+(t3));와 동일함


오버로딩 제약

오버로딩된 연산자는 적어도 하나의 피연산자가 사용자 정의 데이터형일 것을 요구함

  • 즉, 표준 데이터형을 위해 사용되는 연산자들의 오버로딩을 방지함

오버로딩된 연산자를 원래의 연산자에 적용되는 문법 규칙을 위반하는 방식으로는 사용할 수 없음

  • 즉, 나머지 연산자 %를 하나의 피연산자에만 작용하도록 만들 수 없음
  • 연산자 우선순위 역시 원래의 연산자와 동일함

연산자 기호를 새로 만들 수 없음

  • 즉, 거듭제곱을 나타내기 위해 **를 정의할 수 없음

다음과 같은 연산자들은 오버로딩할 수 없음

  • sizeof : sizeof연산자, . : 멤버 연산자, .* : 멤버 지시 포인터 연산자
  • :: : 사용 범위 결정 연산자, ?: : 조건 연산자, typeid : RTTI 연산자
  • const_cast / dynamic_cast / reinterpret_cast / static_cast : 데이터형 변환 연산자

다음의 연산자들은 오버로딩 자체는 가능하나, 오버로딩시 멤버 함수로 정의되어야함

  • = : 대입 연산자, () : 함수 호출 연산자
  • [] : 배열 인덱스 연산자, -> : 클래스 멤버 접근 포인터 연산자

오버로딩으로 사용된 연산자가 어떤 역할을 하는지 직관적으로 떠오르지 않는다면, 보다 설명적인 이름을 가진 클래스 메소드로 정의하는 것이 권장됨


오버로딩 연산자 보충

// mytime2.h

#ifndef MYTIME2_H_
#define MYTIME2_H_

class Time
{
	private:
		int hours;
		int minutes;
	public:
		Time();
		Time(int h, int m = 0);
		void AddMin(int m);
		void AddHr(int h);
		void Reset(int h = 0, int m = 0);
		Time operator+(const Time & t) const;
		Time operator-(const Time & t) const;
		Time operator*(double n) const;
		void Show() const;
};

#endif
// mytime2.cpp

#include <iostream>
#include "mytime2.h"

Time::Time()
{
	hours = minutes = 0;
}

Time::Time(int h, int m)
{
	hours = h;
	minutes = m;
}

void Time::AddMin(int m)
{
	minutes += m;
	hours += minutes / 60;
	minutes %= 60;
}

void Time::AddHr(int h)
{
	hours += h;
}

void Time::Reset(int h, int m)
{
	hours = h;
	minutes = m;
}

Time Time::operator+(const Time & t) const
{
	Time sum;
	sum.minutes = minutes + t.minutes;
	sum.hours = hours + t.hours + sum.minutes / 60;
	sum.minutes %= 60;
	return sum;
}

Time Time::operator-(const Time & t) const
{
	Time diff;
	int tot1, tot2;
	tot1 = t.minutes + 60 * t.hours;
	tot2 = minutes + 60 * hours;
	diff.minutes = (tot2 - tot1) % 60;
	diff.hours = (tot2 - tot1) / 60;
	return diff;
}

Time Time::operator*(double mult) const
{
	Time result;
	long totalminutes = hours * mult * 60 + minutes * mult;
	result.hours = totalminutes / 60;
	result.minutes = totalminutes % 60;
	return result;
}

void Time::Show() const
{
	std::cout << hours << "시간, " << minutes << "분";
}
// usetime2.cpp

#include <iostream>
#include "mytime2.h"

int main()
{
	using std::cout;
	using std::endl;
	Time weeding(4, 35);
	Time waxing(2, 47);
	Time total;
	Time diff;
	Time adjusted;

	cout << "weeding time = ";
	weeding.Show();
	cout << endl;

	cout << "waxing time = ";
	waxing.Show();
	cout << endl;

	cout << "total work time = ";
	total = weeding + waxing;
	total.Show();
	cout << endl;

	diff = weeding - waxing;
	cout << "weeding time - waxing time = ";
	diff.Show();
	cout << endl;

	adjusted = total * 1.5;
	cout << "adjusted work time = ";
	adjusted.Show();
	cout << endl;

	return 0;
}


11.3 프렌드의 도입

C++은 클래스 객체의 private 부분에 접근하기 위해 public 클래스 메소드가 아닌 프렌드(friend)라는 또 하나의 접근 통로를 제공함

  • 프렌드는 프렌드 함수 / 클래스 / 멤버 함수의 세가지 형태로 사용됨
  • 함수를 어떤 클래스에 대한 프렌드로 만들시 해당 프렌드 함수는 클래스의 멤버 함수와 동등한 접근 권한을 가짐
  • 어떤 클래스에 대해 이항 연산자를 오버로딩할시 프렌드를 만들 필요성이 생김
    • 곱셈 오버로딩 연산자의 경우 A = B * 2.75;A = 2.75 * B;는 개념적으로 같아야하지만 2.75는 객체형이 아니기 때문에 A = B.operator*(2.75)로는 적용이 가능해도 그 반대는 가능하지 않음
    • 따라서 멤버가 아닌 함수를 사용해 객체 호출 없이 처리해야하지만, 멤버가 아닌 함수는 private의 데이터에 접근할 수 없기 때문에 프렌드가 필요해짐

프렌드 생성하기

프렌드 함수는 클래스 선언에 friend 키워드가 붙은 원형을 넣어 만듬

  • 즉, freind Time operator*(double m, const Time & t);의 형식으로 원형 선언
  • operator*() 함수는 클래스 선언 안에 선언되지만 멤버 함수가 아니며, 멤버 함수와 동등한 접근 권한을 가짐

프렌드 함수는 멤버 함수가 아니기 때문에 함수 정의에 Time::과 같은 클래스 제한자를 사용하지 않음

Time operator*(double m, const Time & t)
{
	Time result;
	long totalminutes = t.hours * m * 60 + t.minutes * m;
	result.hours = totalminutes / 60;
	result.minutes = totalminutes % 60;
	return result;
}

위와 같이 프렌드 함수가 정의되면 A = 2.75 * B;A = operator*(2.75, B);로 번역되어 프렌드 함수를 호출함

Time operator*(double m, const TIme & t)
{
	return t * m;
}

위와 같이 사용할경우 t.operator*(m)을 사용하여 프렌드가 아닌 함수로도 작성이 가능하지만, 프렌드로 만드는 것이 바람직함

  • 함수가 클래스의 공식 인터페이스의 일부로 결합되기 때문
  • 함수가 private 데이터에 직접 접근할 필요가 생길시 클래스 선언의 원형은 그대로 두고 함수 정의만 바꾸면 됨

프렌드 : « 연산자의 오버로딩

클래스에 << 연산자를 오버로딩하여 cout과 함께 사용해 객체의 내용을 출력할 수 있음
ostream 클래스 선언은 기본 데이터형 각각에 대해 오버로딩된 operator<<() 정의를 가지고있는 것처럼, cout이 클래스 객체를 인식하도록 하기 위해서는 ostream에 새로운 연산자 함수의 정의를 추가해야함
그러나 사용자가 직접 표준 인터페이스를 건드리는 것은 위험하기 때문에, 클래스에 cout을 사용하는 방법을 가르치는 것이 권장됨

void operator<<(ostream & os, const Time & t)
{
	os << t.hours << "시간, " << t.minutes << "분";
}

trip이라는 Time클래스의 객체가 있을 때, cout << trip;과 같은 구문에서는 ostream클래스 객체인 cout이 첫번째 피연산자로 인식되기 때문에 해당 구문을 사용하기 위해서는 프렌드 함수가 필요함
이 때 cout 객체는 객체의 복사본이 아닌 그 자체여야하므로 값이 아닌 참조로 전달함

ostream & operator<<(ostream & os, const Time & t)
{
	os << t.hours << "시간, " << t.minutes << "분";
	return os;
}

cout << "여행 일시 : " << trip << " (화요일)\n";과 같은 구문이 있을 경우 이전의 voidoperator<<()함수로는 정상 작동하지 않음
이는 C++에서의 출력 구문이 왼쪽에서부터 읽히며, 따라서 이 구문은 ((cout << "여행 일시 : ") << trip) << " (화요일)\n";과 같음
ostream 클래스의 operator<< 함수가 ostream 객체를 리턴하기 때문에 cout << "여행 일시 : "의 경우 하나의 객체가 되어 뒤에 오는 << trip도 정상적으로 작동됨
따라서 프렌드 함수로 지정한 operator<<()ostream 객체에 대한 참조를 리턴하도록 만들어야함

// mytime3.h

#ifndef MYTIME3_H_
#define MYTIME3_H_
#include <iostream>

class Time
{
	private:
		int hours;
		int minutes;
	public:
		Time();
		Time(int h, int m = 0);
		void AddMin(int m);
		void AddHr(int h);
		void Reset(int h = 0, int m = 0);
		Time operator+(const Time & t) const;
		Time operator-(const Time & t) const;
		Time operator*(double n) const;
		friend Time operator*(double m, const Time & t)
			{ return t * m; };
		friend std::ostream & operator<<(std::ostream & os, const Time & t);
};

#endif
// mytime3.cpp

#include "mytime3.h"

Time::Time()
{
	hours = minutes = 0;
}

Time::Time(int h, int m)
{
	hours = h;
	minutes = m;
}

void Time::AddMin(int m)
{
	minutes += m;
	hours += minutes / 60;
	minutes %= 60;
}

void Time::AddHr(int h)
{
	hours += h;
}

void Time::Reset(int h, int m)
{
	hours = h;
	minutes = m;
}

Time Time::operator+(const Time & t) const
{
	Time sum;
	sum.minutes = minutes + t.minutes;
	sum.hours = hours + t.hours + sum.minutes / 60;
	sum.minutes %= 60;
	return sum;
}

Time Time::operator-(const Time & t) const
{
	Time diff;
	int tot1, tot2;
	tot1 = t.minutes + 60 * t.hours;
	tot2 = minutes + 60 * hours;
	diff.minutes = (tot2 - tot1) % 60;
	diff.hours = (tot2 - tot1) / 60;
	return diff;
}

Time Time::operator*(double mult) const
{
	Time result;
	long totalminutes = hours * mult * 60 + minutes * mult;
	result.hours = totalminutes / 60;
	result.minutes = totalminutes % 60;
	return result;
}

std::ostream & operator<<(std::ostream & os, const Time & t)
{
	os << t.hours << "시간, " << t.minutes << "분";
	return os;
}
// usetime3.cpp

#include "mytime3.h"

int main()
{
	using std::cout;
	using std::endl;
	Time aida(3, 35);
	Time tosca(2, 48);
	Time temp;

	cout << "Aida와 Tosca:\n";
	cout << aida << "; " << tosca << endl;
	temp = aida + tosca;
	cout << "Aida + Tosca : " << temp << endl;
	temp = aida * 1.17;
	cout << "Aida * 1.17 : " << temp << endl;
	cout << "10 * Tosca : " << 10 * tosca << endl;
	return 0;
}


11.4 오버로딩 연산자 : 멤버 함수와 멤버가 아닌 함수

연산자 오버로딩 구현시

  • 멤버 함수로 구현 : this 포인터를 통해 하나의 피연산자가 암시적으로 전달되고, 나머지 하나는 함수 매개변수로 명시적으로 전달됨
  • 멤버가 아닌 함수로 구현 : 일반적으로는 프렌드 함수, 피연산자 둘 다를 매개변수로 전달

두 형식은 동일한 표현식에 해당하므로 두 형식 모두로 정의할 경우 모호성 에러로 간주되어 컴파일 에러가 발생함
일부 연산자들에 대해서는 멤버함수가 유일하면서도 적절한 선택임
그 밖의 경우에는 별다른 차이가 없지만, 클래스 설계에 따라서는 데이터형 변환을 정의했을 경우 멤버가 아닌 버전이 유리할 수 있음



11.5 오버로딩 보충 : Vector 클래스

여기서의 Vector 클래스는 vector 템플릿 클래스와는 다름

벡터를 표현할 때는 연산자 오버로딩을 사용함

  • 벡터는 하나의 값으로 나타낼 수 없기 때문에 벡터를 표현하는 클래스를 정의해야함
  • 벡터 연산은 덧셈, 뺄셈 등의 일반적인 산술 연산과 비슷하기 때문에 연산자 오버로딩이 제안됨
// vect.h

#ifndef VECTOR_H_
#define VECTOR_H_
#include <iostream>

namespace VECTOR
{
	class Vector
	{
		public:
			enum Mode {RECT, POL};
		private:
			double x;
			double y;
			double mag;
			double ang;
			Mode mode;
			void set_mag();
			void set_ang();
			void set_x();
			void set_y();
		public:
			Vector();
			Vector(double n1, double n2, Mode form = RECT);
			void reset(double n1, double n2, Mode form = RECT);
			~Vector();
			double xval() const {return x;};
			double yval() const {return y;};
			double magval() const {return mag;};
			double angval() const {return ang;};
			void polar_mode();
			void rect_mode();
			Vector operator+(const Vector & b) const;
			Vector operator-(const Vector & b) const;
			Vector operator-() const;
			Vector operator*(double n) const;
			friend Vector operator*(double n, const Vector & a);
			friend std::ostream & operator<<(std::ostream & os, const Vector & v);
	};
}

#endif
// vect.cpp

#include <cmath>
#include "vect.h"

using std::sqrt;
using std::sin;
using std::cos;
using std::atan;
using std::atan2;
using std::cout;

namespace VECTOR
{
	const double Rad_to_deg = 45.0 / atan(1.0);

	void Vector::set_mag()
	{
		mag = sqrt(x * x + y * y);
	}

	void Vector::set_ang()
	{
		if (x == 0.0 && y == 0.0)
			ang = 0.0;
		else
			ang = atan2(y, x);
	}

	void Vector::set_x()
	{
		x = mag * cos(ang);
	}

	void Vector::set_y()
	{
		y = mag * sin(ang);
	}

	Vector::Vector()
	{
		x = y = mag = ang = 0.0;
		mode = RECT;
	}

	Vector::Vector(double n1, double n2, Mode form)
	{
		mode = form;
		if (form == RECT)
		{
			x = n1;
			y = n2;
			set_mag();
			set_ang();
		}
		else if (form == POL)
		{
			mag = n1;
			ang = n2 / Rad_to_deg;
			set_x();
			set_y();
		}
		else
		{
			cout << "Vector()에 전달된 제3의 매개변수가 잘못되었다.\n";
			cout << "그래서 벡터를 0으로 설정하였다.\n";
			x = y = mag = ang = 0.0;
			mode = RECT;
		}
	}

	void Vector::reset(double n1, double n2, Mode form)
	{
		mode = form;
		if (form == RECT)
		{
			x = n1;
			y = n2;
			set_mag();
			set_ang();
		}
		else if (form == POL)
		{
			mag = n1;
			ang = n2 / Rad_to_deg;
			set_x();
			set_y();
		}
		else
		{
			cout << "Vector()에 전달된 제3의 매개변수가 잘못되었다.\n";
			cout << "그래서 벡터를 0으로 설정하였다.\n";
			x = y = mag = ang = 0.0;
			mode = RECT;
		}
	}

	Vector::~Vector()
	{
	}

	void Vector::polar_mode()
	{
		mode = POL;
	}

	void Vector::rect_mode()
	{
		mode = RECT;
	}

	Vector Vector::operator+(const Vector & b) const
	{
		return Vector(x + b.x, y + b.y);
	}

	Vector Vector::operator-(const Vector & b) const
	{
		return Vector(x - b.x, y - b.y);
	}

	Vector Vector::operator-() const
	{
		return Vector(-x, -y);
	}

	Vector Vector::operator*(double n) const
	{
		return Vector(n * x, n * y);
	}
	
	Vector operator*(double n, const Vector & a)
	{
		return a * n;
	}

	std::ostream & operator<<(std::ostream & os, const Vector & v)
	{
		if (v.mode == Vector::RECT)
			os << "(x,y) = (" << v.x << ", " << v.y << ")";
		else if (v.mode == Vector::POL)
		{
			os << "(m,a) = (" << v.mag << ", "
			   << v.ang * Rad_to_deg << ")";
		}
		else
			os << "Vector 객체의 모든 지정이 틀렸습니다.\n";
		return os;
	}
}

상태 멤버 사용하기

Vector 클래스는 직각 좌표와 극 좌표를 둘 다 저장하기 때문에, 둘 중 어떤 형식을 사용할지를 결정하기 위해 mode 멤버를 사용함
mode 멤버가 RECT이면 직각 좌표 / POL이면 극 좌표를 나타냄으로써 클래스에서 사용할 모드를 변경
이렇게 객체의 상태를 나타내는 멤버를 상태 멤버(state member)라고 부름

클래스는 여타 다른 표기 방식들을 가능케 하는 변환들을 내부적으로 처리함으로써, 본질적인 의미에만 집중하면 되므로 편리함


Vector 클래스를 위한 산술 연산자 오버로딩

Vector Vector::operator+(const Vecotr & b) const
{
	vector sum;
	sum.x = x + b.x;
	sum.y = y + b.y;
	sum.set_ang(sum.x, sum.y);
	sum.set_mag(sum.x, sum.y);
	return sum;
}

Vector Vector::operator+(const VEctor & b) const
{
	return Vector(x + b.x, y + b.y);
}

위와 같은 코드를 사용할 수도 있으나, 아래와 같이 생성자를 사용하는 것이 훨씬 더 간단하고 신뢰성있음

뺄셈 연산자와 같이 이항 연산자(binary operator), 단항 연산자(unary operator) 두가지 경우로 사용되는 연산자의 경우에도 오버로딩이 가능함
operator-() 함수가 서로 다른 시그내처를 사용하도록 정의하여 벡터의 이항 연산과 단항 연산 버전을 둘 다 정의할 수 있음
즉, 연산자 오버로딩은 함수를 통해 구현되기 때문에 각각의 함수가 서로 구별되는 시그내처를 사용하고 C++에 내장된 해당 연산자가 요구하는 피연산자의 개수와 동일한 개수의 피연산자를 요구한다면 같은 연산자 함수를 여러번 오버로딩 할 수 있음


구현에 대한 보충 설명

현재 구현한 Vector 클래스는 객체 안에 직각 좌표와 극 좌표를 모두 저장함
이를 객체에 x, y 성분만 저장하도록 변경할 수 있으며 이 경우에 메소드들의 세부 구현이 변경되지만 사용자 인터페이스는 그대로 유지됨
즉, 클래스를 사용하는 프로그램의 코드는 변경하지 않고 세부 구현을 미세하게 조정할 수 있음
이와 같이 구현과 인터페이스를 분리하는 것이 OOP의 목표 중 하나임

데이터가 더 많이 저장될수록 더 많은 메모리를 차지하고, 객체가 변경될 때마다 코드가 직접 데이터들을 갱신해주어야함을 의미함
대신 데이터 룩업(lookup) 속도가 빨라지기 때문에 형식에 모두 접근할 필요가 있다면 이 방식이 더 바람직함
반대로 어떠한 형식의 데이터가 드물게 사용된다면 데이터를 저장하기보다는 데이터를 직접 계산하는 구현 방식이 더 유용할 수 있음


Vector 클래스와 마구잡이 걸음

// randwalk.cpp

#include <iostream>
#include <cstdlib>
#include <ctime>
#include "vect.h"

int main()
{
	using namespace std;
	using VECTOR::Vector;
	srand(time(0));
	double direction;
	Vector step;
	Vector result(0.0, 0.0);
	unsigned long steps = 0;
	double target;
	double dstep;

	cout << "목표 거리를 입력하십시오(끝내려면 q) : ";
	while (cin >> target)
	{
		cout << "보폭을 입력하십시오 : ";
		if (!(cin >> dstep))
			break;
		
		while (result.magval() < target)
		{
			direction = rand() % 360;
			step.reset(dstep, direction, Vector::POL);
			result = result + step;
			steps++;
		}
		cout << steps << " 걸음을 걸은 후 술고래의 현재 위치 : \n";
		cout << result << endl;
		result.polar_mode();
		cout << " 또는\n" << result << endl;
		cout << "걸음당 기둥에서 벗어난 평균 거리 = "
			 << result.magval()/steps << endl;
		steps = 0;
		result.reset(0.0, 0.0);
		cout << "목표 거리를 입력하십시오(끝내려면 q) : ";
	}

	cout << "프로그램을 종료합니다.\n";
	cin.clear();
	while (cin.get() != '\n')
		continue;
	return 0;
}

using VECTOR::Vector;으로 클래스 이름을 사용 범위 안으로 불러들였기 때문에 Vector 클래스의 모든 메소드들을 다른 선언 추가 필요없이 사용할 수 있음

rand() : 특정 값의 범위에 속하는 하나의 무작위 정수를 리턴하는 함수

  • 단, rand()로 얻어진 값이 다음번 rand()호출의 씨앗값으로 사용되므로 완전한 의미의 무작위는 아님
  • 디폴트 씨앗값을 무시하고싶다면 srand()함수를 사용하여 다른 난수 집합을 발생시킬 수 있음

time() : time_t형 변수의 주소를 취해 해당 변수에 시간(특정 지정일로부터 경과한 현재의 달력 시간을 초 단위 수로 나타낸 시간)을 저장하여 리턴함

result = result + step;의 경우 덧셈 연산자 함수에서 새로운 벡터를 생성하여 리턴하는데, 이 때 디폴트 생성자에서의 모드가 r로 디폴트 설정되어있기 때문에 result에 대입되는 벡터는 항상 r 모드임

#include <fstream>
...
ofstream fout;
fout.open("thewalk.txt");
fout << result << endl;

위 코드를 사용하면 파일에 결과값들을 저장할 수 있음



11.6 자동 변환과 클래스의 데이터형 변환

C++에서는 표준 데이터형의 경우 두 데이터형이 서로 호환될 때, 그 값의 데이터형을 대입받는 입장에 있는 변수와 동일한 데이터형으로 자동 변환됨
호환되지 않는 데이터형은 자동으로 변환되지 않으며, 강제 데이터형 변환을 이용할 수는 있음

클래스 역시 기본 데이터형이나 다른 클래스 사이간에 변환할 수 있는 클래스를 정의할 수 있으며, 자동 변환과 강제 변환중 어떠한 것을 사용할지 사용자가 지시할 수 있음

// stonewt.h

#ifndef STONEWT_H_
#define STONEWT_H_

class Stonewt
{
	private:
		enum {Lbs_per_stn = 14};
		int stone;
		double pds_left;
		double pounds;
	public:
		Stonewt(double lbs);
		Stonewt(int stn, double lbs);
		Stonewt();
		~Stonewt();
		void show_lbs() const;
		void show_stn() const;
};

#endif
// stonewt.cpp

#include <iostream>
using std::cout;
#include "stonewt.h"

Stonewt::Stonewt(double lbs)
{
	stone = int(lbs) / Lbs_per_stn;
	pds_left = int(lbs) % Lbs_per_stn + lbs - int(lbs);
	pounds = lbs;
}

Stonewt::Stonewt(int stn, double lbs)
{
	stone = stn;
	pds_left = lbs;
	pounds = stn * Lbs_per_stn + lbs;
}

Stonewt::Stonewt()
{
	stone = pounds = pds_left = 0;
}

Stonewt::~Stonewt()
{
}

void Stonewt::show_stn() const
{
	cout << stone << "스톤, " << pds_left << "파운드\n";
}

void Stonewt::show_lbs() const
{
	cout << pounds << "파운드\n";
}
// stone.cpp

#include <iostream>
using std::cout;
#include "stonewt.h"

void display(const Stonewt & st, int n);

int main()
{
	Stonewt pavarotti = 275;
	Stonewt wolfe(285.7);
	Stonewt taft(21, 8);

	cout << "테너 가수의 몸무게 : ";
	pavarotti.show_stn();
	cout << "탐정의 몸무게 : ";
	wolfe.show_stn();
	cout << "대통령의 몸무게 : ";
	taft.show_lbs();
	pavarotti = 276.8;
	taft = 325;
	cout << "저녁 식사를 마친 후 테너 가수의 몸무게 : ";
	pavarotti.show_stn();
	cout << "저녁 식사를 마친 후 대통령의 몸무게 : ";
	taft.show_lbs();
	display(taft, 2);
	cout << "레슬링 선수는 그보다 더 무겁다.\n";
	display(422, 2);
	cout << "비만은 건강의 최대의 적이다.\n";
	return 0;
}

void display(const Stonewt & st, int n)
{
	for (int i = 0; i < n; i++)
	{
		cout << "와! ";
		st.show_stn();
	}
}
Stonewt(double lbs);
Stonewt myCat;
myCat = 19.6;

Stonewt(double lbs)doubleStonewt 객체로 변환하는 명령으로 사용됨
Stonewt 임시 객체를 생성한 후, 19.6을 초기화값으로 사용함
이후 멤버별 대입으로 임시 객체의 내용이 myCat 객체에 복사됨
이와같이 하나의 매개변수만 사용할 수 있는 생성자의 경우 아래와 같이 암시적 데이터형 변환이 가능

단, 이러한 자동 데이터형 변환은 예상하지 못한 오류를 일으킬 수 있으므로 explicit 키워드를 사용하여 금지할 수 있음

  • explicit Stonewt(double lbs);의 형태로 사용
  • explicit을 사용하더라도 명시적 데이터형 강제 변환은 허용됨

Stonewt(double) 함수의 경우 암시적 데이터형 변환이 일어날 때마다 사용됨

  • Stonewt 객체를 double형으로 초기화할 때
  • Stonewt 객체에 double형 값을 대입할 때
  • Stonewt형 매개변수를 기대하는 함수에 double형 값을 전달할 때
  • Stonewt형 값을 리턴하도록 선언된 함수가 double형 값을 리턴하려고 시도할 때
  • 앞의 네 상황에서 double형 대신 모호하지 않게 double형으로 변환할 수 있는 내장 데이터형을 사용할 때
    • 즉, Stonewt Jumbo(7000);의 경우 int값인 7000이 dobule로 변환된 뒤 Stonewt(double) 함수가 사용됨
    • 단, 모호하지 않아야 하기에 만약 Stonewt(long) 생성자가 정의되어있었다면 intdouble로 변환할지 long으로 변환할지 모호하기 때문에 컴파일러가 구문 수행을 거부함

변환 함수

Stonewt wolfe(285.7);
double host = double(wolfe);
double thinker = (double)wolfe;

클래스 객체를 다른 데이터형으로 변환하는 것도 가능함

  • 이 때는 생성자를 사용하지 않고, 변환 함수(conversion function)이라고 부르는 특별한 형태의 C++ 연산자 함수를 사용함
  • 사용자 정의 강제 데이터형 변환이므로 일반적인 강제 데이터형 변환(type cast)처럼 사용

변환함수는 operator typeName();의 형식으로 사용함

  • 변환 함수는 클래스의 메소드여야함
  • 변환 함수는 리턴형을 가질 수 없음
  • 변환 함수는 매개변수를 가질 수 없음
// stonewt1.h

#ifndef STONEWT1_H_
#define STONEWT1_H_

class Stonewt
{
	private:
		enum {Lbs_per_stn = 14};
		int stone;
		double pds_left;
		double pounds;
	public:
		Stonewt(double lbs);
		Stonewt(int stn, double lbs);
		Stonewt();
		~Stonewt();
		void show_lbs() const;
		void show_stn() const;
		operator int() const;
		operator double() const;
};

#endif
// stonewt1.cpp

#include <iostream>
using std::cout;
#include "stonewt1.h"

Stonewt::Stonewt(double lbs)
{
	stone = int(lbs) / Lbs_per_stn;
	pds_left = int(lbs) % Lbs_per_stn + lbs - int(lbs);
	pounds = lbs;
}

Stonewt::Stonewt(int stn, double lbs)
{
	stone = stn;
	pds_left = lbs;
	pounds = stn * Lbs_per_stn + lbs;
}

Stonewt::Stonewt()
{
	stone = pounds = pds_left = 0;
}

Stonewt::~Stonewt()
{
}

void Stonewt::show_stn() const
{
	cout << stone << "스톤, " << pds_left << "파운드\n";
}

void Stonewt::show_lbs() const
{
	cout << pounds << "파운드\n";
}

Stonewt::operator int() const
{
	return int (pounds + 0.5);
}

Stonewt::operator double() const
{
	return pounds;
}
// stone1.cpp

#include <iostream>
#include "stonewt1.h"

int main()
{
	using std::cout;
	Stonewt poppins(9, 2.8);
	double p_wt = poppins;
	
	cout << "double형으로 변환 => ";
	cout << "Poppins : " << p_wt << "파운드\n";
	cout << "int형으로 변환 => ";
	cout << "Poppins : " << int (poppins) << "파운드\n";
	return 0;
}

명시적인 강제 데이터형 변환을 생략했을시, 컴파일러는 이를 double형 또는 int형 둘 모두로 변환할 수 있는 모호한 상황이 되기 때문에 에러가 발생함
단, 만약 변환 함수의 종류가 한가지 뿐이라면 모호하지 않으므로 정상적으로 컴파일됨
따라서 클래스에 두개 이상의 변환 함수를 정의했을 시에는 명시적 강제 데이터형 변환을 사용해야함

암시적 변환은 원하지 않을 때에도 마음대로 수행될 수 있기 때문에 명시적 변환을 사용하는 것이 권장됨
C++11에서는 explicit 키워드를 변환 함수에도 사용할 수 있음


변환과 프렌드

double형 값을 Stonewt형 값에 더할 경우

  • operator+(const Stonewt &, const Stonewt &)를 프렌드 함수로 정의한 후 Stonewt(double) 생성자를 사용하여 double형 매개변수를 Stonewt형 매개변수로 변환함
    • 함수를 더 적게 정의하므로 프로그램이 짧아짐
    • 프로그래머가 혼란을 만들어 낼 위험성이 적음
    • 변환시 변환 생성자를 호출해야하므로 시간과 메모리 부담이 있음
  • Stonewt operator+(double x);, freind Stonewt operator+(double x, Stonewt & s); 두가지 함수로 덧셈 연산자를 한번 더 오버로딩함
    • 프로그램이 길어지고, 프로그래머가 할 일이 더 많아짐
    • 실행 속도가 빠르고 메모리 부담이 적음
  • 따라서 이러한 경우(double형 값을 Stonewt형 객체에 더하는 일)이 자주 발생한다면 후자를 선택하여 명시적 변환을, 드물게 발생한다면 전자를 선택하여 자동 변환에 의존하는 것이 간단함


연습문제

  1.  Stonewt operator*(double a);
    
     Stonewt operator*(double a)
     {
         return Stonewt(a * pounds);	
     }
    
  2. 멤버 함수 : 클래스 정의의 일부로 객체에 의해 호출됨
    멤버 연산자를 사용하지 않고도 호출한 객체에 암시적으로 접근 가능
    프렌드 함수 : 클래스의 일부가 아니므로 일반적인 함수 호출 방식으로 호출함
    클래스 멤버에 암시적으로 접근할 수 없기 때문에 멤버 연산자를 사용해야 함

  3. private 멤버에 접근하기 위해서는 그래야 하지만, public 멤버에는 프렌드가 아니더라도 접근할 수 있음

  4.  friend Stonewt operator*(double a, const Stone & b);
    
     Stonewt operator*(double a, const Stone & b)
     {
         retrun Stonewt(a * b.pounds);
     }
    
  5. sizeof, ., .*, ::, ?:, typeid, const_cast, dynamic_cast, reinterpret_cast, static_cast

  6. 멤버 함수로 정의되어야함

  7.  operator double() const;
    
     Vector::operator double() const
     {
         return mag;
     }
    

    또는

     operator double() { return mag; };
    

Tags:

Categories:

Updated: