Правильный метод ввода для ввода строки символов в C - PullRequest
0 голосов
/ 09 ноября 2018

Как мне лучше всего принять участие в программе, которая просит пользователя ввести имя студента, разделенное пробелом, а затем балл студента, например:

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

Ответы [ 3 ]

0 голосов
/ 09 ноября 2018

Вы почти у цели. Однако вы должны убедиться, что вы делаете все правильно. Давайте просто сделаем это шаг за шагом:

Шаг 1, получите ОДНО имя и ОДИН счет

#include <stdio.h>

#define MAX_NAME_LENGTH 30

int main() {
      char name[MAX_NAME_LENGTH+1]; /* an array of characters making up ONE name (+1 for terminating NUL char in case of max-length name) */
      unsigned int score; /* a score */

      scanf("%30s", name); /* scan a name (assuming NO spaces in the name)*/
      /* also notice that name has no & in front of it because it already IS a pointer to the array name[MAX_NAME_LENGTH] */

      scanf("%u", &score);

      printf("%s scored %u in the test\n", name, score);
      return 0;
}

(См. Это работает на http://tpcg.io/jS3woS)

ШАГ 2 - Итерация для получения нескольких баллов

Теперь давайте прочитаем 5 пар, а затем распечатаем 5 пар.

#include <stdio.h>

#define MAX_NAME_LENGTH 30
/* i called rows iterations here just to provide contrast */
/* you can call them ROWS if you want but it then creates a confusion about name length */


#define ITERATIONS 5 

int main() {
      char name[ITERATIONS][MAX_NAME_LENGTH+1]; /* an array of names where each name is MAX_NAME_LENGTH long (notice the order) */
      unsigned int score[ITERATIONS]; /* score */

      int i; 

      for(i = 0; i < ITERATIONS; i++ ) {
           scanf("%30s", name[i]); /* scan a name (assuming NO spaces in the name)*/
           /* notice that name[i] has no & in front of it because name[i] is the pointer to the i-th row */

           scanf("%u", &score[i]);
      }

      for(i = 0; i < ITERATIONS; i++ ) {
           printf("%s scored %u in the test\n", name[i], score[i]);
      }

      return 0;
}

Смотрите это в действии здесь (http://tpcg.io/iTj4ag)

0 голосов
/ 09 ноября 2018

Ваш type для scores (int scores[row];) не соответствует вашей попытке прочитать scores с scanf (scanf("%s", &scores);). Спецификатор преобразования "%s" предназначен для преобразования строк, разделенных пробелами, а не целых чисел. "%d" спецификатор преобразования предоставляется для целочисленных преобразований.

Прежде чем смотреть на конкретику. Каждый раз, когда у вас есть задача кодирования, состоящая в координации различных типов данных как единой единицы (например, student каждый с name (char*) и score (int), вы должны думать о использование struct, содержащего name и score в качестве членов. Таким образом, нужен только один массив struct , вместо того, чтобы пытаться координировать несколько массивов разных типов, содержащих одну и ту же информацию.

Кроме того, не экономит на размере буфера для символьных массивов. Вы бы предпочли 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");

С объявленным массивом struct вы можете перейти к обработке ввода. Надежный способ обработки ввода - это непрерывный цикл, проверка получения ожидаемых вами данных на каждой итерации, обработка любых возникающих ошибок (изящно, чтобы ваш код продолжался) и отслеживание количества вводимых данных, чтобы вы могли защищает границы вашего массива и избегает вызова неопределенного поведения путем записи за пределы конца вашего массива.

Вы можете начать цикл ввода, запрашивая и читая строку ввода с 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) если последний символ не 't '\n' проверка того, равна ли длина максимальной длине (-1), что указывает на то, что символы могут быть оставлены непрочитанными. (которые оставлены вам)

Теперь, когда вы знаете, что у вас есть строка ввода, и она не пустая, вы можете позвонить sscanf, чтобы проанализировать строку в name и 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;
        }
    }

Если вы обращаете внимание, вы можете увидеть одно из преимуществ использования подхода fgets и 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);
}

Пример использования / Вывод

Всякий раз, когда вы пишете подпрограмму ввода - Go Try and Break It! . Преднамеренно вводите неверные данные. Если ваша входная подпрограмма не работает - Go Fix It! . В указанном коде единственной проверкой, которую осталось выполнить и обработать, является ввод числа, превышающего 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 указывает, что имена, заканчивающиеся суффиксом _t, зарезервированы для его использования. (size_t, uint64_t, etc...) Также помните, что вы увидите этот суффикс, используемый в обычной практике. Поэтому проверьте, прежде чем придумать свой собственный (но у нас нет, POSIX student_t тип не существует).
0 голосов
/ 09 ноября 2018

первый взгляд scores и names определены как массивы, поэтому

   scanf("%s",names[i]);
   scanf("%s", &scores[i]);

секунда scores равна int, поэтому "%d" вместо "%s"

   scanf("%s",names[i]);
   scanf("%d", &scores[i]);

в-третьих, вы уже определили int i;, поэтому тот, что в for loop, на самом деле не имеет никакого смысла, делайте это только в одном месте.

четвертый, если ваши входные имена содержат spaces scanf - неправильный вариант

из справочных страниц scanf

Each conversion specification in format begins with either the character '%' or the character sequence "%n$" (see below for the distinction) followed by:

   ·      An  optional decimal integer which specifies the maximum field width.  Reading of characters stops either when this maximum is reached or when a non‐
          matching character is found, whichever happens first.  Most conversions discard initial white space characters (the exceptions are noted below),  and
          these discarded characters don't count toward the maximum field width.  String input conversions store a terminating null byte ('\0') to mark the end
          of the input; the maximum field width does not include this terminator.

еще несколько точек, в которых вы можете ограничить число символов, которые будут сканироваться во время сканирования, до определенного предела, например "%20s"

...