Вы ошибочно принимаете [
как модификатор для %s
и %d
- например. %3d
- это не так. %[
сам по себе является спецификатором преобразования и работает как %s
.
Таким образом, как указано в комментарии @ user3629249, s
и d
в конце спецификатора %[
(например, в %[^\n A-Z a-z]s
) являются посторонними. Также пробелы в %[
имеют значение. Так что %[A-z a-z]
отличается от %[A-Za-z]
Давайте рассмотрим проблемы, которые возникают при компиляции с включенными предупреждениями о формате. (-Вформат, если вы используете gcc или clang), и вы получите что-то вроде:
foo.c:19:68: warning: format specifies type 'char *' but the argument has type 'int *' [-Wformat]
while (printf("Input the amount of data! ") && scanf("%[0-9]*d", &input_max)<1) {
~~~~~ ^~~~~~~~~~
%d
foo.c:29:55: warning: format specifies type 'char *' but the argument has type 'char (*)[21]' [-Wformat]
while (printf("Name\t: ") && scanf("%[A-Z a-z]s", &bio[index_struct].name)<1) {
~~~~~~~~~ ^~~~~~~~~~~~~~~~~~~~~~~
foo.c:34:54: warning: format specifies type 'char *' but the argument has type 'char (*)[65]' [-Wformat]
while (printf("Address\t: ") && scanf("%[^\n]s", &bio[index_struct].address)<1) {
~~~~ ^~~~~~~~~~~~~~~~~~~~~~~~~~
foo.c:39:78: warning: format specifies type 'char *' but the argument has type 'char (*)[11]' [-Wformat]
while (printf("Birthdate\t: (YYYY/MM/DD)\n") && scanf("%[^\n A-Z a-z]s", &bio[index_struct].date)<1) {
~~~~~~~~~~~~~ ^~~~~~~~~~~~~~~~~~~~~~~
foo.c:44:67: warning: format specifies type 'char *' but the argument has type 'char (*)[17]' [-Wformat]
while (printf("Phone Number\t: ") && scanf("%[^\n A-Z a-z]s", &bio[index_struct].phone)<1) {
~~~~~~~~~~~~~ ^~~~~~~~~~~~~~~~~~~~~~~~
Есть другие проблемы с вашим кодом:
- Вы индексируете свои биоданные от 1 до 10 (посмотрите на
index_struct
для цикла), однако они объявлены как массив размером 10 Biodata bio[10];
В массивах C основаны 0
, поэтому они переходят от 0
до size-1
, и ваш for-loop приведет к ошибке сегментации, поскольку bio[10]
будет неопределенным.
вы запрашиваете input_max
внутри вашего цикла for, но он вам нужен для цикла for.
Что произойдет, если input_max больше, чем объявленный размер массива bio
?
Некоторые другие хорошие вещи для рассмотрения:
printf - плохая функция для сообщения об ошибках, ошибки должны идти в stderr, а не в stdout, поэтому лучше использовать fprintf и указать stderr.
, поскольку вы заинтересованы в правильности синтаксического анализа входных данных, почему бы не создать собственный синтаксический анализатор вместо использования scanf?
вы заставляете повторять запрос об ошибке, давайте разберем это с его собственной функцией.
Давайте сделаем это вместе.
Примечание о стиле C и соглашениях об именах, которые я использую ниже
Мой стиль C немного отличается от вашего, и у меня есть свои причины :-), и поскольку это просто мнение, давайте продолжим с моим.
Структура с некоторыми комментариями о том, что мы хотим в ней
struct biodata {
char name[21]; /* format: FirstName LastName */
char address[65]; /* format: Free-form upto 65 chars */
char birthday[11]; /* format: YYYY/MM/DD */
char phone[17]; /* format: up to digits or a spaces */
};
Некоторые совпадения
Ниже приведен набор функций сопоставления, который получает строку ввода и сообщает нам, полностью ли соответствует строка, которую мы ожидаем. Если он это делает, он возвращает true, иначе он возвращает false Для этого вам нужно будет #include <stdbool.h>
.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
struct biodata {
char name[21]; /* format: FirstName LastName */
char address[65]; /* format: Free-form upto 65 chars */
char birthday[11]; /* format: YYYY/MM/DD */
char phone[17]; /* format: up to digits or a spaces */
};
bool match_name(const char *line)
{
char tmp[128];
return line!=NULL
&& sscanf(line, "%128[A-Za-z]%128[ ]%128[A-Za-z]", tmp, tmp, tmp) == 3
&& strlen(tmp) < 21;
}
bool match_address(const char *line)
{
return line != NULL
&& strlen(line) > 5
&& strlen(line) < 65; /* no format check here, maybe later */
}
bool match_telephone(const char *line)
{
char tmp[128];
return line /* notice the alternate form of checking line!=NULL */
&& sscanf(line, "%128[0-9 ]", tmp)==1
&& strlen(tmp) < 17;
}
/* here we'll use strptime to see if our line is a valid date */
bool match_birthday(const char *line)
{
struct tm tm; /* time structure */
if(!line)
return false;
return strptime(line, "%Y/%m/%d", &tm) != NULL;
}
char * ask(const char *prompt, char *line, size_t maxlen)
{
printf("%-30s:", prompt);
fflush(stdout);
fgets(line, maxlen, stdin);
return line; /* we return the pointer for ease of use */
}
/* a test function */
void test_matchers() {
char line[256];
/* remember ask returns our line so we are able to pass it to our matchers */
while(!match_name(ask("Name (first last)", line, sizeof(line))))
;
while(!match_address(ask("Address (at least 5 chars)", line, sizeof(line))))
;
while(!match_birthday(ask("Birthday (YYYY/MM/DD)", line, sizeof(line))))
;
while(!match_telephone(ask("Telephone (max 16 digits)", line, sizeof(line))))
;
}
int main()
{
test_matchers();
return 0;
}
Проверьте это.
$ ./bar
Name (first last) :Ahmed Masud
Address (at least 5 chars) :1999 Somewhere Out there, Bingo, Bongo, 10002, USA
Birthday (YYYY/MM/DD) :1970/01/10
Telephone (max 16 digits) :1-201-555-1212
Теперь давайте скопируем вещи в нашу структуру разумным образом
Функция для печати биоданных
/* add a function to print a biodata */
void print_bio(const struct biodata *bio)
{
printf("***** bio data *****\n"
"Name: %-10s\nAddress: %-64s\nBirthday: %-10s\nPhone: %-16s\n",
bio->name, bio->address,
bio->birthday, bio->phone);
}
новая основная функция
обратите внимание, что большинство из них похоже на test_matches
. За исключением того, что мы добавили копирование
строка в соответствующее поле
int main()
{
char line[256];
struct biodata bio;
while(!match_name(ask("Name (first last)", line, sizeof(line))))
;
strncpy(bio.name, line, sizeof(bio.name));
while(!match_address(ask("Address (at least 5 chars)", line, sizeof(line))))
;
strncpy(bio.address, line, sizeof(bio.address));
while(!match_birthday(ask("Birthday (YYYY/MM/DD)", line, sizeof(line))))
;
strncpy(bio.birthday, line, sizeof(bio.birthday));
while(!match_telephone(ask("Telephone (max 16 digits)", line, sizeof(line))))
;
strncpy(bio.phone, line, sizeof(bio.phone));
print_bio(&bio);
return 0;
}
Хорошо, мы можем подсказать пользователю и внести изменения в нашу структуру, но делать это в основном неуклюже, поэтому давайте превратим это в свою собственную функцию.
int get_bio(struct biodata *bio)
{
char line[256];
while(!match_name(ask("Name (first last)", line, sizeof(line))))
;
strncpy(bio->name, line, sizeof(bio->name));
while(!match_address(ask("Address (at least 5 chars)", line, sizeof(line))))
;
strncpy(bio->address, line, sizeof(bio->address));
while(!match_birthday(ask("Birthday (YYYY/MM/DD)", line, sizeof(line))))
;
strncpy(bio->birthday, line, sizeof(bio->birthday));
while(!match_telephone(ask("Telephone (max 16 digits)", line, sizeof(line))))
;
strncpy(bio->phone, line, sizeof(bio->phone));
return 0;
}
int main()
{
struct biodata bio[3]; /* let's get 3 records */
int i;
/* bio is made up of a bunch of struct biodata's so we divide its size by sizeof the struct biodata to get how many (in our case 3) */
for(i = 0; i < sizeof(bio)/sizeof(struct biodata); i++)
{
printf("\n\nEnter record number: %d\n", 1+i); /* why 1+i here ? :) */
get_bio(&bio[i]);
}
for(i=0; i < sizeof(bio)/sizeof(struct biodata); i++)
{
print_bio(&bio[i]);
}
return 0;
}
Упражнение
Я оставлю остальные функции в качестве упражнения.
Между тем, я бы хотел, чтобы вы рассмотрели, как мы это разработали. Начиная с самой внутренней функциональности и медленно продвигаясь наружу.
Разбейте проблему, как кусочки лего, и сначала поработайте над внутренними частями, проверьте, что они делают именно то, что вы от них хотите, а затем медленно строите вокруг них.
Очевидно, что сопоставители должны были быть разработаны индивидуально и проверены перед разработкой Ask. Я оставляю это тебе, чтобы сломать это.