Програмен език C++
Част 42
.
Превръщане на обекти от потребителски класове в такива от предефинираните типове
(Conversion from Objects to Basic Types)
.
(съдържание)
.
В предишните лекции 39, 40 и 41 се запознахме с т.н. презаредени оператори, които позволяват използването на един и същ оператор с различни по тип операнди (или само един операнд), и които оператори извършват различни действия, в зависимост от типа на операндите. В настоящата лекция ще разгледаме преобразуването на обекти от специфициран от нас клас в стойности от предефинираните типове, като int, float или double.

Може би помните от т.3 на лекция 33, че в програмата englcon.cpp можехме да запишем следния ред:

  E3 = 3         // call again 1-arg constructor

който преобразуваше реалното число 3.0 в обекта E3 от класа English. Ако в същата програма се опитаме да напишем твърднието

  float meters = E3;  // Error: Cannot convert 'English' to 'float'

компилаторът издава съобщението за грешка:

[C++ Error] : E2034 Cannot convert 'English' to 'float'.

което на практика означава, че няма оператор за преобразуване на обекти от класа English в реални стойности. В тази лекция ще научим как става точно такова преобразуване на обекти от специфициран от нас клас в стойности от предефинираните типове с помощта на функции-оператори.
.
1. Преобразуване на предефинираните типове (Type Casting: Conversion for Basic Types). Досега в няколко от нашите програми компилаторът по подразбиране преобразуваше между предефинираните типове. Например, даже и в разгледания ред по-горе от програмата englcon.cpp

  E3 = 3         // call again 1-arg constructor

компилаторът има на разположение конструктор, който преобразува реални числа (от типа float), затова той първо преобразува целочисленото число 3 в реалното 3.0 и чак тогава използва едноаргументния конструктор English(float meters).

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

Друг пример е присвояване на реална стойност на целочислена. Например следният код

  int a;
  float b = 2.718182;
  a = b;

ще даде стойност на a, равна на 2.

Разбира се, така написан третият ред е пример за лоша програмистка практика, особено в големи програми, защото е трудно да се разбере от пръв поглед, че дробната част на b се губи. Много по-добър запис е

  int a;
  float b = 2.718282;
  a = int(b);

който дава същия резултат, но един програмист веднага ще разбере, че става дума за "взимане" само на целочислената стойност на b. Изразът int(b) се нарича на английски type casting, което може да се преведе като преобразуване на типовете.

Този запис е по-скоро в стила на C (C style), и затова в C++ има друг запис, който се нарича на английски static type casting или static_cast approach, което може да се преведе като статично преобразуване на типовете. Той има следния вид:

  a = static_cast<int>(b);

Предполага се, че създателите на C++ са имали предвид по-явния вид на горния запис и неговата уникалност, която помага по-бързо да бъде потърсен с интегрираната работна среда.

! Има и друг запис на преобразуването на типове, но той не се препоръчва в ръководствата по C++!

  a = (int)b;   // alternative syntax (not recommended)

Въпреки автоматичното преобразуване, има случаи когато е необходимо изрично (explicitly) да се преобразува даден тип в друг, за да се изпълни замисленият алгоритъм. Ето един пример от курса на Лафоор [1]:

 float parts(const float & orig, float & fracpart)
 {
   float wholepart = int(orig);   // find whole part
   fracpart = orig - wholepart;   // find fractional part
   return wholepart;              // return whole part
 }

Целта на тази функция е да върне цялата част и дробната част на едно реално число - случая то се дава с аргумента orig. И двете части се връщат в променливи от тип float  - целочислената стойност се връща от функцията с твърдението return wholepart;, а дробната с променливата fracpart, която е аргумент по връзка, т.е. може да се променя. Например при orig = 2.718282, функцията ще върне 2.0, а аргументът fracpart ще е 0.718282.

2. Превръщане на обекти от потребителски класове в такива от предефинираните типове. Много често с обектите на потребителските дефинирани класове са свързани стойности от типа на предефинираните типове, като int, float или double. Например, всяко едно комплексно число има модул (наречен още абсолютна стойност), който е равен на корен квадратен от сумата от квадратите на реалната и имагинерната част. Този модул е реално число и донякъде е уместно, ако присвояваме едно комплексно число на реално, то да се присвоява неговия модул, т.е. реалното число да става равно на модула на комплексното.

Друг хубав пример е да се изчисляват минутите в обект от класа airtime от лекция 12 или пък да се превръща автоматично едно разстояние (което е във футове и инчове) от класа English от лекция 33 в метри.

Функцията оператор за модула на комплексното число ще е следната:

    operator float()
    {
      return sqrt(real*real + imaginary*imaginary);
    }

Може да я прибавите в програмата complex4.cpp на лекция 39, а също и да прибавите следните редове в главната функция за да тествате преобразуването.

  // find module and ...
  float module = c1;
  // ... display the result
  cout << "|";
  c1.display();
  cout << "| = " << module;
  cout << endl << endl;

Ключовият ред по-горе е

  float module = c1;

Така написан, той е инициализиране на променливата module от типа float с обекта c1 от класа complex_number. Но можете да напишете и два реда

  float module;
  module = c1;

като вторият от тях е присвояване на стойността на обекта c1 на реалната променлива module. Разбира се, най-ясният запис би бил

  float module;
  module = float(c1);

или

  float module;
  module = static_cast<float>(c1);

! Разбира се, донякъде е спорно, доколко е уместно да присвояваме комплексни числа на реални. При работа в колектив при голяма програма, един друг програмист би бил затруднен да разбере за какво точно става въпрос - дали твърдението module = c1; връща модула на комплексното число, неговата реална или имагинерна част (последните две също представляват реални числа).

3. Превръщане на обекти от класа English в реални (Conversion from English to float). Ще се спрем на един пример от курса на Лафоор [1] за превръщане на обекти от класа English, който поддържа разстояние във футове и инчове, в разстояние в метри. В програмата englconv.cpp, дадена по-долу, функцията-оператор за това превръщане е следната:

   // convert English to float
   operator float()
   {
      float fracfeet = inches/12;   // inches to feet
      fracfeet += float(feet);      // add the feet
      return fracfeet/MTF;          // convert to meters and return
   }

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

! Присвояването

  fracfeet += float(feet);

е еквивалентно на

  fracfeet = fracfeet + float(feet);

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

// englconv.cpp
// converts English objects to meters

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

class English                          // English distances
{
   private:
      int feet;
      float inches;
      static const float MTF;          // meters to feet, declaration

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

      // 1-argument constructor
      English(float meters)
      {
         float fltfeet = MTF*meters;   // get decimal feet
         feet = int(fltfeet);          // integer part is feet
         inches = 12*(fltfeet - feet); // remainder is inches
      }

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

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

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

      // convert English to float
      operator float()
      {
         float fracfeet = inches/12;   // inches to feet
         fracfeet += float(feet);      // add the feet
         return fracfeet/MTF;          // convert to meters and return
      }

};  // end English class

const float English::MTF = 3.280833;   // meters to feet, definition

void main()
{
   English engman(1.70);               // meters to English

   cout << "\nengman = ";
   engman.display();                   // display English

   float metman;

// works also!!! but it is not prefered approach
// metman = (float)engman;             // English to meters

   metman = float(engman);             // English to meters
   cout << "\nmetman = " << metman;    // display meters

   // intialization with conversion
   float sasha = English(5, 8.0);

   cout << "\nSasha = " << sasha;      // display meters

   // intialization with conversion
   float ivan(English(5, 5.0));

   cout << "\nIvan = " << ivan;        // display meters

   // implicit cast !!! not prefered !!!
   metman = engman;                    // English to meters
   cout << "\nmetman = " << metman;    // display meters

   // explicit cast !!! prefered !!!
   metman = static_cast<float>(engman);             // English to meters
   cout << "\nmetman = " << metman;    // display meters

   getch();
}   // main()

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

 static const float MTF;     // meters to feet, declaration

която се инициализира (дефинира) извън класа

 const float English::MTF = 3.280833;   // meters to feet, definition

със стойността 3.280833, която показва колко фута има в един метър.

4. Превръщане на обекти от класа xString в низове (Conversion from xString to String). Друг пример от курса на Лафоор [1] е за превръщане на обекти от класа xString, който поддържа стингови (низови) променливи, в низове (стрингове) от типа String (char*). Класът xString беше въведен в т.6. на лекция 19. Смисълът на това превръщане е да могат да се използват многото функции, които работят с низове. В следващата програма, такава е функцията stricmp(), използвана за сравняване на стрингове. Ето цялата програма:

// strconv.cpp
// converts xString to normal C string

#include <iostream.h>
#include <string.h>                 // for strlen(), strcpy(), etc.
#include <conio.h>                  // for getch()

class xString
{
   private:
      enum { MAX = 80 };            // maximum length of xStrings
      char str[MAX];                // ordinary C string

   public:
      xString()                     // no-arg constructor
      { strcpy(str, ""); }

      xString( char s[] )           // convert string to xString
      { strcpy(str, s); }

      void input()                  // get string from user
      { cin.get(str, MAX); }

      void display()                // display string
      { cout << str; }

      operator char*()              // convert xString to string
      {
         return str;
      }
};

void main()
{
   xString s1("George");            // constructor converts
                                    // string to xString
   xString s2;

   cout << "Enter your name: ";
   s2.input();                      // get s2 from user

   // convert s1, s2 to strings
   if (stricmp(s1, s2) == 0)         // compare them with stricmp()
      cout << "You're George!";
   else
      cout << "You aren't George.";

   getch();
}   // main()

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

 char * char_arr_var1 = "Some string";

и

 char char_arr_var2[] = "Some string";

са еквивалентни., което означава че редът

 cout << stricmp(char_arr_var2, char_arr_var1) << endl;

би дал, че са еднакви (0 на екрана).

В програмата превръщането на обекти от класа xString в низове става по-подразбиране (implicitly), чрез извикването на функцията stricmp() с аргументи от класа xString.

  stricmp(s1, s2)

Ако двата обекта s1 и s2 от класа xString имат еднакви стрингове (пазени в променливата str), то тази функция връща нула, ако имат различни - връща число, различно от нула.

! Спомнете си, че функцията stricmp() сравнява низове без да обръща внимание, дали буквите са големи или малки (case insensitive)!

В следващата лекция ще се занимаем с превръщането на обекти от различни класове един в друг (Conversions Between Classes).
.

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