Як функція getline() оптимізує обробку файлів та управління пам’яттю
Зі стандартної бібліотеки C Library
Мова C — універсальна, процедурна та імперативна мова програмування загального призначення. Її розробив у 1972 році Денніс Рітчі для створення операційної системи UNIX.
Попри свій поважний вік, C залишається актуальною й сьогодні, знаходячи застосування в багатьох сферах. Разом із C++ — мовою наступного покоління, яка вже охоплює об’єктно-орієнтований підхід, — їх вважають одними з найшвидших мов програмування. До речі, символ ++, який використовують для інкременту в обох мовах, надає назві C++ значення «C+1», тобто оновлену, покращену на одиницю версію попередника.
За десятки років ці мови пройшли значну оптимізацію і тепер можуть похвалитися багатим набором корисних функцій у стандартних бібліотеках. Їхнє застосування дає змогу заощадити час і значно підвищити ефективність розробки програм.
getline()
Однією з таких функцій, які варто розглянути, є getline.
Це дуже зручний та корисний інструмент для зчитування рядка тексту до символу нового рядка (\n) або до завершення файлу (EOF). Головна перевага getline — динамічне виділення пам’яті, яке значно спрощує роботу з файлами, особливо коли обсяг даних у файлі заздалегідь невідомий.
З опису вже зрозуміло: якщо не використовувати цю функцію, то легко застрягнути у створенні власного «велосипеда» для виконання базового функціонала, що може відібрати чимало часу й зусиль.
Виконаємо програму
З’ясуймо, як це працює.
Для того, щоб зрозуміти краще, створимо простеньку програму main.c:
#include <stdio.h>
#include <stdlib.h>
int main() {
char *line = NULL;
size_t len = 0;
ssize_t read;
FILE *file = fopen("file_with_lines.txt", "r");
if (!file) {
perror("Error");
return 1;
}
while ((read = getline(&line, &len, file)) != -1) {
printf("Line (%ld symbols): %s", read, line);
}
free(line);
fclose(file);
return 0;
}
Відповідно, створимо і наповнимо наш файл текстовими даними:
Suspendisse et turpis auctor, lobortis urna ut, mollis tellus.
Vivamus tincidunt urna at sem placerat pretium.
Integer quis turpis sit amet augue accumsan cursus.
Ut nec ligula vel mi consequat fermentum.
Mauris fermentum nunc vitae eros condimentum, eget luctus tellus suscipit.
Для компілювання і виконання вам потрібно мати встановлений компілятор.
Після виконання програми ми отримаємо такий вивід:
Line (63 symbols): Suspendisse et turpis auctor, lobortis urna ut, mollis tellus.
Line (48 symbols): Vivamus tincidunt urna at sem placerat pretium.
Line (52 symbols): Integer quis turpis sit amet augue accumsan cursus.
Line (42 symbols): Ut nec ligula vel mi consequat fermentum.
Line (75 symbols): Mauris fermentum nunc vitae eros condimentum, eget luctus tellus suscipit.
Як використовувати getline()
Розберімося, що тут у нас відбулося.
Передусім ми підключили бібліотеки:
#include <stdio.h> // наша функція getline розташована саме тут, окрім того, нам знадобляться й інші функції, зокрема printf, fopen тощо
#include <stdlib.h> //знадобиться нам, щоб викликати free і вивільнити пам'ять, яку самостійно виділить getline() для зчитування
Оголосили головну функцію main:
int main() {}
Одразу оголосили змінні та ініціалізували деякі з них. Саме їх ми надалі застосовуватимемо для передачі в ролі параметрів до функції getline().
char *line = NULL; // вказівник на символьний тип у пам’яті, який ми згодом використовуватимемо для передачі до функції getline як параметр
size_t len = 0; // беззнаковий тип даних для передачі параметру, який відповідає за розмір буферу
ssize_t read; // знаковий тип даних для збереження повернутого функцією getline значення кількості зчитаних символів, знаковий, бо є також значення -1, яке повертається у разі помилки або закінчення файлу
Відкрили файл зі збереженням дескриптора у змінній FILE *file:
FILE *file = fopen("file_with_lines.txt", "r");
Перевіряємо файл на наявність та можливість відкриття — якщо не вдається, виводимо повідомлення помилки. Тут перевіряємо дескриптор: якщо дескриптор є NULL, виводимо відповідний меседж разом із меседжем, який нам додає функція згідно з глобальною змінною errno.
if (!file) {
perror("Error");
return 1;
}
Таким чином, завдяки функції perror() ми отримаємо ерор-меседж на стандартний вивід під час передачі імені неіснуючого файлу:
Error: No such file or directory
Далі в циклі, використовуючи функцію getline(), зчитуємо полінійно наш файл. Водночас кожну ітерацію виводимо порядково інформацію про зчитаний рядок на стандартний вивід через функцію printf().
Отже, бачимо, що тут ми виконуємо цикл доти, доки значення змінної read не дорівнюватиме -1. Як відомо, таке відбудеться в тому випадку, коли файл закінчиться або відбудеться помилка зчитування. Тож поки у нас все ок, зчитуємо із файлу декриптор, якого передали змінною file. Отже, третій параметр — дескриптор, що раніше повернула функція fopen().
До цього ми оголосили та ініціалізували ще 2 змінні:
char *line = NULL;
size_t len = 0;
І, відповідно, передаємо їх першим і другим параметром.
Оскільки в ролі виділеної пам’яті (line) даємо NULL і розмір буфера (len) передаємо 0, функція сама подбає про створення буферу із потрібним і можливим розміром. Ми також самостійно могли б виділити пам’ять під операцію зчитування. В такому разі функція так само могла б його розширити за потреби.
while ((read = getline(&line, &len, file)) != -1) {
printf("Line (%ld symbols): %s", read, line);
}
Після виконання циклу отримуємо інформацію про перебіг зчитування на стандартний вивід. Саме так, як ми бачили на початку статті.
Далі ще є один важливий момент.
Оскільки функція виділяла під зчитування нашого файлу пам’ять, а ми передали змінну, яка вкаже, де саме буде виділено пам’ять, і ніде не загубили вказівник на цю ділянку, нашим обов’язком є вивільнити пам’ять функцією free():
free(line);
Як чемні програмісти, ми не лишаємо ніде висячі дескриптори, тож закриваємо файл функцією fclose(). Незалежно від того, чи система їх закриє самостійно, чи ні, це вважають правильним тоном, а ця тема — вже зовсім інша історія.
fclose(file);
Завершуємо нашу головну функцію поверненням значення, як роблять у С.
return 0;
getdelim()
Функція getline() фактично є «спрощеною» версією getdelim(). Відмінність між ними полягає в одному параметрі — delimiter, що в getline() за замовчуванням встановлений як символ нового рядка \n. Тобто getline() завжди читає до кінця рядка або файлу, тоді як getdelim() дозволяє зчитувати текст із потоку до вказаного символу-роздільника, який ви самі задаєте. Наприклад, коли потрібно зчитати текст до коми або до певного символу, якого немає в кінці рядка. Така гнучкість особливо корисна під час роботи з різними форматами даних або нестандартними файлами, де стандартний роздільник не підходить.
Висновок
Якщо ви працюєте з мовами C або C++, функція getline() (або її «сестра», про яку ми теж згадали вище) точно стане вам у пригоді. getline() також може бути чудовим інструментом для глибшого розуміння роботи з пам’яттю, якщо ви наважитеся зазирнути їй «під капот». І не забувайте: правильне вивільнення пам’яті та закриття файлових дескрипторів — це не лише гарна практика, а й запорука стабільної та надійної роботи вашого коду.