입력과 출력이란??
프로그램을 기준
대표적인 입출력 장치는 아래와 같다.
그 밖에도 일반적으로 컴퓨터에 연결되는 마우스, 프린터, 카메라 등 다양한 장치들도 입출력 장치에 해당한다.
키보드, 모니터, 프로그램은 기본적으로 연결되어 있는 개체가 아닌 서로 떨어져 있는 개체들이다.
이렇게 각각 떨어져 있는 개체들을 연결시켜주는 다리가 필요한데 이를 스트림(stream)이라한다.
위 사진을 보면, 실행중인 프로그램과 모니터 사이에는 출력 스트림 이라는 다리가 놓여져 있고 데이터가 모니터로 가고, 키보드와 프로그램 사이에는 입력 스트림 이라는 다리가 놓여져 있고 데이터가 키보드에서 프로그램으로 흘러간다.
우리가 자주 사용했던 printf
, scanf
함수를 통해 데이터를 입출력하는 근본적인 이유가 저 사진을 보면 알 수 있다.
Console = 키보드, 모니터
콘솔 입출력과 파일 입출력에는 차이가 있다.
파일을 연결하는 스트림의 생성은 직접 요구해야 하지만, 콘솔과의 연결 스트림은 요구할 필요없이 자동적으로 생성이 된다.
콘솔 입출력 스트림은 프로그램이 실행되면 자동으로 생성되고, 프로그램이 종료되면 자동으로 소멸된다.
이를 가리켜 표준 스트림(Standard Stream)이라 한다.
표준 스트림에는 3가지가 있다
stdout과 stderr은 모니터를 대상으로 출력이 이루어 진다는 점은 동일하다. 하지만 후에 입출력 리다이렉션(redirection)이라는 기술(주로 유닉스, 리눅스에서 배운다)을 익히면 stderr의 출력 대상을 변경할 수 있기 때문에 stdout과 stderr을 구분 할 수 있다.
위의 입출력 원리와 스트림의 이해가 된 이후 아래 함수를 같이 공부하는 것이 좋다
모니터로 하나의 문자를 출력할 때 일반적으로 사용하는 함수는 두 가지 이다.
#include <stdio.h>
int putchar(int c);
int fputc(int c, FILE * stream);
// 함수 호출에 성공하면 쓰여진 문자 정보가, 실패하면 EOF를 반환한다.
putchar 함수는 인자로 전달된 문자정보를 stdout으로 표현되는 표준 출력 스트림으로 전송하는 함수
즉, 인자로 전달된 문자를 모니터로 출력하는 함수라 할 수 있다.
fputc 함수도 문자를 전송하는 측면에서는 putchar 함수와 동일하지만, fputc 함수는 문자를 전송할 스트림을 지정할 수 있다.
즉, fputc 함수를 이용하면 stdout 뿐 아니라, 파일을 대상으로 데이터를 전송할 수 있다.
그래서 fputc 함수의 두 번째 매개변수는 문자를 출력할 스트림의 지정에 사용된다. 여기에 stdout을 전달하면 putchar와 동일한 함수가 된다.
키보드로 하나의 문자를 입력 받을 때 일반적으로 사용하는 함수 두 가지이다.
#include <stdio.h>
int getchar(void);
int fgetc(FILE * stream);
// 파일 끝에 도달하거나 함수 호출 실패 시 EOF 반환
getchar 함수는 stdin으로 표현되는 표준 입력 스트림으로 하나의 문자를 입력 받아서 반환하는 함수
fgetc 함수도 getchar와 같이 문자를 입력 받는 함수
단, getchar와 달리 문자를 입력 받을 스트림을 지정할 수 있다.
위의 putchar, fputc의 관계와 같다.
#include <stdio.h>
int main() {
int ch1, ch2;
ch1 = getchar(); // 문자 입력
ch2 = fgetc(stdin); // 엔터 키 입력
putchar(ch1); // 문자 출력
fputc(ch2, stdout); // 엔터 키 출력
return 0;
}
>>> p
>>> p
실행 결과를 보면 두 개의 문자를 출력하고 있다.
겉보기에는 하나의 문자만 입력되었고 출력된 것처럼 보이지만 실제로는 2개의 문자가 입력되고, 출력되었다.
그 이유는 엔터키 입출력 때문인데 엔터키는 아스키 코드 값이 10인 '\n'으로 표현되는 문자기 때문이다.
EOF는 End Of File의 약자로, 파일의 끝을 표현하기 위해 정의해 놓은 상수
파일을 대상으로 fgetc 함수가 호출되고, 그 결과로 EOF가 반환되면, 이는 파일의 끝이라 더 읽을 것이 없다는 뜻이된다.
출력의 경우는 위와 같지만 입력의 경우는 아래 두 가지 경우 중 하나를 만족할 때 EOF를 반환한다.
키보드 입력에는 파일의 끝이 없기 때문에 커맨드 키를 통해 EOF를 반환하도록 약속한 것이다.
#include <stdio.h>
int main() {
int ch;
while (1) {
ch = getchar();
if (ch == EOF) break;
putchar(ch);
}
return 0;
}
>>> Hi~
Hi~
I like C lang.
I like C lang.
^Z
생각해보면 문자인데 int에다가 담는다.
그 이유는 위의 두 함수가 반환하는 값 중 하나인 EOF는 -1로 정의된 상수이기 때문이다.
char를 unsigned char로 처리하는 컴파일러에 의해 컴파일 되면, EOF는 엉뚱하게 양의 정수로 변환이 되버리고 만다.
그런 불상사를 막기 위해 int를 사용해 -1을 유지할 수 있는 int형으로 반환형을 정의해 놓은 것이다.
생각해보면 앞에서 배운 printf, scanf를 사용하면 될 것 같은데 왜 굳이 귀찮게 문자 단위 입출력을 사용하는 것일까??
printf, scanf는 서식지정을 통해서 새로운 입출력의 형태를 구성하는 함수로, 아주 유용하지만 거기에서 오는 단점도 있다.
단점;
위와 같은 단점도 존재해 단순히 문자 하나를 입출력 하는 것이 목적이라면 printf, scanf를 사용하는 것보다 문자 단위 입출력 함수를 쓰는 것이 좋다.
앞에서는 문자 단위 입출력 함수였다면 지금은 문자'열' 단위 입출력 함수이다.
scanf는 공백이 포함된 문자열의 경우에는 입력 받는데 제한이 있었다.
하지만 문자열 입력 함수는 공백을 포함하는 문자열도 입력 받을 수 있다.
#include <stdio.h>
int puts(const char * s);
int fputs(const char * s, FILE * stream);
// 성공 시 음수가 아닌 값을, 실패 시 EOF 반환
puts 함수는 출력의 대상이 stdout으로 결정되어 있지만, fputs 함수는 두 번째 매개변수를 통해서 출력의 대상을 결정할 수 있다.
또한 둘 다 첫 번째 인자로 전달되는 주소 값의 문자열을 출력하지만, 출력의 형태에 차이가 있다.
#include <stdio.h>
int main(void)
{
char * str = "Simple String";
printf("1. puts test ----- \n");
puts(str);
puts("So Simple String");
printf("2. fputs test ----- \n");
fputs(str, stdout); printf("\n");
fputs("So Simple String", stdout); printf("\n");
printf("3. end of main ------ \n");
return 0;
}
큰 차이는 아니지만 코드를 보면 \n
의 차이가 보인다
puts
함수는 자동 개행이 이루어지지만, fputs
함수는 자동 개행이 이루어지지 않는다.
#include <stdio.h>
char * gets(char * s);
char * fgets(char * s, int n, FILE * stream);
// 파일 끝에 도달하거나 함수 호출 실패시 NULL 포인터 반환
문자열 입력 함수는 앞에서 배웠던 것들과 좀 다르다
선언 부터 char *
로 선언하며, (아마 g++ 컴파일러면 const char *
로 해야할 것이다.) 호출하는 방법도 다르다
int main() {
char str[7]; // 7바이트 메모리 공간 할당
gets(str); // 입력 받은 문자열을 배열 str에 저장
문장 구성은 아주 간단하다. 하지만 선언해놓은 배열을 넘어가게 되면 할당하지 않은 메모리에 침범해 에러가 발생한다.
그래서 gets 보다는 대체로 fgets 함수로 호출하는 것이 낫다.
int main() {
char str[7];
fgets(str, sizeof(str), stdin);
// stdin으로부터 문자열 입력 받아서 str에 저장
fgets 함수 호출의 정의는 다음과 같다
stdin으로 문자열을 입력 받아, 배열 str에 저장하는데, sizeof(str)의 길이만큼 저장한다.
예를 들어 "123456789"을 입력한다면 sizeof(str)의 반환 값인 7 - 1
인 6 길이 만큼의 문자열이 저장된다.
왜 한개를 제외할까?? 문자열하면 항상 마지막에 나오는 널 문자를 잊지말자
널 문자가 들어갈 자리를 만들고 마지막에 널 문자를 자동으로 추가해준다.
또 다른 fgets 함수의 특징이 있다
아래 코드를 실행하고 확인해보자
#include <stdio.h>
int main() {
char str[7];
int i;
for (i=0; i<3; i++) {
fgets(str, sizeof(str), stdin);
printf("Read %d : %s \n", i+1, str);
}
return 0;
}
>>> 실행결과
input : 12345678901234567890
Read 1 : 123456
Read 2 : 789012
Read 3 : 345678
위 결과를 보면 배열의 길이보다 입력 문자열의 길이가 길기 때문에 입력 배열 길이 -1의 길이만큼 문자열을 읽어 들이고 있다.
하지만 입력은 한번만 했지만 3번 출력한 것을 확인할 수 있다.
다음은 위 코드에서 str[5]
로 변경하고 we, like, you
를 각각 입력할 수 있도록 수정해보자
we
Read 1 : we
like
Read 2 : like
you
Read 3 : you