Розробка власного класу STRING
Курсовой проект - Компьютеры, программирование
Другие курсовые по предмету Компьютеры, программирование
ри, але в результаті присвоювання s1 = s2 вказівник на один з них буде знищений, і заміниться копією другого. Після виходу з f () буде викликаний для s1 і s2 деструктор, що двічі видалить той самий вектор, результати чого по всій видимості будуть жалюгідні. Для рішення цієї проблеми потрібно визначити відповідне присвоювання обєктів типу string:
struct string {
char* p;
int size; // розмір вектора, на який указує p
string (int size) { p = new char [size=sz]; }
~string () { delete p; }
string& operator= (const string&);
};
string& string:: operator= (const string& a)
{
if (this! =&a) { // небезпечно, коли s=s
delete p;
p = new char [size=a. size];
strcpy (p,a. p);
}
return *this;
}
При такім визначенні string попередній приклад пройде як задумано. Але після невеликої зміни в f () проблема виникає знову, але в іншому виді:
void f ()
{
string s1 (10);
string s2 = s1; // ініціалізація, а не присвоювання
}
Тепер тільки один обєкт типу string будується конструктором string:: string (int), а знищуватися буде два рядки. Справа в тому, що користувальницька операція присвоювання не застосовується до неініціалізованого обєкта. Досить глянути на функцію string:: operator (), щоб зрозуміти причину цього: вказівник p буде тоді мати невизначене, по суті випадкове значення. Як правило, в операції присвоювання передбачається, що її параметри проініціалізовані. Отже, щоб упоратися з ініціалізацією потрібна схожа, але своя функція:
struct string {
char* p;
int size; // розмір вектора, на який указує p
string (int size) { p = new char [size=sz]; }
~string () { delete p; }
string& operator= (const string&);
string (const string&);
};
string:: string (const string& a)
{
p=new char [size=sz];
strcpy (p,a. p);
}
Ініціалізація обєкта типу X відбувається за допомогою конструктора X (const X&). Особливо це важливо в тих випадках, коли визначений деструктор. Якщо в класі X є нетривіальний деструктор, наприклад, що робить звільнення обєкта у вільній памяті, найімовірніше, у цьому класі буде потрібно повний набір функцій, щоб уникнути копіювання обєктів по членах:
class X {
// ...
X (something); // конструктор, що створює обєкт
X (const X&); // конструктор копіювання
operator= (const X&); // присвоювання:
// видалення й копіювання
~X (); // деструктор
};
Є ще два випадки, коли доводиться копіювати обєкт: передача параметра функції й повернення нею значення. При передачі параметра неініціалізована змінна, тобто формальний параметр ініціалізується. Семантика цієї операції ідентична іншим видам ініціалізації. Теж відбувається й при поверненні функцією значення, хоча цей випадок не такий очевидний. В обох випадках використається конструктор копіювання:
string g (string arg)
{
return arg;
}
main ()
{
string s = "asdf";
s = g (s);
}
Очевидно, після виклику g () значення s повинне бути "asdf". Не важко записати в параметр s копію значення s, для цього треба викликати конструктор копіювання для string. Для одержання ще однієї копії значення s по виходу з g () потрібний ще один виклик конструктора string (const string&). Цього разу ініціалізується тимчасова змінна, котра потім привласнюється s. Для оптимізації одну, але не обидві, з подібних операцій копіювання можна забрати. Природно, тимчасові змінні, використовувані для таких цілей, знищуються належним чином деструктором string:: ~string ().
Якщо в класі X операція присвоювання X:: operator= (const X&) і конструктор копіювання X:: X (const X&) явно не задані програмістом, що бракують операції будуть створені транслятором. Ці створені функції будуть копіювати по членах для всіх членів класу X. Якщо члени приймають прості значення, як у випадку комплексних чисел, це, те, що потрібно, і створені функції перетворяться в просте й оптимальне поразрядное копіювання. Якщо для самих членів визначені користувальницькі операції копіювання, вони й будуть викликатися відповідним чином:
class Record {
string name, address, profession;
// ...
};
void f (Record& r1)
{
Record r2 = r1;
}
Тут для копіювання кожного члена типу string з обєкта r1 буде викликатися string:: operator= (const string&).
У нашому першому й неповноцінному варіанті строковий клас має член-вказівник і деструктор. Тому стандартне копіювання по членах для нього майже напевно невірно. Транслятор може попереджати про такі ситуації.
1.15.6 Інкремент і декремент
Нехай є програма з розповсюдженою помилкою:
void f1 (T a) // традиційне використання
{
T v [200];
T* p = &v [10];
p--;
*p = a; // Приїхали: `p настроєні поза масивом,
// і це не виявлено
++p;
*p = a; // нормально
}
Природне бажання замінити вказівник p на обєкт класу CheckedPtrTo, по якому непряме звертання можливо тільки за умови, що він дійсно вказує на обєкт. Застосовувати інкремента і декремента до такого вказівника буде можна тільки в тому випадку, що вказівник настроєний на обєкт у границях масиву й у результаті цих операцій вийде обєкт у границях того ж масиву:
class CheckedPtrTo {
// ...
};
void f2 (T a) // варіант із контролем
{
T v [200];
CheckedPtrTo p (&v [0],v, 200);
p--;
*p = a; // динамічна помилка:
// p вийшов за межі масиву
++p;
*p = a; // нормально
}
Інкремент і декремент є єди