Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Пособие

.pdf
Скачиваний:
66
Добавлен:
22.03.2015
Размер:
1.35 Mб
Скачать

З відкритого таким чином файлу можна читати інформацію. Після закінчення роботи з файлом, його необхідно закрити за допомогою функції fclose().

Якщо файл відкривався би за допомогою fopen("test.txt", "rt+"), то можна було б не тільки читати, але й записувати в нього інформацію.

З текстового файлу можна читати інформацію за рядками, за символами або за форматом.

Запис символу в файловий потік здійснюється функцією putc().

int putc(int ch, FILE *f);

Читання рядка здійснюється за допомогою функції fgets().

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

У виклику функції fgets() s - покажчик на буфер, в який читається рядок, n – кількість символів. Читання символу в рядок проходить або до появи символу кінця рядка "\n", або читається n-1 символ. У кінці прочитаного рядка записується нульовий символ

[16].

#include <stdio.h> #include <string.h> #include <conio.h> int main()

{

char s[80]; FILE *f;

if ((f=fopen("1.cpp", "rt"))==NULL)

{

printf("There are an error\n"); return 0;

_getch();

}

do

{

fgets(s,80,f);

printf("%s",s); } while (!feof(f));

fclose(f);

141

_getch(); return 0;

}

Функція feof() перевіряє, чи не прочитаний символ завершення файлу. Якщо такий символ прочитаний, то feof() повертає ненульове значення, і цикл завершується.

Читання з текстового файлу форматованих даних може здійснюватися функцією fscanf(). Синтаксис :

int fscanf(FILE *stream, const char *format[, address, …]);

Параметр format визначає рядок форматування аргументів, що задаються своїми адресами.

При форматованому читанні можуть виникати помилки у зв’язку з досягненням завершення файлу або невірним форматом записаних у файлі даних. Перевірити, чи успішно пройшло читання даних можна за значенням, що повертає функція fscanf(). При успішному читанні вона повертає кількість прочитаних полів. Тому читання даних можна організовувати наступним чином:

if (fscanf(f,"%d%d%d",&a,&b,&c)!=3)

{

printf("Помилка читання!\n");

};

Існує також і ряд функцій для запису даних у текстовий файл. Найчастіше використовуються функції fgetc(), fputs() та fprintf().

Функція fgetc() використовується для читання чергового символу з потоку, відкритого функцією fopen().

int fgetc(FILE *f);

Синтаксис функції fprintf():

int fprintf(FILE *stream, const char *format[,argument,…]);

Вона працює майже мак само, як і функція printf(), але їй потрібний додатковий аргумент для посилання на файл. Він є першим у

142

списку аргументів. Наведемо приклад, що ілюструє звертання до наведених вище функцій.

Приклад.

#include<stdio.h> #include <conio.h> int main()

{

FILE *fi; int age;

fi=fopen("age.txt","r");

/* відкриття файла для читання */ fscanf(fi,"%d",&age);

/*читання з файлу числового значення */ fclose(fi); /* закриття файлу */ fi=fopen("data.txt", "a");

/* відкриття файлу для додавання інформації в кінець */ fprintf(fi, "Age==%d.\n",age); /* запис рядка в файл */ fclose(fi); /* закриття файлу */ _getch();

return 0;

}

18.2 Двійкові файли

Розглянемо роботу з двійковими файлами. Двійковий файл представляє собою просто послідовність символів. Що саме і в якій послідовності зберігається в двійковому файлі - повинна знати програма.

Двійкові файли мають переваги порівняно з текстовими при зберіганні числових даних. Операції читання і запису з такими файлами виконуються набагато швидше, ніж з текстовими, так як відсутня необхідність форматування (переведення в текстове представлення та навпаки). Двійкові файли зазвичай мають менший розмір, ніж аналогічні текстові файли. В двійкових файлах можна переміщуватися в будь-яку позицію і читати або записувати дані в довільній послідовності, в той час, як в текстових файлах практично завжди виконується послідовна обробка інформації.

143

Запис і читання в двійкових файлах виконується відповідно фун-

кціями fwrite і fread.

size_t fwrite(const void *ptr, size_t size, size_t n, FILE*stream);

size_t fread(void *ptr, size_t size, size_t n, FILE *stream);

В обидві функції повинен передаватися покажчик ptr на дані, що вводяться або виводяться. Параметр size задає розмір в байтах даних, що читаються або записуються.

#include <stdio.h> #include <conio.h> struct mystruct

{

int i; char ch;

};

int main(void)

{

FILE *stream; struct mystruct s;

if ((stream = fopen("test.txt", "wb")) ==

NULL)

{

fprintf(stderr,

"Неможливо відкрити файл\n"); return 1;

}

s.i = 0; s.ch = 'A';

fwrite(&s, sizeof(s), 1, stream); fclose(stream);

_getch(); return 0;

}

Тепер розглянемо особливості записування і читання рядків.

char s[10];

strcpy(s, "Example");

144

fwrite(s, sizeof(char),strlen(s)+1, stream);

У даному прикладі число символів, що записуються – strlen(s)+1 (одиниця додається на нульовий символ в кінці).

Дуже часто доводиться працювати з рядками різних довжин. У таких випадках можна перед рядком записати у файл ціле число, що рівне числу символів у рядку.

int i=strlen(s)+1; fwrite(&i,1,sizeof(int),stream); fwrite(s,i,1,stream);

fread(&i,1,sizeof(int),stream);

fread(s,i,1,stream);

В усіх наведених вище прикладах читання даних проходило послідовно. Але, працюючи з двійковими файлами, можна організувати читання даних у довільному порядку. Для цього використовується «покажчик файлу» (курсор), що визначає поточну позицію у файлі. При читанні даних курсор автоматично зміщується на число прочитаних байтів. Отримати поточну позицію курсору файлу можна за допомогою функції ftell().

long ftell(FILE *stream);

А встановлюється поточна позиція курсору у файлі за допомогою функції fseek():

int fseek(FILE *stream, long offset, int whence);

Ця функція задає зміщення на число байтів offset від точки відліку, що визначається параметром whence. Цей параметр може приймати значення 0, 1, 2 (табл. 18.2).

 

 

Таблиця 18.2.

Можливі значення параметра whence функції fseek()

Константа

whence

Точка відліку

SEEK_SET

0

Початок файлу

SEEK_CUR

1

Поточна позиція

SEEK_END

2

Кінець файлу

 

 

145

Якщо задане значення whence=1, то offset може приймати як додатне, так і від’ємне значення, тобто зсув уперед або назад.

Функція rewind() переміщує курсор на початок файлу.

void rewind(FILE *stream);

Те ж саме можна зробити за допомогою функції fseek():

fseek(stream, 0L, SEEK_SET);

Приклад програми, в якій використовуються описані вище функції:

#include <stdio.h> #include <conio.h>

long filesize(FILE *stream); int main(void)

{

FILE *stream;

stream = fopen("test.txt", "w+"); fprintf(stream, "This is a test"); printf("Розмір файла test.txt рівний

%ld байт\n", filesize(stream));

fclose(stream); return 0;

}

long filesize(FILE *stream)

{

long curpos, length; curpos = ftell(stream);

fseek(stream, 0L, SEEK_END); length = ftell(stream); fseek(stream, curpos, SEEK_SET); return length;

}

146

19 ІНДИВІДУАЛЬНІ ЗАВДАННЯ ДО П. 18

Необхідно організувати файл даних із структурою взятої з попереднього індивідуального завдання та передбачити функції, що дозволяють :

коригування обраного запису файлу;

пошук інформації за різними полями;

додавання записів у кінець файлу;

вилучення інформації з файлу;

перегляд вмісту файлу.

147

20ДИНАМІЧНІ СТРУКТУРИ ДАНИХ

20.1Загальні відомості про динамічні структури даних

Будь-яка програма призначена для опрацювання даних, від спо-

собу організації яких залежать алгоритми роботи, тому вибір структур даних повинен передувати створенню алгоритмів. Вище були розглянуті стандартні способи організації даних, що надаються мовою C, основні і складені типи. Найбільш часто в програмах використовуються масиви, структури та їх поєднання, наприклад, масиви структур, полями яких є масиви і структури [11].

Пам’ять під дані виділяється або на етапі компіляції (у цьому випадку необхідний обсяг повинен бути відомий до початку виконання програми, тобто заданий у вигляді константи), або під час виконання програми за допомогою функції mallос (необхідний обсяг повинен бути відомий до розподілу пам’яті). В обох випадках виділяється безперервна ділянка пам’яті.

Якщо до початку роботи з даними неможливо визначити, скільки пам’яті потрібно для їх зберігання, пам’ять виділяється окремими блоками, зв’язаними один з одним за допомогою покажчиків. Такий спосіб організації даних називається динамічними структурами даних, оскільки їх розмір змінюється під час виконання програми. З динамічних структур у програмах найчастіше використовуються лінійні списки, стеки, черги і бінарні дерева. Вони розрізняються способами зв’язку окремих елементів і допустимими операціями. Динамічна структура може займати несуміжні ділянки оперативної пам’яті.

Динамічні структури широко застосовують і для більш ефективної роботи з даними, розмір яких відомий, особливо для вирішення завдань сортування, оскільки упорядкування динамічних структур не вимагає перестановки елементів, а зводиться до зміни покажчиків на ці елементи. Наприклад, якщо в процесі виконання програми потрібно багато разів впорядковувати великий масив даних, має сенс організувати його у вигляді лінійного списку. При вирішенні завдань пошуку елемента в тих випадках, коли важлива швидкість, дані найкраще представити у вигляді бінарного дерева.

Елемент будь-якої динамічної структури даних являє собою структуру (в смислі struct), що містить принаймні два поля: для зберігання даних і для покажчика. Полів даних і покажчиків може бути декілька. Поля даних можуть бути будь-якого типу: основного,

148

складеного або типу покажчик. Опис найпростішого елемента (вузла) виглядає наступним чином:

struct Node

{

Data d; // тип даних Data повинен // бути визначено заздалегідь

Node *р:

};

Розглянемо реалізацію основних операцій з динамічними структурами даних.

20.2 Лінійні списки

Найпростіший спосіб зв’язати безліч елементів - зробити так, щоб кожен елемент містив посилання на наступний. Такий список називається односпрямованим (однозв’язним). Якщо додати в ко-

жен елемент ще одне посилання на попередній елемент, вийде двоспрямований список (двозв’язний), якщо останній елемент зв’язати покажчиком з першим, вийде кільцевий список.

Кожен елемент списку містить ключ, що ідентифікує цей елемент. Ключ зазвичай буває або цілим числом, або рядком і є частиною поля даних.

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

Над списками можна виконувати такі операції:

-початкове формування списку (створення першого елемента);

-додавання елемента в кінець списку;

-читання елемента із заданим ключем;

-вставка елемента в задане місце списку (до або після елемента із заданим ключем);

-видалення елемента із заданим ключем;

-впорядкування списку за ключем.

Схематично лінійний односпрямований список виглядає так :

149

Рис. 20.1 Схематичне зображення односпрямованого лінійного списку

Реалізація основних операцій :

1. Включення елемента в початок списку.

val next

val next

val next

 

...

first

Рис. 20.2 Схема дії операції включення елемента в початок списку

list *addbeg(list *first, elemtype x)

{

list *n_sp;

n_sp = (list *) malloc(sizeof(list)); n_sp->val=x;

n_sp->next=first; first=n_sp; return first;

}

2. Видалення елемента з початку списку.

Рис. 20.3. Схема дії операції видалення елемента

150