X & X::opertor=(const X & )
или
X & X::opertor=(X & )
Точно този оператор ни позволяваше да пишем оператори за присвояване между обектите на един клас, като например в следния отрязък от код на програма
X obj1, obj2; // create two objects of class X
obj2.setobj() // set up the second object
obj1 = obj2; // assign second to first object
Но по-важното е, че операторът за присвояване по подразбиране прави копие на всички променливи на обекта в дясно към тези на обекта в ляво - точно това е отразено и в пълното му име на английски, дадено по-горе. Понякога това копиране е просто това, което искаме и затова не е необходимо да презаредим оператора за присвояване. Но в някои случаи е необходимо да инициализираме някои от пременливите (особено ако те са указатели), да променим някои от статичните променливи на класа и прочие подобни дейности, които не се осъществяват от оператора за присвояване по подразбиране, а трябва да бъдат зададени от програмиста. Тогава презареждането на този оператор е неизбежно.
2. Презареждане на оператора за присвояване без връщане на стойност. В този му вид най-общо функцията-оператор изглежда по следния начин:
void
operator=(const X & right) // overloaded assignment
{
// copy data if appropriate
}
Обърнете внимание на ключовата дума void, която означава че при такъв презареден оператор не може да пишете двойни, тройни и т.н. присвоявания от вида
X obj1, obj2, obj3; // create three objects of class X
obj1.setobj() // set up the first
object
obj3 = obj2 = obj1; // not allowed because of the void return type!
! За това че в общия случай, присвояването (различно от горния случай) връща стойност вижте т. 4 в лекция 40.
Функцията-оператор има един аргумент от типа на класа, който аргумент обикновено е постоянен. В тялото на функцията се пишат твърдения за съответните присвоявания на променливите на обектите, както и някои други инициализации, промени на статитични променливи на класа и т.н. Всичко това много прилича на копиращия конструктор (вижте лекция 35), но трябва да се отбележи, че е различно в това, че не се създава нов обект - т.е. копиращият конструктор се "включва в работа" при създаването на нов обект, а не при операция за присвояване. Ето един илюстративен пример:
omega beta = alpha; // construction and initialization
// uses copy constructor
beta = alpha; // assignment
// uses assignment operator
3. Пример за презареждане на оператора за присвояване. Ще разгледаме една примерна програма от курса на Лафоор [1], която демонстрира презареждане на оператора за присвояване като използва вече познат клас, omega, от програмата copycon2.cpp от т.1. на лекция 36 .
В програмата assign.cpp всеки обект си има име, което може да се копира, но има и уникален номер, който не може да се копира. (Ако си спомняте в предишната програма, copycon2.cpp, този номер се даваше при създаване на обекта, включително и с помощта на копиращия конструктор!) Тази ситуация донякъде съответства на името и ЕГН на хората в България - името може да се смени в съда, но ЕГН-то остава същото.
! Обърнете внимание, че обектът от дясната страна на оператора за присвояване "влиза" като аргумент на функцията-оператор, която се "извиква" за обекта в ляво от оператора, когото променя!
//
assign.cpp
//
demonstrates overloaded assignment operator
//
using objects that number themselves
#include
<iostream.h>
#include
<string.h>
// for strncpy()
#include
<conio.h>
// for getch()
class
omega
{
private:
enum { size = 20 };
char name[size];
static int total;
const int number;
public:
// one-arg constructor
omega(char str[]) : number(++total) // number the object
{
strncpy(name, str, size); //
give it a name
cout << "\n1-arg constructor has "
<< "created " << name << "-" << number;
}
// overloaded assignment operator
void operator = (const omega & right)
{
cout << "\n" << right.name << "-" << right.number
<< " assigned to " << name << "-" << number;
strncpy(name, right.name, size); // copy the name
cout << ", making " << name << "-" << number;
}
// display the omega object
void display()
{ cout << name << "-" << number; }
// get the total value
int get_total()
{ return total; }
};
int omega::total = 0; // (no objects when program starts)
void
main()
{
omega om1("Harriet"); // uses one-arg constructor
omega om2("Bernard");
om2 = om1; // assignment
cout << "\nom1 = ";
om1.display();
cout << "\nom2 = ";
om2.display();
cout << "\ntotal = " << om1.get_total() << endl;
getch();
}
// main()
Както винаги, леко сме изменили програмата в главната функция, с цел по-голяма яснота за читателя - прибавено е показването на броя на всички обекти от класа omega, създадени по време на изпълнение на програмата.
В главната функция на програмата се създават два обекта с различни имена, след това първият обект се присвоява на втория и те се отпечатват на екрана, заедно с броя създадени обекти (който в случая е ясен, че е 2, но в следващата програма ще видим някои неприятни изненади с този брой). Ето изходът от програмата:
1-arg
constructor has created Harriet-1
1-arg
constructor has created Bernard-2
Harriet-1
assigned to Bernard-2, making Harriet-2
om1
= Harriet-1
om2
= Harriet-2
total
= 2
Друг особен момент в програма е, че сме дефинирали една от пременливите на класа omega, номерът number, постоянен с помощта на const int number;. В този случай задължително трябва да презаредим операторът за присвояване, защото при липса на презареден оператор, компилаторът издава съобщение на реда с присвояването
[C++ Error] : E2125 Compiler could not generate operator= for class 'omega'.
което означава, че компилаторът не може да генерира оператор за присвояване по подразбиране. Това е така, понеже ако генерира такъв оператор и го използваме, то той ще трябва да променя постоянната променлива на обекта в ляво от =, а това е забранено.
Също така, не можем в нашия оператор за присвояване да променяме постоянна променлива на класа. Ако напишем във функцията-оператор =,например
number++;
то компилаторът издава съобщението за грешка
[C++ Error] : E2024 Cannot modify a const object.
4. Оператор за присвояване, който връща стойност (Assignment Operator That Allows Chaining). При това презареждане с, например функция-оператор от вида (предполагаме, че ще променяме само променливата name в класа X)
X
operator=(const X & right) // overloaded assignment
op
{
// copy data if appropriate
return X(name);
// returns by value; uses 1-arg constructor
}
можем да използваме оператора във "верига" от присвоявания, като например
X obj1, obj2, obj3; // create three objects of class X
obj1.setobj() // set up the first
object
obj3 = obj2 = obj1; // this time allowed because of the X return
type!
Твърдението return във функцията-оператор позволява да се връща при присвояването (в случая, безименен) обект от типа на класа.
Ето една примерна програма от от курса на Лафоор [1], която презарежда оператора = за да работи "във верига" с обекти от класа omega.
//
assign2.cpp
//
overloaded assignment operator with self-numbering objects
//
returns a value
#include
<iostream.h>
#include
<string.h>
// for strncpy()
#include
<conio.h>
// for getch()
class
omega
{
private:
enum { size = 20 };
char name[size];
static int total;
int number;
public:
// one-arg constructor
omega(char str[]) : number(++total)
{
strncpy(name, str, size);
cout << "\n1-arg constructor has "
<< "created " << name << "-" << number;
}
// Copy constructor
omega(const omega & right) : number(++total)
{
cout << "\n\nWith copy constructor " << right.name <<
"-" << right.number
<< " assigned to " << name << "-" << number;
strncpy(name, right.name, size);
cout << ", making " << name << "-" << number;
}
~omega()
{
cout << "\nDestructor has "
<< "destroyed " << name << "-" << number;
}
// overloaded assignment operator
omega operator = (const omega & right)
{
cout << "\n\n" << right.name << "-" << right.number
<< " assigned to " << name << "-" << number;
strncpy(name, right.name, size);
cout << ", making " << name << "-" << number;
return omega(name);
}
// display the omega object
void display()
{ cout << name << "-" << number; }
// get the total value
int get_total()
{ return total; }
};
int omega::total = 0; // no objects when program starts
void
main()
{
omega om1("Harriet"); // uses one-arg
constructor
omega om2("Bernard");
omega om3("Damien");
om3 = om2 = om1; // chained assignment
cout << "\nom1 = ";
om1.display();
cout << "\nom2 = ";
om2.display();
cout << "\nom3 = ";
om3.display();
cout << "\ntotal = " << om1.get_total() << endl;
} // main()
Както винаги, сме изменили програмата, с цел по-голяма яснота за читателя - прибавено е показването на броя на всички обекти от класа omega, създадени по време на изпълнение на програмата, както и е добавен копиращ конструктор, за да се покаже, че при присвояванията
om3 = om2 = om1; // chained assignment
не се извиква този конструктор, а операторът за присвояване.
В главната функция на програмата се създават три обекта с различни имена, два от които после се променят с горното твърдение.
! Необходимо е тази програма да бъде изпълнена извън интегрираната работна среда (IDE), за да се видят действието на деструкторите - точно затова сме махнали и функцията getch(), която обикновено поставяме накрая в главната функция, за да "задържим" изхода на екрана, ако работим в IDE.
При изпълнение на програмата извън на екрана се изписва
D:\PROGRAMMING\C++\Cpp_Lafore\Programs\ch06>assign2
1-arg
constructor has created Harriet-1
1-arg
constructor has created Bernard-2
1-arg
constructor has created Damien-3
Harriet-1
assigned to Bernard-2, making Harriet-2
1-arg
constructor has created Harriet-4
Harriet-4
assigned to Damien-3, making Harriet-3
1-arg
constructor has created Harriet-5
Destructor
has destroyed Harriet-5
Destructor
has destroyed Harriet-4
om1
= Harriet-1
om2
= Harriet-2
om3
= Harriet-3
total
= 5
Вижда се, че общо се създават пет обекта, понеже функцията-оператор връща обект по стойност. Това са двата безименни обекта Harriet-4 и Harriet-5, създадени с твърдението
return omega(name);
във функцията-оператор omega operator = (const omega & right). (Те имат име name, в случая Harriet, но ги наричат "безименни", защото самите обекти като променливи нямат име!)
Това създаване на излишни обекти е неприятен страничен ефект и в някои случаи може да повлече други нежелателни странични ефекти. Затова то може да се избегне, ако функцията-оператор връща стойност по връзка, но това в този вариант не може да стане, защото не може да връщаме по връзка временни обекти, като този създаден с горното твърдение! В следващия раздел от тази лекция ще изпреварим малко изложението и ще предложим един начин, без да влизаме в подробни обяснения (които главно засягат т.н. указатели, които още не сме изучили)
! За връщането по връзка и ограниченията, свързани с него, вижте лекция 30.
5. Връщане по връзка при функцията-оператор =. Както споменахме в началота на настоящата лекция, ако не създадем своя собствена функция-оператор = за даден клас X, то компилаторът създава такъв по подразбиране, който в зависимост от някои параметри, които още не сме изучили, има вида
X & X::opertor=(const X & )
или
X & X::opertor=(X & )
Обърнете внимание, че в този оператор по подразбиране връщането на стойност е по връзка! Тогава в гореразгледаната програма assign2.cpp няма да се създават два излишни обекта. Това лесно може да се види, ако презаредената функция-оператор = за класа omega се загради в коментари - стартиращи с /* и завършващи с */. В този случай, изходът на екрана ще е:
D:\PROGRAMMING\C++\Cpp_Lafore\Programs\ch06>assign2
1-arg
constructor has created Harriet-1
1-arg
constructor has created Bernard-2
1-arg
constructor has created Damien-3
om1
= Harriet-1
om2
= Harriet-1
om3
= Harriet-1
total
= 3
Destructor
has destroyed Harriet-1
Destructor
has destroyed Harriet-1
Destructor
has destroyed Harriet-1
Как ние самите, обаче, да напишем собствена презаредена функция-оператор = , която да връща по връзка, след като временен обект не може да се връща по връзка ? (за това последното вижте т. 2 от лекция 30) Отговорът е чрез използване на указател. Използва се указател към обекта, за който се извиква функция-оператор =. Този указател е с името this, който представлява указател към обекта, за който се извиква тази функция. Ето променената функция-оператор = :
// overloaded assignment operator
// that return by reference
omega & operator = (const omega& right)
{
cout << "\n\n" << right.name << "-" << right.number
<< " assigned to " << name << "-" << number;
strncpy(name, right.name, size);
cout << ", making " << name << "-" << number;
return *this;
}
За упражнение, нека читателят копира тази функция в програмата assign2.cpp, да я стартира и да види, че не се създават двата излишни обекта.
В следващата
лекция ще се занимаем с оператора за индекс (Overloading the Subscript
Operator, [])
.
.
[2] Programming Languages C++
. ISO/IEC 14882:1998(E).
(това е книга от 776 стр., която може да се намери по Интернет във вид
на PDF файл!)
.
Автор: Проф.
Процесор Аритметиков