1. 컴파일 환경
OS는 Windows 7, Visual Studio 2015 (_MSC_VER==1900)에서 코딩 및 컴파일했다.
2. 연산자 설정
통상적인 계산이 아닌 @, #, &이라는 특수한 연산 기호를 사용해 a, b, c를 처리하는 것이므로 연산 자체를 어떻게 처리할 것인지부터 생각해야 했다. 그래서 내린 결론은 ‘전역으로 각각 operationAt, operationSharp, operationAnd라는 char형 배열을 정의해 연산 결과를 매칭시킨다’였다. 이를 그림으로 표현하면 아래와 같다.
이렇게 구현한다면 연산하는 과정 없이 그대로 매칭만 시켜주면 된다. 그래서 전역으로 각각 연산자마다의 연산 결과를 담은 배열을 정의했다.
계산기가 동작하기 전에, 연산자들(@, #, &)의 연산 결과를 operations.txt에서 받아와서 등록하는 과정이 필요하다. 그래서 ifstream을 활용해 파일을 읽어내 각각 배열에 대입해주었다.
위와 같이 정의하고 계산 기능을 하는 코드가 실행되기 전에 setOperation 함수를 호출한 후, 전역에 정의된 배열의 상태를 보면 operations.txt의 data와 일치하게 설정된 것을 볼 수 있다.
이것으로 연산자의 연산 처리에 대한 설정은 끝마쳤다.
3. 계산
이제 입력 받은 수식을 계산해야 한다. 입력은 cin으로 받아 string형 vector eq에 넣고, “EOI”를 입력 받으면 입력을 종료한다.
그리고 eq에 있는 요소를 하나씩, 계산하는 함수 Calculate에 넘겨준다.
Calculate 함수는 다음과 같이 이루어져있다.
isValidEq 함수에서 true를 받으면 수식이 유효하다는 의미이다. 유효할 경우는 postfix의 길이와 같은 크기로 공간을 동적으로 할당받아 그 공간에 postfix로 변환된 수식을 저장하고, 변환된 수식을 가지고 실제 계산을 진행한다.
A. 수식의 유효성 검사
Calculate 함수는 입력 받은 수식의 연산 결과를 출력한다. 하지만 연산을 시행해 결과를 내기 전에, 입력 받은 수식이 유효한지부터 검사해야 한다. 잘못된 수식을 즉시 연산하다가는 오류가 발생할 수 있기 때문이다.
수식의 유효성을 검사하기 위해서 isValidEq 함수를 정의했다.
유효성 검사 코드는 수식 자체가 유효한지 더 자세히 검사한다. 즉, 괄호를 제대로 썼는지, 연산자를 연속해서 사용하지는 않았는지, 연산자로 수식이 시작하거나 끝나지는 않았는지, 그리고 연산자에 피연산자를 제대로 작성했는지이다.
좌측 괄호가 들어오면 스택에 push해놓고, 우측 괄호가 들어오면 괄호의 유효성을 검사한다. 만약 스택이 비어있으면 당연히 괄호의 쌍이 맞지 않는다는 것이므로 false를 return한다. 그렇지 않을 경우에는 우리가 비교하고자 하는 것은 스택에서 가장 위에 있는 좌측 괄호이다. 스택의 맨 위에 있는 괄호가 알맞은 짝인지 판단해 다르면 false를 return하고, 올바르면 스택에서 빼낸 후에 검사를 계속 진행한다.
연산자에 대한 처리는 먼저 연산자가 수식의 맨 앞이나 뒤에 왔는지 검사한다. 좌측 피연산자나 우측 피연산자가 존재하지 않는 수식이라는 의미이기 때문에 반드시 올바른 수식이 아니기 때문에 false를 return한다. 그렇지 않으면 수식의 현재 검사하고 있는 문자의 앞과 뒤의 문자로 괄호의 시작과 끝과 맞닿아 있을 경우에 피연산자가 빠지지는 않았는지, 연산자가 연속해서 오지는 않았는지 검사한다. 이 검사를 모두 통과하면 수식 검사를 계속한다.
수식 검사가 1차적으로 끝난 후엔 스택이 비어있는지 확인한다. ‘(a#a)&(a#a‘와 같은 수식은 이상의 테스트를 통과하지만, 쌍이 맞지 않는 불완전한 수식이기 때문이다.
주석 처리된 부분은 시행 착오를 여러 번 거치다 필요 없는 구문으로 판단되어 지워진 코드들이다. 먼저, a부터 c까지의 operands들이 들어오면 원래는 numCount라는 변수로 피연산자들이 몇 개나 되는지 카운트했었다. ‘a#’과 같이 1개 이하의 피연산자가 있다는 것은 구문에 오류가 있다는 의미이기 때문이다. 하지만 이는 연산자의 위치가 수식의 제일 앞이거나 뒤인지 판단하는 구문에서 이미 걸러지기 때문에 필요하지 않은 코드가 되었다. 또한, 원래는 연산자를 확인한 후에는 스택에 넣었었다. 처음에는 올바르게 사용된 연산자를 스택에 넣어놓고 나중에 다른 어떤 부분에서 확인하는데 사용할 수 있을 것이라 생각했다. 하지만 기능을 모두 구현한 다음에 살펴보니 연산자를 스택에 넣는 행위 자체가 의미 없는 코드가 되었다. 괄호를 검사할 때도 이 때 넣어놓은 연산자 때문에 따로 연산자를 모두 꺼내야 되는 코드가 추가적으로 필요했고, 연산자를 잘못 사용한 경우는 이미 함수가 false로 return되기 때문이었다. 그래서 연산자를 스택에 넣어두는 코드 자체를 아예 없애 불필요한 코드를 줄였다.
B. Postfix로 변환
검사를 마친 수식은 postfix로 바뀌어도 문제가 없다. 스택 계산기를 구현하려면 수식을 postfix 형태로 만들어내는 것이 계산 과정에서 훨씬 편리하다. makePostfix 함수는 바꿀 수식 string을 전달받아 buf에 postfix로 바뀐 수식을 저장해준다.
피연산자가 들어오면 작성하고 있는 buf의 현재 위치에 그대로 넣어준다. 연산자가 들어오면 먼저 스택이 비어있는지, 혹은 스택의 맨 위에 좌측 괄호가 있는지 확인해 그렇다면 스택에 바로 넣어준다. 그렇지 않다면 &과 #, @로 나누어 처리해준다. &는 #과 @보다 높은 연산 우선순위를 가지고 있다. 스택의 맨 위에 있는 연산자보다 연산 우선순위가 같거나 높은 연산자를 만나면 스택에서 맨 위에 있는 연산자를 꺼내 buf에 넣어줘야 한다. 그러므로, #와 @는 가장 낮은 연산 우선순위를 가지므로 같거나 높은 연산자를 만날 수 밖에 없으므로, 연산자를 만날 때 마다 스택 맨 위의 연산자를 buf에 넣고 pop한다. 그리고 &은 같은 &을 만났을 때는 @와 #과 마찬가지로 바로 buf에 넣고 pop을, &이 아닌 다른 연산자를 만났을 때는 항상 더 높은 연산순위를 가지므로 스택에 넣어준다.
좌측 괄호를 만나면 스택에 넣어준다. 우측 괄호를 만나면 적절한 좌측 괄호를 만날 때까지 연산자를 꺼내 buf에 넣어준다.
위의 for문이 끝났을 때 스택이 비어있으면 그대로 return을, 아직 남아있으면 들어있는 연산자를 모두 꺼내 buf에 넣어주고 return한다. 이렇게 수식을 Postfix로 만들어내는 과정이 끝났다.
C. Postfix문 계산
이제 스택 계산기로 계산하기 상대적으로 쉬운 postfix문까지 만들어냈으니, 계산만 해서 그 값을 출력하면 된다. Postfix문을 계산해 출력하는 함수 calcPostfix는 다음과 같다.
피연산자(a, b, c)를 만나면 스택에 넣고, 연산자를 만나면 newCalc 함수를 호출한다. newCalc 함수는 스택 맨 위의 두 피연산자를 꺼내 cursor에 해당하는 연산자로 연산 결과 값을 구해 스택에 넣어주는 함수이다.
상술했듯이, 미리 설정해놓은 배열들에서 대응하는 값을 스택에 넣어주는 것으로 연산을 진행한다.
이렇게 연산이 모두 끝나고 나면 스택의 맨 위에는 연산 결과만이 남아있을 것이다. 이 결과를 출력해주고 calcPostfix 함수를 마친다. 우리는 입력 받은 수식을 계산해 출력하는 과정까지 끝냈다.
4. 다른 예시 입력
- a#(a@b&c)
- a&b&c@(a&b&c)
- a&&b
- a#b#(a&c#)
모두 정상적으로 출력되는 것을 확인할 수 있었다.
'Programming > Programming' 카테고리의 다른 글
[C++] 사용자 정의 Vector 클래스 디자인 (0) | 2018.12.23 |
---|---|
[C++] 함수 및 다중 정의 (Overload) (1) | 2018.01.21 |
[C++] Try 구역과 예외 처리 (0) | 2018.01.15 |
[C++] 문자열 & 벡터 (0) | 2018.01.13 |
[C++] 변수와 기본 타입 (0) | 2018.01.11 |