int
alpha = 19; // create an int with value 19
int
beta = alpha; // create another int that's a copy of alpha;
it has a value of 19, too!
Да не забравяме, че за компилатора променливите от предефинираните типове са обекти. Също така равенството не изразява присвояване (assignment operator), а означава инициализация на променлива (initializing a variable).
Еквивалентният запис на горните два реда е:
int
alpha(19);
int
beta(alpha); // copy alpha to beta
Обърнете внимание, че този запис наподобява инициализацията в конструкторите и обикновенно се нарича функционално означаване (functional notation).
2. Копиране на обекти (Copying Objects). По същия начин могат да се копират и обекти. Ето един схематичен пример:
AClass
obj1;
obj1.getdata();
AClass obj2 = obj1, obj3(obj1);
Първо се създава обект obj1 от класа AClass. След това той получава стойности на своите променливи с функцията getdata(). После се създават два други обекта - obj2 и obj3, който се инициализат със стойността на първия.
Последният ред от кода по-горе изисква класът AClass да има копиращ конструктор. Както споменахме, ако програмистът не създаде такъв конструктор, компилаторът създава такъв по подразбиране, който има задачата да копира всички променливи на единия обект в другия - това се нарича на английски memberwise copy. Много често точно това е необходимо на програмиста, затова той може и да не създаде свой копиращ конструктор.
Но понякога е необходимо
да се извършат някакви действия при извикване на копиращия конструктор
и тогава програмистът трябва да създаде свой. Например, спомнете си програмата
static.cpp
от лекция 28, която
брои създадените обекти. Ето едно нейно подобрение, в което функцията init()
е заменена от конструктор, а освен това третият обект
се създава с копиращ конструктор. В тази програма се илюстрират два момента:
(1) програмата е подобрена, чрез създаване на безаргументан конструктор,
който брои и номерира джаджите, и (2) не е създаден копиращ конструктор,
а в main()
се копира един обект (widget
w3(w1);)
и това създава проблем с номерирането и броенето на обектите (джаджите
в този случай). В следващата лекция
ще срещнем подобрена версия на тази програма (със създаден от програмиста
копиращ конструктор).
.
!
Във версията по-долу на тази програма,
програмистът не е създал собствен копиращ конструктор и това води до логическа
грешка при преброяването на създадените обекти. Компилаторът създава свой
собствен копиращ конструктор, който само копира всички променливи на единия
обект в тези на другия, но не увеличава броя на създадените обекти.
//
static1.cpp
//
demonstrates static data and functions
//
uses constructor to initialize one widget
#include
<iostream.h>
#include
<conio.h>
using
namespace std;
////////////////////////////////////////////////////////////////
class
widget
{
private:
int widget_number;
// a widget's serial number
static int total_widgets; // all widgets made so far
// NOTE: declaration only
public:
widget()
// constructor that initialize one widget
{
widget_number = 10000 + total_widgets++;
}
int get_number()
// get a widget's number
{
return widget_number;
}
static int get_total() // get total widgets
{
return total_widgets;
}
};
// class widget
int widget::total_widgets = 0; // NOTE: definition
void
main()
{
cout << "Total widgets = " << widget::get_total() <<
endl;
widget w1; // create widgets
cout << "Total widgets = " << widget::get_total() <<
endl;
widget w2; // create widgets
cout << "Total widgets = " << widget::get_total() <<
endl;
widget w3(w1); // create widgets
cout << "Total widgets = " << widget::get_total() <<
endl;
cout << "w1 = " << w1.get_number() << endl;
cout << "w2 = " << w2.get_number() << endl;
cout << "w3 = " << w3.get_number() << endl;
// added to stop the screen
getch();
}
За съжаление, когато третият обект w3 е създаден с копиращ конструктор, програмата не работи правилно: тя наистина копира номера на първия обект в третия, но бърка в броенето на създадените джаджи (widgets). Ето защо е необходимо добавянето на копиращ конструктор, който е следният:
widget(const widget & w)
// copy constructor
{
widget_number = w.widget_number;
total_widgets++;
}
.
!
Примерът
е схематичен, но е много показателен как програмистът може да забрави да
добави свой собствен копиращ конструктор и програмата да не работи логически
вярно, а както е известно логическите грешки са най-трудни за откриване!
3. Копиращ конструктор
(Copy Constructor).
Синтаксисът на копиращия конструктор е следният: той има един единствен
аргумент и той е типа на същия клас и се предава по връзка (Reference
Arguments). Обикновено се използват константни аргументи, целта за
което ще бъде пояснена по-долу. Ето една схематична програма от курса на
Лафоор [1], която демонстрира копиращия конструктор.
.
//
copycon.cpp
//
demonstrates copy constructors
#include
<iostream.h>
#include
<conio.h>
// for getch()
class
omega
{
private:
int intvar;
public:
omega(int v) : intvar(v) // one-arg constructor
{
cout << "\nI am one-argument constructor";
}
omega(const omega & om) // copy constructor
{
intvar = om.intvar;
cout << "\nI am the copy constructor";
}
int retdata()
{ return intvar; }
};
void
main()
{
omega om1(27);
// uses one-arg constructor
omega om2 = om1;
// uses copy constructor
omega om3(om1);
// uses copy constructor
cout << endl << endl;
cout << "\nom1 = " << om1.retdata() << endl;
cout << "\nom2 = " << om2.retdata() << endl;
cout << "\nom3 = " << om3.retdata() << endl;
getch();
}
// main()
В тялото на двата конструктора е вкаран програмен ред, който да изписва кой конструктор се извиква: това е направено с цел да разбере читателят какво точно става при трите дефиниции на обектите. Както се вижда от горния копиращ конструктор в него има два нови момента - (1) аргументът е от същия клас и се предава по връзка, и (2) използвана е ключовата дума const. Затова ще разгледаме тези две особености.
4. Константни аргументи (const for Function Arguments). В лекция 29 беше разгледано предаването на аргументи по връзка (Reference Arguments) и там беше изяснено, че това се използва при големи по памет аргументи с цел спестяване на памет и постигане на бързина. Освен това, за разлика от предаването по стойност предаването по връзка прави възможно промяната на оригиналните стойности на аргументите, с които се извиква функцията. Но при копиращите конструктори обикновено не целим промяната на оригиналния обект, който се копира в другия, то ето защо използваме ключовата дума const.
При употреба на предаване по стойност (Passing by Value), ключовата дума const не е необходима, но в случая на копиращ конструктор е позволено само предаването по връзка (защо е така ще стане ясно от следващата лекция)
Ето една промяна на програмата richman2.cpp от лекция 32, в която е въведен копиращ конструктор. В копиращия конструктор, освен че се копират парите - променливата money - се запитва и за името на новия "притежател".
//
richman2.cpp
//
multiple constructors and a copy constructor
#include
<iostream.h>
#include
<conio.h>
// for getch()
class
richman
{
private:
string name;
int money;
public:
richman() : name("Jack"), money(0)
{ }
richman(int m) : name("Angel"), money(m)
{ }
richman(string n) : name(n), money(-1)
{ }
richman(string n, int m) : name(n), money(m)
{ }
richman(const richman & r) : name("Jack"), money(0)
{
money = r.money;
cout << "Name? ";
cin >> name;
}
void display()
{
cout << name << " has M$" << money << endl;
}
};
// class richman
void
main()
{
richman plamen("Plamen", 10), sasha("Sasha", 1),
angel(100), peter("Peter"), jack;
plamen.display();
sasha.display();
angel.display();
peter.display();
jack.display();
richman natasha(sasha);
natasha.display();
richman nick = jack;
nick.display();
getch();
} // main()
Промяната в главната функция е създаването с копиращия конструктор на още два обекта natasha и nick.
Както споменахме, аргументите в копиращия конструктор могат да се предават само по връзка. Ако промените декларатора (declarator) на копиращия конструктор по следния начин:
richman(richman r) : name("Jack"), money(0)
То компилаторът издава следното съобщение за грешка:
The constructor 'richman::richman(richman)' is not allowed.
Ако изберете с мишката грешката и натиснете клавиша <F1> то помощната система на Борланд С++ Билдер 4.0 дава следния текст:
Constructors of the form
X(X); // Error
};
are not allowed.
This is the correct way to write a copy constructor:
class X {
X(const X&); // Copy constructor
};
който означава на български, че при копиращите конструктори, аргументите от същия клас не могат да бъдат предавани по стойност и е коректно аргументите да бъдат предавани по връзка (Reference Arguments). Както споменахме по-горе, в следващата лекция ще разберем защо това е така, но тук ще изпреварим нещата (с цел да замислим читателя) и ще споменем, че ако се предаваше по стойност обектът, то щеше да се получи порочен кръг без край, който се състои от извикване на копиращия конструктор на класа!
В дефиницията на горния (а въобще и на
всеки) копиращ конструктор може да бъде премахната ключовата дума const
пред аргумента и да се компилира успешно новия код, но това не се прави!
.
Keywords:
С++, OOP programming , C++ , Classes , Inheritance , Reusability
, Creating New Data Types , Polymorphism and Overloading
Ключови
думи: клас , обект, обектно ориентирано програмиране , полиморфизъм switch
if else ?
Copy
Constructors
Copying
Variables
Equivalent
Syntax
Copying
Objects
Passing
by Value
A
Simple Example of a Copy Constructor
Argument
Must Be Passed by Reference
Argument
Should Be const