Нижегородский государственный университет им. Н.И.Лобачевского
Опубликован: 25.11.2008 | Доступ: свободный | Студентов: 9593 / 1296 | Оценка: 4.06 / 3.66 | Длительность: 21:16:00
Лекция 15:

Классы. Создание новых типов данных

< Лекция 14 || Лекция 15: 12345 || Лекция 16 >

14.6. Переопределение операций (резюме)

Создавая новый тип данных, мы были вынуждены объяснить компилятору, как надлежит выполнять обычные операции (сложения, вычитания, сравнения, ввод/вывод и т.п.) с объектами нового типа. По сути дела, операции – это простейшие функции, на вход которых поступает один или два операнда, над ними выполняется обусловленное действие и формируется возвращаемое значение. В этом смысле переопределение операций, практически, ничем не отличается от переопределения функций. В самом общем виде операция переопределяется следующим образом:

тип\_ результата \ имя\_ класса::operator\otimes (список\_ аргументов) 
\\
\{  //процедура \ выполнения \ новой \ операции \}

Здесь \otimes – обозначение знака операции.

Единственное отличие от переопределения функции заключается в специфическом имени, заменяющем имя функции.

В связи с тем, что должна быть сохранена логика обработки компилятором арифметических и логических формул, процедур ввода/вывода, операторов присваивания, инкрементирования и декрементирования, на переопределение операций накладываются три важных ограничения:

  • приоритет операций, описанный в стандарте языка C++, менять нельзя;
  • количество операндов, которое изначально было объявлено в языке, для каждой операции должно быть сохранено;
  • нельзя переопределять операции '.' (разделитель в составных именах), '.*' (обращение к полю объекта через указатель), '::' (спецификатор принадлежности), '?:' (условное выражение), '#' (директива препроцессора), '##' (операция конкатенации в препроцессоре).

При написании процедуры переопределения двухместной операции a1\otimes a2 надо помнить, что аргумент a1, расположенный левее знака операции, передается в функцию-член класса двумя способами. Во-первых, как поля объекта, объявленные в качестве членов-данных класса. Во-вторых, как скрытый указатель this. Поэтому первый операнд операции как аргумент в функции переопределения не указывается.

Еще одно ограничение на функцию переопределения операции заключается в том, что среди ее параметров нельзя пользоваться значениями по умолчанию.

Рассмотрим варианты переопределения двухместных операций на примере класса Tpoint, представляющего точку с парой целочисленных координат:

class Tpoint {
  int x,y;
public:
  Tpoint(){x=0;y=0;}	//конструктор по умолчанию
  Tpoint(int xx,int yy){x=xx;y=yy;}	//конструктор инициализации
  void GeTpoint(int &xx,int &yy){xx=x; yy=y;}	//опрос координат
  Tpoint operator+(Tpoint P2);
};

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

Tpoint Tpoint::operator+(Tpoint P2)
{ Tpoint q;
  q.x=x+P2.x; q.y=y+P2.y;
  return q;
}

Более экономный вариант функции сложения заключается в использовании ссылки на объект P2:

Tpoint Tpoint::operator+(Tpoint &P2)
{ Tpoint q;
  q.x=x+P2.x; q.y=y+P2.y;
  return q;
}

Во-первых, ссылка на объект P2 – это 4 байта, тогда как передача значения P2 потребовала бы передачи двух четырехбайтовых координат. Во-вторых, для значения P2, являющегося формальным параметром первого варианта функции, пришлось бы выделять временную память (как и для локальной переменной q ), а при выходе из функции эту память освобождать.

Операция присваивания над объектами типа Tpoint a1=a2 тоже является двухместной, но ее переопределение использует указатель this, чтобы возвратить значение:

Tpoint Tpoint::operator=(Tpoint &P2)
{ x=P2.x; y=P2.y; return *this; }

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

int Tpoint::operator==( Tpoint &P2)
{ return (x==P2.x)&&(y==P2.y); }

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

Tpoint Tpoint::operator-(Tpoint &P2)
{ Tpoint q;
  q.x=x-P2.x; q.y=y-P2.y; return q;
}
Tpoint Tpoint::operator-()
{ x=-x; y=-y; return *this; }

Мы уже упоминали, что с операциями левого и правого декремента ситуация несколько сложнее. Операция ++P1 мало чем отличается от унарной смены знака -P1, и ее переопределение вопросов не вызывает:

Tpoint Tpoint::operator++()
{ x++; y++; return *this; }

А вот для операции P1++ в реализации C++ пришлось придумывать специальный выход – было решено, что если знак инкремента (или декремента) следует за операндом, то после этого знака якобы появляется фиктивный нулевой операнд. Поэтому соответствующее переопределение операции P1++ "использует" фиктивный формальный параметр:

Tpoint Tpoint::operator++(int P)
{ Tpoint q=*this;
  x++; y++; return q; }

Иногда для переопределения операций прибегают к услугам дружественных функций. Хотя дружественные функции и имеют доступ к приватным данным класса, но указатель this они не получают. Поэтому, например, с их помощью нельзя переопределить операцию '='. А другие унарные или бинарные операции такому переопределению поддаются легко, надо только передавать в дружественные функции на один параметр больше:

class Tpoint {
  int x,y;
public:
  Tpoint(){x=0;y=0;}	//конструктор по умолчанию
  Tpoint(int xx,int yy){x=xx;y=yy;}	//конструктор инициализации
  void GeTpoint(int &xx,int &yy){xx=x; yy=y;}	//опрос координат
  friend Tpoint operator+(Tpoint P1,Tpoint P2);
};
Tpoint Tpoint::operator+(Tpoint P1,Tpoint P2)
{ Tpoint q;
  q.x=P1.x+P2.x; q.y=P1.y+P2.y; return q;
}

О специфике переопределения операций потокового ввода и вывода мы уже упоминали при проектировании класса Rational. Продемонстрируем это еще раз на примере ввода и вывода данных типа "точка". Предположим, что нас устраивает следующий формат представления "точек" на вводе и выводе – ( x,y ), где x и y представляют целочисленные значения соответствующих координат. Напоминаем, что среди аргументов функций должна быть ссылка на входной ( istream & ) или выходной ( ostream & ) поток. Ввод или вывод должен быть организован по указанной ссылке и в качестве возвращаемого результата должна быть указана эта ссылка. Поэтому тип возвращаемого значения тоже должен быть ссылкой на входной или выходной поток:

#include <iostream.h>
#include <conio.h>
class Tpoint {
  int x,y;
public:
  Tpoint(){x=0;y=0;}	//конструктор по умолчанию
  Tpoint(int xx,int yy){x=xx;y=yy;}	//конструктор инициализации
  void GetPoint(int &xx,int &yy){xx=x; yy=y;}	//опрос координат
  friend istream& operator>>(istream& t,Tpoint &P);
  friend ostream& operator<<(ostream& t,Tpoint &P);
};
//ввод в формате (x,y) (дружественная функция)
  istream& operator>>(istream& t,Tpoint &P)
{ char c; //для чтения разделителей /
  t >> c >> P.x >> c >> P.y >> c;
  return t; }
//-------------------------------------------------------
// вывод в формате (x,y) (дружественная функция)
  ostream& operator<<(ostream& t,Tpoint &P)
{ t<<'('<<P.x<<','<<P.y<<')'; return t; }
void main()
{ Tpoint P(10,20);
  cout<<P<<endl;
  cin>>P;
  cout<<P<<endl;
  getch();
}
//=== Результат работы
(10,20)
(30,50) <Enter>        //введена точка с новыми координатами
(30,50)
< Лекция 14 || Лекция 15: 12345 || Лекция 16 >
Alexey Ku
Alexey Ku

Попробуйте часть кода до слова main заменить на 

#include "stdafx.h" //1

#include <iostream> //2
#include <conio.h>

using namespace std; //3

Александр Талеев
Александр Талеев

#include <iostream.h>
#include <conio.h>
int main(void)
{
int a,b,max;
cout << "a=5";
cin >> a;
cout <<"b=3";
cin >> b;
if(a>b) max=a;
else max=b;
cout <<" max="<<max;
getch();
return 0;
}

при запуске в visual express выдает ошибки 

Ошибка    1    error C1083: Не удается открыть файл включение: iostream.h: No such file or directory    c:\users\саня\documents\visual studio 2012\projects\проект3\проект3\исходный код.cpp    1    1    Проект3

    2    IntelliSense: не удается открыть источник файл "iostream.h"    c:\Users\Саня\Documents\Visual Studio 2012\Projects\Проект3\Проект3\Исходный код.cpp    1    1    Проект3

    3    IntelliSense: идентификатор "cout" не определен    c:\Users\Саня\Documents\Visual Studio 2012\Projects\Проект3\Проект3\Исходный код.cpp    6    1    Проект3

    4    IntelliSense: идентификатор "cin" не определен    c:\Users\Саня\Documents\Visual Studio 2012\Projects\Проект3\Проект3\Исходный код.cpp    7    1    Проект3

при создании файла я выбрал пустой проект. Может нужно было выбрать консольное приложение?