cpp-oop (823968), страница 5
Текст из файла (страница 5)
«Объектно-ориентированное программирование на языке C++ в среде Microsoft Visual Studio 2008»27public:void f2 () const{f1();// нельзя использовать не const метод}};int main(){const B b;b.f2();getch();return 0;}В этом случае компилятор выдаст сообщение:error C2662: 'A::f1' : cannot convert 'this' pointer from 'constB' to 'A &'.Чтобы исправить ошибку во втором случае достаночно описать метод f1(), какметод для константнного объекта. Однако, если исходный класс, включающий этот метод– библиотечный, то доступ к его методам у нас отсутствует.Для предотвращения ошибок компиляции в обоих случаях можно выполнитьпреобразование const_cast, которое отменяет атрибут const объекта.В первом примере при вызове обычного метода для константного объекта восновной программе следует написать:const_cast<A*>(&a)->f();Во втором примере при вызове обычного метода из метода, описанного дляконстантного объекта, необходимо написать:ОглавлениеИванова Г.С., Ничушкина Т.Н.
«Объектно-ориентированное программирование на языке C++ в среде Microsoft Visual Studio 2008»28const_cast<B*>(this)->f1();К сожалению, в обоих случаях, используя преобразование const_cast, мыотменяем действие атрибута const, нарушая строгую защиту полей объекта отошибочных изменений.ОглавлениеИванова Г.С., Ничушкина Т.Н. «Объектно-ориентированное программирование на языке C++ в среде Microsoft Visual Studio 2008»291.5Классы с динамическими полямиДинамически выделять память под простые переменные, включаемые в объект,невыгодно, так как память расходуется не только для размещения самой переменной, но ипод информацию о выделении памяти. Поэтому динамическое распределение памятиобычно используют при работе со сложными структурами данных, особенно, если размербудущего поля заранее не известен (например, зависит от исходных данных иопределяется в процессе вычислений).Наиболее часто динамическое распределение памяти применяют в классах,использующих в качестве полей массивы, строки, структуры и их комбинации.
В этомслучае поле содержит указатель на переменную соответствующего типа. Указательполучает свое значение при выделении памяти под структуру данных, для которой онопределен. Освобождение этой памяти происходит по адресу в указателе в соответствии стипом переменной.При создании классов с динамическими полями необходимо учитывать большоеколичество особенностей. Так конструктор такого класса обычно осуществляет выделениеучастков памяти требуемого размера под переменные, адреса которых присваиваютсясоответствующим указателям, и обеспечивает контроль наличия доступной памяти.Деструктор класса соответственно должен при уничтожении объекта освобождатьраспределенную при конструировании память, поскольку автоматически эта память неосвобождается.При этом, если в классе предусмотрен неинициализирующий конструктор и впрограмме по какой-то причине динамические поля могут быть не созданы, то вдеструкторе нужно проверять факт выделения памяти под поля прежде, чем ееосвобождать,иначеосвобождения памяти.привыполнениипрограммыбудетсгенерированаошибкаКроме того, для инициализации полей объекта, созданного спомощью неинициализирующего конструктора, следует предусмотреть метод класса,который выделял бы память под поля такого объекта и инициализировал их.Дополнительные проблемы могут возникнуть при необходимости создания копииобъекта, например при передаче объекта в подпрограмму в качестве параметра-значения.В этом случае для реализации операции копирования класс с динамическими полямипомимо обычного конструктора, в котором будет выделяться память под размещениеОглавлениеИванова Г.С., Ничушкина Т.Н.
«Объектно-ориентированное программирование на языке C++ в среде Microsoft Visual Studio 2008»30динамического поля (например см. рис. 1.1, а), должен включать копирующийконструктор для корректного создания копии объекта. Это связано с тем, что прииспользовании стандартного копирующего конструктора адрес динамического поляпросто копируется, и после этого мы получаем два объекта, использующих одни и те жединамические поля (см. рис. 1.1, б). Соответственно при уничтожении копии объектадеструктором единое для двух объектов динамическое поле освобождается, оставляяосновной объект без динамического поля (см.
рис. 1.1, в). Теперь при любом обращении кэтому полю мы получим сообщение об ошибке.ОбъектОбъектПолеаПолеКопияобъектабОбъект?вРис. 1.1. Объект с динамическим полем:а – после конструирования; б – после некорректного создания копии;в – после удаления неверно созданной копииПример 1.13. Работа с объектом, включающим динамическое поле.#include <stdio.h>#include <conio.h>class TNum{public:int *pn;// указатель для адреса динамического поля// инициализирующий конструкторTNum(int n){pn=new int(n);}TNum(const TNum &Obj) // копирующий конструктор{pn=new int(*Obj.pn);~TNum()}// деструкторОглавлениеИванова Г.С., Ничушкина Т.Н.
«Объектно-ориентированное программирование на языке C++ в среде Microsoft Visual Studio 2008»31{delete pn;}};void Print(TNum b){// подпрограмма с параметром объектомprintf("%d ",*b.pn); }void main(){TNum A(1);Print(A);_getch();}ОглавлениеИванова Г.С., Ничушкина Т.Н. «Объектно-ориентированное программирование на языке C++ в среде Microsoft Visual Studio 2008»321.6Создание, инициализация и уничтожение динамических объектовКак было указано в разделе 1.1, по правилам языка С++ объекту некоторого классапамять может быть выделена статически – на этапе компиляции или динамически – вовремя выполнения программы. При этом выделение и освобождение участковосуществляется при выполнении операций new и delete.При выделении памяти для отдельных объектов используют следующие формыобращения к операции new:<Имя указателя на объект>=new <Имя класса>;или<Имя указателя на объект>=new <Имя класса>(<Список параметров>);Вторую форму применяют при наличии у конструктора списка параметров.Операция delete требует указания только имени объекта:delete <Имя указателя на объект>;Например:Num *a;// объявление указателя на объектa = new Num; // выделение памяти под неинициализированный объектdelete a;// освобождение выделенной памятиВ этом случае класс, под объекты которого выделяется память, должен содержатьконструктор, вызываемый без указания аргументов, и при наличии защищенных илискрытых полей – метод инициализации невидимых извне полей.Для создания инициализированных динамических объектов класс должен содержатьинициализирующий конструктор с соответствующими параметрами:Num *b;// объявление указателя на объектb = new Num(5); // выделение памяти под инициализированный объектОглавлениеИванова Г.С., Ничушкина Т.Н.
«Объектно-ориентированное программирование на языке C++ в среде Microsoft Visual Studio 2008»33delete b;При освобождении памяти автоматически будет вызван деструктор класса, а при егоотсутствии автоматически будет сгенерирован «пустой» деструктор вида:Num::~Num(){}Пример 1.14. Использование динамических объектов со статическими полями.#include <locale.h>#include <iostream>#include <iomanip>using namespace std;class TVector{private:int x,y,z;public:TVector(){} // неинициализирующий конструкторTVector(int ax,int ay,int az) // инициализирующий конструктор{x=ax;y=ay;z=az; }~TVector(){} // деструкторvoid PrintVec();void SetVec(int ax,int ay,int az) // инициализирующий метод{x=ax;y=ay;z=az; }};void TVector::PrintVec(){cout<<"Значение вектора: "<<setw(5)<<x<<" , ";cout<<setw(5)<<y<<" , "<<setw(5)<<z<<"\n";}void main(){setlocale(0,"russian");TVector *a,*b; // два указателя на объекты классаОглавлениеИванова Г.С., Ничушкина Т.Н.
«Объектно-ориентированное программирование на языке C++ в среде Microsoft Visual Studio 2008»34// выделяем память под динамические объекты классаa=new TVector(12,34,23); // инициализированный объектb=new TVector;// неинициализированный объектb->SetVec(10,45,56); // инициализация объектаa->PrintVec();// выводит: 12, 34, 23b->PrintVec();// выводит: 10, 45, 56// освобождаем память, выделенную под динамические объекты классаdelete a;// вызывает деструкторdelete b;// вызывает деструкторsystem("pause");}При работе с динамическими объектами следует помнить, что присваивание одногообъекта другому с помощью указателей сводится к копированию адреса: послевыполнения операции первый указатель содержит тот же адрес, что и второй.
Староезначение в первом указателе стирается, и, если он содержал адрес некоторого объекта, топамять, выделенная ранее под этот объект, остается занятой и более недоступной. Такаяошибка получила название «утечка памяти». К ошибке также приведет попыткаосвободить память по обоим указателям, так как один и тот же участок памяти,выделенный под объект, освобождается дважды. Во избежание этих ошибок необходимоучитывать и контролировать подобные ситуации.На класс, используемый для создания динамических объектов, можно возложитьзадачу управления памятью, если переопределить операции new и delete.
Особенно этополезно для классов, которые являются базовыми для многочисленных производныхклассов.Кроме этого в конструкторе или методе, выполняющем распределение памяти,следует предусмотреть контроль ее наличия. При обнаружении недостатка памятинеобходимо корректно выйти из создавшегося положения.ОглавлениеИванова Г.С., Ничушкина Т.Н. «Объектно-ориентированное программирование на языке C++ в среде Microsoft Visual Studio 2008»351.7Динамические массивы объектов и массивы указателей на объектыПо требованию решаемой задачи из динамических объектов могут создаватьсямассивы. Это можно сделать тремя способами:• создать динамический массив объектов –память под него выделяют однимнепрерывным фрагментом равным объему всех объектов массива (рис.
1.2, а), например:B mas[] = new B[n];• создать статический массив указателей на объекты и замем динамически выделитьпамять под элементы (рис. 1.2, б), например:B *mas[n]; // память под массив указателей выделена статическиfor (i=0; i<n; i++) mas[i]=new B; // выделение памяти под объекты• создать динамический массив указателей и затем также динамически выделитьпамять под элементы (рис. 1.2, в), например:B **mas=new B *[n]; // память под массив указателей выделена динамическиfor (i=0; i<n; i++) mas[i]=new B; // выделение памяти под объектыrmsaбвРис. 1.2.