C언어의 대표적인 헤더파일 중 하나인 <stdio.h>
아마 초심자 입장에서 가장 익숙한 헤더파일이지 않을까 한다.
그럼 stdio.h가 무슨 뜻일까?
많은 사람들이 그냥 스튜디오쩜에이치라고 읽는 것으로 아는데 (나포함)
실제로는
STanDard Input Output library의 약어라고 한다. (뒤에 .h는 header file이라는 뜻!)
직역하면 우리에게 익숙한 '표준 입출력 함수 라이브러리'이다.
그럼 이 표준 입출력 함수 라이브러리에는 어떤 함수들이 있냐?
크게 printf, scanf 등과 같은 일반적인 입출력 함수들이 있고,
fopen 등의 파일 입출력 함수가 있다.
파일 입출력 함수에 대해서는 나중에 배워보도록 하고
우선은 일반적인 입출력 함수!
그 중에서도 가장 대표적인 몇가지만 익혀보도록 하자.
※printf, scanf, gets(fgets), puts, getchar, putchar 등을 다룰 예정
printf
#include <stdio.h>
int main() {
int a = 3;
printf("%d", a);
return 0;
}
우리들의 영원한 친구 printf. 말 그대로 원하는 데이터를 출력하는 함수이다.
기초적인 내용은 생략하고, 내가 헷갈렸던 내용 위주로 정리를 해보겠다.
- 하나의 문자에 대한 서식 지정자(%c)와 문자열에 대한 서식 지정자(%s)는 다르다.
- printf문을 사용하여 변수의 값을 출력하고자 하는 경우, 변수명을 호출할 때 기본적으로 call by value 방식을 사용한다. 그러나 예외적으로 문자열을 저장하고 있는 배열을 출력할 때는 call by reference 방식을 채택한다. (아래 코드 참고)
#include <stdio.h>
int main() {
char a[] = "My name is Monkey D Luffy.";
printf("%s", a);
return 0;
}
위 코드에서 a는 문자형 배열의 시작 주소를 담고 있는 포인터 변수이다.
그럼에도 불구하고 printf 함수에서 인자로 받아 해당 문자열을 출력할 수 있다.
printf 함수에서 문자열을 출력할 때 또 명심해야할 점이 있는데, 바로 NULL문자 바로 앞까지 출력한다는 점이다.
문자열을 생성하면, 자동으로 해당 문자열의 끝에 NULL 문자가 생성된다.
다음 코드의 출력값으로 얼마가 출력될 지 예상해보자.
#include <stdio.h>
int main() {
char a[] = "Hello";
printf("%d", sizeof(a)/sizeof(char));
return 0;
}
H, e, l, l, o 다섯 글자를 배열에 지정해주었으니,
크기 5짜리 배열이 선언될 것이라고 생각했으나, 실제로는 6이 출력되었다.
이는 문자열 a의 마지막에 자동으로 NULL 문자가 생성되었기 때문이었다.
참고로, NULL 문자는 '\0' 이런 식으로 직접 생성할 수도 있다.
이를 이용해 의도적으로 문자열의 중간까지 출력할 수도 있을 것이다.
scanf
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
int a, b;
scanf("%d %d", &a, &b);
printf("a : %d\nb : %d\n", a, b);
return 0;
}
/*
<"3 6" 입력에 대한 출력 결과>
a : 3
b : 6
*/
scanf 또한 익숙한 함수일 것이다.
우선 짚고 넘어가야할 점은, 값을 하나 입력 받을 때마다 공백 혹은 enter 전까지의 값만 읽어온다.
따라서 공백을 포함한 문자열을 입력을 받고 싶다면 다음과 같이 조정해준다.
char a[10];
scanf("%[^\n]s", a);
※또는 아래에서 다룰 fgets 함수를 활용하는 방법도 있다.
또한 scanf는 printf 함수와 달리, 변수에 접근할 때 기본적으로
call by value가 아닌 call by reference 방식을 사용한다.
※ scanf의 두 번째 매개변수에 &가 붙는 이유
scanf와 call by reference에 대해서는 이전에 '포인터와 배열' 포스팅에서 다룬 적이 있으므로 짧게 다루고 넘어간다.
gets / fgets
다음으로 다뤄볼 함수는 gets 함수이다.
gets 함수는 'enter 키를 입력 받기 전까지 모든 문자열을 받아오는 함수'이다.
scanf가 다양한 자료형으로 입력 받을 수 있는 반면, gets는 문자열 특화이다.
이는 gets의 함수명에서도 유추해볼 수 있을 것이다.
※ get + s(tring)
앞서 다뤘듯 일반적인 형태인 scanf에서는 공백 전까지의 문자열만 읽어오기 때문에
해당 함수의 존재는 프로그래밍하는데 분명 편리한 요소가 될 듯하다.
그러나 결론부터 말하자면 gets 함수는 사용하지 않는 것이 좋을 듯하다.
우선, gets 함수는 다음과 같이 사용한다.
#include <stdio.h>
int main() {
char a[10];
gets(a);
return 0;
}
코드를 보면 알 수 있듯, gets 함수는 문자열 배열 a를 매개변수로 받아
사용자로부터 입력 받은 문자열을 a에 저장한다.
그러나, 입력 받는 과정에서 문자열의 크기에 대한 어떠한 제한도 없다.
따라서 위 코드에서와 같이 크기가 10인 배열에 입력을 받는데,
사용자가 배열의 크기를 초과하여 입력하면 프로그램이 죽어버린다.
이것이 gets 사용이 권장되지 않는 이유이다.
실제로 유닉스/리눅스에서 사용하는 표준 컴파일러인 gcc 컴파일러에서는
gets 함수 표준 함수에서 제외시켰다.
※ gets 함수를 사용하기만 해도 에러가 뜬다.
그러면 gets 함수의 대안으로는 무엇이 있을까?
물론 앞서 scanf에서 다뤘듯 scanf의 매개변수를 조금 만져주어 공백은 제외하고 입력받게 만들 수는 있다.
또다른 방법으로는 fgets 함수가 있다.
해당 방법은 우리가 gcc 컴파일러를 이용해 gets 함수를 컴파일 시켰을 때,
에러가 발생함과 함께 컴파일러에게 우리에게 권장해주는 대안이기도 하다.
그렇다면 fgets는 또 뭐하는 놈인지 알아보자.
fgets 함수의 이름을 분석해보자.
앞의 'f'는 'file'을 의미하며, 따라서 fgets 함수의 본래 역할은
파일에서부터 문자열을 읽어오는 것이다.
fgets 함수의 원형은 다음과 같다.
char* fgets(char* str, int num, FILE* pFile);
아직 file을 다루는 단계는 아니지만, 해당 함수의 매개변수들에 적절한 값을 넣으면
우리가 원하는 형식(공백을 포함해서 문자열 입력받기)으로 사용할 수 있다.
아래 예시를 살펴보자.
#include <stdio.h>
int main() {
char ary[10];
fgets(ary, sizeof(ary), stdin);
printf("%s", ary);
return 0;
}
위 코드에서와 같이
fgets(입력 받은 문자열을 저장할 배열, 입력 받을 배열의 크기, stdin);
//stdin -> 표준 입력창에서 입력을 받아오겠다
형태로 사용해 줄 수 있다.
그러나 해당 방법으로 문자열을 입력 받으면 또 하나의 문제점이 생긴다.
그것은 바로, 사용자가 입력을 마치고 누르는 enter키마저 문자열에 저장해버린다는 점이다
다음 코드의 출력 결과를 보면 이 말의 뜻을 이해할 수 있을 것이다.
#include <stdio.h>
#include <string.h>
int main() {
char ary[10];
fgets(ary, sizeof(ary), stdin); //"Hi" 입력
strcat(ary, "Jerry!");
printf("%s\n\n", ary);
for (int i = 0; i < sizeof(ary); i++) {
printf("%c %d\n", ary[i], i);
}
return 0;
}
/*
출력 결과
Hi
Jerry!
H 0
i 1
2
J 3
e 4
r 5
r 6
y 7
! 8
9
*/
두 문자열을 이어붙히는 strcat 함수를 사용하였는데,
열이 바뀌어서 이어붙혀졌다.
배열을 한 문자씩 출력해보니,
fgets 함수로 입력 받은 "Hi" 뒤에
enter키가 저장되어 있음을 알 수 있었다.
그러나 우리가 원하는 것은 뒤에 enter키가 포함되지 않은 문자열이다.
따라서 fgets 함수 뒤에 strchr 함수를 이용해 다음과 같은 처리를 해준다.
#include <stdio.h>
#include <string.h>
int main() {
char ary[10];
fgets(ary, sizeof(ary), stdin); //"Hi" 입력
*strchr(ary, '\n') = '\0';
strcat(ary, "Jerry!");
printf("%s\n", ary);
return 0;
}
/*
출력 결과
HiJerry!
*/
※strcat, strchr 등 문자열 처리 함수들에 대해서는 다른 포스팅에서 따로 다룰 예정
이로써 ary에 우리가 원하는 입력값을 저장해줄 수 있게 되었다.
공백을 포함하여 입력 받고 싶다면 위 코드와 같이
fgets 함수와 strchr 함수를 세트처럼 항상 묶어서 같이 사용하거나,
scanf("%[^\n]s, a) 구문을 이용하면 될 듯하다.
puts
원래는 gets와 세트로 쓰라고 만들어 놓은 듯한 puts..
gets는 안정성 문제로 사용하지 않게 되었지만,
puts 함수는 여전히 필요에 따라서 사용되는 것 같다.
일단 puts 함수는 문자열을 출력하는 함수이다.
puts 함수는 put + s(tring)으로, printf 함수와는 달리
문자열만 출력해주는 함수이다.
다음은 puts 함수를 사용한 예시이다.
#include <stdio.h>
#include <string.h>
int main(){
char a[10];
fgets(a, 10, stdin); //"Hi!" 입력
*strchr(a, '\n') = '\0';
puts(a);
return 0;
}
/*
출력 결과
Hi!
*/
puts 함수를 사용할 때 조심해야 할 부분이 하나 있다.
그것은 바로
출력을 하고나서 자동으로 줄을 바꿔준다는 것이다.
이 점을 명시하고 필요한 부분에 적절히 사용해주도록 하자!
getchar/putchar
getchar 함수는 말그대로 문자 하나를 읽어오는 함수이고,
putchar 함수는 문자 하나를 출력한다.
getchar 함수는 매개변수가 없으며, putchar 함수는 출력한 문자를 매개로 받는다.
#include <stdio.h>
int main(){
char ch;
ch = getchar(); //a 입력
putchar(ch); //a 출력, 줄바꿈X
return 0;
}
이때 두 문자는 당연하게도 '문자' 형태로 처리하며,
그 문자에 상응하는 아스키 코드로 사용해도 된다.
※ 물론 이때는 따옴표는 생략한다.
이에 따라 getchar의 값을 정수형 변수에 저장할 수도 있고, 문자형 변수에도 저장할 수 있는 것이다.
getchar 함수를 사용할 때 조심해야할 부분이 있는데,
이는 '입력 버퍼'와 연관이 되어있다.
getchar 함수는 입력 버퍼 맨 앞의 문자 하나를 가져온다.
만약 입력 버퍼가 비어있다면 사용자의 입력을 기다리겠지만,
이미 입력 버퍼가 입력돼있는 문자가 있다면, 해당 값을 채택할 것이다.
그런데 우리가 코딩을 할 때, 의도치 않게 입력 버퍼에 문자를 남겨놓는 일이 생긴다.
그 대표적인 경우로 scanf, getchar 등 무언가를 입력받는 함수들이 있다.
우선 우리가 인지해야할 점은, enter키 또한 엄연한 하나의 문자라는 점이다.
※ enter키 == '\n', tab키 == '\t', NULL문자 == '\0' 등
예를 들어 scanf에서 우리가 입력할 값을 치고 enter를 누른다.
그러면 scanf에서는 입력이 끝났다고 인지하고 enter를 누르기 전까지의 값들을 읽어간다.
이 때 enter키가 사실은 입력 버퍼에 남아있는 상태인 것이다.
이 상태에서 바로 getchar()를 실행시켜서 문자를 입력 받으려고 해도,
프로그램은 입력 받을 기회를 주지 않을 것이다.
→ 입력 버퍼 안에 남아있던 '\n' 문자를 읽어갔기 때문!!
따라서 우리는 입력버퍼를 비워줄 줄 알아야 한다.
이 비워주는 작업 또한 getchar 함수를 이용해서 한다!
아래 코드에서 getchar 함수의 3가지 사용 방법을 살펴보자.
#include <stdio.h>
int main(){
int a = getchar(); //문자 입력 받아 그 아스키 값을 정수형으로 저장
getchar(); //입력 버퍼 비우기
char b = getchar(); //문자 입력 받아 문자형으로 저장
return 0;
}
이와 같이 getchar()의 값을 어디에 저장해주지 않고 단독으로 실행하면
버퍼에서 문자를 하나 가져와서 버린다.
앞서 다뤘듯이, getchar 함수를 이용해서 버퍼를 비워주는 작업을 하는데,
간혹가다 버퍼에 2개 이상의 문자들이 입력되어 있을 수도 있다.
이런 상황에서도 원하는 값을 입력받게 하기 위해서는 다음과 같이 코드를 짠다.
while( getchar() != '\n'); //버퍼의 문자 값이 '\n'이 나올 때까지 읽어와서 삭제한다.
'C언어 > 문법' 카테고리의 다른 글
[C] 2차원 배열 (0) | 2024.02.21 |
---|---|
[C] 표준 라이브러리 <stdlib.h> (0) | 2024.02.20 |
[C] _CRT_SECURE_NO_WARNINGS (0) | 2024.01.15 |
[C] 동적 메모리 할당 (0) | 2024.01.12 |
[C] 포인터와 배열 간의 관계 (1) | 2024.01.11 |