문자, 문자열 관련 함수

ROK·2022년 7월 21일
0

스트림과 데이터의 이동

입력과 출력이란??

프로그램을 기준

  • 프로그램 안으로 데이터가 흘러 들어가는 것이 입력
  • 프로그램 밖으로 데이터가 흘러 나가는 것이 출력

대표적인 입출력 장치는 아래와 같다.

  • 입력 : 키보드, 파일
  • 출력 : 모니터, 파일

그 밖에도 일반적으로 컴퓨터에 연결되는 마우스, 프린터, 카메라 등 다양한 장치들도 입출력 장치에 해당한다.

키보드, 모니터, 프로그램은 기본적으로 연결되어 있는 개체가 아닌 서로 떨어져 있는 개체들이다.
이렇게 각각 떨어져 있는 개체들을 연결시켜주는 다리가 필요한데 이를 스트림(stream)이라한다.

위 사진을 보면, 실행중인 프로그램과 모니터 사이에는 출력 스트림 이라는 다리가 놓여져 있고 데이터가 모니터로 가고, 키보드와 프로그램 사이에는 입력 스트림 이라는 다리가 놓여져 있고 데이터가 키보드에서 프로그램으로 흘러간다.

우리가 자주 사용했던 printf, scanf 함수를 통해 데이터를 입출력하는 근본적인 이유가 저 사진을 보면 알 수 있다.

스트림의 생성과 소멸

Console = 키보드, 모니터
콘솔 입출력과 파일 입출력에는 차이가 있다.

파일을 연결하는 스트림의 생성은 직접 요구해야 하지만, 콘솔과의 연결 스트림은 요구할 필요없이 자동적으로 생성이 된다.

콘솔 입출력 스트림은 프로그램이 실행되면 자동으로 생성되고, 프로그램이 종료되면 자동으로 소멸된다.

이를 가리켜 표준 스트림(Standard Stream)이라 한다.
표준 스트림에는 3가지가 있다

  • stdin : 표준 입력 스트림 // 키보드 대상으로 입력
  • stdout : 표준 출력 스트림 // 모니터 대상으로 출력
  • stderr : 표준 에러 스트림 // 모니터 대상으로 출력

stdout과 stderr은 모니터를 대상으로 출력이 이루어 진다는 점은 동일하다. 하지만 후에 입출력 리다이렉션(redirection)이라는 기술(주로 유닉스, 리눅스에서 배운다)을 익히면 stderr의 출력 대상을 변경할 수 있기 때문에 stdout과 stderr을 구분 할 수 있다.

문자 단위 입출력 함수

위의 입출력 원리와 스트림의 이해가 된 이후 아래 함수를 같이 공부하는 것이 좋다

문자 출력 함수 : putchar, fputc

모니터로 하나의 문자를 출력할 때 일반적으로 사용하는 함수는 두 가지 이다.

#include <stdio.h>

int putchar(int c);
int fputc(int c, FILE * stream);

// 함수 호출에 성공하면 쓰여진 문자 정보가, 실패하면 EOF를 반환한다.

putchar 함수

putchar 함수는 인자로 전달된 문자정보를 stdout으로 표현되는 표준 출력 스트림으로 전송하는 함수
즉, 인자로 전달된 문자를 모니터로 출력하는 함수라 할 수 있다.

fputc 함수

fputc 함수도 문자를 전송하는 측면에서는 putchar 함수와 동일하지만, fputc 함수는 문자를 전송할 스트림을 지정할 수 있다.
즉, fputc 함수를 이용하면 stdout 뿐 아니라, 파일을 대상으로 데이터를 전송할 수 있다.

그래서 fputc 함수의 두 번째 매개변수는 문자를 출력할 스트림의 지정에 사용된다. 여기에 stdout을 전달하면 putchar와 동일한 함수가 된다.

문자 입력 함수 : getchar, fgetc

키보드로 하나의 문자를 입력 받을 때 일반적으로 사용하는 함수 두 가지이다.

#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

EOF는 End Of File의 약자로, 파일의 끝을 표현하기 위해 정의해 놓은 상수

파일을 대상으로 fgetc 함수가 호출되고, 그 결과로 EOF가 반환되면, 이는 파일의 끝이라 더 읽을 것이 없다는 뜻이된다.

출력의 경우는 위와 같지만 입력의 경우는 아래 두 가지 경우 중 하나를 만족할 때 EOF를 반환한다.

  • 함수 호출의 실패
  • Windows : Ctrl + Z, Linux : Ctrl + D 를 입력할 경우

키보드 입력에는 파일의 끝이 없기 때문에 커맨드 키를 통해 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, int에 문자를 담는 이유??

생각해보면 문자인데 int에다가 담는다.

그 이유는 위의 두 함수가 반환하는 값 중 하나인 EOF는 -1로 정의된 상수이기 때문이다.
char를 unsigned char로 처리하는 컴파일러에 의해 컴파일 되면, EOF는 엉뚱하게 양의 정수로 변환이 되버리고 만다.
그런 불상사를 막기 위해 int를 사용해 -1을 유지할 수 있는 int형으로 반환형을 정의해 놓은 것이다.

✔ 문자 단위 입출력 함수가 왜 필요한가??

생각해보면 앞에서 배운 printf, scanf를 사용하면 될 것 같은데 왜 굳이 귀찮게 문자 단위 입출력을 사용하는 것일까??

printf, scanf는 서식지정을 통해서 새로운 입출력의 형태를 구성하는 함수로, 아주 유용하지만 거기에서 오는 단점도 있다.

단점;

  • 사용하는 메모리 공간이 크다
  • 해야하는 연산의 양이 많아 상대적으로 속도 느리다.
  • 별도 서식지정을 해줘야해 문장 구성도 번거롭다

위와 같은 단점도 존재해 단순히 문자 하나를 입출력 하는 것이 목적이라면 printf, scanf를 사용하는 것보다 문자 단위 입출력 함수를 쓰는 것이 좋다.

문자열 단위 입출력 함수

앞에서는 문자 단위 입출력 함수였다면 지금은 문자'열' 단위 입출력 함수이다.

scanf는 공백이 포함된 문자열의 경우에는 입력 받는데 제한이 있었다.
하지만 문자열 입력 함수는 공백을 포함하는 문자열도 입력 받을 수 있다.

문자열 출력 함수 : puts, fputs

#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 함수는 자동 개행이 이루어지지 않는다.

문자열 입력 함수 : gets, fgets

#include <stdio.h>

char * gets(char * s);
char * fgets(char * s, int n, FILE * stream);
// 파일 끝에 도달하거나 함수 호출 실패시 NULL 포인터 반환

문자열 입력 함수는 앞에서 배웠던 것들과 좀 다르다
선언 부터 char *로 선언하며, (아마 g++ 컴파일러면 const char *로 해야할 것이다.) 호출하는 방법도 다르다

gets 함수 호출

int main() {
	char str[7]; 	// 7바이트 메모리 공간 할당
    gets(str);		// 입력 받은 문자열을 배열 str에 저장

문장 구성은 아주 간단하다. 하지만 선언해놓은 배열을 넘어가게 되면 할당하지 않은 메모리에 침범해 에러가 발생한다.

fgets 함수 호출

그래서 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
profile
하루에 집중하자

0개의 댓글