В серия от лекции, следвайки прекрасното изложение на Лафоор в неговия курс [1], ще се запознаем с презареждането на операторите. Това е една много мощна концепция на програмния език C++, която прави удобно ползването на различни оператори за действия с дефинирани от програмиста обекти.
Първо ще започнем с презареждането на бинарните аритметични оператори +, -, *, и /. В следващите лекции ще разгледаме презареждането на другите бинарни оператори, като операторите за сравнение <, >, <=, >=, == и !=, както и тези за присвояване: +=, -=, *= и /=. След това ще разпространим концепцията върху унарните оператори ++, -- и - (знакът минус).
Превръщането на обекти (данни) от един тип в друг (data conversion) е пряко свързано с презареждането на оператори и както ще видим с негова помощ ще се научим да превръщаме дефинираните от нас обекти в предефинираните типове (като int, float и т.н.), както и превръщането на обекти от един дефиниран от нас клас в такива от друг клас.
Накрая в тази серия ще завършим с презареждането на оператора за присвояване = и средните скоби [] (subscript operator, които се използват за обръщане към даден елемент на масив). Ще бъдат допълнително демонстрирани техники, които правят презареждането на оператори по-ефективно и по-безопасно.
В серията ще бъдат
използвани редица програми от превъзходния курс на Лафоор [1]
(както винаги това е изрично споменато в текста), но също така ще бъдат
разширени и допълнени с новите концепции програмите за работа с комплексни
числа - вижте например лекция
10 и лекция
11.
.
1. Защо се презареждат
оператори? Спомнете си за класа xString,
който създадохме да поддържа стрингове (низове) - вижте т. 6 от лекция
19. В него два стринга се свързваха един с друг (concatenate) с помощта
на функцията void
append(xString xs),
която добавяше стринга в обекта xs
към стринга на обекта, който извиква функцията. Например,
s1.append(s2); // append s2 to s1
ще добави стринга на обекта s2 към този на s1.
Този запис изисква време от един програмист да навлезе в "кухнята" на тази функция, т.е. да разбере, кой стринг къде се навързва (отпред или отзад на другия) и кой остава постоянен и кой се променя. Също така този запис не е елегантен, както е следното твърдение
// you can append two strings
s1 = s2 + s1;
от програмата cppstr.cpp от т. 5 на същата лекция 19, която програма илюстрира класа string от стандартната C++ библиотека (добавена с #include <string>).
Спомнете си и за класа complex_number от програмата complex2.cpp от лекция лекция 11, който моделира комплексните числа. В нея, за да съберете комплексното число c2 към c1 трябва да извикате функцията void add(c2) по следния начин:
c1.add(c2);
Разбира се, след време програмистът (или особено някой негов колега) ще бъде затруднен да разбере (точно като в преждецитирания случай) кое число към кое се събира и кое остава постоянно и кое се променя. Отново записът не е елегантен и е далеч от разбиранията в математиката за означаване на събирането на числа, което един програмист на C++ би записал така:
c1 += c2;
Спомнете си също, че класът airtime от програмата timeret.cpp от лекция 14 беше изменен, така че да може да събира две времена със следния ред
t3 = t1.add(t2); // add t1 and t2, result in t3
което е много по-ясно и разбираемо от първоначалната функция в лекция 12, където събирането
t3.add(t1, t2); // add t1 and t2, result in t3
и въпреки това най-ясно и разбираемо и близко до стандартния запис в математиката е да предефинираме оператора +, така че да можем да пишем:
t3 = t1 + t2; // add t1 and t2, result in t3
2. Още за презареждането на оператори. Тук трябва да се спомене, че обектите не на всеки клас могат да се събират! Просто понякога няма логика да събираме дадени обекти, като например тези от класа employee от лекция 16. Какво би означавало следното твърдение?
emp3 = emp1 + emp2;
Нищо! (Освен, да съберете джобните пари на двама служителя и да ги дадете на трети :-))
Също така, не може да си измисляте оператори, които ги няма в програмния език C++ и да пишете за тях презареждащи функции. Може да презареждате само предифинираните оператори (built-in operators)!. Например, не може да напишете презареждащ оператор (например с името operator **) за Вашата идея за вдигане на степен или някакъв друг оператор за друга цел, като например operator <-.
Освен това, не всички предефинирани оператори могат да се презареждат! (You Can’t Overload Everything!). Например, не може да презаредите точковия оператор (dot operator .), оператора за решение за обхвата (the scope resolution operator ::), условния оператор (the conditional operator ?:,), както и някои други, които още не сме изучили.
3. Функцията-оператор (The operatorX() Function). В случая на бинарен оператор X, който е презареден за класа SomеClass, синтаксисът за функцията operator X () е следният:
SomeClass operator X (SomeClass arg_obj)
{
// needs function body
}
Нека в класа airtime от лекция 12 да презаредим оператора +, така че да може да събираме два часа (обекта) със следния ред
// you can append two strings
at3 = at2 + at1;
Функцията за презареждане на оператора + в този случай е:
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
}
Както става ясно, същият синтаксис важи за всеки един бинарен оператор, например, ако е за изваждане, вместо operator + ще напишем operator -, а също ще изменим и тялото на функцията, така че да отразява изваждането на времената.
В горенаписаната функция operator + има няколко съществени момента:
(1) функцията е с име operator, последвано от операторът, който ще презареждаме (в случая +).
(2) функцията има само един аргумент и той е от типа на класа airtime. На негово място влиза десният операнд на операцията събиране, т.е. обектът t2, в случая на извикването
t3 = t1 + t2;
(3) левият операнд се подразбира, че е обектът за който се извиква функцията: грубо казано, в горния израз
t1 +
се подразбира като
t1.operator+()
(4) функцията връща стойност от типа на класа, т.е. airtime. Върната стойност в случая на горното извикване се присвоява на t3.
Забележете, че винаги функцията operatorX() има брой аргументи (в случая 1) с единица по-малък от броя операнди (в случая два - двете събираеми)!
4. Програма за събиране на обекти от класа airtime. Следната програма addair.cpp от курса на Лафоор [1] демонстрира събирането на обекти от този клас:
//
addair.cpp
//
class models time data type
//
overloads the + operator
#include
<iostream.h>
#include
<conio.h>
// for getch()
#include
<iomanip.h>
// for setw() & fill()
class
airtime
{
private:
int hours;
// 0 to 23
int minutes;
// 0 to 59
public:
// 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 right) const
{
// 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
}
};
// end class airtime
void
main()
{
airtime at1, at2, at3;
cout << "Enter first airtime: ";
at1.get();
cout << "Enter second airtime: ";
at2.get();
at3 = at1 + at2; // overloaded
+ operator
// adds at2 to at1
cout << "sum = ";
at3.display();
// display sum
getch();
}
// main()
Обясненията за тялото на функцията operator + са излишни, тъй като действията в нея са същите като тези във функцията airtime add(airtime at2) от програмата timeret.cpp от лекция 14. Обърнете внимание, че аналогично на извикването на тази функция във вида
//
add t1 and t2, result in t3
t3
= t1.add(t2);
можем да запишем
//
a strange use of operator overloading
//
to add t1 and t2 and get the result in t3
t3
= t1.operator+(t2);
но това не се прави! А просто се пише твърдението със знака плюс.
5. Програма за събиране на обекти от класа xString. Следната програма strplus от курса на Лафоор [1] демонстрира събирането на обекти от този клас:
//
strplus.cpp
//
overloads + operator to concatenate xStrings
#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[] )
// 1-arg constructor
{ strcpy(str, s); }
void input()
// get string from user
{ cin.get(str, MAX); }
void display()
// display string
{ cout << str; }
// concatenate two strings
xString operator+(xString right)
{
xString temp;
// temporary xString
if (strlen(str) + strlen(right.str) < MAX - 1)
{
strcpy(temp.str, str); // copy us to temp
strcat(temp.str, right.str); // concatenate argument
}
else
cout << "\nError: xString too long" << endl;
return temp;
// return temporary
}
};
void
main()
{
xString s1("Greetings, ");
xString s2, s3;
cout << "Enter your name: ";
s2.input();
// get s2 from user
s3 = s1 + s2 + ".";
// concatenate period to s2, s2 to s1
s3.display();
// display s3
getch();
}
// main()
И в тази програма, както в предишната, във функцията operator+ се създава локален обект temp (в случая той е от класа xString), който да приеме резултата (в случая - обединяването на стринговете). Функцията връща по стойност този обект.
6. Програма за работа с комплексни числа. Това е аналог на програмата complex3.cpp от лекция 14, в която четирите функции на класа complex_number, add, sub, mult и div са променени с презаредените оператори.
! Не забравяйте, че в C++ има специален клас, който поддържа комплексните числа и операциите с тях. Така че, предишните наши програми, тази и следващите, които работят с комплексни числа, са само за учебни цели.
//
complex4.cpp
//
a class that models a complex number data type
#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
<< ')';
}
// 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);
}
}; // 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;
cout << "Do another (y/n)? ";
cin >> choice;
} while(choice != 'n');
} //end main
Ако сравните кода на двете програми complex3.cpp и complex4.cpp ще забележите пълна прилика, с изключение на това, че четирите функции в complex3.cpp за действията с комплексни числа са сменени с презаредени оператори и начинът на извикването на операторите в главната функция е:
c3 = c1 + c2;
вместо
c3 = c1.add(c2);
и
c3 = c1 - c2;
вместо
c3 = c1.sub(c2);
и
c3 = c1 * c2;
вместо
c3 = c1.mult(c2);
и
c3 = c1 / c2;
вместо
c3 = c1.div(c2);
Обърнете внимание, че функцията float module() не може да бъде сменена с презареден оператор, наречен модул, защото такъв няма в програмния език C++. Но в следващите глави ще се спрем на едно преобразуване на комплексно число в реално, като под това ще рабираме изчисляване на модула на комплексното число.
Изходът от изпълнението на програмата при действията с комплексните числа 2i и 5i е следният:
For
c1, Enter number (format Re, Im): 0,2
For
c2, Enter number (format Re, Im): 0,5
(0.00000,2.00000) + (0.00000,5.00000) = (0.00000,7.00000)
(0.00000,2.00000) - (0.00000,5.00000) = (0.00000,-3.00000)
(0.00000,2.00000) * (0.00000,5.00000) = (-10.0000,0.00000)
(0.00000,2.00000) / (0.00000,5.00000) = (0.400000,0.00000)
Do another (y/n)?
В следващата
лекция ще са запознаем с презареждането на другите бинарни оператори.
.