ostream &operator<< (ostream soutput, const PhoneNumber &num)
{
output << " (" << num. areaCode << ")"
"num. exchange << "-" " num. line;
return output; // дозволяє cout << a << b <<c;
}
// Перевантажена операція взяти зпотоку
istream &operator>> (istream sinput, PhoneNumber &num)
{
input. ignore (); // пропуск (
input. getline (num. areaCode,
4); // введення коду місцевості
input. ignore (2); // пропуск) і пробілу
input. getline (num. exchange,
4); // введення комутатора
input. ignore (); // пропуск дефіса (-)
input. getline (num. line,
5); // введення лінії
return input; // дозволяє cin >> a >>b >>c;
}
main () {
PhoneNumber phone; // створення об'єкта phone
cout << "Введіть номер телефону у "
"" вигляді (123) 456-7890: " " endl;
// cin >> phone активізує функцію operator>> // шляхом виклику operator>> (cin, phone). cin >> phone;
// cout << phone активізує функцію operator<< // шляхом виклику operator<< (cout, phone).
cout << "Був введений номер телефону: "<<endl
<< phone << endl;
return 0; }
Введіть номер телефону у вигляді (123) 456-7890: (800) 555-1212
Був введений номер телефону: (800) 555-1212
Мал.8 Задані користувачем операції "помістити в потік" і "взяти з потоку"
Цей виклик міг би потім повернути посилання на cin як значення cin " phonel, так що частина, що залишилася, вираження була б інтерпретована просто як cin " phone2. Це було б виконане шляхом виклику
operator" (cin, phone2);
Операція помістити в потік одержує як аргументи посилання output типу ostream і посилання пшп на певний користувачем тип PhoneNumber і повертає посилання типу ostream. Функція operator" виводить на екран об'єкти типу PhoneNumber. Коли компілятор бачить вираження
cout << phone
в main, він генерує виклик функції
operator<< (cout, phone);
Функція operator" виводить на екран частини телефонного номера як рядка, тому що вони зберігаються у форматі рядка (функція-елемент getline класу istream зберігає нульовий символ після завершення введення).
Помітимо, що функції operator" і operator" об’явлені в class PhoneNumber не як функції-елементи, а як дружні функції. Ці операції не можуть бути елементами, тому що об'єкт класу PhoneNumber з'являється в кожному випадку як правий операнд операції; а для перевантаженої операції, записаної як функція-елемент, операнд класу повинен з'являтися ліворуч. Перевантажені операції помістити в потік і взяти з потоку повинні об’являтися як дружні, якщо вони повинні мати прямий доступ до закритих елементів класу з міркувань продуктивності.
Створимо базовий клас TPString у якому розмістимо мінімальнонеобхідні компоненти, але при цьому цей клас вже буде функціональною одиницею. На основі класу TPString створимо два нащадки TPStrThread-відповідатиме за потокову обробку рядка, а клас TPStrCompare-відповідатиме за порівнння. Обидва класи будуть абстрактними, так як представляють логічно незавершений результат виконання завдання. Використовуючи множинне успадкування створимо результуючий клас clsString, додавши іще декілька методів.
Загальна UML діаграма пропонованого варіанту
Почнемо аналізувати програму з класу TPString. Цей клас є базовим, а крім того може бути і віртуальним (для нащадків), тобто потрібен конструктор за замовченням. Цю функцію краще всього виконає конструктор перетворення. Його прототип:
TPString (const char * = "");
Цей конструктор виконуватиме перетворення рядків у стилі С. Код конструктору
TPString:: TPString (const char *s)
{
len=strlen (s);
BuffLen=0;
symb=NULL;
setString (s);
}
Захищена змінна len зберігає довжину рядка, а змінна BuffLen зберігає довжину буферу пам’яті, на який посилається вказівник symb. Функція setString виконує всю роботу зі збереження рядка.
Конструктор копіювання реалізований майже однаково з конструктором перетворення, окрім того, що аргументом є посилання на об’єкт TPString.
TPString:: TPString (TPString & copy)
{
len (copy. len)
BuffLen=0;
symb=NULL;
setString (copy. symb);
}
Головне завдання деструктору-звільнити пам’ять, де зберігається рядок.
TPString:: ~TPString ()
{
delete [] symb;
}
Операція індексації реалізована 2 функціями
(1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13) (14) | char&TPString:: operator [] (int index) { if ( (index<0) || (index>=len)) FatalError (); return * (symb+index); } const char &TPString:: operator [] (int index) const { if ( (index<0) || (index>=len)) { FatalError (); } return symb [index]; } |
(1) даємо посилання на відповідний до індексу символ для того, щоб у нього можна було записати необхідну інформацію. Але недоліком є те, що замість присвоєння символу, ми можемо за посиланням зберегти адресу цього символа, а це може викликати помилки в роботі. Для недопущення цієї помилки використовуємо другу операцію індексації (7), котра повертає константний символ і, до того ж, є константною.
У разі спроби прочитати/записати символ за неіснуючим індексом, программа викликає функцію FatalError (), котра завершить виконання.
Перевантаження оператора писвоєння є аналогічною до конструктора копіювання, за виключенням того,що вона повертає вказівник на об’єкт, котрому присвоюється значення. Це дає можливість наступного коду:
TPString a,b,c;
· · ·
a = b = c;
Операцій додавання рядків, або конкатенація реалізовані наступним чином:
(1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13) (14) (15) (16) (17) (18) (19) (20) (21) (22) (23) | TPString &TPString:: operator+= (const TPString& part) { if (BuffLen< (len+1+part. len)) { BuffLen=len+1+part. len; char *ptr=new char [BuffLen]; strcpy (ptr,symb); strcpy (ptr+len,part. symb); ptr [BuffLen-1] =0; delete [] symb; symb=ptr; } else { strcpy (symb+len,part. symb); } len+=part. len; return *this; } TPString &TPString:: operator+ (const TPString& part) { TPString temp (*this); temp+=part; return temp; } |
В (1) об’являється оператор += котрий і виконує конкатенацію. Параметром є посилання на об’єкт типу TPString. сonst гарантує незмінність об’єкту. (3) перевірка на достатність виділеної пам’яті для розміщення рядка. (4) обчислення необхідго розміру буферу. (5) видіеня необхідної пам’яті. (6) та (7) копіювання рядків до нового буферу. (8) встановлення символу кінця рядка. (9) знищення старого буферу.
Оператор + загалом об’влений у (18) також має константний параметр. У (20) створюється тимчасовий об’єкт на основі існуючого. Далі виклик += і повернення результату. Завдяки первантаженню операції = уникаємо помлок копіювання.
Функція Clear () присвоює значення рядку “”
void TPString:: Clear ()
{
len=0;
if (symb! =NULL) symb [0] ='\0';
}
Функція видалення певної кількості символів приймає два параметра: стартова позиція та кількість символів.
(1) (2) (3) (4) (5) (6) (7) (8) | void TPString:: TPdelete (int startpos, int count) { if (startpos>=len||startpos<0||count<0) return; if (startpos+count>=len||count==0) count=len-startpos+1; int st=startpos+count; for (; st<=len; st++) symb [startpos++] =symb [st]; len=len-count; } |
Алгоритм видалення досить простий: символи з кінця рядка переписуються замість тих, що підлягають видаленню (6). Перевірки (3) та (4) реалізують коректність роботи алгоритму.
Алгоритм вставки рядка в рядок є більш складним, але подібний до операції інкременту.
(1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13) (14) (15) (16) (17) (18) (19) (20) (21) (22) (23) (24) (25) (26) (27) | void TPString:: insert (TPString& part, int pos, int count) { if (pos>len) return; if (count>part. len) count=part. len; if (part. len<count||count<=0) count=part. len; if (BuffLen>=len+count+1) { for (int i=len; i>=pos; - -i) { symb [i+count] =symb [i]; } for (int i=0; i<count; i++, pos++) { symb [pos] =part. symb [i]; } } else { char *temp=new char [len+part. len+1]; strncpy (temp,symb,pos); strncpy (temp,part. symb,count); strncpy (temp,symb+pos,len-pos); delete [] symb; symb=temp; BuffLen=len+part. len+1; } len+=count; } |
Рядки (3) - (5) реалізують коректність роботи алгоритму. У рядках (7) - (24) реалізований алгоритм конкатенації, оснований на попередньому "роздвиганні" базового рядка у який виконується ставка. У (26) ми встановлюємо поточну довжину рядка.
Захищена функція
void TPString:: setString (const char* s)
{
if (BuffLen<len+1)
{
if (symb! =NULL) delete [] symb;
BuffLen=len+1;
symb=new char [BuffLen];
}
strcpy (symb,s);
}
виконує виділення необхідного об’єму буферу та копіювання у нього нове значення.
Об’ява нащадку TPStrThread від TPString має наступний вигляд:
class TPStrThread abstract: virtual public TPString
{
···
}
Ключове слово abstract говорить про те, що об’єкти класу неможна створювати, проте ми додали конструктор, що ініціалізуватиме у подальшому усю систему.
Головною відмінністю є наявність двох дружніх функцій, що є перевантаженням операцій введення/виведення.
(1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13) (14) (15) (16) (17) (18) (19) (20) (21) (22) (23) (24) (25) (26) (27) (28) (29) (30) (31) | ostream &operator<< (ostream& out, const TPStrThread& tp) { for (int i=0; i<tp. len; i++) out<<tp. symb [i]; return out; } istream &operator>> (istream& input, TPStrThread& tp) { int i=256; int k=-1; char *temp=new char [i]; do{ k++; if (k>i) { i<<1; char * t=new char [i]; strncpy (t,temp,k); delete [] temp; temp=t; } input. get (temp [k]); }while (temp [k] ! ='\n'); temp [k] =0; if (tp. symb! =NULL) delete [] tp. symb; tp. symb=temp; tp. BuffLen=i; tp. len=strlen (temp); return input; } |
У рядку (1) перевантажується операція виведення, вона подібна до операції виведення рядка у стилі С. (5) повертає посилання на потік, що дозволяє виконувати наступну операцію:
cout << … << …;
Операція введення (8) реалізована на основі динамічного алгоритму: виділяється буфер, якщо не вистачає, то виділяється у два рази більший (у нього копіюється попередній і звільняється) і т.д. Замість множення на 2 використовуюємо швидшу операцію зсуву. Це дозволяє максимально збалансувати швидкість виконання та використання ресурсів.
Об’ява нащадку TPStrCompare від TPString має наступний вигляд:
class TPStrCompare abstract: virtual public TPString
{···}
У ньому ми перевантажимо всі операції порівняння:
(1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13) (14) (15) (16) (17) (18) (19) (20) (21) (22) (23) (24) (25) (26) (27) (28) (29) | bool TPStrCompare:: operator! () const { if (len==0) return true; else return false; } bool TPStrCompare:: operator! = (const TPStrCompare& part) const { return (strcmp (symb,part. symb) ! =0); } bool TPStrCompare:: operator== (const TPStrCompare& part) const { return! (*this! = part); } bool TPStrCompare:: operator< (const TPStrCompare& part) const { return strcmp (symb,part. symb) <0; } bool TPStrCompare:: operator> (const TPStrCompare& part) const { return (strcmp (symb,part. symb) >0); } bool TPStrCompare:: operator<= (const TPStrCompare& part) const { return! (*this> part); } bool TPStrCompare:: operator>= (const TPStrCompare& part) const { return! (*this< part); } |
У рядках (1), (6), (10), (14), (16), (18), (22), (26) об’являються оператори порівнняння. Кожен з них є константною функцією та приймає константні аргументи, що гарантує захищеність, та щожливість використання, коли об’єкт був об’явлений константно.