Лекции.ИНФО


Эта версия программы генерирует такие результаты.



1, 2, 3

10, 10, 10

11, 12, 13

22, 24, 26

1, 2, 3

1, 2, 3

2, 3, 4

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

Как вы знаете, операторы "++" и "--" имеют префиксную и постфиксную формы. Например, оператор инкремента можно использовать в форме

++0;

И в форме

0++;.

Как отмечено в комментариях к предыдущей программе, функция operator++() определяет префиксную форму оператора "++" для класса three_d. Но нам ничего не мешает перегрузить и постфиксную форму. Прототип постфиксной формы оператора "++" для класса three_d имеет следующий вид.

three_d three_d::operator++(int notused);

Операторы инкремента и декремента имеют как префиксную, так и постфиксную формы.

Параметр notused не используется самой функцией. Он служит индикатором для компилятора, позволяющим отличить префиксную форму оператора инкремента от постфиксной. (Этот параметр также используется в качестве признака постфиксной формы и для оператора декремента.) Ниже приводится один из возможных способов реализации постфиксной версии оператора "++" для класса three_d.

// Перегрузка постфиксной версии оператора "++".

three_d three_d::operator++(int notused)

{

 three_d temp = *this; // сохранение исходного значения

 x++; // инкремент координат х, у и z

 у++;

 z++;

 return temp; // возврат исходного значения

}

Обратите внимание на то, что эта функция сохраняет текущее значение операнда путем выполнения такой инструкции.

three_d temp = *this;

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

В следующей версии исходной программы реализованы обе формы оператора "++".

// Демонстрация перегрузки оператора "++" с

// использованием его префиксной и постфиксной форм.

#include <iostream>

using namespace std;

class three_d {

  int x, у, z; // 3-мерные координаты

 public:

  three_d() { x = у = z = 0; }

  three_d(int i, int j, int k) {x = i; у = j; z = k; }

  three_d operator+(three_d op2); // Операнд op1 передается неявно.

  three_d operator=(three_d op2); // Операнд op1 передается неявно.

  three_d operator++(); // префиксная версия

  three_d operator++(int notused); // постфиксная версия

  void show();

};

// Перегрузка оператора " + ".

three_d three_d::operator+(three_d op2)

{

 three_d temp;

 temp.x = x + op2.x; // Операции сложения целочисленных

 temp.у = у + ор2.у; // значений сохраняют оригинальный

 temp.z = z + op2.z; // смысл.

 return temp;

}

// Перегрузка оператора присваивания.

three_d three_d::operator=(three_d op2)

{

 x = op2.x; // Операции присваивания целочисленных

 у = ор2.у; // значений сохраняют оригинальный

 z = ор2.z; // смысл.

 return *this;

}

// Перегрузка префиксной версии оператора "++".

three_d three_d::operator++()

{

 х++; // инкремент координат х, у и z

 У++;

 z++;

 return *this;

}

// Перегрузка постфиксной версии оператора "++".

three_d three_d::operator++ (int notused)

{

 three_d temp = *this; // сохранение исходного значения

 х++; // инкремент координат х, у и z

 у++;

 z++;

 return temp; // возврат исходного значения

}

// Отображение координат X, Y, Z.

void three_d::show()

{

 cout << x << ", ";

 cout << у << ", ";

 cout << z << "";

}

Int main()

{

 three_d a(1, 2, 3), b(10, 10, 10), c;

 a.show();

 b.show();

 с = a + b; // сложение объектов а и b

 c.show();

 c=a+b+c; // сложение объектов a, b и с

 с.show();

 с = b = a; // множественное присваивание

 с.show();

 b.show();

 ++c; // префиксная форма инкремента

 c.show();

 с++; // постфиксная форма инкремента

 с.show();

 а = ++с; // Объект а получает значение объекта с после его инкрементирования.

 a.show(); // Теперь объекты а и с

 с.show(); // имеют одинаковые значения.

 а = с++; // Объект а получает значение объекта с до его инкрементирования.

 a.show(); // Теперь объекты а и с

 с.show(); // имеют различные значения.

 return 0;

}

Вот как выглядят результаты выполнения этой версии программы.

1, 2, 3

10, 10, 10

11, 12, 13

22, 24, 26

1, 2, 3

1, 2, 3

2, 3, 4

3, 4, 5

4, 5, 6

4, 5, 6

Б

5, 6, 7

Как подтверждают последние четыре строки результатов программы, при префиксном инкрементировании значение объекта c увеличивается до выполнения присваивания объекту a, при постфиксном инкрементировании — после присваивания.

Помните, что если символ "++" стоит перед операндом, вызывается операторная функция operator++(), а если после операнда — операторная функция operator++(int notused).Тот же подход используется и для перегрузки префиксной и постфиксной форм оператора декремента для любого класса. В качестве упражнения определите оператор декремента для класса three_d.

Важно! Ранние версии языка C++ не содержали различий между префиксной и постфиксной формами операторов инкремента и декремента. Тогда в обоих случаях вызывалась префиксная форма операторной функции. Это следует иметь в виду, если вам придется работать со старыми С++-программами.

Советы по реализации перегрузки операторов

Действие перегруженного оператора применительно к классу, для которого он определяется, не обязательно должно иметь отношение к стандартному действию этого оператора применительно к встроенным С++-типам. Например, операторы "<<" и ">>", применяемые к объектам cout и cin, имеют мало общего с аналогичными операторами, применяемыми к значениям целочисленного типа. Но для улучшения структурированности и читабельности программного кода создаваемый перегруженный оператор должен по возможности отражать исходное назначение того или иного оператора. Например, оператор "+", перегруженный для класса three_d, концептуально подобен оператору "+", определенному для целочисленных типов. Ведь вряд ли есть логика в определении для класса, например, оператора "+", который по своему действию больше напоминает оператор деления (/). Таким образом, основная идея создания перегруженного оператора — наделить его новыми (нужными для вас) возможностями, которые, тем не менее, связаны с его первоначальным назначением.

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

. :: .* ?

Оператор ".*" — это оператор специального назначения (он рассматривается ниже в этой книге).

О значении порядка операндов

Перегружая бинарные операторы, помните, что во многих случаях порядок следования операндов имеет значение. Например, выражение А+В коммутативно, а выражение А-В — нет. (Другими словами, А - В не то же самое, что В - А!) Следовательно, реализуя перегруженные версии некоммутативных операторов, необходимо помнить, какой операнд стоит слева от символа операции, а какой — справа от него. Например, в следующем фрагменте кода демонстрируется перегрузка оператора вычитания для класса three_d.

// Перегрузка оператора вычитания.

three_d three_d::operator-(three_d op2)

{

 three_d temp;

 temp.x = x - op2.x;

 temp.у = у - op2.y;

 temp.z = z - op2.z;

 return temp;

}

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

Х - ор2.х.

Перегрузка операторов с использованием функций-не членов класса

Бинарные операторные функции, которые не являются членами класса, имеют два параметра, а унарные (тоже не члены) — один.

Перегрузку оператора для класса можно реализовать и с использованием функции, не являющейся членом этого класса. Такие функции часто определяются "друзьями" класса. Как упоминалось выше, функции-не члены (в том числе и функции-"друзья") не имеют указателя this. Следовательно, если для перегрузки бинарного оператора используется функция-"друг", явным образом передаются оба операнда. Если же с помощью функции-"друга" перегружается унарный оператор, операторной функции передается один оператор. С использованием функций-не членов класса нельзя перегружать такие операторы:

=, (), [] и ->.

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

// Перегрузка оператора "+" с помощью функции-"друга".

#include <iostream>

using namespace std;

class three_d {

  int x, y, z; // 3-мерные координаты

 public:

  three_d() { x = у = z = 0; }

  three_d(int i, int j, int k) { x = i; у = j; z = k; }

  friend three_d operator+(three_d op1, three_d op2);

  three_d operator= (three_d op2); // Операнд op1 передается неявно.

  void show();

};

// Теперь это функция-"друг".

three_d operator+(three_d op1, three_d op2)

{

 three_d temp;

 temp.x = op1.x + op2.x;

 temp.у = op1.у + op2.y;

 temp.z = op1.z + op2.z;

 return temp;

}

// Перегрузка присваивания.

three_d three_d::operator=(three_d op2)

{

 x = op2.x;

 у = op2.у;

 z = op2.z;

 return *this;

}

// Отображение координат X, Y, Z.

void three_d::show()

{

 cout << x << ", ";

 cout << у << ", ";

 cout << z << "";

}

Int main()

{

 three_d a(1, 2, 3), b(10, 10, 10), c;

 a.show();

 b.show();

 с = a + b; // сложение объектов а и b

 c.show();

 c=a+b+c; // сложение объектов a, b и с

 с.show();

 с = b = а; // демонстрация множественного присваивания

 с.show();

 b.show();

 return 0;

}

Как видите, операторной функции operator+() теперь передаются два операнда. Левый операнд передается параметру op1, а правый — параметру ор2.

Во многих случаях при перегрузке операторов с помощью функций-"друзей" нет никакого преимущества по сравнению с использованием функций-членов класса. Однако возможна ситуация (когда нужно, чтобы слева от бинарного оператора стоял объект встроенного типа), в которой функция-"друг" оказывается чрезвычайно полезной. Чтобы понять это, рассмотрим следующее. Как вы знаете, указатель на объект, который вызывает операторную функцию-член, передается с помощью ключевого слова this. При использовании бинарного оператора функцию вызывает объект, расположенный слева от него. И это замечательно при условии, что левый объект определяет заданную операцию. Например, предположим, что у нас есть некоторый объект ob, для которого определена операция сложения с целочисленным значением, тогда следующая запись представляет собой вполне допустимое выражение.

ob + 10; // будет работать

Поскольку объект ob стоит слева от оператора "+", он вызывает перегруженную операторную функцию, которая (предположительно) способна выполнить операцию сложения целочисленного значения с некоторым элементом объекта ob. Но эта инструкция работать не будет.

10 + ob; // не будет работать

Дело в том, что в этой инструкции объект, расположенный слева от оператора "+", представляет собой целое число, т.е. значение встроенного типа, для которого не определена ни одна операция, включающая целое число и объект классового типа.

Решение описанной проблемы состоит в перегрузке оператора "+" с использованием двух функций-"друзей" В этом случае операторной функции явным образом передаются оба аргумента, и она вызывается подобно любой другой перегруженной функции, т.е. на основе типов ее аргументов. Одна версия операторной функции operator+() будет обрабатывать аргументы объект + int-значение, а другая — аргументы int-значение + объект. Перегрузка оператора "+" (или любого другого бинарного оператора) с использованием функций-"друзей" позволяет ставить значение встроенного типа как справа, так и слева от оператора. Реализация этого решения показана в следующей программе.

#include <iostream>

using namespace std;

class CL {

 public:

  int count;

  CL operator=(CL obj);

  friend CL operator+(CL ob, int i);

  friend CL operator+(int i, CL ob);

};

CL CL::operator=(CL obj)

{

 count = obj.count;

 return *this;

}

// Эта версия обрабатывает аргументы

// объект + int-значение.

CL operator+(CL ob, int i)

{

 CL temp;

 temp.count = ob.count + i;

 return temp;

}

// Эта версия обрабатывает аргументы

// int-значение + объект.

CL operator+(int i, CL ob)

{

 CL temp;

 temp.count = ob.count + i;

 return temp;

}

Int main()

{

 CL o;

 o.count = 10;

 cout << o.count << " "; // выводит число 10

 o=10+o; // сложение числа с объектом

 cout << o.count << " "; // выводит число 20

 o=o+12; // сложение объекта с числом

 cout << 0.count; // выводит число 32

 return 0;

}

Как видите, операторная функция operator+() перегружается дважды, позволяя тем самым предусмотреть два возможных способа участия целого числа и объекта типа CL в операции сложения.

Использование функций-"друзей" для перегрузки унарных операторов

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

// Перегрузка префиксной формы оператора "++".

three_d three_d::operator++()

{

 х++;

 у++;

 z++;

 return *this;

}

Как вы знаете, каждая функция-член получает (в качестве неявно переданного) аргумент this, который является указателем на объект, вызвавший эту функцию. При перегрузке унарного оператора с помощью функции-члена аргументы явным образом не передаются вообще. Единственным аргументом, необходимым в этой ситуации, является неявный указатель на вызывающий объект. Любые изменения, вносимые в данные объекта, повлияют на объект, для которого была вызвана эта операторная функция. Следовательно, при выполнении инструкции х++ (в предыдущей функции) будет инкрементирован член х вызывающего объекта.

В отличие от функций-членов, функции-не члены (в том числе и "друзья" класса) не получают указатель this и, следовательно, не имеют доступа к объекту, для которого они были вызваны. Но мы знаем, что "дружественной" операторной функции операнд передается явным образом. Поэтому попытка создать операторную функцию-"друга" operator++() в таком виде успехом не увенчается.

// ЭТОТ ВАРИАНТ РАБОТАТЬ НЕ БУДЕТ

three_d operator++(three_d op1)

{

 op1.x++;

 op1.y++;

 op1.z++;

 return op1;

}

Эта функция неработоспособна, поскольку только копия объекта, активизировавшего вызов функции operator++(), передается функции через параметр op1. Таким образом, изменения в теле функции operator++() не повлияют на вызывающий объект, они изменяют только локальный параметр.

Если вы хотите для перегрузки операторов инкремента или декремента использовать функцию-"друга", необходимо передать ей объект по ссылке. Поскольку ссылочный параметр представляет собой неявный указатель на аргумент, то изменения, внесенные в параметр, повлияют и на аргумент. Применение ссылочного параметра позволяет функции успешно инкрементировать или декрементировать объект, используемый в качестве операнда.

Если для перегрузки операторов инкремента или декремента используется функция-"друг", ее префиксная форма принимает один параметр (который и является операндом), а постфиксная форма — два параметра (вторым является целочисленное значение, которое не используется).

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

// В этой программе используются перегруженные

// операторные функции-"друзья" operator++() .

#include <iostream>

using namespace std;

class three_d {

  int x, y, z; // 3-мерные координаты

 public:

  three_d() { x = у = z = 0; }

  three_d(int i, int j, int k) {x = i; у = j; z = k; }

  friend three_d operator+(three_d op1, three_d op2);

  three_d operator=(three_d op2);

  // Эти функции для перегрузки

  // оператора "++" используют ссылочные параметры.

  friend three_d operator++(three_d &op1);

  friend three_d operator++(three_d &op1, int notused);

  void show();

};

// Теперь это функция-"друг".

three_d operator+(three_d op1, three_d op2)

{

 three_d temp;

 temp.x = op1.x + op2.x;

 temp.у = op1.у + op2.y;

 temp.z = op1.z + op2.z;

 return temp;

}

// Перегрузка оператора "=".

three_d three_d::operator=(three_d op2)

{

 x = op2.x;

 у = op2.y;

 z = op2.z;

 return *this;

}

/* Перегрузка префиксной версии оператора "++" с использованием функции-"друга". Для этого необходимо использование ссылочного параметра.

*/

three_d operator++(three_d &op1)

{

 op1.х++;

 op1.у++;

 op1.z++;

 return op1;

}

/* Перегрузка постфиксной версии оператора "++" с использованием функции-"друга". Для этого необходимо использование ссылочного параметра.

*/

three_d operator++(three_d &op1, int notused)

{

 three_d temp = op1;

 op1.x++;

 op1.у++;

 op1.z++;

 return temp;

}

// Отображение координат X, Y, Z.

void three_d:: show()

{

 cout << x << ", ";

 cout << у << ", ";

 cout << z << "";

}

Int main()

{

 three_d a(1, 2, 3), b(10, 10, 10), c;

 a.show();

 b.show();

 с = a + b; // сложение объектов а и b

 c.show();

 c=a+b+c; // сложение объектов a, b и с

 с.show();

 с = b = a; // демонстрация множественного присваивания

 с.show();

 b.show();

 ++c; // префиксная версия инкремента

 c.show();

 с++; // постфиксная версия инкремента

 с. show();

 а = ++с; // Объект а получает значение объекта с после инкрементирования.

 a.show(); // В этом случае объекты а и с

 с.show(); // имеют одинаковые значения координат.

 а = C++; // Объект а получает значение объекта с до инкрементирования.

 a.show(); // В этом случае объекты а и с

 с.show(); // имеют различные значения координат.

 return 0;

}

Узелок на память. Для реализации перегрузки операторов следует использовать функции-члены. Функции-"друзья" используются в C++ в основном для обработки специальных ситуаций.

Перегрузка операторов отношения и логических операторов

Операторы отношений (например, "==" или "<") и логические операторы (например, "&&" или "||") также можно перегружать, причем делать это совсем нетрудно. Как правило, перегруженная операторная функция отношения возвращает объект класса, для которого она перегружается. А перегруженный оператор отношения или логический оператор возвращает одно из двух возможных значений: true или false. Это соответствует обычному применению этих операторов и позволяет использовать их в условных выражениях.

Рассмотрим пример перегрузки оператора "==" для уже знакомого нам класса three_d.

// Перегрузка оператора "=="

bool three_d::operator==(three_d op2)

{

 if((x == op2.x) && (y == op2.y) && (z == op2.z)) return true;

 else return false;

}

Если считать, что операторная функция operator==() уже реализована, следующий фрагмент кода совершенно корректен.

three_d а, b;

 // ...

if(а == b) cout << "а равно b";

else cout << "а не равно b";

Поскольку операторная функция operator==() возвращает результат типа bool, ее можно использовать для управления инструкцией if. В качестве упражнения попробуйте реализовать и другие операторы отношений и логические операторы для класса three_d.

Подробнее об операторе присваивания

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

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









Читайте также:

Последнее изменение этой страницы: 2016-03-17; Просмотров: 104;


lektsia.info 2017 год. Все права принадлежат их авторам! Главная