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

C _Учебник_МОНУ

.pdf
Скачиваний:
206
Добавлен:
12.05.2015
Размер:
11.12 Mб
Скачать

Типи користувача

379

Розглянемо кілька прикладів застосовування операцій, поданих у табл. 11.2:

Set <short, 0,

200> f;

f << 1 << 5

<<

19 << 119;// f складається з 1, 5, 19, 119

Set <short,

0,

200> f1;

f1 << 2 << 45;

// f1 складається з 2, 45

f = f.operator +(f1);

//або f+=f1; тепер f складається з 1,2,5,19,45,119

Set <short, 0, 200> f2;

f2 << 2 << 5 << 19 << 47; // f2 складається з 2, 5, 19, 47 f = f.operator * (f2); // Операція operator* або f *= f1;

// f є перетином множин f та f2, в якому залишилися числа 2, 5 та 19

Наведемо приклади застосовування множин на практиці. Якщо треба обмежити можливість вводити до вікна редактора Edit1 лише числа від 0 до 9, для цього у відгуку події OnKeyPress вікна Edit1 слід записати такі оператори:

Set <char, '0', '9'> Dig;

Dig <<'0'<<'1'<<'2'<<'3'<<'4'<<'5'<<'6'<<'7'<<'8'<<'9'; if(!Dig.Contains(Key)) Key=0;

Відомо, що для компонента StringGrid властивість Options визначена як множина такого вигляду:

enum TGridOption { goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goRangeSelect, goDrawFocusSelected, goRowSizing, goColSizing, goRowMoving, goColMoving, goEditing, goTabs, goRowSelect, goAlwaysShowEditor, goThumbTracking };

typedef Set <TGridOption, goFixedVertLine, goThumbTracking> TGridOptions;

Тому, для долучення певних опцій компонента StringGrid1 із зазначених значень множини, наприклад для можливості редагування даних у комірках при виконуванні програми і для переміщення від однієї комірки до іншої натисненням клавіші <Tab>, у програмі можна записати оператор

StringGrid1->Options<<goEditing<<goTabs;

а для вилучення однієї з опцій властивості Options, наприклад goTabs – оператор

StringGrid1->Options>>goTabs;

Питання та завдання для самоконтролю

1)В який спосіб можна створювати нові типи даних на базі вже існуючих? Запишіть оголошення типу Str як масиву з 60-ти символів.

2)Дайте означення структури як типу даних.

3)У чому полягає відмінність структури від масиву?

4)Як здійснюється доступ до полів структури?

5)Як визначається обсяг пам‟яті, потрібний для зберігання структури?

6)Чи можуть в одній програмі збігатися імена полів структури і змінних?

7)Чи можуть збігатися в одній програмі імена полів двох структур?

8)Чи дозволяється створювати вкладені структури?

380

Розділ 11

9) Придумайте оголошення структури, яка описуватиме для деякого електричного приладу такі характеристики: назва приладу, споживана потужність та номінальна напруга.

10)Чи може матриця бути полем структури? Якщо так, наведіть приклад.

11)Чи має значення порядок слідування полів у описі структури?

12)Чи може структура бути типом елемента масиву? Якщо так, наведіть

приклад.

13)Оголосіть структуру з ім‟ям Student, що містить такі поля: прізвище, назва групи, масив з п‟яти екзаменаційних оцінок. Запишіть команди, які нададуть початкові значення усім полям змінної типу Student.

14)Найдіть та виправте помилки припущені в описі структури: structure { int arr[12], char string, int *sum }

15)Дайте означення типу даних “об‟єднання”.

16)Як визначається обсяг пам‟яті, потрібний для зберігання усіх полів певного об‟єднання?

17)Напишіть об‟єднання, в якому може зберігатися чи то покажчик, чи ціле, чи дійсне число.

18)Охарактеризуйте тип перерахування.

19)Яких значень набувають константи перерахування за відсутності їхньої ініціалізації?

20)Якщо перерахування COLOR задано в такий спосіб, то яким є значення його елементів white, red, blue та yellow?

enum COLOR { white, black=100, red, blue, green=300, yellow};

21)Які помилки містять наведені оператори?

enum State { on, off }; enum YesNo { on, off };

22)Правильним чи ні є таке оголошення перерахування? enum YesNo { no = 0, No = 0, yes = 1, Yes = 1 };

23)Створіть оголошення перерахування response зі значеннями “Так”, “Ні” і “Можливо”. Значення “Так” повинно мати порядковий номер 1, “Ні” – 0 та “Можливо” – 2.

24)Дайте означення множини в С++ Builder.

25)Якими є елементи множини f, якщо її визначено у такий спосіб:

Set <int, 100, 300> f;

f << 1 << 105 << 119 << 245 << 419;

а) 1, 105, 119, 245, 419; б) 105, 119, 245;

в) усі цілі числа в межах від 100 до 300; г) пуста множина, оскільки станеться помилка.

Розділ 12

Файли

12.1 Загальні відомості про файли

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

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

Файли відрізняються від звичайних масивів тим, що

вони можуть змінювати свій розмір;

звертання до елементів файлів здійснюється не за допомогою операції індексації [], а за допомогою спеціальних системних викликів та функцій;

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

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

С++ Builder надає засоби опрацювання двох типів файлів: текстових та

бінарних.

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

лідовністю '\n', а розділювачами слів та чисел у рядку є пробіли й символи табуляції. Оскільки вся інформація текстового файла є символьною, програмне опрацювання такого файла полягає в читанні рядків, виокремлюванні з рядка слів і, за потреби, перетворюванні цифрових символьних послідовностей на числа відповідними функціями перетворювання. Створювати, редагувати текстові файли можна не лише в програмі, а й у якому завгодно текстовому редакторі, наприклад Блокноті чи Word.

Бінарні файли зберігають дані в тому самому форматі, в якому вони були оголошені, і їхній вигляд є такий самий, як і в пам‟яті комп‟ютера. І тому відпадає потреба у використанні розділювачів: пробілів, керувальних послідовностей, а отже, обсяг використовуваної пам‟яті порівняно з текстовими файлами з аналогічною інформацією є значно меншим. Окрім того, немає потреби у застосуванні функцій перетворення числових даних. Але кожне опрацювання даних

TSringList і рядкову

382

Розділ 12

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

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

Робота з файлами в С++ Builder може виконуватися кількома різними способами:

використання бібліотечних компонентів;

робота з файлами як з потоками у стилі С;

робота з файлами як з потоками у стилі С++;

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

вих файлів.

12.2 Текстові файли

12.2.1Зчитування і записування текстових файлів за допомогою компонентів С++ Builder

Робота з текстовими файлами може здійснюватися за допомогою методів

LoadFromFile() та SaveToFile(), які є у класів TStrings та TSringList. Ці класи описують списки рядків і мають безліч методів маніпулювання рядками.

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

визначити дві глобальні змінні – список типу змінну типу AnsiString, наприклад:

TStringList *List = new TStringList; AnsiString SFile = "Test.txt";

завантажити файл з ім‟ям SFile до свого списку за допомогою команди

List->LoadFromFile(SFile);

зберегти свій файл після редагування за допомогою команди

List->SaveToFile(SFile);

При відкриванні та зберіганні файла можна користуватися стандартними компонентами-діалогами OpenDialog та SaveDialog, розташованими на закладці Dialogs (див. табл. 2.4 у п. 2.1.1). Для відкривання, переглядання й редагування файла можна користуватися стандартними багаторядковими компонентами типу TMemo й TRichEdit. У останньому разі можна працювати з файлами у форматі RTF. Властивості Lines цих компонентів мають тип TStrings, що дозволяє безпосередньо користуватися методами LoadFromFile() та

SaveToFile(), наприклад:

Memo1->Lines->LoadFromFile(SFile); RichEdit1->Lines->LoadFromFile(SFile);

Через компоненти C++ Builder можна працювати не лише з текстовими файлами, але і з файлами зображень і мультимедіа.

Файли

383

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

Memo, OpenDialog, SaveDialog та три кнопки Button й матимемо зображення

Текст програми в C++ Builder:

// Кнопка “Завантажити файл”.

void __fastcall TForm1::Button1Click(TObject *Sender) { if(OpenDialog1->Execute())

Memo1->Lines->LoadFromFile(OpenDialog1->FileName);

}

//Кнопка “Зберегти змінення у файлі”.

//Зберігання файла виконується при зміненні тексту в Memo1.

void __fastcall TForm1::Button2Click(TObject *Sender) { if(Memo1->Modified)

if(SaveDialog1->Execute()) Memo1->Lines->SaveToFile(SaveDialog1->FileName);

}

12.2.2 Робота з текстовими файлами у стилі С

У мові С файл розглядається як потік послідовності байтів. Інформація про файл заноситься до змінної типу FILE*. Цей тип оголошує вказівник потоку, який використовується надалі у всіх операціях з цим файлом. Тип FILE означено у бібліотеці <stdio.h>. Тому, якщо в програмі передбачається робота з файлами, цей заголовний файл слід долучити:

#include <stdio.h>

Тепер можна оголосити файлову змінну – вказівник потоку, який в подальшому буде передаватися у функції введення-виведення у якості параметра:

FILE *f;

Функція fopen() для відкривання файла має такий синтаксис:

FILE *fopen(const char *filename, const char *mode);

Перший параметр filename визначає ім‟я файла, який відкривається. Другий параметр mode задає режим відкривання файла (табл. 12.1).

384

Розділ 12

 

Таблиця 12.1

 

Специфікатори режиму відкривання файлів

 

 

Параметр

Опис

 

 

r

відкрити файл лише для зчитування даних

r+

відкрити існуючий файл для зчитування й записування даних

а

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

a+

відкрити чи створити файл для зчитування чи то записування даних

 

у кінець файла

w

створити файл для записування даних

w+

створити файл для зчитування і записування даних

До зазначених специфікаторів наприкінці чи перед знаком “+” може дописуватись символ чи то “t” – для текстових файлів, чи “b” – для бінарних (двійкових) файлів.

Функція fopen() повертає вказівник на об‟єкт, який керує потоком. Наприклад, створити файл з ім‟ям Prim.txt можна так:

f = fopen("Prim.txt", "wt");

Якщо файл відкрити не вдалося, fopen() повертає нульовий вказівник NULL. Для уникання помилок після відкриття файла слід перевірити, чи насправді файл відкрився:

if(f == NULL) {ShowMessage("Файл не відкрито"); return;}

Припинити роботу з файлом можна за допомогою функції fclose(FILE *f).

Ця функція закриває файл, на який посилається параметр функції. Наведемо приклад відкривання текстового файла для зчитування.

FILE *f;

// Перевіряємо, чи повертає ця функція нульовий вказівник

 

if((f=fopen("t.txt","rt"))==NULL)

 

{ ShowMessage("Неможливо відкрити файл");

return; }

. . .

// Сюди вставляються команди зчитування з файла.

fclose(f);

// Закриття файла.

 

З текстового файла можна читати цілі рядки й окремі символи. Слід звернути увагу на те, що тип FILE* перейшов в C++ зі стандартного С, і такий файл “розуміє” лише С-рядки, тобто рядки типу char*. Тому в C++ Builder усі рядки типу AnsiString доводиться перетворювати за допомогою методу c_str() на С-рядок і, навпаки, всі С-рядки для виведення на форму треба перетворювати на AnsiString.

Зчитування рядка з файла здійснюється функцією fgets(): char *fgets(char *s, int m, FILE *stream);

де s – перший параметр – рядок типу char*; m – кількість читаних символів (байтів); stream – вказівник на потік даних файла.

Файли

385

Перевірка кінця файла здійснюється функцією feof(): int feof (FILE *stream);

Розглянемо детальніше використання цих функцій:

char s[50]; Memo1->Clear(); do { fgets(s,50,f);

Memo1->Lines->Add(s); } while (!feof(f));

Даний приклад ілюструє зчитування даних з файла порядкóво за допомогою функції fgets() і виведення рядків до компонента Memo. Перший параметр функції fgets() – це С-рядок, в який читається черговий рядок текстового файла. Зчитування рядка відбувається до появи символу кінця рядка '\n' або ж припиняється, коли прочитано m-1 символ. У нашому прикладі m=50. Отже, якщо файл містить рядок, який має понад 50 символів, то буде прочитано лише перші 49 символів. При цьому поточна позиція файла залишиться в тому самому рядку й за подальшого використання функції fgets() читатиметься залишок рядка. Третій параметр зазначає потік, з якого здійснюється зчитування.

У даному прикладі при зчитуванні всіх даних з файла f використовується функція feof(f), яка перевіряє, чи не прочитано символ кінця файла. Після зчитування цього символу функція feof(f) поверне ненульове значення – і цикл перерветься.

У наведеному прикладі є два недоліки:

1) річ у тім, що прочитаний рядок може завершуватися символом '\n', який є ознакою кінця рядка. Цей символ також відобразиться у вікні Memo й “зіпсує” вигляд вихідних даних. Усунути цей недолік можна за допомогою перевірки командою

if(s[strlen(s)-1]=='\n') s[strlen(s)-1]= '\0';

який прибере з рядка символ '\n';

2) використання функції feof() часто призводить до появи дубліката останнього рядка файла, тому рекомендовано використовувати в якості умови циклу функцію зчитування даних, наприклад fgets(), або контролювати добігання кінця файла, тобто перевіряти, чи прочитано всі записані у файлі символи (нагадаємо, що 1 символ – 1 байт). Наведений приклад зчитування рядків з файла можна тепер записати в такий спосіб:

while(fgets(s,50,f))

{if(s[strlen(s)-1]=='\n') s[strlen(s)-1] = '\0'; Memo1->Lines->Add(s); }

Зчитування форматованих даних можна також здійснювати за допомогою функції fscanf():

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

Наведемо приклад використання цієї функції:

float r; Memo1->Clear();

while(fscanf(f,"%e",&r)>0) Memo1->Lines->Add(FloatToStr(r));

386

Розділ 12

У даному прикладі здійснюється зчитування даних з файла, який містить дійсні числа. За розділювачі поміж числами вважаються пробіли. Перший параметр f функції fscanf() визначає файл, з якого відбувається зчитування. Другий параметр задає формат рядка аргументів, заданих їхніми адресами. Функція fscanf() є цілого типу і повертає, як своє вихідне значення, кількість прочитаних елементів. При форматованому читанні часто можуть виникати помилки через невідповідність форматів чи то кінець файла. Для уникання таких помилок доцільно організовувати перевірки кількості прочитаних функцією fscanf() елементів. У наведеному прикладі формат "%e" визначає дійсне число з рухомою крапкою. Прочитане дійсне число з файла зберігається за адресою змінної r типу float.

Рядок форматування параметра format будується з послідовності симво- лів-специфікаторів типів читаних даних, найпоширеніші з яких наведено в табл. 12.2.

 

 

Таблиця 12.2

 

Специфікатори параметра format

 

 

 

 

Символ

Значення, що вводиться

Тип аргументу

i, l

десяткове, вісімкове чи шістнадцяткове ціле число

int, long

d, D

десяткове ціле число

int, long

e, E

дійсне число з рухомою крапкою

float

f, F

дійсне число з фіксованою крапкою

float

s

рядок символів

char s[]

c

символ

char

Записування даних до текстового файла можна здійснювати за допомогою функції

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

де s – рядок типу char, stream – файловий потік.

Для записування даних до файла слід відкрити файл у форматі записування даних у кінець файла, за потреби перетворити рядок на тип char і записати дані до файла. Послідовність процесу показано у фрагменті програми:

f=fopen("а.txt","at+"); if(f==0)

{ ShowMessage("Не вдається створити файл "); return;

}

char s[40];

//Копіюємо рядок з Edit1 до s, перетворюючи його на С-рядок. strcpy(s,Edit1->Text.c_str());

//Долучаємо до рядка символ <Enter>, інакше у файлі strcat(s,"\n");// все буде записано одним рядком.

fputs(s,f);

// Записуємо рядок s до файла.

fclose(f);

 

Файли

387

Записування до текстового файла можна здійснити також за допомогою функції fprintf():

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

Ця функція є подібна до функції fscanf(), але має ширші можливості побудови рядка форматування, наприклад:

char s[20]; strcpy(s, "Іванов"); int year=1985;

fprintf(F, "ХАРАКТЕРИСТИКА\nспівробітник %s, %i р.н.\n", &s, year);

У результаті виконання цих команд до файла буде записано таке:

ХАРАКТЕРИСТИКА співробітник Іванов, 1985 р.н.

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

ція ftell():

long int ftell(FILE *stream);

A змінити поточну позицію файла можна за допомогою функції fseek(): int fseek(FILE *stream, long offset, int whence);

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

Константа

whence

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

SEEK_SET

0

початок файла

SEEK_CUR

1

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

SEEK_END

2

кінець файла

Наприклад, для переміщення поточної позиції на початок файла можна скористатися функцією

fseek(f, 0, SEEK_SET);

або

fseek(f, 0, 0);

За допомогою функцій ftell() та fseek() можна визначити сумарний обсяг пам‟яті у байтах, який займає файл. Для цього є достатньо переміститися на кінець файла:

fseek(f, 0, SEEK_END); int d = ftell(f);

Отже, послідовність роботи з файлом при записуванні даних є така: 1) Відкрити чи то створити файл fname.txt для записування (або для

зчитування й записування) у кінець файла: f=fopen("fname.txt", "at+");

388

Розділ 12

2) Записати дані до файла f однією з команд:

fputs(s, f); // Записування С-рядка s

//Форматоване записування С-рядка nazva, дійсного числа price і цілого числа kol fprintf(f, "%s %6.2f %i\n", nazva, price, kol);

3) Закрити файл: fclose(f);

Для зчитування даних з файла треба виконати таку послідовність дій:

1)Відкрити файл для зчитування f=fopen("fname.txt","rt+");

2)Здійснити зчитування даних з файла однією з команд:

fgets(s, 80, f); // Зчитування С-рядка s довжиною у 80 символів // Форматоване зчитування за адресами відповідних змінних fscanf(f, "%s %f %i\n", &nazva, &price, &kol);

3) Для зчитування послідовно всіх даних з файла треба організувати цикл з умовою, яка перевіряє досягання кінця файла, використовуючи чи то функцію feof(f), яка є ідентична до true при досяганні кінця файла, чи функцію зчитування даних, яка дає нульовий результат за досягання кінця файла. Можна використовувати інформацію про сумарний розмір файла і контролювати досягання кінця файла функцією ftell(f).

4) Закрити файл: fclose(f);

Уприкладах, що наводяться нижче, всі ці можливості послідовно подано

упрограмах.

Приклад 12.1 Увести відомості про товари до текстового файла: назву, ціну товару та кількість товару на складі. Прочитати дані з файла і віднайти відомості про товари, кількість яких є менше за 30. Визначити назву і розмір файла.

Форма додатка з результатами роботи матиме вигляд

Текст програми:

#include <stdio.h>

// Глобальне оголошення файлової змінної і визначення імені файла

FILE *f;

char s[]="proba.txt";

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]