Вопрос программирования NOOB C: у меня проблемы с вставкой значений, которые читаются из файла, в другие переменные в моем коде - PullRequest
0 голосов
/ 29 октября 2018

Итак, чтобы упростить мою проблему, я создал эту более модульную программу, которая запрашивает ввод пользователя сначала в моем случае переключателя в моем main (), а также в двух других методах. У меня есть этот код внизу, и в моем методе readDigits() мне интересно, как получить конкретные значения из value в 'firstDigit и secondDigit. Скажем, когда value == 5, я бы хотел, чтобы '5' попал в firstDigit.

#include <stdio.h>
#include <stdlib.h>

int firstDigit, secondDigit, input;

void getNumbers() {
   printf("Enter these digits: \n");
   scanf("%d", &firstDigit);
}

void getMoreNumbers() {
   printf("Enter some more digits: \n");
   scanf("%d", &secondDigit);
}

int readDigits(int value) {
   FILE *fp
   fp = fopen("digits.txt", "r");
   if(fp == NULL) {
     printf("Failed to open file");
     return -1;
   }
   while(fscanf(fp, "%d", &value)==1){
      printf("%d ", value);


**#I was thinking of doing these 'if' checks whenever value has a number that 
#I would want 'firstDigit' ,'secondDigit' , or 'input' to be. But then I 
#figured that this would be too tedious and not efficient If I were to read 
#a file with a lot of data in it.**

      if(value== 1){
         firstDigit = value;
      }
  }   
  fclose(fp);
}  

int main() {
   while(input < 3){
   printf("Please select which method you'd like to access:\n1) getNumbers()\n2getMoreNumbers()");
  // I also want input's value to be read from digits.txt
  scanf("%d", &input);  
  switch(input){
     case 1:
        getNumbers();
        break;
     case 2:
        getMoreNumbers();
        break;
     default:
        printf("Program complete");
     }  
  }
  return 0;
}

Я понимаю, что это не самая трудная вещь, однако, я новичок в C и действительно переживаю из-за того, ЧТО Я ЧУВСТВУЮ, это простая проблема.

Мой файл digits.txt имеет значения:

1
10
299
1
200
15
3
150
13
2
150

1 Ответ

0 голосов
/ 30 октября 2018

Спасибо за размещение вашего входного файла. Причина, по которой ваш вопрос был менее чем хорошо принят, заключается в том, что довольно сложно определить, чего вы пытались достичь. Хотя очевидно, что вам нужна помощь, с чего начать, немного загадочно.

Почему? Для начала, ваши функции getNumbers() [1] и getMoreNumbers() делают одно и то же, просто используя другую переменную global . Нет никакой причины использовать глобальные переменные в любой из ваших функций, так как они могут быть легко переданы в качестве параметров любой функции, которая нуждается в них. В более общем смысле вы хотите избегать использования глобальных переменных, за исключением случаев, когда это абсолютно необходимо (существуют законные варианты использования глобальных переменных, но ни с одной из них вы, скорее всего, не столкнетесь при первоначальном изучении C - избегайте их)

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

Отступая назад и пытаясь понять, что вы хотите сделать, кажется, что ваша цель двоякая, (1) прочитать целое число из stdin на основе выбора меню или (2) прочитать из файла "digits.txt" если был сделан альтернативный выбор меню. Однако ваша readDigits() функция никогда не вызывается. Кроме того, что если вы хотите прочитать из файла, отличного от "digits.txt"? Почему бы просто не запросить у пользователя имя файла, а затем открыть этот файл (обработав любую ошибку), прочитать все целые числа из файла, а затем закрыть файл после завершения?

Итак, как нам достичь этих двух целей: либо чтение целого числа из stdin, либо чтение всех целых чисел из имени файла, заданного пользователем?

Для начала вы должны полностью проверить все вводимые пользователем данные и затем обработать эти входные данные, обрабатывая любую возможную ошибку. scanf (и его семейство функций) полны ловушек для нового программиста на Си. Например, что происходит в случае, когда пользователь случайно вводит буквенный символ вместо «цифры» при любом вызове функции scanf ("%d", ...)? Попробуй.

При использовании scanf, если вход не совпадает с , спецификатор преобразования использовал сбой при совпадении . При возникновении ошибки совпадения чтение из входного буфера прекращается, дальнейшие символы не извлекаются, и все символы остаются такими, какими они были на момент сбоя непрочитанные во входном буфере (stdin в вашем случае), просто жду, чтобы снова вас укусить при следующей попытке прочитать. Кроме того, только числовые спецификаторы преобразования и "%s" занимают ведущие пробелы .

Значение любого другого спецификатора преобразования (или линейно-ориентированной входной функции в этом отношении), используемого для ввода, либо ничего не читающего (останавливается, когда они сталкиваются с '\n', сгенерированным пользователем, нажимая Enter и оставлен в stdin), или он будет читать '\n' как пользовательский ввод. Не то, что вы хотите.

Как вы гарантируете, что каждый вход, полученный от пользователя, будет тем, что пользовательский ввод? Вы очищаетесь после себя, очищая входной буфер после каждого ввода, чтобы убедиться, что в stdin после предыдущей попытки ввода не осталось случайных или дополнительных символов. (и именно поэтому новым программистам на С рекомендуется избегать scanf для пользовательского ввода и вместо этого использовать строковую функцию ввода, такую ​​как fgets() (с подходящим размером буфера) или POSIX getline, которая будет считывать и распределять как необходимо прочитать любую строку. Обе строчно-ориентированные функции читают и включают '\n' в заполняемые ими буферы, исключая вероятность того, что он останется во входном буфере непрочитанным.

Если вы решите продолжить с scanf, то по крайней мере вручную очищайте входной буфер (stdin) с помощью простой функции каждый раз, когда есть вероятность того, что символы остаются в stdin непрочитанными. Простая функция сделает это. Просто прочитайте каждый символ с getchar(), пока не встретите '\n' или EOF, например,

void empty_stdin (void)
{
    int c = getchar();

    while (c != '\n' && c != EOF)
        c = getchar();
}

примечание: если функция не принимает параметров, то правильное объявление включает (void) в качестве списка параметров, чтобы сделать явным, что параметры не ожидаются.

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

Если ваше намерение с getNumbers() [1] и getMoreNumbers() состояло в том, чтобы просто прочитать целое число из stdin, тогда просто используйте одну функцию с немного более описательным именем для выполнения работы, такие как:

int readintstdin (int *value) {

    int rtn = 0;

    printf ("Enter digits: ");

    rtn = scanf ("%d", value);     /* read 1 integer */

    if (rtn == EOF)
        fputs ("(user canceled input.)\n", stderr);
    else if (rtn == 0)
        fputs (" readintstdin() error: invalid input.\n", stderr);

    empty_stdin();  /* remove all remaining chars from stdin */

    return rtn;
}

Примечание: при использовании scanf вы несете ответственность за обработку трех случаев:

  1. пользователь отменяет ввод, генерируя вручную EOF, нажимая Ctrl + d (или Ctrl + z в окнах);
  2. a соответствие или вход ошибка; и
  3. хороший случай ввода, где вы должны проверить, был ли полученный вход в ожидаемом диапазоне и т. Д.

Имея это в виду, вы можете создать функцию для чтения из stdin, аналогичную:

int readintstdin (int *value) {

    int rtn = 0;

    printf ("Enter digits: ");

    rtn = scanf ("%d", value);     /* read 1 integer */

    if (rtn == EOF)
        fputs ("(user canceled input.)\n", stderr);
    else if (rtn == 0)
        fputs (" readintstdin() error: invalid input.\n", stderr);

    empty_stdin();  /* remove all remaining chars from stdin */

    return rtn;
}

обратите внимание на две вещи: значение ** адреса передается в качестве параметра, позволяющего сохранять входные данные по этому адресу памяти и доступным обратно в вызывающую функцию (main() здесь) и return значение для scanf возвращается как функция return, указывающая успех / неудачу чтения обратно в main(). Вы всегда должны создавать функцию с соответствующим типом возврата, который будет указывать на успех или неудачу функции обратно в вызывающую функцию. В противном случае ничем не отличается от того, что не удалось проверить возврат scanf для начала.

Ваша следующая цель - прочитать все значения из "digits.txt". Хотя это зависит от вас, общий подход заключается в том, чтобы открыть FILE* stream обратно в вызывающей стороне, чтобы проверить, открыт ли файл для чтения, прежде чем передать этот поток файлов в качестве параметра функции. В противном случае, если открытие завершится неудачно, нет необходимости вызывать функцию для начала, например ::

int readfromfile (FILE *fp) {

    int value = 0, count = 0;

    while (fscanf (fp, "%d", &value) == 1) {    /* while valid int read */
        printf ("%d ", value);
        count++;
    }

    putchar ('\n'); /* tidy up with newline */

    return count;   /* return value indicating number of values read */
}  

Здесь, чтобы указать успешность / неудачу чтения из файла, возвращается число значений, прочитанных из файла. Если возвращаемое значение равно 0, то из файла не было прочитано никаких значений.

Далее ваше меню. Вы должны применить эти же уроки к вашему меню. Это означает, что вам нужно обработать все три случая для scanf и удалить все символы, которые остаются в stdin между каждым отображением меню. Поместив эти меры безопасности, вы можете сделать что-то вроде следующего, например,

#define MAXC 1024
...
int main (void) {

    int input = 1,          /* initialize all variables */
        value = 0;
    char fname[MAXC] = "";  /* buffer to hold filename to read */
    FILE *fp = NULL;

    while (0 < input && input < 3) {
        int rtn = 0;    /* store scanf return */
        printf ("\nPlease select which method you'd like to access:\n"
                " 1) readintstdin()\n"
                " 2) readfromfile()\n"
                " (any other numeric input exits)\n\n"
                " choice: ");
        rtn = scanf ("%d", &input);
        if (rtn == EOF) {   /* user generated manual EOF */
            fputs ("(user canceled input)\n", stderr);
            break;
        }
        else if (rtn == 0) {    /* no integer input */
            fputs (" error: invalid integer input.\n", stderr);
            empty_stdin();  /* empty all chars from stdin */
            continue;       /* re-display menu, try again */
        }
        else    /* good integer input */
            empty_stdin();  /* empty all chars from stdin */
        ...

Осталось только обработать switch на input. В первом случае вызов readintstdin является тривиальным, просто вызовите readintstdin, передав в качестве параметра адрес value и проверьте, что возвращаемая функция не равна нулю, прежде чем печатать value, например

        switch (input) {    /* switch on integer input */
            case 1:
                if (readintstdin (&value))  /* read from stdin */
                    printf ("value: %d\n", value);
                break;

Второй случай требует больше размышлений. Вы будете принимать ввод от пользователя для имени файла (таким образом, цель для переменной fname в начале main()). Здесь вы просто запрашиваете и читаете имя файла (которое может содержать пробелы), проверяете чтение и затем передаете имя файла fopen, проверяя, что файл открыт для чтения. После того, как вы подтвердите, что файл открыт для чтения, вы можете просто передать поток открытых файлов вашей функции (проверка возврата вашей функции на успех / неудачу) и затем закрыть файл, когда закончите. Это можно сделать так:

            case 2:
                printf ("\n enter filename: "); /* get filename to read */
                if (scanf ("%1023[^\n]", fname) != 1) {
                    fputs ("(user canceled input)\n", stderr);
                    empty_stdin();
                    break;
                }
                /* open/validate file open for reading */
                if ((fp = fopen (fname, "r")) == NULL) {
                    perror ("fopen-fname");
                    empty_stdin();
                    break;
                }
                if (!readfromfile (fp)) /* read/output integers */
                    fprintf (stderr, "error: no values read from '%s'\n",
                            fname);
                fclose (fp);        /* close file */
                break;

примечание: ваш случай default: должен сообщить пользователю, что получено значение, не входящее в допустимые значения меню, и затем вы можете указать, что программа была завершена. (это просто гнида, и вы можете справиться с ней так, как вам нравится)

В целом, вы можете сделать что-то вроде следующего:

#include <stdio.h>

#define MAXC 1024

void empty_stdin (void)
{
    int c = getchar();

    while (c != '\n' && c != EOF)
        c = getchar();
}

int readintstdin (int *value) {

    int rtn = 0;

    printf ("Enter digits: ");

    rtn = scanf ("%d", value);     /* read 1 integer */

    if (rtn == EOF)
        fputs ("(user canceled input.)\n", stderr);
    else if (rtn == 0)
        fputs (" readintstdin() error: invalid input.\n", stderr);

    empty_stdin();  /* remove all remaining chars from stdin */

    return rtn;
}

int readfromfile (FILE *fp) {

    int value = 0, count = 0;

    while (fscanf (fp, "%d", &value) == 1) {    /* while valid int read */
        printf ("%d ", value);
        count++;
    }

    putchar ('\n'); /* tidy up with newline */

    return count;   /* return value indicating number of values read */
}  

int main (void) {

    int input = 1,      /* initialize all variables */
        value = 0;
    char fname[MAXC] = "";
    FILE *fp = NULL;

    while (0 < input && input < 3) {
        int rtn = 0;    /* store scanf return */
        printf ("\nPlease select which method you'd like to access:\n"
                " 1) readintstdin()\n"
                " 2) readfromfile()\n"
                " (any other numeric input exits)\n\n"
                " choice: ");
        rtn = scanf ("%d", &input);
        if (rtn == EOF) {   /* user generated manual EOF */
            fputs ("(user canceled input)\n", stderr);
            break;
        }
        else if (rtn == 0) {    /* no integer input */
            fputs (" error: invalid integer input.\n", stderr);
            empty_stdin();  /* empty all chars from stdin */
            continue;       /* re-display menu, try again */
        }
        else    /* good integer input */
            empty_stdin();  /* empty all chars from stdin */

        switch (input) {    /* switch on integer input */
            case 1:
                if (readintstdin (&value))  /* read from stdin */
                    printf ("value: %d\n", value);
                break;
            case 2:
                printf ("\n enter filename: "); /* get filename to read */
                if (scanf ("%1023[^\n]", fname) != 1) {
                    fputs ("(user canceled input)\n", stderr);
                    empty_stdin();
                    break;
                }
                /* open/validate file open for reading */
                if ((fp = fopen (fname, "r")) == NULL) {
                    perror ("fopen-fname");
                    empty_stdin();
                    break;
                }
                if (!readfromfile (fp)) /* read/output integers */
                    fprintf (stderr, "error: no values read from '%s'\n",
                            fname);
                fclose (fp);        /* close file */
                break;
            default:    /* handle invalid input */
                fputs ("Selection not menu entry.\n", stderr);
                break;
        }  
    }
    printf ("Program complete\n");

    return 0;
}

примечание: , если вы не используете функцию или константу, предоставленную stdlib.h, нет смысла включать заголовок. Это не больно, а просто показывает неправильное понимание необходимых заголовочных файлов.

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

Теперь приступайте к упражнению с вашей программой и намеренно пытайтесь нарушить процедуру ввода, вводя неправильный / недействительный ввод в каждом из ваших запросов. Если что-то сломалось, иди починить. Я не пробовал каждый угловой случай, но понимание того, как работают ваши функции ввода, позволяет вам вначале защитить от большинства угловых случаев. Посмотрите на первый целочисленный ввод "four-hundred twenty-one", программа ломается? Посмотрите на неверное имя файла "somefile.ext", программа ломается? Делайте это для каждого из ваших входов.

$ ./bin/menureadfilestdin

Please select which method you'd like to access:
 1) readintstdin()
 2) readfromfile()
 (any other numeric input exits)

 choice: 1
Enter digits: four-hundred twenty-one
 readintstdin() error: invalid input.

Please select which method you'd like to access:
 1) readintstdin()
 2) readfromfile()
 (any other numeric input exits)

 choice: 1
Enter digits: 421
value: 421

Please select which method you'd like to access:
 1) readintstdin()
 2) readfromfile()
 (any other numeric input exits)

 choice: 2

 enter filename: somefile.ext
fopen-fname: No such file or directory

Please select which method you'd like to access:
 1) readintstdin()
 2) readfromfile()
 (any other numeric input exits)

 choice: 2

 enter filename: dat/digits.txt
1 10 299 1 200 15 3 150 13 2 150

Please select which method you'd like to access:
 1) readintstdin()
 2) readfromfile()
 (any other numeric input exits)

 choice: 1
Enter digits: 422
value: 422

Please select which method you'd like to access:
 1) readintstdin()
 2) readfromfile()
 (any other numeric input exits)

 choice: (user canceled input)
Program complete

Надеюсь, это решило большинство ваших проблем и помогло вам избежать «действительно нервничать из-за того, ЧТО Я ЧУВСТВУЮ, - простая проблема». Да, но C требует глубины понимания, выходящей далеко за рамки «Я попробую это, если это не сработает, я что-то изменю, перекомпилирую и попробую снова ...» Вы управляете всей машиной и отвечаете за обработку каждого байта вашего входного буфера и обеспечение адекватного хранения для каждого байта вашей программы обрабатывает. На C. нет тренировочных колес. Вот где он получает свою невероятную скорость. Но «с великой силой приходит большая ответственность».

примечания:

1. Хотя это и не ошибка, C обычно избегает использования camelCase или MixedCase имен переменных в пользу всех строчных букв при резервировании верхнего -case имена для использования с макросами и константами. Это вопрос стиля - так что это полностью зависит от вас, но если вы не будете следовать ему, то в некоторых кругах может произойти неправильное первое впечатление.

...