C For循环跳过第一次迭代,并从循环scanf中伪造数

问题描述 投票:0回答:1

我正在为学校创建一个邮件标签生成器,并且遇到了一些问题。我的程序要使用0到10的个人的全名,地址,城市,州和邮政编码。运行我的程序时,我遇到两个主要问题。 for循环跳过全名“ safergets()”,然后转到地址safergets。我继续查看其他所有功能是否正常,但是我对邮政编码的验证无法正常工作。我添加了一个printf来查看输入的数字是否相同,并且发现它是虚假的。另外,我在尝试大写状态输出的行中收到错误代码。我确定我没有正确使用toupper。以下是我的代码,错误代码和输出。

#include <stdio.h>
#include <ctype.h>

/* Define structure */

struct information
{
    char full_name[35], address[50], city[25], state[3];
    long int zip_code;
};

/* Function safer_gets */
/* ------------------- */

void safer_gets (char array[], int max_chars)
{
  /* Declare variables. */
  /* ------------------ */

  int i;

  /* Read info from input buffer, character by character,   */
  /* up until the maximum number of possible characters.    */
  /* ------------------------------------------------------ */

  for (i = 0; i < max_chars; i++)
  {
     array[i] = getchar();


     /* If "this" character is the carriage return, exit loop */
     /* ----------------------------------------------------- */

     if (array[i] == '\n')
        break;

   } /* end for */

   /* If we have pulled out the most we can based on the size of array, */
   /* and, if there are more chars in the input buffer,                 */
   /* clear out the remaining chars in the buffer.                      */
   /* ----------------------------------------------------------------  */

   if (i == max_chars )

     if (array[i] != '\n')
       while (getchar() != '\n');

   /* At this point, i is pointing to the element after the last character */
   /* in the string. Terminate the string with the null terminator.        */
   /* -------------------------------------------------------------------- */

   array[i] = '\0';


} /* end safer_gets */

/* Begin main */

int main()
{
    /* Declare variables */

    struct information person[10];
    int x, i;

    /* Issue greeting */

    printf("Welcome to the mailing label generator program.\n\n");

    /* Prompt user for number of individuals between 0 - 10. If invalid, re-prompt */

    do
    {
        printf("How many people do you want to generate labels for (0-10)? ");
        scanf("%i", &x);

        if(x<0 || x>10)
        printf("Invalid number. Please re-enter number. Must be from 0 to 10.\n");

    }while(x<0 || x>10);

    /* Begin loop for individual information */

    for(i = 0; i < x; i++)
    {
        printf("\n\nEnter name: ");
        safer_gets(person[i].full_name, 35); /* This is the step being skipped */

        printf("\nEnter street address: ");
        safer_gets(person[i].address, 50);

        printf("\nEnter city: ");
        safer_gets(person[i].city, 25);

        printf("\nEnter state: ");
        gets(person[i].state);

        /* Begin loop to verify correct zipcode */

        do
        {
            printf("\nEnter zipcode: ");
            scanf("%ld", person[i].zip_code); /* I get a bogus number here */

            if(person[i].zip_code<00001 || person[i].zip_code>99999)
            {
                printf("\nInvalid zipcode. Must be from 00001 to 99999.");
            }
        }while(person[i].zip_code<00001 || person[i].zip_code>99999);
        /* end loop */

    }/* end of loop */

    /* Output individual information in mailing format, condition for 0 individuals */
    if(x>0 && x<10)
    {
    printf("\n\nBelow are your mailing labels:\n\n");
    }

    /* Begin loop for outputting individual(s) mailing labels */

    for(i = 0; i < x; i++)
    {
        printf("%s\n",person[i].full_name);
        printf("%s\n",person[i].address);
        printf("%s\n",person[i].city);

        /* Output state in all uppercase */

        printf("%s\n", toupper(person[i].state)); /* This is where the error code is occurring */

        printf("%.5ld\n\n", person[i].zip_code);
    } /* end of loop */

    printf("Thank you for using the program.\n");

}/*end of main */

错误代码:142:警告:传递'toupper'的arg 1使指针的整数不进行强制转换。

输出:

Welcome to the mailing label generator program.

How many people do you want to generate labels for (0-10)? 1


Enter name:
Enter street address: 100 Needhelp Ave.

Enter city: Gardner

Enter state: NY

Enter zipcode: 01420

Invalid zipcode. Must be from 00001 to 99999.
Enter zipcode:

我已经在这里查看了几个问题,试图了解我要去哪里,但是如果感觉到可能会影响我的程序的几个问题。此外,我们的教授还为我的班级提供了safergets函数,以确保用户输入的字符不会超出数组可以容纳的数量。感谢您的帮助和宽容,帮助我理解自己的错误!

c for-loop scanf toupper bogus
1个回答
4
投票

让我们一一看一下这个问题:

换行号或读取的人后保留在标准输入中

    printf("\n\nEnter name: ");
    safer_gets(person[i].full_name, 35); /* This is the step being skipped */

被跳过,因为您的safer_gets()仅读到第一个'\n'换行符>>字符,而不是回车,即'\r'))。但是,在输入流中看到的第一个字符saver_gets()是在以下位置调用'\n'后仍未读取的stdin字符:

scanf

所有 printf("How many people do you want to generate labels for (0-10)? "); scanf("%i", &x); 格式说明符

用于数字转换,仅读入组成数字的最后一位(或小数点),而使用户按Enter生成的scanf在输入中未读-stream(此处为'\n')。这是鼓励新的C程序员阅读使用line-aligned输入函数(例如stdin(或POSIX fgets()))的用户输入,然后使用getline()来解析C中的值的主要原因之一。填充缓冲区。

为什么用户优选使用面向行的输入功能

通过使用具有足够缓冲区的面向行

输入功能,将消耗用户输入的完整行(包括用户按下Enter的sscanf)。这样可以确保'\n'准备好用于下一次输入,并且不会有先前的输入遗留下来的未读字符在等着您。

正确使用所有输入功能

如果您没有从此答案中获得任何其他收益,请学习此知识-除非您检查返回值

,否则您将无法正确使用任何输入函数。对于stdin系列功能尤其如此。为什么?如果您尝试使用scanf读取整数,而用户输入的是scanf,则会出现matching failure,并且将从输入流中提取字符,并停止第一个无效字符,而将所有有问题的字符保留在输入中流未读。 (等着再次咬你)。

正确使用scanf

"four",如果正确使用,则可以使用。这意味着

负责检查scanf 每次返回。您必须处理三个条件
  1. [scanf用户通过按Ctrl + d(或在窗口Ctrl + z上)生成手册(return == EOF)来取消输入;
  2. EOF a matching
  3. input失败。对于matching失败,您必须考虑输入缓冲区中剩余的每个字符。 (在输入缓冲区中向前扫描,读取并丢弃字符,直到找到(return < expected No. of conversions)'\n');最后
  4. [EOF表示读取成功–然后由您检查输入是否满足任何其他条件(例如,正整数,正浮点,在所需范围内,等等。)。
  5. 您还必须考虑用(return == expected No. of conversions)成功读取之后输入流中剩余的内容。如上所述,除非您在format string

中特别说明了这一点,否则scanf将使所有转换说明符的输入流中的scanf保持未读状态(如果考虑在内,则通常会导致输入格式易碎)字符串,在想要的输入之后但在'\n'之前很容易被其他多余的字符挫败。)使用'\n'进行输入时,必须戴上会计帽,并考虑输入流中剩余的每个字符并清空输入流必要时,所有冒犯性的字符。

您可以编写一个简单的scanf函数来处理用户输入后剩下的所有多余字符,方法是向前扫描以丢弃所有剩余的字符,直到找到empty_stdin()或遇到'\n'为止。您可以在EOF功能中进行不同程度的操作。您可以编写一个简单的函数,如下所示:

safer_gets()

例如,您可以通过简单的void empty_stdin(void) { int c = getchar(); /* read character */ while (c != '\n' && c != EOF) /* if not '\n' and not EOF */ c = getchar(); /* repeat */ } 循环内联执行相同的操作

for

下一个问题-试图写入无效的地址

[使用for (int c = getchar(); c != '\n' && c != EOF; c = getchar()) {} 时,scanf期望相应转换的参数为适当类型的指针

。在:
scanf

您没有提供指针,而是提供了 printf("\nEnter zipcode: "); scanf("%ld", person[i].zip_code); /* I get a bogus number here */ 值。由于long int的类型为person[i].zip_code,以便为long int填充提供pointer

,因此必须使用address-of运算符,例如scanf告诉&person[i].zip_code用哪个地址填充其提供转换的值。

等待?为什么我不必对数组进行操作?

在访问时,数组将转换为指向第一个元素的指针。因此,对于字符串输入,如果使用数组保存字符串,则会自动将其转换为指针scanf

toupper使用字符而不是字符串

C11 Standard - 6.3.2.1 Other Operands - Lvalues, arrays, and function designators(p3)

如我的评论中所述, printf("%s\n", toupper(person[i].state)); /* This is where the error code is occurring */ 将类型toupper作为参数,而不是类型int。要将字符串转换为大写/小写,您需要遍历每个字符,分别转换每个字符。但是,如果您使用结构的char*成员,则只需要担心2个字符,因此在读取它们时只需将它们都转换即可,例如

.state

safer_gets()中的基本问题

这解决了大多数明显的问题,但是 /* just 2-chars to convert to upper - do it here */ person[i].state[0] = toupper (person[i].state[0]); person[i].state[1] = toupper (person[i].state[1]); 函数本身具有几个基本问​​题。具体地,当由safer_gets()返回时,它无法处理EOF,并且由于没有返回任何类型为getchar()的内容,因此无法向用户提供请求的用户输入是成功还是失败的任何指示。在您编写的任何函数中,如果函数内可能发生故障,则必须向调用函数提供有意义的返回

,以指示所请求的函数操作成功还是失败。

void可以做什么?为什么不返回提供成功读取字符数的简单safer_gets()值,失败则返回int-1的正常值)。您现在可以验证输入是否成功了,这是双重奖励-您还可以获得字符串中的字符数(限制为EOF个字符)。现在,您还可以通过在Linux上使用Ctrl + d

Ctrl + z(Windows)来生成手动EOF,来处理用户取消输入的操作。

您还应该在2147483647以外的所有情况下清空所有输入的字符的stdin。这样可以确保在您调用EOF之后没有任何未读的字符在以后调用另一个输入函数时会咬住您。进行这些更改,您可以将safer_gets()编写为:

safer_gets()

[[note:

/* always provide a meaninful return to indicate success/failure */ int safer_gets (char *array, int max_chars) { int c = 0, nchar = 0; /* loop while room in array and char read isn't '\n' or EOF */ while (nchar + 1 < max_chars && (c = getchar()) != '\n' && c != EOF) array[nchar++] = c; /* assing to array, increment index */ array[nchar] = 0; /* nul-terminate array on loop exit */ while (c != EOF && c != '\n') /* read/discard until newline or EOF */ c = getchar(); /* if c == EOF and no chars read, return -1, otherwise no. of chars */ return c == EOF && !nchar ? -1 : nchar; } 上的测试之上,确保为nul-termination
字符保留一个字符,并且只是nchar + 1 < max_chars的更安全的重排)

输入验证的通用方法

[现在,您可以使用输入函数来指示输入成功/失败,从而使您可以在调用函数中验证输入(此处为nchar < max_chars - 1)。以使用main()读取.full_name成员为例。您不能只是盲目地调用safer_gets()并且不知道输入是被取消还是遇到过早的safer_gets(),然后使用然后继续使用它充满了对代码的信心的字符串。 *验证,确认,确认

每个表达式。返回EOF,您可以通过如下调用main()来读取safer_gets()(以及所有其他字符串变量)来做到这一点:
.full_name

note: #define NAMELEN 35 /* if you need a constant, #define one (or more) */ #define ADDRLEN 50 /* (don't skimp on buffer size) */ ... for (;;) { /* loop continually until valid name input */ fputs ("\nEnter name : ", stdout); /* prompt */ int rtn = safer_gets(person[i].full_name, NAMELEN); /* read name */ if (rtn == -1) { /* user canceled input */ puts ("(user canceled input)"); return 1; /* handle with graceful exit */ } else if (rtn == 0) { /* if name empty - handle error */ fputs (" error: full_name empty.\n", stderr); continue; /* try again */ } else /* good input */ break; } 的返回值被捕获在变量safer_gets()中,然后求值为rtn-1),EOF空字符串或大于0,好的输入)

您可以为需要使用的每个字符串变量执行此操作,然后使用上面讨论的相同原理读取和验证0。简而言之,您可以执行以下操作:

.zip_code

note:

通过使用#include <stdio.h> #include <ctype.h> #define NAMELEN 35 /* if you need a constant, #define one (or more) */ #define ADDRLEN 50 /* (don't skimp on buffer size) */ #define CITYLEN 25 #define STATELEN 3 #define PERSONS 10 struct information { char full_name[NAMELEN], address[ADDRLEN], city[CITYLEN], state[STATELEN]; long int zip_code; }; /* always provide a meaninful return to indicate success/failure */ int safer_gets (char *array, int max_chars) { int c = 0, nchar = 0; /* loop while room in array and char read isn't '\n' or EOF */ while (nchar + 1 < max_chars && (c = getchar()) != '\n' && c != EOF) array[nchar++] = c; /* assing to array, increment index */ array[nchar] = 0; /* nul-terminate array on loop exit */ while (c != EOF && c != '\n') /* read/discard until newline or EOF */ c = getchar(); /* if c == EOF and no chars read, return -1, otherwise no. of chars */ return c == EOF && !nchar ? -1 : nchar; } int main (void) { /* declare varaibles, initialize to all zero */ struct information person[PERSONS] = {{ .full_name = "" }}; int i = 0, x = 0; puts ("\nWelcome to the mailing label generator program.\n"); /* greeting */ for (;;) { /* loop continually until a valid no. of people entered */ int rtn = 0; /* variable to hold RETURN from scanf */ fputs ("Number of people to generate labels for? (0-10): ", stdout); rtn = scanf ("%d", &x); if (rtn == EOF) { /* user generated manual EOF (ctrl+d [ctrl+z windows]) */ puts ("(user canceled input)"); return 0; } else { /* either good input or (matching failure or out-of-range) */ /* all required clearing though newline - do that here */ for (int c = getchar(); c != '\n' && c != EOF; c = getchar()) {} if (rtn == 1) { /* return equals requested conversions - good input */ if (0 <= x && x <= PERSONS) /* validate input in range */ break; /* all checks passed, break read loop */ else /* otherwise, input out of range */ fprintf (stderr, " error: %d, not in range 0 - %d.\n", x, PERSONS); } else /* matching failure */ fputs (" error: invalid integer input.\n", stderr); } } if (!x) { /* since zero is a valid input, check here, exit if zero requested */ fputs ("\nzero persons requested - nothing further to do.\n", stdout); return 0; } /* Begin loop for individual information */ for (i = 0; i < x; i++) { /* loop until all person filled */ /* read name, address, city, state */ for (;;) { /* loop continually until valid name input */ fputs ("\nEnter name : ", stdout); /* prompt */ int rtn = safer_gets(person[i].full_name, NAMELEN); /* read name */ if (rtn == -1) { /* user canceled input */ puts ("(user canceled input)"); return 1; /* handle with graceful exit */ } else if (rtn == 0) { /* if name empty - handle error */ fputs (" error: full_name empty.\n", stderr); continue; /* try again */ } else /* good input */ break; } for (;;) { /* loop continually until valid street input */ fputs ("Enter street address : ", stdout); /* prompt */ int rtn = safer_gets(person[i].address, ADDRLEN); /* read address */ if (rtn == -1) { /* user canceled input */ puts ("(user canceled input)"); return 1; /* handle with graceful exit */ } else if (rtn == 0) { /* if address empty - handle error */ fputs ("error: street address empty.\n", stderr); continue; /* try again */ } else /* good input */ break; } for (;;) { /* loop continually until valid city input */ fputs ("Enter city : ", stdout); /* prompt */ int rtn = safer_gets(person[i].city, CITYLEN); /* read city */ if (rtn == -1) { /* user canceled input */ puts ("(user canceled input)"); return 1; /* handle with graceful exit */ } else if (rtn == 0) { /* if city empty - handle error */ fputs ("error: city empty.\n", stderr); continue; /* try again */ } else /* good input */ break; } for (;;) { /* loop continually until valid state input */ fputs ("Enter state : ", stdout); /* prompt */ int rtn = safer_gets(person[i].state, STATELEN); /* read state */ if (rtn == -1) { /* user canceled input */ puts ("(user canceled input)"); return 1; /* handle with graceful exit */ } else if (rtn == 0) { /* if state empty - handle error */ fputs ("error: state empty.\n", stderr); continue; /* try again */ } else { /* good input */ /* just 2-chars to convert to upper - do it here */ person[i].state[0] = toupper (person[i].state[0]); person[i].state[1] = toupper (person[i].state[1]); break; } } /* read/validate zipcode */ for (;;) { /* loop continually until valid zipcode input */ fputs ("Enter zipcode : ", stdout); /* prompt */ int rtn = scanf ("%ld", &person[i].zip_code); /* read zip */ if (rtn == EOF) { /* user pressed ctrl+d [ctrl+z windows] */ puts ("(user canceled input)"); return 1; } else { /* handle all other cases */ /* remove all chars through newline or EOF */ for (int c = getchar(); c != '\n' && c != EOF; c = getchar()) {} if (rtn == 1) { /* long int read */ /* validate in range */ if (1 <= person[i].zip_code && person[i].zip_code <= 99999) break; else fprintf (stderr, " error: %ld not in range of 1 - 99999.\n", person[i].zip_code); } else /* matching failure */ fputs (" error: invalid long integer input.\n", stderr); } } } /* Output individual information in mailing format, condition for 0 individuals */ for(i = 0; i < x; i++) /* you only need a single printf */ printf ("\n%s\n%s\n%s, %s %ld\n", person[i].full_name, person[i].address, person[i].city, person[i].state, person[i].zip_code); fputs ("\nThank you for using the program.\n", stdout); } 创建所需的常量,如果需要调整数字,则只有一个地方可以进行更改,并且不必留意每个变量的声明和循环限制尝试进行更改)

示例使用/输出

完成任何输入例程的编写后,请--[[尝试破坏它!

]查找失败并修复的极端情况。继续尝试通过故意输入不正确/无效的输入来打破它,直到它不再除用户需要输入的内容以外。练习输入例程,例如#define
指定用户希望在Linux上使用

Ctrl + d

Ctrl + z(Windows)生成手动EOF时随时希望取消输入,您应该可以随时处理在您的代码中。在第一个提示处:

$ ./bin/nameaddrstruct Welcome to the mailing label generator program. Number of people to generate labels for? (0-10): 3 Enter name : Mickey Mouse Enter street address : 111 Disney Ln. Enter city : Orlando Enter state : fL Enter zipcode : 44441 Enter name : Minnie Mouse Enter street address : 112 Disney Ln. Enter city : Orlando Enter state : Fl Enter zipcode : 44441 Enter name : Pluto (the dog) Enter street address : 111-b.yard Disney Ln. Enter city : Orlando Enter state : fl Enter zipcode : 44441 Mickey Mouse 111 Disney Ln. Orlando, FL 44441 Minnie Mouse 112 Disney Ln. Orlando, FL 44441 Pluto (the dog) 111-b.yard Disney Ln. Orlando, FL 44441 Thank you for using the program.

或此后任何提示:

$ ./bin/nameaddrstruct Welcome to the mailing label generator program. Number of people to generate labels for? (0-10): (user canceled input)

处理零人请求:

$ ./bin/nameaddrstruct Welcome to the mailing label generator program. Number of people to generate labels for? (0-10): 3 Enter name : Mickey Mouse Enter street address : 111 Disney Ln. Enter city : (user canceled input)

(**个人,我只是更改输入测试,而是让他们输入$ ./bin/nameaddrstruct

Welcome to the mailing label generator program.

Number of people to generate labels for? (0-10): 0

zero persons requested - nothing further to do.
的值)

无效的输入:

1-10

您明白了...底线,必须在使用程序中的输入之前验证每个用户输入并知道它是有效的。除非

检查返回值

,否则无法验证任何功能的任何输入。如果您没有带走其他东西,那么学习是值得的。
[仔细检查,如果您还有其他问题,请告诉我。 (并询问您的教授$ ./bin/nameaddrstruct Welcome to the mailing label generator program. Number of people to generate labels for? (0-10): -1 error: -1, not in range 0 - 10. Number of people to generate labels for? (0-10): 11 error: 11, not in range 0 - 10. Number of people to generate labels for? (0-10): banannas error: invalid integer input. Number of people to generate labels for? (0-10): 10 Enter name : (user canceled input) 如何处理safer_gets(),以及如何验证功能是成功还是失败)
© www.soinside.com 2019 - 2024. All rights reserved.