Перегрузка операторов в языке С++. Общие понятия

Автор работы: Пользователь скрыл имя, 30 Мая 2012 в 00:07, лекция

Краткое описание

Ранее мы познакомились с механизмом перегрузки функций, когда имеется возможность определять несколько функций, имеющих одно и то же имя при условии, что у них разные сигнатуры (списки аргументов). На основе перегрузки функций в языке С++ реализуется функциональный полиморфизм.

Вложенные файлы: 1 файл

Лекция 28. Перегрузка операторов в языке С++. Общие понятия.doc

— 122.00 Кб (Скачать файл)


Основы алгоритмизации и программирования                            Лекция 28

1. Перегрузка операторов в языке С++. Общие понятия

 

Ранее мы познакомились с механизмом перегрузки функций, когда имеется возможность определять несколько функций, имеющих одно и то же имя при условии, что у них разные сигнатуры (списки аргументов). На основе перегрузки функций в языке С++ реализуется функциональный полиморфизм.

 

В языке С++ реализован механизм перегрузки операторов. Такая перегрузка представляет собой еще один пример полиморфизма. Перегрузка операторов предоставляет возможность рассматривать операторы языка C++ сразу в нескольких смыслах. Фактически часть операторов языка C++ уже перегружены изначально. Например, оператор *, будучи примененной к адресу, дает значение, которое хранится по этому адресу. Однако, применяя оператор * к паре числовых значений, получаем произведение этих значений. Таким образом, в данном случае в языке C++ используется количество и типы операндов, чтобы решить, какое действие предпринять.

 

В отличие от функций для операторов языка С++ предусматриваются два дополнительных свойства, существенным образом влияющих на их функциональность: приоритет и ассоциативность. Действительно, встает вопрос как при записи понимать: a+b*c – как (a+b)*c или как a+(b*c)? Выражение a-b+c — это (a-b)+c или a-(b+c)?).

 

Приоритет (ранг или старшинство оператора) — формальное свойство оператора, влияющее на очередность его выполнения в выражении с несколькими различными операторами при отсутствии явного (с помощью скобок) указания на порядок их вычисления. Например, оператор умножения * обладает изначально более высоким приоритетом, чем оператор сложения +, а потому в выражении a+b*c будет получено сначала произведение b и c, а потом уже сумма.

 

Операторы могут иметь одинаковый приоритет, тогда они вычисляются по правилу ассоциативности, установленному для них. В программировании ассоциативностью операторов называют последовательность (очередность) их выполнения в случае, когда операторы имеют одинаковый приоритет и отсутствует явное (с помощью скобок) указание на очерёдность их выполнения. Причем различается левая ассоциативность, при которой вычисление выражения происходит слева–направо, и правая ассоциативность — справа–налево. Соответствующие операторы называют левоассоциативными и правоассоциативными. Например, в языке xBase СУБД Visual FoxPro большинство операторов имеет левую ассоциативность, в то время как возведение в степень правоассоциативно: x2 записывается по правилам xBase в виде x**2 или x^2.

 

В языках программирования (в том числе в языке С++) применяются два способа задания приоритетов операторов:

      Первый из них связан с распределением всех операторов по иерархии приоритетов. Этот способ всегда используется для задания приоритетов по умолчанию и фиксируется в описании языка в виде соглашения, что соответствующим операторам присваивается определенные приоритеты.

      Второй способ дает возможность изменять приоритеты по умолчанию, определяя их в явном виде с помощью парных круглых скобок. При этом глубина вложенности прямо пропорциональна величине приоритета, то есть более вложенные внутренние скобки указывают на более высокий приоритет, чем внешние, обрамляющие их.

 

Поскольку для встроенных в язык операторов всегда предусматриваются приоритеты и ассоциативность, то возникает вопрос: какие приоритеты и ассоциативность будут иметь переопределённые версии этих операторов или, тем более, новые созданные программистом операторы?

 

Кроме того, имеются и другие “тонкие моменты”, которые могут требовать уточнения. Например, в языке С++ существуют две формы операторов инкремента (увеличения) и декремента (уменьшения) значения ++ и -- — префиксная и постфиксная, поведение которых различается. Как должны вести себя перегруженные версии таких операций? Различные языки по-разному решают приведённые вопросы. Так, в языке C++ приоритет и ассоциативность перегруженных версий операторов сохраняются такими же, как и у определённых в языке, а описания перегрузки префиксной и постфиксной формы операторов инкремента и декремента используют различные сигнатуры.

 

Итак!!! Перегрузка операторов — это возможность назначать новый смысл операторам при использовании их с определенным классом.

 

Использование механизма перегрузки операторов позволяет повысить удобочитаемость программ и облегчить их понимание, выражая операторы класса в более понятном виде. При этом:

      Чтобы перегрузить оператор, необходимо определить класс, которому оператор будет назначен;

      Когда перегружаете оператор, то перегрузка действует только для класса, в котором он определяется. Если программа использует оператор с неклассовыми переменными (например, переменными типа int или float), используется стандартное определение оператора;

      Чтобы перегрузить оператор класса, необходимо использовать ключевое слово языка C++ operator для определения метода класса, который вызывается каждый раз, когда переменная класса использует оператор;

      В языке С++ разрешается перегружать следующие операторы:

+    -    *    /    %    ^    &    |    ~    !

=    <    >    +=   -=   *=   /=   %=   ^=   &=

|=   <<   >>   >>=  <<=  ==   !=   <=   >=   &&

||   ++   --   ->*  ,    ->  []   ()   new  delete

      C++ не позволяет вашим программам перегружать оператор выбора элемента (.), оператор указателя на элемент (.*), оператор разрешения области видимости (::), условный оператор сравнения (?:), оператор sizeof().

 

2. Пример перегрузки унарных и бинарных операторов в языке С++

 

Рассмотрим пример программы, в которой демонстрируется механизм перегрузки операторов. В программе определяется класс int_Matrix для организации обработки целочисленной матрицы. Внутри этого класса реализован явный конструктор, которым предусматривается генерация значений матрицы в диапазоне от 0 до 9 с помощью ГСЗ. Определен метод OutPut() для вывода матрицы на экран. В классе реализована перегрузка бинарных операторов – и +, а также перегрузка унарного оператора –.

 

1. #include <iostream>

2. #include <stdlib.h>

3. #include <time.h>

4. using namespace std;

 

5. class int_Matrix // класс для обработки целочисленной матрицы

6. { public:

7.              int_Matrix(int, int, int, int); // прототип конструктора

8.              void OutPut(int, int);              // прототип метода вывода матрицы

             

9.              //прототип бинарного оператора - (вычитания)             

10.              int_Matrix operator - (int_Matrix& F);

             

11.              //прототип бинарного оператора + (сложения)

12.              int_Matrix operator + (int_Matrix& D);

 

13.              // прототип унарного оператора -

14.              void operator - ();             

             

15.               private:

16.              enum {n=10, m=12};

17.              int int_Matr[n][m];              // поле для размещения матрицы

18. };

 

19. // определение конструктора

20. int_Matrix::int_Matrix(int n, int m, int min, int max)

21.              { for(int i=0; i<n; i++)

22.                 for(int j=0; j<m; j++)

23.                                          int_Matr[i][j]=rand()%(max-min+1)+min;}

 

24. // определение метода OutPut()

25. void int_Matrix::OutPut(int n, int m)

26.              { for(int i=0; i<n; i++) {

27.                 for(int j=0; j<m; j++) cout << int_Matr[i][j] << ' ';  28.                                                                                      cout << endl; } }

 

29. // определение бинарного оператора -

30. int_Matrix int_Matrix::operator - (int_Matrix& F)

31.              {  for(int i=0; i<n; i++)

32.                   for(int j=0; j<m; j++)

33.                                          F.int_Matr[i][j]=int_Matr[i][j]-F.int_Matr[i][j];

34.              return F;  }

 

35. // определение бинарного оператора +

36. int_Matrix int_Matrix::operator + (int_Matrix& D)

37. {  for(int i=0; i<n; i++)

38.     for(int j=0; j<m; j++)

39.                                          D.int_Matr[i][j]=int_Matr[i][j]+D.int_Matr[i][j];

40.              return D;  }

 

41. // определение унарного оператора -

42.              void int_Matrix::operator - ()             

43.              {  for(int i=0; i<n; i++)

44.     for(int j=0; j<m; j++) int_Matr[i][j]=-int_Matr[i][j]; }

 

45. int main()

46. { int n, m, min=0, max=9;

47.              srand((unsigned)time(NULL));

48.              cout << "Vvedite kol-vo strok (<=10) i stolbtsov (<=12): ";

49.              cin >> n >> m;

             

50.              cout << "\nMatritsa A:";              

51.              int_Matrix A(n,m,min,max);              cout << endl;              A.OutPut(n,m);

             

52.              cout << "\nMatritsa B:";

53.              int_Matrix B(n,m,min,max);              cout << endl;              B.OutPut(n,m);

             

54.              int_Matrix C(n,m,min,max);

             

55.              C=B; cout << "\nMatritsa A-B:\n"; C=A-C;              C.OutPut(n,m);

56.              C=B; cout << "\nMatritsa A+B:\n"; C=A+C;              C.OutPut(n,m);

57.                            cout << "\nMatritsa -B:\n";    -B;              B.OutPut(n,m);

58.              return 0;

59. }

 

В строках с 5 по 18 включительно представлено определение класса int_Matrix для организации обработки целочисленной матрицы.

 

В строках с 29 по 34 и строках с 35 по 40 определены соответственно бинарные операторы – и +.  Например, на вход оператора + (см. стр. 35) подается объект D класса int_Matrix. На основе вложенных циклов реализована операция поэлементного сложения матриц объекта D и объекта, стоящего в выражении сложения слева от +. В результате формируется матрица объекта D, которая и возвращается в качестве результата.

 

В строках с 42 по 44 определен унарный оператор –.  На основе вложенных циклов реализована операция поэлементного отрицания значений элементов матрицы, для которой выполняется эта унарная операция.

 

Функция main() определена в строках с 45 по 59 включительно. В начале определяются переменные n и m для размещения в них значений числа соответственно строк и столбцов матрицы. Переменные min и max используются для определения границ диапазона, в котором случайным образом формируются значения элементов матриц A,B,C.  Отметим, что матрица C используется как промежуточная для корректного выполнения операций A+B, A–B.

 

В строке 51 создается объект А, внутри которого с помощью ГСЗ формируется матрица целочисленных значений и ее значения выводятся на экран методом OutPut().

 

В строке 53 создается объект B, внутри которого с помощью ГСЗ формируется матрица целочисленных значений и ее значения выводятся на экран методом OutPut().

 

В строке 54 создается объект C, внутри которого с помощью ГСЗ формируется матрица целочисленных значений.

 

В строках 55 и 56 соответственно реализованы операции A–B и A+B, причем матрица C играет важную роль, поскольку в процессе реализации указанных операций необходимо, с одной стороны, сохранить исходные значения матриц A и B, с другой стороны, формировать промежуточные результаты.

 

3. Основные правила перегрузки операторов в языке С++

1)     Нельзя вводить собственные обозначения для операторов, не совпадающие со стандартными операторами языка С++.

2)     Не все операторы языка С++ могут быть перегружены. Так нельзя перегрузить:

. – прямой выбор элемента

.* – обращение к элементу через указатель на него

? : – условная операция

:: – операция указания области видимости

sizeof, # и ## – препроцессорные операции.

3)     Каждый оператор в языке имеет определенное число операндов, свой приоритет и ассоциативность. Все эти правила сохраняются и при перегрузке, а изменить их нельзя.

4)     Любой унарный оператор OP определяется двумя способами: либо как компонентная функция без параметров, либо как глобальная (возможно дружественная) функция с одним параметром. Выражение OPz означает в первом случае вызов z.operator OP(), во втором - вызов operator OP(z).

5)     Любой бинарный оператор OP определяется также двумя способами: либо как компонентная функция с одним параметром, либо как глобальная (возможно дружественная) функция с двумя параметрами. В первом случае x OP y означает вызов x.operator OP(y), во втором – вызов operator OP(x, y).

6)     Перегруженный оператор не может иметь аргументы по умолчанию.

7)     В языке С++ установлена идентичность некоторых операторов, например, ++z – это тоже, что и z += 1. Эта идентичность теряется для перегруженных операторов.

8)     Функцию operator можно вызвать по ее имени, например, z = operator * (x, y) или z = x.operator *(y). В первом случае вызывается глобальная функция, во втором – компонентная функция класса X, и x – это объект класса X. Однако, чаще всего функция operator вызывается косвенно, например, z = x * y.

9)     За исключением перегрузки операторов new и delete функция operator должна быть либо нестатической компонентной функцией, либо иметь как минимум один аргумент (операнд) типа "класс" или "ссылка на класс" (если это глобальная функция).

10) Операторы =, [ ], –> можно перегружать только с помощью нестатической компонентной функции operator OP. Это гарантирует, что первыми операндами будут леводопустимые выражения.

Информация о работе Перегрузка операторов в языке С++. Общие понятия