Как считать все строки из файла c
Перейти к содержимому

Как считать все строки из файла c

Как считать все строки из файла c

При работе с текстовыми файлами граздо проще работать с данными не как с отдельными символами, а как со строками с помощью функций fgets() и fputs() .

Запись текстового файла

Функция fputs() записывает в файл строку, то есть набор символов, который завершается символом ‘\0’. Она имеет следующий прототип:

int fputs(const char *s, FILE *stream);

Первый параметр функции — записываемая строка, а второй — указатель на файловый поток. В качестве результата функция возвращает неотрицательное целое число. При ошибках в процессе записи возвращается значение EOF .

При записи строки нулевой символ ‘\0’ в файл не записывается.

Например, запишем строку в файл:

#include int main(void) < // строка для записи char * message = "Hello METANIT.COM!\nAn apple a day keeps the doctor away"; // файл для записи char * filename = "data.txt"; // запись в файл FILE *fp = fopen(filename, "w"); if(fp) < // записываем строку fputs(message, fp); fclose(fp); printf("File has been written\n"); >>

Запись довольно проста: открываем файл на запись и с помощью вызова fputs(message, fp) записываем в файл искомую строку.

Чтение текстового файла

Функция fgets() считывает из файла одну строку и имеет следующий прототип:

char * fgets(char *s, int n, FILE *sream);

Первый параметр char *s представляет строку, в которую производится считывание. Второй параметр — число n указывает, сколько символов необходимо считывать. И третий параметр представляет файловый поток, из которого производится считывание.

При вызове функция считывает из файла не более n-1 символов. Функция прекращает чтение, когда прочтет n-1 символов или встретит символ переноса строки \n. Все считанные символы записываются в строку s, в том числе символ \n. И также конец каждой строки дописывается нулевой символ ‘\0’.

При успешном завершении функция возвращает указатель s , а при ошибке или достижении конца файла возвращается значение NULL .

Считаем данные из выше записанного файла «data.txt»:

#include int main(void) < // файл чтения char * filename = "data.txt"; // буфер для считавания данных из файла char buffer[256]; // чтение из файла FILE *fp = fopen(filename, "r"); if(fp) < // пока не дойдем до конца, считываем по 256 байт while((fgets(buffer, 256, fp))!=NULL) < printf("%s", buffer); >fclose(fp); > >

Здеcь открываем файл на чтение и в цикле считываем из файла по 256 символов и выводим их на консоль с помощью вызова fgets(cc, 256, fp) . Когда данные в файле закончатся, функция возвратит NULL, и произойдет выход из цикла.

Копирование файлов

Другой пример работы с текстовыми файлами — копирование содержимого из одного файла в другой:

#include int main(void) < char * filename1 = "data1.txt"; char * filename2 = "data2.txt"; char buffer[256]; FILE *f1 = fopen(filename1, "r"); // файл на чтение FILE *f2 = fopen(filename2, "w"); // файл на запись if(!f1 || !f2) < printf("Error occured while opening file\n"); >else < // пока не дойдем до конца, считываем по 256 байт из файла f1 while((fgets(buffer, 256, f1))!=NULL) < // записываем строку в файл f2 fputs(buffer, f2); printf("%s", buffer); >> fclose(f1); fclose(f2); return 0; >

Как считать все строки из файла c

Доступ к диску (чтение/запись) гораздо (на несколько порядков) медленнее, чем доступ к данным в оперативной памяти. Кроме того, если мы читаем или записываем файл при помощи системных вызовов маленькими порциями (по 1-10 символов)

char c; while( read(0, &c, 1)) . ; /* 0 - стандартный ввод */

то мы проигрываем еще в одном: каждый системный вызов — это обращение к ядру операционной системы. При каждом таком обращении происходит довольно большая дополнительная работа (смотри главу «Взаимодействие с UNIX«). При этом накладные расходы на такое посимвольное чтение файла могут значительно превысить полезную работу.

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

Для решения этих двух проблем была построена специальная библиотека функций, названная stdio — «стандартная библиотека ввода/вывода» (standard input/output library). Она является частью библиотеки /lib/libc.a и представляет собой надстройку над системными вызовами (т.к. в конце концов все ее функции время от времени обращаются к системе, но гораздо реже, чем если использовать сисвызовы непосредственно).

Небезызвестная директива #include stdio.h> включает в нашу программу файл с объявлением форматов данных и констант, используемых этой библиотекой.

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

  • дескриптор fd файла для обращения к системным вызовам;
  • указатель на буфер, размещенный в памяти программы;
  • указатель на текущее место в буфере, откуда надо выдать или куда записать очередной символ; этот указатель продвигается при каждом вызове getc или putc;
  • счетчик оставшихся в буфере символов (при чтении) или свободного места (при записи);
  • режимы открытия файла (чтение/запись/чтение+запись) и текущее состояние файла. Одно из состояний — при чтении файла был достигнут его конец **;
  • способ буферизации;

Предусмотрено несколько стандартных структур FILE, указатели на которые называются stdin, stdout и stderr и связаны с дескрипторами 0, 1, 2 соответственно (стандартный ввод, стандартный вывод, стандартный вывод ошибок). Напомним, что эти каналы открыты неявно (автоматически) и, если не перенаправлены, связаны с вводом с клавиатуры и выводом на терминал.

Буфер в оперативной памяти нашей программы создается (функцией malloc) при открытии файла при помощи функции fopen(). После открытия файла все операции обмена с файлом происходят не по 1 байту, а большими порциями размером с буфер — обычно по 512 байт (константа BUFSIZ).

При чтении символа

int c; FILE *fp = . ; c = getc(fp);

getc выдает ее первый байт.

При последующих вызовах getc выдаются следующие байты из буфера, а обращений к диску уже не происходит! Лишь когда буфер будет исчерпан — произойдет очередное чтение с диска. Таким образом, информация читается из файла с опережением, заранее наполняя буфер; а по требованию выдается уже из буфера. Если мы читаем 1024 байта из файла при помощи getc(), то мы 1024 раза вызываем эту функцию, но всего 2 раза системный вызов read — для чтения двух порций информации из файла, каждая — по 512 байт.

char c; FILE *fp = . ; putc(c, fp);
  • буфер заполнен (содержит BUFSIZ символов).
  • при закрытии файла (fclose или exit ***).
  • при вызове функции fflush (см. ниже).
  • в специальном режиме — после помещения в буфер символа ‘\n‘ (см. ниже).
  • в некоторых версиях — перед любой операцией чтения из канала stdin (например, при вызове gets), при условии, что stdout буферизован построчно (режим _IOLBF, смотри ниже), что по-умолчанию так и есть.

Приведем упрощенную схему, поясняющую взаимоотношения основных функций и макросов из stdio (кто кого вызывает). Далее s означает строку, c — символ, fp — указатель на структуру FILE **** . Функции, работающие со строками, в цикле вызывают посимвольные операции. Обратите внимание, что в конце концов все функции обращаются к системным вызовам read и write, осуществляющим ввод/вывод низкого уровня.

Системные вызовы далее обозначены жирно, макросы — курсивом.

Открыть файл, создать буфер:

#include stdio.h> FILE *fp = fopen(char *name, char *rwmode); | вызывает V int fd = open (char *name, int irwmode); Если открываем на запись и файл не существует (fd < 0), то создать файл вызовом: fd = creat(char *name, int accessmode); fd будет открыт для записи в файл.

По умолчанию fopen() использует для creat коды доступа accessmode равные 0666 (rwrw-rw-).

Соответствие аргументов fopen и open:

rwmode irwmode ------------------------ "r" O_RDONLY "w" O_WRONLY|O_CREAT |O_TRUNC "r+" O_RDWR "w+" O_RDWR |O_CREAT |O_TRUNC "a" O_WRONLY|O_CREAT |O_APPEND "a+" O_RDWR |O_CREAT |O_APPEND 

Для r, r+ файл уже должен существовать, в остальных случаях файл создается, если его не было.

Если fopen() не смог открыть (или создать) файл, он возвращает значение NULL:

if((fp = fopen(name, rwmode)) == NULL)
Итак, схема:

printf(fmt. )--->--,----fprintf(fp,fmt. )->--* fp=stdout | fputs(s,fp)--------->--| puts(s)----------->-------putchar(c)-----,---->--| fp=stdout | fwrite(array,size,count,fp)->--| | Ядро ОС putc(c,fp) ------------------* | |файловая---write(fd,s,len)---------------read(fd,s,len)-* _flsbuf(c,fp) | | ! | |системные буфера ! | | | ! V ungetc(c,fp) |драйвер устр-ва ! | | |(диск, терминал) ! | _filbuf(fp) | | | ! *--------->-----БУФЕРgetc(fp) | rdcount=fread(array,size,count,fp)--<--| gets(s)-------<---------c=getchar()------,----<--| fp=stdout | | fgets(sbuf,buflen,fp)-<--| scanf(fmt. /*ук-ли*/)--<-,--fscanf(fp,fmt. )-* fp=stdin 

Закрыть файл, освободить память выделенную под буфер:

fclose(fp) ---> close(fd);

И чуть в стороне — функция позиционирования:

fseek(fp,long_off,whence) ---> lseek(fd,long_off,whence);

Функции _flsbuf и _filbuf — внутренние для stdio, они как раз сбрасывают буфер в файл либо читают новый буфер из файла.

По указателю fp можно узнать дескриптор файла:

int fd = fileno(fp);

Это макроопределение просто выдает поле из структуры FILE. Обратно, если мы открыли файл open-ом, мы можем ввести буферизацию этого канала:

int fd = open(name, O_RDONLY); /* или creat() */ . FILE *fp = fdopen(fd, "r");

(здесь надо вновь указать КАК мы открываем файл, что должно соответствовать режиму открытия open-ом). Теперь можно работать с файлом через fp, а не fd.

В приложении имеется текст, содержащий упрощенную реализацию главных функций из библиотеки stdio.

4.11.

Функция ungetc(c,fp) «возвращает» прочитанный байт в файл. На самом деле байт возвращается в буфер, поэтому эта операция неприменима к небуферизованным каналам. Возврат соответствует сдвигу указателя чтения из буфера (который увеличивается при getc()) на 1 позицию назад. Вернуть можно только один символ подряд (т.е. перед следующим ungetc-ом должен быть хоть один getc), поскольку в противном случае можно сдвинуть указатель за начало буфера и, записывая туда символ c, разрушить память программы.

while((c = getchar()) != '+' ); /* Прочли '+' */ ungetc(c ,stdin); /* А можно заменить этот символ на другой! */ c = getchar(); /* снова прочтет '+' */
4.12.

Очень часто делают ошибку в функции fputc, путая порядок ее аргументов. Так ничего не стоит написать:

FILE *fp = . ; fputc( fp, '\n' );

Запомните навсегда!

int fputc( int c, FILE *fp );

указатель файла идет вторым! Существует также макроопределение

putc( c, fp );

Оно ведет себя как и функция fputc, но не может быть передано в качестве аргумента в функцию:

#include putNtimes( fp, c, n, f ) FILE *fp; int c; int n; int (*f)(); < while( n >0 )< (*f)( c, fp ); n--; >> возможен вызов putNtimes( fp, 'a', 3, fputc ); но недопустимо putNtimes( fp, 'a', 3, putc );

Тем не менее всегда, где возможно, следует пользоваться макросом — он работает быстрее. Аналогично, есть функция fgetc(fp) и макрос getc(fp).

Отметим еще, что putchar и getchar это тоже всего лишь макросы

#define putchar(c) putc((c), stdout) #define getchar() getc(stdin)
4.13.

Известная вам функция printf также является частью библиотеки stdio. Она входит в семейство функций:

FILE *fp; char bf[256]; fprintf(fp, fmt, . ); printf( fmt, . ); sprintf(bf, fmt, . );

Первая из функций форматирует свои аргументы в соответствии с форматом, заданным строкой fmt (она содержит форматы в виде %-ов) и записывает строку-результат посимвольно (вызывая putc) в файл fp. Вторая — это всего-навсего fprintf с каналом fp равным stdout. Третяя выдает сформатированную строку не в файл, а записывает ее в массив bf. В конце строки sprintf добавляет нулевой байт ‘\0’ — признак конца.

Для чтения данных по формату используются функции семейства

fscanf(fp, fmt, /* адреса арг-тов */. ); scanf( fmt, . ); sscanf(bf, fmt, . );

Функции fprintf и fscanf являются наиболее мощным средством работы с текстовыми файлами (содержащими изображение данных в виде печатных символов).

4.14.

Текстовые файлы (имеющие строчную организацию) хранятся на диске как линейные массивы байт. Для разделения строк в них используется символ ‘\n‘. Так, например, текст

стр1 стрк2 кнц

хранится как массив

с т р 1 \n с т р к 2 \n к н ц длина=14 байт ! указатель чтения/записи (read/write pointer RWptr) (расстояние в байтах от начала файла)

При выводе на экран дисплея символ \n преобразуется драйвером терминалов в последовательность \r\n, которая возвращает курсор в начало строки (‘\r‘) и опускает курсор на строку вниз (‘\n‘), то есть курсор переходит в начало следующей строки.

В MS DOS строки в файле на диске разделяются двумя символами \r\n и при выводе на экран никаких преобразований не делается ***** . Зато библиотечные функции языка Си преобразуют эту последовательность при чтении из файла в \n, а при записи в файл превращают \n в \r\n, поскольку в Си считается, что строки разделяются только \n. Для работы с файлом без таких преобразований, его надо открывать как «бинарный»:

FILE *fp = fopen( имя, "rb" ); /* b - binary */ int fd = open ( имя, O_RDONLY | O_BINARY ); '\n' - '\012' (10) line feed '\r' - '\015' (13) carriage return '\t' - '\011' (9) tab '\b' - '\010' (8) backspace '\f' - '\014' (12) form feed '\a' - '\007' (7) audio bell (alert) '\0' - 0. null byte

Все нетекстовые файлы в MS DOS надо открывать именно так, иначе могут произойти разные неприятности. Например, если мы программой копируем нетекстовый файл в текстовом режиме, то одиночный символ \n будет считан в программу как \n, но записан в новый файл как пара \r\n. Поэтому новый файл будет отличаться от оригинала (что для файлов с данными и программ совершенно недопустимо!).

Задание: напишите программу подсчета строк и символов в файле. Указание: надо подсчитать число символов ‘\n‘ в файле и учесть, что последняя строка файла может не иметь этого символа на конце. Поэтому если последний символ файла (тот, который вы прочитаете самым последним) не есть ‘\n‘, то добавьте к счетчику строк 1.

4.15.

Напишите программу подсчета количества вхождений каждого из символов алфавита в файл и печатающую результат в виде таблицы в 4 колонки. (Указание: заведите массив из 256 счетчиков. Для больших файлов счетчики должны быть типа long).

4.16.

Почему вводимый при помощи функций getchar() и getc(fp) символ должен описываться типом int а не char?

Ответ: функция getchar() сообщает о конце файла тем, что возвращает значение EOF (end of file), равное целому числу (-1). Это НЕ символ кодировки ASCII, поскольку getchar() может прочесть из файла любой символ кодировки (кодировка содержит символы с кодами 0. 255), а специальный признак не должен совпадать ни с одним из хранимых в файле символов. Поэтому для его хранения требуется больше одного байта (нужен хотя бы еще 1 бит). Проверка на конец файла в программе обычно выглядит так:

. while((ch = getchar()) != EOF )< putchar(ch); . >
  • Пусть ch имеет тип unsigned char. Тогда ch всегда лежит в интервале 0. 255 и НИКОГДА не будет равно (-1). Даже если getchar() вернет такое значение, оно будет приведено к типу unsigned char обрубанием и станет равным 255. При сравнении с целым (-1) оно расширится в int добавлением нулей слева и станет равно 255. Таким образом, наша программа никогда не завершится, т.к. вместо признака конца файла она будет читать символ с кодом 255 (255 != -1).
  • Пусть ch имеет тип signed char. Тогда перед сравнением с целым числом EOF байт ch будет приведен к типу signed int при помощи расширения знакового бита (7ого). Если getchar вернет значение (-1), то оно будет сначала в присваивании значения байту ch обрублено до типа char: 255; но в сравнении с EOF значение 255 будет приведено к типу int и получится (-1). Таким образом, истинный конец файла будет обнаружен. Но теперь, если из файла будет прочитан настоящий символ с кодом 255, он будет приведен в сравнении к целому значению (-1) и будет также воспринят как конец файла. Таким образом, если в нашем файле окажется символ с кодом 255, то программа воспримет его как фальшивый конец файла и оставит весь остаток файла необработанным (а в нетекстовых файлах такие символы — не редкость).
  • Пусть ch имеет тип int или unsigned int (больше 8 бит). Тогда все корректно.

Отметим, что в UNIX признак конца файла в самом файле физически НЕ ХРАНИТСЯ. Система в любой момент времени знает длину файла с точностью до одного байта; признак EOF вырабатывается стандартными функциями тогда, когда обнаруживается, что указатель чтения достиг конца файла (то есть позиция чтения стала равной длине файла — последний байт уже прочитан).

В MS DOS же в текстовых файлах признак конца (EOF) хранится явно и обозначается символом CTRL/Z. Поэтому, если программным путем записать куда-нибудь в середину файла символ CTRL/Z, то некоторые программы перестанут «видеть» остаток файла после этого символа!

Наконец отметим, что разные функции при достижении конца файла выдают разные значения: scanf, fscanf, fgetc, getc, getchar выдают EOF, read — выдает 0, а gets, fgetsNULL.

4.17.

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

gets(s); fgets(s,slen,fp);

В чем разница?

Ответ: функция gets() читает строку (завершающуюся ‘\n‘) из канала fp==stdin. Она не контролирует длину буфера, в которую считывается строка, поэтому если строка окажется слишком длинной — ваша программа повредит свою память (и аварийно завершится). Единственный возможный совет — делайте буфер достаточно большим (очень туманное понятие!), чтобы вместить максимально возможную (длинную) строку.

Функция fgets() контролирует длину строки: если строка на входе окажется длиннее, чем slen символов, то остаток строки не будет прочитан в буфер s, а будет оставлен «на потом». Следующий вызов fgets прочитает этот сохраненный остаток. Кроме того fgets, в отличие от gets, не обрубает символ ‘\n‘ на конце строки, что доставляет нам дополнительные хлопоты по его уничтожению, поскольку в Си «нормальные» строки завершаются просто ‘\0‘, а не «\n\0«.

char buffer[512]; FILE *fp = . ; int len; . while(fgets(buffer, sizeof buffer, fp))< if((len = strlen(buffer)) && buffer[len-1] == '\n') /* @ */ buffer[--len] = '\0'; printf("%s\n", buffer); >

Здесь len — длина строки. Если бы мы выбросили оператор, помеченный ‘@’, то printf печатал бы текст через строку, поскольку выдавал бы код ‘\n‘ дважды — из строки buffer и из формата «%s\n».

Если в файле больше нет строк (файл дочитан до конца), то функции gets и fgets возвращают значение NULL. Обратите внимание, что NULL, а не EOF. Пока файл не дочитан, эти функции возвращают свой первый аргумент — адрес буфера, в который была записана очередная строка файла.

Фрагмент для обрубания символа перевода строки может выглядеть еще так:

#include stdio.h> #include string.h> char buffer[512]; FILE *fp = . ; . while(fgets(buffer, sizeof buffer, fp) != NULL)< char *sptr; if(sptr = strchr(buffer, '\n')) *sptr = '\0'; printf("%s\n", buffer); >
4.18.

В чем отличие puts(s); и fputs(s,fp); ?

Ответ: puts выдает строку s в канал stdout. При этом puts выдает сначала строку s, а затем — дополнительно — символ перевода строки ‘\n‘. Функция же fputs символ перевода строки не добавляет. Упрощенно:

fputs(s, fp) char *s; FILE *fp; < while(*s) putc(*s++, fp); >puts(s) char *s;

4.19.

Найдите ошибки в программе:
#include main()
Мораль: надо быть внимательнее к формату вызова и смыслу библиотечных функций.

* Это не та «связующая» структура file в ядре, про которую шла речь выше, а ЕЩЕ одна — в памяти самой программы.

** Проверить это состояние позволяет макрос feof(fp); он истинен, если конец был достигнут, ложен — если еще нет.

*** При выполнении вызова завершения программы exit(); все открытые файлы автоматически закрываются.

**** Обозначения fd для дескрипторов и fp для указателей на файл прижились и их следует придерживаться. Если переменная должна иметь более мнемоничное имя — следует писать так: fp_output, fd_input (а не просто fin, fout).

***** Управляющие символы имеют следующие значения:

© Copyright А. Богатырев, 1992-95
Си в UNIX

Чтение файла построчно

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

Отслеживать
9,345 6 6 золотых знаков 24 24 серебряных знака 57 57 бронзовых знаков
задан 6 окт 2013 в 6:48
Никола Кривошея Никола Кривошея
1,497 4 4 золотых знака 34 34 серебряных знака 72 72 бронзовых знака
Смотрите метод getline() Например: std::string name; std::getline (std::cin,name);
6 окт 2013 в 6:56

2 ответа 2

Сортировка: Сброс на вариант по умолчанию

вот код не вырванный из контекста

#include #include // подключаем строки #include // подключаем файлы using namespace std; // используем стандартное пространство имен int main() < string s; // сюда будем класть считанные строки ifstream file("C:\\PriceList.dat"); // файл из которого читаем (для линукс путь будет выглядеть по другому) while(getline(file, s))< // пока не достигнут конец файла класть очередную строку в переменную (s) cout file.close(); // обязательно закрываем файл что бы не повредить его return 0; > 

Отслеживать
ответ дан 1 авг 2015 в 11:01
9,981 14 14 золотых знаков 53 53 серебряных знака 118 118 бронзовых знаков

Ээээ. А вы уверены насчёт eof ? Если я не ошибаюсь, eof для istream ‘ов возвращает true не когда поток находится в конце файла, а когда поток находится в конце файла, и предыдущая попытка чтения из-за этого завершилась неудачно.

1 авг 2015 в 11:08

если честно я сам не уверен, насколько я понимаю при считывании очередной строки указатель сдвигается на конец этой строки. давно не кодил но пример отлично отрабатывает без ошибок. а вот сдесь cplusplus.com/forum/beginner/11304 тоже похожий код указан

Как реализовать считывание строк из файла в массив строк?

По ходу дела, я очень плохо понял тему указателей и работы с памятью, и теперь вот никак не могу сделать, казалось бы простейшую вещь.
Есть текстовый файл со словами, записанными по одному слову в строчку. Нужно считать каждую строку в массив arr[]. Пробовал и fscanf и fgetsе — не получается 🙁
Подскажите, что я не так делаю, и как правильно?

#include #define SIZE 45 int main() < char *text = "example"; FILE *fp = fopen(text, "r"); if (fp == NULL) < printf("Could not open %s.\n", text); return 1; >//char word[SIZE+1]; char *arr[26]; int k=0; while(!feof(fp)) < fscanf(fp,"%s",arr[k]); //fgets(word, SIZE, fp); //arr[k]=word; k++; >for(int i=0; i%s\n", i+1, arr[i]); > return 0; >
abc bcd cde def zet
  • Вопрос задан более трёх лет назад
  • 14267 просмотров

Комментировать
Решения вопроса 1
Developer, ex-admin

На сколько я понял вы пытаетесь прочитать строку из файла и сохранить ее как элемент массива arr.
Но делаете это не правильно.
Ваша ошибка в том, что fscanf/fgets не выделяет память для хранения строки, она использует тот буфер, который вы ей предоставите. Но вы ничего не предоставляете.
Вам нужно для каждого элемента arr выделить память под строку:
arr[i] = (char*)malloc(sizeof(char) * BUFLEN);
где BUFLEN — некоторая целочисленная константа, обозначающая максимальную длину строки.
Тогда чтение из файла, как реализовано у вас пройдет.
В конце память выделенную с помощью malloc нужно освободить с помощью вызова free для каждого элемента массива arr.
И не забывайте обрабатывать ошибки выделения памяти и возвраты файловых операций.
fscanf со спецификатором формата «%s» считывает не строку, а слово — в строке может быть много слов. Для чтения строки используйте fgets, либо читайте посимвольно fgetc.
Когда заработает, потренируйтесь на файле, содержащем строку из более чем BUFLEN символов и попытайтесь найти корректный выход из этой ситуации.

Ответ написан более трёх лет назад
Нравится 2 8 комментариев
romajke @romajke Автор вопроса

Спасибо за ответ!
Функция fscanf мне вполне подходит, т.к. в моих исходных данных все строки состоят из одного слова. С выделением памяти, я кажется разобрался. В цикле, перед тем как использовать fscanf я выделяю память для элемента массива.

while(!feof(fp)) < arr[k] = (char*)malloc(sizeof(char) * SIZE); fscanf(fp,"%s",arr[k]); k++; >fclose(fp);

Но с высвобождением — не уверен что делаю верно. Правильно ли высвобождается память в этом цикле?

for(int i=0; i

В итоге программа работает, но не совсем корректно. Я вывожу на экран не пустые ячейки массива с помощью цикла:

for(int i=0; i%s\n", i+1, arr[i]); >

И получаю результат:

1 ->abc 2 ->bcd 3 ->cde 4 ->def 5 ->zet 6 -> 7 -> 8 -> Segmentation fault

Откуда берутся лишние три строки, и ошибка сегментации?

romajke, Проверка
if (arr[i])
в вашем случае не срабатывает.
Потому что, чтоб в arr[i] были нули (т.е. условие ложно) их нужно туда присвоить, а у вас этого нет.
Перед чтением из файла, присвойте всем значениям arr нули.
Потом, когда будете выделять память для строк значащие элементы примут значения !=0, а пустые строки так и останутся нулями и условие отработает как надо.

romajke @romajke Автор вопроса

Все верно, это помогло. Спасибо!
Код работает корректно.
А что с памятью? Верно ли я ее высвобождаю циклом

for(int i=0; i

Или нужно как то по другому?

romajke, Раз уж вы выделяете память не для всех элементов массива, то и освобождать нужно только их: добавьте перед free условие:
if(arr[i])

romajke @romajke Автор вопроса

res2001, да. Это понятно, теперь код полностью корректно работает. Но не совсем понятна работа fscanf в данном контексте. Если я, например, не сразу считываю в элемент массива arr[], а предварительно записываю данные в созданную переменную типа char word[SIZE]:

char word[SIZE] while(!feof(fp))

то код уже работает не корректно.
Все элементы массива заполняются последней считанной строкой:

1 ->zet 2 ->zet 3 ->zet 4 ->zet 26 ->zet *** Error in `./tes': double free or corruption (out): 0x00007fffeebbd000 *** Aborted

Если я правильно понимаю, то элементы массива получают ссылку на word, и получают значение которое хранится по адресу переменной word. Но как это изменить - не пойму. Как передать в элемент массива именно значение word?

romajke @romajke Автор вопроса

Проблему решил, с помощью использования strcpy, но понимание не пришло 🙁
Буду очень благодарен, если объясните.

romajke, Операция
arr[k]=word;
Не приводит к копированию строки из одного буфера в другой - этим вы сохраняете адрес буфера word в массив. Поэтому у вас все элементы массива будут ссылаться на один и тот же буфер. Кроме того, что при этом вы получаете не правильный результат, у вас еще образуется утечка памяти, т.к. указатели, на выделенную с помощью malloc память, потеряны и во free вы передаете указатель на word, что при этом освобождается . не известно и поведение не предсказуемо.
Вы правильно решили проблему, явно копируя строку с помощью strcpy. Аналогично нужно делать и с любыми другими массивами только использовать функцию memcpy (с помощью memcpy можно копировать и строки то же, она в этом плане универсальнее чем strcpy).
Ну и на последок - в Си как таковых строк нет, строка - это массив байт, оканчивающийся байтом со значением 0 (нулевой байт то же является частью массива/строки), поэтому и думать нужно о них как о массивах. Различие в том, что под строкой вы понимаете какую-то самостоятельную единую сущность, а это не так - это набор сущностей (байт).

dio4

Вам не нужно сразу разбираться с памятью - зачем?

/* читаем из файла требуемое кол. строк ROWS или до конца файла EOF (что наступит раньше), формируем строку по конечному символу '\n', в файле, добавляем завершающий '\0' и полученную строку СИ записываем в двумерный массив по 1-й строке в каждый элемент массива. Затем массив выводим на консоль. Есть подсчет считанных строк. */ #include #include /* prototype exit() */ #include #define ROWS 7 #define COLS 20 void file_open(void); FILE * file_in; //FILE* file_out; char ch, arr_str[ROWS][COLS]; int j, i, count = 0; int main(int agrc, char * argv[]) < file_open(); //ch=getc(file_in); for(j = 0; j < ROWS; ++j)< for(i = 0; (ch=getc(file_in)) != '\n' && (ch != EOF); ++i) < arr_str[j][i] = ch; // заполнение строки >//конец внутреннего for if(ch == EOF) < puts("достигнут конец файла, выход."); //arr_str[j][i] = '\0'; fprintf(stdout, "count is: %i \n", count); break; >// конец if else < arr_str[j][i] = '\0'; ++count; >// конец else > // конец внешнего for fclose(file_in); //fclose(file_out); /* вывод массива на консоль */ fprintf(stdout, "Из файла считано %i строк, которые имеют вид:\n", count); puts(""); // пропуск строки for(j = 0; j < count; ++j)< fprintf(stdout, "%s\n", arr_str[j]); >puts(""); // пропуск строки puts("Программа завершена."); exit(EXIT_SUCCESS); > void file_open(void) < if( (file_in = fopen("file_in", "r")) == NULL)< fprintf(stdout, "%s\n", "не могу отрыть файл для чтения"); exit(EXIT_FAILURE); >/* if( (file_out = fopen("file_out", "w")) == NULL) < fprintf(stdout, "%s\n", "не могу отрыть файл для записи"); exit(EXIT_FAILURE); >*/ > /* Из файла считано 7 строк, которые имеют вид: I am a human 1 I am a 2 human`s 2 I am a 3 human 3 Nmjh## ___ 987 ht! 5555555555555555 66666666666666 77777777777777 Программа завершена. */

Ответы на вопрос 4

Используйте двумерный массив для строк:
char arr[26][SIZE];

И добавьте fclose(fp) после цикла while<. >.

Ответ написан более трёх лет назад
Нравится 1 3 комментария
romajke @romajke Автор вопроса
Все равно получается ошибка сегментации
romajke, а какая максимальная длинна строки в файле?
romajke @romajke Автор вопроса
Входные данные:

abc bcd cde def zet

sergio_nsk @sergio_nsk
fscanf замени на

size_t alloced = 0; getline(&arr[k], &alloced, fp);

getline с сравнением с -1 можно поместить в while.

Ответ написан более трёх лет назад
romajke @romajke Автор вопроса
getline это разве не C++?
sergio_nsk @sergio_nsk
getline - это не C++. Тебя разве забанили в Гугле?
romajke @romajke Автор вопроса
sergio_nsk @sergio_nsk
romajke, это расширение GNU.

dio4

team leader, system engineer, master of sports

/* читаем из файла требуемое кол. строк ROWS или до конца файла EOF * (что наступит раньше), формируем строку по конечному символу '\n' * в файле, затем добавляем завершающий '\0' и полученную строку СИ * записываем * в двумерный массив по 1-й строке в каждый элемент массива. * Затем массив выводим на консоль. Есть подсчет считанных строк. * См также ниже мой коммент */ /* file_in находится в каталоге программы и имеет вид: I am a human 1 I am a 2 human`s 2 I am a 3 human 3 Nmjh## ___ 987 ht! 5555555555555555 66666666666666 77777777777777 8888888888888888 */ #include #include /* prototype exit() */ #include #define ROWS 7 #define COLS 20 void file_open(void); FILE * file_in; char ch, arr_str[ROWS][COLS]; int j, i, count = 0; int main(int agrc, char * argv[]) < file_open(); for(j = 0; j < ROWS; ++j)< for(i = 0; (ch=getc(file_in)) != '\n' && (ch != EOF); ++i) < arr_str[j][i] = ch; // заполнение строки >//конец внутреннего for if(ch == EOF) < puts("достигнут конец файла, выход."); fprintf(stdout, "count is: %i \n", count); break; >// конец if else < arr_str[j][i] = '\0'; ++count; >// конец else > // конец внешнего for fclose(file_in); /* вывод массива на консоль */ fprintf(stdout, "Из файла считано %i строк, которые имеют вид:\n", count); puts(""); // пропуск строки for(j = 0; j < count; ++j)< fprintf(stdout, "%s\n", arr_str[j]); >puts(""); // пропуск строки puts("Программа завершена."); exit(EXIT_SUCCESS); > void file_open(void) < if( (file_in = fopen("file_in", "r")) == NULL)< fprintf(stdout, "%s\n", "не могу отрыть файл для чтения"); exit(EXIT_FAILURE); >> /* Вывод на консоль: Из файла считано 7 строк, которые имеют вид: I am a human 1 I am a 2 human`s 2 I am a 3 human 3 Nmjh## ___ 987 ht! 5555555555555555 66666666666666 77777777777777 Программа завершена. */

Ответ написан более трёх лет назад

dio4

посимвольная работа со строками имеет более жесткий контроль над процессом. Обратите внимание, что строк в файле больше, чем мы считали. Это задается #define ROWS 7 если задать к примеру 10, то получим:

достигнут конец файла, выход.
count is: 9
Из файла считано 9 строк, которые имеют вид:

I am a human 1
I am a 2 human`s 2
I am a 3 human 3
Nmjh## ___ 987 ht!
5555555555555555
66666666666666
77777777777777
8888888888888888

сообщение о конце файла (EOF тоже отлавливается). И в строке arr_str[j][i] = '\0'; формируется окончательно строка "си" завершающим символом '\0', что тоже важно. Можно использовать динамический массив или массив переменной длины(пропадает нужда следить и использовать free() , но придется точно знать количество строк в файле (высчитывать заранее). Также, при желании для уменьшения времени компиляции можно переписать программку в нотации указателей (компилятор все равно это будет делать сам) и вместо функций использовать указатели на них 🙂 .

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *