Пример класса VECTOR с перегруженными операциями Краткие теоретические положения Введение. Объектно-ориентированное программирование (ООП) родилось и получило широкое распространение именно благодаря попыткам разрешения следующих проблем, возникавших в процессе проектирования и разработки больших программных комплексов: 1. Развитие языков и методов программирования не успевало за все более растущими потребностями в прикладных программах. Единственным реальным способом уменьшения времени разработки был метод многократного использования разработанного программного обеспечения (ПО), то есть проектирования нового программного комплекса на базе разработанных и отлаженных ранее модулей. 2. Ускорение разработки программного обеспечения требовало решения проблемы упрощения его сопровождения и модификации. 3. Не все задачи поддаются алгоритмическому описанию по методу структурного программирования. 4. В целях упрощения процесса проектирования необходимо было приближать структуру программы к структуре решаемой задачи. Создание метода объектно-ориентированного программирования решило эти проблемы. Класс – это набор методов и свойств, описывающих поведение объекта. Объект – это конкретный представитель определенного класса. Таким образом, класс подобен стандартному элементу управлению в визуальных средах разработки, но без графического интерфейса. Класс используется аналогично, но его нельзя программировать визуально, как это делается в случае элементов управления. Программу, в которой созданные классы близко совпадают с понятиями прикладной задачи, обычно легче понять, модифицировать и отладить, чем программу, в которой этого нет. Инкапсуляция является одним из основных понятий ООП. Инкапсуляция – объединение в одном месте описания всех методов и данных класса. Инкапсуляция облегчает понимание работы программы, а также ее отладку и модификацию, так как внутренняя реализация используемых объектов редко интересует разработчика программного проекта; главное, чтобы объект обеспечивал функции, которые он должен предоставить. Кроме того, инкапсуляция защищает данные и методы класса от внешнего вмешательства или неправильного использования. Другими словами, инкапсуляция означает сокрытие деталей реализации класса внутри него самого. Это одно из важнейших понятий ООП, так как в любом крупном проекте постоянные изменения приводят к тому, что в любой момент времени детали реализации класса могут измениться и все разработчики, которые использовали данные детали реализации, обнаружат свой код в неработающем состоянии. Инкапсуляция кода внутри класса позволяет разрабатывать код, работающий при любом изменении деталей реализации отдельных классов. Взаимодействие с объектом происходит через интерфейс, а детали реализации остаются инкапсулированными. В объектах интерфейсами являются свойства, методы и события. Только они предоставляются данным объектом в распоряжение других объектов. Таким образом, инкапсуляция обеспечивает использование объекта, не зная, как он реализован внутри. Объявление класса Заголовок определения.Определение класса начинается с ключевых слов class, struct или union. Правда, union применяется крайне редко. И структуры, и классы, и объединения относятся к “классовым” типам C++. Спецификации доступа.Ключевые слова private и public называются спецификаторами доступа. Спецификатор private означает, что элементы данных и элементы-функции, размещенные под ним, доступны только функциям-элементам данного класса. Это так называемый закрытый доступ. Спецификатор public означает, что размещенные под ним элементы доступны как данному классу, так и функциям других классов и вообще любым функциям программы через представитель класса. Есть еще спецификатор защищенного доступа protected, означающий, что элементы в помеченном им разделе доступны не только в данном классе, но и для функций-элементов классов, производных от него. Структуры, классы и объединения.Типы, определяемые с ключевыми словами struct, class и union, являются классами. Отличия их сводятся к следующему: · Структуры и классы отличаются только доступом по умолчанию. Элементы, не помеченные никаким из спецификаторов, в структурах имеют доступ public (открытый); в классах — private (закрытый). · В объединениях по умолчанию принимается открытый доступ. · Элементы (разделы) объединения, как и в С, перекрываются, т. е. начинаются с одного и того же места в памяти. Элементы данных и элементы-функции.Элементы данных класса совершенно аналогичны элементам структур в С, за исключением того, что для них специфицирован определенный тип доступа. Объявления элементов-функций аналогичны прототипам обычных функций. Интерфейс и реализация. Реализация класса, располагаемая часто в отдельном файле, содержит код его функций-элементов, а также некоторые элементы данных, называемые статическими. Элементы-функции. Функция-элемент класса объявляется внутри определения класса. Там же может быть расположено и определение тела функции. В этом случае функцию-элемент называют встроенной и компилятор будет генерировать ее встроенное расширение на месте вызова. Если определение функции располагается вне тела класса, то к ее имени добавляется префикс, состоящий из имени класса и операции разрешения области действия (::). Например: void Point::SetPoint(int, int){... // определение функции} Класс как область действия. В языке С выделялось несколько различных типов области действия: глобальная, файл, функция, блок. В C++'вводится новый вид области действия — класс. Имена элементов класса расположены в области действия класса, и к ним можно обращаться из функций-элементов данного класса. Кроме того, получить доступ к элементам класса можно в следующих случаях: · Для существующего представителя класса с помощью операции доступа к элементу (точки). · Через указатель на существующий представитель класса с помощью операции косвенного доступа к элементу (стрелки). · С помощью префикса, состоящего из имени файла и операции разрешения области действия (::). Доступ к элементам данных. Поскольку функции-элементы класса находятся в его области действия, они могут обращаться к элементам данных непосредственно по имени. Обычные функции или функции-элементы других классов могут обращаться к элементам существующего представителя класса с помощью операций “.” или “->”. Пример: class Time {public: int hour; int min; }; int main() {Time start; // Объявление локального объекта класса Time. Time *pTime = &start; //Создаем указатель на локальный объект. start.hour = 17; // Операция доступа к элементу. pTime->min = 30; // Косвенный доступ к элементу. return 0;} Вызов функций-элементов класса.Совершенно аналогично тому, что имеет место в случае элементов-данных, функции-элементы класса могут вызываться функциями-элементами того же класса просто по имени. Обычные функции и элементы других классов могут вызывать функции-элементы данного класса для существующих его представителей с помощью операций “ . ” или “->” (через указатель). Приведенный ниже пример это иллюстрирует. #include <stdio.h> class Time {int hour; int min; public: void SetTime(int h, int m) {hour = h; min = m; } void ShowTime(void) {printf("Time: %02d:%02d\n", hour, min); } }; int main() {Time start; Time *pStart = &start; int hr, min; start.SetTime(17, 15); // Вызов элемента для объекта start. pStart~>ShowTime(); // вызов элемента через указатель на объект. return 0;} Указатель this.Любая функция-элемент класса, не являющаяся статической имеет доступ к объекту, для которого она вызвана, через посредство ключевого слова this. Типом this является имя_класса*. Например: class Dummy {void SomeFunc(void) {...}; public: Dummy(); }; Dummy::Dummy() {SomeFunc(); this->SomeFunc(); (*this).SomeFunc(); } В этом примере каждый оператор конструктора вызывает одну и ту же функцию SomeFunc (). Поскольку функции-элементы могут обращаться к элементам класса просто по имени, подобное использование указателя this довольно бессмысленно. Это ключевое слово чаще всего применяется для возврата из функции-элемента указателя или ссылки на текущий объект. Перегрузка операций Язык C++ позволяет переопределять для классов существующие обозначения операций. Это называется перегрузкой операций. Благодаря ей класс можно сделать таким, что он будет вести себя подобно встроенному типу. Например, в классе можно перегрузить такие операци: +, -, *, /, =, >, <, >=, <=, += и другие. Функции-операции, реализующие перегрузку операций, имеют вид тип_возвращаемого_значения operator знак_операции([операнды]) {тело функции} Если функция является элементом класса, то первый операнд соответствующей операции будет самим объектом, для которого вызвана функция-операция. В случае одноместной операции список параметров будет пуст. Для двухместных операций функция будет иметь один параметр, соответствующий второму операнду. Если функция-операция не является элементом класса, она будет иметь один параметр в случае одноместной операции и два — в случае двухместной. Для перегрузки операций существуют такие правила: · Приоритет и правила ассоциации для перегруженных операций остаются теми же самыми, что и для операций над встроенными типами. · Нельзя изменить поведение операции по отношению к встроенному типу. · Функция-операция должна быть либо элементом класса, либо иметь один или несколько параметров типа класса. · Функция-операция не может иметь аргументов по умолчанию. · Обычно операцию присваивания определяют так, чтобы она возвращала ссылку на свой объект. В этом случае сохраняется семантика арифметических присваивании, допускающая последовательные присваивания в выражении (т. е. с = b = а;). · Невозможно изменить синтаксис перегруженных операций. Одноместные операции должны быть одноместными, а двухместные — двухместными. · Нельзя изобретать новые обозначения операций. Возможные операции ограничиваются тем списком, что приведен в начале этого раздела. · Желательно сохранять смысл перегружаемой операции. Пример класса VECTOR с перегруженными операциями Определим класс для работы с трехмерными векторами в евклидовом пространстве. В этом классе будут использоваться перегруженные операции сложения (знак +) и присваивания (знак =) как операции с трехмерными векторами. Сумма двух векторов будет вычисляться как вектор, компоненты которого равны суммам соответствующих компонент слагаемых. Операция = будет выполнять, как и положено, покомпонентное присваивание векторов. Вначале создадим файл Vector.h: #include <iostream> using namespace std; class vector {int x,y,z ; public: vector operator+(vector t); vector operator=(vector t); void show(void); void assign(int , int , int); }; void vector::assign(int mx, int my, int mz) { x=mx; y=my; z=mz; } void vector::show(void) {cout<<x<<", "; cout<<y<<", "; cout<<z<<"\n"; } vector vector::operator=(vector t) {x=t.x; y=t.y; z=t.z; return *this; } vector vector::operator+(vector t) {vector temp; temp.x=x+t.x; temp.y=y+t.y; temp.z=z+t.z; return temp; } Затем создадим файл Vector.cpp: #include <iostream> #include <conio.h> #include "vector.h" using namespace std; int main() {vector a,b,c; cout<<"******************************\n"; a.assign(1,2,3); b.assign(10,10,10); cout<<"Vector a: "; a.show(); cout<<"Vector b: "; b.show(); c=a+b; cout<<"Vector c: "; c.show(); c=a+b+c; cout<<"Vector c: "; c.show(); c=b=a; cout<<"Vector c: "; c.show(); cout<<"Vector b: "; b.show(); getch(); return 0; } Окно IDE Visual Studio с проектом должно выглядеть примерно следующим образом:  После запуска программы получим на экране: ****************************** Vector a: 1, 2, 3 Vector b: 10, 10, 10 Vector c: 11, 12, 13 Vector c: 22, 24, 26 Vector c: 1, 2, 3 Vector b: 1, 2, 3 Конструкторы и деструкторы. Как известно, в классе могут быть объявлены две специальные функции-элемента – конструктор и деструктор. Конструктор отвечает за создание представителей данного класса. Его объявление записывается без типа возвращаемого значения и ничего не возвращает, а имя должно совпадать с именем класса. Конструктор может иметь любые параметры, необходимые для конструирования, т. е. создания, нового представителя класса. Если конструктор не определен, компилятор генерирует конструктор по умолчанию, который просто выделяет память, необходимую для размещения представителя класса. С помощью конструктора можно при создании объекта элементам данных класса присвоить некоторые начальные значения, определяемые в программе. Это реализуется путем передачи аргументов конструктору объекта. Конструкторы можно перегружать, чтобы обеспечить различные варианты задания начальных значений объектов класса. Конструктор может содержать значения аргументов по умолчанию. Для каждого класса может существовать только один конструктор с умолчанием. Когда объявляется объект класса, между именем объекта и точкой с запятой в скобках можно указать список инициализации элементов. Эти начальные значения передаются в конструктор класса при вызове конструктора. Деструктор отвечает за уничтожение представителей класса. Если деструктор не определен, генерируется деструктор по умолчанию, который просто возвращает системе занимаемую объектом память. Деструктор объявляется без типа возвращаемого значения, ничего не возвращает и не имеет параметров. Имя деструктора совпадает с именем класса, но перед ним ставится символ ~ (тильда). Класс может иметь только один деструктор, то есть перегрузка деструктора запрещена. Представленные в качестве примеров классы не были до сих пор обеспечены деструкторами. На самом деле, деструкторы редко используются с простыми классами. Деструкторы имеют смысл в классах, использующих динамическое распределение памяти под объекты (например, для массивов или строк). Если для класса не определено никакого конструктора, компилятор создает сам конструктор с умолчанием. Такой конструктор не задает никаких начальных значений, так что после создания объекта нет никакой гарантии, что он находится в непротиворечивом состоянии. Области памяти, занятые данными базовых типов, таких как int, float, double и др., выделяются и освобождаются автоматически и не нуждаются в помощи конструктора и деструктора. Пример. Усовершенствуем класс vector. Объявим в нем конструктор с аргументами по умолчанию (конструктор с умолчанием). Задание в конструкторе аргументов по умолчанию позволяет гарантировать, что объект будет находиться в непротиворечивом состоянии, даже если в вызове конструктора не указаны никакие значения. Вначале создадим файл Vector.h: #include <iostream> using namespace std; class vector {int x,y,z ; public: vector operator+(vector t); vector operator=(vector t); void show(void); vector(int , int , int);//конструктор с параметрами ~vector(); //деструктор }; //определение конструктора с аргументами по умолчанию //для задания начальных значений закрытых данных vector::vector(int mx=0, int my=0, int mz=0) {x=mx; y=my; z=mz; cout<< "Вектор с координатами ("<<x<<","<<y<<","<<z<< ") создан.\n"; } //определение деструктора vector::~vector() {cout<< "Вектор с координатами ("<<x<<","<<y<<","<<z<< ") разрушен.\n"; } void vector::show(void) {cout<<x<<", "; cout<<y<<", "; cout<<z<<"\n"; } vector vector::operator=(vector t) {x=t.x; y=t.y; z=t.z; return *this; } vector vector::operator+(vector t) {vector temp; temp.x=x+t.x; temp.y=y+t.y; temp.z=z+t.z; return temp; } Затем создадим файл Vector.cpp: #include <iostream> #include "vector1.h" using namespace std; int main() { cout<<"******************************\n"; //создадим три объекта класса vector vector a(1,2,3), b(10,10,10),c; cout<<"Vector a: "; a.show(); cout<<"Vector b: "; b.show(); c=a+b; cout<<"Vector c: "; c.show(); c=a+b+c; cout<<"Vector c: "; c.show(); c=b=a; cout<<"Vector c: "; c.show(); cout<<"Vector b: "; b.show(); } Данная программа создает три объекта класса vector: два объекта со всеми тремя указанными аргументами, а один (с) – с аргументами по умолчанию. |