c언어 문자 & 문자열 관련 함수 정리
->스트림, 문자 및 문자열 단위 입출력 함수 (putchar, fputc, getchar, fgetc, puts, fputs, gets, fgets), 입출력 버퍼를 비우는 방법, 문자열 관련 함수(strlen, strcpy, strncpy, strcat, strncat, strcmp, strncmp)
스트림 (stream)
데이터의 입력 / 출력을 위해 프로그램 상에서 입력/출력장치를 서로 연결시켜주는 다리의 역할을 하는 매개체를 '스트림(stream)'이라고 한다.
운영체제에서 외부장치와 프로그램간의 데이터 입/출력의 도구가 되는 스트림을 제공하는데,
실행 중인 프로그램과 기억장치(하드, ssd 등)에 저장되어 있는 파일과의 연결을 위한 스트림의 형성을 운영체제에 직접 요구하기도 한다.
프로그램을 실행하면 자동으로 입/출력 스트림이 생성되며, 프로그램을 종료하면 자동으로 소멸한다.
* 표준 스트림(standard stream) - 기본으로 제공됨
stdin (표준 입력 스트림)
stdout (표준 출력 스트림)
stderr (표준 에러 스트림) - 표준 출력 스트림과 동일하게 출력이 이루어지지만, 입출력 리디렉션(redirection)을 통해 표준 에러 스트림의 출력 대상을 변경시킬 수 있으므로 stdout과 용도가 구별된다.
문자 단위 입출력 함수
------
getchar - int getchar(void)
stdin으로부터 하나의 문자를 입력받아서 반환
fgetc - int fgetc (FILE*stream)
하나의 문자를 입력받아서 반환 + 문자를 입력받을 스트림을 지정할 수 있다.
------
-> 파일의 끝에 도달하거나, 함수 호출 실패 시 EOF 반환
•EOF (End Of File) : 파일의 끝을 표현하기 위해 정의해둔 상수
------
putchar - int putchar (int c)
전달된 문자 정보를 stdout으로 전송하는 함수 (출력)
fputc - int fputc (int c, FILE * stream)
마찬가지로 문자 정보를 전송 (출력) + 문자를 전송할 스트림을 지정할 수 있다. - stdout을 지정하면 putchar함수와 동일
------
-> 함수 호출 성공 시 문자 정보가 출력 / 실패 시 EOF 반환
두 개의 변수 ch1, ch2을 만들었는데
앞에서 다룬 문자열 함수 4가지를 모두 사용해보았다.
변수들의 자료형으로 int를 사용한 이유는 다음과 같다.
1. EOF는 '-1'로 정의된 상수이다.
2. char을 unsigned char로 처리하는 컴파일러가 존재하는데, 이런 컴파일러를 사용하였을 경우 EOF의 변환 과정에서 '+1' 즉, 양수로 형변환이 일어날 수 있다.
=> 반환되는 값을 그대로 유지하기 위해 항상 -1을 인식할 수 있는 int형으로 반환형을 정의하였다.
* printf, scanf 함수와의 차이점
printf, scanf는 서식지정을 통해 새로운 입출력 형태를 구성하는 함수이기 때문에
메모리를 많이 사용하며, 연산의 양도 상대적으로 많기 때문에 속도가 느릴 수 있다.
따라서, 단순히 문자 하나를 입출력 하는 것이 목적이라면
getchar, fgetc, putchar, fputc를 사용하는 것이 더 효율적일 수 있다.
문자열 단위 입출력 함수
------
gets - char * gets (char *s)
다음과 같이 사용된다.
char str[10]; //10바이트의 메모리 공간 할당
gets(str);
-> 할당받은 메모리를 넘어서는 문자열이 입력되면 오류가 발생한다.
gets 함수를 사용해서 간단한 입출력 코드를 작성했는데,
char str[7];
gets(str);
printf ("result : %s\n", str);
위 코드를 작성해서 실행해보았더니
실행하자마자 warning 메세지가 뜬다.
12345678901234567890을 입력하였더니 결과값은 출력이 되긴 하지만
위와 같은 무시무시한 화면이 떠버린다.
잘은 모르겠지만 gets 함수를 사용할 때는 주의가 필요한 듯 하다.
-------
------
fgets - char * fgets (char *s, int n, FILE *stream)
다음과 같이 사용된다.
char str[10];
fgets (str, sizeof(str), stdin); //stdin으로부터 문자열을 입력받아 str에 저장
->
문자열을 입력받으면 문자열의 끝에 자동으로 NULL 문자가 추가된다.
따라서 반환값(위의 코드의 경우 10)보다 하나가 작은 길이의 문자열을 저장한다.
또한 fgets 함수에는 다음과 같은 특징이 있다.
위 코드를 작성하고,
위와 같은 값들을 차례로 입력했을 때의 결과이다.
(차례대로 1234567890엔터, 123456엔터, 12 345 6엔터)
1. 1234567890엔터를 입력한 경우 result 1,2를 살펴보면
배열의 반환값인 7보다 하나가 적은 6개만 str(소스에서 사용한 변수)에 저장되고, 출력된다.
또한, fgets 함수는 0 다음에 입력한 엔터값 '\n'도 문자열의 일부로 받아들인다.
따라서 printf에 쓰여진 \n과 더불어 두번의 \n이 들어가있기 때문에
결과 화면에서 보이는 'result 2: 7890' 줄과, 다음 입력하는 부분인 '123456' 줄 사이에 한 줄의 공백이 더 생기게 된다.
2. 123456엔터를 입력한 경우 result 3,4를 살펴보면
마찬가지로 배열의 반환값보다 하나가 적은 6개만 str에 저장되고 출력되는데,
이 경우 result3에서 123456까지의 값을 저장/출력하고,
result4에서 그 뒷부분인 엔터(\n)를 저장/출력하기 때문에 위와 같은 결과를 확인할 수 있다.
3. 12 345 6엔터를 입력한 경우 result 5,6을 살펴보면
중간에 있는 공백(띄어쓰기) 부분도 문자열로 인식하는 것을 확인할 수 있다.
=>
fgets 함수는 문자열을 입력받을 경우, \n을 만날 때까지 문자열을 읽어들이며,
입력값에 있는 공백(띄어쓰기)과 \n을 모두 문자열의 한 값으로 받아들인다.
또한, 마지막 자리에는 NULL값이 자동으로 추가되기 때문에 반환값보다 1 적은 크기의 문자열이 저장된다.
scanf함수를 통해서는 공백을 포함하는 형태의 문자열을 입력받을 수 없다.
------
puts - int puts (const char *s)
하나의 문자열을 출력하는 함수, 출력대상이 stdout으로 정해져있다.
문자열 출력 후 자동으로 개행된다. (자동 엔터)
->성공 시 음수가 아닌 값을 반환 / 실패 시 EOF 반환
fputs - int puts (const char *s, FILE *stream)
하나의 문자열을 출력하는 함수, 출력 대상을 지정할 수 있다.
문자열 출력 후 개행되지 않는다. (수동으로 \n 처리를 해야한다.)
위와 같이 puts, fputs 함수를 동일한 방법으로 (\n을 사용하지 않고) 사용해보았다.
결과는 다음과 같다.
puts 함수는 자동으로 엔터 처리가 되는 반면,
fputs 함수는 별도로 \n의 출력 명령을 내리지 않는 경우 다음 줄로 넘어가지 않는다.
입출력 버퍼
키보드가 눌러질 때마다 출력시키는 것보다,
중간에 메모리 버퍼를 두고 데이터를 한 번에 이동시키는 것이 더 빠르고 효율적이다.
가령, fgets 함수를 사용했을 때
입력된 값이 입력 stream을 거쳐 입력buffer로 들어가는 시점은 '엔터 키'가 눌러질 때이며, 그 전에는 입력 버퍼가 비워져 있다.
------
fflush - int fflush(FILE *stream)
출력 버퍼를 비우는 함수, 특정 스트림의 버퍼를 비운다.
파일을 대상으로 호출할 수도 있으며, 일반적인 os의 경우 stdout을 대상으로 fflush 함수를 사용할 일은 많지 않다고 한다.
출력 버퍼를 대상으로 사용하는 함수이기 때문에
만약 stdin을 대상으로 함수를 사용하는 경우, 입력버퍼가 지워지는 것이 아니라 어떠한 결과가 일어날지 예측할 수 없다.
------
여러 가지 데이터를 입력받아야하는 상황에서,
입력버퍼를 비울 수 있는 방법은 다음과 같다.
while (getchar()!='\n') 문을 바로 사용하거나, 함수로 추가하여 사용한다.
위의 소스는 생년월일과 이름을 각각 입력받고, 출력하는 프로그램이다.
while (getchar()!='\n')
을 중간에 사용하지 않았다면, 생년월일을 6자리로 입력했다고 하더라도,
입력버퍼에 남아있는 \n 때문에 이름값을 입력하지 못하고 프로그램이 종료된다.
만약 이를 해결하기 위해 birth[8]로 수정한다고 해도, 여전히 문제가 발생한다.
생년월일에 6자리의 숫자만 입력했을 경우에는
이름값을 입력받을 수는 있게 되지만
만약 필요한 글자 수보다 많은 값을 입력하게 되면
마찬가지로 이름값을 입력하지 못하고 엉뚱한 값을 저장, 출력하게 된다.
가령,
생년월일을 입력받는 부분에서 3483729357239857와 같이 긴 문자열을 입력받을 경우,
앞의 7자리를 생년월일로 받아들이며, 이름은 그 뒷부분이 출력될 뿐 문제가 해결되지 않는다.
이러한 여러가지 문제점들을 해결하기 위해,
필요한 데이터들을 입력받는 사이에 입력버퍼를 비워주는 과정이 필요한 것이며
그 과정에서 앞서 언급한 while 절을 사용하는 것이다.
두 가지 데이터만 입력받는 상황을 가정했기 때문에
한 줄만 추가해서 사용했지만,
더 많은 데이터를 입력받는 경우 while(getchar()!='\n')을 아예 함수화시켜서 사용하는 것이 편할 수 있다.
void ClearReadbuffer(void)
{
while (getchar() != '\n');
}
처럼 함수를 정의하여 필요한 부분마다 해당 함수를 입력해주면 된다.
입력 버퍼를 통째로 비울 수 있는 어떠한 주어진 함수인 것은 아니고,
\n을 읽을 때까지 입력버퍼에 저장된 문자들을 지우는 역할을 한다.
string.h 헤더파일에 선언된 문자열 관련 함수 (strlen, strcpy, strncpy, strcat, strncat, strcmp, strncmp)
--------
strlen - size_t strlen(const char *s)
문자열 길이 반환 (널 문자는 길이에 포함되지 않는다.)
size_t는 다음과 같이 선언되어 있다.
typedef unsigned int size_t; // size_t == unsigned int
strlen 함수의 반환형은 size_t이기 때문에 unsigned int형 변수에 저장하고 %u로 출력하는 것이 정확한 방법이지만,
문자열의 길이가 아무리 길어도 int형 변수에 저장이 가능하기 때문에 보통 int형 변수에 저장하고, %d로 출력하는 일이 더 흔하다고 한다.
ex) fgets를 통해 문자열을 입력받는 경우, '\n'문자를 문자열에서 제외시키고 싶을 때
위와 같이 코드를 작성했을 때,
어떤 문장을 입력하면, 함수 RemoveN을 사용했을 때 \n이 문자열에서 제외되는 것을 확인할 수 있다.
주의메시지가 떠있는 이유는 strlen을 unsigned int가 아닌 int로 사용했기 때문인데,
int 대신 size_t를 사용하면 해당 메시지는 뜨지 않게 되지만, int를 사용해도 정상적으로 출력된다.
printf 함수에서 나타나는 노란 경고메시지도 마찬가지로 strlen을 %d로 출력했기 때문인데, 주의메시지는 뜨지만 정상적으로 출력된다.
-------
strcpy - char *strcpy(char *dest, const char *src)
strncpy - char *strncpy(char *dest, const char *src, size_t n)
문자열을 복사하는 함수
strcpy(str1, str2) //str2의 문자열을 str1에 복사한다.
strncpy(str1, str2, sizeof(str1)) //str2의 문자열을 sizeof(str1)만큼의 길이만큼 str1에 복사
strcpy의 경우, 복사할 문자열의 길이보다 문자열을 받을 배열의 길이가 작지 않도록 사용해야 하고,
strncpy의 경우, 문자열을 받을 배열의 길이가 더 짧아도 받을 수는 있지만 따로 NULL 문자를 삽입해야 한다.
puts 함수를 이용해 출력을 할 때, NULL 문자 이전까지 출력을 해야 하는데,
strncpy를 통해 문자열을 복사를 했고, 문자열을 단순하게 복사하기 때문에 마지막에 NULL문자가 저절로 삽입되지 않기 때문에
위와 같이 NULL문자를 따로 삽입해주지 않는다면 정상적으로 출력되지 않는다.
-------
strcat - char *strcat (char *dest, const char *src)
strncat - char *strncat (char *dest, const char *src, size_t n)
문자열의 뒤에 다른 문자열을 복사하는 함수
원래 문자열의 NULL문자가 있는 위치부터 복사된 문자열이 시작되며,
strncat의 경우, 복사할 문자열 중 최대 n개를 원래 문자열의 뒤에 덧붙이게 된다.
여기서 n개에는 NULL문자는 포함되지 않기 때문에, 실제로는 NULL값을 포함한 총 n+1개의 문자가 덧붙여지게 된다.
strncat함수와는 달리, 문자열의 마지막에 NULL문자를 자동으로 삽입한다.
--------
strcmp - int strcmp (const char *s1, const char *s2)
strncmp - int strncmp (const char *s1, const char *s2, size_t n)
문자열을 비교하는 함수, 두 문자열을 비교하여 내용이 같다면 0을, 같지 않다면 0이 아닌 값을 반환한다.
또한 문자열을 비교할 때 아스키 코드 값을 기준으로 비교한다. 문자열 s1, s2를 비교한다고 가정했을 때 반환되는 값은 아래와 같다.
s1 > s2
: 0보다 큰 값을 반환 (양수라는 것만 알면 되고, 어떤 '값'인지는 컴파일러에 따라 다르다.)
s1 == s2
: 0을 반환
s1 < s2
: 0보다 작은 값을 반환 (마찬가지로 음수라는 것만 알면 된다.)
strncmp의 경우, n개까지의 문자열을 부분적으로 비교한다.
첫 if 문에 (!strcmp(s1,s2))을 사용했는데,
c언어에서는 '0'이라는 값을 '거짓'으로, 나머지 값을 '참'으로 인식한다.
따라서 strcmp 함수를 사용했을 때 문자열이 동일하다면 0이 반환되는데, 이 값은 c언어에서 '거짓'으로 인식하므로,
!을 사용해서 거짓의 반대인 참인 경우를 가정하게 되는 것이다.
또한, 아래에 사용한 if 문에서 앞의 3글자만 비교하는 strncmp 함수도 사용했다.
s1, s2의 문자열은 동일하지 않지만, 앞의 3글자인 'APP'이 동일하므로 결과는 아래와 같이 출력된다.
*** 흔히 하는 오해
문자열 비교를 하기 위해 strcmp 혹은 strncmp 함수를 사용하지 않고,
위와 같이 단순히 if문을 이용해서 두 배열을 비교해도 같은 결과가 나올 것이라고 오해할 수 있는데,
배열의 이름은 배열의 주소값(포인터)을 의미하므로
문자열이 비교되는 것이 아니라, 두 배열의 주소값을 비교하게 된다.
오른쪽에 노란색의 주의 메시지에 해당 내용이 적혀있다.
'C' 카테고리의 다른 글
열혈강의 자료구조 - 연습문제 2장 01번 (0) | 2021.01.20 |
---|---|
macbook/mac에서 VS code를 이용, c언어 개발환경 구축하기 (2) | 2020.10.31 |
c언어 - 자료형, 형식지정자 정리 (0) | 2020.10.20 |
열혈c - 도전!프로그래밍3 , 도전6 야구게임 (0) | 2020.10.13 |
열혈c - 도전!프로그래밍3, 도전2 달팽이배열 (0) | 2020.10.12 |