【问题标题】:proper use of scanf in a while loop to validate input在 while 循环中正确使用 scanf 来验证输入
【发布时间】:2016-10-22 00:01:09
【问题描述】:

我编写了这段代码:

/*here is the main function*/
int x , y=0, returned_value;
int *p = &x;
while (y<5){
    printf("Please Insert X value\n");
    returned_value = scanf ("%d" , p);
    validate_input(returned_value, p);
    y++;
}

功能:

void validate_input(int returned_value, int *p){
    getchar();
    while (returned_value!=1){
        printf("invalid input, Insert Integers Only\n");
        getchar();
        returned_value = scanf("%d", p);
    }
}

虽然它通常工作得很好,但是当我插入例如 "1f1" 时,它接受 "1" 并且不报告任何错误,当插入 "f1f1f" 时它会读取两次并破坏第二次读取/扫描和依此类推(即第一次读取打印出“无效输入,仅插入整数”,而不是再次等待重新读取用户的第一次读取,它继续第二次读取并再次打印出“无效输入,仅插入整数”再次...

它需要最后润色,我阅读了很多答案但找不到它。

【问题讨论】:

  • 注意:validate_input() 是无限递归应该returned_value == EOF
  • 如果您不想接受1f1 作为有效输入,那么scanf 是错误的函数。
  • @chux ,我尝试了 EOF 并没有工作!
  • @4386427 那我应该用什么?
  • 如果 OP 不愿意使用 scanf(),建议 How to test input is sane

标签: c validation input while-loop scanf


【解决方案1】:

如果您不想接受1f1 作为有效输入,那么scanf 是错误的函数,因为scanf 在找到匹配项后立即返回。

而是阅读整行,然后检查它是否只包含数字。之后可以拨打scanf

类似:

#include <stdio.h>

int validateLine(char* line)
{
    int ret=0;

    // Allow negative numbers
    if (*line && *line == '-') line++;

    // Check that remaining chars are digits
    while (*line && *line != '\n')
    {
        if (!isdigit(*line)) return 0; // Illegal char found

        ret = 1;  // Remember that at least one legal digit was found
        ++line;
    }
    return ret;
}

int main(void) {
    char line[256];
    int i;

    int x , y=0;
    while (y<5)
    {
        printf("Please Insert X value\n");
        if (fgets(line, sizeof(line), stdin)) // Read the whole line
        {
            if (validateLine(line))  // Check that the line is a valid number
            {
                // Now it should be safe to call scanf - it shouldn't fail
                // but check the return value in any case
                if (1 != sscanf(line, "%d", &x)) 
                {
                    printf("should never happen");
                    exit(1);
                }

                // Legal number found - break out of the "while (y<5)" loop
                break;
            }
            else
            {
                printf("Illegal input %s", line);
            }
        }
        y++;
    }

    if (y<5)
        printf("x=%d\n", x);
    else
        printf("no more retries\n");

    return 0;
}

输入

1f1
f1f1

-3

输出

Please Insert X value
Illegal input 1f1
Please Insert X value
Illegal input f1f1
Please Insert X value
Illegal input 
Please Insert X value
x=-3

另一种方法 - 避免 scanf

您可以让您的函数计算数字,从而完全绕过 scanf。它可能看起来像:

#include <stdio.h>

int line2Int(char* line, int* x)
{
    int negative = 0;
    int ret=0;
    int temp = 0;

    if (*line && *line == '-') 
    {
        line++;
        negative = 1;
    }
    else if (*line && *line == '+')  // If a + is to be accepted
        line++;                      // If a + is to be accepted

    while (*line && *line != '\n')
    {
        if (!isdigit(*line)) return 0; // Illegal char found
        ret = 1;

            // Update the number
        temp = 10 * temp;
        temp = temp + (*line - '0');

        ++line;
    }

    if (ret)
    {
        if (negative) temp = -temp;
        *x = temp;
    }
    return ret;
}

int main(void) {
    char line[256];
    int i;

    int x , y=0;
    while (y<5)
    {
        printf("Please Insert X value\n");
        if (fgets(line, sizeof(line), stdin)) 
        {
            if (line2Int(line, &x)) break;  // Legal number - break out

            printf("Illegal input %s", line);
        }
        y++;
    }

    if (y<5)
        printf("x=%d\n", x);
    else
        printf("no more retries\n");

    return 0;
}

【讨论】:

  • 此代码的fgets() 是最好的方法。次要的。不接受 "+123"" 456"(前导空格)。最后,使用strol() 可能会更容易。
  • @chux - 感谢您的意见。我忘了+ 可以很容易地添加。不确定 OP 是否会认为前面的空格是有效的,但你有一个很好的观点。我没有使用strol 的经验,但快速阅读确实表明它可能很方便。也许你可以使用strol 给 OP 一个很好的例子(因为我不能):-)
【解决方案2】:

一般来说,我认为您最好从输入中读取所有内容(当然,在您的缓冲区大小范围内),然后然后验证输入确实是正确的格式.

在您的情况下,您使用 f1f1f 之类的字符串会看到错误,因为您没有读取整个 STDIN 缓冲区。因此,当您再次调用scanf(...) 时,STDIN 内部仍有数据,因此首先读取数据,而不是提示用户输入更多输入。要阅读所有 STDIN,您应该执行以下操作(部分代码从 Paxdiablo 的答案中借用:https://stackoverflow.com/a/4023921/2694511):

#include <stdio.h>
#include <string.h>
#include <stdlib.h> // Used for strtol

#define OK          0
#define NO_INPUT    1
#define TOO_LONG    2
#define NaN         3 // Not a Number (NaN)

int strIsInt(const char *ptrStr){
    // Check if the string starts with a positive or negative sign
    if(*ptrStr == '+' || *ptrStr == '-'){
        // First character is a sign. Advance pointer position
        ptrStr++;
    }

    // Now make sure the string (or the character after a positive/negative sign) is not null
    if(*ptrStr == NULL){
        return NaN;
    }

    while(*ptrStr != NULL){
        // Check if the current character is a digit
        // isdigit() returns zero for non-digit characters
        if(isdigit( *ptrStr ) == 0){
            // Not a digit
            return NaN;
        } // else, we'll increment the pointer and check the next character
        ptrStr++;
    }

    // If we have made it this far, then we know that every character inside of the string is indeed a digit
    // As such, we can go ahead and return a success response here
    // (A success response, in this case, is any value other than NaN)
    return 0;
}

static int getLine (char *prmpt, char *buff, size_t sz) {
    int ch, extra;

    // Get line with buffer overrun protection.
    if (prmpt != NULL) {
        printf ("%s", prmpt);
        fflush (stdout);
    }
    if (fgets (buff, sz, stdin) == NULL)
        return NO_INPUT;

    // If it was too long, there'll be no newline. In that case, we flush
    // to end of line so that excess doesn't affect the next call.
    // (Per Chux suggestions in the comments, the "buff[0]" condition
    //   has been added here.)
    if (buff[0] && buff[strlen(buff)-1] != '\n') {
        extra = 0;
        while (((ch = getchar()) != '\n') && (ch != EOF))
            extra = 1;
        return (extra == 1) ? TOO_LONG : OK;
    }

    // Otherwise remove newline and give string back to caller.
    buff[strlen(buff)-1] = '\0';
    return OK;
}

void validate_input(int responseCode, char *prompt, char *buffer, size_t bufferSize){
    while( responseCode != OK ||
            strIsInt( buffer ) == NaN )
    {
        printf("Invalid input.\nPlease enter integers only!\n");
        fflush(stdout); /* It might be unnecessary to flush here because we'll flush STDOUT in the
                           getLine function anyway, but it is good practice to flush STDOUT when printing
                           important information. */
        responseCode = getLine(prompt, buffer, bufferSize); // Read entire STDIN
    }

    // Finally, we know that the input is an integer
}

int main(int argc, char **argv){
    char *prompt = "Please Insert X value\n";
    int iResponseCode;
    char cInputBuffer[100];
    int x, y=0;
    int *p = &x;
    while(y < 5){
        iResponseCode = getLine(prompt, cInputBuffer, sizeof(cInputBuffer)); // Read entire STDIN buffer
        validate_input(iResponseCode, prompt, cInputBuffer, sizeof(cInputBuffer));

        // Once validate_input finishes running, we should have a proper integer in our input buffer!
        // Now we'll just convert it from a string to an integer, and store it in the P variable, as you
        // were doing in your question.
        sscanf(cInputBuffer, "%d", p);
        y++;
    }
}

作为免责声明/注意:我已经很长时间没有用 C 语言编写了,所以如果这个例子中有任何错误,我提前道歉。我也没有机会在发布之前编译和测试这段代码,因为我现在很着急。

【讨论】:

  • if (buff[strlen(buff)-1] != '\n') 是黑客攻击,因为buf[0] 可能为0。建议if (buff[0] &amp;&amp; buff[strlen(buff)-1] != '\n')buff[strcspn(buff,"\n')] = '\0';
  • @chux,感谢您的建议。我很快就会编辑它。我从答案中链接的帖子中复制了该功能,因此我没有费心对其进行编辑或检查以确保一切安全。
  • 感谢您的回答,加一。
【解决方案3】:

如果您正在读取一个您知道是文本流的输入流,但您不确定是否只包含整数,那么请读取字符串。

此外,一旦您读取了一个字符串并想查看它是否为整数,请使用标准库转换例程strtol()。通过这样做,你们双方都会确认它是一个整数,并且会为您将其转换为 long

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

bool convert_to_long(long *number, const char *string)
{
    char *endptr;

    *number = strtol(string, &endptr, 10);

    /* endptr will point to the first position in the string that could
     * not be converted.  If this position holds the string terminator
     * '\0' the conversion went well.  An empty input string will also
     * result in *endptr == '\0', so we have to check this too, and fail
     * if this happens.
     */

    if (string[0] != '\0' && *endptr == '\0')
        return false; /* conversion succesful */

    return true; /* problem in conversion */
}

int main(void)
{
    char buffer[256];

    const int max_tries = 5;
    int tries = 0;

    long number;

    while (tries++ < max_tries) {
        puts("Enter input:");

        scanf("%s", buffer);

        if (!convert_to_long(&number, buffer))
            break; /* returns false on success */

        printf("Invalid input. '%s' is not integer, %d tries left\n", buffer,
               max_tries - tries);
    }

    if (tries > max_tries)
        puts("No valid input found");
    else
        printf("Valid input: %ld\n", number);

    return EXIT_SUCCESS;
}

补充说明:如果您将 base(最后一个参数为 strtol())从 10 更改为 0,您将获得代码转换十六进制数字和八进制数字的附加功能(分别以0x00开头的字符串)转换成整数。

【讨论】:

    【解决方案4】:

    我采用了@4386427 的想法,只是添加了代码来弥补它遗漏的部分(前导空格和 + 号),我对其进行了多次测试,它在所有可能的情况下都能完美运行。

    #include<stdio.h>
    #include <ctype.h>
    #include <stdlib.h>
    
    int validate_line (char *line);
    
    int main(){
    char line[256];
    int y=0;
    long x;
    
    while (y<5){
       printf("Please Insert X Value\n");
    
       if (fgets(line, sizeof(line), stdin)){//return 0 if not execute
                if (validate_line(line)>0){ // check if the string contains only numbers
                        x =strtol(line, NULL, 10); // change the authentic string to long and assign it
                        printf("This is x %d" , x);
                        break;
                }
                else if (validate_line(line)==-1){printf("You Have Not Inserted Any Number!.... ");}
                else {printf("Invalid Input, Insert Integers Only.... ");}
       }
    
    
    y++;
    if (y==5){printf("NO MORE RETRIES\n\n");}
    else{printf("%d Retries Left\n\n", (5-y));}
    
    }
    return 0;}
    
    
    int validate_line (char *line){
    int returned_value =-1;
    /*first remove spaces from the entire string*/
    char *p_new = line;
    char *p_old = line;
    while (*p_old != '\0'){// loop as long as has not reached the end of string
           *p_new = *p_old; // assign the current value the *line is pointing at to p
                if (*p_new != ' '){p_new++;} // check if it is not a space , if so , increment p
            p_old++;// increment p_old in every loop
            }
            *p_new = '\0'; // add terminator
    
    
    if (*line== '+' || *line== '-'){line++;} // check if the first char is (-) or (+) sign to point to next place
    
    
    while (*line != '\n'){
        if (!(isdigit(*line))) {return 0;} // Illegal char found , will return 0 and stop because isdigit() returns 0 if the it finds non-digit
        else if (isdigit(*line)){line++; returned_value=2;}//check next place and increment returned_value for the final result and judgment next.
         }
    
         return returned_value; // it will return -1 if there is no input at all because while loop has not executed, will return >0 if successful, 0 if invalid input
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-03-08
      • 1970-01-01
      • 2021-05-13
      • 2016-05-27
      • 2015-05-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多