Как мне лучше всего ввести ввод в программу, которая просит пользователя ввести имя студента, разделенное пробелом, а затем его балл, 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] */ );
}
}
zach 85
kevin 96
nick 56
martha 88
tim 77
Вы могли бы использовать fgets
для чтения всей строки в память (подтверждение того, что чтение прошло успешно), а затем вы использовали бы sscanf
для анализа строки, заполненной fgets
, для получения name
и score
(подтверждение того, что преобразование 2
имело место, прежде чем полагаться на ввод или преобразование)
Есть люди с более длинными именами. Что еще хуже, есть люди, имена которых состоят из нескольких слов, а внутри есть пробелы.
Также обратите внимание: char names[row][col];
объявляет Массив переменной длины (VLA), потому что, например, const int row = 5;
объявляет const квалифицированныйint
, а не целочисленная константа.
@ DavidC.Rankin "объявляет массив переменной длины", нет. row
и col
- это целочисленные постоянные выражения. Вы не можете «объявить целочисленную константу», в C++ такого нет.
@ DavidC.Rankin хм да, извините, я почему-то был уверен, что это вопрос C++.
@ n.m. - у меня все время бывает :)
Сначала посмотрите, 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, потому что это то, чему нас учили
Я всегда предпочитаю использовать fgets () для чтения полной строки, а затем анализировать с помощью sscanf () и / или strtok () плюс sscanf ()
@Zachariah "только с использованием scanf" вызывает сожаление, поскольку fgets()
- лучшая функция для чтения большей части пользовательского ввода. Просто следуйте инструкциям профессора, но помните, что это не то, что нужно делать за плату.
Вы почти там. Однако вы должны убедиться, что все делаете чисто. Давайте сделаем это шаг за шагом:
#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)
Теперь давайте прочитаем по 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];
Ваш 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
Хотя вы можете использовать два отдельных массива, один массив структуры - гораздо лучший подход. Просмотрите все и дайте мне знать, если у вас возникнут дополнительные вопросы.
сноски:
_t
, зарезервированы для его использования. (size_t, uint64_t, etc...
) Также имейте в виду, что вы увидите, что этот суффикс используется в обычной практике. Поэтому проверьте, прежде чем создавать свои собственные (но мы не знаем, что POSIX student_t
не существует).Незначительный: char buf[COL];
слишком мал для самого длинного допустимого имени, партитуры, '\n'
, ведущих / промежуточных / конечных пробелов. Предложите вдвое больший ожидаемый допустимый ввод, чтобы избежать проблем с ограниченным размером буфера. Но не без ограничений, чтобы предотвратить хакерские эксплойты.
Какие используются примеры имен?