Преобразуването между обектите на различни класове не винаги има смисъл. Ако класовете представят данни от напълно различен характер, то преобразуването между тях няма никакъв смисъл - например преобразуването между обектите на класовете 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, =).
.