我正在为学校创建一个邮件标签生成器,并且遇到了一些问题。我的程序要使用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函数,以确保用户输入的字符不会超出数组可以容纳的数量。感谢您的帮助和宽容,帮助我理解自己的错误!
让我们一一看一下这个问题:
换行号或读取的人后保留在标准输入中
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
字符:
用于数字转换,仅读入组成数字的最后一位(或小数点),而使用户按Enter生成的scanf
所有
printf("How many people do you want to generate labels for (0-10)? "); scanf("%i", &x);
格式说明符
scanf
在输入中未读-stream(此处为'\n'
)。这是鼓励新的C程序员阅读使用line-aligned输入函数(例如stdin
(或POSIX fgets()
))的用户输入,然后使用getline()
来解析C中的值的主要原因之一。填充缓冲区。 为什么用户优选使用面向行的输入功能
通过使用具有足够缓冲区的面向行
输入功能,将消耗用户输入的完整行(包括用户按下Enter的sscanf
)。这样可以确保'\n'
准备好用于下一次输入,并且不会有先前的输入遗留下来的未读字符在等着您。正确使用所有输入功能
如果您没有从此答案中获得任何其他收益,请学习此知识-除非您检查返回值
,否则您将无法正确使用任何输入函数。对于stdin
系列功能尤其如此。为什么?如果您尝试使用scanf
读取整数,而用户输入的是scanf
,则会出现matching failure,并且将从输入流中提取字符,并停止第一个无效字符,而将所有有问题的字符保留在输入中流未读。 (等着再次咬你)。正确使用scanf
"four"
,如果正确使用,则可以使用。这意味着您
scanf
每次的返回。您必须处理三个条件scanf
用户通过按Ctrl + d(或在窗口Ctrl + z上)生成手册(return == EOF)
来取消输入;EOF
a matching(return < expected No. of conversions)
或'\n'
);最后EOF
表示读取成功–然后由您检查输入是否满足任何其他条件(例如,正整数,正浮点,在所需范围内,等等。)。您还必须考虑用(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
期望相应转换的参数为适当类型的指针
,因此必须使用address-of运算符,例如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
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
您还应该在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 + z(Windows)生成手动EOF时随时希望取消输入,您应该可以随时处理在您的代码中。在第一个提示处:Ctrl + d
$ ./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()
,以及如何验证功能是成功还是失败)