难以使用 scanf 进行输入
Difficulty using scanf for input
有人可以帮我解决我的问题吗?我对 %[^\n]
有疑问。当我尝试输入错误输入时,程序循环我写的警告,但如果我使用 %s
并输入我的字符串,则下一条语句无法正常工作。
#pragma warning (disable:4996)
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main(){
char name[30];
char number[12];
int flag, flag1, flag2, flag3;
int i;
printf("Add New Contact\n");
do {
printf("input name [1..30 char]: ");
scanf("%[^\n]", name); fflush(stdin);
if ((name[0] >= 'A' && name[0] <= 'Z') || (name[0] >= 'a' && name[0] <= 'z')) {
flag = 1;
}
else {
flag = 0;
printf("First letter of name should be an alphabet (A-Z or a-z)\n");
}
if (strlen(name) > 30) {
flag1 = 0;
printf("Length of name should be between 1 and 30 characters\n");
}
else {
flag1 = 1;
}
} while (flag == 0 || flag1 == 0);
do {
printf("Input phone number[6..12 digits]: ");
scanf("%s", number); fflush(stdin);
for (i = 0; i < strlen(number); i++) {
if (number[i] >= '0' && number[i] <= '9') {
flag2 = 1;
}
else {
flag2 = 0;
}
}
if (flag2 == 0) {
printf("Phone numbers should only contain digits (0-9)\n");
}
if (strlen(number) >= 6 && strlen(number) <= 12) {
flag3 = 1;
}
else {
flag3 = 0;
printf("Length of phone numbers should be between 6 and 12 digits\n");
}
} while (flag2 == 0 || flag3 == 0);
printf("\n");
printf("New contact successfully added!\n");
printf("Press Enter to continue...");
getchar();
getchar();
return 0;
}
哦,顺便说一句,问题可能只是 scanf
调用在缓冲区中留下了换行符,如果您循环并重试,看到的第一个字符将是换行符和 scanf
不应该读任何东西。
有两件事你应该做:首先检查scanf
returns,它应该return1
如果它读取一个字符串。其次,您应该通过在格式字符串中首先添加 space 来告诉 scanf
丢弃任何可能的前导白色-space:" %[^\n]"
.
大多数 scanf
格式会自动跳过前导白色-space,但在使用 "%["
或 "%c"
格式时不会。
此外,为了不担心写出数组的边界,您应该添加一个长度修饰符以确保 scanf
读取的输入不会超过它可以写入的输入:" %29[^\n]"
.如果在此之后字符串的长度是 29
,那么您可能应该逐个字符地阅读直到到达行尾。
这是你的程序修复:
#include <stdio.h>
#include <ctype.h>
#include <string.h>
// In case you need this -- not needed for this case
void discard_input()
{
char c;
while( ( c = getchar() ) != '\n' && c != EOF );
}
void remove_trailing_newline(char * s)
{
char * ch = s + strlen( s ) - 1;
while( ch != s ) {
if ( *ch == '\n' ) {
*ch = 0;
break;
}
--ch;
}
return;
}
int main(){
char name[30];
char number[12];
int flag, flag1, flag2, flag3;
int i;
printf("Add New Contact\n");
do {
printf("\nInput name [1..30 char]: ");
fgets( name, 30, stdin );
remove_trailing_newline( name );
flag1 = flag = 1;
if ( !isalpha( name[ 0 ] ) ) {
flag = 0;
printf("First letter of name should be an alphabet (A-Z or a-z), found: %s\n", name );
}
// impossible
if (strlen(name) > 30) {
flag1 = 0;
printf("Length of name should be between 1 and 30 characters\n");
}
} while (flag == 0 || flag1 == 0);
do {
printf("\nInput phone number[6..12 digits]: ");
fgets( number, 12, stdin );
remove_trailing_newline( number );
flag2 = flag3 = 1;
int len_phone = strlen( number );
for (i = 0; i < strlen(number); i++) {
if ( !isdigit( number[ i ] ) ) {
flag2 = 0;
}
}
if (flag2 == 0) {
printf("Phone numbers should only contain digits (0-9), found:'%s'\n", number);
}
if ( len_phone < 6 || len_phone > 12) {
flag3 = 0;
printf("Length of phone numbers should be between 6 and 12 digits, found: %d\n", len_phone );
}
} while (flag2 == 0 || flag3 == 0);
printf("\n");
printf( "Name: '%s'\n", name );
printf( "Phone: '%s'\n", number );
printf("New contact successfully added!\n");
printf("Press Enter to continue...");
getchar();
return 0;
}
您可以找到程序 here。
修复或多或少很有趣,我在这里列举它们:
起初,我认为问题是尾随的新行被留在输入缓冲区中。 fflush(stdin)
实际上是 C 中的未定义行为,因为 fflush()
函数用于输出流。无论如何,我将代码包含在 question 12.26b of the comp.lang.c FAQ 中,因为我认为将其作为参考很有趣。然后,我决定把scanf()
改成fgets()
。这是由于 scanf()
以空格作为分隔符,因此您无法写出完整的名称,即姓名和姓氏。请记住 gets()
不是一个选项,因为它写入的输入超过了缓冲区的限制。实际上,fgets()
通过让我们定义要读取的字符限制来解决这个问题。问题是 fgets()
还在缓冲区中包含 '\n',所以,这就是我包含 remove_trailing_newline()
函数的原因。很棘手,不是吗?
您添加了一个条件来检查输入的名称是否超过三十个字符。实际上,这是不可能签入您的程序的。首先,fgets()
将读取 29 个字符 + 最终字符标记 (0)。其次,如果您实际上允许输入超过 30 个字符,那么输入将超过缓冲区的大小,这是未定义的行为(在大多数情况下会崩溃)。您将不得不使用更复杂的东西,例如 C++ 中的 std::string,然后检查其长度。或者可以为 C 使用第三方可扩展 string
。或者推出您自己的 expandable string...
您可以使用isalpha(c)
和isdigit(c)
函数来决定是字母字符还是数字。
当你要多次使用一个值时,比如strlen(name)
,那么你应该预先计算它并将它存储在一个局部变量中。虽然一个好的编译器(它的优化器)会检测到这种情况并为您解决,但您永远不知道哪个编译器将编译您的代码,以及它有多先进。此外,让优化器更容易做事也没有错。
当您遇到设置标志以指示错误情况的情况时,在检查任何内容之前将其设置为 "no error" 值更容易,并且仅在以下情况下一个错误,将其设置为 "error" 值。这将更容易阅读,因此也更容易理解。
希望这对您有所帮助。
有人可以帮我解决我的问题吗?我对 %[^\n]
有疑问。当我尝试输入错误输入时,程序循环我写的警告,但如果我使用 %s
并输入我的字符串,则下一条语句无法正常工作。
#pragma warning (disable:4996)
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main(){
char name[30];
char number[12];
int flag, flag1, flag2, flag3;
int i;
printf("Add New Contact\n");
do {
printf("input name [1..30 char]: ");
scanf("%[^\n]", name); fflush(stdin);
if ((name[0] >= 'A' && name[0] <= 'Z') || (name[0] >= 'a' && name[0] <= 'z')) {
flag = 1;
}
else {
flag = 0;
printf("First letter of name should be an alphabet (A-Z or a-z)\n");
}
if (strlen(name) > 30) {
flag1 = 0;
printf("Length of name should be between 1 and 30 characters\n");
}
else {
flag1 = 1;
}
} while (flag == 0 || flag1 == 0);
do {
printf("Input phone number[6..12 digits]: ");
scanf("%s", number); fflush(stdin);
for (i = 0; i < strlen(number); i++) {
if (number[i] >= '0' && number[i] <= '9') {
flag2 = 1;
}
else {
flag2 = 0;
}
}
if (flag2 == 0) {
printf("Phone numbers should only contain digits (0-9)\n");
}
if (strlen(number) >= 6 && strlen(number) <= 12) {
flag3 = 1;
}
else {
flag3 = 0;
printf("Length of phone numbers should be between 6 and 12 digits\n");
}
} while (flag2 == 0 || flag3 == 0);
printf("\n");
printf("New contact successfully added!\n");
printf("Press Enter to continue...");
getchar();
getchar();
return 0;
}
哦,顺便说一句,问题可能只是 scanf
调用在缓冲区中留下了换行符,如果您循环并重试,看到的第一个字符将是换行符和 scanf
不应该读任何东西。
有两件事你应该做:首先检查scanf
returns,它应该return1
如果它读取一个字符串。其次,您应该通过在格式字符串中首先添加 space 来告诉 scanf
丢弃任何可能的前导白色-space:" %[^\n]"
.
大多数 scanf
格式会自动跳过前导白色-space,但在使用 "%["
或 "%c"
格式时不会。
此外,为了不担心写出数组的边界,您应该添加一个长度修饰符以确保 scanf
读取的输入不会超过它可以写入的输入:" %29[^\n]"
.如果在此之后字符串的长度是 29
,那么您可能应该逐个字符地阅读直到到达行尾。
这是你的程序修复:
#include <stdio.h>
#include <ctype.h>
#include <string.h>
// In case you need this -- not needed for this case
void discard_input()
{
char c;
while( ( c = getchar() ) != '\n' && c != EOF );
}
void remove_trailing_newline(char * s)
{
char * ch = s + strlen( s ) - 1;
while( ch != s ) {
if ( *ch == '\n' ) {
*ch = 0;
break;
}
--ch;
}
return;
}
int main(){
char name[30];
char number[12];
int flag, flag1, flag2, flag3;
int i;
printf("Add New Contact\n");
do {
printf("\nInput name [1..30 char]: ");
fgets( name, 30, stdin );
remove_trailing_newline( name );
flag1 = flag = 1;
if ( !isalpha( name[ 0 ] ) ) {
flag = 0;
printf("First letter of name should be an alphabet (A-Z or a-z), found: %s\n", name );
}
// impossible
if (strlen(name) > 30) {
flag1 = 0;
printf("Length of name should be between 1 and 30 characters\n");
}
} while (flag == 0 || flag1 == 0);
do {
printf("\nInput phone number[6..12 digits]: ");
fgets( number, 12, stdin );
remove_trailing_newline( number );
flag2 = flag3 = 1;
int len_phone = strlen( number );
for (i = 0; i < strlen(number); i++) {
if ( !isdigit( number[ i ] ) ) {
flag2 = 0;
}
}
if (flag2 == 0) {
printf("Phone numbers should only contain digits (0-9), found:'%s'\n", number);
}
if ( len_phone < 6 || len_phone > 12) {
flag3 = 0;
printf("Length of phone numbers should be between 6 and 12 digits, found: %d\n", len_phone );
}
} while (flag2 == 0 || flag3 == 0);
printf("\n");
printf( "Name: '%s'\n", name );
printf( "Phone: '%s'\n", number );
printf("New contact successfully added!\n");
printf("Press Enter to continue...");
getchar();
return 0;
}
您可以找到程序 here。
修复或多或少很有趣,我在这里列举它们:
起初,我认为问题是尾随的新行被留在输入缓冲区中。
fflush(stdin)
实际上是 C 中的未定义行为,因为fflush()
函数用于输出流。无论如何,我将代码包含在 question 12.26b of the comp.lang.c FAQ 中,因为我认为将其作为参考很有趣。然后,我决定把scanf()
改成fgets()
。这是由于scanf()
以空格作为分隔符,因此您无法写出完整的名称,即姓名和姓氏。请记住gets()
不是一个选项,因为它写入的输入超过了缓冲区的限制。实际上,fgets()
通过让我们定义要读取的字符限制来解决这个问题。问题是fgets()
还在缓冲区中包含 '\n',所以,这就是我包含remove_trailing_newline()
函数的原因。很棘手,不是吗?您添加了一个条件来检查输入的名称是否超过三十个字符。实际上,这是不可能签入您的程序的。首先,
fgets()
将读取 29 个字符 + 最终字符标记 (0)。其次,如果您实际上允许输入超过 30 个字符,那么输入将超过缓冲区的大小,这是未定义的行为(在大多数情况下会崩溃)。您将不得不使用更复杂的东西,例如 C++ 中的 std::string,然后检查其长度。或者可以为 C 使用第三方可扩展string
。或者推出您自己的 expandable string...您可以使用
isalpha(c)
和isdigit(c)
函数来决定是字母字符还是数字。当你要多次使用一个值时,比如
strlen(name)
,那么你应该预先计算它并将它存储在一个局部变量中。虽然一个好的编译器(它的优化器)会检测到这种情况并为您解决,但您永远不知道哪个编译器将编译您的代码,以及它有多先进。此外,让优化器更容易做事也没有错。当您遇到设置标志以指示错误情况的情况时,在检查任何内容之前将其设置为 "no error" 值更容易,并且仅在以下情况下一个错误,将其设置为 "error" 值。这将更容易阅读,因此也更容易理解。
希望这对您有所帮助。