Програмен език C++
Част 41
.
Презареждането на унарните оператори
(Overloading Unary Operators).
.
(съдържание)
.
В предишната лекция се запознахме с т.н. презаредени бинарни оператори, които позволяват използването на един и същ оператор с различни по тип операнди, и който оператор извършва различни действия, в зависимост от типа на тези операнди. В настоящата лекция ще разгледаме презареждането на унарни оператори, като операторите за нарастване и намаляване [increment (++) and decrement (--) operators], операторът за знак минус (отрицателна стойност) и логическия оператор не [logical not (!) operator].

Най-често се презареждат от програмистите операторите за нарастване и намаляване, както в тяхната префиксна форма, така и в постфиксната форма - за тези оператори вижте т. 3 на лекция 5.
.

1. Функцията-оператор за префиксната версия на оператора за нарастване (Prefix Version of Operator ++). Повечето унарни оператори оперират върху аргумент, който се намира в дясно от тях, но както знаем, този оператор (както и този за намаляване, --) има две форми. Първо ще разгледаме префиксната му форма, за която функцията за презареждане на оператора ++ е следната:

   SomeClass operator ++ ()
   {
      // here are the statements for the increment
      // of member data of SomeClass

      return SomeClass(...);       // return object of SomeClass
   }

В случая е дадена общата форма за произволен клас SomeClass. Последният ред във функцията на класа извиква съответен конструктор (едно аргументен или двуаргументен и пр.), който връща обект от класа SomeClass. Обърнете внимание на последното твърдение - това позволява операторът да се използва във вида:

 SomCalssObj2 = ++SomCalssObj1;

Обърнете внимание, че функцията-оператор operator++() няма аргументи, работи с обекта, за който се извиква и връща стойност, която е от типа на класа.

В курса на Лафоор [1] е предложена следната версия на оператора:

      // overloaded prefix ++ operator
      airtime operator++ ()
      {
         ++minutes;          // increment this object
         if (minutes >= 60)
         {
            ++hours;
            minutes -= 60;
         }

         // return new value
         return airtime(hours, minutes);
      }

По същия начин може да се напише и функцията-оператор за префиксната версия на оператора за намаляване --:

      // overloaded prefix -- operator
      airtime operator-- ()
      {
         --minutes;          // decrement this object
         if (minutes < 0)
         {
            --hours;
            minutes = 59;
         }

         // return new value
         return airtime(hours, minutes);
      }

Нека разгледаме следната програма prefix.cpp от курса на Лафоор [1], в която е презареден префиксният оператор за нарастване за класа airtime, и която е изменена от нас с добавянето на префиксен оператор за намаляване --.

// prefix.cpp
// overloads the ++ operator, prefix version

#include <iostream.h>
#include <conio.h>           // for getch()
#include <iomanip.h>         // for setw() & setfill()

class airtime
{
   private:
      int hours;             // 0 to 23
      int minutes;           // 0 to 59

   public:
      // no-arg constructor
      airtime() : hours(0), minutes(0)
      {  }

      // 2-arg constructor
      airtime(int h, int m) : hours(h), minutes(m)
      {  }

      // output to screen
      void display() const
      {
         cout << hours << ':'
              << setw(2) << setfill('0')
              << minutes;
      }

      // input from user
      void get()
      {
         char dummy;
         cout << "\nEnter time (format 12:59): ";
         cin >> hours >> dummy >> minutes;
      }

      // overloaded prefix ++ operator
      airtime operator++ ()
      {
         ++minutes;          // increment this object
         if (minutes >= 60)
         {
            ++hours;
            minutes -= 60;
         }

         // return new value
         return airtime(hours, minutes);
      }

      // overloaded prefix -- operator
      airtime operator-- ()
      {
         --minutes;          // decrement this object
         if (minutes < 0)
         {
            --hours;
            minutes = 59;
         }

         // return new value
         return airtime(hours, minutes);
      }
};  // end class airtime

void main()
{
   airtime at1, at2;         // make two airtimes
   at1.get();                // get value for one

   ++at1;                    // increment it

   cout << "\nat1=";
   at1.display();            // display result

// at1++;                    // error: postfix

   at2 = ++at1;              // increment again, and assign

   cout << "\nat2=";
   at2.display();            // display assigned value

   --at1;                    // increment it

   cout << "\nat1=";
   at1.display();            // display result

// at1--;                    // error: postfix -- operator

   at2 = --at1;              // increment again, and assign

   cout << "\nat2=";
   at2.display();            // display assigned value

   getch();
}   // main()

В програмата операторът ++ първо се използва във вида ++at1;, което извършва нарастване на минутите на at1 с единица. Ако погледнете функцията оператор operator++() ще видите, че когато минутите станат равни на 60, те се нулират и часът се увеличава. Разбира се, вие може да презаредите оператора за нарастване така, както вие искате - например само часът да се увеличава с единица, което евентуално би отговаряло на смяна на часовите зони.

Второто извикване на този оператор в главната функция на програмата е от вида at2 = ++at1;. Именно заради този начин на извикване, функцията-оператор трябва да връща стойност, която е от типа на класа. Обърнете внимание, че функцията-оператор е направена така, че първо се променя обектът, а после се връща неговата стойност! Точно това е и замисълът на префиксната форма.

След това, аналогично на първите две извиквания, се извиква два пъти и операторът за намаляване. Ето екранът, който програмата дава, когато потребителят вкара час 8:59.

Enter time (format 12:59): 8:59

at1=9:00
at2=9:01
at1=9:00
at2=8:59

Обърнете внимание на двата реда в програмата

// at1++;      // error: postfix ++ operator
// at1--;      // error: postfix -- operator

които са в коментари, защото е невъзможно да извикате постфиксната версия на оператора, която още не е презаредена.

2. Функцията-оператор за постфиксната версия на оператора за нарастване (Postfix Version of Operator ++). В тази част ще разгледаме примерна програма за постфиксната версия на оператора за нарастване, както и този за намаляване. Създателите на C++ са решили да различават префиксните и постфиксните оператори само чрез добавянето на фиктивен аргумент (dummy argument) в прототипа на функцията-оператор при постфиксния оператор. Този аргумент е от целочислен тип и въобще не участва в никакви твърдения, свързани с действието на оператора. Например за класа airtime разликата в прототипите на префиксния и постфиксния оператори за нарастване е следната:

 airtime operator++ ()       // prefix version
 airtime operator++ (int)    // postfix version

Разбира се, постфиксният оператор при следното извикване

at2 = at1++;              // increment again, and assign

действа по следния начин: първо предава предишната стойността на обекта at1 на обекта at2, а чак след това увеличава минутите в at1. Запазването на предишната стойност се осъществява чрез временен обект temp, който се инициализира с двуаргументен конструктор с часа и минутите на обекта, който променяме.

    // overloaded postfix ++ operator
    airtime operator++ (int)
    {
       airtime temp(hours, minutes); // save original value
       ++minutes;                    // increment this object

       if (minutes >= 60)
       {
          ++hours;
          minutes -= 60;
       }
       // return old original value
       return temp;
    }

Нека разгледаме следната програма postfix.cpp от курса на Лафоор [1], в която е презареден постфиксеният оператор ++ за класа airtime, и която е изменена от нас с добавянето на постфиксен оператор за намаляване --.

// postfix.cpp
// overloads the ++ and -- operators, postfix version

#include <iostream.h>
#include <conio.h>                     // for getch()
#include <iomanip.h>         // for setw() & setfill()

class airtime
{
   private:
      int hours;             // 0 to 23
      int minutes;           // 0 to 59

   public:
      // no-arg constructor
      airtime() : hours(0), minutes(0)
      {  }

      // 2-arg constructor
      airtime(int h, int m) : hours(h), minutes(m)
      {  }

      // output to screen
      void display() const
      {
         cout << hours << ':'
              << setw(2) << setfill('0')
              << minutes;
      }

      // input from user
      void get()
      {
         char dummy;
         cout << "\nEnter time (format 12:59): ";
         cin >> hours >> dummy >> minutes;
      }

      // overloaded postfix -- operator
      airtime operator-- (int)
      {
         airtime temp(hours, minutes); // save original value
         --minutes;                    // decrement this object

         if (minutes < 0)
         {
            --hours;
            minutes = 59;
         }
         // return old original value
         return temp;
      }

      // overloaded postfix ++ operator
      airtime operator++ (int)
      {
         airtime temp(hours, minutes); // save original value
         ++minutes;                    // increment this object

         if (minutes >= 60)
         {
            ++hours;
            minutes -= 60;
         }
         // return old original value
         return temp;
      }
};  // end class airtime

////////////////////////////////////////////////////////////////
void main()
{
   airtime at1, at2;         // make two airtimes

   cout << "For at1: ";
   at1.get();                // get value for one

   cout << "\n\n-----\nat1++;" << endl;
   at1++;                    // increment it

   cout << "\nat1=";
   at1.display();            // display result

// ++at1;                    // error: prefix ++ operator

   cout << "\n\n-----\nat2 = at1++;" << endl;
   at2 = at1++;              // increment again, and assign

   cout << "\nat1=";
   at1.display();            // display result
   cout << "\nat2=";
   at2.display();            // display assigned value

   cout << "\n\n-----\nat1--" << endl;
   at1--;                    // decrement it
   cout << "\nat1=";
   at1.display();            // display result

// --at1;                    // error: prefix -- operator

   cout << "\n\n-----\nat2 = at1--" << endl;
   at2 = at1--;              // decrement again, and assign

   cout << "\nat1=";
   at1.display();            // display result
   cout << "\nat2=";
   at2.display();            // display assigned value

   getch();
}   // main()

Разликата с предишната програма е не само в двата нови оператора, но и че в главната функция са добавени повече твърдения, за да стане ясно как се извършват операциите с тези два оператора. Ето екранът, който програмата дава, когато потребителят вкара час 8:59.

For at1:
Enter time (format 12:59): 8:59

-----
at1++;

at1=9:00

-----
at2 = at1++;

at1=9:01
at2=9:00

-----
at1--

at1=9:00

-----
at2 = at1--

at1=8:59
at2=9:00

Обърнете внимание на двата реда в главната функция на програмата:

// ++at1;           // error: prefix ++ operator
// --at1;           // error: prefix -- operator

които са в коментари, защото е невързможно да извикате префиксната версия на оператора, която не е презаредена (няма написани функции-оператори за тези действия). Като упражнение, читателят може да комбинира двете програми и да напише една, в която и четирите оператора са презаредени.

3. Презареждане на унарния оператор отрицателен знак (The Unary Minus Operator). Даже и да сме презаредили оператора разлика, и например, за комплексните числа поддържани от наша програма да можем да роботим по следния начин:

cn3 = cn2 - cn1;

то е уместно да се презареди и операторът отрицателен знак, защото израз от вида

cn3 = -cn1 + cn2;

няма да може да бъде изчисляван.

Също така следващото присвояване не може да бъде извършено без наличието на презареден оператор отрицателен знак:

cn3 = -cn1;

От последния израз е ясно, че искаме операторът отрицателен знак да връща комплексно число, чийто реална и имагинерна част са противоположни на оригиналните, но самото число да не се променя. За комплексните числа, поддържани от нашата програма в една от предишните лекции, функцията-оператор би изглеждала по този начин:

    // Overloaded Unary Minus Operator
    complex_number operator-()
    {
      return complex_number(-real, -imaginary);
    }

Обърнете внимание, че прототипът на тази функция се различава от този на функцията-оператор за разлика, в това че няма аргумент.

За да работи, обаче, тази функция е необходимо да имаме написан от нас двуаргументен конструктор

   // two-argument constructor
   complex_number(float re, float im): real(re), imaginary(im)
   {    }

А напишем ли двуаргументен конструктор, ни е необходим и едноаргументен,

   // no-argument constructor
   complex_number(): real(0.0), imaginary(0.0)
   {    }

защото няма да можем да създаваме временните обекти от класа complex_number в другите оператори-функции със следното твърдение:

complex_number cn;

И така, цялата програма complex4.cpp, която е допълнената програма complex3.cpp от една от предишните лекции, е дадена по-долу

 // complex4.cpp
// a class that models a complex number data type
// Overloaded Unary Minus Operator

#include <iostream.h>
#include <iomanip.h>

class complex_number
{
  private:
    float real;          // real part
    float imaginary;     // imaginery part
  public:
    void set()
    {
      char dummy;        // for comma

      cout << "Enter number (format Re, Im): ";
      cin >> real >> dummy >> imaginary;
    }

    void display()
    {
      cout << '('
           << showpoint
           << setprecision(6) << real       // real part
           << ','
           << setprecision(6) << imaginary  // imaginary part
           << ')';
    }

    // no-argument constructor
    complex_number(): real(0.0), imaginary(0.0)
    {    }

    // two-argument constructor
    complex_number(float re, float im): real(re), imaginary(im)
    {    }

    // add to the complex number another one (compl_num)
    complex_number operator + (complex_number compl_num)
    {
      complex_number cn;
      cn.real = real + compl_num.real;
      cn.imaginary = imaginary + compl_num.imaginary;

      return cn;
    }

    // subtract from the complex number another one (compl_num)
    complex_number operator - (complex_number compl_num)
    {
      complex_number cn;
      cn.real = real - compl_num.real;
      cn.imaginary = imaginary - compl_num.imaginary;

      return cn;
    }

    // multiply two complex numbers and assign the result to third one
    complex_number operator * (complex_number compl_num)
    {
      complex_number cn;

      cn.real = real*compl_num.real - imaginary*compl_num.imaginary;
      cn.imaginary = real*compl_num.imaginary + compl_num.real*imaginary;

      return (cn);
    }

    // divide the complex number by another one (compl_num)
    complex_number operator / (complex_number compl_num)
    {
      complex_number cn;

      cn.real = real;
      cn.imaginary = imaginary;

      float sqr_mod = compl_num.module();
      if (sqr_mod == 0)
      {
        cout << "Devision by zero!" << endl;
        return cn;
      }

      sqr_mod *= sqr_mod;

      cn.real = (real*compl_num.real + compl_num.imaginary*imaginary)/sqr_mod;
      cn.imaginary = (compl_num.real*imaginary - real*compl_num.imaginary)/sqr_mod;

      return cn;
    }

    // calculate the module of the complex number
    float module()
    {
      float m = real*real + imaginary*imaginary;
      return sqrt(m);
    }

    // Overloaded Unary Minus Operator
    complex_number operator-()
    {
      return complex_number(-real, -imaginary);
    }

};  // class complex_number

void main()
{
  // create three complex_number variables
  complex_number c1, c2, c3;
  char choice;

  do
  {
    // enter c1
    cout << "For c1, ";
    c1.set();       // set c1

    // enter c1
    cout << "For c2, ";
    c2.set();       // set c2
    cout << endl;

    // perform addition and ...
    c3 = c1 + c2;
    // ... display the result
    c1.display();
    cout << " + ";
    c2.display();
    cout << " = ";
    c3.display();
    cout << endl << endl;

    // perform subtraction and ...
    c3 = c1 - c2;
    // ... display the result
    c1.display();
    cout << " - ";
    c2.display();
    cout << " = ";
    c3.display();
    cout << endl << endl;

   // perform multiplication and ...
    c3 = c1 * c2;
    // ... display the result
    c1.display();
    cout << " * ";
    c2.display();
    cout << " = ";
    c3.display();
    cout << endl << endl;

    // perform division and ...
    c3 = c1 / c2;
    // ... display the result
    c1.display();
    cout << " / ";
    c2.display();
    cout << " = ";
    c3.display();
    cout << endl << endl;

    // perform negation and ...
    c3 = -c1;
    // ... display the result
    cout << "- ";
    c1.display();
    cout << " = ";
    c3.display();
    cout << endl << endl;

    // perform c3 = -c1 + c2 and ...
    c3 = -c1 + c2;
    // ... display the result
    cout << "- ";
    c1.display();
    cout << " + ";
    c2.display();
    cout << " = ";
    c3.display();
    cout << endl << endl;

    cout << "\nDo another (y/n)? ";
    cin >> choice;
  } while(choice != 'n');

}  //end main

Ето и изходът от едно примерно изчисление:

For c1, Enter number (format Re, Im): -1, 3
For c2, Enter number (format Re, Im): 4, -2

(-1.00000,3.00000) + (4.00000,-2.00000) = (3.00000,1.00000)

(-1.00000,3.00000) - (4.00000,-2.00000) = (-5.00000,5.00000)

(-1.00000,3.00000) * (4.00000,-2.00000) = (2.00000,14.0000)

(-1.00000,3.00000) / (4.00000,-2.00000) = (-0.500000,0.500000)

- (-1.00000,3.00000) = (1.00000,-3.00000)

- (-1.00000,3.00000) + (4.00000,-2.00000) = (5.00000,-5.00000)

Ако изграждаме свои класове за наши обекти, подобно на този, който поддържа комплексните числа, е хубаво и за пълнота на математичните операции да презаредим и унарния положителен оператор (въпреки, че той не променя комплексното число), за да можем да пишем твърдения от рода на:

cn3 = + cn1 - cn2;
cn3 = + cn2;

За упражнение, читателят може да прибави презаредения оператор complex_number operator+() към програмата по-горе и да го тества с новонаписани редове в главната функция.

В следващата лекция ще се занимаем с превръщането на обекти от наш клас в предефинираните типове (Conversion from Objects to Basic Types).
.

(съдържание)
.
Литература
.
[1] Robert Lafore; C++ Interactive Course. Waite Group Press, Macmillan Computer Publishing, 1996.
.
Автор: Проф. Процесор Аритметиков
.
[ това е материал от брой 45 от ноември 2010 г на списание "Коснос" www.kosnos.com ]
.
Ключови думи: клас , обект, обектно ориентирано програмиране , полиморфизъм
Keywords: С++,  OOP programming , C++ , Classes , Inheritance , Reusability , Creating New Data Types
OPERATOR OVERLOADING
Overloading Binary Arithmetic Operators The operatorX() Function Arguments Return Value
Overloading Other Binary Operators Overloading Relational Operators Passing the Argument by const Reference Assignment Operators Avoiding Temporary Objects
Overloading Unary Operators Prefix Version of Operator ++ Postfix Version of Operator ++ The Unary Minus Operator
Conversion from Objects to Basic Types  Type Casting: Conversion for Basic Types Conversion Function Invoked Automatically Casting for Clarity A Static Constant The static_cast Approach
Conversions Between Classes The One-Argument Constructor
Overloading the Assignment Operator (=) Syntax of the Overloaded Assignment Operator An Assignment Operator That Allows Chaining
Overloading the [ ] Operator Access with access() Function Access with Overloaded [ ] Operator
Fine-Tuning Overloaded Operators Constant Arguments Constant Functions Constant Overloaded Operators
Returns from Assignment Operators The Amazing *this Object The += Operator Revisited The Increment Operator Revisited