티스토리 뷰

728x90

정보 처리 과정을 가장 단순한 구조로 설명할 때 입력(Input)-처리(Process)-출력(Output)으로 말하는 것처럼 프로그램에 있어 입력과 출력 과정은 프로그램의 정체성을 나타내는 매우 중요한 부분입니다. 요즘이야 스마트폰으로 다양한 센서 정보를 받기도 하지만 전통적인 C언어의 표준 입력키보드이고 C언어에서는 표준 입력에 대해서 stdin이라는 파일 포인터를 제공합니다. 프로그램에서 프린터나 인터넷과 같은 통신으로 출력을 내보낼 수도 있지만 C언어에서는 표준 출력은 화면이고 C언어에서는 표준 출력에 대해서 stdout이라는 파일 포인터를 제공합니다. 

C언어에서는 stdin, stdout, stderr과 같은 시스템 파일포인터와 함께 표준 입출력을 위한 라이브러리 함수를 제공하는데 표준 입력에 대해서는 문자 하나를 읽는 getchar(), 문자열을 읽는 gets(), 형식을 지정해서 읽는 scanf() 함수를 지원합니다. 표준 출력에 대해서는 문자 하나를 출력하는 putchar(), 문자열을 출력하는 puts(), 형식을 지정해서 출력하는 printf() 함수를 지원합니다.

23. 다음 중 scanf 함수에서 정수 값을 입력 받기 위해 사용하는 입력 형식은?
① "%d" ② "\r" ③ "%f" ④ "\n" ⑤ "%%"

위의 문제는 표준 입력으로부터 형식을 지정해서 읽는 scanf()함수에 관련한 것으로 scanf()함수의 기본 형식은 아래와 같습니다.

int scanf ( const char * format, ... );

위와 같은 코드를 C언어에서는 함수 프로토타입(Function prototype)이라 하는데 일반적으로 소스 코드없이 오브젝트 코드나 라이브러리로 제공되는 함수를 사용할 수 있도록 함수의 리턴 타입, 함수 이름, 파라미터 타입과 이름등을 기술합니다. 위의 scanf 함수의 리턴 타입은 int 정수형으로 성공하면 성공적으로 값이 채워진 변수의 개수를 리턴하고 실패하면 EOF(통상 -1)를 리턴합니다. scanf의 첫 파라미터 타입은 문자열 포인터(char *)인데 앞에 const가 붙은 이유는 scanf 함수에서 해당 파라미터를 변경하지 않는다는 의미입니다. 마침표 3개를 연달아 적는 ...(ellipsis)도 C언어 문법의 하나입니다. ...은 파라미터가 0부터 n개 까지 가변적으로 기술될 수 있음을 의미하는 것으로 scanf, printf 처럼 다양한 C 함수에서 사용합니다. 함수 프로토타입에서 파라미터의 타입은 필수이지만 이름은 생략할 수 있습니다. 위의 scanf 프로토타입에서 format이라는 파라미터 이름은 생략할 수 있다는 의미입니다.

scanf 함수는 표준 입력(키보드)으로부터 입력을 받아서 첫 파라미터로 지정한 입력 형식에 따라 두번째 이후로 전달되는 변수의 포인터가 지정하는 곳에 값을 저장해 주므로 scanf 호출시 정상적으로 데이터를 저장할 수 있는 변수의 포인터를 전달해야만 합니다. 참고로 C언어에서는 기본 변수나 구조체 변수 앞에 &(ampersand)를 붙여 해당 변수의 포인터를 구할 수 있으며 배열의 경우에는 배열명 자체가 포인터로 취급되지만 "&ary[0]" 처럼 배열의 첫 원소의 포인터임을 명확히 할 수도 있습니다. 

%[*][폭][길이]지정자 

위의 문법은 scanf의 형식 지정 문법(format)으로 %와 지정자는 필수이지만 대괄호([])로 감싸진 것들은 옵션으로 필요에 따라 붙일 수 있습니다. 다음은 지정자의 종류와 설명입니다.

  •  i
    정수형. 10진수, 8진수(0으로 시작), 16진수(0x로 시작)를 받아들일 수 있습니다.
  •  d, u
    십진 정수형. u는 Unsigned 정수를 의미 합니다.
  •  o
    8진수 정수형. 변수는 unsigned 변수로 받음.
  •  x
    16진수 정수형. 변수는 unsigned 변수로 받음.
  •  f, e, g, a
    부동소수점수. a는 16진수로 부동소수점를 입력 받을 때 사용합니다.
  •  c
    문자 n개를 입력받아 전달받은 문자열에 저장합니다. 주의할 것은 아래의 s와 달리 C언어에서 문자열 끝을 표시하는 널(Null) 문자를 붙이지 않습니다. 폭을 지정하지 않으면 1로 간주합니다.
  •  s
    문자열 입력으로 입력된 내용에서 문자열의 끝은 화이트 스페이스(White space)를 만날때 까지를 하나의 문자열로 간주하고 문자열을 버퍼에 저장할 때 널 문자를 끝에 붙여줍니다. 화이트 스페이스는 공백이나 탭, 개행문자등을 의미합니다.
  •  p
    포인터 값 직접 입력
  •  n
    현재 시점까지 입력된 문자의 개수를 전달된 파라미터에 저장합니다. 입력을 진행하지는 않습니다.
  •  %  
    %문자 자체를 검사합니다.
main()
{
    int a = 0, b = 0, ret;
    long long ld = 0;
    float f = 0.0;
    char s[100], c[10];
    
    ret = scanf("%i=%s%n%%%f%2c%lld", &b, s, &a, &f, c, &ld);
    printf("\na:%d b:%d c:%c%c ld:%lld f:%f s:%s ret:%d", a, b, c[0], c[1], ld, f, s, ret);
}

위에서 언급한 지정자(Specifier)에 더불어서 scanf로 전달한 형식 문자열에 화이트 스페이스가 있으면 해당 부분은 그냥 무시됩니다. 그런데 %문자로 시작하는 규칙에 해당이 않되는 문자열이 지정된 경우에는 해당 문자열이 해당 위치에 입력되었는지는 검사(Matching)하고 일치하지 않으면 해당 지점에서 입력 받기를 중단합니다. 위의 예제 코드에서 형식 문자열에 기입한 "=" 문자와 "%%"가 그 예로 입력 스트림에서 해당 지점에서 지정한 문자열이 없으면 입력을 중단합니다.

---->
123=good %11.23AM34252222

a:8 b:123 c:AM ld:34252222 f:11.230000 s:good ret:5
---->
123 good 11.23

a:0 b:123 c:? ld:0 f:0.000000 s:? ret:1

위의 그림은 코드를 실행시킨 두가지 예제로 첫번째 예제는 입력 형식에 맞추어 입력 했기 때문에 5개의 변수에 모두 값이 전달되었지만 두번째 예제의 경우 첫 정수 입력은 성공했으나 "="문자가 지정 위치에 들어오지 않았기 때문에 1개 입력되었다는 의미로 scanf에서 1이 리턴되었습니다.

지정자 앞에 있는 옵션 항목중에 "*"는 해당 항목을 입력 받기는 하지만 무시하고 전달한 변수에 저장하지 않습니다. "폭"을 지정하면 현재 입력에서 읽을 수 있는 최대 문자수를 의미합니다. 예제 코드에서는 "%2c"로 문자 2개를 입력 받도록 했습니다. "길이" 옵션은 의미는 길이이지만 숫자를 지정하는 것이 아니고 데이터 저장소의 크기를 "hh, h, l, ll, z, L" 등의 문자를 지정자 앞에 붙여 저장소의 크기를 조정하는 것입니다. 예제 코드에서는 "%lld"가 사용되었는데 "%d"는 십진 정수를 입력 받아 int 타입에 저장하라는 지시이지만 아주 큰 값을 입력받아 저장소도 정수형 중에 가장 큰 long long 타입에 저장하기 위하여 "길이" 옵션을 "%lld"와 같이 사용한 것입니다.

"올림피아드 기출문제로 배우는 C언어 - 기본 문법"에서 언급한 것 처럼 C언어의 정수형 표현은 1바이트의 char부터 가장 크기가 큰 long long에 이르기 까지 다양한 타입을 지원하는데 "길이" 옵션은 정수 값 입력을 다양한 타입의 정수형 변수에 담을 수 있도록 조정합니다. 정수형에 대한 길이 옵션과 타입은 hh-char, h-short, l-long, ll-long long, z-size_t 와 같습니다. 실수형 값의 경우 기본적으로는 float 타입의 변수에 저장하지만 l 옵션을 la, le, lf, lg 처럼 지정자 앞에 붙이면 double 타입의 변수에 저장할 수 있고 L 옵션을 앞에 붙이면 long double 타입에 값을 저장할 수 있습니다.

int printf ( const char * format, ... );

%[플래그][폭][.정밀도][길이]지정자 

표준 출력으로 값을 형식에 맞게 출력하는 printf()함수의 프로토타입과 형식 문자열의 문법은 위와 같습니다. scanf와 크게 다르지 않고 방향만 바뀌었다고 생각해도 됩니다. 우선 지정자(Specifier)의 경우 scanf와 같고 추가해서 16진수와 부동소수점을 표현하는 x, f, e, g, a에 대해서 표시하는 영문자를 대문자로 표시할 수 있도록 X, F, E, G, A가 추가되었다는 차이점이 있습니다. scanf에서 %n이 해당 지점까지 입력받은 문자수를 파라미터로 전달해 주는 기능이라면 printf에서 %n을 현식 문자열에 사용하면 실제 출력하는 것은 아무 것도 없지만 해당 지점까지 출력한 문자의 개수를 파라미터로 전달한 변수에 전달해 줍니다.

printf를 통해서 다양한 형식으로 출력할 수 있지만, 같은 형식으로 스트링 출력에 사용하는 sprintf, 파일 출력의 fprintf 에서도 응용할 수 있기 때문에 printf의 옵션 항목들을 익혀둘 필요가 있습니다. "플래그" 옵션은 아래와 같이 좌우 맞춤, 채우기, 부호 표시 등을 제어할 수 있습니다.

  • - : 폭을 지정한 경우 폭보다 값이 작을때 기본은 우측 맞춤이지만, 지정하면 좌측 맞춤으로 변경

  • + : 기본은 음수일 경우만 앞에 '-'문자를 붙이지만, 지정하면 무조건 부호를 표시

  • 공백 : 기본은 음수일 경우만 앞에 '-'문자를 붙이지만, 지정하면 양수일 경우에도 공백을 표시

  • # : 8진수, 16진수에 대하여 0이 아닌 값에 대하여 0 또는 0x를 붙임. 실수의 경우 기본은 소숫점이 없으면 표시하지 않지만, 지정하면 소숫점 없는 실수도 소숫점 이하를 표시

  • 0 : 폭을 지정한 경우 폭보다 값이 작을때 기본은 공백으로 나머지를 체우지만, 지정하면 '0'으로 채움

"폭"옵션은 값을 출력할 최소 크기로 폭보다 내용이 작으면 나머지는 공백으로 채웁니다. 폭보다 내용이 크면 자르지는 않습니다. 폭을 형식 문자열에서 지정하지 않고 변수로 전달하려면 숫자 대신 "*"를 기술하고 해당 위치 다음 파라미터로 폭에 해당하는 정수값을 넘기면 됩니다. "정밀도" 옵션의 경우에도 "폭" 처럼 직접 정밀도를 지정하지 않고 ".*"로 지정하여 파라미터로 정밀도를 지정할 수 있습니다. ".숫자"로 정밀도를 지정하면 타입별로 다르게 적용되는데 정수형의 경우에는 최소로 표시할 자릿수로 값이 이보다 작으면 앞에 0을 채웁니다. 플래그의 '0'이 폭 전체에 적용한다면 폭 범위 내에서 표시하는 정수의 자릿수이므로 차이가 있습니다. 정밀도를 .0으로 했을때 값이 0이면 값을 표시하지 않습니다. 실수형중에서 a, e, f에 경우에 정밀도는 소숫점 크기를 의미합니다. 기본값은 6입니다. g 실수형의 경우에는 유효 숫자의 개수를 의미하고 문자열 s의 경우에는 문자열의 최대 출력 길이를 의미해서 이 값보다 큰 문자열은 표시하지 않습니다. printf() 함수가 정상적으로 수행되면 출력된 문자의 개수를 리턴하고 실패하면 -1을 리턴합니다.

복잡해 보이지만 몇가지 사례를 테스트하면서 이해하면 실제 프로그래밍 과정에서도 매우 유용하게 사용할 수 있습니다. 천천히 살펴보시길 권합니다.

728x90
댓글
최근에 올라온 글
최근에 달린 댓글
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함