Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > fscanf как работает?


Автор: nns2009 14.2.2009, 13:17
У меня программа должна уметь сохранять, а через некоторое время читать информацию из файла в формате .csv(значения разделённые ;).
(Чтобы можно было через Excel открывать).
В файле хранится:
версия программы,
количество типов содержимого ячеек таблицы,
типы содержимого ячеек таблицы,
размер таблицы,
таблица
В общем, пример содержимого файла я прикрепил.

Его создаёт такой код
Код

void SaveTable()
{
    int wotf = max(5, width_of_table);
    char* table_string = new char[4*width_of_table];
    table_string[0] = 0;
    FILE* file;
    file = fopen(file_name, "w");
        char* point_w_z = new char[wotf];
        for (int i = 0; i < wotf-1; i++)
            point_w_z[i] = ';' ;
        point_w_z[wotf-1] = 0;
        fprintf(file, "V %g%s\n", 1.0, point_w_z);
        fprintf(file, "%d%s\n", number_of_pases, point_w_z);

        point_w_z[wotf-5] = 0;
        for (int i = 0; i < number_of_pases; i++)
            fprintf(file, "%d;%d;%d;%s;%s%s\n", pases[i].r, pases[i].g, pases[i].b, pases[i].name, pases[i].description, point_w_z);
        point_w_z[wotf-5] = ';';

        point_w_z[wotf-2] = 0;
        fprintf(file, "%d;%d%s\n", width_of_table, height_of_table, point_w_z);
        point_w_z[wotf-2] = ';';

        char decimal[3];
        for (int i = 0; i < height_of_table; i++)
        {
            for (int j = 0; j < width_of_table; j++)
            {
                itoa(table[i][j], decimal, 10);
                strcat(table_string, decimal);
                if (j != width_of_table-1) strcat(table_string, ";");
            }
            fprintf(file, "%s\n", table_string);
            table_string[0] = 0;
        }
    fclose(file);
    delete [] table_string;
}


Вопрос в том, как можно произвести обратное действие?
1. Можно построчно читать файл и разбирать строки на составляющие вручную.
2. Думаю с помощью функции fscanf() это можно реализовать удобнее, но как?
На сколько я понял сканф останавливает сканирование как только находит пробел и уже при первом вызове, строка, которую он создаёт, содержит только один символ "V"(что для меня не очень удобно).

Автор: andrew_121 14.2.2009, 18:31
Цитата(nns2009 @  14.2.2009,  13:17 Найти цитируемый пост)
На сколько я понял сканф останавливает сканирование как только находит пробел

Не верно!

Автор: Earnest 15.2.2009, 16:44
csv-файл удобнее всего читать построчно и разбирать каждую строку на лексемы, разделеные заданным символом (;). Это элементарно написать самому, но есть и готовые функции, нарпимер strtok. А уже отдельные слова интерпретировать вообще пустяк. scanf удобно использовать тогда, когда формат абсолютно фиксирован и точно известен. 

Автор: nns2009 16.2.2009, 15:17
Цитата(Earnest @  15.2.2009,  16:44 Найти цитируемый пост)
scanf удобно использовать тогда, когда формат абсолютно фиксирован и точно известен.  

А разве у меня формат не фиксирован? :

версия
количество данных
данные
количество данных
данные

Каждая часть данных записывается на отдельной строке в одинаковом формате.
Ну, если что буду разбирать построчно.

Автор: Earnest 16.2.2009, 18:15
Цитата(nns2009 @  16.2.2009,  16:17 Найти цитируемый пост)
А разве у меня формат не фиксирован? :

Если есть "кол-во данных", а потом "данные" в этом самом количестве, то нет. Как ты формат для scanf запишешь, если во время компиляции неизвестно сколько "данных" читать? Ну то есть можно поизвращаться, но разбор на лексемы проще и нагляднее по результирующему коду.

Автор: nns2009 16.2.2009, 23:07
Цитата(Earnest @  16.2.2009,  18:15 Найти цитируемый пост)
Если есть "кол-во данных", а потом "данные" в этом самом количестве, то нет.

Но ведь по идее данные серавно читаются в цикле, даже если их известное количество изначально, а внутренняя часть цикла одинакова
для известного числа чтений:

Код

int number = 7;
for (int i = 0; i < number; i++)
{
    чтение_данных(откуда, формат, куда[i])
}
и для неизвестного:

Код

int number =   ...   ;
for (int i = 0; i < number; i++)
{
    чтение_данных(откуда, формат, куда[i])
}

Можете привести пример кода(с использованием fscanf()), который читает данные из файла, созданного функцией SaveTable() (в моём первом сообщении) для определённого числа данных, предположим трёх.

Автор: inside_pointer 17.2.2009, 13:37
через strtok

Автор: inside_pointer 17.2.2009, 13:39
вывод
Код

[guest@station test]$ cc *.c -o parse
[guest@station test]$ ./parse
V 1;;;;;;;;;;;;;;;;;;;;;;;
4;;;;;;;;;;;;;;;;;;;;;;;
255;255;255;Не заполнено;Не заполнено;;;;;;;;;;;;;;;;;;;
255;0;0;Отдых;В это время работник может отдыхать.;;;;;;;;;;;;;;;;;;;
0;255;0;Работа;В это время работник обязан работать.;;;;;;;;;;;;;;;;;;;
0;0;255;Обед;В это время работник может обедать.;;;;;;;;;;;;;;;;;;;
24;7;;;;;;;;;;;;;;;;;;;;;;
3;2;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;2;3
2;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;2
0;1;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;1;0
0;1;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;1;0
0;1;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;1;0
2;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;2
3;2;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;2;3

 V 1
4
255 255 255 Не заполнено Не заполнено
255 0 0 Отдых В это время работник может отдыхать.
0 255 0 Работа В это время работник обязан работать.
0 0 255 Обед В это время работник может обедать.
24 7
3 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 3
2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
0 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 0
0 1 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 1 0
0 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 0
2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
3 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 3

[guest@station test]$


Автор: inside_pointer 18.2.2009, 05:06
подредактировал чтобы читала построчно и разбирала каждую строку через strtok

вывод
Код

[guest@station parse]$ cc *.c -o parse
[guest@station parse]$ ./parse
 V 1;;;;;;;;;;;;;;;;;;;;;;;
 V 1

 4;;;;;;;;;;;;;;;;;;;;;;;
 4

 255;255;255;Не заполнено;Не заполнено;;;;;;;;;;;;;;;;;;;
 255 255 255 Не заполнено Не заполнено

 255;0;0;Отдых;В это время работник может отдыхать.;;;;;;;;;;;;;;;;;;;
 255 0 0 Отдых В это время работник может отдыхать.

 0;255;0;Работа;В это время работник обязан работать.;;;;;;;;;;;;;;;;;;;
 0 255 0 Работа В это время работник обязан работать.

 0;0;255;Обед;В это время работник может обедать.;;;;;;;;;;;;;;;;;;;
 0 0 255 Обед В это время работник может обедать.

 24;7;;;;;;;;;;;;;;;;;;;;;;
 24 7

 3;2;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;2;3
 3 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 3

 2;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;2
 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2

 0;1;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;1;0
 0 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 0

 0;1;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;1;0
 0 1 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 1 0

 0;1;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;1;0
 0 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 0

 2;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;2
 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2

 3;2;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;2;3
 3 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 3

[guest@station parse]$          

ни strtok ни fscanf не могут залезть между ;; и вывести, что там ничего нет, если юзать fscanf, её работа будет сведена к посимвольному разбору (то есть то же самое, что просто символы анализировать); многие путают и думают что fscanf это обратный аналог к fprintf, на самом деле это совершенно различные функции и их следует изучать по отдельности, иначе будет путаница

Автор: Earnest 18.2.2009, 10:50
Цитата(inside_pointer @  18.2.2009,  06:06 Найти цитируемый пост)
ни strtok ни fscanf не могут залезть между ;; и вывести, что там ничего нет

strtok может, а вот scanf действительно нет.

Автор: nns2009 19.2.2009, 15:35
Цитата(inside_pointer @  18.2.2009,  05:06 Найти цитируемый пост)
многие путают и думают что fscanf это обратный аналог к fprintf, на самом деле это совершенно различные функции и их следует изучать по отдельности

Я тоже так думал. А есть ли тогда обратный аналог fprintf();

Код

char *ParseLine(char *s, const char *delim)
{
    static int state = 0;
    char *p;
    
    if (state == 0) {
        state = 1;
        p = strtok(s, delim);
    } else
        p = strtok(NULL, delim);
    if (p == NULL)
        state = 0;
    return p;    
}

На сколько я понял strtok();  возвращает строку до разделителя, а в строке, переданной через параметр, обрезает её с разделителем

Код

FILE *OpenFile(const char *fname)
{
    return fopen(fname, "rb");
}
int CloseFile(FILE *fp)
{
    if (fp == NULL)
        return EOF;
    return fclose(fp);
}

А зачем эти функции были написаны?

А также зачем вот это:
Print("");

Автор: inside_pointer 20.2.2009, 05:36
новая версия теперь через fscanf (сохраняет лексемы в массив, выводит пустоты как пустоты)

Код

[guest@station parse2]$ ./parse
 V 1 empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty 1
 empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty 1
 255 255 Не заполнено Не заполнено empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty 1
 0 0 Отдых В это время работник может отдыхать. empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty 1
 255 0 Работа В это время работник обязан работать. empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty 1
 0 255 Обед В это время работник может обедать. empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty 1
 7 empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty 1
 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 1
 1 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 1 1
 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 1
[guest@station parse2]$


strtok не может пролезть между ;;;, т.к. она вместо каждого разделителя записывает нуль-символ и переводит указатель поиска на следующую лексему после него, если встречает конец строки возвращает нулевой указатель

Добавлено через 2 минуты и 11 секунд
Цитата(nns2009)

Я тоже так думал. А есть ли тогда обратный аналог fprintf();

она может вывести что угодно, то есть множество обратных аналогов стремится к бесконечности, потому наверное и нету

Автор: inside_pointer 20.2.2009, 06:08
там баг с последним значением

Автор: Earnest 20.2.2009, 10:59
Цитата(inside_pointer @  20.2.2009,  06:36 Найти цитируемый пост)
strtok не может пролезть между ;;;, т.к. она вместо каждого разделителя записывает нуль-символ и переводит указатель поиска на 

Бог ты мой какие сложности! smile Во-первых, ничего она не "переводит", а просто возвращает следующий за разделителем символ: что делать с этим возвратом - твое дело. Предыдущий симовл-разделитель забит нулем, и в чем здесь проблема? Текущий указатель опять содержит разделитель - т.е. следующая лексема будет пустой. Кстати, есть еще функция strpbrk, которую удобно использовать для тех же целей. 

Автор: math64 20.2.2009, 11:51
strtok() заменяет разделители нулями, поэтому если есть разные разделители, невозожно определить какой он был.
Кроме того, он портит исходную строку, поэтому иногда её нужно предварительно скопировать.
Другие функции, которые можно использовать вместо неё для поиска разделителей: strspn(), strcspn(), strchr(), strstr()

Автор: Earnest 20.2.2009, 12:14
Цитата(math64 @  20.2.2009,  12:51 Найти цитируемый пост)
strtok() заменяет разделители нулями, поэтому если есть разные разделители, невозожно определить какой он был.

Это правда, но здесь-то разделитель один...

Автор: inside_pointer 21.2.2009, 03:24
она за один шаг проскакивает все повторы, заменяет их нулями, если учесть вычисление разности адресов

Код

;one;;;;;;two;;


strtok возвращает адрес символа o, а у себя там сохраняет адрес символа t (заменив все разделители нулями), при следующем вызове вернёт адрес символа t, а сохранит адрес нуль-символа (из-за которого и вернёт потом NULL и так всё время)

ясно, как через strtok, но я хочу через sscanf попробовать, потому что весь файл читать через fscanf неправильно, даже если она всё правильно будет учитывать при переводах, так как строки имеют единый формат; нужно писать функцию по обработке одной строки

про strstr тоже неясно, вместо strchr юзать её ?

Автор: inside_pointer 21.2.2009, 05:15
здесь с sscanf

вывод

Код

[guest@station parse3]$ ./parse
 V 1;;;;;;;;;;;;;;;;;;;;;;1;
 V 1 empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty 1
 4;;;;;;;;;;;;;;;;;;;;;;;
 4 empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty
 255;255;255;Не заполнено;Не заполнено;;;;;;;;;;;;;;;;;;;
 255 255 255 Не заполнено Не заполнено empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty
 255;0;0;Отдых;В это время работник может отдыхать.;;;;;;;;;;;;;;;;;;;
 255 0 0 Отдых В это время работник может отдыхать. empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty
 0;255;0;Работа;В это время работник обязан работать.;;;;;;;;;;;;;;;;;;;
 0 255 0 Работа В это время работник обязан работать. empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty
 0;0;255;Обед;В это время работник может обедать.;;;;;;;;;;;;;;;;;;;
 0 0 255 Обед В это время работник может обедать. empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty
 24;7;;;;;;;;;;;;;;;;;;;;;;
 24 7 empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty empty
 3;2;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;2;3
 3 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 3
 2;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;2
 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
 0;1;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;1;0
 0 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 0
 0;1;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;1;0
 0 1 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 1 0
 0;1;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;1;0
 0 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 0
 2;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;2
 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
 3;2;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;2;3
 3 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 3
[guest@station parse3]$

Автор: nns2009 27.2.2009, 23:27
А какие бывают форматы в функциях fscanf(), sscanf() и подобных?

Автор: inside_pointer 28.2.2009, 03:00
printf
http://bookc.narod.ru/bb12.html

scanf
http://bookc.narod.ru/bb13.html

хорошо написано всё, но в самой книге не хватает страниц (по крайней мере в бумажной у меня хоть и были несколько утеряных строк или ошибки печати, но вот чтобы страниц не было - это нонсенс)

Автор: nns2009 7.3.2009, 16:45
Спасибо всем, теперь всё правильно читает.
Реализовал с помощью fscanf();

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)