- •Овсянник в.Н. Лабораторные работы по курсу «Объектно-ориентированное программирование»
- •1.Интегрированная среда mvs-2010
- •1.1.Методические указания
- •2.Массивушки и подпрограммочки
- •Прочитайте это задание и методические указания до конца, прежде чем терзать клавиатуру, соседа, преподавателя и пр. Сущности
- •Попробуйте сдать работу, предварительно тщательно протестировав ее на предмет отсутствия хомутов
- •2.1.Методические указания
- •3.Поиск экстремумов с ограничениями или «Брачное агенство»
- •Постановка задачи
- •Задание
- •Листинг файла mAgency.Cpp (с главной функцией)
- •Листинг файла Lib.H
- •Листинг файла Lib.Cpp
- •4.Задача «куча камней»
- •4.1.Постановка задачи.
- •4.2.Методические указания.
- •Некоторые примеры разделения камней на две кучи, которые должна решать ваша программа
- •5.Разработка простого класса
- •5.1.Задание
- •5.2.Описание вариантов заданий
- •5.3.Драгоценные методические указания
- •6.Класс вектор
- •6.1.Пример класса tVector
- •6.2.Класс tVector с перегруженными операциями
- •7.Наследование классов
- •7.1.Задание
- •7.2.Методическая помощь
- •7.3.Описание самых легких вариантов заданий
- •7.4.Контроль качества выполненной работы
- •8.Приложение с окном вида
- •9.Абстрактные классы и виртуальные функции
- •9.1.Задание
- •9.2.Методические указания
- •Void PrintClassNamе1(cBasе *pb) // параметр-указатель
- •Void PrintClassNamе2(cBasе &b) // параметр-ссылка
- •Void PrintClassNamе3(cFirst f) // параметр-значение
- •9.3.Варианты заданий
- •10.Разработка класса контейнера
- •10.1.Задание
- •10.2.Описание вариантов заданий
- •10.3.Методические указания
- •12.Перегрузка векторных и матричных операций
- •12.1.Задание
- •12.2.Бесценные методические указания
- •12.3.Некоторые особенности перегрузки операций
- •12.4.Варианты заданий
- •13.Программирование односвязного списка
- •13.1.Задание
- •13.2.Описание вариантов заданий
- •14.Приложение, основанное на модальном диалоговом окне
- •14.1.Нудные методические указания Модальные и немодальные диалоговые окна
- •Ресурсы и элементы управления
- •Сценарий создания приложения
- •Разбор приложения
- •Усовершенствование приложения
- •15.Приложения
- •15.1.Виды сортировок
- •Классификация сортировок
- •Сортировка массивов
- •15.2.Алгоритм сортировки включением
- •Пример сортировки с помощью прямого включения
- •15.3.Сортировка Шелла
- •Список литературы
12.3.Некоторые особенности перегрузки операций
При реализации операций-функций, особенно для классов, член-данные которых размещаются в динамической памяти, полезно проанализировать порядок вызовов конструкторов, конструкторов копирования и деструкторов.
С этой целью, во-первых, добавим в класс собственный конструктор копирования исключительно для того, чтобы отследить момент его вызова. Во-вторых, добавим в конструкторы и деструктор макросы TRACE, с помощью которых будем отслеживать вызовы конструкторов и деструктора.
Рассмотрим сначала класс комплексных чисел:
class Complex
{
double Re,Im;
char * ObjName; // для хранения имени объекта
public:
Complex(double iRe=0, double iIm=0, char * ObjName="")
{
Re=iRe; Im=iIm; this->ObjName=ObjName;
TRACE("Complex ObjName=%s this=%d\n",ObjName,(int)this);
}
~Complex()
{
if((!ObjName) || ((int)ObjName==0xcccccccc)) ObjName="Bad_Ptr";
TRACE("~Complex ObjName=%s this=%d\n",ObjName,(int)this);
}
Complex(const Complex & Org)
{
TRACE("CopyCTR Org.ObjName=%s this=%d\n",Org.ObjName,(int)this);
Re=Org.Re; Im=Org.Im; ObjName=Org.ObjName;
}
Complex & operator=(const Complex & Org)
{
TRACE("operator= Org.ObjName=%s this=%d\n",Org.ObjName,(int)this);
Re=Org.Re; Im=Org.Im;
return *this;
}
Complex operator +(Complex &X)
{
return Complex(Re+X.Re,Im+X.Im,"Return operator+");
}
Complex operator -(Complex X);
Complex operator -=(Complex X)
{ Re-=X.Re; Im-=X.Im; return *this;}
friend Complex operator *(Complex X,Complex Y);
friend ostream & operator <<(ostream &Out, Complex X)
{ return Out<< '('<<X.Re<<"+i"<<X.Im<<')';}
};
Complex Complex::operator -(Complex X)
{return Complex(Re-X.Re,Im-X.Im);}
Complex operator *(Complex X,Complex Y)
{
return Complex(X.Re*Y.Re-X.Im*Y.Im, X.Re*Y.Im+Y.Re*X.Im);
}
Проверка корректности значения указателя ObjName в деструкторе выполнена для того, чтобы избежать ошибки времени выполнения в некоторых сложных случаях.
Теперь внимательно проанализируйте результаты выполнения следующего программного кода. В комментариях приведены результаты работы макросов TRACE.
void main()
{
{
Complex a(1.23,4,"a"), b(2.34,5,"b"), c(0,0,"C");
//Complex ObjName=a this=1244980
//Complex ObjName=b this=1244948
//Complex ObjName=C this=1244916
c=a+b;
//CopyCTR Org.ObjName=b this=1244604
//Complex ObjName=Return operator+ this=1244628
//~Complex ObjName=b this=1244604
//operator= Org.ObjName=Return operator+ this=1244824
//~Complex ObjName=Return operator+ this=1244628
//CopyCTR Org.ObjName=C this=1244624
//~Complex ObjName=C this=1244624
// Если параметр операции-функции сложения объявить ссылкой
// Complex operator +(Complex &X),
// то получим такую последовательность вызовов методов:
//Complex ObjName=Return operator+ this=1244648
//operator= Org.ObjName=Return operator+ this=1244916
//~Complex ObjName=Return operator+ this=1244648
//CopyCTR Org.ObjName=C this=1244644
//~Complex ObjName=C this=1244644
// Если операцию-функцию присвоить объявить как
// Complex & operator=(const Complex & Org)
// (и при этом операцию-функцию сложения объявить как
// Complex operator +(Complex &X),
// то получим такую последовательность вызовов методов:
//Complex ObjName=Return operator+ this=1244680
//operator= Org.ObjName=Return operator+ this=1244916
//~Complex ObjName=Return operator+ this=1244680
cout<<c<<endl;
//CopyCTR Org.ObjName=C this=1244624
//~Complex ObjName=C this=1244624
}
//~Complex ObjName=C this=1244916
//~Complex ObjName=b this=1244948
//~Complex ObjName=a this=1244980
}
Для того чтобы отследить порядок вызова конструкторов и деструктора, необходимо выполнить трассировку программы, причем с заходом в функции.
Какие можно сделать основные выводы из результатов выполнения этой программы, точнее, реализации операций-функций?
Во-первых, если объект передается в функцию как параметр-значение, то его копия создается с помощью конструктора копирования. Это значит, что если реализация конструктора копирования по умолчанию нас не устраивает, то требуется разработать его корректную реализацию. Например, если среди член-данных класса есть указатели и для них выделяется динамическая память, то требуется разработать корректный конструктор копирования. Также стоит отметить, что конструктор копирования вызывается и при возврате объекта из функции с помощью оператора return.
В качестве еще одного примера рассмотрим класс CArr, предназначенный для обработки одномерных массивов.
|
|
|
|
Реализация операции вычитания массивов, как и других полезных операций, отдана на ваше усмотрение. После выполнения этой работы использование объектов класса может быть реализовано следующим образом:
void main()
{
setlocale(LC_ALL,"rus");
srand( (unsigned)time( NULL ) );
int N=7;
CArr A(N,"A");
CArr B(N,"B"),*C;
for(int i=0;i<N;i++)
{
double Val=(double)rand()/RAND_MAX*10;
A.setElem(i,Val);
Val=(double)rand()/RAND_MAX*10;
B.setElem(i,Val); // можно так
B[i]=Val; // а можно и так
}
cout<<"Массив А"<<endl<<A<<endl<<endl;
cout<<"Массив B"<<endl<<B<<endl<<endl;
C=A+B;
cout<<"Массив C=A+B"<<endl<<*C<<endl<<endl;
delete C;
CArr D(N,"D");
D=A-B;
cout<<"Массив D=A-B"<<endl<<D <<endl<<endl;
cout<<"Массив A-B" <<endl<<(A-B)<<endl<<endl;
}
