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

Обновлено: 04.07.2024

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

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

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

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

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


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

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

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

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

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

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

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


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

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

Способы считывания строки не известной длины (си) Что то не могу понять логику работы. Допустим есть файл, из него нужно считать строки и сохранить их для дальнейшей работы. Размер файла и строк не известен. Читаю пару книг, смотрю в интернете, но везде приводят примеры с заранее фиксированном кол-вом символов. Можно но словах, но если не трудно можно не большим примером. Как такое вообще делается.

Написал и вроде сам разобрался. Не знаю, может я не правильно что делаю?

Можно начать считывать файл в первом проходе, подсчитывая количество переносов строк (символы с кодами 10 и/или 13) и количество символов (расстояние) между началом файла / предыдущим переносом строки и очередным переносом строки. Это расстояние будет размером строки. Затем динамически malloc или new выделять память под эти строки для чтения их во втором проходе.

Ну или может как-то ещё средствами C++.

realloc - это некрасивое решение. Проще прочитать два раза, я думаю.

Последний раз редактировалось Felian; 14.12.2011 в 10:26 .

Можно начать считывать файл в первом проходе, подсчитывая количество переносов строк (символы с кодами 10 и/или 13) и количество символов (расстояние) между началом файла / предыдущим переносом строки и очередным переносом строки. Это расстояние будет размером строки. Затем динамически malloc или new выделять память под эти строки для чтения их во втором проходе.

Ну или может как-то ещё средствами C++.

realloc - это некрасивое решение. Проще прочитать два раза, я думаю.

Да о прочтении два раза уже думал, но думал есть решение проще. Если и правда первый вариант предпочтительней то выберу его. Подход правильный - читать посимвольныо, пока не встретится конец строки. Только реализация немножко не правильная.
Вместо того, чтобы все время дописывать один символ в вашу строку - создайте большой буфер, и читайте в него, когда нашли символ конца строки, создаете новую строку, размером равную количеству символов в буфере, и переписываете содержимое буфера в нее. Только не забывайте следить, чтобы буфер не переполнился. Если к моменту, когда буфер закончился, символ конца строки еще не прочитан, можно либо с помощью realloc() увеличить буфер, либо переписать содержимое в новую строку, и начать читать в буфер с начала, а потом дописать прочитанное в первую строку, опять же увеличив ее с помощью realloc() (я бы выбрал второй вариант).
Читать файл два раза - самое плохое решение, из всех тут рассмотренных Мужество есть лишь у тех, кто ощутил сердцем страх, кто смотрит в пропасть, но смотрит с гордостью в глазах. (с) Ария Имеются мелкие замечания по поводу написания кода, оптимизации. В целом этот вариант работает Все персонажи вымышлены, все совпадения случайны.
Если жизнь игра, тогда я её разработчик © . Сначала тоже его добавлял, но потом убрал. Т.к. вычитал, что компилятор его подставляет автоматически.

// readlines: считывает строки из файла в массив указателей на строки, возвращает количество считанных строк
// getline: считывает строку в s из файла, используется в readlines(), возвращает длину строки

Чтение из файла

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

Стандартная библиотека языка C++ <fstream> включает множество функций для работы с файлами. Описание функций можно найти в сети или в учебниках по C++. Здесь же я опишу одну, которая позволит произвести чтение строки из файла.

Содержание файла strings.txt

Три строки, содержащиеся в файле, я запишу массив и выведу на экран.

Пингвин читает содержимое файла

Переходим к написанию программы на C++.

Нашей программе понадобятся два заголовочных файла <iostream> и <fstream>. Первый нужен будет для использования вывода на консоль, второй для работы с файлами.

Объявим две целочисленные константы len и strings, они будут хранить максимальную длину наших строк и количество строк. Объявим символьную константу ch, которая будет хранить разделяющий символ. Первые две константы будут использоваться для объявления массива. Мой файл содержит 3 строки

При помощи значений двух первых констант объявим двумерный массив символов.

Создадим объект класса ostream, в конструктор поместим адрес файла, флаги открытия.

Флаг ios::in позволяет открыть файл для считывания, ios::binary открывает его в двоичном режиме.

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

В данный момент программа имеет такой вид

Теперь остается описать алгоритм считывания строк из файла и занесения их в массив с последующим выводом. Для этого понадобится цикл от нуля до strings с инкрементом переменной r. Во время каждого прохода цикла используем перегруженную функцию getline() объекта fs с тремя аргументами.

fs.getline(Массив_символов, Макс_длина_строки, Разделитель_строк)

Функция считывает символы из файла, пока не считает количество равное Макс_длина_строки, либо не встретит на своём пути символ равный Разделитель_строк, а после записывает считанные символы в Массив_символов. В качестве разделителя в моём текстовом файле используется перенос строки.

После сразу же выводим содержимое строки, хранящееся в массиве, при помощи поточного вывода в консоль cout.

Весь листинг конечной программы

За счет константных переменных её можно легко модернизировать. Изменив константу strings, можно указать количество выводимых строк. Чтение из файла будет производится до тех пор, пока массив не заполнится нужным количеством строк. Изменив константу ch, можно изменить разделитель строк(Например, можно разделять их пробелом и занести в массив отдельные слова из файла и т.д.).

Если Вас интересует запись в файл, то почитайте статью о чтении из input.txt и записи данных в файл output.txt.

Пример работы string и пример считывания всех строк из файла в массив с последующим выводом

Репутация: нет
Всего: нет

Задача одновременно проста, но оказалась сложной, суть ее такова:
Есть файл(ы) который нужно прочитать построчно в массив, а потом работать уже с массивом, количество строк в файле - неизвестно. Соответственно массив должен быть динамическим и колчиство его элементов должно меняться при чтении файла.

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

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

Сразу замечу, что код желательно напистаь на C(не С++), и под *NIX системы.

char** array; //массив, который в теории должен потом растягиваться до нужных размеров

ЗЫ: Просьба не посылать к манам, ибо уже они мне сняться.

Репутация: 9
Всего: 59

Не проще ли воспользоваться вектором? Для таких целей - самое то.

Сразу замечу, что код желательно напистаь на C(не С++), и под *NIX системы.

Добавлено через 9 минут и 49 секунд
Этот участок мне не понятен:

Получается что весь массив заполнен одним и тем же указателем на str
Чтобы записать строку в массив ее надо посимвольно скопировать, тем же strcpy

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

2.
Считывать построчно, задавая максимально возможный размер строки (как это сделано), но для хранения использовать связанный список

3. Написать что-то вроде STL-овского вектора (если нужен именно Си)

Репутация: 9
Всего: 59

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

Какие баги в вашем коде я увидел (и исправил):
1.
array = ( char**)realloc(array, sizeof(char*));
Данная функция, на сколько я помню, увеличивает массив не на указанную величину, а до указанной величины - т.е. надо задавать новый размер массива

2.
array[i] = ( char*)malloc(strlen(str)+1);
У нас первое значение i будет 1 - потеряется нулевой элемент - так как i инкрементируется до этого

3.
array[i] = str;
Так строки не копируется - этим вы копируете указатель на ваш статический массив

4.
for(j=0; i<=j; j++)Посмотрите внимательно на условие цикла. Вы перепутали переменые.

Репутация: 35
Всего: 60

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

Репутация: 9
Всего: 59


А разве realloc автоматически не копирует блок, если он помещается на новое место?

Репутация: 35
Всего: 60

Репутация: нет
Всего: нет

Какие баги в вашем коде я увидел (и исправил):
1.
array = ( char**)realloc(array, sizeof(char*));
Данная функция, на сколько я помню, увеличивает массив не на указанную величину, а до указанной величины - т.е. надо задавать новый размер массива

2.
array[i] = ( char*)malloc(strlen(str)+1);
У нас первое значение i будет 1 - потеряется нулевой элемент - так как i инкрементируется до этого

3.
array[i] = str;
Так строки не копируется - этим вы копируете указатель на ваш статический массив

Спасибо огромное всем кто ответил!

ЗЫ: Извиняюсь возможно за некоторое незнание некоторых моментов в С, о я последнюю строчку на С, написал в 99 году, по-этому сейчас спустя 8 лет достаточно сложно восстановить знания, прошу отнестись с пониманием

Репутация: 9
Всего: 59

А вот на счет передачи массива в функцию:
Если мы не знаем заренее сколько строк в файле, то какой массив мы должны передать? Наоборот - это функция должна возвратить считанный массив.

Репутация: 15
Всего: 88


Согласен с Anikmar`ом. И указатель на файл можно не передавать. Достаточно передать только имя файла. И потом уже работать с файлом непосредственно в самой ф-ции.
Можно ещё передать в ф-цию переменную, которая бы сохраняла количество строк в файле для дальнейшей работы с ними.
Ну, что-нибудь такое.

Репутация: 15
Всего: 88

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

Репутация: нет
Всего: нет

Репутация: 52
Всего: 207

Syberex,
нашел же тему
по вопросу
тут нужен mmap()

Ах, у елі, ах, у ёлкі, ах, у елі злыя волкі ©

Репутация: 63
Всего: 196

Syberex, функция filelength() нестандартна. Надо пользоваться комбинацией методов класса istream: seekg(0, std::ios_base::end).tellg().

Репутация: 1
Всего: 5

А лет 7 назад я не мог себе позволить выделить памяти 10 Мег под файл и усложнял подобные задачи введением буфера длиной всего 64 Кила. Потому что в DOS памяти 10 Мег не было (ехидно ухмыльнулся)/

Пятница. Машину на прикол. Всем хороших выходных = с пьянством и развратом!

Репутация: нет
Всего: нет

akizelokro, конечно алгоритм замечательный, за тем маленьким исключением, что размер файла изначально не известен, и он может быть от 2Мб, до 2Гб, если выделять область памяти = размер файла+N байт, то реально можно положить любую машину на лопатки при встрече с громадным файлом. Язык С выбран был не случайно, т.к. подобная задача уже реализована на Perl, но нужно реальное быстродействие и наименьшее потребление ресурсов, путь по которому ты предлагаешь идти - ресурсоемкий, соответственно не подходит :(. Но все равно - спасибо за совет
  • Черновик стандарта C++ (за октябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика(4.4мб).
  • Черновик стандарта C (за сентябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика (3.4мб).
  • Прежде чем задать вопрос, прочтите это и/или это! хранится весь мировой запас ссылок на документы, связанные с C++ :)
  • Не брезгуйте пользоваться тегами [code=cpp][/code].
  • Пожалуйста, не просите написать за вас программы в этом разделе - для этого существует "Центр Помощи".

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn

[ Время генерации скрипта: 0.2086 ] [ Использовано запросов: 21 ] [ GZIP включён ]

Читайте также: