Правильный метод ввода для ввода строки символов в C

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

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

Какие используются примеры имен?

chux - Reinstate Monica 09.11.2018 06:03
zach 85kevin 96nick 56martha 88tim 77
Zachariah 09.11.2018 06:10

Вы могли бы использовать fgets для чтения всей строки в память (подтверждение того, что чтение прошло успешно), а затем вы использовали бы sscanf для анализа строки, заполненной fgets, для получения name и score (подтверждение того, что преобразование 2 имело место, прежде чем полагаться на ввод или преобразование)

David C. Rankin 09.11.2018 07:10

Есть люди с более длинными именами. Что еще хуже, есть люди, имена которых состоят из нескольких слов, а внутри есть пробелы.

n. 1.8e9-where's-my-share m. 09.11.2018 07:13

Также обратите внимание: char names[row][col]; объявляет Массив переменной длины (VLA), потому что, например, const int row = 5; объявляет const квалифицированныйint, а не целочисленная константа.

David C. Rankin 09.11.2018 07:17

@ DavidC.Rankin "объявляет массив переменной длины", нет. row и col - это целочисленные постоянные выражения. Вы не можете «объявить целочисленную константу», в C++ такого нет.

n. 1.8e9-where's-my-share m. 10.11.2018 21:08

@ DavidC.Rankin хм да, извините, я почему-то был уверен, что это вопрос C++.

n. 1.8e9-where's-my-share m. 11.11.2018 00:53

@ n.m. - у меня все время бывает :)

David C. Rankin 11.11.2018 01:13
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
8
1 581
3

Ответы 3

Сначала посмотрите, 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.

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

К сожалению, у профессора есть строгие правила, поэтому мы использовали только scanf, потому что это то, чему нас учили

Zachariah 09.11.2018 06:09

Я всегда предпочитаю использовать fgets () для чтения полной строки, а затем анализировать с помощью sscanf () и / или strtok () плюс sscanf ()

ulix 09.11.2018 06:21

@Zachariah "только с использованием scanf" вызывает сожаление, поскольку fgets() - лучшая функция для чтения большей части пользовательского ввода. Просто следуйте инструкциям профессора, но помните, что это не то, что нужно делать за плату.

chux - Reinstate Monica 09.11.2018 14:28

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

Шаг 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)

По одному: #define MAX_NAME_LENGTH 30 char name[MAX_NAME_LENGTH]; scanf("%30s", name); -> scanf("%29s", name); или лучше char name[MAX_NAME_LENGTH + 1];

chux - Reinstate Monica 09.11.2018 14:20

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

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

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

Остается только объявить массив 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, чтобы проанализировать строку в 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);
}

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

Когда бы вы ни писали процедуру ввода - Иди, попробуй и сломай!. Умышленно вводите неверные данные. Если ваша процедура ввода прерывается - Иди, исправь!. В коде, как отмечалось, единственная проверка, которую вам нужно реализовать и обработать, - это ввод большего количества символов, чем 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 не существует).

Незначительный: char buf[COL]; слишком мал для самого длинного допустимого имени, партитуры, '\n', ведущих / промежуточных / конечных пробелов. Предложите вдвое больший ожидаемый допустимый ввод, чтобы избежать проблем с ограниченным размером буфера. Но не без ограничений, чтобы предотвратить хакерские эксплойты.

chux - Reinstate Monica 09.11.2018 14:17

Другие вопросы по теме