[백준] 10951번 : A + B - 4 - [C++]
https://www.acmicpc.net/problem/10951
10951번: A+B - 4
두 정수 A와 B를 입력받은 다음, A+B를 출력하는 프로그램을 작성하시오.
www.acmicpc.net
- 문제
이 전 문제인 A+B - 5와 문제가 같아보이나 이 번 문제는 유의해야 할 점이 있다.
- 알고리즘 [접근 방법]
이 번 문제의 키 포인트는 문제를 자세히 보면 몇 개를 입력받는지 알 수 없다는 것이다.
이렇게 주어진 입력 파일만 갖고 입력을 받을 때 더이상 읽을 수 있는 데이터가 없는 경우 즉, 파일의 끝일 때 이를 EOF(End Of File) 이라고 한다.
위 문제를 본다면 입력에서 더이상의 읽을 수 있는 데이터가 존재하지 않을 때 반복문을 종료하라는 것이다.
(참고로 우리는 일상적으로 문장의 끝을 Enter로 치지만, Enter(개행) 또한 하나의 '문자'다. Ascii 코드표를 보면 LF가 눈에 보이지는 않지만 개행을 하는 값이다.)
즉, 입력 스트림이 더이상 읽을 것이 없는 파일의 끝에 도달했을 때를 처리해야한다는 것이다.
여기서 많은 분들이 착각하는 것이 파일의 끝까지 읽었다고 EOF가 되는 것이 아니다. 끝까지 읽고난 뒤 그 다음 파일을 읽으려 할 때 읽을 데이터가 없을 때 EOF가 되는 것이다. (시점을 정확히 파악해야한다.)
예로들어 이렇다.
abcd<EOF> |
그리고 만약 위 텍스트를 읽는다면 다음과 같다.
즉, 단순히 d까지 읽었다고 EOF가 되는 것이 아니라 끝에 도달 한 후 더 읽으려고 할 때 EOF가 된다고 보시면 될 것이다.
참고로 에디터에서는 입력 파일을 따로 생성하여 읽지 않는 한 일반적인 키보드에서는 EOF키가 없어 EOF(입력 끝) 상태를 전송할 수가 없다. 그 대신 키맵이 따로 있는데, 콘솔에서 윈도우의 경우 CTRL + Z를 입력해주면 되며 리눅스(유닉스) 계열은 CTRL + D를 눌러주면 된다.
- scanf
stdio.h(혹은 cstdio)의 scanf의 경우는 int반환값이 존재한다. 반환값은 읽어들인 데이터의 개수를 반환하는데, 예로들어 scanf("%d %d %d, &a, &b, &c); 라고 했을 떄 지정한 포맷("%d")에 따른 3개의 변수에 알맞게 입력이 들어왔으면 3을 반환한다.
만약 데이터를 읽는동안 파일의 끝(EOF)에 도달한 뒤 읽고자 하면 -1 을 반환한다. 즉 다음과 같이 작성할 수 있다.
while(scanf("%d %d", &a, &b) != -1) {
...
}
- cin
가장 많이 틀리는 부분이다. cin도 마찬가지로 EOF를 발생시키는데, 크게 두 가지 방식이 있다.
일단 eof() 함수를 쓰는 경우다. 마찬가지로 eof() 함수도 파일의 끝에 도달한 뒤 읽고자 할 경우 true를 반환한다.
while(!(cin >> a >> b).eof()) {
...
}
위와 같이 작성해야한다.
만약 아래와 같이 하면 틀린다.
while(!cin.eof()) {
...
}
앞서 abcd 로 예시로 들었지만, d까지 읽는다고 EOF 상태인 것이 아니다.
예로들어 다음과 같이 입출력을 받는다고 해보자.
char val;
while(!cin.eof()) {
cin >> val;
cout << val;
}
그리고 입력으로 다음과 같이 입력한다고 가정해보자.
[입력 파일]
a
b
c
d
그러면 위 출력은 어떻게 될까?
위와같이 되어 결과적으로 abcdd 출력이 되어버린다.
cin에서 EOF가 되면 더이상 내부 스트림 상태를 fail로 두고 val 변수에 값을 저장하지 않는다. 즉, val의 상태는 이전 루프의 값을 갖고있게 되는 것이다.
즉, eof() 가 우리가 원하는 올바른 시점에 true가 되려면 "읽기 시도를 한 후"에 eof 검사를 해야한다는 것이다.
그렇기 때문에 (cin >> a >> b).eof() 방식으로 괄호 안의 cin >> a >> b를 통해 읽은 후 eof상태인지를 검사하는 것이다.
eof() 함수를 써도 되지만, 더욱 간단한 방식도 있다.
while(cin >> a >> b) {
...
}
보통은 cin >> a >> b 를 읽은 후 스트림 객체가 반환되지만, 위처럼 조건문 안에 있는 경우 연산자 오버로딩에 의해 bool 값으로 true 혹은 false값을 반환한다. (스트림이 정상 상태면 true, 아닐 경우 false)
연산자 정의는 다음과 같은 형식으로 구현되어있다.(버전마다 조금씩 상이할 수 있음)
explicit operator bool() const;
그래서 cin >> a >> b 과정에서 읽기를 실패했을 경우 스트림 상태를 변경하고, 위 연산자 오버로딩에 의해 반환되는 값이 true에서 false로 바뀌면서 while문을 종료하게 되는 것이다.
(사실 어떻게 bool로 캐스팅이 되는지, flag변수는 무엇인지를 설명하고 싶어도 자세히 파고들어가면 정말 복잡하다.. 설명이 너무 길어져 문제를 푸는게 아닌 사실상 스트림에 대한 개념으로 흘러가기 때문에 일단 이정도만 알아두어도 크게 문제는 없을 것이다.)
결과적으로 cin을 통한 방식과 scanf 방식 모두 "읽은 뒤 EOF 체크"를 해야 한다는 점이 바로 포인트다.
- 4가지 방법을 사용하여 풀이한다.
이 번에는 위에서 설명한 scanf방식, cin.eof() 와 cin의 연산자 오버로딩에 의한 bool값 활용 방식 3가지를 테스트 해보도록 하겠다.
따로 입출력 향상은 비교하는 것은 그렇게 큰 의미는 없을 것 같아서 마지막에 bool값 활용 방식에만 적용하여 테스트를 해보겠다. 즉, 다음과 같이 4가지 방식을 풀이하도록 하겠다.
1. scanf
2. cin.eof
3. cin operator bool
4. cin operator bool + 향상 된 입출력
- 풀이
- 방법 1 : [scanf]
#include <cstdio>
int main(int argc, const char * argv[]) {
int a, b;
// 또는 scanf("%d %d", &a, &b) == 2 로도 가능
while(scanf("%d %d", &a, &b) != -1) {
printf("%d\n", a + b);
}
return 0;
}
c언어나 c++에서 c언어 방식을 활용하여 가장 쉽게 할 수 있는 방식일 것이다.
- 방법 2 : [cin.eof()]
c++ 표준 입력 방식인 cin 을 사용하여 eof() 함수를 활용하는 방식이다. 앞서 설명에서 말했듯, 반드시 EOF 시점이 언제인지를 판단하고 짜야한다.
#include <iostream>
using namespace std;
int main(int argc, const char *argv[]) {
int a, b;
while (!(cin >> a >> b).eof()) { // 혹은 eof() 대신 fail()을 사용해도 된다.
cout << a + b << "\n";
}
return 0;
}
아마 이 부분에서 while문 조건식을 활용하는 부분이 많이 틀렸을 것 같다.
- 방법 3 : [cin operator bool]
cin이 조건식에 있으면 bool 값이 반환되도록 오버로딩 된 방식을 활용한 것이다.
#include <iostream>
using namespace std;
int main(int argc, const char *argv[]) {
int a, b;
while (cin >> a >> b) {
cout << a + b << "\n";
}
return 0;
}
- 방법 4 : [향상 된 입출력 + cin operator bool]
sync_with_stdio 와 cin.tie 을 활용하는 건 아마 이쯤되면 익숙할 것이라고 생각되기에,, 다른 방식에 적용하는 건 여러분이 직접 작성하는 것을 추천한다. 필자는 방법 3에만 적용시키기로 했다.
#include <iostream>
using namespace std;
int main(int argc, const char *argv[]) {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int a, b;
while (cin >> a >> b) {
cout << a + b << "\n";
}
return 0;
}
- 성능
채점 번호 : 30477953 - 방법 4 : 향상 된 입출력 + cin operator bool
채점 번호 : 30477947 - 방법 3 : cin operator bool
채점 번호 : 30477937 - 방법 2 : cin.eof()
채점 번호 : 30477928 - 방법 1 : scanf()
- 정리
이 번 문제는 EOF에 대한 개념이 없었다면 조금 어려웠을 수 있다.
최대한 자세히 설명해놓긴 했는데... 만약 어렵거나 이해가 되지 않은 부분이 있다면 언제든 댓글 남겨주시면 최대한 빠르게 답변드리겠다.
'C++ - 백준 [BAEK JOON] > 반복문' 카테고리의 다른 글
[백준] 1110번 : 더하기 사이클 - [C++] (0) | 2021.07.06 |
---|---|
[백준] 10952번 : A+B - 5 - [C++] (2) | 2021.06.13 |
[백준] 2439번 : 별 찍기 - 2 - [C++] (1) | 2021.05.11 |
[백준] 2438번 : 별 찍기 - 1 - [C++] (0) | 2021.05.08 |
[백준] 11022번 : A+B - 8 - [C++] (0) | 2021.05.02 |