Programming Language/C++

Ch 03. 입출력 - 02. scanf

hustle_D 2025. 3. 25. 21:12
반응형

scanf는 동일하게 cstdio에 존재하기에 cstdio를 우선 import해주자.

 

printf를 생각해보면 아래와 같이 사용했었던걸 기억할것이다.

그런데 scanf의 경우는 아래와 같이 사용이 불가능하다.

그 이유는 scanf라는 함수 내부에서 값을 외부에 있는 변수에 넣어주기 위해서는 해당 변수의 주소값을 가져가야만 하기 때문이다.

그렇기에 사실 사용하기 위해서는 &연산자를 사용해서 주소값을 전달해줘야만 한다.

그래야지만 결과에서 사용자의 입력 값을 저장하는 것을 볼 수 있다.

추가로 위 처럼 초기화를 하는 경우 형식 문자에 맞지 않은 데이터를 넣으려고 시도한다면 

이렇게 scanf를 통해 전달받은 데이터를 넣지 않는다.

또한 초기화하 되어 있지 않는 경우는 

이렇게 변수가 처음 할당될대 제공 받은 쓰레기값이 변경없이 저장되어 있음을 확인할 수 있다.

 

그리고 scanf의 경우는 입력이 성공한 변수의 갯수를 반환하게 된다.

이렇게 하나의 값이 잘 저장되었음을 알려준다.

만약 a와 같이 형식 문자에 맞지않은 데이터를 입력하면 

정상적으로 저장되지 않았기에 어떤 값도 저장되지 않았다고 0을 반환하는 것을 볼 수 있다.

 

이런 특성을 사용한다면 if 문에서 scanf문을 조건절로 줘서 

정상적인 입력을 받은 경우와 그렇지 않은 경우에 대한 조건절로도 사용이 가능하다.

문자를 입력해서 입력이 정상적으로 되지 않은 경우
정상적으로 정수를 입력해서 정상적인 입력이 된 경우

 

scanf의 함수 내부에 문자열로 추가되는 경우도 사용이 가능하다

 

이렇게 scanf내부에 %d 와 같은 서식문자를 제외한 문자도 추가가 될 수 있다.

대신 사용할때 scanf내부에 사용했던 문자의 형식을 지켜줘야한다.

만약 지키지 않고 맘대로 사용한다면


이렇게 위치에 맞는 값빼고는 쓰레기 값이 들어간다

 

그리고 기존에 printf에서는 %d와 %i는 같은걸로 사용했었는데 scanf에서는 조금 다르다.

이렇게 코드를 썼을때 입력의 앞에 prefix를 어떻게 추가하냐에 따라 

0을 prefix로 추가하는 경우 8진수로 인식하고 10을 십진법으로 8로 표현해준다.

이렇게 10이 8이 될수도 있고

0x를 prefix로 추가하는 경우 16진수로 인식하고 10을 십진법으로 16으로 표현해준다.

이렇게 10이 16이 될 수 도 있는 것이다.

 

char형의 경우는 scanf에서 %c로 입력을 받을 수 있다.

그 출력은 %c로도 %i로도 출력이 가능하다

왜냐면 char 형은 실제로 내부적으로 정수형이 들어 있기 때문이다.

다만 char형 변수를 scanf로 데이터를 받을때에는 %i로는 받을수 없다는 것을 알아둬야 한다

 

float형을 scanf에서 다뤄 보자면 먼저 printf에서는 %f를쓰던 %lf를 쓰던 자동 형변환이 일어나서 동일하게 double형이 되게 된다.

그런데 scanf에서는 다르다.

부동 소수점을 공부해보면 double은 대부분 8바이트이고 float는 대부분 4바이트가 되는 것을 알 수 있는데 이렇게 메모리의 형태가 float와 double이 다른 경우에는 지수부와 가수부의 비트수가 다르게 되는데 scanf에서 전부 다 double로 처리해버리게 되면 가수부와 지수부의 비트수가 다르기 때문에 float를 넣은 경우엔 형태가 달라지게 될 수 도 있다.

그래서 scanf의 경우는 내부적으로 넘겨준 데이터가 어떤 자료형인지를 알아야한다.

보통은 이렇게 &연산자를 사용해서 변수를 넘겨주게 되는데 이렇게 &가 붙여져서 넘어갈때는 scanf는 내부적으로 이렇게 전달된 데이터의 형태가 float형인지 double형인지 알 수 가 없다.

그렇기에 형식 지정자로 형의 형태를 지정해서 전달해줘야 하는 것이다(지수부와 가수부의 형태가 타입마다 다르기 때문에).

 

그래서 꼭 float와 double과 같이 부동 소수점의 형태의 데이터를 전달할때에는 %f와 %lf와 같이 형태를 구분해서 scanf에 형식 지정자를 사용해줘야 한다.

이렇게만 보면 딱히 크게 다를 바가 없는 데이터이긴 한데

 

만약 

이렇게 4바이트의 크기를 가진 float형 변수 f1을 double형인 lf로 scanf로 전달하게 되면 

이렇게 에러를 띄우게 된다.

 

이는 4바이트의 변수에 8바이트의 값을 넣으려고 하기 때문에 생기는 일이다(scanf에 서식문자를 %lf를 사용하면 너가 입력한 값의 형태는 8바이트의 형태를 갖고 있어 -> float에 8바이트 데이터를 저장하려고 함 -> 크기를 넘어서서 에러를 발생시킴 )

 

그럼 반대로 8바이트 변수에 4바이트의 값을 넣는 경우는 

저장에서는 문제가 없는것을 알 수 있다.

 

그치만 이게 큰 정밀한 데이터가 될 수록 이상한 값이 될 수 도 있으니 정확하게 맞춰서 형식 문자를 사용해야 한다는 것을 알아야한다.

 

서식 문자 중에서 정수를 넣는 과정에서 진법에 따라 형식을 지정하는 %o, %x와 같은 것을 사용하면 

따로 prefix를 추가하지 않고 저장하더라도 해당 데이터는 해당 서식 문자를 사용한 진수로 인식해서 

이렇게 %o를 넣은 경우 10을 8진법으로 인식해서 8을 저장하고, %x의 경우는 10을 16진법으로 인식해서 16을 저장하는 것을 알 수 있다.

 

scanf를 사용하게 되면 버퍼 같은데에 쌓이던게 조금 남아서 발생하는 에러를 찾을 수 도 있는데 

이렇게 연속으로 문자를 입력 받고 프린트로 출력하는 경우에 만약 a를 입력하고 엔터를 치면 

이렇게 입력을 모두 받고 끝을 내버리는 것을 알 수 있다.

a만 넣고 엔터를 넣는게 아니라 이번엔 ab를 입력하고 엔터를 쳐보면

이렇게 정상적으로 출력되는 것을 볼 수 있다.

그러면 첫번째로 a와 엔터만 쳤을때는 왜 하나만 입력했는데 scanf가 두번 다 종료가 되었을까 

정확하게 알려면 두번째 printf문에서 %c로 받는 데이터를 %d로 변경주고

다시 a를 입력하고 엔터를 쳐보자

보면 10이라는 숫자가 입력된 것을 볼 수 있는데 이걸걸 ascii code에서 찾아보면

LF(Line Feed), 줄 바꿈, 즉 엔터 그 자체가 나오는 것을 볼 수 있다.

우리가  a를 입력하고 엔터를 쳤는데 첫번째 scanf를 만날때 두개가 한번에 버퍼에 저장되고 scanf에서 %c를 하나 받아주기에 a가 먼저 비워지고 프린트 문을 지나서 다음 scanf를 만날때 버퍼에 남아 있던 엔터가 바로 %c를 만나 비워지고 엔터가 printf를 만나서 출력되는 현상이 생기는 것이다.

 

이를 방지하고 싶다면 scanf에서 %c로 하나의 문자를 받은 후에 다음 scanf를 만나기 전에 이 버퍼를 비워준다면 다음 문자도 우리가 원할때 입력해서 직접 넣어 줄 수 있게 될 것이다.

 

이렇게 버퍼를 비워주는 함수를 보통은 std::fflush()라는 함수를 사용하는데 이건 사실 각 컴파일러나 플랫 폼에 따라서 동작이 다를 수 있다

그래서 사실 이걸 사용하는 것은 모든 플랫폼에서 사용할 수 있는 것은 아니다.

우리가 원하는 방식인 하나만 비워주는 방식을 생각한다면 그냥 getchar()라는 함수를 사용해서 하나의 문자를 그냥 소비만 시켜주는 방식이 더 좋을 수도 있다(현재 처럼 하나의 문자만 입력받고 엔터를 친다는 가정하에)

이렇게 엔터를 비워준다고 생각하면 된다

그러나 물론 위에서 가정한것 처럼 문자를 여러개 입력해버리면 

중간에 있는 문자인 b를 소비해버리고 엔터를 출력하는 결과를 얻을 수 있다는 점을 알아야 한다.

그래서 추가적으로 이렇게 여러가지 문자를 막 입력하는 경우에도 돌아갈 수 있도록 하게 하려고 한다면

getchar()가 버퍼에 존재하는 \n(엔터)를 만날때 까지 while문을 통해서 그냥 돌려버리면

첫번째 입력한 데이터에서 첫번째 문자를 제외, \n를 만날때 까지 그냥 비워버려 다음 입력을 온전하게 받을 수 있도록 하겠다라는 코드가 되는 것이다.

이렇게 된다는 것이다(초보적인 실수지면 while문의 가장 마지막에 ;을 붙이지 않으면 scanf가 while문 내부에 있는 걸로 인식해서 엔터를 요청하게 되니 혼란스러워하지 말자고 ;를 붙여주자.)

 

printf나 scanf의 경우는 출력과 입력의 기본이 console로 되어 있어 위와 같이 cmd창과 같은 터미널 창에서 입력을 받거나 출력을 해주거나 하는 과정이 있게 된다.

이 방향을 파일로 전환해서 파일을 통해 입력을 받고 파일로 출력을 하게 하도록 스트림의 방향을 조정하는 것도 가능하다.

그 때 사용하는 함수는 freopen이란 함수로 스트림의 방향을 전환할 수 있게 된다.

더보기

freopen() 함수란?

FILE *freopen(const char *filename, const char *mode, FILE *stream);

기존 stream을 닫고, 해당 filename으로 새로 열어서 그 스트림을 해당 파일에 연결하는 함수

 

- filename : 연결할 파일 경로/이름

- mode : 열기 모드 ("r", "w", "a" 등)

- stream : 리다이렉션할 기존 스트림 (stdin, stdout, stderr) 중 하나

 

stdin을 연결하면 입력을 파일로하고 stdout을 연결하면 출력을 파일로 하고 stderr의 경우는 에러에 대한 출력을 파일로 넣어주겠다는 것이다. 

이렇게 표준 입력을 input.txt라는 파일로 해서 정수 하나를 읽어서 그걸 콘솔로 프린트로 출력해보겠다는 코드이다.

그러면 input.txt를 솔루션 탐색기를 열어서

프로젝트에 우클릭을 해준 다음에 파일 탐색기에서 폴더 열기를 눌러주고

열린 폴더 기준으로 input.txt를 생성해준 다음에

열어서 정수 하나를 입력해주고 저장하고 닫아주자.

실행시켜보면 입력한 정수가 출력되는 것을 볼 수 있다.

반응형