Ефективне використання пам’яті класом String 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Ефективне використання пам’яті класом String



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

 

Недоліки класу String

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

s2=s1;

Але, якщо ми насправді перезавантажимо оператор присвоювання, постане питання, що ж робити з реальними рядками (тобто масивами символьного типу char), які є принциповими елементами даних для класу String.

Можна зробити так, щоб кожен об’єкт мав спеціально відведене «місце» для зберігання рядка. Тоді, присвоюючи один об’єкт класу String іншому, ми просто копіюємо рядок з вихідного об’єкту в об’єкт призначення, Але, якщо ми хочемо економити пам’ять, то зручніше працювати не з самими рядками, а з вказівниками на них. Тепер, присвоюючи значення одного об’єкта іншому, ми копіюємо тільки вказівник з одного об’єкту в інший. Обидва вказівники, звичайно, вказуватимуть на той сам рядок. При цьому ми зберігаємо в пам’яті тільки один екземпляр кожного рядку.

Але при цьому треба уважно слідкувати за видаленням об’єктів класу String. Якщо деструктор цього класу використовує delete для вивільнення пам’яті, зайнятої символьним рядком, і якщо є кілька вказівників на цей рядок, об’єкти так і залишаться з «завислими» вказівниками, вказуючими на місце, де давно нема рядка.

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

 

Клас-лічильник рядків

Припустимо, що існує кілька об’єктів класу String, що вказують на певний рядок, і ми хочемо навчити програму підраховувати, скільки саме об’єктів на нього вказує. Де нам зберігати цей лічильник?

Для кожного об’єкту класу String було б надто обтяжливо підраховувати, скільки його колег вказує на даний рядок, тому ми не будемо вводити лічильник до складу полів класу String. Наступна ідея – використати статичну змінну, точніше, статичний масив, який зберігав би список адрес рядків та їх порядкових номерів. Але ще раціональніше було б створити новий особливий клас для підрахунку рядків та вказівників. Кожен об’єкт такого класу – назвімо його strCount – буде містити лічильник і сам вказівник на рядок. В кожен об’єкт класу String помістимо вказівник на відповідний об’єкт класу strCount. Щоб переконатися у нормальному доступі об’єктів String до об’єктів strCount, зробимо String дружнім по відношенню до strCount. Крім того, нам хотілося б достовірно знати, що клас strCount використовується тільки класом String. Щоб заборонити несанкціонований доступ до будь-яких його функцій, зробімо всі методи strCount прихованими. Оскільки String є дружнім, на нього це обмеження не поширюється.

Далі приведений лістінг програми 13.17

 

 #include<iostream.h>

 #include<conio.h>

 #include<stdio.h>

 #include<bios.h>

 #include<string.h>

 /////////////////

 class strCount

 {private:

 int count;

 char* str;

 friend class String;

 strCount(char* s)

 {int length=strlen(s);

 str=new char[length+1];

 strcpy(str,s);

 count=1;

 }

 ~strCount()

 {delete[] str;}

 };

 /////////////////

 class String

 {private:

 strCount* psc;

 public:

 String()//конструктор без аргументів

 {psc=new strCount("NULL");}

 //-------

 String(char* s)//конструктор з 1 аргументом

 {psc=new strCount(s);}

 //-----------

 String(String& S) //конструктор копіювання

 {psc=S.psc;

 (psc->count)++;}

 //----------

 ~String() //деструктор

{if(psc->count==1)

delete psc;

else

(psc->count)--;

}

//----------

void display()

{cout<<psc->str;

cout<<" (addr="<<psc<<")"; //виведення адреси

}

//-----------

void operator=(String& S)

{if(psc->count==1)

delete psc;

else

(psc->count)--;

psc=S.psc;

(psc->count)++;

}

 };

 //////////////

 int main()

 {clrscr();

 String s3="Яка-небудь пробна фраза";

 cout<<"\ns3=";s3.display();

 String s1;

 s1=s3;

 cout<<"\ns1=";s1.display();

 String s2(s3);

 cout<<"\ns2=";s2.display();

 cout<<endl;

 bioskey(0);

 return 0;

 }

Програма 13.17

В частині main() даної програми ми визначаємо об’єкт класу String s3, що містить яку-небудь пробну фразу. Потім визначаємо ще один об’єкт s1 і присвоюємо йому значення s3. Визначаємо s2 та ініціалізуємо його за допомогою s3. При цьому ми спершу запускаємо перезавантажену операцію присвоювання, а потім – перезавантажений конструктор копіювання. Потім виводимо всі три рядки, а також адресу об’єкту класу strCount, на який посилається вказівник кожного об’єкту. Ми це робимо, щоб показати, що всі рядки насправді представлені тим самим рядком.

Інші обов’язки класу String ми поділили між класами strCount і String.

Клас strCout містить вказівник на реальний рядок і підраховує, скільки об’єктів класу String на нього вказують. Його єдиний конструктор розглядає вказівник на рядок в якості аргументу і виділяє для нього область пам’яті.

str=new char[length+1];

Він копіює рядок в цю область і встановлює лічильник в одиницю, оскільки лише один об’єкт String вказує на цей рядок відразу ж після створення.

strcpy(str,s);

count=1;

Деструктор класу strCount вивільняє пам’ять, зайняту рядком. Ми використовуємо delete[] з квадратними дужками, оскільки рядок – це масив.

Натомість у класу String є 3 конструктори. При створенні нового рядка генерується новий об’єкт strCount для його зберігання, а вказівник psc зберігає посилання на цей об’єкт. Якщо копіюється вже існуючий об’єкт String, вказівник psc продовжує вказувати на старий об’єкт strCount, а лічильник в цьому об’єкті збільшується. Перезавантажена операція присвоювання повинна нарівні з деструктором видаляти старий об’єкт strCount, на який вказує psc, якщо лічильник рівний одиниці. (Тут нам квадратні дужки після delete вже непотрібні, оскільки ми видаляємо єдиний об’єкт strCount). Але чому оператор присвоювання повинен займатися видаленням? Справа в тому, що об’єкт String, що стоїть у виразі зліва від знаку рівності (назвемо його s1), вказував на певний об’єкт strCount (назвемо його oldstrCount). Після присвоєння s1 буде вказувати на об’єкт, що знаходиться справа від знаку рівності. А якщо більше не існує об’єкту String, що вказують на oldstrCount, він повинен бути видалений. Якщо ж ще залишилися об’єкти, що посилаються на нього, його лічильник повинен бути зменшеним.

if(psc->count==1)

delete psc;

else

(psc->count)--;

 

Вказівник this

Методи кожного об’єкта мають доступ до вказівника this, що є аналогом відомого нам з вивчення Паскалю неявного параметра Self. В програмі 13.18 показано механізм роботи з цим вказівником.

 #include<iostream.h>

 #include<conio.h>

 #include<stdio.h>

 #include<bios.h>

 class where

 {private:

 char chararray[10];

 public:

 void reveal()

 { cout<<"\n Адреса обєкту "<<this;}

 };

 ///////////////

 int main()

 {clrscr();

 where w1,w2,w3;

 w1.reveal();

 w2.reveal();

 w3.reveal();

 cout<<endl;

 bioskey(0);

 return 0;

 }

Програма 13.18

В головній функції цієї програми створюються три об’єкти типу where. Потім друкуються їх адреси за допомогою методу reveal(). Цей метод просто виводить значення вказівника this на екран, показуючи адресу кожної змінної. Конкретна адреса залежатиме від багатьох факторів і відрізнятиметься при різних виконаннях програми.

 



Поделиться:


Последнее изменение этой страницы: 2021-12-15; просмотров: 49; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.145.111.183 (0.017 с.)