Главная страница
Навигация по странице:

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

  • Узелок на память.

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

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

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

  • Шилдт c++_базовый_курс издание 3. Герберт Шилдт С базовый курс


    Скачать 9.37 Mb.
    НазваниеГерберт Шилдт С базовый курс
    АнкорШилдт c++_базовый_курс издание 3.pdf
    Дата13.02.2017
    Размер9.37 Mb.
    Формат файлаpdf
    Имя файлаШилдт c++_базовый_курс издание 3.pdf
    ТипКнига
    #2637
    страница15 из 33
    1   ...   11   12   13   14   15   16   17   18   ...   33
    Глава 13: Перегрузка операторов
    В C++ операторы можно перегружать для "классовых" типов, определяемых программистом. Принципиальный выигрыш от перегрузки операторов состоит в том, что она позволяет органично интегрировать новые типы данных в среду программирования.
    Перегружая оператор, можно определить его значение для конкретного класса.
    Например, класс, который определяет связный список, может использовать оператор "+"
    для добавления объекта к списку. Класс, которые реализует стек, может использовать оператор "+" для записи объекта в стек. В каком-нибудь другом классе тот же оператор "+"
    мог бы служить для совершенно иной цели. При перегрузке оператора ни одно из оригинальных его значений не теряется. Перегруженный оператор (в своем новом качестве)
    работает как совершенно новый оператор. Поэтому перегрузка оператора "+" для обработки, например, связного списка не приведет к изменению его функции (т.е. операции сложения) по отношению к целочисленным значениям.
    Перегрузка операторов тесно связана с перегрузкой функций. Чтобы перегрузить оператор, необходимо определить значение новой операции для класса, к которому она будет применяться. Для этого создается функция operator (операторная функция), которая определяет действие этого оператора. Общий формат функции operator таков.
    тип имя_класса::operator#(список_аргументов)
    {
    операция_над_классом
    }
    Операторы перегружаются с помощью функции operator.
    Здесь перегружаемый оператор обозначается символом "#", а элемент тип представляет собой тип значения, возвращаемого заданной операцией. И хотя он в принципе может быть любым, тип значения, возвращаемого функцией operator, часто совпадает с именем класса,
    для которого перегружается данный оператор. Такая корреляция облегчает использование перегруженного оператора в составных выражениях. Как будет показано ниже, конкретное значение элемента список_аргументов определяется несколькими факторами.
    Операторная функция может быть членом класса или не быть им. Операторные функции, не являющиеся членами класса, часто определяются как его "друзья".
    Операторные функции-члены и функции-не члены класса различаются по форме перегрузке. Каждый из вариантов мы рассмотрим в отдельности.
    Перегрузка операторов с использованием функций-членов
    Начнем с простого примера. В следующей программе создается класс three_d, который поддерживает координаты объекта в трехмерном пространстве. Для класса three_d
    перегружаются операторы "+" и "=". Итак, рассмотрим внимательно код этой программы.
    // Перегрузка операторов с помощью функций-членов.

    #include
    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; }
    three_d operator+(three_d op2); // Операнд op1 передается неявно.
    three_d operator=(three_d op2); // Операнд op1 передается неявно.
    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 = op2.z; // смысл.
    return *this;
    }
    // Отображение координат X, Y, Z.
    void three_d::show()
    {
    cout << x << ", ";
    cout << у << ", ";
    cout << z << "\n";
    }
    int main()
    {
    three_d a(1, 2, 3), b(10, 10, 10), c;
    a.show();
    b.show();
    c=a+b; // сложение объектов а и b c.show();
    c=a+b+c; // сложение объектов a, b и с с.show();
    c=b=a; // демонстрация множественного присваивания с.show();
    b.show();
    return 0;
    }
    При выполнении эта программа генерирует такие результаты.
    1, 2, 3 10, 10, 10 11, 12, 13 22, 24, 26 1, 2, 3 1, 2, 3
    Исследуя код этой программы, вы, вероятно, удавились, увидев, что обе операторные функции имеют только по одному параметру, несмотря на то, что они перегружают бинарные операции. Это, на первый взгляд, "вопиющее" противоречие можно легко объяснить. Дело в том, что при перегрузке бинарного оператора с использованием функции- члена ей передается явным образом только один аргумент. Второй же неявно передается через указатель this. Таким образом, в строке temp.x = х + ор2.х;
    под членом х подразумевается член this->x, т.е. член х связывается с объектом, который вызывает данную операторную функцию. Во всех случаях неявно передается объект,
    указываемый слева от символа операции, который стал причиной вызова операторной функции. Объект, располагаемый с правой стороны от символа операции, передается этой функции в качестве аргумента. В общем случае при использовании функции-члена для перегрузки унарного оператора параметры не используются вообще, а для перегрузки бинарного — только один параметр. (Тернарный оператор "?" перегружать нельзя.) В
    любом случае объект, который вызывает операторную функцию, неявно передается через указатель this.
    Чтобы понять, как работает механизм перегрузки операторов, рассмотрим внимательно предыдущую программу, начиная с перегруженного оператора "+". При обработке двух объектов типа three_d оператором "+" выполняется сложение значений соответствующих координат, как показано в функции operator+(). Но заметьте, что эта функция не модифицирует значение ни одного операнда. В качестве результата операции эта функция возвращает объект типа three_d, который содержит результаты попарного сложения координат двух объектов. Чтобы понять, почему операция "+" не изменяет содержимое ни одного из объектов-участников, рассмотрим стандартную арифметическую операцию сложения, примененную, например, к числам 10 и 12. Результат операции 10+12 равен 22,
    но при его получении ни 10, ни 12 не были изменены. Хотя не существует правила, которое бы не позволяло перегруженному оператору изменять значение одного из его операндов, все же лучше, чтобы он не противоречил общепринятым нормам и оставался в согласии со своим оригинальным назначением.
    Обратите внимание на то, что функция operator+() возвращает объект типа three_d.
    Несмотря на то что она могла бы возвращать значение любого допустимого в C++ типа, тот факт, что она возвращает объект типа three_d, позволяет использовать оператор "+" в таких составных выражениях, как a+b+с. Часть этого выражения, а+Ь, генерирует результат типа
    three_d, который затем суммируется с объектом с. И если бы эта часть выражения генерировала значение иного типа (а не типа three_d), такое составное выражение попросту не работало бы.
    В отличие от оператора "+", оператор присваивания приводит к модификации одного из своих аргументов. (Прежде всего, это составляет саму суть присваивания.) Поскольку функция operator=() вызывается объектом, который расположен слева от символа присваивания (=), именно этот объект и модифицируется в результате операции присваивания. После выполнения этой операции значение, возвращаемое перегруженным оператором, содержит объект, указанный слева от символа присваивания. (Такое положение вещей вполне согласуется с традиционным действием оператора "=".) Например, чтобы можно было выполнять инструкции, подобные следующей а = b = с = d;
    необходимо, чтобы операторная функция operator=() возвращала объект, адресуемый указателем this, и чтобы этот объект располагался слева от оператора "=". Это позволит выполнить любую цепочку присваиваний. Операция присваивания — это одно из самых важных применений указателя this.
    Узелок на память. Если для перегрузки бинарного оператора используется функция-
    член, объект, стоящий слева от оператора, вызывает операторную функцию и передается

    ей неявно через указатель this. Объект, расположенный справа от оператора, передается
    операторной функции как параметр.
    Использование функций-членов для перегрузки унарных операторов
    Можно также перегружать такие унарные операторы, как "++", "--", или унарные "-" и
    "+". Как упоминалось выше, при перегрузке унарного оператора с помощью функции-члена операторной функции ни один объект не передается явным образом. Операция же выполняется над объектом, который генерирует вызов этой функции через неявно переданный указатель this. Например, рассмотрим расширенную версию предыдущего примера программы. В этом варианте для объектов типа three_d определяется операция инкремента.
    // Перегрузка унарного оператора.
    #include
    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; }
    three_d operator+(three_d op2); // Операнд op1 передается неявно.
    three_d operator=(three_d op2); // Операнд op1 передается неявно.
    three_d operator++(); // префиксная версия оператора ++
    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 = op2.z; // смысл.
    return *this;
    }
    // Перегруженная префиксная версия оператора "++".
    three_d three_d::operator++()
    {
    х++; // инкремент координат х, у и z у++;
    z++;
    return *this;
    }
    // Отображение координат X, Y, Z.
    void three_d::show()
    {
    cout << x << ", ";
    cout << у << ", ";
    cout << z << "\n";
    }
    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();
    return 0;
    }
    Эта версия программы генерирует такие результаты.
    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
    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 << "\n";
    }
    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 4, 5, б
    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.х.
    1   ...   11   12   13   14   15   16   17   18   ...   33


    написать администратору сайта