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

Штерн В. - Основы C++. Методы программной инженерии - 2003

.pdf
Скачиваний:
240
Добавлен:
13.08.2013
Размер:
28.32 Mб
Скачать

240

Часть I * Введение в г1рогро^г«^ирование на'С-^Ф

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

Листинг 6.14. Использование динамического массива для считывания набора строк и их записи в файл на диске

#inclucle

<iostream>

 

 

/ / для объектов ifstream,

ofstream

#inclucle

<fsteam>

 

 

using

namespace std;

 

 

 

 

 

int main (void)

 

 

 

 

 

{

 

 

 

 

 

 

 

// короткий буфер для ввода

const int LEN = 8; char buf[LEN];

 

int cnt = 0;

 

 

 

// счетчик строк

 

ofStream f("data.out");

 

// новый файловый объект для вывода

cout «

" Введите данные (или нажмите Enter для завершения): п";

 

do {

 

 

 

 

 

 

 

/ /

начало цикла для ввода строк

int len = 0;

 

 

АО'

//начальная длина данных

char *data = new char[1]; data[0]

 

 

 

do {

 

 

 

 

 

/ /

начало внутреннего цикла для

 

cin.get(buf,LEN);

 

/ /

сегментов строк

 

 

 

/ /

получить следующий сегмент строки

 

len+=strlen(buf);

 

/ /

обновить общую длину

строки

 

char *temp = newchar[len+1];

 

 

 

 

 

strcpy(temp,data); strcat(temp,buf);

 

 

 

 

delete [] data; data = temp;

 

/ /

расширение для длинной строки

 

int ch = cin.peekO;

 

/ /

что находится слева в буфере?

 

if (ch == '\n' M

ch == EOF)

 

/ /

выход, если новая строка или EOF

 

 

{ ch = cin.getO; break; }

 

/ /

сначала удалить из ввода

 

} while

(true);

 

 

/ /

продолжать до новой строки

i f

(len

== 0)

break;

 

 

/ /

выход, если вводимая строка пуста

cout

«

"

строка " «

++cnt « ": "

« data «

endl;

 

 

f

«

data

«

endl;

 

 

/ /

сохранить данные в файле

delete

[ ]

data;

 

 

/ /

во избежании "утечек

памяти"

} while

(true);

 

 

/ /

продолжать до пустой

строки

cout

«

" Данные сохранены в файле data.out"

« endl;

 

 

return

0;

 

 

 

 

 

 

 

Что если диск переполнен или защищен от записи? Операция создания файла просто не выполняется. Никакой ошибки этапа выполнения не генерируется.

Один из способов решения этой проблемы состоит в вызове компонентной функции failO, возвращающей true, если предыдущая операция ввода-вывода была завершена неудачно (по любой причине), и false в случае успешного завер­ шения.

ofstream

f("data.out");

/ / открывает

выходной файл data.out

i f ( f . f a i l O )

/ / проверка на успешное выполнение,

отказ, если неудача

{ cout

« "Невозможно открыть файл" « endl;

return 0; }

 

Многие программисты полагают, что переполнение диска или диск, защищен­ ный от записи,— случаи редкие и данной вероятностью можно пренебречь. С этим трудно согласиться, поскольку такая программа не будет переносимой. В листин­ ге 6.14 мы этим пренебрегли, а напрасно.

Данные сохранены в файле data.out

Глава 6 ^ Управление памятью CJKJ

После успешного создания объекта ofstream его можно использовать для хра­ нения значений в физическом файле подобно тому, как объект cout используется для вывода значений на экран. Это означает, что при включении операции встав­ ки « битовая последовательность в памяти компьютера преобразуется в после­ довательность символов, представляюндих данные. Для символьных данных преобразование тривиально:

f « data « endl;

/ / записывает массив в выходной файл, а не в cout

Как видно, синтаксис доступа к данным здесь тот же, что и для объекта cout. Может ли операция вывода завершиться неудачно? Многие программисты думают, что если файл успешно открыт, нет никакой необходимости проверять каждую операцию. Это не так. Не забывайте, что речь идет о больших объемах данных. Даже современные диски большой емкости могут переполняться, не говоря уже о дискетах и дисках Zip. Поэтому нужно проверять успешное выполнение каждой операции.

f «

data «

endl;

/ /

сохранение данных в файле на диске

i f ( f . f a i l O )

/ /

проверка успешного

выполнения операции

{

cout «

"Диск переполнен,

вывод прекращен" « endl;

break; }

Введите данные (или нажмите Enter для завершения):

First line

line

 

1 строка 1: First

 

1 Second

line

line

 

строка

2: Second

 

This is the last line oftext

1

строка

3: This isthe last line oftext

На рис. 6.17 показан пример выполнения про­ граммы из листинга 6.14. Для представленных здесь введенных данных файл data, out содержит следуюидие строки

First line

Second line

This IS the last line of text

Рис. 6.17. Пример выполнения

программы

Когда файловый объект ofstream выходит из

области действия (в листинге 6.14 в конце функ­

из листинга 6.14

 

ции main О), он уничтожается. При этом переста­

 

 

ет суш,ествовать связь между файловым объектом

и физическим файлом, а физический файл закрывается. Исчезновение объекта ofSt ream не приводит к исчезновению физического файла.

Ввод из файла

Теперь рассмотрим примеры, когда программа использует данные, генерируе­ мые другой программой (например, текстовым редактором) или коммуникацион­ ной линией. Для этого можно определить объект класса if stream (input file stream — поток ввода из файла), представляющий входной файл.

Подобно классу ofstream, класс if stream определяется в заголовочном файле fSt ream, который должен включаться в исходный файл программы. Кроме того, аналогично классу ofstream, имя физического файла на диске используется как параметр объекта:

ifstream f("amounts.dat");

/ / открыть файл amounts.dat для ввода

Что если заданный файл не найден или его нельзя открыть, поскольку он ис­ пользуется другим приложением? Как и ofstream, объект ifstream все равно со­ здается, но его нельзя будет использовать для ввода. Любая попытка создания объекта ifstream должна сопровождаться проверкой на успешное выполнение:

ifstream f("amounts.dat");

/ /

открыть файл amounts.dat для ввода

i f ( f . f a i l O )

/ /

проверка на успешное выполнение

{cout « "Невозможно открыть файл" « endl; return 0; }

242

Часть ! ^ Введение в програ1У11ширование на C++

 

 

Если файловый объект if stream определен успешно, имя объекта ассоцииру­

 

ется с именем физического файла на диске. После этого можно использовать

 

операцию извлечения » для считывания данных в переменные программы. Вмес­

 

то объекта cin, представляющего клавиатуру, будет применяться определяемый

 

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

 

что и для объекта cin. Все другие функции, get(), getlineO, setf() и precision(),

 

доступны наряду с манипуляторами и применяются точно так же.

 

 

Стоит напомнить, что при использовании операции извлечения данных после­

 

довательность считываемых символов преобразуется в битовую последователь­

 

ность указанного типа (если такое преобразование возможно): int, double, char

 

и пр. Предшествующий пробел или символ новой строки (если он есть) операция

 

пропускает и ищет символы д/ш преобразования, останавливаясь, если находит

 

нечто, что не может быть частью значения (к примеру, символ новой строки).

 

Кроме того, можно считать данные в двоичной форме, а не как последовательность

 

символов. Двоичная форма более компактна, но вид данных в текстовом редакторе

 

или при выводе на экран будет нечитаемым.

 

Может ли неудачно завершиться операция ввода? Конечно. Более того, при

 

чтении данных из входного файла возможно неуспешное выполнение при достиже­

 

нии конца файла. Для проверки на конец файла можно использовать компонент­

 

ную функцию eof(),

которая

возвращает

true, если достигается конец файла,

 

и false

в противном случае:

 

 

 

do

 

 

/ /

выполнять,

пока EOF не даст неуспешное выполнение

 

double

amount;

/ /

локальная переменная для ввода

 

f

» amount;

/ /

получить из файла следующее значение double

 

i f

( f . e o f O ) break;

/ /

остановить

ввод, если больше нет данных

Обратите внимание, что предыдущее утверждение несколько туманно. Что зна­ чит "достигается конец файла"? Здесь два возможных варианта, нужно понимать разницу между ними. Когда программа читает данные из файла, условие "конец файла" может достигаться немедленно после чтения последней записи в нем. Другой вариант — попытка чтения после завершающей записи в файле.

В языках Ада и Паскаль используется первая интерпретация. В этих языках цикл do, считывающий данные из внешнего файла, выглядит следующим образом:

do {

 

i f

( f . e o f O ) break;

double amount;

f »

amount;

...

}

/ /

структура

цикла в языках Ада и Паскаль

/ /

остановить

ввод, если больше нет данных

/ /

локальная

переменная для ввода

/ /

получить из файла следующее значение double

/ /

обработка

чтения amount

В языках Кобол, C + + и Java применяется вторая интерпретация: условие "конец файла" возникает при попытке программы чтения после конца данных в файле. В этих языках структура цикла do, считывающего данные из внешнего файла, выглядит по-другому:

do {

 

/ /

структура

цикла в языках C++ и Java

double amount;

/ /

локальная

переменная для ввода

f »

amount;

/ /

получить из файла следующее значение double

i f

( f . e o f O ) break;

/ /

остановить

ввод,

если больше нет данных

. . .

}

/ /

обработка чтения

amount

Что произойдет, если сделать ошибку и использовать в программе C + +

пер­

вую структуру цикла, а не вторую? Последнее значение будет считываться из

файла и обрабатываться в остальной части цикла. На следующей итерации

eof()

вернет false и оператор f » amount; будет выполнен снова. При отсутствии данных возникнет условие "конец файла", но значение amount в памяти останется

Глава 6 • Управление памятью

243

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

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

#Избегайте подобной ситуации.

В листинге 6.15 приведена версия программы из листинга 6.13, считывающей данные из файла, а не с клавиатуры. Чтобы легче было сравнивать, здесь за­ комментированы операторы чтения данных с клавиатуры. Как видно, перейти от чтения данных с клавиатуры к чтению из файла совсем нетрудно. Результаты программы показаны на рис. 6.18.

Листинг 6.15. Использование связанного списка узлов в динамически распределяемой памяти для чтения данных из файла на диске

#include

<iostream>

 

#include

<iomanip>

 

#include

<fstream>

/ / для класса ifstream

using namespace std;

typedef

double Item;

 

struct

Node {

 

Item item;

 

Node* next; } ;

 

int main ()

 

{

 

//счетчик amount

int count =0;

Node *data=0, *last;

// указатели наначало и конец списка

ifstream f("amounts.dat");

// файл для чтения данных

if (f.failO)

{ cout « "Невозможно открыть файл" « endl; return 0; }

do {

 

//пока не встретится EOF

double amount;

 

// локальная переменная для ввода

// cout «

" Введите сумму (или Одля завершения): ";

 

// cin »

amount;

//получить от пользователя след. значение double

// if (amount ==0) break;

//получить следующее значение double из файла

f » amount;

if (f.eofO) break;

// создать вдинамической области новый узел

Node* q = new Node;

if (q ==0)

//проверка науспешное выполнение запроса

{ cout « "Нет памяти вдинамической области" «

endl; break; }

q->item =amount; q->next =NULL;

 

 

 

(data == 0 ? data : last->next) = q;

last=last->next; также годится

last =q;

II

count++;

 

 

 

} while (true);

 

" знач.\п";

 

cout «

"\n5cero загружено " « count «

 

if (count ==0) return 0;

// нет вывода, если нет ввода из файла

cout «

"\Номер Сумма Промежуточный

итог\п\п";

//печать заголовка

cout.setf(ios: ifixed);

 

// фиксированный формат для double

cout.precision(2);

//цифры после десятичной точки

I

244

I

Часть ! ^ Введение в програттирошаимв на C+-f

 

 

 

double total = 0;

 

 

 

// сумма для ввода значений

 

 

 

int i = 0;

 

 

 

 

//OK

 

 

 

for (Node *q = data; q != NULL; q = q ->next)

 

 

 

 

{ total +=q->item;

 

 

// накопление итоговой суммы

 

 

 

 

G0ut.setw(3); « i+1;

 

// номер транзакции

 

 

 

 

cout.setw

(10); «

q->item;

endl;

// значение транзакции

 

 

 

 

cout.setw

(11); «

total «

// текущий итог

 

 

 

 

}

 

 

= data;

 

 

 

 

 

Node *p = data, *r

 

 

 

 

 

while (p != 0)

 

 

 

// предотвращение "зависания" последнего узла

 

 

{ p = p-> next;

 

 

 

 

 

 

delete

r; г = p; }

 

 

 

 

 

 

return 0;

 

 

 

 

 

 

 

Всего загружено

4 значений

 

 

Файл amount.dat, который использовался для получения

 

 

 

результата для рис. 6.18, содержит следуюш,ие строки:

 

 

Номер

Сумма

Промежуточный итог

330.16

 

 

 

1

 

330.16

 

330.16

 

76.33

 

 

 

2

 

76.33

 

406.49

 

50.00

 

 

 

3

 

50.00

 

456.49

 

120.00

 

 

 

4

 

120.00

 

576.49

 

 

 

1

i

««..Ш

...«..•-....UM.U.

 

 

 

 

Многих программистов функция eof() вполне устраива­

Рис.

6.18. Результат

 

 

ет, однако она делает программу уязвимой в случае ошибок

 

 

в форматировании файла ввода.

 

 

 

 

выполнения

программы

 

 

 

 

из листинга

6.15

Предположим, что при наборе числа 50 в третьей строке

 

 

 

 

 

 

 

 

место О была нажата буква ' о'. Когда оператор f >> amount;

 

 

 

 

 

считает эту строку, он найдет там 5, а затем ' о'. Программа сделает вывод, что

 

 

 

 

 

вводится значение 5, оставляет символ ' о' в строке ввода и выполняет следующий

 

 

 

 

 

оператор. На следующей итерации оператор f >> amount находит ' о' в строке вво­

 

 

 

 

да, делает вывод, что ввод закончен, и завершается. Выполняется следующий опе­

 

 

 

 

 

ратор, и программа зацикливается.

 

 

 

 

 

 

Конечно, опечатки такого рода более вероятны при вводе с клавиатуры, чем из

 

 

 

 

 

файла, ведь файл можно проверить перед выполнением программы. Тем не менее

 

 

 

 

 

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

» ,

 

 

 

 

так как она слишком уязвима к ошибкам формата ввода. Зацикливание — непри­

 

 

 

 

ятное явление, как при вводе с клавиатуры, так и из файла. Вместо операции

»

 

 

 

 

они применяют для чтения символьных данных описанные ранее функции get()

 

 

 

 

и getlineO. Когда введенная строка находится в памяти, программа может проа­

 

 

 

 

нализировать данные и сгенерировать интеллектуальное сообщение об ошибке,

 

 

 

 

если они некорректны.

 

 

 

 

 

 

 

Еще один источник уязвимости — способ завершения файла. В приведенном

 

 

 

 

выше примере символ новой строки вводился после каждого значения, включая

 

 

 

 

последнее — 120. Если за последней записью в файле следует символ новой стро­

 

 

 

 

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

 

 

 

 

этим символом новой строки. В таком случае условие "конец файла" достигается,

 

 

 

 

только когда программа читает запись, следующую за последней записью.

 

 

 

 

 

 

Что происходит, если последний символ новой строки добавлен не был? Или

 

 

 

 

все значение набраны на одной строке без завершающего символа новой строки?

 

 

 

 

В этом случае функция извлечения данных считывает маркер конца файла и воз­

 

 

 

 

никает условие "конец файла". Функция eof (), которая вызывается после опера­

 

 

 

 

тора f »

amount;, возвращает true, и цикл завершается без обработки последнего

 

 

 

 

значения.

 

 

 

 

Глава 6 * Управление памятью

245

Это нехорошо. Программа должна быть написана так, чтобы ее поведение не изменялось в зависимости от того, поместит пользователь (или телекоммуника­ ционное ПО) вслед за последней записью в файле символ новой строки или нет. Чтобы устранить проблему, некоторые программисты избегают применять функ­ цию eof (). Вместо нее они используют старую добрую функцию fail():

do {

 

/ /

структура цикла C++ или Java

double amount;

/ /

локальная

переменная для ввода

f »

amount;

/ /

получить из файла следующее значение

i f

( f . f a i l O ) break;

/ /

остановить

ввод,

если больше нет данных

 

}

/ /

остальная

часть

цикла

Функция failO возвращает значение true, когда операция по какой-то причине (включая достижение конца файла) завершается неудачно. Если вместо 50 на­ брать 5о, то 5 счйтывается, а ' о' обнаруживается в потоке ввода при следующей итерации цикла. Оператор f>> amount; ничего не считывает, и функция fail() возвращает true. Цикл ввода завершается. Во-первых, преждевременное завер­ шение лучше, чем зацикливание. Во-вторых, после завершения цикла программа может проанализировать ситуацию и сгенерировать сообщение об ошибке в слу­ чае, если это произошло преждевременно.

Во втором примере, когда за значением 120 не следует символ новой строки, возникает условие "конец файла", но функция fail() возвращает false, так как значение 120 было считано оператором f » amount; корректно. Лишь на следую­ щем проходе цикла, когда программа пытается прочитать следующее значение, эта функция возвращает true. Следовательно, последнее значение в файле обра­ ботано корректно.

Файловые объекты ввода и вывода

Кроме if St ream и ofstream библиотека iostream в C++ определяет большое число других классов. В 99% случаев программисту не потребуется ничего знать о них. Здесь упоминается только один потоковый класс fstream, так как он объе­ диняет в себя характеристики классов if stream и ofstream.

При создании объектов типа if stream и ofstream режим открытия не указыва­ ется: if St ream создается по умолчанию для чтения, а ofstream — для записи. Для объектов класса fst ream задается режим открытия. Для этого при создании объек­ та используется второй аргумент:

fstream

of("data.out,ios::out);

/ /

выходной файл

fstream

infC'amounts.dat,ios::in);

/ /

входной файл

Режим ввода задается по умолчанию, его можно опустить. В число других доступных режимов входят ios: :арр (файл открывается для добавления данных в конец), ios: :binary (файл открывается в двоичном, а не в текстовом формате) и другие. Эти режимы реализованы как двоичные флаги. Если нужно, их можно комбинировать с помощью двоичной операции "включающее ИЛИ" — ('|').

fstream mystream("archive.dat", i o s : : i n | i o s : : o u t ) ;

/ / ввод-вывод

В C++ существует несколько способов проверки успешного или неуспешного выполнения операции с файлом. Кроме описанной выше функции fail(), можно применять функцию good():

fstream infC'amounts.dat",ios::in);

/ / входной файл

i f (! inf .goodO)

//еще один способ

{cout « "Невозможно открыть файл" « endl; return 0; }

/ / остановка, когда с объектом что-то не так

246

Часть I • Введен1^е в програ^1^ировани[е но С-ь+

Можно даже интерпретировать файловый объект как числовое значение. Когда операция завершается неудачно, оно равно О, а в случае успешного выполнения содержит нечто, отличное от нуля. Вот еш.е один пример проверки успешного открытия файла:

fstream inf("amounts.dat,ios::in);

/ /

входной файл

i f

(!inf )

/ /

еще один способ

{

count « "Невозможно открыть файл" «

endl; return 0; }

 

Тот же синтаксис можно применять для проверки успешного выполнения опера­ ций чтения и записи. Например, нетрудно подсчитать число символов в файле с помощ.ью функции get О с односимвольным параметром. Если операция чтения завершается неудачно (из-за достижения конца файла или по любой другой при­ чине), функция возвраш.ает О и это значение можно использовать для завершения цикла while.

int Count = 0; char oh; while (inf.get(ch))

count++;

cout « "Всего символов: " « count « endl;

Обычно закрывать файлы не требуется. Они закрываются, когда в конце об­ ласти действия уничтожается ассоциированный с файлом объект. Однако иногда возникает необходимость закрыть файл явно. Это делается с помош,ью функции close():

inf .closeO;

/ / закрыть файл

Такая необходимость может возникать, если требуется закрыть файл до того, как завершится область действия его файлового объекта, например когда откры­ вается несколько файлов и следуюш,ий файл открыть не удается. В такой ситуации до попытки восстановления или завершения программы лучше явно закрыть все открытые файлы. Может понадобиться закрывать файл и в том случае, если нежелательно хранить несколько файлов открытыми, например когда данные считываются из одного файла, обрабатываются в памяти, а затем результаты записываются в другой файл, чтобы их можно было использовать позднее.

В листинге 6.16 показана модифицированная программа из листинга 6.15. Кроме вывода результатов на экран, она сохраняет отчет в файле amounts, rep. Здесь путем сравнения файловых объектов с нулем проверяется успешность выполнения операций ввода-вывода. В С+-1- это общепринятый подход. Входной файл закрывается в конце ввода.

Листинг 6.16. Чтение из файла, вывод на экран и в выходной файл

#inclucle <iostream> #inclucle <iomanip> #inclucle <fstream> using namespace std;

typedef double Item; struct Node {

Item item; Node* next; } ;

int main ()

 

 

 

{

 

 

/ /

счетчик amount

int

count

= 0;

Node *data=0, *last;

/ /

указатели на начало и конец списка

fstream inf("amounts.dat",ios: :in);

/ /

файл для чтения данных

i f

(!inf )

{ cout « "Невозможно открыть файл" «

endl; return 0; }

 

 

 

 

Глава 6 • Управление памятью

247

do {

 

 

 

 

// пока не встретится EOF

 

double amount;

 

 

// локальная переменная для ввода

 

inf » amount;

 

 

// получить следующее значение double из файла

if (!inf)break;

 

 

// создать в динамической области новый узел,

Node* q = new Node;

"Her памяти вдинам

if (q == 0) {cout «

области" « endl; break; }

 

q->item = amount; q->next = NULL;

// заполнить данными

 

(data == 0 ? data

: last->next) = q;

 

 

 

 

last = q; count++;

 

 

 

 

 

 

} while (true);

 

 

 

// файл больше ненужен

 

inf .closeO;

 

 

 

 

fstream of("amounts, rep. ios::out);

 

// файл для записи данных

 

if (!of)

{cout << "Невозможно открыть выходной файл

' « endl; }

 

cout « "\пВсего загружено " « count << "знач.\п";

 

 

 

of « "\пВсего загружено " « count « " значДп";

// нет вывода, если нет ввода изфайла

if (count == 0) return 0;

 

cout « "\Номер Сумма Промежуточный итог\п\п";

// печать заголовка

 

of « "\Номер Сумма Промежуточный итог\п\п";

 

// печать заголавка

 

cout. setf(ios:ifixed); cout precision (2);

 

// точность для экрана

 

of.setf(ios::fixed); of.precision(2);

 

// точность для файла

 

double total = 0; int i = 0;

 

// промежуточный итог, счетчик строк

for (Node *q = data; q !=NULL; q = q ->next)

 

//ОК

 

{ total += q->item;

 

 

/ /

накопление итоговой суммы

 

cout «

setw(3) « i;

 

/ /

номер транзакции

 

cout «

setw(10) «

q->item;

 

/ /

значение транзакции

 

cout «

setw(11) «

total « endl;

 

/ /

текущий итог

 

of «

setw(3) «

++i « setw(10) « q->item

/ /

транзакция

 

of «

setw(11) «

total « endl; }

 

/ /

текущий итог

 

Node *p = data, *r = data;

 

 

 

 

while (p != 0)

 

 

 

 

 

 

{ p = p-> next;

 

 

/ / предотвращение "зависания" последнего узла

delete r; r = p; }

 

 

 

 

 

 

return 0;

 

 

 

 

 

 

 

Вы видите, операторы форматирования данных при выводе в файл те же, что и при выводе данных на экран. При вводе данных из рис. 6.18 программа из лис­ тинга 6.16 создает выходной файл со следующими данными:

Всего

загружено 4 значения

Номер

Сумма

Промежуточный итог

1

330.16

330.16

2

76.33

406.49

3

50.00

456.49

4

120.00

576.49

Это все, что нужно знать о работе с файлами при помощи библиотеки lost ream. Данная библиотека содержит значительно больше средств, чем здесь описано, но пока что не стоит вдаваться в детали. Сначала нужно познакомиться с классами и наследованием. На самом деле даже после изучения классов и наследования может не потребоваться знать больше того, о чем рассказывалось выше. Библио­ тека lost ream предлагает несколько способов выполнения одних и тех же дейст­ вий, и вовсе не обязательно изучать все их сразу. Вместо этого лучше обратить

248

Часть I * Введение в программирование на С^*

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

Итоги

в данной главе изложен достаточно сложный материал. Мы рассмотрели несколько примеров использования указателей в С+4-. Первый предусматривал применение указателей для ссылки на обычные переменные, распределяемые в стеке, и реализации альтернативного метода доступа к данным переменным через псевдонимы. За исключением передачи параметров через указатель (о чем подробнее рассказано в следуюш,их главах) этот метод не особенно полезен. Но, некоторые программисты считают, что подобная техника делает их программы более понятными. Поэтому нужно быть готовым к тому, чтобы иметь дело с такого рода операциями с указателями в унаследованном программном коде.

Второй тип использования указателей состоит в выделении памяти для отдель­ ных переменных не в стеке, а в динамически распределяемой области. Переменные в динамически распределяемой области памяти не имеют имен, и указатели — единственный способ доступа к их содержимому. В то же время применение пере­ менных в динамически распределяемой области вместо обычных переменных в стеке не дает практических преимуществ, его следует избегать. Некоторые по­ лагают, что такая техника дает возможность снизить вероятность переполнения стека, а потому нужно быть готовым к применению подобных операций с указате­ лями в унаследованных программах.

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

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

воперациях с указателями очень трудно обнаружить. Они не всегда проявляются

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

Если возникает необходимость использовать связанные списки, нужно рас­ смотреть альтернативные варианты. Один из них — динамические массивы, вто­ рой — применение стандартной библиотеки шаблонов с готовыми вариантами реализации таких структур, как связанные списки, очереди, деревья и пр. Подоб­ ные библиотеки позволяют программисту комбинировать гибкость динамического распределения памяти с простотой применения, так что нужно этим пользоваться.

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

Глава 6 • Управление памятью

249

где они могут находиться неопределенно яолго. После сохранения на данные не повлияет аварийное или нормальное завершение программы или сбой питания. Кроме того, что еще более важно, данные могут использоваться другой програм­ мой в другое время и/или в другом месте. Это существенно расширяет гибкость информационных систем.

Данная глава завершает обсуждение необъектно-ориентированных средств C++. В следующих главах мы начнем детальное обсуждение функций и классов C+ + , изучим создание объектно-ориентированных программ. Весьма впечатляющая тема! Как уже упоминалось, объектно-ориентированный подход является, вероятно, единственным, помогающим разработчикам создавать программы из относительно независимых компонентов и выражать свои идеи, понятные сопрово>вдающему приложение программисту, непосредственно в исходном коде. Такие навыки не вырабатываются автоматически, просто в процессе изучения языка. Нужно на­ деяться, что дальнейшее чтение книги поможет вам освоить это важное искусство.

Соседние файлы в предмете Программирование на C++