首页 > 解决方案 > 在 C 中输入 char 字符串的正确输入法

问题描述

我如何最好地在一个程序中输入,该程序要求用户输入用空格分隔的学生姓名,然后输入学生分数,例如:

zach 85

由于空终止符,我将必须考虑两个输入吗?我已经在我的程序中使用了两个 scanfs。

int main()
{
   const int row = 5;
   const int col = 10;
   int i;
   char names[row][col];
   int scores[row];
   int total;

   // Read names and scores from standard input into names and scores array  
   // Assume that each line has a first name and a score separated by a space
   // Assume that there are five such lines in the input
   // 10 points

   for(int i = 0; i<row; i++)
   {
       printf("Enter student name: \n");
       scanf("%s",&names);
       scanf("%s", &scores);
   }
   // Print the names and scores
   // The print out should look the same as the input source
   // 10 points

   for(int i = 0; i<row; i++)
   {
       printf( "%s %d \n", names[i]  /*, scores[i] */ );       
   }
}

标签: cinputnull-terminated

解决方案


type的 for scores( ) 与您尝试使用( )int scores[row];阅读不对应。转换说明符用于转换空格分隔的字符串,而不是整数。为整数转换提供了转换说明符。scoresscanfscanf("%s", &scores);"%s" "%d"

在看细节之前。任何时候你有一个将不同类型的数据协调为一个单元的编码任务(例如,student每个都有一个namechar*)和一个scoreint)),你应该考虑使用一个struct包含namescore作为成员。这样只有一个需要的结构数组,而不是试图协调多个不同类型的数组以包含相同的信息。

此外,不要吝啬字符数组的缓冲区大小。您宁愿 10,000 个字符太长也不愿 1 个字符太短。如果您认为您的最大名称是 10-16 个字符,请使用 64 字符(或更大)的缓冲区以确保您可以读取整行数据 - 消除输入的一些杂散字符可能导致字符保持未读的可能性stdin

一个简单stuct的就是所有需要的。为了方便起见,您可以添加一个typedef(以避免必须struct name_of_struct为每个声明或参数键入),例如

 #include <stdio.h>

#define ROW 5       /* if you need a constant, #define one (or more) */
#define COL 64

typedef struct {        /* delcare a simple struct with name/score */
    char name[COL];     /* (add a typedef for convenience)         */
    int score;
} student_t;

现在你有了一个包含你的学生的结构,name并且score作为一个单元而不是两个数组,一个char和一个int你必须处理。[1]

student_t剩下的就是声明一个用于您的代码的数组,例如

int main (void) {

    int n = 0;      /* declare counter */
    student_t student[ROW] = {{ .name = "" }};  /* array of struct */

    puts ("\n[note; press Enter alone to end input]\n");

声明结构数组后,您可以转向输入处理。处理输入的一种稳健方法是不断循环,验证您是否在每次迭代中收到预期的输入,处理出现的任何错误(优雅地使您的代码继续),并跟踪输入的数量以便您可以保护您的数组边界并避免通过超出数组末尾的写入来调用未定义的行为

您可以开始输入循环,提示并阅读您的输入行,fgets如我的评论中所述。与尝试使用scanf. 最值得注意的是,输入缓冲区(stdin此处)中仍未读取的内容不取决于使用的转换说明符。整行(直到并包括尾随'\n')从输入缓冲区中提取并放置在您给fgets填充的缓冲区中。您还可以检查用户是否只需按下Enter可以方便地指示输入结束的按键,例如

    for (;;) {  /* loop until all input given or empty line entered */
        char buf[COL];              /* declare buffer to hold line */

        fputs ("Enter student name: ", stdout); /* prompt */
        if (!fgets (buf, sizeof buf, stdin))    /* read/validate line */
            break;
        if (*buf == '\n')   /* check for empty line */
            break;

(请注意,您可以(并且应该)另外检查填充的缓冲区的字符串长度(1)检查读取的最后一个字符是否'\n'确保读取了完整的行;以及(2)如果最后一个字符不'\n'检查长度是否等于最大长度 ( -1),表示字符可能未读。(留给您)

现在您知道您有一行输入并且它不是空的,您可以调用sscanf以将行解析为每个学生的nameand score,同时优雅地处理转换中的任何失败,例如

        /* parse line into name and score - validate! */
        if (sscanf (buf, "%63s %d", student[n].name, &student[n].score) != 2)
        {   /* handle error */
            fputs ("  error: invalid input, conversion failed.\n", stderr);
            continue;
        }
        n++;                /* increment row count - after validating */
        if (n == ROW) {     /* check if array full (protect array bounds) */
            fputs ("\narray full - input complete.\n", stdout);
            break;
        }
    }

如果您注意,您可以从健壮性的角度看到使用fgetsand方法的好处之一。sscanf您将获得 (1) 读取用户输入的独立验证;(2) 将该输入分离(或解析)为所需的值。任何一种情况下的故障都可以得到适当的处理。

将所有部分放在一个简短的程序中,您可以执行以下操作:

#include <stdio.h>

#define ROW 5       /* if you need a constant, #define one (or more) */
#define COL 64

typedef struct {        /* delcare a simple struct with name/score */
    char name[COL];     /* (add a typedef for convenience)         */
    int score;
} student_t;

int main (void) {

    int n = 0;      /* declare counter */
    student_t student[ROW] = {{ .name = "" }};  /* array of struct */

    puts ("\n[note; press Enter alone to end input]\n");

    for (;;) {  /* loop until all input given or empty line entered */
        char buf[COL];              /* declare buffer to hold line */

        fputs ("Enter student name: ", stdout); /* prompt */
        if (!fgets (buf, sizeof buf, stdin))    /* read/validate line */
            break;
        if (*buf == '\n')   /* check for empty line */
            break;
        /* parse line into name and score - validate! */
        if (sscanf (buf, "%63s %d", student[n].name, &student[n].score) != 2)
        {   /* handle error */
            fputs ("  error: invalid input, conversion failed.\n", stderr);
            continue;
        }
        n++;                /* increment row count - after validating */
        if (n == ROW) {     /* check if array full (protect array bounds) */
            fputs ("\narray full - input complete.\n", stdout);
            break;
        }
    }

    for (int i = 0; i < n; i++) /* output stored names and values */
        printf ("%-16s %3d\n", student[i].name, student[i].score);
}

示例使用/输出

每当您编写输入例程时——去尝试并打破它!. 故意输入无效数据。如果您的输入程序中断 -去修复它!. 在如上所述的代码中,唯一留给您实现和处理的检查是输入大于COL字符数(例如键盘上的猫步)。练习你的输入:

$ ./bin/studentnamescore

[note; press Enter alone to end input]

Enter student name: zach 85
Enter student name: the dummy that didn't pass
  error: invalid input, conversion failed.
Enter student name: kevin 96
Enter student name: nick 56
Enter student name: martha88
  error: invalid input, conversion failed.
Enter student name: martha 88
Enter student name: tim 77

array full - input complete.
zach              85
kevin             96
nick              56
martha            88
tim               77

虽然您可以使用两个单独的数组,但单个结构数组是一种更好的方法。如果您还有其他问题,请仔细查看并告诉我。

脚注:

  1. 请注意,POSIX 指定以 suffix 结尾的名称_t保留供其使用。( size_t, uint64_t, etc...) 另请注意,您会看到常用的后缀。所以在你自己制作之前检查一下(但我们没有没有 POSIXstudent_t类型)。

推荐阅读