Четирикратното изпълнение на exhaustFreeStore() С аргументи,
които имат различен размер дава следния резултат
Free Store
Exhaused
ckunk 1000000 depth 4
Free Store Exhaused
ckunk 100000
depth 22
Free Store Exhaused
ckunk 10000 depth 209
Free Store
Exhaused
ckunk 1000 depth 2072
Една от С++ библиотеките предлага
известна помощ, като поддържа информация за свободната памет. Манипулаторът,
обработващ изключенията _new_handler се разглежда в Раздел 5.4 по-нататък в тази
глава.
Програмистът също може да постави обект, разположен в свободната
памет на определен адрес. Формата на такова извикване на оператора new има
вида
new (place_address) type-specifier
където place_address
трябва да бъде указател. За да използвате оператора new по този начин трябва
включите заглавния файл new.h. По този начин програмистът може да преразпределя
паметта, която в един по-нататъшен момент ще съдържа обекти, определени чрез
тази форма на оператора new. Например,
#include
<stream.h>
#include <new.h>
const Chunk = 16;
class
Foo { public
int val;
Foo() { val = 0; }
};// preallocate memory, but
no Foo objects
char *buf = new char[ sizeof( Foo ) * Chunk ];
main()
{
// construct Chunk Foo objects for buf
Foo *pb = new (buf) Foo[ Chunk
];
// check that objects were plased in buf
if ( (char*)pb == buf
)
cout << "Operator new worked! pb "<< pb << " buf
"
<< (void* )buf << "n";
}
Когато тази програма бъде
компилирана и изпълнена ще получим следните резултати
Operator new
worked!
pb 0x234cc
buf 0x234cc
Възможно е да се появи известно
объркване относно тази програма. То е свързано с изпращането на buf към void*.
Това е необходимо, понеже когато към оператора за изход се изпраща операнд char*
се отпечатва “null terminated string”, т.е. низа, който е адресиран. Чрез
изпращането на buf към void* операторът за изход знае, че трябва да отпечата
адресната стойност на buf. Това се дължи на факта, че операторът за изход се
презарежда така, че да използват два различни указателни типове на аргументи
char* и void*. Презаредимите функции се разглеждат в един от подразделите на
тази глава. Въпреки, че този тип на оператора new се използва главно с типовете
class, той може да се използват и за вградените типове данни.
Например,
#include <new.h>
int *pi = new
int;
main(){
int *pi2 = new (pi) int;
}
5.2. Един
пример за свързан списък
В този раздел се реализира един елементарен
клас списък от цели числа за да бъде илюстрирана както работата с указатели,
така и използването на операторите new и delete. Като минимум IntList трябва да
поддържа две стойности - цялата стойност на елемента на списъка и адреса на
следващия елемент на списъка. Това може да се представи по следния
начин
int val;
ListItem *next;
Един списък представлява
последователност от елементи. Всеки елемент съдържа стойност и указател, може и
null, към следващия елемент на списъка. Списъкът може да бъде и празен; т.е. да
бъде списък без елементи
IntList i1; // the empty list
Списъкът
може да нараства чрез добавяне на елементи. Тези елементи могат да бъдат
вмъквани в началото на списъка
i1.insert( someValue );
или
добавяни към края му
i1.append( someValue );
Списъкът може да бъде
намаляван чрез отстраняване на елементи (предполага се, че той не е
празен)
i1.remove( someValue );
Потребителят трябва да бъде в
състояние да показва елементите на на списъка
i1.display();
Ето
една първа програма, която бихме желали да напишем като използваме класа
IntList.
#include "IntList.h"
const SZ = 12;
main()
{
IntList i1;
i1.display();
for ( int i = 0; i < SZ; ++i
)
i1.insert( i );
i1.display();
IntList i12;
for ( i = 0; i <SZ;
++i ) i12.append( i );
i12.display();
return 0;
}
Когато тази
програма бъде компилирана и изпълнена се получава следния резултат
(
empty )( 11 10 9 8 7 6 5 4 3 2 1 0 )( 0 1 2 3 4 5 6 7 8 9 10 11 )
Първата
стъпка за реализацията на тази програма е дефинирането на класа IntList. Това е
и първото място, където можем да сгрешим. Неправилен за проекта избор ще бъде да
декларираме както val, така и next като членове на IntList.
Например,
class IntList {
public IntList ( int = ??? );
//
...private
int val;
IntLIst *next;
};
При този проект възникват
няколко проблема. Всичките те произтичат от объркването между обекта списък и
елемента на списъка. Например, при този проект не се допуска наличието на празен
списък. Не съществува начин за разграничаване на списъка, съдържащ един елемент
от празния списък. Въпросителните знаци, в сигнатурата на конструктора на
IntList са предназначени да подчертаят този проблем. Няма подразбираща се
стойност за инициализиране на val, чрез която да се отбелязва, че списъкът е
празен. Другите проблеми възникват от това, че не е определен смисъла на
insert() и remove() когато обектът от тип IntList предлага също и първия елемент
на списъка.
В пректа на IntList трябва да бъде направено разграничаване
между елементите на списъка и самия обект списък като такъв. Един от начините да
бъде направено това е да бъде дефиниран както клас IntList, така и клас IntItem.
Ето дефиницията на IntItem
class IntList;
class intItem{
friend
class IntList;
private IntItem( int v=0 ) { val = v; next = 0 )
IntItem
*next;
int val;
};
IntItem се нарича клас private (личен). Само на
IntList е разрешено да създава и обработва IntItem обектите. Това е смисъла на
декларацията friend. Раздел 6.5 разглежда подробно тази декларация. Раздел 6.1
обяснява разликите между декларациите private и public. IntList е реализиран по
следния начин:
class IntItem;
class IntList {
public IntList(int
val) { list = new IntItem( val ); }
IntList() { list = 0; }//
...
private
IntItem *list;
};
Упражнение 5-1. Защо IntList се
нуждае от два конструктора? Защо, например, да не дефинираме простоIntList( val
= 0 );
Упражнение 5-2. Един допълнителен член данни на IntList може да бъде
int len; // length of list, който да съдържа броя на елементите на списъка.
Разгледайте аргументите за и против тази декларация.
Следващата стъпка се
състои в реализирането на член-функции, които поддържат потребителските
обработки на IntList обектите. insert() поставя даден нов IntItem в началото на
списъка. Това се реализира така
IntList
insert( int val ) {
// add
to the front of the list
IntItem *pt = new IntItem( val );
pt->next =
list;
list = pt;
return val;
}
append() е малко по-сложна. Тя
трябва да добавя нов IntItem в края на списъка. Една помощна функция, atEnd(),
връща указател към последния елемент на списъка
IntItem
*IntList
atEnd(){ // return pointer to last item on list
IntItem *prv,
*pt;
for ( prv=pt=list; pt; prv=pt; pt=pt->next ); // null
statement
return prv;
}
append() трябва да проверява специалния
случай на празен списък. Реализацията изглежда по следния
начин
IntList
append( int val ) {
// add to the back of the
list
IntItem *pt = new IntItem( val );
if ( list == 0 ) list = pt;
else
(atEnd())->next = pt;
return val;
}