Програмен език C++
Част 40
.
Презареждане на другите бинарни оператори
(Overloading Other Binary Operators)
.
(съдържание)
.
В предишната лекция се запознахме с т.н. презаредени бинарни аритметични оператори, които позволяват използването на един и същ оператор с различни по тип операнди, и който оператор извършва различни аритметични действия, в зависимост от типа на тези операнди. В настоящата лекция ще разгледаме и другите бинарни оператори, както и ще направим някои подобрения във функцията на оператора.
.
1. Функцията-оператор за бинарни оператори. Функцията за презареждане на оператора +, която разгледахме в предишната лекция има този вид:

   airtime operator + (airtime right)
   {
      // make a temporary object
      airtime temp;
      // add data
      temp.hours = hours + right.hours;
      temp.minutes = minutes + right.minutes;

      // check for carry
      if (temp.minutes >= 60)
      {
        temp.hours++;
        temp.minutes -= 60;
      }

      return temp;          // return temporary object
   }

и се извиква по следния начин с три обекта от класа airtime:

 at3 = at1 + at2;

Обектът at2 става аргумент на тази функция, а изразът "at1 + " може да се разглежда като извикване на функцията-оператор от обекта at1 по следният начин:

 t1.operator+()

т.е. функцията-оператор се извиква за обекта, който се намира в ляво от оператора +. Резултатът, който връща функцията и който е от типа на класа airtime, се присвоява на третия обект at3.

В т. 6 на предишната лекция, в програмата complex4.cpp, ви показахме как се записват другите три аритметични бинарни оператора, които извършват аритиметичните действия изваждане, умножение и делене на комплексни числа. Както се вижда от програмата, има голяма аналогия между техния запис - само се сменя името на оператора и се пренаписват съответните действия във функцията. Така е и с останалите бинарни оператори, като допълнително в някои от случаите се сменя и типа на връщаната стойност.

2. Презареждане на оператори за сравняване (Overloading Relational Operators). В  Таблица 1 на лекция 6 бяха дадени операторите за сравняване и тяхното значение при сравняване на различни стойности. Резултатът, който връщат е от тип bool, при който стойността нула има смисъл на лъжа, а всяка една стойност, различна от нула на истина - за този тип данни вижте лекция 22. Ако искаме да презаредим оператора по-малко, <, то схематичният запис на функцията оператор ще е следният:

   bool operator < (SomeClass arg_obj)
   {
      // needs function body
   }

като какво точно ще стои в тялото на тази функция, зависи от това, какво сравняваме в обектите на класа SomeClass.

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

// compair.cpp
// overloads the < operator

#include <iostream.h>
#include <conio.h>                     // for getch()

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

   public:
      // output to screen
      void display() const
      {
         cout << hours << ':' << minutes;
      }

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

      // overloaded < operator
      bool operator < (const airtime & right)
      {
         if (hours < right.hours)
            return true;

         if (hours == right.hours && minutes < right.minutes)
            return true;

         return false;
      }
};  // end class airtime

void main()
{
   airtime at1, at2;

   cout << "Enter first airtime: ";
   at1.get();

   cout << "Enter second airtime: ";
   at2.get();

   if (at1 < at2)
      cout << "\nfirst less than second";
   else
      cout << "\nfirst not less than second";

   getch();
}   // main()

Логиката на функцията-оператор operator<() не е трудна за разбиране. Първо се сравнява часа на двата обекта, а ако той е еднакъв, след това минутите. Обърнете внимание, че твърдението return прекъсва изпълнението на функцията, т.е. твърденията в тялото на функцията след него не се изпълняват. При извикване на функцията по следният начин if(at1 < at2), се извиква функцията-оператор за обекта, който е от ляво на оператора, т.е. за обекта at1, като аргумент на функцията е обектът at2. Резултатът е от тип bool и се използва в оператора if.

Не е трудно да бъдат написани и функциите-оператори за останалите пет оператора за сравнение от Таблица 1 на лекция 6. Те са дадени по-долу:

      // overloaded > operator
      bool operator > (const airtime & right)
      {
         if (hours > right.hours)
            return true;

         if (hours == right.hours && minutes > right.minutes)
            return true;

         return false;
      }

      // overloaded <= operator
      bool operator <= (const airtime & right)
      {
         if (hours <= right.hours)
            return true;

         if (hours == right.hours && minutes <= right.minutes)
            return true;

         return false;
      }

      // overloaded >= operator
      bool operator >= (const airtime & right)
      {
         if (hours >= right.hours)
            return true;

         if (hours == right.hours && minutes >= right.minutes)
            return true;

         return false;
      }

      // overloaded == operator
      bool operator == (const airtime & right)
      {
         if (hours == right.hours && minutes == right.minutes)
            return true;

         return false;
      }

      // overloaded != operator
      bool operator != (const airtime & right)
      {
         if (hours == right.hours && minutes == right.minutes)
            return false;

         return true;
      }

Обърнете внимание, че в първите три оператора е сменен само оператора по-малко със съответния оператор. А, ако сравните последните два оператора, ще видите, че местата на true и false са разменени. Ето цялата програма all_comp_air.cpp, която сравнява обекти от класа airtime с всичките шест оператора за сравнение.

3. Постоянни аргументи по връзка (Passing the Argument by const Reference). В шестте функции-оператори в програмата all_comp_air.cpp единственият аргумент се предава по връзка, за да не бавим програмата с копиращия конструктор, който иначе трябва да се извика, ако се предава аргументът по стойност. Но също така, за да не променим случайно този обект-аргумент сме поставили ключовата дума const, пред него. Това не е непозната за нас конструкция - вече я използвахме в конструкторите в лекция лекция 37.

Друго, което е интересно в програмата all_comp_air.cpp е, че няма изрично написан от нас конструктор, както в предните програми от предишната лекция. Той не е необходим, защото не връщаме обект от същия клас, а типът на връщане е предефиниран - bool.

4. Презареждане на операторите за присвояване +=, -=, *=, /= (Overloading Assignment Operators). При тези оператори се променя стойността на левият операнд, като съответно към него се добавя или изважда стойността на десния, или левият се умножава или дели на тази стойност.
! Преди да разгледаме презареждането на тези оператори ще направим едно отклонение, за да покажем че операцията присвояване връща стойност, която е равна на стойността, която е присвоена. Точно затова в C++ (а и в C е така) една от най-неприятните логически грешки е да запишете

   if (x = y)       // with the assignment operator
   {
      // do something
   }

Вместо

   if (x == y)
  {
      // do something
   }

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

[C++ Warning]: W8060 Possibly incorrect assignment.

Именно връщането на стойност от присвояването позволява да напишем съвсем законно в C++ (и C)

 Z = x = y

което прави следното: стойността на y се присвоява на x, а самото присвояване получава стойност, равна на тази на y, която стойност се присвоява на z. Така трите числа стават равни едно на друго. Същото е с операторите +=, -=, *= и /=. Това може лесно да се разбере с примерната програма assign_test.cpp.

Сега за презареждането на операторите за присвояване. Ще искаме не само да можем да ги използваме по следния начин

at1 += at2;

но и чрез двойното присвояване

at3 = at1 += at2;

Затова тези оператори трябва да връщат стойност от типа на класа, чийто обекти са операнди на оператора. Ето една примерна програма от курса на Лафоор [1], която презарежда оператора += за да работи с обекти от класа airtime.

// pleqair.cpp
// overloads the += operator

#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 += operator
      airtime operator += (const airtime & right)
      {
         // add argument to us
         hours += right.hours;
         minutes += right.minutes;

         // check for carry
         if (minutes >= 60)
            { hours++; minutes -= 60; }

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

void main()
{
   airtime at1, at2, at3;

   cout << "Enter first airtime: ";
   at1.get();

   cout << "Enter second airtime: ";
   at2.get();

   at1 += at2;                    // overloaded += operator
                                  // adds at2 to at1
   cout << "\nat1 += at2 = ";
   at1.display();                // display result

   at3 = at1 += at2;             // do it again, use return value

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

   cout << "\nat3 (at3 = at1 += at2;) = ";
   at3.display();                // display result

   getch();
}   // main()

Във функцията-оператор operator+= има няколка ключови момета. Първо, обектът, който стои отдясно на оператора (в главната функция в първото присвояване той е at2) не се променя и затова аргументът е постоянен, въпреки че се предава по връзка. Второ, самият обект, който се намира в лявата страна на оператора +=, се променя и това е отразено в тялото на функцията, където неговия час и минути се променят - добавят се към тях тези на обекта right (в главната функция в първото присвояване right става равен на at2). И трето, функцията връща стойност, която е равна на новата стойност на обекта, който се променя. Това става, чрез извикване на двуаргументния конструктор, който се инициализира с часа и минутите на обекта, който се променя.

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

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

// overloaded + operator
airtime operator + (const airtime & right)
{
  int thrs = hours + right.hours;     // add data
  int tmins = minutes + right.minutes;
  if(tmins >= 60)                     // check for carry
  { thrs++; tmins -= 60; }

   return airtime(thrs, tmins);       // return unnamed
}

В курса на Лафоор [1] пише, че това е едно особено твърдение, което не е извикване на конструктор - "This is a very special kind of statement. It looks like a constructor, and you might think it will cause the creation of a new airtime object.". Но за да се изпълни това твърдение, то трябва да имаме в класа двуаргументен конструктор, написан от нас, иначе компилаторът се оплаква и не успява да компилира програмата:

[C++ Error]: E2285 Could not find a match for 'airtime::airtime(int,int)'.

5. Презареждане на други бинарни оператори. В курса на Лафоор [1] е дадено упражнение да се напише функция-оператор за умножаване на обект от класа airtime с реално число. Това е една интересна задача, която не е трудно да бъде решена от читателя, но въпреки това ние ще дадем по-долу едно наше решение.

// exer2.cpp (Exercise 2)
// class models time data type
// overloads the + and - operators
// overloads also "airtime operator *= (int right)"

#include <iostream.h>
#include <conio.h>                     // for getch()

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

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

      // two argument constructor
       airtime(int h, int m): hours(h), minutes(m)
      {   }

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

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

      // overloaded + operator
      airtime operator + (airtime right)
      {
         // make a temporary object
         airtime temp;
         // add data
         temp.hours = hours + right.hours;
         temp.minutes = minutes + right.minutes;

         // check for carry
         if (temp.minutes >= 60)
         {
            temp.hours++;
            temp.minutes -= 60;
         }

         return temp;          // return temporary object
      }

      // overloaded - operator
      airtime operator - (airtime right)
      {
         // make a temporary object
         int mins, hous;
         mins =  (hours*60 + minutes) - (right.hours*60 + right.minutes);
         hous = mins / 60;
         mins -= (hous*60);

         return airtime(hous, mins);          // return temporary object
      }

      // overloaded * operator
      airtime operator * (int right)
      {
         // make a temporary object
         int mins, hous;
         mins =  (hours*60 + minutes)*right;
         hous = mins / 60;
         mins -= (hous*60);

         return airtime(hous, mins);          // return temporary object
      }

      // overloaded *= operator
      airtime operator *= (int right)
      {
         minutes =  (hours*60 + minutes)*right;
         hours = minutes / 60;
         minutes -= (hours*60);

         return airtime(hours, minutes);      // return temporary object
      }
};  // end class airtime

void main()
{
   airtime at1, at2, at3;
   int mult;

   cout << "Enter first airtime: ";
   at1.get();

   cout << "Enter second airtime: ";
   at2.get();

   cout << "Enter a multiplier: ";
   cin >> mult;

   at3 = at1 + at2;         // overloaded + operator
                            //    adds at2 to at1
   cout << "\nsum = ";
   at3.display();           // display sum

   at3 = at1 - at2;         // overloaded + operator
                            //    adds at2 to at1
   cout << "\ndifference = ";
   at3.display();           // display sum

   at3 = at1*mult;

   cout << "\nmultiplication = ";
   at3.display();           // display sum
 

   at1 *= mult;

   cout << "\nmultiplication (t1 *= mult) = ";
   at3.display();           // display sum

   getch();
}   // main()

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

Enter first airtime:
Enter time (format 12:59): 23:34
Enter second airtime:
Enter time (format 12:59): 13:27
Enter a multiplier: 3

sum = 37:1
difference = 10:7
multiplication = 70:42
multiplication (t1 *= mult) = 70:42

В следващата лекция ще се занимаем с презареждането на унарните оператори (Overloading Unary Operators).
.

(съдържание)
.
Литература
.
[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