6. Перегрузка операций
Возможна двусмысленность!
В таких ситуациях достаточно одной функции
Перегрузка операций
Двуместная операция
Пример 1
Пример использования
Пример 2
Перегрузка сравнения ==
Не забыть:
Одноместная операция
Пример 3
Задача
Правила перегрузки:
7. Примеры перегрузки некоторых операций
Следовательно ее надо определить
2. Перегрузка операции =
Это недопустимо по следующим причинам:
Пример перегрузки операции = для класса String
Теперь присвоение s1 = s2 будет выполняться грамотно.
Конструктор копирования и операция присвоения =
Отличие операции = и конструктора копирования
3. Перегрузка операции ()
Конструктор
Перегрузка ()
Пример использования
Замечание
591.00K
Category: programmingprogramming

Л3.Ч1. Перегрузки (1)

1. 6. Перегрузка операций

Перегрузка функций
Перегрузка функций ‒ одно из проявлений полиморфизма в С++.
Перегрузка функций - это использование одинаковых
имен для однотипных функций.
ПРИМЕР. Пусть требуется написать 3 функции вывода:
• массива a из m целых чисел;
• длинного целого числа;
• строки символов.

2.

Начинаем работу с «придумывания имен», например:
void Printm (int *a, int m) для массива,
void Printl (long n) для длинного целого,
void Prints (char *s) для строки.
В С++ все эти 3 функции могут быть заданы одним
именем:
void Print (int *a, int m)
void Print (long n)
void Print (char *s)
Поскольку список формальных аргументов у каждой
функции разный, т.е. отличается количеством и/или
типом (говорят - сигнатурой), то при вызове функций
компилятор по списку фактических аргументов
разберется, какой экземпляр функции требуется
вызвать:

3.

Print(“Hello!”);
Print(a, 20);
Print( 50000l );
// функция Print(char *)
// функция Print(int *, int)
// функция Print(long)
/*суффикс I подчеркивает, что число следует
рассматривать как long*/
Конструкторы – полиморфизм
Определение нескольких конструкторов в классе - это
перегрузка функций, т.е. проявление полиморфизма
для член-функций класса.

4. Возможна двусмысленность!

int sum2(int a, int b)
Причина двусмысленности:
{ return a+b; }
в стандарте языка имеется
float sum2(float a, float b) преобразование по
умолчанию float -> int
{ return a+b; }
void main()
{ int a = 4, b = 5, c; float x = 3.5, y = 2, z;
c = sum2(a, b); cout << “c = ” << c;
двусмысленность
z = sum2(3.5, -1.2);
// Error: Ambiguity between 'sum2(int,int)’ and
// 'sum2(float,float)‘
Компилятор сомневается,
z = sum2(x, y); // true
не применить ли его здесь?!
cout << “z = ” << z;
Решает, что это не в его
компетенции и формирует
}
ошибку!

5. В таких ситуациях достаточно одной функции

float sum2(float a, float b)
{ return a+b; }
void main()
{ int a = 4, b = 5,c; float x = 3.5, y = 2, z;
c = sum2(a, b);//действует преобразование int ->float
cout << “c = ” << c;
z = sum2(3.5, -1.2);
cout << “z = ” << z;
z = sum2(x, y);
cout << “z = ” << z;
}

6. Перегрузка операций

В С++ можно выполнить перегрузку операций для
объектов класса.
То есть с помощью знаков операций
+, -, *, и т.д.
можно определить похожие действия для абстрактных
типов данных.

7. Двуместная операция

Формат перегрузки двуместной операции:
Тип operator @ (операнд_2) {тело_операции},
где Тип ‒ тип возвращаемого значения,
@ - знак двуместной операции.
Первым операндом является объект, в классе которого
эта операция определяется, т.е. * this.
Второй операнд – произвольный (операнд_2).
Используется перегруженный знак так же, как для
стандартных типов данных
операнд1 @ операнд2

8. Пример 1

В классе String вместо функции Plus можно
определить операцию ‘+=’ :
String& String :: operator +=( String &s2 )
{ char *t = new char[ len+1 ];
ctrcpy_s(t, len+1, line);
// сохраним временно 1-й операнд!
delete [] line; len = len + s2.len;// новая длина
line = new char[ len+1 ];
strcpy_s(line, len+1, t);
strcat_s(line, len+1, s2.line);
delete [] t;
return *this;
}
operator +=
вместо имени ч/функции Plus

9. Пример использования

В примере из п.5 вместо оператора
String *s3 = new String(s1.Plus(s2));
можно записать
String *s3= new String(s1+=s2);
Еще пример использования:
String s(“Студент “), r(“Петров “);
s += r; // s = “Студент Петров”

10. Пример 2

В классе String определим функцию сравнения
двух строк
int String :: EqStr(String & s)
{ if(strcmp(line, s.line)) return 0; //строки не равны
return 1; // строки равны
}
Использовать ее можно таким образом:
String s1(“Иванов”), s2(“Петров”);
if ( s1.EqStr(s2)) puts(”Строки равны”);
else puts(”Строки не равны”);
Ответ?

11. Перегрузка сравнения ==

Но было бы нагляднее для
использовать операцию = =.
Перегрузим ее для класса String:
сравнения
строк
int String :: operator ==(String &s)
{if(strcmp(line, s.line)) return 0;
return 1; }
Cравнение теперь выглядит привычнее:
if (s1 == s2) puts(”\n Строки равны”);
else {s1. Print(); printf(” - это не “); s2.Print();}
Ответ?
String s1(“Иванов”), s2(“Петров”);
operator ==
Иванов – это не Петров
вместо имени ч/функции EqStr

12. Не забыть:

При описании класса эти перегруженные
операции надо объявить
class String { ...
public:
...
String& operator +=( String &);
int operator ==(String &);
...
};

13. Одноместная операция

Формат перегрузки одноместной операции:
где
пусто
Тип operator @() {тело_операции}
Тип ‒ тип возвращаемого значения
@ ‒ знак одноместной операции.
Обращение:
@ операнд
У одноместной операции единственный операнд и это
*this!

14. Пример 3

Определим операцию реверса строки, т.е. перестановки
символов в обратном порядке.
String String :: operator ~()
{int i; char t;
for(i = 0; i<len/2; i++)
{t = line[i]; line[i] = line[ len – i - 1]; line[len – i - 1] = t;}
return *this;
}
Пример использования:
String r(«телефон»);
~r;
r. Print();
// вывод «нофелет»

15. Задача

Задано слово. Является ли слово «перевертышем»?
Напомню:
void main()
приоритет одноместных операций
выше!
{ String s1(“казак”);
String s2 = s1; // Работает конструктор копирования,
// чтобы не изменить s1 операцией ~
s1.Print();
if(s1 == ~s2) puts(” – перевертыш”);
else puts(” - не перевертыш”);
}
Ответ?
казак - перевертыш

16. Правила перегрузки:

1. При перегрузке операции, как член-функции класса,
двуместная операция имеет один аргумент,
одноместная – ни одного;
2. Знак одноместной операции может быть перегружен только как одноместный, а двуместной –
только как двуместный;
3. Наряду с обычным использованием перегруженного знака obj1 @ obj2 для двуместной операции
и @ obj
для одноместной он может использоваться как член-функция класса
obj1.operator @(obj2) и obj.operator @()

17.

4. Нельзя перегружать операции для стандартных
типов данных
Например, + для массивов, определенных, как
int * a или int a[20].
5. Нельзя перегружать операции
::
.
?:
sizeof

18. 7. Примеры перегрузки некоторых операций

1. Перегрузка [ ]
Пусть определен объект
String s(«Еденица»);
Заметив ошибку, попытаемся её исправить:
s[2] = ’и’;
// ошибка: операция [ ] в классе
// String не определена

19. Следовательно ее надо определить

Для этого вместо функции Index (см п. 1), определим
операцию [ ]:
Трактуется компилятором
как двуместная операция
char & String :: operator [ ] ( int i)
{ if (i<0 || i>=len) {puts(“Индекс за пределами строки»);
exit(0);}
return line[i];
}
В этом случае можно записать оператор
s[2] = ’и’; //это возможно благодаря char &, ‘Единица’
или вывести символ
cout << s[0]; // вывод буквы ‘Е’
Не забудем объявить её в классе
char & operator [ ] (int );

20. 2. Перегрузка операции =

Если объект использует динамическую область, то для
него надо перегрузить операцию ‘= ‘ - присвоение.
Рассмотрим почему.
Пусть заданы 2 объекта:
String s1, s2(“ФПМК”);
...
s1 = s2;

21.

Картина
присвоения
напоминает
инициализацией (даже хуже):
ситуацию
с
До присвоения:
line
s1:
line
s2:
80 байт
len
0
\0
len
4
s1 = s2;
После присвоения:
line
s1:
line
s2:
len
4
len
4
...
Так как компилятор выполняет
5 байт
простое копирование
Ф П М К \0
s1.line = s2.line;
s1.len = s2.len
и память брошена
80 байт
\0
...
и оба объекта используют
одну и ту же память
5 байт
Ох, как жаль
Ф П М К \0
эти 80 байтов!

22. Это недопустимо по следующим причинам:

1. Память в 80 байтов у объекта s1 будет «брошена»
(считаться занятой);
2. Объекты s1 и s2 будут использовать одну и ту же
динамическую память по указателю line, что
приведет к тому, что любое изменение в поле line
объекта s1 приведет к изменению поля line объекта
s2 и наоборот;
3. При выходе из функции деструктор будет пытаться
дважды освободить одну и ту же динамическую
память: это фатальная ошибка!

23.

В классах, где используется динамическая память,
операция ‘=’ обязательно перегружается.

24. Пример перегрузки операции = для класса String

String & String :: operator =(const String &s)
{ if( this != &s)
// на случай присвоения s = s
{ delete [ ] line; // Важно освободить
// динамическую память у *this
line = new char [(len = s.len)+1];
// сразу определим поле len
strcpy_s(line, len+1, s.line);
}
return *this;
}

25. Теперь присвоение s1 = s2 будет выполняться грамотно.

Основные действия
операции =
line
delete [ ] line;
// 1)
line = new char [(len = s.len)+1]; // 2)
strcpy( line, s.line);
// 3)
len
Пустая строка из 80 байтов
4 0
\0
s1:
Ф
line
s2:
...
П
М
К
2)
\0
3)
len
4
1)
Ф
П
М
К
\0
куча
Все неприятности исчезнут!

26. Конструктор копирования и операция присвоения =

String s(“Лето”), r(“Солнышко”);
String p = s;
// работает конструктор копирования

r = s;
// работает перегруженная операция =

27. Отличие операции = и конструктора копирования

Оператор = выполняет 3 действия :
1. Освобождает динамическую память у левого объекта
(её могло быть меньше или больше, чем у правого);
2. берёт новую динамическую память такого же размера,
как у правого объекта;
3. копирует поля правого объекта в поля левого.
Конструктор копирования выполняет 2 действия:
1. Берет динамическую память для левого объекта такого
же размера, как у правого;
2. копирует поля правого объекта в поля левого.
Конструктор копирования не может выполнить освобождение памяти у левого объекта, так как у него её ещё и
НЕ БЫЛО!

28. 3. Перегрузка операции ()

Если объект - матрица, то для обращения к ее
элементам нельзя перегрузить [][].
В этом случае можно использовать перегрузку
операции ().
class Matrix
{ int **a, m, n;
public:
Matrix( int m1 = 1, int n1 = 1, int t = 0, int d = 10);
// конструктор с аргументами по умолчанию
~Matrix();
void Show(); // вывод матрицы
int & operator()(int, int);
...
};

29. Конструктор

Matrix::Matrix(int mm, int nn, int t, int d)
// mm – строк, nn – столбцов, d - диапозон
// t != 0 – генерировать случайные числа
{m = mm; n = nn; int i, j;
a = new int * [m];
for( i = 0; i<m; i++)
a[i] = new int [n];
if(t) for(i = 0; i<m; i++)
for(j = 0; j<n; j++)
a[i][j] = rand()%d;
}

30. Перегрузка ()

int & Matrix :: operator()(int i, int j)
{ if(i<0 || i>=m || j<0 || j>=n)
{puts("\n Значения индексов недопустимы. Выход.”);
exit(1);
}
return a[ i ][ j ];
}
Возвращаемое значение - ссылка int & - для того,
чтобы иметь возможность
менять значения элементов матрицы.

31. Пример использования

void main()
{ srand(time(0));
Matrix A(3,4), B(3,3,1);
// A не инициализируется
// случайными числами,
// B – инициализируется
for ( int i = 0; i<3; i++)
B(i, i) = 1;
// занесение 1 на главную диагональ
puts("\nB:“);
B.Show();
...
}

32. Замечание

Операция () - единственная, которая может иметь
произвольное количество аргументов (в частности 0).
ПРИМЕР
void Matrix:: operator()()
// обнулить матрицу
{ int i, j;
for ( i = 0; i<m; i++)
for ( j = 0; j<n; j++)
a[i][j] = 0;
}
ИСПОЛЬЗОВАНИЕ
Matrix B(5, 5);
B();
English     Русский Rules