반응형
C++ Creating excel part 2.
1.C++ Cell class extension
Cell 클래스에는 string 데이터만 저장할 수 있기 때문에 이를 상속 받는 클래스들을 만들어서 셀에 다양한 데이터들을 보관할 수 있게 할 것이다.
class Cell { protected: int x, y; Table* table; public: virtual string stringify() = 0; virtual int to_numeric() = 0; Cell(int x, int y, Table* table); };
일단 기존의 Cell 클래스에서 문자열 데이터를 보관했던 것과는 달리 항목을 빼버리고, 이를 상속 받는 클래스에서 데이터를 보관하도록 했다. 또한, stringify 함수와 to_numeric 을 순수 가상 함수로 정의해서이를 상속 받는 클래스에서 이 함수들을 반드시 구현 토록 하였다.
class StringCell : public Cell
{
string data;
public:
string stringify();
int to_numeric();
StringCell(string data, int x, int y, Table* t);
};
class NumberCell : public Cell
{
int data;
public:
string stringify();
int to_numeric();
NumberCell(int data, int x, int y, Table* t);
};
class DateCell : public Cell
{
time_t data;
public:
string stringify();
int to_numeric();
DateCell(string s, int x, int y, Table* t);
};
위 소스코드는 각각 문자열, 정수, 시간 정보를 보관하는 클래스들이다. 단순히 데이터를 문자열이나 정수 형으로 바꾸기만 해주면 되기 때문이다.
참고로 DateCell 의 경우에는 편의를 위해서 yyyy-mm-dd 형식으로만 입력을 받는 것으로 정하였다.
Cell::Cell(int x, int y, Table* table) : x(x), y(y), table(table) { } StringCell::StringCell(string data, int x, int y, Table* t) : data(data), Cell(x, y, t) {} string StringCell::stringify() { return data; } int StringCell::to_numeric() { return 0; } /* NumberCell */ NumberCell::NumberCell(int data, int x, int y, Table* t) : data(data), Cell(x, y, t) {} string NumberCell::stringify() { return to_string(data); } int NumberCell::to_numeric() { return data; } /* DateCell */ string DateCell::stringify() { char buf[50]; tm temp; localtime_s(&temp, &data); strftime(buf, 50, "%F", &temp); return string(buf); } int DateCell::to_numeric() { return static_cast(data); } DateCell::DateCell(string s, int x, int y, Table* t) : Cell(x, y, t) { // 입력받는 Date 형식은 항상 yyyy-mm-dd 꼴이라 가정한다. int year = atoi(s.c_str()); int month = atoi(s.c_str() + 5); int day = atoi(s.c_str() + 8); tm timeinfo; timeinfo.tm_year = year - 1900; timeinfo.tm_mon = month - 1; timeinfo.tm_mday = day; timeinfo.tm_hour = 0; timeinfo.tm_min = 0; timeinfo.tm_sec = 0; data = mktime(&timeinfo); }
// 입력받는 Date 형식은 항상 yyyy-mm-dd 꼴이라 가정.
int year = atoi(s.c_str());
int month = atoi(s.c_str() + 5);
int day = atoi(s.c_str() + 8);
tm timeinfo; timeinfo.tm_year = year - 1900; timeinfo.tm_mon = month - 1; timeinfo.tm_mday = day; timeinfo.tm_hour = 0; timeinfo.tm_min = 0; timeinfo.tm_sec = 0; data = mktime(&timeinfo);
class ExprCell : public Cell {
string data;
string* parsed_expr;
Vector exp_vec;
// 연산자 우선 순위를 반환합니다.
int precedence(char c);
// 수식을 분석합니다.
void parse_expression();
public:
ExprCell(string data, int x, int y, Table* t);
string stringify();
int to_numeric();
};
2.C++ Literary Notation & Postfix Notation
흔히 3 + 4 * 5 + 4 * (7 - 2) 표기하는 방식을 중위 표기법이라고 한다. 사실 위 방식에 익숙해서 어떠한 순서로 계산하는지 쉽게 알 수 있지만
컴퓨터에 경우 이를 계산하는데 어려울 수 있다. 일단 고려해야할 점들이 먼저 괄호를 우선으로 계산하고, 그 다음에 * 와 / , 그리고 + 와 - 의 우선 순위로 나누어서 계산해야 한다.
쉽게 말해 위 수식의 경우 비록 맨 앞에 3 + 4 이 있지만 사실은 4 * 5 를 먼저 계산해야 된다.
즉, 컴퓨터가 이 수식을 계산하기 위해서는 계산하는 위치를 우선 순위에 맞게 이러 저리 옮겨다녀야 한다.
위 처럼 피연산자와 피연산자 사이에 연산자를 넣는 형태로 수식을 표현하는 방법을 중위 표기법(infix notation) 이라고 부른다.
쉽게 말해 연산자가 '중간' 에 들어가서 중위 표기법이다.
반면에 아래의 수식을 살펴보도록 하자.
3 4 5 * + 4 7 2 - * +
흔히 생각하는 수식의 모습이랑 사뭇 다르다. 사실 위 수식은 앞서 말한 수식과 정확히 동일한 수식인데, 그 표현 방식이 다를 뿐이다.
이러한 형태로 수식을 표현하는 방식을 후위 표기법(postfix notation) 이라고 한다. 자세히 보자면 이전 수식과 다른 점을 두 가지 찾을 수 있는데,
하나는 이전과는 달리 연산자들이 피연산자 뒤쪽에 위치해 있다는 점과, 또 하나는 괄호가 사라졌다는 점이다.
괄호가 사라졌다는 것이 무슨 의미가 있을까?
기존의 중위 표현법이 컴퓨터가 해석하기에 불편했던 점이 바로 연산자의 우선 순위나 괄호에 따라 이리 저리 계산하는 부분을 찾아다녀야
했던 점이었다. 하지만 후위 표기법에서는 놀랍게도 이리 저리 계산할 위치를 찾으러 돌아다닐 필요 없이, 읽어들이는 순서 대로 계산을 쭉 할 수 있다.
그렇다면 이 후위 표기법으로 표현된 식을 컴퓨터가 어떻게 해석하는지 살펴보겠다. 컴퓨터는 아래와 같은 과정으로 위 후위 표기법으로 변환된 식을 계산한다.
1.피연산자를 만나면 스택에 push한다.
2.연산자를 만나면 스택에서 두 개를 pop 한 뒤에 그 둘에 해당 연산을 한 후, 그 결과를 다시 스택에 push 한다.
실제로 3 + 4 * 5 + 4 * (7 - 2) 을 계산 했을 때와 그 결과가 같음을 알 수 있다. 이를 바탕으로 후위 표기법으로 된 수식을 계산하는 is_numeric 함수를 살펴보도록 하겠다.
int ExprCell::to_numeric() { double result = 0; NumStack stack; for (int i = 0; i < exp_vec.size(); i++) { string s = exp_vec[i]; // 셀 일 경우 if (isalpha(s[0])) { stack.push(table->to_numeric(s)); } // 숫자 일 경우 (한 자리라 가정) else if (isdigit(s[0])) { stack.push(atoi(s.c_str())); } else { double y = stack.pop(); double x = stack.pop(); switch (s[0]) { case '+': stack.push(x + y); break; case '-': stack.push(x - y); break; case '*': stack.push(x * y); break; case '/': stack.push(x / y); break; } } } return stack.pop(); }
// 셀 일 경우
if (isalpha(s[0])) {
stack.push(table->to_numeric(s));
}
// 숫자 일 경우 (한 자리라 가정)
else if (isdigit(s[0])) {
stack.push(atoi(s.c_str()));
}
else {
double y = stack.pop();
double x = stack.pop();
switch (s[0]) {
case '+':
stack.push(x + y);
break;
case '-':
stack.push(x - y);
break;
case '*':
stack.push(x * y);
break;
case '/':
stack.push(x / y);
break;
}
}
3.C++ Converting lexical notation to postfix notation
중위 표기법을 후위 표기법으로 변환하는 것은 다음의 방식을 따른다.
1.피연산자 (셀 이름이나 숫자) 일 경우 그냥 exp_vec 에 넣는다.
2.여는 괄호( (, [, { 와 같은 것들 ) 을 만날 경우 스택에 push 한다.
3.닫는 괄호( ), ], } 와 같은 것들 ) 을 만날 경우 여는 괄호가 pop 될 때 까지 pop 되는 연산자들을 exp_vec 에 넣는다.
4.연산자일 경우 자기 보다 우선순위가 낮은 연산자가 스택 최상단에 올 때 까지 (혹은 스택이 빌 때 까지) 스택을 pop 하고 (낮은 것은 pop 하지 X), pop 된 연산자들을 exp_vec 에 넣는다. 그리고 마지막에 자신을 스택에 push 한다.
연산자들의 우선 순위는 아래의 함수에 의해 정의된다.
int ExprCell::precedence(char c) {
switch (c) {
case '(':
case '[':
case '{':
return 0;
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
}
return 0;
}
void ExprCell::parse_expression() {
Stack stack;
// 수식 전체를 () 로 둘러 사서 exp_vec 에 남아있는 연산자들이 push 되게 해줍니다.
data.insert(0, "(");
data.push_back(')');
for (int i = 0; i < data.length(); i++) {
if (isalpha(data[i])) {
exp_vec.push_back(data.substr(i, 2));
i++;
}
else if (isdigit(data[i])) {
exp_vec.push_back(data.substr(i, 1));
}
else if (data[i] == '(' || data[i] == '[' || data[i] == '{') { // Parenthesis
stack.push(data.substr(i, 1));
}
else if (data[i] == ')' || data[i] == ']' || data[i] == '}') {
string t = stack.pop();
while (t != "(" && t != "[" && t != "{") {
exp_vec.push_back(t);
t = stack.pop();
}
}
else if (data[i] == '+' || data[i] == '-' || data[i] == '*' || data[i] == '/') {
while (!stack.is_empty() && precedence(stack.peek()[0]) >= precedence(data[i])) {
exp_vec.push_back(stack.pop());
}
stack.push(data.substr(i, 1));
}
}
}
if (isalpha(data[i])) { // 셀 이름의 경우 첫 번째 글자가 알파벳이다.
exp_vec.push_back(data.substr(i, 2));
i++;
}
else if (isdigit(data[i])) { // 첫번째 글자가 숫자라면 정수 데이터
exp_vec.push_back(data.substr(i, 1));
}
else if (data[i] == '(' || data[i] == '[' || data[i] == '{') { // Parenthesis
stack.push(data.substr(i, 1));
}
else if (data[i] == ')' || data[i] == ']' || data[i] == '}') {
string t = stack.pop();
while (t != "(" && t != "[" && t != "{") {
exp_vec.push_back(t);
t = stack.pop();
}
}
반면에 괄호의 경우 여는 괄호를 만나면 스택에 push 하고, 닫는 괄호를 만나면 위 처럼 여는 괄호가 스택에서 나올 때 까지 pop 하고, 그 pop 한 연산자들을 벡터에 넣으면 된다. 주의할 점은 pop 한 연산자가 괄호일 경우 넣지 않는다는 점이다.
else if (data[i] == '+' || data[i] == '-' || data[i] == '*' || data[i] == '/') {
while (!stack.is_empty() && precedence(stack.peek()[0]) >= precedence(data[i])) {
exp_vec.push_back(stack.pop());
}
stack.push(data.substr(i, 1));
}
// 수식 전체를 () 로 둘러 사서 exp_vec 에 남아있는 연산자들이 push 되게 해준다.
data.insert(0, "(");
data.push_back(')');
int main()
{
TxtTable table(5, 5);
table.reg_cell(new NumberCell(2, 1, 1, &table), 1, 1);
table.reg_cell(new NumberCell(3, 1, 2, &table), 1, 2);
table.reg_cell(new NumberCell(4, 2, 1, &table), 2, 1);
table.reg_cell(new NumberCell(5, 2, 2, &table), 2, 2);
table.reg_cell(new ExprCell("B2+B3*(C2+C3-2)", 3, 3, &table), 3, 2);
table.reg_cell(new StringCell("B2 + B3 * ( C2 + C3 - 2 ) = ", 3, 2, &table), 3, 1);
cout << table;
}
4.C++ Excel Program
이제 사용자의 입력을 받아서 비록 마우스는 쓸 수 없더라도, 키보드로 명령을 처리하는 엑셀 프로그램을 만들어보도록 해본다.
class Excel
{
Table* current_table;
public:
Excel(int max_row, int max_col, int choice);
int parse_user_input(string s);
void command_line();
};
또한 parse_user_input 함수의 경우 사용자의 입력을 인자로 받아서, 이를 처리하는 역할을 수행한다.
Excel::Excel(int max_row, int max_col, int choice = 0) {
switch (choice) {
case 0:
current_table = new TxtTable(max_row, max_col);
break;
case 1:
current_table = new CSVTable(max_row, max_col);
break;
default:
current_table = new HtmlTable(max_row, max_col);
}
}
반응형
'#Programming Language > C++' 카테고리의 다른 글
C++ Constructors and destructors. (0) | 2018.04.02 |
---|---|
C++ Template. (0) | 2018.04.02 |
C++ Creating excel part 1. (0) | 2018.04.02 |
C++ I / O in C++ (0) | 2018.04.01 |
C++ Virtual functions and inheritance. (0) | 2018.04.01 |