카테고리 없음

코딩연습 #2 오퍼레이터 추가 연습

마쿠어 2023. 5. 8. 20:50
#include <iostream>
using namespace std;

class Complex
{
        public:
                Complex(float r=0.0, float i=0.0): re(r), im(i) {}
                Complex operator +(Complex &c2) { return Complex(re + c2.re, im + c2.im);}
                Complex operator -()
                {
                        Complex tmp;
                        tmp.re = -re;
                        tmp.im = -im;
                        return tmp;
                }
                void Print() { cout << re << " + " << im << " i \n"; }
        private:
                float re, im;
};

int main()
{
        Complex com1(1.0, 2.0), com2(3.0, 4.0);
        Complex com3 = com1 + com2;
        Complex com4 = -com1;
        cout << com1 << com2 << com3 << com4;
}

위 코드는 작업 중에 의문점이 들어서 메모 차원에서 올려본 코드이다. 의문이 들은 부분은 operator 과정에서 나는 본래 8번 줄과 같이 써 왔는데, 과제에서는 아래 operator -와 같이 tmp를 활용해서 반환값을 정하고 있었다. 

[위 코드는 작동하지 않는 오류 투성이 코드...]

 

우선 수정한 코드는 아래와 같다

#include <iostream>
using namespace std;

class Complex
{
        public:
                Complex(float r=0.0, float i=0.0): re(r), im(i) {}
                Complex operator +(const Complex &c2) { return Complex(re + c2.re, im + c2.im);}
                Complex operator -() { re = -re; im = -im; return *this; }
                friend ostream& operator<<(ostream& os, const Complex& c)
                        {
                                if(c.im >= 0)
                                        { os << c.re << " + " << c.im << "i \n "; }
                                else
                                        { os << c.re << " " << c.im << "i \n "; }
                                return os;
                        }
        private:
                float re, im;
};

int main()
{
        Complex com1(1.0, 2.0), com2(3.0, 4.0);
        Complex com3 = com1 + com2;
        Complex com4 = -com1;
        cout << com1 << com2 << com3 << com4;
}

위 코드도 완벽하지는 않아서 최종 코드는 아래와 같다

#include <iostream>
using namespace std;

class Complex
{
        public:
                Complex(float r=0.0, float i=0.0): re(r), im(i) {}
                Complex operator +(const Complex &c2) { return Complex(re + c2.re, im + c2.im);}
                Complex operator -() const
                         {
                                Complex tmp;
                                tmp.re = -re;
                                tmp.im = -im;
                                return tmp;
                         }
                friend ostream& operator<<(ostream& os, const Complex& c)
                        {
                                if(c.im >= 0)
                                        { os << c.re << " + " << c.im << "i \n "; }
                                else
                                        { os << c.re << " " << c.im << "i \n "; }
                                return os;
                        }
        private:
                float re, im;
};

int main()
{
        Complex com1(1.0, 2.0), com2(3.0, 4.0);
        Complex com3 = com1 + com2;
        Complex com4 = -com1;
        cout << com1 << com2 << com3 << com4;
}

 

아래서 cout << com1의 형태이므로 <<에 대한 operator를 추가했으며, 이 단계에서도 오류가 발생했는데 friend를 통해서 문제를 해결하였다.

 

그렇다면 왜 friend가 필요했을까?

 

추측1 ) operator <<는 클래스 내부에서 별도로 선언한 함수라기보다는, 외부에 있는 객체를 가져와서 쓰는 함수이다 (&ostream). 즉, 클래스 내부에 있는 함수가 아니므로, 이 함수의 경우 private에 대해서 접근할 권한이 없는 것이다. 따라서 friend를 선언하지 않았을 경우, im, re 값이 정상적으로 전달되지 않음을 예상할 수 있다.

 

어쨋든 해당 코드를 작성하면서 가진 의문점은 크게 2가지이다.

 

1. operator +, - 에서 tmp를 사용하는 것과 사용하지 않는 것에는 무슨 차이가 있을까?

2. operator << 에서 별도로 friend를 선언하는 이유는 무엇이었을까?

 

<매우 중요> 1.

tmp를 사용하지 않을 경우, Complex com4 = -com1 의 연산 과정에서 com1에도 영향이 가서, com1 또한 -1 -2i의 값이 출력되게 된다. 이러한 의도치 않은 변경을 방지하고자, 임시 객체인 tmp를 연산 과정에 추가하는 것이다. const를 활용해서 이러한 의도치 않은 변경을 방지하는 방법도 있지만, 위와 같은 경우에는 반드시 tmp를 사용하는 것이 중요하다.

 

<매우 중요>2.

operator <<의 형태를 유심히 보면

friend ostream& operator<<(ostream& os, const Complex& c)

우선 operator<<는 애초에 Complex의 멤버 함수가 아니라 iostream 라이브러리의 객체인 ostream의 멤버함수 임을 알 수 있다. 따라서 Complex의 public에 선언이 되었다 할지라도, operator <<는 외부함수이다. 따라서 private에 접근하기 위해서는 반드시 friend 선언을 해줘야 하는 것이다.

 

[추가로 위의 경우를 보면 ostream&를 활용해서 객체의 참조를 보이고 있다. 이것이 무엇을 의미하는지 알아보자]

ostream&는 외부 객체인 os를 사용하기 위해서 선언한 외부 객체이다. 더군다나 반환하는 값은 ostream의 객체가 되어야 하기에 &ostream, 즉 클래스의 참조를 하고 있는 것이다.

추가로 아래에서 이 부분은 cout에 해당한다.

 

 

~~~~~~~~

다음은 문자열 관련한 문제이다. 다음의 코드에서 mathscore를 operator로 바꾸는 과정이다.

 

#include <iostream>
using namespace std;

class CMath
{
        public:
                CMath(int c, string *n, int *m) : count(c), name(n), math(m) {}
                int mathscore(string n)
                {
                        for(int i=0; i<count; i++)
                                if(n==name[i]) return math[i];
                        return 0;
                }
        private:
                string *name;
                int *math;
                int count;
};

int main(void)
{
        string name[]= {"Kim", "Park", "Lee", "Chong", "Byun" };
        int math[] = { 40, 50, 60, 70, 80};
        CMath cm(5, name, math);
        cout << cm.mathscore("Lee") << endl;
        cout << cm.mathscore("Pyo") << endl;
}

위에서 int mathscore 를 int operator[]로 작동해도 동일하게 작동한다. 어떤 원리에 의해서 가능할까?

1. 전체적으로 클래스 구성 방식

메인 함수에서는 우선 string name[]을 통해서 배열에 각각 string 형태로 이름을 저장한다. 이후, int math[]를 통해서 다시 배열 위치에 점수를 저장한다.

이후 메인 함수에 저장된 각각의 배열 name 과 math를 CMath cm(5, name, math)를 통해서 객체를 만드는 것인데, 이 경우 count 값[원소의 수-학생의 수]를 직접 입력해주었다. ~> 자동적으로 이 값을 늘릴 수 있는 방법도 있을 것

 

중요한 것은 CMath 에서 받는 name과 math 부분에 유의해야 한다. 배열을 받는 것이므로 주소값을 전달 받음에 유의해야한다.

 

<배열의 전달 방식>

 

먼저 유의해야할 것은, 배열의 형태이다. name은 string 형태로 배열을 저장하고 있으며, math의 경우 int형 배열이다. 따라서 전달 받는 부위도 이에 맞춰야 한다. 동시에 배열의 이름 ( ex - arr)은 arr[0]의 주소와 같으며 하나의 값이 아니라 주소의 형태로 전달한다. 따라서 전달 받는 함수에서도 각 형태에 맞는 '주소값'의 형태로 전달받아야 한다. 따라서 위의 코드에서도 각각 string *, int * 형태로 전달 받음을 유의해야한다.

 

 

 

2. mathscore 함수의 실행 내용

1- 전달 받는 내용은 string 즉, 이름을 받는다

2- 이후 for 를 통해서 전달 받은 이름[string 형 배열]이 몇번째 사람인지 확인하는 작업을 거치고

3- 그에 맞는 사람의 수학 점수를 반환 하는 형식으로 함수가 구성되어 있다.

 

결국은 operator로 바꾼다는 것은 기존의 cm.mathscore를 cm[]의 형태로 바꿀 수 있는 것인가? 이다. 지금까지 해왔던 operator는 주로 기본 연산에 사용되는 기호의 역할에 조건을 더하는 정도이다. +, -, * 등 이미 많이 사용하는 연산자였으나, 이번 과제의 경우 []를 사용함과 동시에, 그 사이에 전달하고자 하는 값인 "Lee" 가 들어가고 있다.

 

이전의 operator +와 같은 경우, 메인 함수에서

com1 + com2 와 같이 사용된다 -> 즉, 우리가 흔히 사용하는 + 의 형태로 사용되고 있다

그렇다면 [] 연산자에 대해서도 우리가 흔히 사용하듯이 [ 전달하고픈 대상 ] 의 형태로 사용될 것이고, 이번 코드에서도 그렇게 사용 되었다.

 

이러한 차이가 나는 이유는 operator 상에 사용되는 기호들은 모두 c++ 문법 하에 작성되어야 한다는데에 있다. 흔히 + 와 - 와 같은 연산자는 '이항 연산자' 라고 하여 2개 대상 사이에 위치하는 것이 대부분이며, 단항 연산자들은 각자의 위치에 맞게 연산자가 배치가 되어야 한다. 따라서 [] 의 경우도 단항 연산자에 해당하여서 위와 같은 문법 형태를 갖게 된다는 c++의 문법 하에 해당 형태를 띄게 되는 것이다.

 

 

<최종>

따라서 operator []의 경우는 전달하고자 하는 값을 [] 사이에 넣어서 전달하게 되며, 앞에 놓이는 값이 호출 객체가 되며, [] 사이에 놓이는 값이 전달객체가 되는 관계를 이해해야 한다. 이후로는 기존의 cm.mathscore와 동일하게 작동된다.(사실 그냥 똑같다 형태가 다를 뿐)