русс | укр

Языки программирования

ПаскальСиАссемблерJavaMatlabPhpHtmlJavaScriptCSSC#DelphiТурбо Пролог

Компьютерные сетиСистемное программное обеспечениеИнформационные технологииПрограммирование

Все о программировании


Linux Unix Алгоритмические языки Аналоговые и гибридные вычислительные устройства Архитектура микроконтроллеров Введение в разработку распределенных информационных систем Введение в численные методы Дискретная математика Информационное обслуживание пользователей Информация и моделирование в управлении производством Компьютерная графика Математическое и компьютерное моделирование Моделирование Нейрокомпьютеры Проектирование программ диагностики компьютерных систем и сетей Проектирование системных программ Системы счисления Теория статистики Теория оптимизации Уроки AutoCAD 3D Уроки базы данных Access Уроки Orcad Цифровые автоматы Шпаргалки по компьютеру Шпаргалки по программированию Экспертные системы Элементы теории информации

Закрыть файл.


Дата добавления: 2014-04-05; просмотров: 1107; Нарушение авторских прав


Пример работы с файлом

 

bool Запись_данных_в_текстовый_файл (char * FileName)

{

ofstream File; // Создали поток вывода для записи данных в файл

File.open ( FileName ); // Открыли файл

if ( ! File.is_open () ) // Проверили удалось ли открыть файл

{

cout << "Открыть файл не удалось! \n";

return 0; // Файл не открыт

}

for (int I = 1; I <= 10; ++ I) // Записываем 10 строк в файл

File << "Строка " << I << endl;

File.close (); // Закрываем файл

return 1;

}

 

bool Чтение_данных_из_текстового_файла (char * FileName)

{

ifstream File; // Создали поток ввода для чтения данных из файла

File.open ( FileName ); // Открыли файл

if ( !File.is_open () ) // Проверили удалось ли открыть файл

{

cout << “Открыть файл не удалось! \n” ;

return 0; // Файл не открыт

}

char S[100];

while ( ! File.eof () ) // Читаем все строки из файла и выводим их на экран

{

File.getline ( S, 100 );

cout << S << endl;

}

File.close (); // Закрываем файл

return 1;

}

 

int main ()

{

setlocale (0, "");

if ( Запись_данных_в_текстовый_файл ( "E:\\test.txt" ) )

Чтение_данных_из_текстового_файла( "E:\\test.txt" );

system("pause");

return 0;

}

 

11.2. Работа с файлами

Создание потока, открытие и закрытие файла

 

Либо последовательность из двух инструкций:

 

ofstream File; - создали поток

File.open ( "E:\\test.txt" ); - связали поток с файлом (открыли файл)

 

Либо так:

 

ofstream File ( "E:\\test.txt" ); - создали поток и открыли файл

 

После открытия файла необходимо обязательно проверить открылся ли файл.

Если файл открыть не удалось, то переменная потока (File) принимает значение false, если файл открыт – true. Следовательно, проверку успешного открытия файла можно выполнит так:



 

if ( ! File)

// Ошибка

 

Еще один способ – использовать функцию потока is_open (), которая также возвращает логическое значение в зависимости от результата операции открытия файла:

 

if ( ! File.is_open () )

// Ошибка

 

Файл закрывается с помощью функции потока close ():

 

File.close ();

 

Подробнее об открытии файла. Каждый поток использует свой вариант функции open. Их прототипы выглядят так:

 

void ifstream::open ( const char * FileName,

ios::openmode Mode = ios::in );

 

void ofstream::open ( const char * FileName,

ios::openmode Mode = ios::out | ios::trunc );

 

void fstream::open ( const char * FileName,

ios::openmode Mode = ios::in | ios::out );

 

Первый параметр определяет имя открываемого файла (представляет собой массив символов).

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

· ios::app – при открытии файла на запись (поток ofstream) обеспечивает добавление всех выводимых в файл данных в конец файла;

· ios::ate – обеспечивает начало поиска данных в файле начиная с конца файла;

· ios::in – файл открывается для чтения из него данных;

· ios::out – файл открывается для записи данных в файл;

· ios::binary – открытие файла в двоичном режиме (по умолчанию все файлы открываются в текстовом режиме);

· ios::trunc – содержимое открываемого файла уничтожается (его длина становится равной 0).

Эти флаги можно комбинировать с помощью побитовой операции ИЛИ (|).

Если файл открывается без использования функции open, эти флаги тоже можно использовать:

fstream File ( "E:\\test.txt", ios :: out | ios :: app ); - файл открывается на вывод с добавлением записываемых данных в конец файла.

 

Запись и чтение данных в текстовых файлах

Запись и чтение данных в текстовых файлах ничем не отличается от способов ввода-вывода данных с помощью потоков cin и cout. Методы форматирования вывода и вводы данных остаются такими же (флаги форматирования, манипуляторы, функции потоков).

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

Запись и чтение данных в двоичном режиме

Для работы с файлом в двоичном режиме его необходимо открыть с флагом ios :: binary.

Чтение и запись двоичных данных в этом режиме можно осуществлять двумя способами:

· по одному байту – функции файловых потоков get () и put ();

· блокам определенной длины - функции файловых потоков read () и write ().

Один из вариантов прототипов функций get () и put () (чаще всего используемый) выглядит так:

 

ifstream & get (char & ch);

ofstream & put (char ch);

 

Функция get () берет один символ из потока ввода, помещает его в символьный параметр ch и возвращает ссылку на поток ввода. Когда достигается конец файла, значение ссылки на поток становится равным 0.

Функция put () помещает символ ch в поток вывода и возвращает ссылку на поток вывода.

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

 

int main ()

{

setlocale (0, "");

 

// Запись массива А в_файл "E:\test.dat"

ofstream o_File; // Создали поток вывода для записи данных в файл

o_File.open ( "E:\\test.dat", ios::binary ); // Открыли файл

if (! o_File.is_open()) // Проверили, удалось ли открыть файл

{

cout << "Открыть файл не удалось! \n" ;

return 0;

}

// Записываем данные из массива А в файл

int A[5] = {1, 2, 3, 4, 5};

char *ch = (char *) A; // ch – ссылка на массив А, как на массив символов (байт)

for (int I = 0; I < sizeof(A); ++ I)

o_File.put(ch[I]);

o_File.close(); // Закрываем файл

 

// Чтение данных из_файла "E:\test.dat" в массив В;

ifstream i_File; // Создали поток ввода для чтения данных из файла

i_File.open ( "E:\\test.dat", ios::binary ); // Открыли файл

if (! i_File.is_open()) // Проверили, удалось ли открыть файл

{

cout << "Открыть файл не удалось! \n" ;

return 0;

}

// Читаем данные из файла в массив B

int B[5];

ch = (char *) B; // ch – ссылка на массив В, как на массив символов (байт)

int I = 0;

while (i_File)

i_File.get(ch[I++]);

// Предыдущий цикл можно записать короче, например, так:

// while (i_File.get(*ch++);

i_File.close(); // Закрываем файл

 

// Выводим массив В на экран

for (I = 0; I < 5; ++ I)

cout << B[I] << " ";

cout << endl;

 

 

system("pause");

return 0;

}

 

А вот как выглядит та же программа при использовании блочных функций чтения и записи (read () и write ()) в двоичном режиме:

 

int main ()

{

setlocale (0, "");

 

// Запись массива А в_файл "E:\test.dat"

ofstream o_File; // Создали поток вывода для записи данных в файл

o_File.open ( "E:\\test.dat", ios::binary ); // Открыли файл

if ( ! o_File.is_open() ) // Проверили, удалось ли открыть файл

{

cout << "Открыть файл не удалось! \n" ;

return 0;

}

int A[5] = {1, 2, 3, 4, 5};

o_File.write ( (char *) A, sizeof (A) ); // Записываем данные из массива А в файл

o_File.close (); // Закрываем файл

 

// Чтение данных из_файла "E:\test.dat" в массив В;

ifstream i_File; // Создали поток ввода для чтения данных из файла

i_File.open ( "E:\\test.dat", ios::binary ); // Открыли файл

if ( ! i_File.is_open() ) // Проверили, удалось ли открыть файл

{

cout << "Открыть файл не удалось! \n" ;

return 0;

}

 

int B[5];

i_File.read ( (char *) B, sizeof (B) ); // Читаем данные из файла в массив B

i_File.close (); // Закрываем файл

 

// Выводим массив В на экран

for (int I = 0; I < 5; ++ I)

cout << B[I] << " ";

cout << endl;

 

 

system("pause");

return 0;

}

 

Функции read () и write () имеют следующие прототипы:

 

ifstream & read (char * buf, streamsize n);

ofstream & write (const char * buf, streamsize n);

 

Первый параметр этих функций определяет адрес буфера (некоторого массива символов - байт) для чтения или записи данных в соответствующий файловый поток. Второй параметр задает количество символов – байт, которые необходимо взять из потока или записать в поток (тип данных streamsize – целый тип данных). Размер буфера должен соответствовать значению второго параметра.

Функция write () обеспечивает запись из буфера, адрес которого указан в первом параметре функции, n символов данных в поток вывода и возвращает ссылку на поток.

Функция read () обеспечивает запись из потока ввода n символов данных в память по адресу, указанному в первом параметре buf. При достижении конца файла функция возвращает ссылку на поток, равную 0, а фактическое количество взятых из потока символов может быть меньше, чем значение n второго параметра (буфер заполнен не полностью). Фактическое количество считанных из потока ввода символов после выполнения последней операции чтения можно определить с помощью функции потока ввода gcount(). Следующий пример иллюстрирует использование функций блочного чтения и записи данных из произвольного файла.

 

void FileToScr (char * FileName)

{

fstream File ( FileName, ios::in | ios::binary );

if ( !File ) // Проверили удалось ли открыть файл

{

cout << "Открыть файл не удалось! \n" ;

return;

}

char Buf [1024];

do

{

File.read ( Buf, sizeof ( Buf ) ); // Читаем данные из файла в буфер

cout.write ( Buf, File.gcount () ); // Выводим содержимое буфера на экран

}

while ( File );

cout << endl;

File.close ();

}

 

Функция FileToScr () обеспечивает чтение любого указанного файла в двоичном режиме и вывод его на экран в символьном виде с использованием функций блочного чтения и записи. Этот пример показывает, что функции блочного чтения и записи применимы и к стандартным потокам cin и cout (впрочем, как и функции get () и put ()). Ключевым моментом здесь является использование функции потока ввода gcount (). С помощью этой функции удается точно определить количество символов, которое необходимо вывести на экран из буфера после очередной операции чтения из файла.

Как обнаружить конец файла?

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

Обнаружить конец файла при чтении данных позволяет функция потока:

 

bool eof ();

 

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

Другой способ определения конца файла основан на том, что функции чтения данных из файла (get (), read ()) при достижении конца файла возвращают нулевую ссылку на поток. Этот способ иллюстрируется в последнем примере.

Прямой доступ при работе с файлами

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

Поддержка этих методов основана на понятии указателя позиции в файле (или просто – указателя). В языке C++ поддерживается работа с двумя указателями. Один указатель (get-указатель) определяет место в файле, откуда должны быть прочитаны данные. Другой – put-указатель определяет место для записи данных. При выполнении операций чтения-записи соответствующие указатели автоматически перемещаются в файле на расстояние, равное объему прочитанных или записанных данных, тем самым подготавливая следующую операцию чтения или записи.

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

Перемещение указателей в потоках осуществляется с помощью соответствующих потоковых функций:

· ifsnteam &seekg ( off_type offset, seekdir way ); - перемещение get-указателя;

· ofsnteam &seekp ( off_type offset, seekdir way ); - перемещение put-указателя.

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

Тип данных seekdir параметра way имеет три возможных значения:

· ios :: beg – начало файла;

· ios :: cur – текущая позиция;

· ios :: end – конец файла.

Существуют перегруженные функции позиционирования указателей, обеспечивающие абсолютную адресацию указателя в заданную позицию файла:

· ifsnteam & seekg ( pos_type pos ); - перемещение get-указателя в позицию pos;

· ofsnteam & seekp ( pos_type pos ); - перемещение put-указателя в позицию pos.

Тип данных pos_type является целочисленным беззнаковым типом. Позиция 0 соответствует началу файла.

Определить текущие позиции указателей можно с помощью следующих потоковых функций:

pos_type tellg (); - возвращает текущую позицию get-указателя;

pos_type tellp (); - возвращает текущую позицию put-указателя;

В качестве иллюстрации напишем функцию, возвращающую размер в байтах заданного файла:

 

unsigned FileSize ( char *FileName)

{

ifstream File ( FileName, ios::in | ios::binary );

if ( !File ) // Проверили удалось ли открыть файл

{

cout << "Файл не найден! \n" ;

return 0;

}

File.seekg (0, ios::end );

unsigned Size = File.tellg();

File.close ();

return Size;

}

Статус потоков ввода-вывода

Потоки ввода-вывода после выполнения каждой операции ввода-вывода находятся в определенном состоянии, характеризующимся, так называемым, статусом потока. Статус потока ввода-вывода представляет собой перечисление, имеющее следующие значения:

· ios :: goodbit – нормальное состояние потока – ошибки отсутствуют;

· ios :: eofbit – достигнут конец файла;

· ios :: failbit – исправимая ошибка;

· ios :: bedbit – неисправимая ошибка.

Значение статуса после завершения очередной операции ввода-вывода можно узнать с помощью функции rdstate (), которая возвращает значение одного из перечисленных выше значений:

 

fstream File (……..);

while (File.rdstate () = ios :: goodbit)

{

// Выполняем очередную операцию с файлом

}

 

Еще один способ узнать значение статуса потока ввода-вывода состоит в использовании одной из следующих функций потока, возвращающих значение true, при установке соответствующего флага статуса:

· bool good ()

· bool eof () – эту функцию мы уже использовали раньше

· bool fail ()

· bool bed ()

 

Например:

 

fstream File (……..);

while ( File.good () )

{

// Выполняем очередную операцию с файлом

}

 

Если после выполнения очередной операции ввода-вывода установлен статус потока отличающийся от ios :: goodbit, дальнейшее выполнение операций по работе с потоком может стать невозможным. Для попытки продолжения работы с потоком в этом случае следует воспользоваться потоковой функцией clear (). Эта функция очищает статус потока ввода-вывода и устанавливает флаг статуса ios :: goodbit.

Некоторые другие функции управления потоками ввода-вывода

Функция потока ввода peek () позволяет прочитать из потока очередной символ, не удаляя его из потока.

Функция putback (char c) возвращает в поток ввода символ c.

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

Примеры по работе с файлами

Пример 1. Для выполнения этой программы скопируйте ее текст в новый проект.

 

//////////////////////////////////

// Эта программа предназначена для иллюстрации работы с файлами в двоичном режиме.

// Обеспечивает:

// 1. Ввод и добавление в файл Persons.dat сведений о людях.

// 2. Чтение данных из файла Persons.dat и вывод их на экран.

// Данные о людях (персонах) включают в себя фамилию, имя и год рождения,

// оформленные в виде структуры t_Person.

// Различного рода запросы, требующие ответа "да" или "нет", реализуются

// с помощью функции MessageBox() из заголовочного файла <windows.h>.

// Эта функция возвращает целочисленные значения 6 и 7, означающие "да" или "нет"

// соответственно.

// Для очистки экрана в программе используется функция system("cls"), которая

// выполняет команду операционной системы cls - очистка экрана.

// Для русификации консольного ввода-вывода используются потоки консольного

// ввода-вывода wcin и wcout, а вместо типа char двухбайтные символы типа wchar_t

// и кодовая страница .866, устанавливаемая в главной функции с помощью

// функции setlocale(LC_ALL, ".866")

////////////////////////////////////

 

 

#include "stdafx.h"

#include <iostream> // Для работы со стандартными потоками ввода-вывода

#include <fstream> // Для работы с файлами

#include <windows.h> // Для использования функции MessageBox()

#include <iomanip> // Для использования функции setw()

 

using namespace std;

 

bool FileExists ( char *FileName)

// Возвращает true, если файл FileName существует

{

ifstream File ( FileName ); // Открываем файл для чтения

bool Fl = (bool)File; // Фиксируем результат открытия файла в переменной Fl

File.close(); // Закрываем файл

return Fl; // Возвращаем значение true, если файл существует

}

 

struct t_Person // Тип данных для "персоны"

{

wchar_t Fam[20]; // Фамилия

wchar_t Name[20]; // Имя

int Year; // Год рождения

};

 

void ReadConPerson(t_Person &P)

// Ввод данных персоны с клавиатуры

{

int W = 17; // Ширина поля вывода для подсказки

wcout << setw(W) << L"Фамилия: "; // Выводим подсказку для фамилии

wcin >> P.Fam; // Вводим фамилию

wcout << setw(W) << L"Имя: "; // Выводим подсказку для имени

wcin >> P.Name; // Вводим имя

wcout << setw(W) << L"Год рождения: "; // Выводим подсказку для года рождения

wcin >> P.Year; // Вводим год рождения

}

 

void WriteConPerson(t_Person P)

// Вывод данных персоны на экран

{

int W = 17; // Ширина поля вывода для подсказки

wcout << setw(W) << L"Фамилия: " << P.Fam << endl; // Выводим фамилию

wcout << setw(W) << L"Имя: " << P.Name << endl; // Выводим имя

wcout << setw(W) << L"Год рождения: " << P.Year << endl; // Выводим год рождения

}

 

void WriteFile(char FN[])

// Чтение данных с клавиатуры и их запись в файл с именем FN

{

t_Person P; // Переменная для персоны

fstream File(FN, ios::out | ios::binary | ios::app); // Открываем файл для добавления данных в двоичном режиме

do

// Цикл до тех пор, пока мы не откажемся от ввода данных

{

system("cls"); // Чистим экран

ReadConPerson(P); // Вводим данные с клавиатуры в переменную P

File.write((char *) &P, sizeof(P)); // Записываем данные из переменной Р в файл

}

while (MessageBox(NULL, L"Продолжим ввод?", L"Запрос", MB_YESNO | MB_ICONQUESTION | MB_TOPMOST | MB_DEFBUTTON1) != 7);

File.close(); // Закрываем файл

}

 

void ReadFile(char FN[])

// Чтение данных из файла с именем FN и вывод их на экран

{

t_Person P; // Переменная для персоны

fstream File(FN, ios::in | ios::binary); // Открываем файл для чтения данных в двоичном режиме

if (!File) // Если файл не удалось открыть, выходим из функции

return;

do

// Цикл до тех пор, пока мы не откажемся от вывода данных

{

int Count = 1; // Счетчик выведенных персон

File.clear(); // Очищаем статус состояния потока ввода

File.seekg(0); // Устанавливаем указатель чтения файла в начало файла

do

// Цикл до тех пор, пока не закончился файл или мы не откажемся от вывода данных

{

File.read((char *) &P, sizeof(P)); // Читаем из файла в переменную Р данные об очередной персоне

if (!File.eof()) // Если не достигнут конец файла, то выводим на экран данные о персоне

{

system("cls"); // Очищаем экран

wcout << L"Персона " << Count++ << ":\n"; // Выводим на экран подсказку с номером Count персоны и увеличиваем

// значение Count на единицу

wcout << "----------\n"; // Подчеркиваем подсказку

WriteConPerson(P); // Выводим на экран значение переменной Р с данными о персоне

}

}

while (!File.eof() &&

(MessageBox(NULL, L"Продолжим?", L"Запрос", MB_YESNO | MB_ICONQUESTION | MB_TOPMOST | MB_DEFBUTTON1) != 7));

}

while (MessageBox(NULL, L"Вывод закончен.\nНачнем сначала?", L"Запрос", MB_YESNO | MB_ICONQUESTION | MB_TOPMOST | MB_DEFBUTTON2) != 7);

File.close(); // Закрываем файл

}

 

int _tmain(int argc, _TCHAR* argv[])

{

setlocale (LC_ALL, ".866"); // Устанавливаем русифицированную кодовую страницу для консоли

char FN[] = "Persons.dat"; // Имя файла для данных персон

do

// Цикл до тех пор, пока мы не откажемся от продолжения работы

{

bool Ok = FileExists(FN); // В переменной Ок фиксируем наличие (отсутствие) файла с именем FN

if ( (!Ok) || (Ok &&

(MessageBox(NULL, L"Будем добавлять данные в файл?", L"Запрос", MB_YESNO | MB_ICONQUESTION | MB_TOPMOST | MB_DEFBUTTON2) == 6)))

// Если файл отсутствует или он имеется, и мы хотим добавить в него данные, то вводим данные с клавиатуры и записываем их в файл

WriteFile(FN); // Чтение данных с клавиатуры и их запись в файл с именем FN

 

if ( MessageBox(NULL, L"Будем читать данные из файла?", L"Запрос", MB_YESNO | MB_ICONQUESTION | MB_TOPMOST | MB_DEFBUTTON1) == 6)

// Если мы хотим прочитать данные из файла, то читаем их из файла и выводим на экран

ReadFile(FN); // Чтение данных из файла с именем FN и вывод их на экран

}

while (MessageBox(NULL, L"Закончить работу?", L"Запрос", MB_YESNO | MB_ICONSTOP | MB_TOPMOST | MB_DEFBUTTON2) != 6);

return 0;

}

12. Работа с динамической памятью

Распределение памяти при работе программы

Схема распределения памяти под программу показана на следующем рисунке:

 

  Большие адреса Меньшие адреса Стек
Динамическая область памяти (heap - куча)
Область глобальных данных
Код программы

 

Область кода программы предназначена для хранения инструкций функций программы, обеспечивающих обработку данных. Данные в программе представляются переменными и константами. Для хранения глобальных данных (существуют в течение всего времени работы программы) предназначена область глобальных данных. Стек программы используется при вызове функций для передачи параметров и хранения локальных данных.

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

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

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

Динамическое выделение и освобождение памяти в стиле C++

Для динамического управления памятью в языке C++ используются две инструкции new и delete. Формат этих инструкций:

 

<Переменная-указатель> = new <Тип данных переменной-указателя>

delete <Переменная-указатель>

 

Инструкция new выделяет в динамической области участок памяти, достаточный для размещения данных, тип которых определяется типом данных переменной-указателя, и возвращает адрес этого участка. Этот адрес присваивается переменной-указателю. Например:

 

double *p; // Переменная-указатель на тип double

p = new double; // Выделение памяти

 

или так:

 

double *p = new double;

 

В этом примере инструкция new выделяет в динамической области участок памяти объемом sizeof (double) и присваивает адрес этого участка переменной-указателю p. Дальнейшая работа с переменной-указателем осуществляется как с обычным указателем на тип данных double. Например:

 

*p = 3.14;

cout << *p * 2 << endl; // На экран выведено значение 6.28

cin >> *p; // Вводим с клавиатуры некоторое вещественное значение

cout << *p << endl; // На экран выведено значение, введенное с клавиатуры

 

Размер динамической области памяти ограничен, поэтому при многократном последовательном использовании инструкции new может создаться ситуация, при которой попытка выделения очередного участка памяти с помощью операции new завершится неудачей (возникнет ошибка, связанная с переполнением динамической области памяти). Для того чтобы избежать подобных ошибок, необходимо принудительно освобождать динамическую память с помощью инструкции delete:

 

delete p;

 

Инструкция delete возвращает участок памяти по адресу p в список свободной памяти, и в дальнейшем этот участок памяти может быть использован повторно для динамического размещения других данных.

 

Замечание. Инструкции new и delete это парные инструкции, то есть они всегда должны использоваться совместно – каждой инструкции new должна соответствовать инструкция delete. Динамическая область памяти автоматически освобождается только при завершении программы, поэтому неконтролируемое использование инструкции new может привести к переполнению динамической области памяти и, следовательно, к ошибкам в работе программы.

 

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

 

struct t_Person // Тип данных для "персоны"

{

char Fam[20]; // Фамилия

char Name[20]; // Имя

int Year; // Год рождения

};

 

setlocale ( 0, "" ); // Русификация консоли

 

t_Person *p = new t_Person; // Создаем структуру в динамической памяти

 

strcpy ( (*p).Fam, "Иванов" ); // Заносим фамилию

strcpy ( (*p).Name, "Иван" ); // Заносим имя

(*p).Year = 1995; // Заносим год рождения

 

cout << "Фамилия: " << (*p).Fam << endl; // Выводим фамилию

cout << "Имя: " << (*p). Name << endl; // Выводим имя

cout << "Год рождения: " << (*p).Year << endl; // Выводим год рождения

 

delete p; // Освобождаем память

 

Для обращения к отдельным полям структуры через переменную-указатель мы использовали следующие конструкции:

 

(*p).Fam, (*p).Name, (*p).Year

 

Здесь (*p) обеспечивает разыменование указателя (получение данных персоны, расположенных в памяти по адресу p), а затем с помощью оператора “точка” осуществляется обращение к данным соответствующего поля.

Существует другой способ доступа к полям структур через указатель на структуру с помощью оператора “стрелка” (не требующий предварительного разыменования указателя). Это делается так:

 

p -> Fam, p -> Name, p -> Year

 

То есть следующий вариант той же программы будет также корректным:

 

struct t_Person // Тип данных для "персоны"

{

char Fam[20]; // Фамилия

char Name[20]; // Имя

int Year; // Год рождения

};

 

setlocale ( 0, "" ); // Русификация консоли

 

t_Person *p = new t_Person; // Создаем структуру в динамической памяти

 

strcpy (p -> Fam, "Иванов" ); // Заносим фамилию

strcpy (p -> Name, "Иван" ); // Заносим имя

p -> Year = 1995; // Заносим год рождения

 

cout << "Фамилия: " << p -> Fam << endl; // Выводим фамилию

cout << "Имя: " << p -> Name << endl; // Выводим имя

cout << "Год рождения: " << p -> Year << endl; // Выводим год рождения

 

delete p; // Освобождаем память

 

Для некоторых типов данных одновременно с динамическим выделением памяти можно осуществлять и ее инициализацию. Например:

 

double *p = new double (3.14); // Инициализация значением 3.14

cout << *p << endl; // На экран выведено значение 3.14

Динамическое выделение и освобождение памяти в стиле C

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

В языке C отсутствуют инструкции new и delete. Вместо них для управления динамической памятью используются библиотечные функции, имеющие следующие прототипы:

 

void *malloc ( size_t size );

void *calloc( size_t num, size_t size );

void free( void *memblock );

void *realloc( void *memblock, size_t size );

 

Функция malloc выделяет в динамической области size байт памяти (тип данных size_t представляет собой разновидность беззнакового целого типа данных) и возвращает адрес этого участка в виде нетипизированного указателя (void *). Поскольку возвращаемый указатель не привязан ни к какому типу данных, при работе с ним потребуется явное приведение типов данных (см. пример ниже).

Функция calloc выделяет в динамической области size * num байт памяти и возвращает адрес этого участка в виде не типизированного указателя (void *).

Функция free освобождает участок динамической памяти по адресу memblock и возвращает его в список свободной памяти для повторного использования.

Функция realloc позволяет изменить размер (уменьшить или увеличить) ранее выделенной по адресу memblock памяти, установив новый размер выделенного участка равным size байт. При увеличении размера выделенного участка данные, которые хранились в старом участке, копируются в новый участок памяти. При уменьшении объема выделенного участка, данные которые хранились в нем, усекаются до нового размера. Функция возвращает нетипизированный указатель на область памяти нового размера.

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

 

struct t_Person // Тип данных для "персоны"

{

char Fam[20]; // Фамилия

char Name[20]; // Имя

int Year; // Год рождения

};

 

setlocale ( 0, "" ); // Русификация консоли

 

t_Person *p; // Определяем указатель на тип t_Person

p = ( t_Person * ) malloc ( sizeof ( t_Person ) ); // Выделяем память и используем

// приведение нетипизированного указателя к указателю на тип данных t_Person

 

 

strcpy ( p -> Fam, "Иванов" ); // Заносим фамилию

strcpy ( p -> Name, "Иван" ); // Заносим имя

p -> Year = 1995; // Заносим год рождения

 

cout << "Фамилия: " << p -> Fam << endl; // Выводим фамилию

cout << "Имя: " << p -> Name << endl; // Выводим имя

cout << "Год рождения: " << p -> Year << endl; // Выводим год рождения

 

free ( p ); // Освобождаем память

 

Для переделки программы под стиль C потребовались всего два изменения в программе (эти места выделены красным цветом).

В одной и той же программе не рекомендуется смешивать использование “старого” и “нового” стилей работы с динамической памятью (возможны проблемы с совместимостью).

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

Возможные ошибки при работе с динамической памятью

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

При неудачной попытке выделить память с помощью инструкции new возникает так называемая исключительная ситуация (или исключение). Обработкой исключений в C++ занимается специальная подсистема обработки исключительных ситуаций, использование и изучение которой выходит за рамки настоящего курса лекций. Впрочем, подобные ошибки в рассматриваемых нами примерах и программах вряд ли будут возникать.

Поведение функций malloc и calloc с точки зрения обнаружения таких ошибок более “разумно”. В случае невозможности выделить требуемый объем памяти в динамической области эти функции возвращают нулевой указатель. Контролируя это значение можно избежать ошибок в работе программы:

 

double *p = (double *) malloc (sizeof (double) ); // Пытаемся выделить память

if ( !p ) // Память выделить не удалось

{

// Принимаем меры по исправлению ситуации

}

// Продолжаем работу

 

Однако имеется другой вариант инструкции new, который работает так же, как и функции malloc и calloc (также возвращает нулевой указатель). Вот как его использовать:

 

double *p = new ( nothrow ) double; // Пытаемся выделить память

if ( !p ) // Память выделить не удалось

{

// Принимаем меры по исправлению ситуации

}

// Продолжаем работу

Другая категория ошибок называется “утечкой памяти”. Например:

 

int * p; // Объявляем указатель на целый тип данных

p = new int; // Выделяем память по некоторому адресу p

………

p = new int; // Еще раз выделяем память, и ее адрес записываем опять в p

 

В этом примере повторное присвоение переменной p другого адреса нового участка памяти приводит к потере адреса участка памяти, выделенного первой инструкцией new. Этот “забытый” участок памяти будет занят до конца работы программы, и его нельзя ни освободить, ни использовать для хранения данных – говорят, что произошла утечка памяти. Такие “утечки” могут привести к тому, что опять произойдет переполнение динамической области памяти. Для недопущения подобных ошибок необходимо внимательно следить за своевременным освобождением памяти, на которую ссылается переменная-указатель.

Еще одна категория ошибок связана с попытками обращения к динамической памяти через указатели, не инициализированные с помощью инструкции new и функциями malloc и calloc, а также при попытке обращения к динамической памяти через указатель после освобождения памяти с помощью инструкции delete или функции free.

Динамические массивы

Для того чтобы создать в динамической области некоторый объект необходима одна обычная (не динамическая переменная) переменная-указатель. Сколько таких объектов нам понадобится для одновременной обработки – столько необходимо иметь обычных переменных-указателей. Таким образом, проблема “задач неопределенной размерности” созданием одиночных динамических объектов решена быть не может.

Решить эту проблему поможет возможность создавать в динамической области памяти массивы объектов с таким количеством элементов, которое необходимо в данный момент работы программы – то есть создание динамических массивов. Действительно, для представления массива требуется всего одна переменная-указатель, а в самом массиве, на который ссылается этот указатель, может быть столько элементов, сколько требуется в данный момент времени.

Сначала рассмотрим одномерные динамические массивы.

Для создания одномерного динамического массива, элементами которого являются, например, действительные числа, используется следующий синтаксис инструкции new:

 

double *Arr = new double [1000];

 

Здесь в динамической области памяти будет выделено пространство на 1000 значений типа double, и адрес этой области будет присвоен переменной-указателю Arr. Таким образом, переменная-указатель Arr, как и переменная для обычного массива, будет содержать адрес первого элемента массива.

Освободить динамическую область от этого массива можно так:

 

delete [ ] Arr;

 

После этого участок памяти объемом 1000 * sizeof ( double ) байт будет возвращен в список свободной памяти и может быть повторно использован для размещения других динамических объектов.

С помощью функций malloc и calloc тот же самый одномерный динамический массив создается так:

 

double *Arr = (double *) malloc(1000 * sizeof (double ) );

 

или

 

double *Arr = (double *) сalloc(1000, sizeof (double ) );

 

Освобождение памяти в этих случаях осуществляется с помощью функции free:

 

Free ( Arr );

 

Работа с одномерным динамическим массивом осуществляется так же, как и с обычным. Рассмотрим пример, в котором создадим динамический массив целых с количеством элементов, введенном с клавиатуры; заполним его случайными значениями в диапазоне от 1 до 100; подсчитаем и выведем на экран среднее значение всех элементов этого массива:

 

int n; // Количество элементов массива

cin >> n; // Вводим количество элементов массива с клавиатуры

int *Arr = new int [ n ]; // Создаем массив Arr целых чисел на n элементов

for ( int i = 0; i < n; ++ i) // Заполняем массив случайными значениями

Arr [ i ] = rand ( ) % 100 + 1;

int Sum = 0; // Сумма элементов массива

for ( int i = 0; i < n; ++ i ) // Подсчитываем сумму элементов массива

Sum += Arr [ i ];

cout << (double) Sum / n << endl; // Выводим на экран среднее значение

delete [ ] Arr; // Освобождаем память

 

Очень часто в процессе работы программы требуется изменять размеры уже созданных и заполненных данными массивов. Общий алгоритм решения этой задачи таков:

 

Создать исходный массив размерности N1 и заполнить его данными;

2. создать промежуточный массив размерности N2 (пусть N2 > N1);

Скопировать данные из исходного массива в промежуточный массив;

Освободить память от исходного массива;

Переменной-указателю исходного массива присвоить значение переменной-указателя промежуточного массива;



<== предыдущая лекция | следующая лекция ==>
Определить переменные этого типа для хранения соответствующих данных в памяти. | заполнить новые элементы массива данными.


Карта сайта Карта сайта укр


Уроки php mysql Программирование

Онлайн система счисления Калькулятор онлайн обычный Инженерный калькулятор онлайн Замена русских букв на английские для вебмастеров Замена русских букв на английские

Аппаратное и программное обеспечение Графика и компьютерная сфера Интегрированная геоинформационная система Интернет Компьютер Комплектующие компьютера Лекции Методы и средства измерений неэлектрических величин Обслуживание компьютерных и периферийных устройств Операционные системы Параллельное программирование Проектирование электронных средств Периферийные устройства Полезные ресурсы для программистов Программы для программистов Статьи для программистов Cтруктура и организация данных


 


Не нашли то, что искали? Google вам в помощь!

 
 

© life-prog.ru При использовании материалов прямая ссылка на сайт обязательна.

Генерация страницы за: 0.186 сек.