Перечислим некоторые правила перегрузки операций:
· можно перегружать любые операции С++, за исключением «.», «.*», «?:», «::», sizeof;
· не допускается перегрузка операций для стандартных (определенных в С++) типов данных;
· нельзя изменять синтаксис операций. Например, операция ++ перегружается как унарная (имеющая один операнд), операция + может переружаться как унарная, так и как бинарная;
· функции–операции не могут иметь параметры со значениями по умолчанию;
· при перегрузке бинарной операции перегружаемая функция, как метод класса, должна иметь один параметр. Вторым является неявный параметр – константный указатель this на текущий объект(левый операнд);
· при перегрузке бинарных операций: + - * / % ++ -- в большинстве случаев перегружаемая функция возвращает объект класса;
· чтобы сохранить семантику присваивания, операция = должна перегружаться как функция, принимающая один параметр – ссылку на присваиваемый объект класса, и возвращающая ссылку на объект класса, для которого она вызвана;
· операция индексирования должна перегружаться как функция, возвращаящая ссылку на элемент из множества значений.
Пример 4_2. Класс «Вектор1» с конструкторами и перегрузкой операций
Файл vector 1. h // определение класса вектор,
// его данных и методов
#ifndef _VECTOR1_H //предотвращение многократного
#define _VECTOR1_H //включения заголовочного файла
#include <iostream>
using std::endl;
using std::cout;
class VECTOR1 // Класс для работы с геометрическими векторами
{
//Данные
private:
int x, y; // Координаты конца вектора
//Методы
public:
// Конструктор вызывается при создании нового объекта
// с инициализацией по умолчанию или значениями параметров
VECTOR1 (const int &x1=0, const int &y1=0): x(x1), y(y1){ }
// можно и так:
//VECTOR (const int &x1=0, const int &y1=0) { x = x1; y = y1; }
// Конструктор копирования - вызывается при создании
// нового объекта с инициализацией другим объектом,
// при передаче параметра с типом класса по значению и
// при возврате значения объекта с типом класса из
// метода класса по return.
// Подставляемый метод
VECTOR1 (const VECTOR1 ©)//copy - источник копирования
{
x = copy.x; y=copy.y;
}
// Присвоить значения координатам вектора: подставляемый метод.
void Assign(const int &x1, const int &y1)
{
x = x1; y = y1;
return;
}
// Перегрузка префиксного унарного оператора "++" с
// помощью метода класса: прибавляет к вектору
// единичный вектор и возвращает сам вектор (не ссылку!)
// Метод не имеет явно передаваемых аргументов, так
// как при вызове неявно получает адрес объекта (this),
// для которого он вызван. Подставляемый метод
VECTOR1 operator++(void)
{
x++; y++;
return *this; // Возвращается сам вектор после увеличения
}
// Перегрузка постфиксного унарного оператора "++" с
// помощью метода класса: возвращает значение
// вектора до прибавления единичного вектора. В
// методе "operator++(int)" параметр метода
// игнорируется. Метод является подставляемым
VECTOR1 operator++(int)
{
VECTOR1 vec_tmp = *this;
x++; y++;
return vec_tmp; // Возвращает значение вектора до увеличения
}
// Перегрузка бинарного оператора "+" с помощью метода
// класса: суммирует два вектора и возвращает их
// сумму (не ссылку!). Модификатор const гарантирует, что данный метод
// не изменит значений членов объекта, для которого он вызван.
// Определение будет дано далее.
VECTOR1 operator+(const VECTOR1 &) const;
// Перегрузка бинарной операции "=": подставляемый
// метод (при его наличии можно корректно
// использовать цепочки присваиваний).
// В примере этот метод можно было не записывать, так
// как в примере не используются цепочки присваиваний и
// нет полей указателей на области динамической
// памяти
VECTOR1& operator = (const VECTOR1 &v) // Возвращает ссылку на объект
// this - левый операнд
// v - правый операнд
{
this->x = v.x; this->y = v.y;
// А можно и так: x = v.x; y = v.y;
return *this;
}
// Печать значений координат вектора: объявление метода класса.
// Определение будет дано далее.
void print(void) const;
};
void VECTOR1::print(void) const
{
cout<<"\n Coordinates of a vector: x= "
<<x<<" y= "<<y<<endl;
return;
}
// Оператор "+" бинарный, поэтому метод "operator+"
// долж e н иметь помимо скрытого (this) еще один явно
// передаваемый параметр
VECTOR1 VECTOR1:: operator+(const VECTOR1 &vec) const
{
// Создать вектор для получения суммы и в начале
// присвоить ему значение первого слагаемого
VECTOR1 vec_tmp = *this;
// Сложить векторы и возвратить результат
vec_tmp.x += vec.x; vec_tmp.y += vec.y;
return vec_tmp; //сначала будет вызван конструктор копирования vec_tmp,
//а затем деструктор для удаления vec_tmp
}
#endif _VECTOR1_H
Файл class _ vector 1. cpp // Тестирование класса VECTOR1
#include "vector1.h"
const int N = 5; // Число векторов в массиве
Int main()
{
//Создаем и инициализируем вектор v1 и массив векторов v_a,
//вызывается конструктор по умолчанию
VECTOR1 v1, v_a[N];
//Задаем значения и печатаем массив векторов
for (int i = 0; i < N; i++)
{
v_a[i].Assign(i, i+1);
cout<<"\n Vector v_a["<<i<<"]:";
v_a[i].print();
}
// К элементам массива векторов добавляем единичные вектора
// и печатаем массив
for(int i = 0; i < N; i++)
{
++v_a[i];
cout<<"\n Vector v_a["<<i<<"]:";
v_a[i].print();
}
// Вычисляем сумму элементов массива векторов и печатаем ее
for (int i = 0; i < N; i++)
{
v1 = v1 + v_a[i];
}
cout<<"\n Summa - vector v1: "; v1.print();
cout<<endl;
system("pause");
return 0;
}
Результат работы программы:
![]() |
Наследование и полиморфизм
Наследование позволяет классам-потомкам при сохранении всех свойств классов-родителей добавлять свои собственные свойства, которые отражают их индивидуальность.
На рисунке 4.1 приведена диаграмма классов Figure (фигура), Circle (окружность), Rectangle (прямоугольник).

Рисунок 4.1 – Диаграмма классов
Класс Figure – базовый класс, Circle и Rectangle – наследуемые классы.
Полиморфизм в С++ имеет две формы:
· перегрузка операций, функций, использование шаблонов. В этом случае определение конкретного экземпляра операции, функции или класса выполняется на этапе компиляции и называется статическим связыванием;
· использование виртуальных функций (основная форма полиморфизма). В этом случае определение конкретного экземпляра операции, функции или класса выполняется во время выполнения программы и называется динамическим связыванием.
Рассмотрим пример реализации диаграммы классов (рисунок 4.1) с использованием наследования и виртуалных функций. В примере вместо реальных графических методов будем использовать сообщения, выводимые на консоль.
Пример 4_3. Иерархия классов «Фигура», «Окружность», «Прямоугольник»
Файл fiures. h // определение классов «Фигура», «Окружность»,
// «Прямоугольник», их данных и методов
# ifndef _ FIGURES _ H //предотвращение многократного
#define _FIGURES_H //включения заголовочного файла
#include <iostream>
using std::endl;
using std::cout;
class Figure // Базовый класс
{
// Методы
public:
Figure(void) // Конструктор по умолчанию
{
cout << "The constructor Figure" << endl;
}
virtual ~Figure(void) // Виртуальный деструктор
{
cout << "The destructor Figure" << endl;
}
// Нарисовать фигуру - чисто виртуальная функция
// не имеет реализации в базовом классе
virtual void draw(void) = 0;
};
class Circle: public Figure // Circle наследуется от Figure
{
// Данные
private:
int x, // x-координата центра
y, // y-координата центра
R; // Радиус окружности
// Методы
public:
Circle(int CenterX, int CenterY, int Radius) // Конструктор
{
cout << "The constructor Circle" << endl;
x = CenterX; y = CenterY; r = Radius;
}
virtual ~Circle(void) // Виртуальный деструктор
{
cout << "The destructor Circle" << endl;
}
// Нарисовать фигуру - виртуальная функция
virtual void draw (void);
};
// Нарисовать окружность, реализация в классе Circle
void Circle::draw(void)
{
cout << "The Circle draw" << endl;
}
class Rectangle: public Figure // Rectangle наследуется от Figure
{
// Данные
private:
int l, // Левый верхний
T, // угол
R, // Правый нижний угол
b; // прямоугольника
// Методы
public:
Rectangle(int L, int T, int R, int B) // Конструктор
{
cout << "The constructor Rectangle" << endl;
l = L; t = T; r = R; b = B;
}
virtual ~Rectangle(void) // Виртуальный деструктор
{
cout << "The destructor Rectangle" << endl;
}
// Нарисовать фигуру - виртуальная функция
virtual void draw(void);
};
// Нарисовать прямоугольник, реализация в классе Rectangle
void Rectangle::draw(void)
{
cout << "The Rectangle draw" << endl;
}
# endif _ FIGURES _ H
Файл der _ virt 1. cpp // Тестирование классов «Фигура», «Окружность»,
// «Прямоугольник»
#include "figures.h"
int main() // Тестирование классов Figure, Circle и Rectangle
{
// Создается массив указателей на базовый класс, который
// инициализируется адресами объектов классов Circle и Rectangle
Figure *figures[2];
figures[0] = new Circle(100, 100, 10);
figures [1] = new Rectangle (100, 100, 200, 250);
// Для каждого объекта базового класса вызывается метод «рисования» фигуры
figures[0]->draw();
figures[1]->draw();
// Уничтожаются созданные объекты. Если бы деструкторы не были
// виртуальными, то при уничтожении объектов вызывался бы ~Figure() -
// деструктор базового класса, что приводило бы к ошибке
delete figures[0]; delete figures[1];
cout<<endl;
system (" pause ");
return 0;
}
Результат работы программы:
Обратите внимание на порядок вызова конструкторов – сначала базового, а потом наследуемого классса, и деструкторов – сначала наследуемого классса, а потом базового.
Использование виртуальных функций позволяет с помощью указателя на объект базового класса вызывать методы наследуемых классов (в примере draw) – это и есть полиморфизм!
Примеры программ
4.4.1 Класс «Одномерный динамический массив»
Данный класс содержит одномерный массив целых чисел, память для которых выделяется динамически.
Использование динамической памяти требует «глубинного» (по элементного) копирования в операциях присваивания с объектами данного класса. Поэтому в классе необходимо создать копирующий конструктор и перегрузить операцию присваивания. Для того, чтобы обеспечить повторное присваивание (например, a = b = c) перегружаемая функция должна возвращать ссылку!
В классе реализуется безопасная операция индексирования, которая предотвращает обращение к элементам массива, номер которых выходит за границы массива. Для того, чтобы операцию индексирования можно было использовать с обеих сторон присваивния, перегружаемая функция должна возвращать ссылку, а не значение!
Для проверки корректности выполнения программы в реализации методов класса используется макрос assert (выражение) из стандартной библиотеки assert. h. Если результат выражения, стоящего в скобках – ложь, то выполнение программы прерывается и выдается дианостическое сообщение.
Пример 4_4_1. Класс «Одномерный динамический массив»
Файл DinArray 1. h //определение динамического массива и его операций
#ifndef _DIN_ARRAY1_Н //предотвращение многократного
#define _DIN_ARRAY1_Н //включения заголовочного файла
#include "assert.h"
#include <iostream>
using std::endl;
using std::cout;
class Array1D //динамический одномерный массив
{
//Данные
private:
int *p; //указатель на первый элемент массива целых чисел
int size; //размерность массива
//Методы
public:
Array1D(int n=3); //конструктор по умолчанию(размерность массива = 3)
Array1D(const Array1D& a); //копирующий конструктор
~Array1D() {delete [] p;} //деструктор освобождает память, выделенную
//для массива
void print()const; //вывод значений элементов массива
// перегружаемые операции:
int& operator [](int i)const; // индексирование
Array1D& operator =(const Array1D& a); // присваивание
Array1D operator +(const Array1D& a)const; // сложение (бинарная операция)
};
//реализация методов класса Array 1 D
Array 1 D:: Array 1 D (int n): size (n) //конструктор по умолчанию
{
assert (n >0); //размерность массива должна быть положительной
p = new int [ n ]; //выделяем память для элементов массива
assert (p!=0); //если память не выделена, то аварийное завершение
};
Array1D::Array1D(const Array1D& a): size(a.size) // копирующий конструктор
{
p = new int [ size ]; //выделяем память для элементов массива
assert (p!=0); //если память не выделена, то аварийное завершение
for (int i=0; i<size; ++i)
p [ i ] = a. p [ i ]; //присваиваем значения элементам массива
}
void Array1D::print() const
{
for(int i=0; i<size; ++i)
cout<<p[i]<<endl;
cout<<endl;
}
int & Array1D::operator [](int i) const
{
assert (i >=0 && i < size); //если индекс за границами, то аварийное завершение
return p [ i ]; //возвращаем ссылку на i – й элемент массива
}
Array1D& Array1D::operator =(const Array1D& a)
{
if (this!= &a){ //если присваивание самому себе, то ничего делать не надо
assert (size == a. size); //если размеры массивов не равны, то аварийное
// завершение
for(int i=0; i<size; ++i)
p[i] = a.p[i];
}
return *this; // возвращаем ссылку на объект Array1D
}
Array1D Array1D::operator +(const Array1D& a) const
{
assert (size == a. size); //если размеры массивов не равны, то аварийное
//завершение
Array 1 D sum (size); //создаем массив sum размерности size
for (int i=0; i<size; ++i)
sum.p[i] = p[i] + a.p[i]; // или sum.p[i]=this->p[i] +a.p[i];
return sum; //сначала вызывается конструктор копирования,
//потом деструктор массива sum e
}
# endif _ DIN _ ARRAY 1_Н
Файл DinArray 1. cpp // Тестирование класса Array1D
#include "DinArray1.h"
Int main()
{
Array1D a, b, c; //Размерность массивов по умолчанию = 3
for (int i=0; i<3; ++i) a[i]=i+1; // Инициализируем массив а
cout<<"Array1D a: "<<endl; //и выводим его значения
A.print();
b = a; //Присваиваем элементам массива b
//значения элементов массива a
cout<<"Array1D b: "<<endl; //и выводим значения массива b
B.print();
a = a + b + (c = a + b); //Сначала определяем значения с,
//затем увеличиваем значения а
cout<<"Array1D c: "<<endl; //Выводим значения массива с
c.print();
cout <<" Array 1 D a: " << endl; //Выводим значения массива а
A.print();
system("pause");
return 0;
}
![]() |
Результат работы программы:
4.4.2 Класс «Динамически размещаемая срока»
Данный класс содержит строку произвольной длины, символы которой размещаются в памяти динамически.
В реализации методов класса используются функции из стандартной библиотеки < cstring >:
· strcpy _ s (s, len, str) – копирование не более len символов из строки str в строку s
· strcat _ s (s, len, str) – добавление не более len символов из строки str в строку s
· strlen (str) – длина строки str. Функция возвращает значение типа size _ t
Функции strcpy _ s, strcat _ s безопасные (контролируют число пересылаеых символов) по сравнению с функциями strcpy, strcat, прииспользовании котрыхкомпилятор выдает предупреждение:
«This function or variable may be unsafe. Consider using strcpy_s instead.»
Для проверки корректности выполнения программы в реализации методов класса используется макрос assert (выражение) из стандартной библиотеки assert. h.
Пример 4_4_2. Класс «Динамически размешаемая строка»
Файл DinString. h //определение динамической строки и операций
#ifndef _DIN_STRING_Н //предотвращение многократного
#define _DIN_STRING_Н //включения заголовочного файла
#include <iostream>
using std::endl;
using std::cout;
#include <cstring>
#include "assert.h"
class DinString // динамически размещаемая срока
{
// Данные
private:
char *s; //указатель на строку
int len; //длина строки
//Методы
public:
DinString(): len(0){ // конструктор по умолчанию
s=new char[1]; assert(s!=0); s[0]=0;
}
DinString(const DinString& str); // копирующий конструктор
DinString(const char* str); // преобразующий конструктор -
// преобразует тип char* к типу DinString
~DinString() {delete [] s;} // деструктор освобождает память,
// выделенную для строки
void print()const { cout<<s<<endl;}// вывод строки
// перегружаемые операции
DinString& operator =(const DinString& str); // присваивание
DinString operator + (const DinString& str) const; // конкатенация
};
// реализация методов класса DinString
DinString::DinString(const char* str) // преобразующий конструктор
{
len = static_cast<int>(strlen(str));// преобразуем возвращаемое значение
// функции strlen типа size _ t в
// значение типа int
s=new char[len+1]; //выделяем память для строки
assert(s!=0); //если память не выделена, то аварийное завершение
strcpy_s(s, len+1, str);
};
DinString::DinString(const DinString& str): len(str.len) // копирующий конструктор
{
s=new char[len+1]; //выделяем память для строки
assert(s!=0); //если память не выделена, то аварийное завершение
strcpy_s(s, len+1, str.s);
}
DinString& DinString::operator =(const DinString& str)
{
if (this!= &str){ //если присваивание самому себе, то ничего делать не надо
delete [] s; //удаляем "старую" строку
len = str.len; //формируем длину новой строки
s=new char[len+1]; //выделяем память для строки
assert(s!=0); //если память не выделена, то аварийное завершение
strcpy_s(s, len+1, str.s);
}
return *this; //возвращаем ссылку на объект
}
DinString DinString::operator + (const DinString& str) const
{
DinString tmp;
tmp.s =new char[len + str.len +1];
tmp.len=len + str.len;
strcpy_s(tmp.s, tmp.len+1, s);
strcat_s(tmp.s, tmp.len+1, str.s);
return tmp; //сначала вызывается конструктор копирования,
//потом деструктор tmp
}
#endif _DIN_STRING_Н
Файл DinSring. cpp // Тестирование класса DinString
#include "DinString.h"
Int main()
{
DinString s1("123"), s2("456"), s3(s1), s4;
s4=s3 + s2;
cout<<"s1: "; s1.print(); cout<<"s2: "; s2.print();
cout<<"s3: "; s3.print(); cout<<"s4: "; s4.print();
cout<<endl;
system("pause");
return 0;
}
Результат работы программы:








