Data Structure – Stack Calculator

스택 자료구조를 이용한 계산기 소스

#include <stdio.h>
#define max 30 // 피연산자와 연산자의 개수 
typedef struct {
	int stack[max];
	int top;
}stack_type;
typedef stack_type* stack_type_ptr;

stack_type st;
int stop,num,num2; // stop이 1되면 멈춤, num,num2는 get_symbol에서 유용하게 사용됨 


void init(stack_type_ptr s) {
	s->top = -1;
}
int is_full(stack_type s) {
	if (s.top == max - 1)
		return 1;
	else
		return 0;
}
int is_empty(stack_type s) {
	if (s.top == -1)
		return 1;
	else
		return 0;
}
void push(stack_type_ptr s, int item) {
	if (!is_full(*s)) {
		s->top++;
		s->stack[s->top] = item;
	}
	else
		printf("스택이 꽉 차있음");
}
int pop(stack_type_ptr s) {
	if (!is_empty(*s)) {
		int tmp = s->stack[s->top];
		s->top--;
		return tmp;
	}
	else
		printf("스택이 비어 있음");
}

int peek(stack_type s) { // top을 줄이지 않고 스택 최상단에 있는 것을 출력 
	int tmp = s.stack[s.top];
	return tmp;
}


int PIS(int alter) { // 스택 최상단에 위치한 연산자를 우선 순위에 맞추어 리턴 
	switch (alter) {
	case -40: // 괄호가 들어왔을 때
		return 4;
	case -94:  // ^가 들어왔을 때
		return 3;
	case -37: case -42: case -47: // %,*,/ 가 들어왔을 때
		return 2;
	case -43: case -45: // +,-가 들어왔을 때
		return 1;
	}
}

int PIE(char alter) { // 식에 있는 연산자를 우선 순위에 맞추어 리턴
	switch (alter) {
	case '(':  // 괄호가 들어왔을 때
		return 4;
	case '^':  // ^가 들어왔을 때
		return 3;
	case '%': case '*': case '/': // %,*,/ 가 들어왔을 때
		return 2;
	case '+': case '-': // +,-가 들어왔을 때
		return 1;
	}
}

char get_symbol(char *exp, int sp) { //문자열 받아서 문자 한 개씩 보내기 
	if (sp == 0) 
		return exp[num++];
	else if (sp == 1)  // num에 1을 더하여 저장하면 안 되는 특별한 경우 
		return exp[num];
}

void postfix(char *exp, int *pexp) { // 후위식 만들기

	for (int i = 0; i < max; i++) {
		// pexp함수를 쓰레기값으로 초기화 (초기화 안 하면 두 번째 실행 시, 첫 번째 실행할 때 저장한 요소가 영향을 끼침) 
		pexp[i] = -100;
	}

	char tmp; int left, p; // 임시 변수
	int jnum = 0; // 수의 자릿수 변수
	int n = 0; // 현재 수를 저장한 변수 
	int i = 0; // pexp배열의 인덱스

	while ((tmp = get_symbol(exp, 0)) != '\0') {
		switch (tmp) {
		case '+': case '-': case '*': case '/': case '%': case '(': case'^': case ')':
			if (jnum >= 1) { // 더이상 연속적으로 숫자가 나오지 않았을 때 
				pexp[i++] = n; // 수를 일단 배열에 넣음 (수를 먼저 처리하고 연산자를 처리해야 하므로)
				jnum = 0; n = 0; // 이번 수 처리는 다 했으니, 다음 수 처리를 위해 jnum,n을 0으로
			}

			if (tmp == ')') {
				while ((left = pop(&st)) != -40) { // '('나올 때까지 꺼내고 '('가 아니면 배열에 저장 
					pexp[i++] = left;
				}
				break;
			}

			while (!is_empty(st) && (p = PIS(peek(st))) >= PIE(tmp)) {
				if (p == 4) // 스택 최상위 있는게 '('이면
					break; // '('은 ')'가 와야만 빠질 수 있으므로 break로 중단. '('밑에 쌓인 것은 볼 필요도 없음 
				else
					pexp[i++] = pop(&st);
			}

			if (tmp == '+')
				push(&st, -43);
			else if (tmp == '-')
				push(&st, -45);
			else if (tmp == '*')
				push(&st, -42);
			else if (tmp == '/')
				push(&st, -47);
			else if (tmp == '%')
				push(&st, -37);
			else if (tmp == '^')
				push(&st, -94);
			else // tmp가 괄호일 때 
				push(&st, -40);
			break;

		default: // 그냥 숫자를 받았을 때 
			n = 10 * n;
			n = n + (tmp - 48);
			jnum++;

			if (jnum >= 1 && get_symbol(exp, 1) == '\0') // 연속으로 숫자를 
받았는데, 다음 문자가 널 문자일 때! 
				pexp[i++] = n;
			break;
		}
	}
	while (!is_empty(st)) // 식이 끝나면 스택이 비어있을 때까지 빼네서 배열에 저장 
		if ((left = pop(&st)) != -40) // ')'면 배열에 저장 안 함 
			pexp[i++] = left;
}

int get_symbol2(int *pexp) { //후위식 받아서 (연산자or피연산자) 한 개씩 보내기 
	return pexp[num2++];
}

int power(int a, int b) { // 제곱 함수
	if (b == 0)
		return 1;
	else
		return a * power(a, b - 1);
}

int exec(int tmp, int left, int right) { // tmp데이터에 따라 left와 right로 계산하는 함수
	switch (tmp) {
	case -43: // 더하기
		return right + left;
	case -45: // 빼기
		return right - left;
	case -42: // 곱하기
		return right * left;
	case -47: // 나누기
		return right / left;
	case -37: // 나머지
		return right % left;
	case -94: // 제곱
		return power(right, left);
	}
}

int eval(int *pexp) { // 최종 계산 값 리턴 
	int tmp, r; //임시 변수
	int left, right; // 스택에서 꺼낸 값을 저장하는 변수

	for (int i = 0; pexp[i] > -95; i++) { // -95부터 쓰레기 값이라고 간주함 -> 쓰레기 값이 아니면 계속 연산에 활용함
		tmp = get_symbol2(pexp);
		switch (tmp) {
		case -43: case -45: case -42: case -47: case -37: case -94: // tmp가 연산자일 때 
			left = pop(&st);
			right = pop(&st);
			r = exec(tmp, left, right);
			push(&st, r);
			break;
		default: // tmp가 피연산자일 때 
			if (tmp >= 0)
				push(&st, tmp);
			break;
		}
	}
	return st.stack[st.top]; // 연산이 잘 됐으면 top(0)일 때의 스택 값이 최종 값임 
}

void display(char* exp, int* pexp, int res) {

	// 원래식 출력
	printf("\n%s를 풀어보겠습니다.", exp);

	// 후위식 출력
	printf("\n후위식을 만들어 보면 ");
	for (int i = 0; pexp[i] > -95; i++) { // -95부터 쓰레기 값이라고 간주함 
		if (pexp[i] >= 0) // 피연산자일 때
			printf("%d ", pexp[i]);
		else  // 연산자일 때 
			printf("%c ", -(pexp[i])); // 연산자가 '-(아스키코드)' 형태로 저장되었으니, 다시 -를 해서 문자로 출력
	}
	printf(" 가 됩니다.");

	// 결과 출력
	printf("\n계산해보면 %d이 됩니다.\n", res);
	printf("---------------------------------------------\n\n");
}

void get_exp(char * exp) { // 사용자로부터 중위식(원래식)을 입력받음 

	for (int i = 0; i < max; i++) {
		// exp,pexp함수를 쓰레기값으로 초기화 (초기화 안 하면 두 번째 실행 시, 첫 번째 실행할 때 저장한 요소가 영향을 끼침) 
		exp[i] = -100;
	}

	printf("\n원하는 계산을 입력해주세요 ");
	printf("( '='은 제외하고 입력해주세요!! )\n:");
	scanf_s("%s", exp, max);
}

char menu() {
	num = 0; num2 = 0; // get_symbol에서 필요한 변수 초기화 

	printf("--------------- SH Calculator ---------------\n\n");
	printf("( +,-,*,/,^,나머지 연산이 가능합니다!! )\n");
	printf("한 번만 계산을 하실거면 '1'을, 아니면 아무 숫자를 입력해주세요!!:");
	scanf_s("%d",&stop);
	return stop;
}

int main() {
	char exp[max]; // 중위식이 담길 배열
	int pexp[max]; // 후위식이 담길 배열 (피연산자는 그냥 피연산자로, 연산자는 '-(아스키코드)'로 바뀌어서 이 배열에 담김) 
	// '-(아스키코드)'로 하는 이유는, 프로그램에서 아스키코드를 일반 수와 착각할 수 있기 때문에!! 

	do {
		init(&st);
		stop = menu();
		get_exp(exp); // 사용자로부터 중위식(원래식)을 입력받음 
		postfix(exp, pexp); // 중위식-> 후위식 변환
		int res = eval(pexp); // 수식 계산 
		display(exp, pexp, res); // 결과를 출력 

	} while (stop != 1);
}

Leave a Reply

Your email address will not be published. Required fields are marked *