0

我正在为学校创建一个邮寄标签生成器,但遇到了一些问题。我的程序是从 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:

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

4

1 回答 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()在输入流中看到的第一个字符是在您调用 in 后'\n'仍处于未读状态的字符:stdinscanf

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

用于数字转换的所有scanf 格式说明符仅通过构成数字的最后一位(或小数点)读取,留下'\n'由用户Enter在输入流中按下未读生成的数字(stdin此处)。这是鼓励新的 C 程序员使用诸如(或 POSIX )之类的面向行的输入函数读取用户输入,然后用于解析填充缓冲区中的值的主要原因之一。fgets()getline()sscanf

为什么用户输入首选面向行的输入函数

通过使用具有足够缓冲区的面向行的输入功能,消耗了用户输入的完整行(包括'\n'来自用户的按Enter)。这可确保stdin为下一个输入做好准备,并且不会有前一个输入留下的未读字符等待咬你。

正确使用所有输入功能

如果您从这个答案中没有得到任何其他信息,请学习这一点——除非您检查 return,否则您无法正确使用任何输入函数。scanf对于函数族尤其如此。为什么?如果您尝试读取一个整数,scanf而用户输入"four"了,则会发生匹配失败,并且从您的输入流中提取字符会停止,第一个无效字符会在您的输入流中留下所有违规字符unread。(只是等着再咬你)。

正确使用 scanf

scanf可以使用,如果使用正确。这意味着有责任检查每次退货。你必须处理三个条件scanf

  1. (return == EOF)EOF用户通过按下Ctrl+d(或在 windows 上)生成手册来取消输入Ctrl+z
  2. (return < expected No. of conversions)发生匹配输入失败。对于匹配失败,您必须考虑输入缓冲区中剩余的每个字符。(在输入缓冲区中向前扫描读取并丢弃字符,直到找到一个'\n'EOF);最后
  3. (return == expected No. of conversions)表示读取成功——然后由您来检查输入是否符合任何其他标准(例如正整数、正浮点、在所需范围内等)。

您还必须考虑在成功读取后输入流中剩余的内容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()) {}

下一个问题——尝试写入无效地址

使用 时scanfscanf期望相应转换的参数是指向适当类型的指针。在:

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

您没有提供指针,long int而是提供了一个值。因为person[i].zip_codeis 类型long int为了提供一个要填充的指针scanf所以您必须使用address-of运算符,例如&person[i].zip_code,告诉scanf使用它提供转换的值来填充哪个地址。

等待?为什么我不必用数组来做呢?在访问时,数组被转换为指向第一个元素的指针。因此对于字符串输入,如果使用数组来保存字符串,它会自动转换为指针C11 标准 - 6.3.2.1 其他操作数 - 左值、数组和函数指示符(p3)

toupper 作用于字符而不是字符串

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

正如我在评论中所讨论的,toupper将 typeint作为参数,而不是 type char*。要将字符串转换为大写/小写,您需要遍历每个字符,分别转换每个字符。但是,在您使用.state结构成员的情况下,只需担心 2 个字符,因此只需在读取它们时将它们都转换,例如

            /* 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]);

safe_gets() 中的基本问题

这解决了大多数明显的问题,但safer_gets()函数本身有几个基本问​​题。具体来说,它EOF在返回时无法处理,getchar()并且由于没有返回 type ,因此无法向用户提供所请求的用户输入是成功还是失败的任何指示void。在你编写的任何函数中,如果函数内有任何失败的可能性,你必须向调用函数提供一个有意义的返回,以指示函数的请求操作是成功还是失败。

你能做什么safer_gets()?为什么不返回一个简单的int值,提供成功时读取的字符数,或失败时-1(的正常值EOF)。您现在可以获得双重奖励,即能够验证输入是否成功——您还可以获得字符串中的字符数(仅限于2147483647字符)。Ctrl+d您现在还可以通过在 Linux 或Ctrl+z(windows)上生成手动 EOF 来处理用户取消输入的问题。

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

/* 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;
}

注意:上面的测试nchar + 1 < max_chars确保一个字符保留为nul 终止字符,并且只是一个更安全的重新排列nchar < max_chars - 1

输入验证的一般方法

现在,您可以使用一个输入函数来指示输入的成功/失败,从而允许您在调用函数(main()此处)中验证输入。以使用 读取.full_name成员为例safer_gets()。您不能只是盲目地调用safer_gets()而不知道输入是被取消还是过早EOF遇到,然后继续使用它填充的字符串,对您的代码有任何信心。*验证,验证,验证每个表达式。回到main(),您可以通过safer_gets()如下调用来读取.full_name(以及所有其他字符串变量):

#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. 把它放在一个简短的例子中,你可以这样做:

#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来创建所需的常量,如果您需要调整一个数字,您只有一个地方可以进行更改,并且您不会通过每个变量声明和循环限制来尝试进行更改)

示例使用/输出

当你写完任何输入例程——去尝试打破它!找到失败的极端情况并修复它们。继续尝试通过故意输入不正确/无效的输入来破坏它,直到它不再除用户需要输入的内容之外的任何内容。练习你的输入程序,例如

$ ./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.

Ctrl+d尊重用户希望在 Linux 或(Windows)上生成手动 EOF 时随时取消输入Ctrl+z,您应该能够从代码中的任何位置处理该问题。

在第一个提示:

$ ./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)

您明白了...底线,您必须验证每个用户输入并知道它是有效的,然后才能在程序中使用输入。除非您检查 return,否则您无法验证来自任何函数的任何输入。如果除此之外什么都不带走,那么学习是值得的。

如果您还有其他问题,请仔细查看并告诉我。(并询问您的教授。如何safer_gets()处理EOF以及您应该如何验证功能是成功还是失败)

于 2020-03-01T06:44:53.147 回答