scanf ("% [^ \ n] d", x) или scanf ("% [0-9] d", x) превращает 1 в 49 и 2 в 50 - PullRequest
4 голосов
/ 20 марта 2019

Проблема:

У меня проблемы с scanf(). Я знаю, читая форумы и тому подобное, scanf() довольно проблематично в C, но я все еще изучаю основы, поэтому я не знаю всех деталей.

Фрагмент кода, который я хотел бы решить.

#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>

struct Biodata {
    char name[21],
    address[65],
    date[11],
    phone[17];
};

int main() {
    struct Biodata bio[10];
    int input_max = 0,
    index_struct = 0;
    while (printf("Input the amount of data! ") && scanf("%[0-9]*d", &input_max) < 1) {
        printf("Error! Please, try again!\n\n");
       fflush(stdin);
    }
    for (index_struct = 1; index_struct <= input_max; index_struct++) {
        printf("Your input data count is %d.\n", input_max);
        printf("Data %d.\n", index_struct);
        fflush(stdin);
        while (printf("Name\t: ") && scanf("%[A-Z a-z]s", &bio[index_struct].name) < 1) {
            printf("Error! Please, try again!\n\n");
            fflush(stdin);
        }
        fflush(stdin);
        while (printf("Address\t: ") && scanf("%[^\n]s", &bio[index_struct].address) < 1) {
            printf("Error! Please, try again!\n\n");
            fflush(stdin);
        }
        fflush(stdin);
        while (printf("Birthdate\t: (YYYY/MM/DD)\n") && scanf("%[^\n A-Z a-z]s", &bio[index_struct].date) < 1) {
            printf("Error! Please, try again!\n\n");
            fflush(stdin);
        }
        fflush(stdin);
        while (printf("Phone Number\t: ") && scanf("%[^\n A-Z a-z]s", &bio[index_struct].phone) < 1) {
            printf("Error! Please, try again!\n\n");
            fflush(stdin);
        }
        fflush(stdin);
        printf("\n");
    }
    printf("Input the index number you'd like the data to be pulled from! ");
    scanf("%d", &index_struct);
    printf("%-10s\n%-64s\n%-10s\n%-16s",
           bio[index_struct].name, bio[index_struct].address,
           bio[index_struct].date, bio[index_struct].phone);
    return 0;
}

Я пытаюсь сделать каждый ввод, чтобы иметь возможность вывести ошибку, когда ввод является пробелом. Наборы сканирования [^\n] или [A-Z] или [0-9] обычно помогают мне в более простых случаях. Но в этом, когда я вводю любое число в качестве ввода в input_max для

while (printf("Input the amount of data! ") && scanf("%[0-9]*d", &input_max) < 1) {
    printf("Error! Please, try again!\n\n");
    fflush(stdin);
} 

input_max выдает номера, отличные от тех, которые ему дали. Что здесь происходит? Что я могу сделать, чтобы обойти это?

Я также не совсем понимаю, как этот фрагмент кода работает как вывод ошибок, когда я нашел это где-то в Интернете.

РЕДАКТИРОВАТЬ: , как предложил @JonathanLeffler, scanf() принимает мой ввод в качестве кодовых точек либо в ASCII, ISO 8859-x, либо в Юникоде, либо во всех них. Но вход остается неизменным, когда я удаляю скансет, превращая его в scanf(%d, &input_max). Но мне нужен набор сканирования, чтобы я мог ввести пробел и получить сообщение об ошибке, которое я настроил для всплывающего окна при вводе пробела в scanf().

1 Ответ

4 голосов
/ 20 марта 2019

Вы ошибочно принимаете [ как модификатор для %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. Вы индексируете свои биоданные от 1 до 10 (посмотрите на index_struct для цикла), однако они объявлены как массив размером 10 Biodata bio[10];

В массивах C основаны 0, поэтому они переходят от 0 до size-1, и ваш for-loop приведет к ошибке сегментации, поскольку bio[10] будет неопределенным.

  1. вы запрашиваете input_max внутри вашего цикла for, но он вам нужен для цикла for.

  2. Что произойдет, если input_max больше, чем объявленный размер массива bio?

Некоторые другие хорошие вещи для рассмотрения:

  1. printf - плохая функция для сообщения об ошибках, ошибки должны идти в stderr, а не в stdout, поэтому лучше использовать fprintf и указать stderr.

  2. , поскольку вы заинтересованы в правильности синтаксического анализа входных данных, почему бы не создать собственный синтаксический анализатор вместо использования scanf?

  3. вы заставляете повторять запрос об ошибке, давайте разберем это с его собственной функцией.

Давайте сделаем это вместе.

Примечание о стиле 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. Я оставляю это тебе, чтобы сломать это.

...