Похожие презентации:
peregruzka_operatorov
1. Перегрузка операторов
В качестве примеров полиморфизма в С++ мы имели дело с перегрузкойфункций. А что с перегрузкой операторов?
По сути дела каждый оператор в С++ и представляет собой функцию.
Операторы могут иметь один или два аргумента. Если аргумент один, то такой
оператор называется унарным. Пример ++ или --. Если аргументов два, то мы
имеем дело с бинарным оператором.
Важно! В С++ нельзя перегрузить операторы встроенных типов! Это
означает, что невозможно перегрузить оператор ++ таким образом, чтобы он
инкрементировал элементы одномерного массива типа int. Все операторы
встроенных типов С++ и так являются перегруженными и поэтому не стоит их
перегружать.
Любые операции по перегрузке операторов относятся только к
пользовательским типам данных, а точнее – к типам, которые описывают
объекты.
Перегрузка операторов очень похожа на перегрузку функций. Перегруженный
оператор может являться членом класса или быть дружественным к нему.
В С++ нельзя перегрузить оператор отдельно от класса. Если предполагается
использование перегруженного оператора за пределами класса, он должен быть
объявлен как public.
1
2. Перегрузка операторов – как и что можно
Прототип перегруженного оператора задается следующей синтаксическойформой:
Возвращаемый_тип <имя_класса>:: оператор XX (аргументы);
Вместо XX непосредственно записывается оператор, который перегружается.
В С++ есть ряд операторов, которые не могут перегружаться. В основном
это специфические операторы, используемые для работы с методами и
данными классов: ., .*, ::, sizeof и ?:.
По поводу последнего оператора. Это оператор условной трансляции. Он
является в С++ единственным оператором, имеющим три аргумента.
Пример
…
int n;
…
cout << (n==1 ? "One" : "Non one");
2
3. Перегрузка операторов - ограничения
Нельзя перегрузить приоритет какого-либо оператора.Это значит что вычисление (--k*2)+4 всегда будет происходить в
соответствии с приоритетами в последовательности:
декрементация k, умножение результата на 2, добавление к
результату 4.
И никак нельзя перегрузить оператор +, чтобы он выполнялся
первым.
Могут быть перегружены и такие операторы С++ как new и delete.
Нельзя перегрузить унарный оператор таким образом, чтобы он
работал как бинарный и наоборот.
Оператор не может иметь аргументов по умолчанию
3
4. Перегрузка операторов – базовый пример
Для рассмотрения вариантов перегрузки различных операторов нампонадобится какой-либо пример класса.
Будем работать, занимаясь перегрузками операторов следующего класса:
class Nmb {
int n;
public:
Nmb(int n=0) {this->n = n; }; // конструктор
void print();
};
void Nmb::print()
{
cout << "n= " << n << endl;
}
4
5. Перегрузка унарных операторов
Все унарные операторы имеют только один аргумент. При перегрузке этимаргументом является экземпляр класса, относительно которого производится
перегрузка оператора.
В случае, когда унарный оператор перегружается как член класса ( а не как
дружественная функция), то такой оператор не будет иметь аргументов.
Рассмотрим пример перегрузки унарного оператора «-» для класса Nmb:
class Nmb {
…
Nmb& operator - ();
…
};
Nmb& Nmb::operator - ()
{
n = -n;
return (*this);
}
Унарный оператор в качестве
результирующего значения может
возвращать void, однако возвращение
ссылки на измененный объект позволяет
использовать унарные операции в
сложных записях выражений, особенно,
где происходит присваивание объектов.
Перегруженный оператор может
возвращать и встроенный тип. Это
целесообразно, например, при
перегрузке операции логического «НЕ».
5
6. Перегрузка унарных операторов «++» и «--»
Особо следует уделить внимание перегрузке операторов инкремента идекремента, поскольку только эти операторы имеют постфиксную и
префиксную формы.
Различие этих форм осуществляется путем использования второго
фиктивного аргумента типа int. Если необходимо подчеркнуть , что оператор
будет использоваться в постфиксной форме, то необходимо наличие этого
аргумента, который по умолчанию устанавливается в 0. Для класса Nmb:
Nmb& Nmb::operator ++ ()
{
++n;
return (*this);
}
Nmb& Nmb::operator ++ (int)
{
n++;
return (*this);
}
6
7. Перегрузка дружественных унарных операторов «++» и «--»
7Перегрузка дружественных унарных операторов «++» и «--»
В случае перегрузки указанных операторов как дружественных по
отношению к классу, их описание может быть выполнено как:
class Nmb {
…
friend Nmb& operator ++ (Nmb&);
friend Nmb& operator ++ (Nmb&, int);
…
};
Nmb& operator ++ (Nmb& obj)
{
++(obj.n);
return obj;
}
Nmb& operator ++ (Nmb& obj, int)
{
(obj.n)++;
return obj;
}
8. Перегрузка бинарных операторов
Начнем со случая перегрузки операторов как членов класса. Функцияреализации бинарного оператора будет иметь только один аргумент.
Объясняется это тем, что второй аргумент по умолчанию является
экземпляром этого класса.
Для начала перегрузим бинарные операторы «+» и «-» таким образом,
чтобы их можно было использовать так: Nmb n1, n2(5);
единственный аргумент при перегрузке
n2 = n1 + 5;
будет иметь тип int. В обоих случаях эти n1 = n2 – 3;
операторы должны возвращать объекты,
поскольку результатом операции
является объект, который
присваивается другому.
class Nmb {
…
Nmb operator + (const int);
Nmb operator - (const int);
…
};
Nmb Nmb::operator + (const int m)
{
Nmb tmp;
tmp.n = this->n + m;
return tmp;
}
Nmb Nmb::operator - (const int m)
{
Nmb tmp;
tmp.n = this->n - m;
return tmp;
}
8
9. Перегрузка бинарных операторов - как
Для того, чтобы не возникло никаких побочных эффектов при возвратеобъекта из перегруженного оператора, необходимо наличие конструктора
копирования (если необходимо).
Важно! Данные операторы работают только при записях, приведенных в
примерах их использования.
Если бы перегруженные операторы работали без временных объектов и
возвращали бы ссылки на текущие объекты, то использование таких
операторов было бы некорректным. Почему?
В случае операции n2 = n1 + 5 сначала бы сработал оператор «+», который
бы изменил объект n1, а затем измененный объект присвоил бы объекту n2, т.е.
произошло бы изменение сразу двух объектов.
Важно! Использовать данный оператор можно только в данной записи,
когда первым операндом выступает объект-переменная, а вторым – константа.
Это значит, что компилятор не пропустит записи вида n2 = 5+n1. Дело в том,
что при такой записи для компилятора появляются два явных аргумента – это
целое число и экземпляр класса. В этом случае при описании оператора внутри
класса компилятор не пропустит эту запись и выдаст сообщение о том, что у
бинарного оператора +(-) должен быть один аргумент, а не 2.
9
10. Перегрузка бинарных операторов как дружественных
Для решения этой проблемы можно определить перегружаемый операторкак дружественный к данному классу. В этом случае передача ссылки на
объект в качестве второго аргумента кажется весьма оправданной!
class Nmb {
…
friend Nmb operator + ( const int, const Nmb&);
friend Nmb operator - ( const int, const Nmb&);
…
};
Nmb operator + ( const int m, const Nmb &obj)
{
Nmb tmp;
tmp.n = m + obj.n
return tmp;
}
Перегрузка оператора «-» будет идентичной, за исключением одной
операции вычитания внутри тела оператора.
10
11. Перегрузка операторов расширенного присвоения
11Рассмотрим теперь перегрузку оператора расширенного присвоения,
используемую в записи вида n2 += 6 (упрощенная форма для записи n2 = n2+6)
Для решения этой проблемы можно определить перегружаемый оператор “+=“,
имеющий два аргумента – целое число и объект. При этом оператор должен
возвращать ссылку на тот объект, который используется в качестве аргумента,
т.е. ссылку на самого себя.
class Nmb {
…
Nmb& operator += ( const int);
…
};
Nmb& Nmb::operator += ( const int m)
{
this->n +=m;
return (*this);
}
12. Перегрузка бинарных операторов над объектами
Мы рассматривали случаи, когда операция выполнялась над разнымитипами данных: один объект Nmb, а второй – встроенный тип int.
Но можно перегрузить бинарные операторы так, чтобы оба операнда были
объектами данного класса. Для оператора сложения определение метода
будет выглядеть таким образом, что:
class Nmb {
…
Nmb operator + ( const Nmb &);
…
};
Nmb Nmb::operator + ( const Nmb & obj)
{
Nmb tmp;
tmp.n = this->n + obj.n
Теперь становится возможной такая запись:
return tmp;
}
Nmb n1(3), n2(4), n3;
n3 =n1 + n2;
12
13. Перегрузка операторов присвоения
Рассмотрим теперь перегрузку оператора присваивания «=», который тожеявляется бинарным.
Важно! Перегрузка не может быть выполнена в форме дружественного к
классу оператора – обязательно должен быть членом класса.
Применяется этот оператор в качестве механизма, предотвращающего
создание копий и побитного копирования экземпляров класса.
Для класса Nmb оператор будет иметь один явный аргумент – константную
ссылку на объект и возвращать ссылку на модифицированный объект:
class Nmb {
…
Nmb& operator = ( const Nmb &);
…
};
Nmb& Nmb::operator = ( const Nmb & obj)
{
this->n = obj.n
return (*this);
}
Все рассмотренные нами примеры
перегрузки бинарных операторов
возвращают либо объект, либо
ссылку на него. В С++ нет
ограничений на возвращаемый
тип при перегрузке операторов.
Но так делать удобно – можно
использовать операторы при
описании сложных выражений
Nmb n1(3), n2, n3;
n3 =n1 = n1 + n2 +=5+6;
13
14. Перегрузка логических операторов и операторов отношения
14Все операторы данных типов являются бинарными и если они
перегружаются как члены класса, то должны иметь один аргумент – объект или
ссылку на него. В качестве возвращаемого типа должен указываться один из
встроенных типов данных, который говорил нам верно ли предложенное
выражение. Это позволит при проверке использовать сложные выражения.
Для класса Nmb оператор «==» может быть перегружен следующим образом:
class Nmb {
…
int operator == ( const Nmb &);
…
};
int Nmb::operator == ( const Nmb & obj1)
{
if (this->n == obj.n) return 1;
return 0;
}
Подобным образом
перегружаются и все другие
логические операторы и
операторы отношений.
15. Перегрузка операторов ввода/вывода
15Перегрузка операторов ввода/вывода
Операторы потокового ввода/вывода «<<» и «>>» уже являются
перегруженными в стандарте языка С++.
Для реализации ввода/вывода в С++ используются стандартные классы.
Одними из таких классов являются ostream и istream. Данные классы созданы
для поддержки стандартного вывода и ввода данных соответственно.
Экземпляры данных классов - cout и cin. Их можно перегружать. Но! Данные
операторы не могут быть членами какого-либо класса, но могут быть
дружественными классу.
Общий синтаксис перегрузки оператора вставки в поток для классов:
ostream& operator << (ostream &ссылка_на_поток, имя_класса объект)
friend ostream& operator << (ostream&, Nmb& );
…
ostream& operator << (ostream &os, Nmb &obj)
{
os<<“n=“ << obj.n << endl;
return os;
}
16. Перегрузка оператора ввода
16Перегрузка оператора ввода
Форма перегрузки оператора извлечения из потока похожа на
перегрузку оператора «<<»:
friend istream& operator >> (istream&, Nmb& );
…
istream& operator >> (istream &is, Nmb &obj)
{
cout << “Input n: “;
is >> obj.n;
return is;
}
17. Перегрузка манипуляторов
17Перегрузка манипуляторов
Для стандартных потоков данных в С++ могут быть применены
специальные операторы, называемые манипуляторами.
Манипуляторы предоставляют средства форматированного
ввода/вывода. Часть стандартных марипуляторов определа в
заголовочном файле iostream, а часть – в файле iomanip.
Один из манипуляторов – endl - переход к новой строке и
очистка буфера. Еще: oct, dec, hex – манипуляторы, которые
отвечают за установку системы счисления для выводимых данных. Все
вышеперечисленные манипуляторы работают с внутренними
компонентами, в данном случае, класса ostream, и действуют до их
явной отмены.
Средства работы с манипуляторами в С++ являются
расширяемыми.
18. Определение собственных манипуляторов
18Определение собственных манипуляторов
Пользователь может определить свой собственный манипулятор.
Рассмотрим построение манипулятора, который записывает в
выходной поток десять символов «*».
ostream& operator stars (ostream& os)
{
for (int i=0; i< 10; os<<’*’, i++);
os << endl;
return os;
}
…
cout << stars;
…
Из примера видно, что данный
манипулятор является простейшим –
без параметров. Определение
манипуляторов такого вида весьма
простое – один единственный
аргумент – ссылка на поток (в данном
случае – выводной) и возвращаемое
значение – видоизмененный поток.
Данное описание является описанием
простой функции.
Компилятор воспринимает запись вида cout << stars следующим образом:
Происходит разыменование перегруженного оператора «<<», результат есть
адрес функции stars. Затем выполняется эта функция и после этого
происходит возврат ссылки на поток и джальнейшие связанные с ним
действия.
19. Определение манипуляторов с параметром int
19Определение манипуляторов с параметром int
Пользователь может определить свой собственный манипулятор, у
которого есть один или более аргументов.
В заголовочном файле iomap описан ряд специальных макросов,
предназначенных для создания манипуляторов с аргументами. Простейшие
манипуляторы могут принимать только один аргумент типа int или long. В
этом специальном случае используется макрос _Smanip:
#include "iomanip«
…
void stars(ios_base& os, int n)
{
ostream *pos = dynamic_cast<ostream*>(&os);
for (int i=0; i< n; (*pos)<<'*', i++);
(*pos) << endl;
}
_Smanip<int> Stars(int how_many)
{
return _Smanip<int> (&stars, how_many);
}
…
cout << Stars(12);
…
Сначала определяется
функция, которая
отвечает за выполняемые
над потоком действия.
После этого необходимо
определить манипулятор.
Для этого используется
указанный макрос.
20. Определение манипуляторов с параметрами
20Определение манипуляторов с параметрами
При использовании параметра другого типа используется техника,
которая продемонстрирована на следующем примере:
#include "iostream"
#include "iomanip"
using namespace std;
typedef char* charp;
class money
{
private:
long value;
static char *CurM;
public:
money( long val ) { value = val; }
friend ostream& operator << ( ostream& os, money m ) {
os << m.value << money::CurM; return os;
}
static void setpic( char* szPic ) {
money::CurM = new char[strlen( szPic ) + 1];
strcpy_s( money::CurM, strlen( szPic ) + 1, szPic );
}
};
21. Определение манипуляторов с параметрами 2
21Определение манипуляторов с параметрами 2
Продолжение примера:
char *money::CurM; // Static pointer
void func( ios_base& os, char * somename )
{
money::setpic(somename);
}
_Smanip<charp> setpic(char * somename)
{
return (_Smanip<charp>(&func, somename));
}
void main ()
{
money amt = (long)54321;
cout << setpic( "$") << "amount = " << amt << endl; // Альтернатива Br или €
}
22. Перегрузка оператора new
В С++ имеются две возможности перегрузки операторов new() и delete() локально (в пределах класса) и глобально (в пределах программы). Эти операторыимеют правила переопределения, отличные от рассмотренных правил переопределения
других операторов.
Одна из причин перегрузки этих операторов состоит в том, чтобы придать им дополнительную семантику, например, для обеспечения выдачи диагностической информации, или увеличения защищенности от ошибок. Кроме того, класс может иметь более
эффективную схему распределения памяти, чем та, которая обеспечивается системой.
Оператор new можно задать в следующих формах:
<::> new <аргументы> имя_типа <инициализирующее_выражение>;
<::> new <аргументы> имя_типа [ ];
Параметр ”аргументы” можно использовать либо для того, чтобы различить разные
версии глобальных операторов new, либо для использования их в теле функции
operator. Доопределенную функцию operator new можно объявить:
void *operator new(size_t t, <список _аргументов>);
void *operator new[](size_t t, <список _аргументов>);
Вторая форма используется для выделения памяти для массивов. Возвращаемое
значение всегда должно иметь тип void *. Единственный обязательный аргумент
функции operator всегда должен иметь тип size_t. При этом в функцию operator
автоматически подставляется аргумент sizeof(t).
22
23. Перегрузка оператора new - пример
#include "iostream“using namespace std;
void *operator new(size_t tip, int kol) //глобальная ф-ция operator new
{ cout << "GF 1" <<endl;
// с одним параметром
return new char[tip*kol];
}
void *operator new(size_t tip,int n1,int n2) //глобальная ф-ция operator new
{ cout << "GF 2" <<endl;
// с двумя параметрами
void *p=new char[tip*n1*n2];
return p;
}
class cls
{ char a[40];
public:
cls(char *aa){ strcpy(a,aa); }
~cls(){}
void *operator new(size_t,int);
};
23
24. Перегрузка оператора new - пример
void *cls::operator new(size_t tp,int n) // локальная функция operator new{ cout << "LF " <<endl;
return new char[tp*n];
}
void main()
{ cls obj("Overload of the operator new");
float *ptr1;
ptr1=new (5) float;
// вызов 1 глобальной функции operator new
ptr1=new (2,3) float;
// вызов 2 глобальной функции operator new
ptr1=new float;
// вызов сиcтемной глобальной функции
cls *ptr2=new (3) cls("aa"); // вызов локальной функции operator new
// используя cls::cls("aa")
}
warning C4291: 'void *cls::operator new(size_t,int)' : no matching operator delete
found; memory will not be freed if initialization throws an exception
24
25. Перегрузка оператора delete
Оператор delete разрешается доопределять только по отношению кклассу. В то же время можно заменить системную версию реализации
оператора delete на свою.
Доопределенную функцию operator delete можно объявить:
void operator delete(void *p<,size_t t>);
void operator delete[](void *p<,size_t t>);
Функция operator должна возвращать значение void и имеет один
обязательный аргумент типа void * - указатель на область памяти,
которая должна быть освобождена.
25
26. Перегрузка оператора delete - пример
…class cls {
…
void operator delete(void *);
…
}
void cls::operator delete(void *p) // локальная функция operator
{ cout << “LF DELETE" <<endl;
delete p;
// вызов системной функции delete
}
void operator delete[](void *p)
// глобальная функция operator
{ cout << “GF DELETE" <<endl;
delete p;
// вызов системной функции delete
}
26
27. Перегрузка оператора delete - использование
void main(){ cls obj("перегрузка операторов NEW и DELETE");
float *ptr1;
ptr1=new (5) float;
// вызов глобальной ф-ции доопр. оператора new
delete [] ptr1;
// вызов глобальной ф-ции доопр.оператора delete
cls *ptr2=new (10) cls("aa"); // вызов локальной функции доопределения
// оператора new (из класса cls)
delete ptr2;
// вызов локальной функции доопределения
// оператора delete (из класса cls)
}
Необходимо отметить тот факт, что при реализации переопределения
глобальной функции delete в ней не должен использоваться оператор
delete[], так как это приведет к бесконечной рекурсии. При выполнении
инструкции системный оператор delete ptr2 сначала вызывается локальная
функция доопределения оператора delete для класса cls, а затем из нее
глобальная функция переопределения delete.
27
28. Перегрузка операторов new – delete
warning C4291: 'void *cls::operator new(size_t,int)' : no matching operator deletefound; memory will not be freed if initialization throws an exception
Нужны добавки!
class cls
{…
void operator delete(void *,int);
…
};
void cls::operator delete(void *p, int n)
// локальная функция operator
{
cout << "LFD" <<endl;
delete p;
// вызов системной функции delete
}
28
Программирование