48870 (Розробка власного класу STRING), страница 6

2016-07-30СтудИзба

Описание файла

Документ из архива "Розробка власного класу STRING", который расположен в категории "". Всё это находится в предмете "информатика" из 1 семестр, которые можно найти в файловом архиве . Не смотря на прямую связь этого архива с , его также можно найти и в других разделах. Архив можно найти в разделе "курсовые/домашние работы", в предмете "информатика, программирование" в общих файлах.

Онлайн просмотр документа "48870"

Текст 6 страницы из документа "48870"

class expr {

// ...

public:

expr (); // стандартний конструктор

virtual expr* new_expr () { return new expr (); }

};

Віртуальна функція new_expr () просто повертає стандартно ініціалізований об'єкт типу expr, розміщений у вільній пам'яті. У похідному класі можна перевизначити функцію new_expr () так, щоб вона повертала об'єкт цього класу:

class conditional: public expr {

// ...

public:

conditional (); // стандартний конструктор

expr* new_expr () { return new conditional (); }

};

Це означає, що, маючи об'єкт класу expr, користувач може створити об'єкт в "точності такого ж типу":

void user (expr* p1, expr* p2)

{

expr* p3 = p1->new_expr ();

expr* p4 = p2->new_expr ();

// ...

}

Змінним p3 і p4 привласнюються вказівники невідомого, але підходящого типу.

Тим же способом можна визначити віртуальний конструктор копіювання, названий операцією розмноження, але треба підійти більш ретельно до специфіки операції копіювання:

class expr {

// ...

expr* left;

expr* right;

public:

// ...

// копіювати 's' в 'this'

inline void copy (expr* s);

// створити копію об'єкта, на який дивиться this

virtual expr* clone (int deep = 0);

};

Параметр deep показує розходження між копіюванням властивому об'єкту (поверхневе копіювання) і копіюванням усього піддерева, коренем якого служить об'єкт (глибоке копіювання). Стандартне значення 0 означає поверхневе копіювання.

Функцію clone () можна використати, наприклад, так:

void fct (expr* root)

{

expr* c1 = root->clone (1); // глибоке копіювання

expr* c2 = root->clone (); // поверхневе копіювання

// ...

}

Будучи віртуальної, функція clone () здатна розмножувати об'єкти будь-якого похідного від expr класу. Дійсне копіювання можна визначити так:

void expr:: copy (expression* s, int deep)

{

if (deep == 0) { // копіюємо тільки члени

*this = *s;

}

else { // пройдемося по вказівником:

left = s->clone (1);

right = s->clone (1);

// ...

}

}

Функція expr:: clone () буде викликатися тільки для об'єктів типу expr (але не для похідних від expr класів), тому можна просто розмістити в ній і повернути з її об'єкт типу expr, що є власною копією:

expr* expr:: clone (int deep)

{

expr* r = new expr (); // будуємо стандартне вираження

r->copy (this,deep); // копіюємо '*this' в 'r'

return r;

}

Таку функцію clone () можна використати для похідних від expr класів, якщо в них не з'являються члени-дані (а це саме типовий випадок):

class arithmetic: public expr {

// ...

// нових член-член-даних немає =>

// можна використати вже певну функцію clone

};

З іншого боку, якщо додані члени-дані, то потрібно визначати власну функцію clone ():

class conditional: public expression {

expr* cond;

public:

inline void copy (cond* s, int deep = 0);

expr* clone (int deep = 0);

// ...

};

Функції copy () і clone () визначаються подібно своїм двійникам з expression:

expr* conditional:: clone (int deep)

{

conditional* r = new conditional ();

r->copy (this,deep);

return r;

}

void conditional:: copy (expr* s, int deep)

{

if (deep == 0) {

*this = *s;

}

else {

expr:: copy (s,1); // копіюємо частину expr

cond = s->cond->clone (1);

}

}

Визначення останньої функції показує відмінність дійсного копіювання в expr:: copy () від повного розмноження в expr:: clone () (тобто створення нового об'єкта й копіювання в нього). Просте копіювання виявляється корисним для визначення більш складних операцій копіювання й розмноження. Розходження між copy () і clone () еквівалентно розходженню між операцією присвоювання й конструктором копіювання і еквівалентно розходженню між функціями _draw () і draw (). Відзначимо, що функція copy () не є віртуальною. Їй і не треба бути такою, оскільки віртуальна викликаюча її функція clone (). Очевидно, що прості операції копіювання можна також визначати як функції-підстановки.


1.15 Перевантаження операцій

Звичайно в програмах використовуються об'єкти, що є конкретним поданням абстрактних понять. Наприклад, у С++ тип даних int разом з операціями +, - , *, / і т.д. реалізує (хоча й обмежено) математичне поняття цілого. Звичайно з поняттям зв'язується набір дій, які реалізуються в мові у вигляді основних операцій над об'єктами, що задають у стислому, зручному й звичному виді. На жаль, у мовах програмування безпосередньо представляється тільки мале число понять. Так, поняття комплексних чисел, алгебри матриць, логічних сигналів і рядків у С++ не мають безпосереднього вираження. Можливість задати подання складних об'єктів разом з набором операцій, котрі виконуються над такими об'єктами, реалізують у С++ класи. Дозволяючи програмістові визначати операції над об'єктами класів, ми одержуємо більше зручну й традиційну систему позначень для роботи із цими об'єктами в порівнянні з тієї, у якій всі операції задаються як звичайні функції. Приведемо приклад:

class complex {

double re, im;

public:

complex (double r, double i) { re=r; im=i; }

friend complex operator+ (complex, complex);

friend complex operator* (complex, complex);

};

Тут наведена проста реалізація поняття комплексного числа, коли воно представлено парою чисел із плаваючою крапкою подвійної точності, з якими можна оперувати тільки за допомогою операцій + і *. Інтерпретацію цих операцій задає програміст у визначеннях функцій з іменами operator+ і operator*. Так, якщо b і c мають тип complex, те b+c означає (по визначенню) operator+ (b,c). Тепер можна наблизитися до звичного запису комплексних виражень:

void f ()

{

complex a = complex (1,3.1);

complex b = complex (1.2,2);

complex c = b;

a = b+c;

b = b+c*a;

c = a*b+complex (1,2);

}

Зберігаються звичайні пріоритети операцій, тому другий вираз виконується як b=b+ (c*a), а не як b= (b+c) *a.

1.15.1 Операторні функції

Можна описати функції, що визначають інтерпретацію наступних операцій:

+ - * /% ^ & | ~!

= += - = *= /=%= ^= &=

|= <> >>= <<= ==! = = &&

|| ++ - і - >*, - > [] () new delete

Останні п'ять операцій означають: непряме звертання, індексацію, виклик функції, розміщення у вільній пам'яті й звільнення. Не можна змінити пріоритети цих операцій, так само як і синтаксичні правила для виразів. Так, не можна визначити унарну операцію%, також як і бінарну операцію!. Не можна ввести нові лексеми для позначення операцій, але якщо набір операцій вас не влаштовує, можна скористатися звичним позначенням виклику функції. Тому використайте pow (), а не **. Ці обмеження можна ввжати драконівськими, але більш вільні правила легко приводять до неоднозначності. Припустимо, ми визначимо операцію ** як піднесення до степеня, що на перший погляд здається очевидним і простим завданням. Але якщо як варто подумати, то виникають питання: чи належні операції ** виконуватися ліворуч праворуч або праворуч ліворуч? Як інтерпретувати вираження a**p як a* (*p) або як (a) ** (p)?

Ім'ям операторної функції є службове слово operator, за яким іде сама операція, наприклад, operator<<. Операторна функція описується й викликається як звичайна функція. Використання символу операції є просто короткою формою запису виклику операторної функції:

void f (complex a, complex b)

{

complex c = a + b; // коротка форма

complex d = operator+ (a,b); // явний виклик

}

З урахуванням наведеного опису типу complex ініціалізатори в цьому прикладі є еквівалентними.

1.15.2 Бінарні й унарні операції

Бінарну операцію можна визначити як функція-член з одним параметром, або як глобальну функцію із двома параметрами. Виходить, для будь-якої бінарної операції @ вираження aa @ bb інтерпретується або як aa. operator (bb), або як operator@ (aa,bb). Якщо визначені обидві функції, то вибір інтерпретації відбувається за правилами зіставлення параметрів. Префіксна або постфіксна унарна операція може визначатися як функція-член без параметрів, або як глобальна функція з одним параметром. Для будь-якої префиксної унарної операції @ вираження @aa інтерпретується або як aa. operator@ (), або як operator@ (aa). Якщо визначені обидві функції, то вибір інтерпретації відбувається за правилами зіставлення параметрів. Для будь-якої постфіксної унарної операції @ вираз @aa інтерпретується або як aa. operator@ (int), або як operator@ (aa, int). Якщо визначені обидві функції, то вибір інтерпретації відбувається за правилами зіставлення параметрів. Операцію можна визначити тільки відповідно до синтаксичних правил, наявними для неї в граматиці С++. Зокрема, не можна визначити% як унарну операцію, а + як тернарну. Проілюструємо сказане прикладами:

class X {

// члени (неявно використається покажчик 'this'):

X* operator& (); // префіксна унарная операція &

// (узяття адреси)

X operator& (X); // бінарна операція &

X operator++ (int); // постфіксний інкремент

X operator& (X,X); // помилка: & не може бути тернарною

X operator/ (); // помилка: / не може бути унарною

};

// глобальні функції (звичайно друзі)

X operator- (X); // префіксний унарный мінус

X operator- (X,X); // бінарний мінус

X operator-і (X&, int); // постфіксний інкремент

X operator- (); // помилка: немає операнда

X operator- (X,X,X); // помилка: тернарна операція

X operator% (X); // помилка: унарна операція%


1.15.3 Операторні функції й типи користувача

Операторна функція повинна бути або членом, або мати принаймні один параметр, що є об'єктом класу (для функцій, що перевизначають операції new і delete, це не обов'язково). Це правило гарантує, що користувач не зуміє змінити інтерпретацію виразів, що не містять об'єктів типу користувача. Зокрема, не можна визначити операторну функцію, що працює тільки з вказівниками. Цим гарантується, що в ++ можливі розширення, але не мутації (не вважаючи операцій =, &, і, для об'єктів класу).

Операторна функція, що має першим параметр основного типу, не може бути функцією-членом. Так, якщо ми додаємо комплексну змінну aa до цілого 2, то при підходящому описі функції-члена aa+2 можна інтерпретувати як aa. operator+ (2), але 2+aa так інтерпретувати не можна, оскільки не існує класу int, для якого + визначається як 2. operator+ (aa). Навіть якби це було можливо, для інтерпретації aa+2 і 2+aa довелося мати справа із двома різними функціями-членами. Цей приклад тривіально записується за допомогою функцій, що не є членами.

1.15.4 Конструктори

Замість того, щоб описувати кілька функцій для кожного випадку виклику (наприклад, комбінації типу double та complex з усіма операціями), можна описати конструктор, що з параметра double створює complex:

class complex {

// ...

complex (double r) { re=r; im=0; }

};

Цим визначається як одержати complex, якщо задано double. Конструктор з єдиним параметром не обов'язково викликати явно:

complex z1 = complex (23);

complex z2 = 23;

Обидві змінні z1 і z2 будуть ініціалізовані викликом complex (23).

Конструктор є алгоритмом створення значення заданого типу. Якщо потрібне значення деякого типу й існує його конструктор, параметром якого є це значення, то тоді цей конструктор і буде використатися. Так, клас complex можна було описати в такий спосіб:

class complex {

double re, im;

public:

complex (double r, double i =0) { re=r; im=i; }

friend complex operator+ (complex, complex);

friend complex operator* (complex, complex);

complex operator+= (complex);

complex operator*= (complex);

// ...

};

Всі операції над комплексними змінними й цілими константами з урахуванням цього опису стають законними. Ціла константа буде інтерпретуватися як комплексне число із мнимою частиною, рівної нулю. Так, a=b*2 означає

a = operator* (b, complex (double (2), double (0)))

Нові версії операцій таких, як +, має сенс визначати тільки для підвищення ефективності за рахунок відмови від перетворень типу коштує того. Наприклад, якщо з'ясується, що операція множення комплексної змінної на речовинну константу є критичної, то до безлічі операцій можна додати operator*= (double):

class complex {

double re, im;

public:

complex (double r, double i =0) { re=r; im=i; }

friend complex operator+ (complex, complex);

friend complex operator* (complex, complex);

complex& operator+= (complex);

complex& operator*= (complex);

complex& operator*= (double);

// ...

};

Операції присвоювання типу *= і += можуть бути дуже корисними для роботи з типами користувача, оскільки звичайно запис із ними коротше, ніж з їх звичайними "двійниками" * і +, а крім того вони можуть підвищити швидкість виконання програми за рахунок виключення тимчасових змінних:

inline complex& complex:: operator+= (complex a)

{

re += a. re;

im += a. im;

return *this;

}

При використанні цієї функції не потрібно тимчасовий змінної для зберігання результату, і вона досить проста, щоб транслятор міг "ідеально" зробити підстановку тіла. Такі прості операції як додавання комплексних теж легко задати безпосередньо:

inline complex operator+ (complex a, complex b)

{

return complex (a. re+b. re, a. im+b. im);

}

Тут в операторі return використається конструктор, що дає транслятору коштовну підказку на предмет оптимізації. Але для більше складних типів і операцій, наприклад таких, як множення матриць, результат не можна задати як одне вираження, тоді операції * і + простіше реалізувати за допомогою *= і +=, і вони будуть легше піддаватися оптимізації:

matrix& matrix:: operator*= (const matrix& a)

{

// ...

return *this;

}

matrix operator* (const matrix& a, const matrix& b)

{

matrix prod = a;

prod *= b;

return prod;

}

Відзначимо, що в певної подібним чином операції не потрібних ніяких особливих прав доступу до класу, до якого вона застосовується, тобто ця операція не повинна бути другом або членом цього класу.

Користувальницьке перетворення типу застосовується тільки в тому випадку, якщо воно єдине.

Побудований у результаті явного або неявного виклику конструктора, об'єкт є автоматичним, і знищується за першою нагодою, як правило відразу після виконання оператора, у якому він був створений.

1.15.5 Присвоювання й ініціалізація

Розглянемо простий строковий клас string:

struct string {

char* p;

int size; // розмір вектора, на який указує p

string (int size) { p = new char [size=sz]; }

~string () { delete p; }

};

Рядок - це структура даних, що містить вказівник на вектор символів і розмір цього вектора. Вектор створюється конструктором і знищується деструктором. Але тут можуть виникнути проблеми:

void f ()

{

string s1 (10);

string s2 (20)

s1 = s2;

}

Тут будуть розміщені два символьних вектори, але в результаті присвоювання 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; // нормально

}

Інкремент і декремент є єдиними операціями в С++, які можна використати як постфіксні так і префіксні операції. Отже, у визначенні класу CheckedPtrTo ми повинні передбачити окремі функції для префіксних і постфіксних операцій інкремента й декремента:

class CheckedPtrTo {

T* p;

T* array;

int size;

public:

// початкове значення 'p'

// зв'язуємо з масивом 'a' розміру 's'

CheckedPtrTo (T* p, T* a, int s);

// початкове значення 'p'

// зв'язуємо з одиночним об'єктом

CheckedPtrTo (T* p);

T* operator++ (); // префіксна

T* operator++ (int); // постфіксна

T* operator-- (); // префіксна

T* operator-- (int); // постфісна

T& operator* (); // префіксна

};

Параметр типу int служить вказівкою, що функція буде викликатися для постфісної операції. Насправді цей параметр є штучним і ніколи не використається, а служить тільки для розходження постфіксної і префіксної операції. Щоб запам'ятати, яка версія функції operator++ використається як префіксна операція, досить пам'ятати, що префіксна є версія без штучного параметра, що вірно й для всіх інших унарних арифметичних і логічних операцій. Штучний параметр використається тільки для "особливих" постфіксних операцій ++ і - -. За допомогою класу CheckedPtrTo приклад можна записати так:

void f3 (T a) // варіант із контролем

{

T v [200];

CheckedPtrTo p (&v [0],v, 200);

p. operator-і (1);

p. operator* () = a; // динамічна помилка:

// 'p' вийшов за межі масиву

p. operator++ ();

p. operator* () = a; // нормально

}

1.15.7 Перевантаження операцій помістити в потік і взяти з потоку

C++ здатний вводити й виводити стандартні типи даних, використовуючи операцію помістити в потік " і операцію взяти з потоку ". Ці операції вже перевантажені в бібліотеках класів, якими постачені компілятори C++, щоб обробляти кожний стандартний тип даних, включаючи рядки й адреси пам'яті. Операції помістити в потік і взяти з потоку можна також перевантажити для того, щоб виконувати введення й вивід типів користувача. Програма на малюнку 8 демонструє перевантаження операцій помістити в потік і взяти з потоку для обробки даних певного користувачем класу телефонних номерів PhoneNumber. У цій програмі передбачається, що телефонні номери вводяться правильно. Перевірку помилок ми залишаємо для вправ.

На мал.8 функція-операція взяти з потоку (operator") одержує як аргументи посилання input типу istream, і посилання, названу num, на заданий користувачем тип PhoneNumber; функція повертає посилання типу istream. Функція-операція (operator") використається для введення номерів телефонів у вигляді

(056) 555-1212

в об'єкти класу PhoneNumber. Коли компілятор бачить вираження

cin >> phone

в main, він генерує виклик функції

operator>> (cin, phone);

Після виконання цього виклику параметр input стає псевдонімом для cin, а параметр num стає псевдонімом для phone. Функція-операція використовує функцію-елемент getline класу istream, щоб прочитати з рядка три частини телефонного номера викликаного об'єкта класу PhoneNumber (num у функції-операції й phone в main) в areaCode (код місцевості), exchange (комутатор) і line (лінія). Символи круглих дужок, пробілу й дефіса пропускаються при виклику функції-елемента ignore класу istream, що відкидає зазначену кількість символів у вхідному потоці (один символ за замовчуванням). Функція operator" повертає посилання input типу istream (тобто cin). Це дозволяє операціям введення об'єктів PhoneNumber бути зчепленими з операціями уведення інших об'єктів PhoneNumber або об'єктів інших типів даних. Наприклад, два об'єкти PhoneNumber могли б бути уведені в такий спосіб:

cin >> phonel >> phone2;

Спочатку було б виконане вираження cin " phonel шляхом виклику

operator>> (cin, phonel);

// Перевантаження операцій помістити в потік і взяти з потоку.

#include

class PhoneNumber{

friend ostream soperator <> (istream &, PhoneNumber &);

private:

char areaCode [4]; // трицифровий код місцевості й нульовий символ

char exchange [4]; // трицифровий комутатор і нульовий символ

char line [5]; // чотирицифрова лінія й нульовий символ

};

// Перевантажена операція помістити в потік

// (вона не може бути функцією-елементом).

ostream &operator<< (ostream soutput, const PhoneNumber &num)

{

output << " (" << num. areaCode << ")"

"num. exchange << "-" " num. line;

return output; // дозволяє cout << a << b <

}

// Перевантажена операція взяти зпотоку

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 << "Був введений номер телефону: "<

<< 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 з'являється в кожному випадку як правий операнд операції; а для перевантаженої операції, записаної як функція-елемент, операнд класу повинен з'являтися ліворуч. Перевантажені операції помістити в потік і взяти з потоку повинні об’являтися як дружні, якщо вони повинні мати прямий доступ до закритих елементів класу з міркувань продуктивності.


2. Розробка власного класу clsString

2.1 Загальний алгоритм вирішення

Створимо базовий клас TPString у якому розмістимо мінімальнонеобхідні компоненти, але при цьому цей клас вже буде функціональною одиницею. На основі класу TPString створимо два нащадки TPStrThread-відповідатиме за потокову обробку рядка, а клас TPStrCompare-відповідатиме за порівнння. Обидва класи будуть абстрактними, так як представляють логічно незавершений результат виконання завдання. Використовуючи множинне успадкування створимо результуючий клас clsString, додавши іще декілька методів.

Загальна UML діаграма пропонованого варіанту


2.2 Детальний анализ

Почнемо аналізувати програму з класу 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=len))

FatalError ();

return * (symb+index);

}

const char &TPString:: operator [] (int index) const

{

if ( (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

if (BuffLen>=len+count+1) {

for (int i=len; i>=pos; - -i)

{

symb [i+count] =symb [i];

}

for (int i=0; i

{

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

{

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

out<

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) об’являються оператори порівнняння. Кожен з них є константною функцією та приймає константні аргументи, що гарантує захищеність, та щожливість використання, коли об’єкт був об’явлений константно.

У рядках (16) та (20) викорисовубться функіії порівняння у стилі С. Останні функції порівняння (крім (1)) створювалися на основі заданих.

Розробка результуючого класу пов’язана з успадкуванням від двох абстрактних. Має наступну об’яву:

class clsString: public TPStrThread, public TPStrCompare

{ ··· }

Зауважимо, що базові класи не є віртуальними і немає наслідування від базового класу (як це зазначено у теоретичній частині). Виклики конструкторів будуть проходити так:

Конструктор за замовчуванням TPString

Конструктор за замовчуванням TPStrThread або TPStrCompare

Конструктор за замовчуванням TPStrCompare або TPStrThread

Конструктор clsString

Пункти 2 і 3 рівносильні і їх порядок залежить від компілятору (хоча в стандарті сказано, що вони викликаються в порядку об’яви).

В звязку з тим, що конструктори та операції присвоєння не наслідубться потрібно їх створювати зоново. Конструктори копіювання та перетворення аналогічні TPString. Розглянемо добавлені конструктори.

(1)

(2)

(3)

(4)

(5)

(6)

(7)

(8)

(9)

(10)

(11)

(12)

(13)

(14)

(15)

(16)

(17)

(18)

(19)

clsString:: clsString (const long l)

{

char s [_CVTBUFSIZE];

if (_i64toa_s (l,s,15,10) ==EINVAL) code=1;

else code=0;

len=strlen (s);

BuffLen=0;

symb=NULL;

setString (s);

}

clsString:: clsString (const double d, int pers)

{

char buf [_CVTBUFSIZE];

if (_gcvt (d,pers,buf) ! =0) code=1; else code=0;

len=strlen (buf);

BuffLen=0;

symb=NULL;

setString (buf);

}

У рядках (1) та (11) також об’явлені конструктори перетворення.

Цікавим є виділення пам’яті для тимчасового буферу, використовуючи _CVTBUFSIZE.

Згідно документації вона забезпечує саме той розмір, який необхідно для розміщення будь-якого число у строковому форматі, не залежно від системи (3) та (13).

Функція i64toa_s (4) забезпечує перетворення 64 бітного цілого на строку і у разі помилки повертає EINVAL.

Функція _gcvt (14) перетворює дійсне число у рідок символів і у разі помилки повертає її значення. (6) - (9) та (15) - (18) аналогічні розлянутим раніше.

Оператор () повертає підстроку але за типом класу:

(1)

(2)

(3)

(4)

(5)

(6)

(7)

(8)

(9)

(10)

(11)

clsString& clsString:: operator () (int index, int subLen)

{

if (index=len|| index+subLen>=len) return clsString ("");

char *tempstr=new char [subLen+1];

if (subLen==0) subLen=len-index;

strncpy (tempstr,symb+index,subLen);

tempstr [subLen] ='\0';

clsString temp (tempstr);

delete [] tempstr;

return temp;

}

У (3) забзпечення коректності роботи алгоритму. (4) виділення тимчасовогу буферу.

У рядку (6) - копіювання підрядка, котрий у (8) передається у якості аргументу.

Далі (9) знищення тимчасового буферу та (10) повертання результату.

Функція пошуку першого входження реалізована за надійним але не найшвидшим алгоритмом О (n) =n*m, n-довжина базового рядка, а m-довжина рядка еквівалент якого шукаємо.

(1)

(2)

(3)

(4)

(5)

(6)

(7)

(8)

(9)

(10)

(11)

(12)

(13)

(14)

(15)

int clsString:: find (const clsString& comp, int pos)

{

bool Notequal=1;

if (comp. len>pos+this->len) return - 1;

int fin=this->len-comp. len;

for (; (pos<=fin) &&Notequal; pos++)

{

int k=0;

for (int j=pos; k

if (this->symb [j] ! =comp. symb [k]) break;

if (k==comp. len) Notequal=0;

}

if (Notequal) return - 1;

else return pos;

}

Флагова змінна Notequal (3) слугує для визначення: чи було знайдене вхождения. Змінна fin (5) вказує на кількість символім починаючи з яких необхідно виконати порівняння. (10) вийти з вложенного циклу, якщо знайдені неодинакові символи.

2.3 Тестування

Результат проведення тестування:

(Т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)

(Т32)

(Т33)

(Т34)

(Т35)

(Т36)

(Т37)

(Т38)

(Т39)

This program will test my work

This is a small driver

Testing in process

This is a small driver

Enter string

Enter string

Good day! This is a very good day! I have already done my work!

Good day! This is a very good day! I have already done my work!

s1 is "Testing in process" and s2 is "This is a small driver"

The results of comparing is:

s2==s1 yields false

s2! =s1 yields true

s2>s1 yields true

s2

s2<=s1 yields false

s2>=s1 yields true

s1 += s2 yields s1 = Testing in processThis is a small driver

s1 after s1 [0] = 't' and s1 [1] ='E'

tEsting in processThis is a small driver

find and delete in s1 s2

tEsting in process

**********************************************************

s1 is "112211221122" and s2 is "334433443344334"Insert to s1 5 symbols from s2.

Start position 0:

33443112211221122

Insert to s1 5 symbols from s2. Start position 5:

11221334431221122

Insert to s1 5 symbols from s2. Start position end of s1:

11221122112233443

2007

12.3456789012

Для продолжения нажмите любую клавишу...

Тестування об’яви змінних, тобто конструкторів:

(1)

(2)

(3)

(4)

(5)

(6)

(7)

clsString* temp=new clsString ("This program will test my work");

cout <<*temp<

*temp= (clsString)"This is a small driver";

clsString test1 ("Testing in process");

clsString test2;

clsString test3 (*temp);

cout<<*temp<

Создамо динамічний об’єкт (1) присвоївши значення "This program will test my work". Буде викликаний конструктор преведення типів. Перевіримо отримане значення (2), вивівши його в потік (чим і почнемо тестуваня введення/виведення). Далі виконаємо явне приведення типів (3), а також перевіримо оператор присвоєння. Крім того в покроковому режимі перевіримо роботу деструктора. Варіант створення статичної змінної (4) з початковим значенням, а із значенням за замовчуванням (5). Створення об’єкту на основі конструктора копіювання у рядку (6). Створені об’єкти у рядках (3) - (6) виведемо в потік (7). Результати роботи рядкі (1) - (7) є рядки на єкрані (Т1) - (Т5).

Далі протестуємо оператор введення.

(1)

(2)

(3)

(4)

(5)

(6)

cout<<"\nEnter string"<

cin>>test2;

cout<

cout<<"Enter string"<

cin>>test2;

cout<

У рядках (1) та (3) попросимо ввести рядок символів, що закінчується оператором переходу на новий рядок. Далі (2) та (4) вводимо ці рядки, та відразу ж показуємо введене. Має сенс ввести порожню строку, а потім непорожню. Результати у рядках (6) - (11).

Протестуємо оператори порівняння:

(1)

(2)

(3)

(4)

(5)

(6)

(7)

(8)

(9)

(10)

(11)

(12)

(13)

(14)

cout<<"\ns1 is \""<

<<"\n\nThe results of comparing is: "

<<"\ns2==s1 yields "

<< (test3==test1?"true": "false")

<<"\ns2! =s1 yields "

<< (test3! =test1?"true": "false")

<<"\ns2

<< (test3

<<"\ns2<=s1 yields "

<< (test3<=test1?"true": "false")

<=s1 yields "

<=test1?"true": "false") <

У рядку (1) вказуємо дві строки, які ми будемо порівнювати. У рядках (3), (5), (7), (9), (11), (13) ми говоримо користувачу, яку операцію будемо тестувати, а у наступномі виводимо результат відповідного тестування, використовуючи тернарну операцію. Результати (Т12) - (Т20).

Наступним етапом тестування стане операція конкатенації, процес її тестування подібний до операції тестування (Т23):

cout << "\n\ns1 += s2 yields s1 = ";

test1 += test3; // test overloaded concatenation

cout << test1<

Операція індексації матиме 2 тести: зміна 0 символу (відлік символів починається з 0), будь-якого іншого, наприклад 1, бо вказівник на рядок-вказівник на перший символ, а далі перевірка операції індексації (Т24) - (Т25):

test1 [0] ='t';

test1 [1] ='E';

cout<<"s1 after s1 [0] = 't' and s1 [1] ='E'"<

cout<

Функцію пошуку об’єднаємо з функцією видалення рядка:

(1)

(2)

(3)

int pos=test1. find (test3);

test1. TPdelete (pos,test3. lenght ());

cout<

У змінну pos отримаємо позицію входження, потім (2) з цієї позиції видалимо весь рядок test3, використовуючи функцію визначення довжини рядка. Та виведемо (3) результат видалення (Т26) - (Т27).

Тестування операцій вставки більш складне:

(1)

(2)

(3)

(4)

(5)

(6)

(7)

(8)

(9)

(10)

(11)

(12)

(13)

(14)

(15)

(16)

test1="112211221122";

test3="334433443344334";

cout<<"\ns1 is \""<

cout<<"Insert to s1 5 symbols from s2. Start position 0: "<

test1. insert (test3,0,5);

cout<

test1="112211221122";

test3="334433443344334";

cout<<"Insert to s1 5 symbols from s2. Start position 5: "<

test1. insert (test3,5,5);

cout<

test1="112211221122";

test3="334433443344334";

cout<<"Insert to s1 5 symbols from s2. Start position end of s1: "<

test1. insert (test3,test1. lenght (),5);

cout<

Для полегшення перевірки вставки задамо початкові строки (1) - (2), (7) - (8), та (12) - (13).

Вставка в початок рядка (5), між 5 та 6 символами (10) та в кінець (13). Результати приведені у рядках (Т30) - (Т36).

На останок тестування конструкторів перетворення з числа до строки. Так як вони основані лице на стандартних операціях строк у стилі С, достатньо одного тесту на кожний:

(1)

(2)

(3)

(4)

(5)

(6)

temp=new clsString ( (long) 2007);

cout<<*temp<

temp->~clsString ();

temp=new clsString (-12.34567890123);

cout<<*temp<

temp->~clsString ();

У (1) та (4) створення об’єкту та передача значення. Замітимо явне приведення типу у (1), якщо цьго невиконати, то компілятор видасть помилку "error C2668: 'clsString:: clsString': ambiguous call to overloaded function". Це пов’язане з тим, що компілятор неявно не розрізняє ціле та дійсне числа, тому виника б неоднозначність. Ми використовуємо динамічні об’єкти, а отже виклик деструкторів (3) та (6) обов’язковий. Результат (2) та (3) є рядки (Т37) та (Т38).


Висновки

В ході виконання курсової роботи були отримані наступні результати.

Розроблено клас clsString, який призначений для розв'язку задачі обробки рядків.

Розроблений клас включає 4 компонентів-даних та 29 компонентів-методів, серед яких 2 є захищеними, 29 можуть успадковуватися, а 27 є загальнодоступними. Клас включає 4 конструктори, 1 деструктор, 1 віртуальних функцій, надає можливості з використання найменувань стандартних операцій для виконання введення/виведення в потік, порівняння рядків.

Розроблений клас є похідним від класів TPStrThread та TPStrCompare, котрі є похідними від класу TPString.

Особливостями розробленого класу є можливість ініціалізації, отримуючи дійсне чи ціле число, та пошук першого входження з вказаної позиції підстроки.

Результати тестування підтвердили працездатність і ефективність використання об'єктів, створюваних на основі розробленого класу.

Розроблене програмне забезпечення функціонує під керуванням операційної системи Windows.


Література

  1. MSDN

  2. Pohl, Ira C++ by Dissection. Addison-Winsley: New York, 2002.520p

  3. Stroustrup, Bjarne The C++ Programing Language. Addison-Winsley: New York, 1997.912p

  4. Дейтел Х.М. Дейтел П. Дж. Как программировать на С++. М.: Бином, 2005.1244с

  5. Липпман С.Б. С++ для начинающих. М.: Питер, 2002.1194с


Додатки

Додаток А

Код файлу "TPstr. h", з об’явою класів.

#ifndef TPSTR_H

#define TPSTR_H

#include

using std:: ostream;

using std:: istream;

class TPString

{

public:

// TPString ();

TPString (const char * = "");

TPString (TPString&);

~TPString ();

char&operator [] (int);

const char &operator [] (int) const;

TPString &operator= (TPString&);

TPString &operator+= (const TPString&);

TPString &operator+ (const TPString&);

void Clear ();

void TPdelete (int startpos, int count);

void insert (TPString&, int pos, int count);

int lenght () const;

virtual int ErrorCode () {return 0; }

protected:

int BuffLen;

int len;

char * symb;

void FatalError () const;

void setString (const char*);

};

class TPStrThread abstract: virtual public TPString

{

friend ostream &operator<< (ostream&, const TPStrThread&);

friend istream &operator>> (istream&, TPStrThread&);

public:

char* GetStr (); // повертає копію рядка у стилі С

char* GetStr (int stpos, int count); // копія підрядка у стилі С

};

class TPStrCompare abstract: virtual public TPString

{

public:

bool operator! () const; // Чи порожня строка

bool operator! = (const TPStrCompare&) const;

bool operator== (const TPStrCompare&) const;

bool operator< (const TPStrCompare&) const;

bool operator> (const TPStrCompare&) const;

bool operator<= (const TPStrCompare&) const;

bool operator>= (const TPStrCompare&) const;

};

class clsString: public TPStrThread, public TPStrCompare

{

public:

clsString (const char * = "");

clsString (const long);

clsString (const double, int pers = 12);

clsString (clsString&);

~clsString () {}

clsString &operator () (int, int);

clsString & operator= (const clsString&);

int ErrorCode () {return code; }

int find (const clsString&, int pos =0);

private:

int code;

};

#endif

Додаток Б

Код файлу "TPstr. cpp", з описом методів класів.

#include "TPstr. h"

#include

// class TPString

TPString:: TPString (const char *s)

{

len=strlen (s);

BuffLen=0;

symb=NULL;

setString (s);

}

TPString:: TPString (TPString & copy)

{

len=copy. len;

BuffLen=0;

symb=NULL;

setString (copy. symb);

}

TPString:: ~TPString ()

{

delete [] symb;

}

char&TPString:: operator [] (int index)

{

if ( (index=len)) FatalError ();

return * (symb+index);

}

const char &TPString:: operator [] (int index) const

{

if ( (index=len))

{

FatalError ();

}

return symb [index];

}

TPString &TPString:: operator= (TPString& copy)

{

len=copy. len;

setString (copy. symb);

return *this;

}

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;

}

void TPString:: Clear ()

{

len=0;

if (symb! =NULL) symb [0] ='\0';

}

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;

}

void TPString:: insert (TPString& part, int pos, int count)

{

if (pos>len) return;

if (count>part. len) count=part. len;

if (part. len

if (BuffLen>=len+count+1) {

for (int i=len; i>=pos; - -i)

{

symb [i+count] =symb [i];

}

for (int i=0; i

{

symb [pos] =part. symb [i];

}

// symb [pos] ='\0';

} 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;

}

int TPString:: lenght () const

{

return len;

}

void TPString:: setString (const char* s)

{

if (BuffLen

{

if (symb! =NULL) delete [] symb;

BuffLen=len+1;

symb=new char [BuffLen];

}

strcpy (symb,s);

}

void TPString:: FatalError () const

{

exit (1); }

// class TPStrThread

ostream &operator<< (ostream& out, const TPStrThread& tp)

{

for (int i=0; i

out<

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;

}

// TPStrThread &operator= (TPStrThread&);

char* TPStrThread:: GetStr ()

{

char *temp=new char [len+1];

strcpy (temp,symb);

return temp;

}

char* TPStrThread:: GetStr (int stpos, int count)

{

if (stpos=len||count=len) return NULL;

char *temp=new char [count+1];

strncpy (temp,symb+stpos,count);

temp [count] ='\0';

return temp;

}

// class TPStrCompare

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);

}

// class clsString

clsString:: clsString (const char * s)

{

len=strlen (s);

BuffLen=0;

symb=NULL;

setString (s);

}

clsString:: clsString (const long l)

{

char s [_CVTBUFSIZE];

if (_i64toa_s (l,s,15,10) ==EINVAL) code=1;

else code=0;

len=strlen (s);

BuffLen=0;

symb=NULL;

setString (s);

}

clsString:: clsString (const double d, int pers)

{

char buf [_CVTBUFSIZE];

if (_gcvt (d,pers,buf) ! =0) code=1; else code=0;

len=strlen (buf);

BuffLen=0;

symb=NULL;

setString (buf);

}

clsString:: clsString (clsString& s)

{

len=s. len;

setString (s. symb);

}

clsString & clsString:: operator= (const clsString& copy)

{

len=copy. len;

setString (copy. symb);

return *this;

}

clsString& clsString:: operator () (int index, int subLen)

{

if (index=len|| index+subLen>=len) return clsString ("");

char *tempstr=new char [subLen+1];

if (subLen==0) subLen=len-index;

strncpy (tempstr,symb+index,subLen);

tempstr [subLen] ='\0';

clsString temp (tempstr);

delete [] tempstr;

return temp;

}

int clsString:: find (const clsString& comp, int pos)

{

bool Notequal=1;

if (comp. len>pos+this->len) return - 1;

int fin=this->len-comp. len;

for (; (pos<=fin) &&Notequal; pos++)

{

int k=0;

for (int j=pos; k

if (this->symb [j] ! =comp. symb [k]) break;

if (k==comp. len) Notequal=0;

}

if (Notequal) return - 1;

else return pos;

}

Додаток С

Код файлу "driver. cpp", з драйвером тестування.

#include

#include "TPstr. h"

using namespace std;

int main ()

{

clsString* temp=new clsString ("This program will test my work");

cout <<*temp<

*temp= (clsString)"This is a small driver";

clsString test1 ("Testing in process");

clsString test2;

clsString test3 (*temp);

cout<<*temp<

cout<<"\nEnter string"<

cin>>test2;

cout<

cout<<"Enter string"<

cin>>test2;

cout<

cout<<"s1 is \""<

<<"\n\nThe results of comparing is: "

<<"\ns2==s1 yields "

<< (test3==test1?"true": "false")

<<"\ns2! =s1 yields "

<< (test3! =test1?"true": "false")

<<"\ns2

<< (test3

<<"\ns2<=s1 yields "

<< (test3<=test1?"true": "false")

<=s1 yields "

<=test1?"true": "false") <

cout << "\n\ns1 += s2 yields s1 = ";

test1 += test3; // test overloaded concatenation

cout << test1<

test1 [0] ='t';

test1 [1] ='E';

cout<<"s1 after s1 [0] = 't' and s1 [1] ='E'"<

cout<

cout<<"find and delete in s1 s2"<

int pos=test1. find (test3);

test1. TPdelete (pos,test3. lenght ());

cout<

cout<<"**********************************************************\n";

test1="112211221122";

test3="334433443344334";

cout<<"\ns1 is \""<

cout<<"Insert to s1 5 symbols from s2. Start position 0: "<

test1. insert (test3,0,5);

cout<

test1="112211221122";

test3="334433443344334";

cout<<"Insert to s1 5 symbols from s2. Start position 5: "<

test1. insert (test3,5,5);

cout<

test1="112211221122";

test3="334433443344334";

cout<<"Insert to s1 5 symbols from s2. Start position end of s1: "<

test1. insert (test3,test1. lenght (),5);

cout<

temp->~clsString ();

temp=new clsString ( (long) 2007);

cout<<*temp<

temp->~clsString ();

temp=new clsString (-12.34567890123);

cout<<*temp<

temp->~clsString ();

return 0;

}

Свежие статьи
Популярно сейчас
Зачем заказывать выполнение своего задания, если оно уже было выполнено много много раз? Его можно просто купить или даже скачать бесплатно на СтудИзбе. Найдите нужный учебный материал у нас!
Ответы на популярные вопросы
Да! Наши авторы собирают и выкладывают те работы, которые сдаются в Вашем учебном заведении ежегодно и уже проверены преподавателями.
Да! У нас любой человек может выложить любую учебную работу и зарабатывать на её продажах! Но каждый учебный материал публикуется только после тщательной проверки администрацией.
Вернём деньги! А если быть более точными, то автору даётся немного времени на исправление, а если не исправит или выйдет время, то вернём деньги в полном объёме!
Да! На равне с готовыми студенческими работами у нас продаются услуги. Цены на услуги видны сразу, то есть Вам нужно только указать параметры и сразу можно оплачивать.
Отзывы студентов
Ставлю 10/10
Все нравится, очень удобный сайт, помогает в учебе. Кроме этого, можно заработать самому, выставляя готовые учебные материалы на продажу здесь. Рейтинги и отзывы на преподавателей очень помогают сориентироваться в начале нового семестра. Спасибо за такую функцию. Ставлю максимальную оценку.
Лучшая платформа для успешной сдачи сессии
Познакомился со СтудИзбой благодаря своему другу, очень нравится интерфейс, количество доступных файлов, цена, в общем, все прекрасно. Даже сам продаю какие-то свои работы.
Студизба ван лав ❤
Очень офигенный сайт для студентов. Много полезных учебных материалов. Пользуюсь студизбой с октября 2021 года. Серьёзных нареканий нет. Хотелось бы, что бы ввели подписочную модель и сделали материалы дешевле 300 рублей в рамках подписки бесплатными.
Отличный сайт
Лично меня всё устраивает - и покупка, и продажа; и цены, и возможность предпросмотра куска файла, и обилие бесплатных файлов (в подборках по авторам, читай, ВУЗам и факультетам). Есть определённые баги, но всё решаемо, да и администраторы реагируют в течение суток.
Маленький отзыв о большом помощнике!
Студизба спасает в те моменты, когда сроки горят, а работ накопилось достаточно. Довольно удобный сайт с простой навигацией и огромным количеством материалов.
Студ. Изба как крупнейший сборник работ для студентов
Тут дофига бывает всего полезного. Печально, что бывают предметы по которым даже одного бесплатного решения нет, но это скорее вопрос к студентам. В остальном всё здорово.
Спасательный островок
Если уже не успеваешь разобраться или застрял на каком-то задание поможет тебе быстро и недорого решить твою проблему.
Всё и так отлично
Всё очень удобно. Особенно круто, что есть система бонусов и можно выводить остатки денег. Очень много качественных бесплатных файлов.
Отзыв о системе "Студизба"
Отличная платформа для распространения работ, востребованных студентами. Хорошо налаженная и качественная работа сайта, огромная база заданий и аудитория.
Отличный помощник
Отличный сайт с кучей полезных файлов, позволяющий найти много методичек / учебников / отзывов о вузах и преподователях.
Отлично помогает студентам в любой момент для решения трудных и незамедлительных задач
Хотелось бы больше конкретной информации о преподавателях. А так в принципе хороший сайт, всегда им пользуюсь и ни разу не было желания прекратить. Хороший сайт для помощи студентам, удобный и приятный интерфейс. Из недостатков можно выделить только отсутствия небольшого количества файлов.
Спасибо за шикарный сайт
Великолепный сайт на котором студент за не большие деньги может найти помощь с дз, проектами курсовыми, лабораторными, а также узнать отзывы на преподавателей и бесплатно скачать пособия.
Популярные преподаватели
Нашёл ошибку?
Или хочешь предложить что-то улучшить на этой странице? Напиши об этом и получи бонус!
Бонус рассчитывается индивидуально в каждом случае и может быть в виде баллов или бесплатной услуги от студизбы.
Предложить исправление
Добавляйте материалы
и зарабатывайте!
Продажи идут автоматически
5138
Авторов
на СтудИзбе
443
Средний доход
с одного платного файла
Обучение Подробнее