Програмен език C++
Част 43
.
Превръщането на обекти от различни класове един в друг
(Conversions Between Classes)
.
(съдържание)
.
В предишната лекция се запознахме с превръщане на обекти от потребителски класове в предефинираните типове. В настоящата лекция ще разгледаме превръщането между обектите на различни класове.

Преобразуването между обектите на различни класове не винаги има смисъл. Ако класовете представят данни от напълно различен характер, то преобразуването между тях няма никакъв смисъл - например преобразуването между обектите на класовете English и airtime, които бяха изучени досега. Първият поддържа разстояние, а вторият време - а това напълно лишава от смисъл превръщането на стойностите им една в друга.

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

Досега се срещнахме с едноаргументния конструктор, който преобразува променливи от предефинирани типове или обекти от наши класове в обекти от други класове, специфицирани от нас (вижте лекция 33). Също така изучихме функциите-оператори, които преобразуваха наши обекти в предефинирани типове (вижте лекция 42). Тези две схеми се прилагат и за преобразуването между обектите на различни класове.
.
1. Схематичен пример за преобразуване. За да разберем концепцията ето един, леко изменен от нас (предимно в главната функция), пример от курса на Лафоор [1].

// twotest.cpp
// tests conversions between two classes

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

class alpha
{
   private:
      int ia;

   public:
      alpha(int i)             // converts int to alpha
      { ia = i; }

      int get_ia()             // "peek" function
      { return ia; }
};

class beta
{
   private:
      int ib;

   public:
      beta(alpha a)            // converts alpha to beta
      { ib = a.get_ia(); }     // uses alpha get_ia()

      operator alpha()         // converts beta to alpha
      { return alpha(ib); }

      int get_ib()             // "peek" function
      { return ib; }
};

void main()
{
   alpha a(11);   // alpha 1-arg constructor; int to alpha
   beta b(a);     // beta 1-arg constructor; alpha to beta

   cout << a.get_ia() << endl;
   cout << b.get_ib() << endl;

   a = alpha(12); // using again alpha 1-arg constructor; int to alpha
   b = a;        // beta 1-arg constructor; alpha to beta
   cout << a.get_ia() << endl;
   cout << b.get_ib() << endl;

   a = alpha(13); // using again alpha 1-arg constructor; int to alpha
   a = b;         // beta operator alpha(); beta to alpha
   cout << a.get_ia() << endl;
   cout << b.get_ib() << endl;

   getch();
}   // main()

! Въпреки строго поддържания, и ракламиран от Лафоор в лекциите си, начин за инициализация на променливите на обектите, то в конструкторите на двата класа това не се използва, а се използва присвояване в тялото на конструктора, например, не толкова изящното твърдение ia = i;  в тялото на конструктора alpha(int i), вместо alpha(int i):ia(i). Оставяме като задача на читателя да промени конструкторите в по-елегантния запис!

В програмата са специфицирани два изключително елементарни класа - alpha и abeta. И в двата има само една променлива от целочислен тип и тя се прави достъпна с функция на класа get_ia() и, съответно, get_ib().

Двете преобразуващите процедури са дефинирани във втория клас и са едноаргументният конструктор beta(alpha a) и функцията-оператор alpha().

В главната функция на програмата първо се създава обект a от класа alpha, който се инициализира с едноаргументния конструктор alpha(int) с числото 11. След това се инициализира обектът b от класа beta, като се използва едноаргументния конструктор beta(alpha a).

След това на обекта a се присвоява нова стойност 12. Обърнете внимание на конструкцията

 a = alpha(12);

в която дясната част създава безименен обект с едноаргументния конструктор alpha(int), който се присвоява на дясната част, обекта a. (За тази конструкция, която е извън настоящата лекция, вижте коментарите на Лафоор в т. 4 на лекция 40.)

След принтиране на екрана на стойностите на целочислената променлива на двата обекта, имаме присвояване на обекта a в обекта b. При него се използва отново едноаргументният конструктор beta(alpha a).

В следващото присвояване, отново на обекта a се дава нова стойност, 13, но при последвалото присвояване, но този път в посока от b към a, се вижда че обектът a приема стойността на b. За това присвояване, този път се използва функцията-оператор alpha(int) на класа beta.

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

2. По-реален пример за преобразуване. Следва друг пример от курса на Лафоор [1]. В програмата fracfeet.cpp се спицифицира и използва клас, подобен на класа English, който нов клас поддържа разстояния, измерени във футове и инчове, но в него инчовете са представени като цяло число и рационална дроб (дроб с числител и знаменател), а не с десетична дроб както е в класа English.

! Ако сте виждали линийка с инчове, то ще да сте забелязали, че инчът не е разделен на десет, а на дванадесет части и по Имперската система с такива линийки традиционно се мери с точност до 1/12 от инча, т.е. числата се представят във вида 35/12" или 23/4", ако дробта е била съкратима - 9/12.

Ето и програмата fracfeet.cpp:

// fracfeet.cpp
// converts between English and FracFeet

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

////////////////////////////////////////////////////////////////

class English                     // feet-and-inches class
{
   private:
      int feet;
      float inches;

   public:
      // no-argument constructor
      English() : feet(0), inches(0.0)
      {  }

      // 2-argument constructor
      English(int f, float i) : feet(f), inches(i)
      {  }

      void get()                  // user input
      {
         cout << "   Enter feet: ";   cin >> feet;
         cout << "   Enter inches: "; cin >> inches;
      }

      void display()              // display
      { cout << feet << "\'-" << inches << '\"'; }

      // these functions needed for conversions
      // return feet
      int getfeet()
      { return feet; }

      // return inches
      float getinches()
      { return inches; }

};  // end English class

////////////////////////////////////////////////////////////////

class FracFeet                    // fractional feet class
{
   private:
      int wfeet;                  // whole feet
      int numer;                  // numerator
      int denom;                  // denominator

   public:
      // no-argument constructor
      FracFeet() : wfeet(0), numer(0), denom(1)
      {  }

      // one-argument constructor
      FracFeet(English);          // only declaration!
 

      void get()                  // user input (never use 0
      {                           // in denominator)
         char dummy;
         cout << "   Enter feet: ";
         cin >> wfeet;
         cout << "   Enter fraction (format 2/3): ";
         cin >> numer >> dummy >> denom;
      }

      void display()              // display
      {
         cout << wfeet;
         if(numer != 0)           // if numerator 0, no fraction
            cout << '-' << numer << '/' << denom;
         cout << " ft";
      }

      operator English()          // convert FracFeet to English
      {                           // inches = 12 * n / d
         float temp_inches = 12.0 * float(numer) / float(denom);
         return English( wfeet,  temp_inches);
      }
};  // end class FracFeet

////////////////////////////////////////////////////////////////

FracFeet::FracFeet(English e)     // one-argument constructor
{                                 // convert English to FracFeet
   wfeet = e.getfeet();           // feet are the same
   int i = int(e.getinches());    // convert inches to integer
                                  // find fraction

   if (i == 6)
     { numer = 1;   denom = 2; }     // 1/2
   else if (i == 2 || i == 10)
     { numer = i/2; denom = 6; }     // 1/6, 5/6
   else if (i == 3 || i ==  9)
     { numer = i/3; denom =4; }      // 1/4, 3/4
   else if (i == 4 || i ==  8)
     { numer = i/4; denom = 3; }     // 1/3, 2/3
   else
     { numer = i; denom = 12;}       // i/12
}

////////////////////////////////////////////////////////////////

void main()
{
   FracFeet ff;
   English eng;

   cout << "\nFracFeet value\n";
   ff.get();                      // get FracFeet from user

   cout << "FracFeet = ";
   ff.display();                  // display FracFeet

   eng = ff;                      // convert FracFeet to English

   cout << "\nEnglish = ";
   eng.display();                 // display equivalent English

   cout << "\n\nEnglish value\n";
   eng.get();                     // get English from user

   cout << "English = ";
   eng.display();                 // display English
 

   ff = eng;                      // set English to FracFeet

   cout << "\nFracFeet = ";
   ff.display();                  // display equivalent FracFeet

   getch();
}   // main()

И двете преобразуващи функции се намират в новия клас FracFeet и те отново са едноаргументен конструктор FracFeet(English)и функцията-оператор English(). За да може новият клас да има достъп до променливите на обектите от другия клас, то в последния са дефинирани две функции за достъп до тези променливи - get_feet() и get_inches(). В главната функция се тества преобразунето и в двете посоки - от обектите на единия клас в обекти на другия и обратно.

! Изчисленията в конструктора FracFeet(English) са приближени: например, зададено разстояние 2'-9.9" във формат на English дава разстояние 2-3/4 ft във формат на FracFeet, вместо 2-5/6 ft, което е по-близката стойност. Но въпреки този си недостатък, програмата е един добър пример за преобразуване между обектите на два класа.

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

В следващата лекция ще се занимаем с презареждането на оператора за присвояване (Overloading the Assignment Operator, =).
.

(съдържание)
.
Литература
.
[1] Robert Lafore; C++ Interactive Course. Waite Group Press, Macmillan Computer Publishing, 1996.
.
Автор: Проф. Процесор Аритметиков
.
[ това е материал от брой 46 от декември 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