참고 사이트
요구사항
- 식을 입력하면, num1, num2, sign(+, -, *, /)으로 각각 파싱하여 변수에 저장
- 프로그램은 break 입력 시에만 종료
- 잘못된 입력에 대한 예외처리
[❌]
- 입력 문자열이 12자를 초과하는 경우
- 정수가 아닌 문자를 입력하는 경우
- 정수와 문자를 함께 입력하는 경우
- 사칙연산자가 아닌 다른 기호를 입력한 경우
- num2의 뒤에 연산자가 있는 경우
EX) 1-1-- 맨 앞에 연산기호가 붙는 경우 ('-' 제외)
EX) +1+1- 연산자를 두 개 붙여 입력하는 경우('-' 제외)
EX) 1++1- 세 개 이상의 정수를 입력하는 경우
EX) 1+1+1- '/' 연산 때, 분자 혹은 분모에 '0' 입력 시, 다시 입력 요구하기
원격 저장소의 OJT4(기본 틀이 작성되어 있음)를 내려 받은 다음, 작업을 진행해야 함
(작성자의 로컬 저장소 이름 : OJT_3, 원격 저장소 이름 : OJT)
git pull [원격 저장소 이름] [브랜치 이름]
설치된 VSCode 열고, 작업 (설치방법)
// 부모 클래스
class Operator {
private:
int num1;
int num2;
double result;
protected:
void setResult(double result) { this->result = result; }
int getNum1() { return num1; }
int getNum2() { return num2; }
virtual void calculate() = 0; // 순수 가상함수
public:
void setNumber(int num1, int num2) { this->num1 = num1; this->num2 = num2; }
double getResult() { calculate(); return result; }
};
// 자식 클래스 : 더하기
class Add : public Operator{
// write
virtual void calculate(){
cout << getNum1() << " + " << getNum2() << " = " << getNum1() + getNum2() << '\n';
}
};
... // 빼기, 곱하기, 나누기 모두 똑같음. 연산기호만 바꿔주면 됨.
int main() {
...
case '+':
cout << '\n' << "[출력]" << '\n';
a.setNumber(num1, num2);
a.getResult();
break;
...
}
1) getNum1()
, getNum2()
, getResult()
를 통해서 부모 클래스인 Operator에서 private으로 선언된 변수 num1, num2를 꺼내오고,
2) setNumber()
를 통해 값을 넣어준다.
stringstream () : 문자열에서 맞는 자료형의 정보를 추출
#include <sstream> // stringstream 라이브러리
...
string input_exp;
int num1, num2;
char sign;
...
cout << "수식을 입력하세요. (break 입력 시 종료)" << endl;
getline(cin, input_exp);
stringstream stream(input_exp);
stream >> num1; // input_exp에서 연산자 전까지의 정수 추출해 int num1에 저장
stream >> sign; // input_exp에서 문자만 추출해 sign에 저장
stream >> num2; // sign 이후의 정수만 추출해 num2에 저장
...
if((len > 12) || (sign != '+') && (sign != '-') && (sign != '*') && (sign != '/')) {
cout << '\n' << "올바르게 입력했는지 확인하세요." << '\n';
cout << "===============================" << '\n' << '\n';
continue;
}
- 문자열 위치 찾기 : find()
문자열.find(검색 문자열) // 문자열 위치 검색
- 전달된 문자들 중 첫 번째로 나타나는 문자의 위치
find_first_of("찾을 문자", 찾기 시작할 인덱스)
- 전달된 문자들 중 가장 마지막에 나타나는 문자의 위치
find_last_of("찾을 문자", 찾기 마지막 인덱스)
📌 npos : 검색 함수가 실패할 때 "찾을 수 없음" 또는 "나머지 모든 문자"를 나타내는 -1로 초기화된 부호 없는 정수 값
#include <iomanip> // fixed 라이브러리
#include <cmath> // setprecision 라이브러리
...
int len = input_exp.length(); // 입력받은 문자열 길이 구하기
int sign_index = input_exp.find(sign); // sign 인덱스
int sign_subtract = input_exp.find('-',sign_index+2); // 예외 상황 중 '-'제외에 활용할 '-' 인덱스
char ch[] = {'+', '*', '/'}; // 입력된 연산자와 비교하기 위한 연산자 배열 선언
char sign_arr;
// '-'를 제외한 기호들을 for문을 활용해 하나씩 추출해 3, 4번에 적용
for(int i = 0; i < 3; i++) {
sign_arr = ch[i];
int sign_temp = input_exp.find(sign_arr,sign_index+1); // num2 이후에 또 sign이 입력되면, 그 sign의 인덱스를 저장
int first = input_exp.find_first_of(sign_arr,0); // 입력된 연산자 중 가장 앞 쪽에 놓인 sign의 인덱스
int last = input_exp.find_last_of(sign_arr,len-1); // 입력된 연산자 중 가장 뒤 쪽에 놓인 sign의 인덱스
// 3. 앞에서부터 찾은 연산자 인덱스,뒤에서부터 찾은 연산자 인덱스 비교
if(first == last){
// 4. '-'를 맨 앞 혹은 num2 앞에 입력했는지 판단
if(input_exp.find('-') == 0 || input_exp.find('-') == sign_index+1) {
break;
}
}
else{
cout << '\n' << "계산식을 올바르게 입력했는지 확인하세요." << '\n';
cout << "========================================" << '\n' << '\n';
sign = {}; // sign 초기화
break;
}
// 5. 입력 정수가 3개 이상인지 판단
if(sign_temp != string::npos || sign_subtract != string::npos) {
cout << '\n' << "서로 다른 연산자를 2개 이상 입력했는지 확인하세요.('-'제외)" << '\n';
cout << "===========================================================" << '\n' << '\n';
sign = {}; // sign 초기화
break;
}
}
...
first == last 에서 두 수 초과 입력 예외처리 가능하지 않나?
first에 입력된 sign과 last에 입력된 sign이 같은 경우에만 가능
그래서 예외처리 7번(코드에서는 주석 5번)을 구현해 서로 달라도 가능하도록 함.
sign이 2번 이상 오면 X (음수를 위한 ‘-’는 제외)
sign_index+1 = ‘-’ 혹은 정수 가능
sign_index+2 부터 = 정수만 가능
이를 초과하면, 세 자리 이상 입력된 것
그러므로,
1) [sign_index]+1에서 ‘+, *, /’ 이 발견되는지
2) [sign_index]+2에서 ‘-’가 발견되는지
비교하고, 발견된다면, 오류 메시지 출력.
...
case '/':
if(num1 != 0 && num2 != 0) {
cout << '\n' << "[출력]" << '\n';
d.setNumber(num1, num2);
d.getResult();
}
else {
cout << '\n' << "분자 혹은 분모에 '0'을 입력했는지 확인하세요." << '\n';
cout << "=============================================" << '\n' << '\n';
}
break;
...
참고 사이트
프로그래밍 언어에서는 수를 표현하기 위해 크게 두 가지 타입을 제공하는데, 바로 정수 타입과 부동소수점 타입이다.
- 부동소수점 이란?
실수를 표현할 때, 소수점의 위치를 고정하지 않는 것
- 부동소수점 자료형
- 부동소수점 구조
부동소수점 형식은 정해진 비드(32Bit 혹은 64Bit)를 적절히 분배해 부호부, 지수부, 가수부를 할당하는 일종의 규칙을 가짐
EX] -9.6875 → -1001.1011(2) → -1.0011011×2³ → [부호부 음수, 지수부 3, 가수부 0011011]
.
부호부(sign) : 1비트. 숫자의 부호를 나타내며, 양수일 때는 0, 음수일 때는 1
지수부(Exponent) : 8비트. 지수를 의미
가수부(Mantissa) : 23비트. 가수 또는 유효숫자를 의미
- 부동소수점의 정밀도
부동소수점의 정밀도란 정보 손실 없이 얼마나 많은 유의한 자릿수를 나타낼 수 있는지를 의미
하기 문자열 입력 시, 9999800001
라는 값이 출력되어야 정상
99999*99999
그러나 1409865409
라는 값이 출력되는 상황
999890001
정상 출력99980001
정상 출력하기 문자열 입력 시, 0.00000001
라는 값이 출력되어야 정상
1 / 100000000
그러나 1e-08
이라는 값이 출력되는 상황
0.0001
정상 출력되었지만 '0'을 하나 더 붙이면, 지수로 출력위 설명의 구조에서 알 수 있듯, float이나 double의 경우 가수부의 크키는 일정하기 때문에 지수가 충분히 클 경우에는 소수점 이하를 표현하기 어려움
즉, 부동소수점의 정밀도 때문에 문제가 발생한 것
setprecision()
함수를 사용해 기본 정밀도를 재정의
int는 표현 가능한 범위가 작으므로 두 수를 double로 변환
fixed : 기본이 소수점 아래 6자리까지 출력
setprecision(8) : 정밀도를 8로 재정의
#include <iomanip> // fixed 라이브러리
#include <cmath> // setprecision 라이브러리
// 자식 클래스 : 곱하기
class Multiply : public Operator{
// write
virtual void calculate(){
cout << getNum1() << " * " << getNum2() << " = " << fixed << setprecision(0) << (double)getNum1() * (double)getNum2() << '\n';
}
};
// 자식 클래스 : 나누기
class Divide : public Operator{
// write
virtual void calculate(){
cout << getNum1() << " / " << getNum2() << " = " << fixed << setprecision(8) << (double)getNum1() / (double)getNum2() << '\n';
}
};
#include <iostream>
#include <string>
#include <sstream> // stringstream 라이브러리
#include <iomanip> // fixed 라이브러리
#include <cmath> // setprecision 라이브러리
using namespace std;
// 부모 클래스
class Operator {
private:
int num1;
int num2;
double result;
protected:
void setResult(double result) { this->result = result; }
int getNum1() { return num1; }
int getNum2() { return num2; }
virtual void calculate() = 0; // 순수 가상함수
public:
void setNumber(int num1, int num2) { this->num1 = num1; this->num2 = num2; }
double getResult() { calculate(); return result; }
};
// 자식 클래스 : 더하기
class Add : public Operator{
// write
virtual void calculate(){
cout << getNum1() << " + " << getNum2() << " = " << getNum1() + getNum2() << '\n' << '\n';
}
};
// 자식 클래스 : 빼기
class Subtract : public Operator{
// write
virtual void calculate(){
cout << getNum1() << " - " << getNum2() << " = " << getNum1() - getNum2() << '\n' << '\n';
}
};
// 자식 클래스 : 곱하기
class Multiply : public Operator{
// write
virtual void calculate(){
cout << getNum1() << " * " << getNum2() << " = " << fixed << setprecision(0) << (double)getNum1() * (double)getNum2() << '\n' << '\n';
}
};
// 자식 클래스 : 나누기
class Divide : public Operator{
// write
virtual void calculate(){
cout << getNum1() << " / " << getNum2() << " = " << fixed << setprecision(8) << (double)getNum1() / (double)getNum2() << '\n' << '\n';
}
};
int main()
{
Add a;
Subtract s;
Multiply m;
Divide d;
string input_exp;
int num1, num2;
char sign;
while (1) {
cout << "수식을 입력하세요. (break 입력 시 종료)" << endl;
getline(cin, input_exp);
// write
// stringstream : 문자열에서 필요한 정보만 추출
stringstream stream(input_exp);
stream >> num1;
stream >> sign;
stream >> num2;
int len = input_exp.length(); // 입력받은 문자열 길이 구하기
int sign_index = input_exp.find(sign); // sign 인덱스
int sign_subtract = input_exp.find('-',sign_index+2); // 예외 상황 중 '-'제외에 활용할 '-' 인덱스
// 1. "break" 입력 시, 프로그램 종료
if(input_exp == "break"){
break;
}
// 2. 입력한 문자열이 길이가 12 이하이면서 연산자는 +,-,*,/ 인지 판단
if((len > 12) || (sign != '+') && (sign != '-') && (sign != '*') && (sign != '/')){
cout << '\n' << "올바르게 입력했는지 확인하세요." << '\n';
cout << "===============================" << '\n' << '\n';
continue;
}
char ch[] = {'+', '*', '/'}; // 입력된 연산자와 비교하기 위한 연산자 배열 선언
char sign_arr;
// '-'를 제외한 기호들을 for문을 활용해 하나씩 추출해 3, 4번에 적용
for(int i = 0; i < 3; i++) {
sign_arr = ch[i];
int sign_temp = input_exp.find(sign_arr,sign_index+1); // num2 이후에 또 sign이 입력되면, 그 sign의 인덱스를 저장
int first = input_exp.find_first_of(sign_arr,0); // 입력된 연산자 중 가장 앞 쪽에 놓인 sign의 인덱스
int last = input_exp.find_last_of(sign_arr,len-1); // 입력된 연산자 중 가장 뒤 쪽에 놓인 sign의 인덱스
// 3. 앞에서부터 찾은 연산자 인덱스,뒤에서부터 찾은 연산자 인덱스 비교
if(first == last){
// 4. '-'를 맨 앞 혹은 num2 앞에 입력했는지 판단
if(input_exp.find('-') == 0 || input_exp.find('-') == sign_index+1) {
break;
}
}
else{
cout << '\n' << "계산식을 올바르게 입력했는지 확인하세요." << '\n';
cout << "========================================" << '\n' << '\n';
sign = {}; // sign 초기화
break;
}
// 5. 입력 정수가 3개 이상인지 판단
if(sign_temp != string::npos || sign_subtract != string::npos) {
cout << '\n' << "서로 다른 연산자를 2개 이상 입력했는지 확인하세요.('-'제외)" << '\n';
cout << "===========================================================" << '\n' << '\n';
sign = {}; // sign 초기화
break;
}
}
switch(sign) {
case '+':
cout << '\n' << "[출력]" << '\n';
a.setNumber(num1, num2);
a.getResult();
break;
case '-':
cout << '\n' << "[출력]" << '\n';
s.setNumber(num1, num2);
s.getResult();
break;
case '*':
cout << '\n' << "[출력]" << '\n';
m.setNumber(num1, num2);
m.getResult();
break;
case '/':
if(num1 != 0 && num2 != 0) {
cout << '\n' << "[출력]" << '\n';
d.setNumber(num1, num2);
d.getResult();
}
else {
cout << '\n' << "분자 혹은 분모에 '0'을 입력했는지 확인하세요." << '\n';
cout << "=============================================" << '\n' << '\n';
}
break;
default :
cout << "다시 시도하세요." << '\n' << '\n';
break;
}
num1, num2 = 0; // num1, num2 초기화
sign = {}; // 연산기호 초기화
}
return 0;
}
그러면, 아래와 같이 tasks.json
파일과 main
실행 파일이 생성됨.
git status // 빨간 색
git add .
git status // 초록색으로 변한 것 확인
git commit -m "커밋 메시지"
git pull origin main
만약 여기서 'git pull ...' 이후, 하기와 같은 에러가 뜨는 경우
github you have not concluded your merge
아래 명령어를 입력한 후 다시 pull -> push 해주면 됨.(참고)
git merge --abort
git pull origin main
git push origin main