scanf 대신 입력 변환에 무엇을 사용할 수 있습니까?


125

나는 다른 사람들 scanf이 더 나은 대안이 있다고 말하고 사용하지 못하게하는 사람들을 매우 자주 보았다 . 그러나 내가 보는 것은 "사용하지 마십시오 scanf" 또는 "올바른 형식 문자열이 있습니다" 이며 언급 된 "더 나은 대안"의 예는 결코 없습니다 .

예를 들어 다음 코드를 보자.

scanf("%c", &c);

마지막 변환 후 입력 스트림에 남아있는 공백을 읽습니다. 이에 대한 일반적인 제안 솔루션은 다음을 사용하는 것입니다.

scanf(" %c", &c);

또는 사용하지 마십시오 scanf.

scanf나쁘기 때문에 scanf일반적으로 처리 할 수있는 입력 형식 (예 : 정수, 부동 소수점 숫자 및 문자열) 을 변환하기위한 일부 ANSI C 옵션은 scanf무엇입니까?

답변:


87

입력을 읽는 가장 일반적인 방법은 다음과 같습니다.

  • fgets일반적으로 제안되는 고정 크기로 사용

  • 을 사용 fgetc하면 하나만 읽는 경우에 유용 할 수 있습니다 char.

입력을 변환하기 위해 사용할 수있는 다양한 기능이 있습니다.

  • strtoll문자열을 정수로 변환

  • strtof/ d/ ld, 문자열을 부동 소수점 숫자로 변환

  • sscanf아래에 언급 된 대부분의 오류가 있지만을 사용하는 것만 나쁘지 는 않습니다.scanf

  • 일반 ANSI C에서 구분 기호로 구분 된 입력을 구문 분석하는 좋은 방법은 없습니다. strtok_rPOSIX 또는에서 사용 하며 strtok스레드 안전하지 않습니다. 당신은 또한 수 있습니다 자신의 롤 사용하여 스레드 안전 변종 strcspnstrspn같은 strtok_r특별한 OS 지원을 포함하지 않습니다.

  • 그것은 잔인한있을 수 있습니다,하지만 당신은 렉서와 파서를 사용 (수 flexbison가장 일반적인 예이다).

  • 변환하지 않고 단순히 문자열을 사용하십시오.


나는 scanf 내 질문에 나쁜지 정확히 알지 못했기 때문에 자세히 설명 할 것입니다.

  • 변환 지정자로 %[...]하고 %c, scanf공백을 먹지 않습니다. 이것은 이 질문 의 많은 복제본들에 의해 입증 된 바와 같이, 널리 알려져 있지 않다 .

  • 의 인수 (구체적으로 문자열)를 &참조 할 때 단항 연산자를 언제 사용해야하는지에 대한 혼동이 있습니다 scanf.

  • 의 반환 값을 무시하는 것은 매우 쉽습니다 scanf. 이로 인해 정의되지 않은 동작이 초기화되지 않은 변수를 읽지 못하게 될 수 있습니다.

  • 에서 버퍼 오버플로를 방지하는 것을 잊어 버리는 것은 매우 쉽습니다 scanf. scanf("%s", str)나쁘지 않더라도 나쁘지 않습니다 gets.

  • 로 정수를 변환 할 때 오버플로를 감지 할 수 없습니다 scanf. 실제로 오버플로 는 이러한 함수에서 정의되지 않은 동작 을 유발 합니다.



56

scanf 나쁜가요?

가장 큰 문제는 scanf사용자 입력을 다루기위한 것이 아니라는 점 입니다. "완벽한"형식의 데이터와 함께 사용하도록 고안되었습니다. 나는 그것이 완전히 사실이 아니기 때문에 "완벽하게"라는 단어를 인용했다. 그러나 사용자 입력만큼 신뢰할 수없는 데이터를 구문 분석하도록 설계되지 않았습니다. 기본적으로 사용자 입력은 예측할 수 없습니다. 사용자는 지시 사항을 이해하지 못하고 오타를 만들고 실수로 Enter 키를 누르기 등을 수행합니다 stdin. 숙련 된 * nix 사용자 인 경우 설명이 놀라운 것은 아니지만 Windows 사용자를 혼란스럽게 할 수 있습니다. * nix 시스템에서는 배관을 통해 작동하는 프로그램을 구축하는 것이 매우 일반적입니다.stdoutstdin두 번째. 이렇게하면 출력 및 입력을 예측할 수 있습니다. 이러한 상황에서는 scanf실제로 잘 작동합니다. 그러나 예측할 수없는 입력으로 작업 할 때 모든 종류의 문제가 발생할 위험이 있습니다.

그렇다면 사용자 입력에 사용하기 쉬운 표준 기능이없는 이유는 무엇입니까? 여기서는 추측 만 할 수 있지만, 오래된 하드 코어 C 해커는 기존 기능이 매우 어수선하지만 충분하다고 생각했다고 가정합니다. 또한 일반적인 터미널 응용 프로그램을 볼 때의 사용자 입력을 거의 읽지 않습니다 stdin. 대부분의 경우 모든 사용자 입력을 명령 행 인수로 전달합니다. 물론 예외가 있지만 대부분의 응용 프로그램에서 사용자 입력은 매우 사소한 것입니다.

그래서 당신은 무엇을 할 수 있습니까?

내가 가장 좋아하는 fgets것은와 (과) 조합되어 sscanf있습니다. 한 번 그것에 대해 답변을 썼지 만 완전한 코드를 다시 게시 할 것입니다. 다음은 적절한 (그러나 완벽하지는 않은) 오류 검사 및 구문 분석이 포함 된 예입니다. 디버깅 목적으로 충분합니다.

노트

특히 한 줄에 두 가지 다른 것을 입력하도록 요청하는 것을 좋아하지 않습니다. 나는 그들이 자연스럽게 서로에게 속해있을 때만 그렇게합니다. 예를 들어 좋아 printf("Enter the price in the format <dollars>.<cent>: ")하고를 사용하십시오 sscanf(buffer "%d.%d", &dollar, &cent). 나는 결코 같은 것을하지 않을 것이다 printf("Enter height and base of the triangle: "). fgets아래 사용의 주요 요점은 입력을 캡슐화하여 한 입력이 다음 입력에 영향을 미치지 않도록하는 것입니다.

#define bsize 100

void error_function(const char *buffer, int no_conversions) {
        fprintf(stderr, "An error occurred. You entered:\n%s\n", buffer);
        fprintf(stderr, "%d successful conversions", no_conversions);
        exit(EXIT_FAILURE);
}

char c, buffer[bsize];
int x,y;
float f, g;
int r;

printf("Enter two integers: ");
fflush(stdout); // Make sure that the printf is executed before reading
if(! fgets(buffer, bsize, stdin)) error_function(buffer, 0);
if((r = sscanf(buffer, "%d%d", &x, &y)) != 2) error_function(buffer, r);

// Unless the input buffer was to small we can be sure that stdin is empty
// when we come here.
printf("Enter two floats: ");
fflush(stdout);
if(! fgets(buffer, bsize, stdin)) error_function(buffer, 0);
if((r = sscanf(buffer, "%d%d", &x, &y)) != 2) error_function(buffer, r);

// Reading single characters can be especially tricky if the input buffer
// is not emptied before. But since we're using fgets, we're safe.
printf("Enter a char: ");
fflush(stdout);
if(! fgets(buffer, bsize, stdin)) error_function(buffer, 0);
if((r = sscanf(buffer, "%c", &c)) != 1) error_function(buffer, r);

printf("You entered %d %d %f %c\n", x, y, f, c);

이 작업을 많이 수행하면 항상 플러시되는 래퍼를 만드는 것이 좋습니다.

int printfflush (const char *format, ...)
{
   va_list arg;
   int done;
   va_start (arg, format);
   done = vfprintf (stdout, format, arg);
   fflush(stdout);
   va_end (arg);
   return done;
}```

이렇게하면 일반적인 문제가 제거됩니다.이 문제는 중첩 입력을 망칠 수있는 후행 줄 바꿈입니다. 그러나 또 다른 문제가 bsize있습니다. 로 확인할 수 있습니다 if(buffer[strlen(buffer)-1] != '\n'). 줄 바꿈을 제거하려면을 사용하여 줄 바꿈을 수행 할 수 있습니다 buffer[strcspn(buffer, "\n")] = 0.

일반적으로 사용자가 다른 변수로 구문 분석 해야하는 이상한 형식으로 입력을 입력하지 않을 것을 권장합니다. 당신이 변수를 할당 할 경우 heightwidth, 두 가지를 동시에 요구하지 않습니다. 사용자가 그들 사이에서 Enter를 누를 수있게하십시오. 또한이 방법은 어떤 의미에서 매우 자연 스럽습니다. stdinEnter 키를 누를 때까지 입력을 얻지 못하므로 항상 전체 줄을 읽지 않겠습니까? 물론 라인이 버퍼보다 ​​길면 여전히 문제가 발생할 수 있습니다. C에서 사용자 입력이 어색하다는 언급을 기억 했습니까? :)

버퍼보다 긴 라인의 문제를 피하기 위해 적절한 크기의 버퍼를 자동으로 할당하는 함수를 사용할 수 있습니다 getline(). 단점은 free나중에 결과 가 필요하다는 것입니다 .

게임 강화

사용자 입력으로 C에서 프로그램을 만드는 것이 진지하다면, 같은 라이브러리를 보는 것이 좋습니다 ncurses. 따라서 일부 터미널 그래픽으로 응용 프로그램을 만들고 싶을 수도 있습니다. 안타깝게도 휴대 성이 떨어지면 사용자 입력을 훨씬 잘 제어 할 수 있습니다. 예를 들어, 사용자가 Enter 키를 누를 때까지 기다리지 않고 키 누름을 즉시 읽을 수 있습니다.


참고 (r = sscanf("1 2 junk", "%d%d", &x, &y)) != 2후행 숫자가 아닌 텍스트 나쁜으로 감지하지 않습니다.
chux-복원 Monica Monica

1
@chux 고정 % f % f. 첫 번째 의미는 무엇입니까?
klutt

으로 fgets()"1 2 junk", if((r = sscanf(buffer, "%d%d", &x, &y)) != 2) {그것은 "쓰레기"가 비록 입력이 아무것도 잘못보고하지 않습니다.
chux-복원 Monica Monica

@ chux 아, 이제 알겠습니다. 글쎄 그것은 의도적이었다.
klutt

1
scanf완벽하게 형식화 된 데이터와 함께 사용되도록 설계되었지만 사실이 아닙니다. @chux가 언급 한 "정크"와 관련된 문제 외에도 "%d %d %d"1, 2 또는 3 줄 (또는 빈 줄이있는 경우 더 많은 것)에서 입력을 읽는 것이 행복 하다는 사실도 있습니다. 형식화 된 스트림 입력에 대해 "%d\n%d %d"등 을 수행하여 두 줄 입력을 강제하는 방법 (예 :)은 줄 기반 입력에는 scanf적합 하지 않습니다.
Steve Summit

18

scanf입력이 항상 잘 구성되고 올바르게 작동한다는 것을 알면 굉장 합니다. 그렇지 않으면...

IMO, 가장 큰 문제는 scanf다음 과 같습니다.

  • 버퍼 오버 플로우 위험 - %s%[변환 지정자에 필드 너비를 지정하지 않으면 버퍼 오버 플로우의 위험이 있습니다 (버퍼 크기보다 보유한 입력보다 많은 입력을 읽으려고 시도하는 경우). 불행히도 (와 마찬가지로 printf) 인수로 지정하는 좋은 방법은 없습니다 . 변환 지정자의 일부로 하드 코딩하거나 매크로 셰넌 건을 수행해야합니다.

  • 입력을 수용 한다 거부 - 당신이 함께 입력을 읽는 경우 %d변환 지정자와 같은 당신이 뭔가를 입력 12w4하면 것이라고 기대하고 scanf 그 입력을 거부,하지만 그렇지 않습니다 - 그것은 성공적으로 변환하여 할당 12떠나, w4입력 스트림에 다음에 읽은 내용을 파울 것

대신에 무엇을 사용해야합니까?

일반적으로 모든 대화식 입력을 사용하여 텍스트로 읽는 것이 좋습니다 fgets. 한 번에 읽을 수있는 최대 문자 수를 지정하여 버퍼 오버플로를 쉽게 방지 할 수 있습니다.

char input[100];
if ( !fgets( input, sizeof input, stdin ) )
{
  // error reading from input stream, handle as appropriate
}
else
{
  // process input buffer
}

하나의 특질은 fgets공간이 있다면 당신은 누군가가 당신이 기대했던 것보다 더 많은 입력에 입력 있는지 쉽게 확인 할 수 있도록이 버퍼에 후행 줄 바꿈을 저장하는 것입니다 :

char *newline = strchr( input, '\n' );
if ( !newline )
{
  // input longer than we expected
}

당신이 그것을 처리하는 방법은 당신에게 달려 있습니다-당신은 전체 입력을 손으로 거부하고 getchar다음 과 같이 남은 입력을 철회 할 수 있습니다 :

while ( getchar() != '\n' ) 
  ; // empty loop

또는 지금까지 입력 한 내용을 처리하고 다시 읽을 수 있습니다. 해결하려는 문제에 따라 다릅니다.

입력 을 토큰 화 하려면 (하나 이상의 구분 기호를 기준으로 쪼개십시오)을 사용할 수 strtok있지만주의하십시오- strtok입력을 수정하고 (문자열 종결 자로 구분 기호를 덮어 씁니다) 상태를 유지할 수 없습니다 (예 : t 한 문자열을 부분적으로 토큰 화 한 다음 다른 문자열을 토큰 화 한 다음 원래 문자열에서 중단 한 부분을 선택합니다. strtok_s토크 나이저의 상태를 유지 하는 변형이 있지만 AFAIK의 구현은 선택 사항입니다 (사용 가능한지 확인하려면 __STDC_LIB_EXT1__정의되어 있는지 확인해야 함 ).

입력을 토큰 화 한 후 문자열을 숫자 (예 : "1234"=> 1234) 로 변환해야하는 경우 옵션이 있습니다. strtol그리고 strtod각각의 유형에 정수와 실수의 문자열 표현을 변환합니다. 또한 12w4위에서 언급 한 문제 를 포착 할 수 있습니다. 논쟁 중 하나 는 문자열에서 변환 되지 않은 첫 번째 문자에 대한 포인터입니다 .

char *text = "12w4";
char *chk;
long val;
long tmp = strtol( text, &chk, 10 );
if ( !isspace( *chk ) && *chk != 0 )
  // input is not a valid integer string, reject the entire input
else
  val = tmp;

필드 너비를 지정하지 않으면 ...-또는 변환 억제 (예 : %*[%\n]나중에 답변에서 긴 줄을 처리하는 데 유용함).
Toby Speight

필드 너비의 런타임 사양을 얻는 방법이 있지만 좋지 않습니다. 코드에서 형식 문자열을 구성해야 할 수도 있습니다 (아마도을 사용하여 snprintf()).
Toby Speight

5
가장 일반적인 실수는 isspace()-로 표시된 부호없는 문자를 허용 int하므로 서명 unsigned char된 플랫폼에서 UB를 피하기 위해 캐스트해야합니다 char.
Toby Speight

9

이 답변에서는 텍스트 줄을 읽고 해석한다고 가정 합니다 . 아마도 사용자에게 무언가를 입력하고 RETURN을 누르라는 메시지가 표시 될 수 있습니다. 또는 일종의 데이터 파일에서 구조화 된 텍스트 행을 읽는 중일 수 있습니다.

한 줄의 텍스트를 읽고 있기 때문에 한 줄의 텍스트를 읽는 라이브러리 함수를 중심으로 코드를 구성하는 것이 좋습니다. 표준 기능은 fgets()(를 포함하여 다른 사람이 있기는하지만, getline). 그리고 다음 단계는 어떻게 든 해당 텍스트 줄을 해석하는 것입니다.

fgets한 줄의 텍스트를 읽도록 호출하는 기본 레시피는 다음과 같습니다 .

char line[512];
printf("type something:\n");
fgets(line, 512, stdin);
printf("you typed: %s", line);

이것은 단순히 한 줄의 텍스트를 읽고 다시 인쇄합니다. 작성된 바와 같이 몇 가지 제한 사항이 있습니다. 번호 512 우리가 두 번째 인수로 전달하는 것이 : 그것은 또한 매우 훌륭한 기능이 fgets배열의 크기는 line우리가 요구하는지 fgets에 읽을 수 있습니다. 이 사실 - 우리가 말할 수있는 fgets이 읽을 수 얼마나 많은 - 우리가 확신 할 수 있음을 의미 fgets하지 않습니다 그것으로 너무 많이 읽어 배열 오버 플로우.

이제 우리는 한 줄의 텍스트를 읽는 방법을 알고 있지만 정수, 부동 소수점 숫자, 단일 문자 또는 단일 단어를 실제로 읽고 싶다면 어떻게해야합니까? 합니다 (어떤 경우 즉, scanf우리가 개선하기 위해 노력하고 호출이 같은 형식 지정자를 사용했던 %d, %f, %c, 또는 %s?)

이러한 것들 중 하나로서 텍스트 줄 (문자열)을 쉽게 해석 할 수 있습니다. 문자열을 정수로 변환하는 가장 간단한 방법은 호출하는 것 atoi()입니다. 부동 소수점 숫자로 변환하려면이 atof()있습니다. (몇 분 후에보다 나은 방법도 있습니다.) 다음은 매우 간단한 예입니다.

printf("type an integer:\n");
fgets(line, 512, stdin);
int i = atoi(line);
printf("type a floating-point number:\n");
fgets(line, 512, stdin);
float f = atof(line);
printf("you typed %d and %f\n", i, f);

단일 문자를 (아마도 입력 할 수있는 사용자를 원하는 경우 y또는 n예스로 / 무응답), 당신은 말 그대로 그냥이 같은 라인의 첫 번째 문자를 잡을 수 있습니다 :

printf("type a character:\n");
fgets(line, 512, stdin);
char c = line[0];
printf("you typed %c\n", c);

(물론 사용자가 다중 문자 응답을 입력했을 가능성은 무시하고 입력 된 추가 문자는 조용히 무시합니다.)

마지막으로 사용자 가 공백을 포함 하지 않는 문자열을 입력하도록 하려는 경우 입력 행을 처리하려는 경우

hello world!

문자열 "hello"뒤에 다른 scanf형식 (형식 %s이 수행 한 것)이 있기 때문에, 그 경우, 나는 조금 어리 석었습니다. 결국 그 방식으로 줄을 재 해석하는 것은 그리 쉬운 일이 아닙니다. 질문의 일부는 조금 기다려야 할 것입니다.

그러나 먼저 건너 뛴 세 가지로 돌아가고 싶습니다.

(1) 전화했습니다

fgets(line, 512, stdin);

배열로 읽어 line들이고 512는 배열의 크기 line이므로 fgets오버플로하지 않도록 알고 있습니다. 그러나 512가 올바른 숫자인지 확인하려면 (특히 누군가가 크기를 변경하기 위해 프로그램을 조정했는지 확인하려면) line선언 된 곳 을 다시 읽어야 합니다. 그것은 성가신 일이므로 크기를 동기화하는 더 좋은 두 가지 방법이 있습니다. (a) 프리 프로세서를 사용하여 크기의 이름을 지정할 수 있습니다.

#define MAXLINE 512
char line[MAXLINE];
fgets(line, MAXLINE, stdin);

또는 (b) C의 sizeof연산자를 사용하십시오.

fgets(line, sizeof(line), stdin);

(2) 두 번째 문제는 우리가 오류를 확인하지 않았다는 것입니다. 입력을 읽을 때는 항상 오류 가능성을 확인 해야 합니다. 어떤 이유로 든 fgets요청한 텍스트 행을 읽을 수없는 경우 널 포인터를 리턴하여이를 나타냅니다. 그래서 우리는 다음과 같은 일을해야했습니다

printf("type something:\n");
if(fgets(line, 512, stdin) == NULL) {
    printf("Well, never mind, then.\n");
    exit(1);
}

마지막으로 한 줄의 텍스트를 읽고, 줄 을 끝내는 문자를 fgets찾을 때까지 문자를 읽고 배열에 채우는 문제가 있습니다 . 이전 예제를 약간 수정하면 다음을 볼 수 있습니다.\n\n

printf("you typed: \"%s\"\n", line);

이 메시지를 표시하고 프롬프트가 표시 될 때 "Steve"를 입력하면 인쇄됩니다.

you typed: "Steve
"

"두 번째 줄은 문자열이 읽고 밖으로 사실이었다 다시 인쇄 때문이다에 "Steve\n".

때로는 추가 줄 바꿈이 중요하지 않습니다 ( atoi또는 호출 한 경우와 같이 atof숫자 다음에 숫자가 아닌 추가 입력을 무시하기 때문에).하지만 때로는 중요합니다. 종종 우리는 그 줄 바꿈을 제거하고 싶을 것입니다. 몇 가지 방법으로 몇 분 안에 접근 할 수 있습니다. (나는 그 말을 많이 들었다는 것을 알고있다. 그러나 나는 그 모든 것들로 돌아갈 것이라고 약속한다.)

내가 당신이 말한 생각 "이 시점에서 생각을 할 수 scanf 좋은 없었다,이 다른 방법은 훨씬 더 좋을 것이다 그러나. fgets성가신처럼 보이기 시작 소명은. scanf이었다 너무 쉽게 나는 그것을 계속 사용 할 수 없습니다!? "

물론 scanf원하는 경우 계속 사용할 수 있습니다 . (그리고 정말 간단한 것들, 어떤면에서는 더 간단합니다.) 그러나 제발, 17 가지 기발한 것 중 하나 때문에 당신을 실패하거나 입력으로 인해 무한 루프에 빠질 때 울지 마십시오. 예상하지 못했거나 더 복잡한 것을 수행하는 방법을 알 수없는 경우. fgets의 실제 방해 요소를 살펴 보겠습니다 .

  1. 항상 배열 크기를 지정해야합니다. 물론 이것은 전혀 번거로운 일이 아닙니다. 버퍼 오버플로는 정말 나쁜 일이기 때문에 기능입니다.

  2. 반환 값을 확인해야합니다. 실제로, 그것은 세척입니다. scanf올바르게 사용 하려면 반환 값도 확인해야하기 때문입니다.

  3. 등을 벗겨야합니다 \n. 이것은 진정한 성가신 일입니다. 나는이 작은 문제가 없었 음을 지적 할 수있는 표준 기능이 있었으면 좋겠다. (아무도 기르지 마십시오 gets.) 그러나 scanf's17 가지의 다른 성가신에 비해 , 나는이 성가신 fgets하루를 가져갈 것 입니다.

그럼 어떻게 합니까 당신은 줄 바꿈을 제거? 세 가지 방법 :

(a) 명백한 방법 :

char *p = strchr(line, '\n');
if(p != NULL) *p = '\0';

(b) 까다 롭고 간결한 방법 :

strtok(line, "\n");

불행히도 이것은 항상 작동하지는 않습니다.

(c) 또 다른 작고 약간 애매한 방법 :

line[strcspn(line, "\n")] = '\0';

의 결함 : 그리고 지금 그 길 밖으로 있다고, 우리는 다시 내가 스킵 다른 일을 얻을 수 있습니다 atoi()atof(). 이것의 문제점은 성공 또는 실패의 성공에 대한 유용한 표시를 제공하지 않는다는 것입니다. 숫자가 아닌 후행 입력을 조용히 무시하고 숫자 입력이 없으면 조용히 0을 반환합니다. 다른 장점도있는 선호되는 대안은 strtolstrtod입니다. strtol또한 당신이 (다른 것들 사이)의 효과를 얻을 수 있음을 의미, 10 이외의 기지를 사용할 수 있습니다 %o또는 %x함께scanf. 그러나 이러한 기능을 올바르게 사용하는 방법을 보여주는 것은 그 자체로 하나의 이야기이며, 이미 단편화 된 내러티브로 바뀌고있는 것에서 너무 혼란 스러울 것이므로 지금은 더 이상 아무 말도하지 않겠습니다.

나머지 주요한 이야기는 입력 한 숫자 나 문자보다 더 복잡한 구문 분석을 시도 할 수 있습니다. 두 개의 숫자 나 여러 개의 공백으로 구분 된 단어 또는 특정 프레임 구두점을 포함하는 행을 읽으려면 어떻게해야합니까? 여기서는 일이 흥미로워지고를 사용하여 일을하려고 할 때 일이 복잡해질 수 scanf있는 곳과을 사용하여 한 줄의 텍스트를 깨끗하게 읽었 으므로 훨씬 더 많은 옵션이 있습니다 fgets. 모든 옵션에 대한 전체 이야기 아마도 책을 채울 수 있으니 여기 표면 만 긁을 수있을 것입니다.

  1. 내가 가장 좋아하는 기술은 줄을 공백으로 구분 된 "단어"로 분리 한 다음 각 "단어"로 추가 작업을 수행하는 것입니다. 이 작업을 수행하는 주요 표준 기능 중 하나는 strtok(문제가 있으며 전체적으로 별도의 토론을 평가하는) 기능입니다. 내 자신의 선호는 각각의 깨진 "단어"에 대한 포인터 배열을 구성하기위한 전용 함수입니다 . 이 코스 노트 에서 설명하는 함수 입니다. 어쨌든 "단어"를 얻은 후에는 이미 살펴본 것과 동일한 atoi/ atof/ strtol/ strtod기능을 사용하여 각 단어를 추가로 처리 할 수 ​​있습니다 .

  2. 역설적으로, 우리는 여기서 벗어나는 방법을 알아내는 데 상당한 시간과 노력을 들였지만 scanf방금 읽은 텍스트 줄을 처리하는 또 다른 좋은 방법 fgets은 그것을 전달하는 것 sscanf입니다. 이러한 방식으로의 장점은 scanf대부분 있지만 단점은 거의 없습니다.

  3. 입력 구문이 특히 복잡하면 "regexp"라이브러리를 사용하여 구문 분석하는 것이 적절할 수 있습니다.

  4. 마지막으로, 임의의 특수 구문 분석 솔루션을 사용할 수 있습니다. char *포인터를 사용하여 원하는 문자를 확인 하여 한 번에 한 문자 씩 줄을 이동할 수 있습니다 . 또는 당신이 좋아하는 기능을 사용하여 특정 문자를 검색 할 수 있습니다 strchr또는 strrchr또는 strspn또는 strcspn또는 strpbrk. 또는 이전에 건너 뛴 strtol또는 strtod함수를 사용하여 숫자 문자 그룹을 구문 분석 / 변환하고 건너 뛸 수 있습니다 .

말할 수있는 것이 훨씬 더 많지만,이 소개가 여러분을 시작할 수 있기를 바랍니다.


sizeof (line)단순히 쓰는 것이 아니라 쓸만한 이유가 sizeof line있습니까? 전자는 line형식 이름 처럼 보입니다 !
Toby Speight

@TobySpeight 좋은 이유가 있습니까? 아니요, 의심합니다. 괄호는 내 습관입니다. 필요한 객체 또는 유형 이름인지 기억할 필요가 없기 때문에 많은 프로그래머가 가능할 때 제외시킵니다. (나에게 그것은 개인적인 취향과 스타일, 그리고에 아주 작은 하나의 문제입니다.)
스티브 정상 회의

sscanf변환 엔진으로 사용하지만 다른 도구를 사용하여 입력을 수집 (및 가능한 경우)하는 경우 +1 그러나 어쩌면 getline문맥에서 언급 할 가치가 있습니다.
dmckee --- 전 운영자 고양이 새끼

" fscanf'실제 귀찮은 일" 에 대해 이야기 할 때 , 당신은 의미 fgets합니까? 그리고 성가신 # 3은 실제로 나를 자극합니다. 특히 scanf문자 입력 수를 반환하지 않고 버퍼에 쓸모없는 포인터를 반환합니다 (개행을 훨씬 깨끗하게 제거합니다).
supercat

1
sizeof스타일 을 설명해 주셔서 감사합니다 . 나를 위해, 당신이 파렌을 nee 할 때를 기억하는 것은 쉽다 : 나는 (type)값이없는 캐스트처럼 생각 합니다 (우리는 유형에만 관심이 있기 때문에). 또 다른 한 가지 : 당신은 그것이 strtok(line, "\n")항상 작동하지는 않는다고 말하지만 그것이 그렇지 않을 때는 분명하지 않습니다. 줄이 버퍼보다 ​​길었던 경우를 생각하고 있으므로 줄 바꿈이 없으며 strtok()null을 반환합니까? 실제 동정 fgets()은 더 유용한 값을 반환하지 않으므로 줄 바꿈이 있는지 여부를 알 수 있습니다.
Toby Speight

7

scanf 대신 입력을 구문 분석하는 데 무엇을 사용할 수 있습니까?

대신 scanf(some_format, ...), 고려 fgets()와 함께sscanf(buffer, some_format_and %n, ...)

" %n"코드 를 사용 하면 코드에서 모든 형식이 성공적으로 스캔되었는지, 공백이 아닌 여분의 정크가 없는지 여부를 간단히 감지 할 수 있습니다 .

// scanf("%d %f fred", &some_int, &some_float);
#define EXPECTED_LINE_MAX 100
char buffer[EXPECTED_LINE_MAX * 2];  // Suggest 2x, no real need to be stingy.

if (fgets(buffer, sizeof buffer, stdin)) {
  int n = 0;
  // add ------------->    " %n" 
  sscanf(buffer, "%d %f fred %n", &some_int, &some_float, &n);
  // Did scan complete, and to the end?
  if (n > 0 && buffer[n] == '\0') {
    // success, use `some_int, some_float`
  } else {
    ; // Report bad input and handle desired.
  }

6

파싱 ​​요구 사항을 다음과 같이 설명하겠습니다.

  • 유효한 입력을 수락해야하며 다른 형식으로 변환해야합니다.

  • 유효하지 않은 입력은 거부되어야합니다

  • 입력이 거부되면 거부 된 이유를 설명하는 설명 메시지 ( "프로그래머가 아닌 일반 사람들이 쉽게 이해할 수 있음")를 설명하는 메시지를 사용자에게 제공해야합니다. 문제)

일을 매우 단순하게 유지하기 위해 사용자가 입력 한 하나의 간단한 십진 정수를 구문 분석하고 다른 것은 고려하지 마십시오. 사용자 입력이 거부 될 수있는 이유는 다음과 같습니다.

  • 입력에 허용되지 않는 문자가 포함되어 있습니다
  • 입력은 허용 된 최소값보다 낮은 숫자를 나타냅니다.
  • 입력은 허용 된 최대 값보다 높은 숫자를 나타냅니다.
  • 입력은 0이 아닌 분수 부분을 가진 숫자를 나타냅니다.

"입력 할 수없는 문자를 포함하는 입력"을 올바르게 정의 해 보자. 그리고 말하십시오 :

  • 선행 공백과 후행 공백은 무시됩니다 (예 : "
    5"는 " 5"로 처리됨)
  • 소수점이 0 개 또는 1 개가 허용됩니다 (예 : "1234."및 "1234.000"은 모두 "1234"와 동일하게 처리됨)
  • 하나 이상의 숫자가 있어야합니다 (예 : "."는 거부 됨)
  • 소수점 이하는 허용되지 않습니다 (예 : "1.2.3"은 거부 됨)
  • 숫자 사이에 있지 않은 쉼표는 거부됩니다 (예 : ", 1234"는 거부 됨)
  • 소수점 뒤의 쉼표는 거부됩니다 (예 : "1234.000,000"은 거부 됨)
  • 다른 쉼표 뒤에 오는 쉼표는 거부됩니다 (예 : "1,234"는 거부 됨)
  • 다른 모든 쉼표는 무시됩니다 (예 : "1,234"는 "1234"로 처리됨)
  • 공백이 아닌 첫 번째 문자가 아닌 빼기 기호는 거부됩니다.
  • 공백이 아닌 첫 문자가 아닌 양수 부호는 거부됩니다.

이를 통해 다음과 같은 오류 메시지가 필요하다는 것을 알 수 있습니다.

  • "입력 시작시 알 수없는 문자"
  • "입력 끝에서 알 수없는 문자"
  • "입력 중간에 알 수없는 문자"
  • "숫자가 너무 낮습니다 (최소 ....)"
  • "숫자가 너무 높습니다 (최대 값은 ....)"
  • "숫자는 정수가 아닙니다"
  • "소수가 너무 많음"
  • "소수 자릿수 없음"
  • "숫자 시작시 잘못된 쉼표"
  • "숫자 끝에 잘못된 쉼표"
  • "숫자 중간에 잘못된 쉼표"
  • "소수점 뒤의 쉼표"

이 시점에서 문자열을 정수로 변환하는 적절한 함수는 매우 다른 유형의 오류를 구별해야 함을 알 수 있습니다. " scanf()"또는 " atoi()"또는 " strtoll()"와 같은 것은 입력에 문제가 있음을 표시하지 못하기 때문에 완전히 그리고 전혀 쓸모가 없습니다. 입력").

대신 쓸모없는 것을 쓰도록하자.

char *convertStringToInteger(int *outValue, char *string, int minValue, int maxValue) {
    return "Code not implemented yet!";
}

int main(int argc, char *argv[]) {
    char *errorString;
    int value;

    if(argc < 2) {
        printf("ERROR: No command line argument.\n");
        return EXIT_FAILURE;
    }
    errorString = convertStringToInteger(&value, argv[1], -10, 2000);
    if(errorString != NULL) {
        printf("ERROR: %s\n", errorString);
        return EXIT_FAILURE;
    }
    printf("SUCCESS: Your number is %d\n", value);
    return EXIT_SUCCESS;
}

명시된 요구 사항을 충족하기 위해 이 convertStringToInteger()함수는 결국 수백 줄의 코드로 끝날 것입니다.

자, 이것은 단지 "단일 간단한 십진 정수를 파싱"하는 것입니다. 복잡한 것을 파싱하려고한다고 상상해보십시오. "이름, 주소, 전화 번호, 이메일 주소"구조의 목록처럼; 또는 프로그래밍 언어와 같은 것일 수도 있습니다. 이러한 경우에 주름이없는 농담이 아닌 구문 분석을 작성하려면 수천 줄의 코드를 작성해야 할 수도 있습니다.

다시 말해...

scanf 대신 입력을 구문 분석하는 데 무엇을 사용할 수 있습니까?

요구 사항에 맞게 직접 코드를 작성하십시오 (수천 줄).


5

다음은 flex간단한 입력 (이 경우 US ( n,nnn.dd) 또는 유럽 ( n.nnn,dd) 형식 일 수있는 ASCII 부동 소수점 숫자 파일)을 스캔하는 데 사용하는 예입니다 . 이것은 훨씬 더 큰 프로그램에서 복사되었으므로 해결되지 않은 참조가있을 수 있습니다.

/* This scanner reads a file of numbers, expecting one number per line.  It  */
/* allows for the use of European-style comma as decimal point.              */

%{
  #include <stdlib.h>
  #include <stdio.h>
  #include <string.h>
  #ifdef WINDOWS
    #include <io.h>
  #endif
  #include "Point.h"

  #define YY_NO_UNPUT
  #define YY_DECL int f_lex (double *val)

  double atofEuro (char *);
%}

%option prefix="f_"
%option nounput
%option noinput

EURONUM [-+]?[0-9]*[,]?[0-9]+([eE][+-]?[0-9]+)?
NUMBER  [-+]?[0-9]*[\.]?[0-9]+([eE][+-]?[0-9]+)?
WS      [ \t\x0d]

%%

[!@#%&*/].*\n

^{WS}*{EURONUM}{WS}*  { *val = atofEuro (yytext); return (1); }
^{WS}*{NUMBER}{WS}*   { *val = atof (yytext); return (1); }

[\n]
.


%%

/*------------------------------------------------------------------------*/

int scan_f (FILE *in, double *vals, int max)
{
  double *val;
  int npts, rc;

  f_in = in;
  val  = vals;
  npts = 0;
  while (npts < max)
  {
    rc = f_lex (val);

    if (rc == 0)
      break;
    npts++;
    val++;
  }

  return (npts);
}

/*------------------------------------------------------------------------*/

int f_wrap ()
{
  return (1);
}

-5

먼저, 분석 : 나는 더 높은 수준에 자신을 제한하는 것, 그래서 다른 답변은 오른쪽 낮은 수준의 세부 사항을 제공 당신이 기대하는 것 처럼 보이도록 각 입력 라인. 입력을 형식적인 구문으로 설명해보십시오. 운이 좋으면 정규 문법 이나 문맥이없는 문법을 사용하여 설명 할 수 있습니다 . 정규 문법으로 충분하다면 유한 상태 머신을 코딩 할 수 있습니다한 번에 한 문자 씩 각 명령 줄을 인식하고 해석합니다. 그런 다음 코드는 다른 응답에서 설명한대로 줄을 읽은 다음 상태 머신을 통해 버퍼의 문자를 스캔합니다. 특정 상태에서는 지금까지 스캔 한 부분 문자열을 중지하고 숫자 등으로 변환합니다. 이것이 간단하다면 아마도 당신 자신의 것을 굴릴 수 있습니다. 문맥이없는 완전한 문법이 필요한 경우 기존 구문 분석 도구 (re : lexyacc/ 또는 변형) 를 사용하는 방법을 알아내는 것이 좋습니다 .


유한 상태 머신은 과잉 상태 일 수 있습니다. 변환 errno == EOVERFLOW후 오버플로를 감지하는 더 쉬운 방법 (예 : 을 사용한 후 확인 strtoll)이 가능합니다.
SS Anne

1
플렉스로 간단하게 작성하면 왜 유한 상태 머신을 코딩하겠습니까?
jamesqf
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.