Лекции по структури от данни и програмиране



страница1/4
Дата25.07.2016
Размер0.95 Mb.
#6569
ТипЛекции
  1   2   3   4
Лекции по структури от данни и програмиране
Писани са от мен, Иван Димитров Георгиев (вече завършил) студент по информатика, електронната ми поща е ivandg@yahoo.com.

Четени през зимния семестър на учебната 2002/2003 година. Лектор тогава беше доц. Стоян Бъчваров.

Използвал съм единствено и само мои (и на мои колеги) записки от лекции. Всички програми са компилирани на C++.

1. Вход и изход на потоци. Общи сведения.
стандартните библиотеки в C++ имат разширен набор от средства за вход и изход;

всяка операция за вход и изход в C++ се изпълнява по начин, който зависи от типа на данните; ако за даден тип данни е била дефинирана функция за вход и изход, то именно тя се извиква при обработване на този тип данни; ако няма съответствие между типа на данните и функцията за обработка на този тип данни, компилаторът дава съобщение за грешка; по такъв начин несъответстващите данни не могат да проникнат в системата, както това може да се случи в C, което от своя страна води до възникване на трудно откриваеми грешки;

C++ дава възможност за стандартна обработка на входа и изхода както за вградените типове, така и за типове, дефинирани от потребителя; в C++ се извършва вход и изход на поток от байтове, т.е. на последователност от байтове;

входът и изходът могат да бъдат форматирани или неформатирани, т.е. с преобразуване или без преобразуване; при неформатиран вход и изход се постига максимална ефективност, когато се обработват файлове с голям обем;

библиотеката за вход и изход от потоци е iostream; нейният интерфейс е разбит на няколко заглавни файла:


  • заглавният файл iostream.h съдържа основни сведения, които са необходими за всички операции за вход и изход; предвидени са средства за форматирани и неформатирани вход и изход; този заглавен файл включва обектите:

cin – съответства на стандартния поток за вход;

cout – съответства на стандартния поток за изход;

cerr – съответства на небуфериран поток за извеждане на съобщения за грешки;

clog – съотвества на буфериран поток за извеждане на съобщения за грешки;



  • заглавният файл iomanip.h съдържа информация, полезна за обработката на форматиран вход и изход с помощта на така наречените параметризирани манипулатори на потока;

  • заглавният файл fstream.h съдържа информация за извършване на операции с файлове;

  • заглавният файл strstream.h съдържа информация за използване на форматиран вход и изход в паметта; това прилича на обработката на файлове, но операциите за вход и изход се извършват със символни масиви, а не с файлове;

  • заглавният файл stdiostream.h включва нужните сведения за програми, които използват при изпълнение изпълнение на операциите за вход и изход средства от C;

програмите на C++ могат да използват също други библиотеки, свързани с вход и изход, в които са предвидени например такива специфични за системата възможности, като управляване на специални устройства за вход и изход на аудио и видео данни;
библиотеката iostream съдържа много класове за извършване на широк спектър операции за вход и изход; класът istream и класът ostream са производни класове, които пряко наследяват базовия клас ios; класът iostream е производен клас, който множествено наследява класовете istream и ostream;

предефинирането на операции позволява удобно записване на операциите за вход и изход;

операцията за изместване вляво << е предефинирана за означаване на изход в поток и се нарича операция за извеждане в поток;

операцията за изместване вдясно >> е предефинирана за означаване на вход от поток и се нарича операция за въвеждане от поток;

тези операции се прилагат към обектите на стандартните потоци cin, cout, cerr, clog и също се използват с обекти на потоци, чийто тип е дефиниран от потребителя;

обектът cin е от клас istream и е свързан със стандартното устройство за вход (обикновено клавиатурата);

обектът cout е от клас ostream и е свързан със стандартното устройство за изход (обикновено екрана на монитора);

операциите за въвеждане от поток и извеждане в поток определят типа на използваните данни; ако типът не е предвиден, т.е. операциите не са предефинирани за този тип, се включват различни флагове за грешки, с помощта на които потребителя може да определи дали операциите за въвеждане и извеждане са били успешни;

обектът cerr е от клас ostream и е свързан със стандартното устройство за извеждане на съобщения за грешки; изведеният поток от данни за обекта cerr е небуфериран, т.е. всяка операция за извеждане в cerr води до незабавно появяване на изведеното съобщение;

обектът clog е от клас ostream и също е свързан със стандартното устройство за извеждане на съобщения за грешки; изведеният поток от данни за обекта clog е буфериран, т.е. всяка операция за извеждане в clog води до съхраняване на изведеното в буфер, докато той не бъде запълнен изцяло или докато съдържимото на буфера не бъде изведено принудително;


при обработка на файлове в C++ се използват следните класове:

  • клас ifstream, който изпълнява операции за вход от файлове;

  • клас ofstream, който изпълнява операции за изход във файлове;

  • клас fstream, който е предназначен за операции при вход и изход от файлове;

класът ifstream наследява istream, класът ofstream наследява ostream и класът fstream наследява iostream;
в повечето системи в пълната йерархия на класовете се съдържат още много други класове, които се използват при вход и изход на потоци, но разгледаните класове предоставят широки възможности, достатъчни за много програмисти;
2. Форматиран изход на потоци. Неформатиран вход и изход.
класът ostream в C++ дава възможност за реализиране на форматирано и неформатирано извеждане в поток на следните данни:

  • извеждане на данни от стандартни типове, с помощта на операцията за извеждане в поток (<<);

  • извеждане на символи с помощта на операцията put;

  • неформатирано извеждане с помощта на операцията write;

операцията за изместване наляво стандартно е предефинирана за извеждане на данни от вградени типове, за извеждане на низове и за извеждане стойността на указатели; тази операция може да бъде предефинирана в програмата за извеждане на данни от потребителски типове; възможно е слепване на операцията за извеждане в поток, тъй като тя връща псевдоним на левия си операнд и е ляво-асоциативна;

преминаването на нов ред става с помощта на управляващия символ ‘\n’ или с помощта на манипулатора endl; изпълнението на endl освен това води до незабавно извеждане на съдържимото на буфера; това може да стане и с манипулатора flush;
в езика C++ има функция елемент tie, която се използва за синхронизация, т.е. свързване на изпълнението на операциите с потоци за вход и за изход; функцията tie гарантира, че изведеното ще се покаже преди следващото въвеждане, което е много важно при създаване на интерактивни приложения; за да се синхронизират потока за вход inputStream от клас istream и потока за изход outputStream от клас ostream се използва следния оператор

inputStream.tie (&outputStream);

тази връзка се разпада със следното извикване:

inputStream.tie (0);

в C++ автоматично е създадена такава връзка между cin и cout, т.е.

автоматично се извиква cin.tie (&cout);

операцията << може да се използва за извеждане на данни от тип char* като символен низ, който завършва с нулев байт (‘\0’);

например:

char *string = “Проверка”;

cout << string << endl;

чрез явно преобразуване на типа на указателя string към void* можем да изведем стойността на самия указател string, т.е. адреса на първия символ на низа:

cout << (void *) string << endl;

това се отнася за всеки указател към променлива, ако програмистът иска да изведе нейния адрес;
функцията елемент put извежда един символ; например оператора

cout.put (‘A’);

ще изведе символа ‘A’; извикванията на put могат да бъдат слепени – например:

cout.put (‘A’).put (‘B’);

това е възможно, тъй като операцията ‘.’ е ляво-асоциативна и функцията елемент put връща псевдоним на обекта, за който е била извикана; аргументът на функцията put може да бъде произволен израз, чиято стойност е ASCII код на символ, например:

cout.put (65);

ще изведе символа ‘A’;
неформатиран вход и изход се извършва с помощта на функциите елементи read и write;

функцията елемент read от класа istream въвежда определен брой байтове в символен масив от паметта; тези байтове не се преобразуват; ако са прочетени по-малък брой символи се включва флага failbit;

функцията елемент write от класа ostream извежда определен брой байтове от символен масив в паметта, включително и нулевите байтове; изведените байтове не се преобразуват;

функцията елемент gcount от класа istream връща броя на символите, прочетени от последната операция за вход;

примерна програма:
#include

const int SIZE = 80;

void main ()

{ char buffer[SIZE];

cout << "Въведете изречение: ";

cin.read (buffer, 20);

cout.write (buffer, cin.gcount());

cout << endl;

}
3. Форматиран вход на потоци.
входният поток е разбит на елементи; между елементите има един или повече празни символи (интервал, табулация, символ за нов ред);
операцията за въвеждане от поток (>>) връща нулева стойност, т.е. false, ако срещне в потока символа за край на файла; в противен случай тя връща псевдоним на левия си операнд, т.е. на обекта за който тя е била извикана – това позволява слепване на операцията за въвеждане; операцията за въвеждане включва флага failbit, т.е. задава му стойност 1, при въвеждане на данни от неправилен тип и включва флага badbit при неуспешно завършване на операцията;

типична грешка е да се въвеждат данни от поток от клас ostream или да се извеждат данни в поток от клас istream;

много често операцията за въвеждане от поток се използва като условие в оператор за цикъл; примерна програма:
#include

#include

void main ()

{ double grade, highestgrade = -1;

cout << "Въведете оценка или Ctrl+Z за край: ";

while (cin >> grade)

{ if (highestgrade < grade) highestgrade = grade;

cout << "Въведете оценка или Ctrl+Z за край: ";

}

cout << endl << "Най-високата оценка е: " << setprecision(2) <<



highestgrade << endl;

}
изразът cin >> grade може да бъде използван като условие, тъй като производният клас istream наследява от базовия клас ios предефинирана операция за преобразуване на тип поток в указател от тип void*, при това стойността на указателя е 0 (NULL), ако е възникнала грешка при опит за въвеждане на данни или ако е стигнат символ за край на файла; за да може да използва израз от тип void* като условие компилаторът извършва вградено преобразуване на израза към целочислен тип;


функцията елемент get има три варианта:

  • без параметри – функцията въвежда един символ от указания поток, включително и празните символи (‘ ‘, ‘\t’, ‘\n’) и връща този символ като резултат; връща EOF, ако се достигне край на файла; пример:

int c;

cout << “Въведете низ и Ctrl+Z за край: “;

while ( (c = cin.get()) != EOF) cout.put (c);


  • с един параметър от тип char – функцията въвежда един символ от входния поток, включително и празните символи и го присвоява на символния аргумент; тя връща 0, ако се достигне символ за край на файла, а в останалите случаи връща псевдоним на обекта за който е извикана;

  • с три параметъра – указател от тип char *, максимален брой символи и ограничител, който по премълчаване е ‘\n’; този вариант на get чете символи от входния поток докато техният брой стане с 1 по-малко от зададения брой или докато срещне ограничителя; функцията автоматично добавя нулев байт към низа; ограничителят не се записва в символния масив, а остава във входния поток; пример:

#include

const int SIZE = 80;

void main ()

{ char buffer1[SIZE], buffer2[SIZE];

cout << "Въведете изречение: ";

cin >> buffer1;

cout << buffer1 << endl;

cin.get(buffer2, SIZE);

cout << buffer2 << endl;

}
функцията елемент getline има същите параметри като третия вариант на get и действа по същия начин с тази разлика, че тя чете символа ограничител и го премахва, т.е. не го записва в символния масив; пример:

char buffer[80];

cout << “Въведете изречение: “;

cin.getline (buffer, SIZE);

cout << buffer << endl;
функцията елемент ignore приема два параметъра – брой символи и символ ограничител; тя пропуска зададения брой символи (по премълчаване 1) или завършва своята работа при откриване на ограничителя, при това тя го премахва от входния поток; по премълчаване ограничителят е EOF;
функцията елемент putback връща символ обратно във входния поток; обикновено преди това символът е прочетен с get; функцията е полезна за приложения, които преглеждат входния поток с цел търсене на запис, който започва с даден символ; когато този символ е въведен, приложението го връща обратно във входния поток, за да бъде включен в данните, които ще се въведат по-нататък;
функцията елемент peek връща поредния символ от входния поток, включително и празните символи, но не го премахва от потока;
4. Манипулатори на потоци.
манипулаторите на потоци се използват за форматиране; те позволяват да се изпълняват следните операции:


  • задаване на минимална ширина на поле;

  • задаване на точност за числа с плаваща точка;

  • включване и изключване на флагове на формата;

  • задаване на запълващ символ на поле;

  • изчистване на буферите на потоци;

  • извеждане на символ за нов ред и изчистване на буфера;

  • извеждане на нулев байт;

  • пропускане на символите разделители във входния поток;

основата на целите числа в поток се задава с манипулаторите:



  • dec (decimal) – за десетични цели числа;

  • oct (octal) – за осмични цели числа;

  • hex (hexal) – за шестнайсетични цели числа;

по премълчаване целите числа се интерпретират като десетични;

основата на целите числа в поток може да се зададе и чрез манипулатора setbase, който има един цял параметър, приемащ стойности 8, 10, 16, задаващи съответната основа; тъй като setbase има параметът, той се нарича параметризиран манипулатор; използването на такива манипулатори изисква включването на заглавния файл iomanip.h; основата на бройната система остава една и съща, докато не бъде променена отново; пример:

int n = 20;

cout << n << endl //20



<< hex << n << endl //14

<< oct << n << endl //24

<< dec << n << endl //20

<< setbase (16) << n << endl; //14
с помощта на манипулатора setprecision или функцията елемент precision се задава точността на извежданите числа с плаваща точка, т.е. броя на изведените цифри в дробната част; по премълчаване точността е 6 цифри; зададената точност действа за всички следващи операции за извеждане, докато не бъде явно зададена отново; функцията елемент precision без параметри връща текущата стойност на точността; пример:

#include

#include

#include

void main ()

{ double root2 = sqrt (2);

for (int i = 0; i <= 9; i++)

{ cout.precision (i);

cout << root2 << endl;

}

}



програмата ще върне:

1.

1.4



1.41

1.414


1.4142

1.41421


1.414214

1.4142136

1.41421356

1.414213562

ще отбележим, че при извеждането на числа с плаваща точка се извършва закръгляне; ако използваме манипулатора setprecision тялото на цикъла трябва да изглежда така:

cout << setprecision (i) << root2 << endl;


ширината на полето, т.е. броят на символните позиции, в които стойността се извежда или броя на символите, които се въвеждат се задава с функцията елемент width от класа ios; ако обработваната стойност има по-малко символи от зададената ширина на полето, то в излишните позиции се извежда запълващ символ (по премълчаване ‘ ‘), при това подравняването по премълчаване е отдясно; ако броят на символите в обработваната стойност е по-голям от зададената ширина на полето, то излишните символи не се отчитат и стойността ще бъде напълно изведена; зададената ширина на полето влияе само на следващата операция за извеждане или въвеждане – след това ширината на полето неявно се задава 0, т.е. то ще бъде толкова широко, колкото е необходимо; функцията width без параметри връща текущата ширина на полето; за задаване на ширина на полето може да се използва и параметризирания манипулатор setw; при въвеждане на низ се четат поне с един символ по-малко от зададената ширина, за да може да се разположи нулев байт в края на въвеждания низ; пример:

#include

void main ()

{ int w = 4;

char string[10];

cout << “Въведете изречение: “; cin.width (5);

while (cin >> string)

{ cout.width (w++);

cout << string << endl;

cin.width (5);

}

}

ако въведем изречението: Това е проверка. програмата ще върне



Позиции

1

2

3

4

5

6

7

8

Т

о

в

а

























е
















п

р

о

в
















е

р

к

а

























.

потребителите могат да създават свои собствени манипулатори на потоци, включително и параметризирани; пример:

#include

ostream &bell (ostream &output)

{ return output << ‘\a’; } //извеждане на звук

ostream &ret (ostream &output)

{ return output << ‘\r’; } //връщане в началото на реда

ostream &tab (ostream &output)

{ return output << ‘\t’; } //отместване с 8 символа

ostream &endline (ostream &output)

{ return output << ‘\n’ << flush; } //аналогичен манипулатор на endl

void main ()

{ cout << endline << ‘a’ << tab << ‘b’ << tab << ‘c’ << endline

<< “!!!!!”;

for (int i = 1; i <= 10; i++) cout << bell;

cout << ret << “______” << endline;

}

програмата връща:


a b c

______


5. Флагове за състояние на формата.
различни флагове за състоянието задават вида на форматирането, което се извършва при изпълнение на операциите за вход и изход; определянето на стойността на флаговете се управлява от функциите елементи setf, unsetf, flags; всеки от флаговете за състоянието на формата е определен като стойност от изброен тип в класа ios;

състоянието на формата се определя от защитената данна елемент

x_flags от тип long в класа ios; всеки флаг представлява отделен бит в x_flags – той има стойност 1, ако е вдигнат и 0 в противен случай;

за обединяване на няколко флага в една стойност от тип long може да се използва операцията поразрядно логическо или (‘|’);

извикването на функцията елемент flags с един параметър от тип long задава нова стойност за променливата x_flags, т.е. задава нови стойности за всички флагове за състоянието на формата, и връща нейната предишна стойност; ако функцията бъде извикана без параметри, тя само връща текущата стойност на x_flags;

например след извикването:

cout.flags (ios::fixed | ios::showpoint)

флаговете fixed и showpoint ще бъдат вдигнати (ще имат стойност 1), а всички останали флагове ще имат стойност 0;


функцията елемент unsetf изчиства, т.е. анулира указаните флагове в променливата x_flags и връща нейната предишната стойност;

функцията елемент setf има два варианта:

long setf (long setbits, long resetbits);

този вариант анулира битовете resetbits в променливата x_flags и включва битовете setbits;

long setf (long setbits);

този вариант включва битовете setbits в променливата x_flags;

извикванията cout.flags (cout.flags() | setbits) и cout.setf (setbits) са еквивалентни;
параметризираните манипулатори setiosflags и resetiosflags изпълняват същите действия както функциите setf и unsetf съответно;
вдигнатият флаг skipws показва, че операцията въвеждане от поток (>>) трябва да пропуска символите разделители (‘ ‘, ‘\n’, ‘\t’); по премълчаване skipws е вдигнат, т.е. разделителите се пропускат; ако флагът skipws е вдигнат, то с помощта на извикванeто

cin.unsetf (ios::skipws) се отменя пропускането на разделителните символи; манипулаторът ws вдига флага skipws;


вдигнатият флаг showpoint указва, че при извеждане на числа с плаваща точка, задължително ще се покаже десетичната точка и нулите в края на дробната част на числото, в съответствие със зададената точност; пример:

cout << 9.9900 << endl



<< 9.9000 << endl

<< 9.0000 << endl;

cout.setf (ios::showpoint);

cout << 9.9900 << endl

<< 9.9000 << endl

<< 9.0000 << endl;

този фрагмент ще изведе:

9.99

9.9


9

9.990000


9.900000

9.000000
флаговете left и right задават подравняване на изведеното съответно отляво и отдясно на полето; празните позиции се заемат от запълващия символ (по подразбиране ‘ ‘); по премълчаване подравняването е отдясно;

пример:

int x = 12345;



cout << setw (10) << x << endl << setw (10);

cout.setf (ios::left, ios::adjustfield);

cout << x << endl;

cout.unsetf (ios::left);

cout << setw (10) << x << endl << setw (10)

<< setiosflags (ios::left) << x << endl

<< setw (10) << resetiosflags (ios::left)

<< x << endl;

фрагментът ще изведе:

_ _ _ _ _ 12345

12345 _ _ _ _ _

_ _ _ _ _ 12345

12345 _ _ _ _ _

_ _ _ _ _ 12345
аргументът ios::adjustfield, който съдържа трите флага left, right, internal е необходимо да се задава като параметър на функцията setf, когато тя се използва за вдигане на тези флаговете, тъй като чрез него първоначално те се изчистват, което гарантира, че след изпълнението на setf точно един от трите флага е вдигнат – това се налага, тъй като те взаимно се изключват;
вдигнатият флаг internal показва, че знакът на числото или основата при вдигнат флаг showbase трябва да се подравнява отляво, стойността на числото да се подравнява отдясно, а в междинните позиции да се вмъкнат запълващи символи; вдигането на флага showpos води до извеждане на ‘+’ за положителни числа; пример:

cout << setiosflags (ios::internal | ios::showpos)



<< setw (10) << 123 << endl;

int x = 10000;

cout.setf (ios::showbase);

cout << setw (10) << x << endl;

cout.setf (ios::left, ios::adjustfield);

cout << setw (10) << x << endl;

cout.setf (ios::internal, ios::adjustfield);

cout << setw (10) << hex << x << endl;

cout.setf (ios::right, ios::adjustfield);

cout.fill (‘*’);

cout << setw (10) << dec << x << endl;

cout.setf (ios::left, ios::adjustfield);

cout << setw (10) << setfill (‘%’) << x << endl;

cout.setf (ios::internal, ios::adjustfield);

cout << setw (10) << setfill (‘*’) << hex << x << endl;

фрагментът ще изведе:



Позиции

1

2

3

4

5

6

7

8

9

10

+



















1

2

3

+













1

0

0

0

0

+

1

0

0

0

0













0

x













2

7

1

0

*

*

*

*

+

1

0

0

0

0

+

1

0

0

0

0

%

%

%

%

0

x

*

*

*

*

2

7

1

0

функцията елемент fill задава запълващ символ и връща предишния такъв; манипулаторът setfill има същото действие;


статичният елемент basefield от ios, аналогично на adjustfield, включва флаговете dec, oct, hex; тези флагове показват как да се интерпретират целите числа – като десетични, осмични или шестнайсетични; по аналогични причини (трите флага взаимно се изключват) при извикване на setf се указва като втори аргумент

ios::basefield; ако нито един от флаговете не е вдигнат, то по премълчаване целите числа при операция извеждане в поток се интерпретират като десетични; при операцията въвеждане от поток данните по премълчаване се обработват в тази форма, в която се посочват – целите започващи с 0 като осмични, започващи с 0x или 0X като шестнайсетични, а всички останали като десетични;

ако за потока е зададена основата, то всички цели числа в този поток се обработват при тази основа, докато не бъде зададена нова основа;

при вдигнат флаг showbase се извежда основата на целите числа – осмичните започват с 0, шестнайсетичните с 0X или 0x; пример:

int x = 100;

cout << setiosflags (ios::showbase) << x << endl



<< oct << x << endl << hex << x << endl;

фрагментът извежда:

100

0144


0x64
флаговете scientific и fixed се съдържат в статичния елемент floatfield от класа ios; те се използват в setf аналогично на adjustfield и на basefield; тези флагове управляват формата за извеждане на числата с плаваща точка - вдигнатият флаг scientific води до извеждане в експоненциален формат, докато вдигнатият флаг fixed води до извеждане с фиксирана точка; ако тези флагове не са вдигнати, стойността на числото с плаваща точка определя формата на неговото извеждане; пример:

double x = .001234567, y = 1.946e09;

cout << x <<’\t’ << y << endl;

cout.setf (ios::scientific, ios::floatfield);

cout << x << ‘\t’ << y << endl;

cout.unsetf (ios::scientific);

cout << x << ‘\t’ << y << endl;

cout.setf (ios::fixed, ios::floatfield);

cout << x << ‘\t’ << y << endl;

фрагментът извежда:

0.001235 1.946e+09

1.234567e-03 1.946000e+09

0.001235 1.946e+09

0.001235 1946000000.000000


вдигнат флаг uppercase води до извеждане на главно X и на главни шестнайсетични цифри, които са букви (‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’); например:

cout << setiosflags (ios::uppercase) << hex << 123456789 << endl;

cout << setiosflags (ios::showbase) << hex << 123456789 << endl;

фрагментът извежда:

75BCD15

0X75BCD15



състоянието на потока може да бъде проверено с помощта на битове от класа ios, които са записани в защитения елемент state от тип long;

битът eofbit за входен поток автоматично се включва, ако се срещне признак за край на файла; потребителят може да използва функцията елемент eof за да определи стойността на бита – тя връща стойност истина, ако eofbit е включен и лъжа в противен случай;

битът failbit се включва, ако в потока възникне грешка при форматирането, но символи не се губят; обикновено след тази грешка данните могат да се възстановят; функцията елемент fail, аналогично на eof може да се използва за да се определи дали failbit е включен;

битът badbit се включва при възникване на грешка, която води до загуба на данни; обикновено след такава грешка данните не могат да се възстановят; функцията елемент bad може да се използва за проверка на стойността на badbit;

битът goodbit се включва, ако нито един от битовете eofbit, failbit, badbit не е включен, т.е. ако не са възникнали грешки; функцията елемент good връща истина, ако goodbit е включен и лъжа в противен случай;
функцията елемент rdstate връща стойността на защитения елемент state, който съдържа стойностите на всички битове, определящи състоянието на потока; върнатата стойност евентуално може да се използва за проверка на битовете eofbit, failbit, badbit, goodbit, но

по-удобно средство е използването на функциите елементи

eof, fail, bad, good;
функцията елемент clear променя стойността на защитения елемент state от класа ios, в който са записани битовете за състоянието на потока; обикновено тя се използва за възстановяване на потока в нормално състояние, т.е. тя изключва битовете eofbit, failbit, badbit и включва бита goodbit; функцията има един параметър, който по премълчаване има стойност goodbit, така че например извикването

cin.clear () изчиства състоянието на входния поток cin и включва бита goodbit; оператора cin.clear (ios::failbit) включва бита failbit (и изключва всички останали битове) и той може да се използва ако програмистът срещне проблем при обработката на потребителски тип от данни със cin; пример:

int x;

cout << cin.rdstate () << ‘ ‘



<< cin.eof () << ‘ ‘

<< cin.fail () << ‘ ‘

<< cin.bad () << ‘ ‘

<< cin.good () << endl;

cin >> x; //въвеждаме символ

cout << cin.rdstate () << ‘ ‘

<< cin.eof () << ‘ ‘

<< cin.fail () << ‘ ‘

<< cin.bad () << ‘ ‘

<< cin.good () << endl;

cin.clear ();

cout << cin.rdstate () << ‘ ‘

<< cin.eof () << ‘ ‘

<< cin.fail () << ‘ ‘

<< cin.bad () << ‘ ‘

<< cin.good () << endl;

фрагментът връща:

0 0 0 0 1

2 0 2 0 0

0 0 0 0 1
6. Пространства от имена.
една програма включва много идентификатори, които са с различна област на действие; понякога идентификатор от една област на действие може да бъде дублиран с идентификатор със същото име от друга област на действие; дублирането на идентификатори често възниква в библиотеки от независими разработчици, в които се използват едни и същи имена за външни (глобални) идентификатори;

в такива случаи обикновено се генерира грешка по време на компилация;


в стандарта на C++ се предлага решение на този проблем с помощта на пространства от имена; всяко пространство от имена определя област на действие, в която са включени идентификатори; за да се използва елемент от пространството от имена, неговото име трябва да се обяви заедно с името на пространството и бинарната операция за разрешаване на област на действие по следния начин:

име_на_пространство::елемент_на_пространство

възможно е да се избегне посочването на името на пространството при използването на даден елемент; това се постига чрез оператор

using име_на_пространство::елемент_на_пространство,

поставен в началото на блока, в който този елемент се използва;
оператора

using namespace име_на_пространство;

поставен в началото на блок указва, че всички елементи от това пространство от имена могат да се използват в блока без да се задава името на пространството;
в стандарта на ANSI C++ дефинициите на всички класове, обекти и функции в стандартната библиотека на C++ се намират в пространството от имена std, така че в програмите, които са съобразени с този стандарт трябва да се поставя std:: пред всички стандартни класове, обекти, функции и т.н.; друг начин е използването на оператор

using namespace std;

в началото на програмата; това, обаче, се смята за лоша практика, тъй като употребата на този оператор е смислена само ако членовете на това пространство ще се използват често; препоръчва се чрез другата форма на using да се укажат само тези идентификатори, които ще се използват – например:

using std::cout; using std::endl;

в стандарта на ANSI C++ заглавните файлове нямат разширение ‘.h’;

пример (за стандарта на ANSI C++):

#include

using namespace std;

int myint = 98;

namespace Example {

const double PI = 3.14159;

const double E = 2.71828;

int myint = 8;

void printvalues ();

namespace Inner {

enum years { FISC1 = 1990; FISC2; FISC3 } ;

}

}

namespace {



double d = 88.22;

}

void main ()



{ cout << “d = “ << d << endl;

cout << “Глобален myint = “ << myint << endl;

cout << “PI = “ << Example::PI << endl;

cout << “E = “ << Example::E << endl;

cout << “Example myint = “ << Example::myint << endl;

cout << “FISC3 = “ << Example::Inner::FISC3 << endl;

Example::printvalues ();

}

void Example::printvalues ()



{ cout << “В printvalue: “ << endl;

cout << “myint = “ << myint << endl;

cout << “PI = “ << PI << endl;

cout << “E = “ << E << endl;

cout << “d = “ << d << endl;

cout << “Глобален myint = “ << ::myint << endl;

cout << “FISC3 = “ << Inner::FISC3 << endl;

}
при определяне на пространство от имена се използва следния синтаксис:

namespace име_на_пространство

{ идентификатори }

за разлика от тялото на класа и структурата, тялото на пространството не завършва с ‘;’ ; елементът myint от пространството

Example има същото име като външната променлива myint, но това не води до синтактична грешка, тъй като двете променливи са в различни пространства от имена; при описанието на функцията printvalues, елементите на пространството Example са достъпни непосредствено, тъй като самата функция е от това пространство;

пространствата от имена могат да съдържат константи, променливи, функции, класове, дори вложени пространства; дефинирането на пространства от имена трябва задължително да става в глобалната област на действие или да бъде вложено в друго пространство;

елементите на пространството без име са част от глобалното пространство от имена – те са достъпни непосредствено; външните променливи също са част от това пространство; всички елементи на глобалното пространство от имена са достъпни във всички области на действие, разположени след тяхното дефиниране; при дублиране на вътрешни идентификатори с идентификатори от глобалното пространство може да се използва унарната операция за разрешаване на област на действие; за разлика от външните променливи, елементите които са дефинирани в тялото на пространството без име не са достъпни за други първични файлове, т.е. пространството без име заменя спецификатора static за връзка между първичните файлове;


пространствата от имена могат да имат псевдоними:

namespace ново_име_на_пространство = старо_име_на_пространство


типична грешка е главната функция main да се поставя в пространство от имена или да се дефинира пространство от имена в тялото на функция, тъй като пространствата от имена засягат външни променливи и област на действие файл;
7. Преобразуване на типовете.
в стандарта на C++ има четири операции за преобразуване на типовете; за предпочитане е те да се използват вместо старото преобразуване, включено в C и C++ - въпреки, че са по-малко мощни, те са по-конкретни, което дава възможност за по-точен контрол;

друго предимство на новите операции за преобразуване е, че те имат съвършено различно предназначение за разлика от старото преобразуване, което се използва във всички случаи;


операцията static_cast служи за преобразуване в случаите, в които проверката на типа се извършва по време на компилация;

с нея се извършват стандартни преобразувания – например

void* в char*, int в double и т.н., както и обратните им преобразувания; синтаксисът на операцията е:

static_cast <нов_тип> (израз)

извършването на неразрешено преобразуване с помощта на static_cast води до синтактична грешка;

примери за неразрешено преобразуване са:



  • преобразуване на константни в неконстантни типове и обратно;

  • преобразуване на типове на указатели и псевдоними в типове, които не са в отношение на открито наследяване;

  • преобразуване към тип, за който няма съответен конструктор или операция за преобразуване;

пример:

#include

using std::cout;

using std::endl;

class BaseClass {

public:


void f () const

{ cout << “Base” << endl; }

} ;

class DerivedClass : public BaseClass {



public:

void f () const

{ cout << “Derived” << endl; }

} ;


void test (BaseClass *);

void main ()

{ double d = 8.22;

int x = static_cast < int > (d);

cout << “d = “ << d << endl

<< “x = “ << x << endl;

BaseClass *basePtr = new DerivedClass;

test (basePtr);

delete basePtr;

}
void test (BaseClass *basePtr)

{ DerivedClass *derivedPtr;

derivedPtr = static_cast (basePtr);

derivedPtr -> f ();

}
операцията const_cast премахва или добавя спецификаторите

const и volatile чрез преобразуване; спецификаторът за тип volatile се използва, когато достъпът до обектите от този тип ще се осъществява по-начин, който не може да бъде предвиден от компилатора; той се използва за подтискане на различни видове оптимизации; синтаксисът на операцията const_cast е:

const_cast <нов_тип> (израз)

ще отбележим, че операцията const_cast не може директно да премахне спецификаторите const и volatile от тип на променлива – тя прави това само в рамките на конкретния израз; пример:


#include

using std::cout;

using std::endl;

class ConstCastTest {

public:

void setNumber (int);



int getNumber () const;

void printNumber () const;

private:

int number;

} ;

void ConstCastTest::setNumber (int num)



{ number = num; }

int ConstCastTest::getNumber () const

{ return number; }

void ConstCastTest::printNumber () const

{ cout << “Числото след модификация: “;

const_cast (this) -> number--;

cout << number << endl;

}

void main ()



{ ConstCastTest x;

x.setNumber (8);

cout << x.getNumber() << endl;

x.printNumber();

}
операцията reinterpret_cast се използва за преобразуване на един тип указател към друг тип указател; тя също се използва за преобразуване на указател към цяло число и обратно; reinterpret_cast се използва и за извършване на опасни обработки, които могат да доведат до сериозни грешки по време на изпълнение, използването и може да доведе до това, че една и съща програма да се изпълнява различно на различни платформи; синтаксисът на операцията е:

reinterpret_cast <нов_тип> (израз) пример:

#include

using std::cout;

using std::endl;

void main ()

{ int x = 120, *ptr = &x;

cout << *reinterpret_cast (ptr) << endl;

}
8. Шаблони на функции.
възможно е дефинирането на няколко функции с еднакви имена, но различен брой и типове на параметрите; тези функции се наричат предефинирани; при извикването на предефинирана функция, компилаторът избира съответния вариант като анализира броя и типа на аргументите при извикването; предефинираните функции могат да имат различни или еднакви типове на връщаните резултати, но задължително трябва да имат различни списъци от параметри; предефинираните функции обикновено се използват за извършване на близки по обработка дейности над различни типове данни;

ако за всеки тип данни трябва да се извършват идентични обработки, то по-компактно и удобно решение е използването на шаблони на функции; това е една от новите възможности в C++;

програмистът трябва да напише само едно описание на шаблона на функцията, а компилаторът, изхождайки от типа на аргументите, използвани при извикването на функцията, автоматично ще генерира обектният код на функцията за тези типове;

например може да се напише един шаблон на функция за сортиране на масив на основата на който C++, в зависимост от типа на елементите на масива, за който е извикана функцията, автоматично ще генерира отделен вариант, сортиращ масив от конкретния тип;


шаблоните на функции, подобно на макросите, подпомагат повторното използване на програмно осигуряване, но за разлика от макросите, шаблоните на функции помагат да се отстранят много типове грешки в резултат на пълната проверка на типовете;

всяко описание на шаблон на функция започва с ключовата дума

template, след която следва списък от формалните параметри на шаблона, поставен в < >; всеки параметър трябва да се предшества от ключовата дума class или typename, използването им е еквивалентно; например:

template

template

template

формалните параметри в описанието на шаблона означават всеки вграден тип или тип, дефиниран от потребителя; те могат да се използват при указване на типовете на параметрите на функцията, типа на връщания резултат или типа на променливите, дефинирани в тялото на функцията; поради тази причина формалните параметри на шаблона още се наричат параметри на типа;

след заглавието следва обикновено описание на самата функция; пример:


#include

using std::cout;

using std::endl;

template

void printArray (const T* array, const int count)

{ for (int i = 0; i < count; i++)

cout << array[i] << ‘ ‘;

cout << endl;

}

void main ()



{ const int aCount = 5, bCount = 7, cCount = 6;

int a[aCount] = { 1, 2, 3, 4, 5 } ;

double b[bCount] = { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7 } ;

char c[cCount] = “HELLO”;

cout << “Масив a: “ << endl;

printArray (a, aCount);

cout << “Масив b: “ << endl;

printArray (b, bCount);

cout << “Масив c: “ << endl;

printArray (c, cCount);

}
в шаблона на функцията printArray е дефиниран формален параметър T за типа на масива, който ще се извежда; когато компилаторът открие в програмата извикване на функцията printArray, той заменя T в цялата област на дефиниране на шаблона

с типа на първия фактически параметър от обръщението и по този начин C++ създава шаблонна функция за извеждане на масив от указания тип; в разгледания пример шаблоните позволяват на програмиста да избегне необходимостта от написване на три отделни предефинирани функции със следните прототипи:

void printArray (const int *, const int)

void printArray (const double *, const int)

void printArray (const char *, const int)
при използване на шаблони трябва да се отчита, че програмистът може да създаде твърде много копия на шаблонни функции и шаблонни класове; за тези копия могат да са нужни значителни ресурси от памет;

шаблонните функции и предефинираните функции са тясно свързани; всички шаблонни функции имат едно и също име и затова компилаторът използва механизма на предефинирането за да осигури извикването на съответната функция;

самите шаблони могат да бъдат предефинирани – могат да се дефинират други шаблони, които имат същото име на функция, но различен набор от параметри; например шаблона на функцията printArray може да бъде предефиниран с друг шаблон, който има допълнителни параметри lowsubscript и highsubscript за определяне коя част на масива да бъде изведена;

шаблон на функция може също да бъде предефиниран, ако се въведе друга нешаблонна функция със същото име; например шаблона на функцията printArray може да бъде предефиниран с нешаблонна функция, която извежда символен масив в подходящо табулиран вид по стълбове;

ако шаблон се извиква с потребителски тип като параметър и този шаблон използва операции с обекти от този тип, то такива операции задължително трябва да бъдат предефинирани – в противен случай свързващият редактор ще изведе съобщение за грешка, тъй като компилаторът ще генерира извикване на съответната предефинираща функция на операция без да обърне внимание, че тя не съществува;

компилаторът определя коя функция съответства на дадено извикване по следния начин:

отначало компилаторът се опитва да намери функция, която точно съответства по име и тип на фактическите параметри в извикването; ако при този опит компилаторът не успее, той търси шаблон на функция с указаното име в извикването, с помощта на който той може да генерира шаблонна функция с точно съответствие на типа на фактическите параметри; важно е да се отбележи, че при търсене на шаблон не се извършва автоматично преобразуване на типовете;

ако и този опит е неуспешен, компилаторът търси предефинирана функция; ако съответната функция не е намерена или в една от трите стъпки са намерени няколко такива функции, компилаторът дава съобщение за грешка;


9. Информация за типа по време на изпълнение. Булев тип.
информацията за типа по време на изпълнение (RTTI) предоставя средства да се установи типа на даден обект по време на изпълнение дори когато е известен само указател или псевдоним на този обект;

RTTI е предназначена за използване в йерархии на полиморфно наследяване; за използване на RTTI някои компилатори изискват включване на RTTI възможностите в настройките; ще разгледаме две операции, които спадат към средствата на RTTI – dynamic_cast и typeid;


операцията typeid приема един аргумент и връща псевдоним на константен обект от клас type_info, който представлява типа на аргумента; самият аргумент може да бъде име на тип и тогава typeid ще осигури информация за този тип; класът type_info има функция елемент name, която връща името на типа като константен низ от символи, както и предефинирани операции ==, != с които може да се сравняват типовете на изрази; използването на typeid в switch-подобни конструкции за определяне на типа на обект в някаква йерархия от класове е недопустимо използване на RTTI – вместо това трябва да се използват виртуални функции; използването на typeid изисква включването на заглавния файл typeinfo; пример:

#include

using std::cout;

using std::endl;

#include

template

T Max (T val1, T val2, T val3)

{ T max = val1;

if (max < val2) max = val2;

if (max < val3) max = val3;

const char *datatype = typeid (T).name();

cout << datatype << “-типовете се сравняват:\nMax = “;

return max;

}

void main ()



{ int a = 8, b = 88, c = 22;

double d = 95.96, e = 78.59, f = 83.89;

cout << Max (a, b, c) << endl;

cout << Max (d, e, f) << endl;

}
операцията dynamic_cast е четвъртата операция за преобразуване на типовете в стандарта на C++; тя се използва само с указатели и псевдоними и осигурява извършване на правилни преобразувания по време на изпълнение; за разлика от static_cast, dynamic_cast проверява дали преобразуването е допустимо и връща нулев указател, ако това не е така; операцията dynamic_cast най-често се използва за понижаващо преобразуване на указател от базов клас към указател от производен клас; пример:
#include

using std::cout;

using std::endl;

const double PI = 3.14159;

class Shape {

public :


virtual double area () const

{ return 0; }

} ;

class Circle : public Shape {



public:

Circle (double r = 1)

{ radius = r; }

virtual double area () const

{ return PI*radius*radius; }

protected:

double radius;

} ;


class Cylinder : public Circle {

public:


Cylinder (double h = 1)

{ height = h; }

virtual double area () const

{ return 2*PI*radius*height + 2*Circle::area(); }

private:

double height;

} ;

void outputShapeArea (const Shape *);



void main ()

{ Circle circle;

Cylinder cylinder;

Shape *ptr = 0;

outputShapeArea (&circle);

outputShapeArea (&cylinder);

outputShapeArea (ptr);

}

void outputShapeArea (const Shape *shapePtr)



{ const Circle* circlePtr;

const Cylinder* cylinderPtr;

cylinderPtr = dynamic_cast (shapePtr);

if (cylinderPtr != 0)

cout << “Пълна повърхнина на цилиндъра: “

<< shapePtr -> area ();

else {


circlePtr = dynamic_cast (shapePtr);

if (circlePtr != 0)

cout << “Лице на кръга: “

<< shapePtr -> area ();

else cout << “Нито кръг, нито цилиндър!”;

}

cout << endl;



}
при опит за използване на dynamic_cast за преобразуване на указател от тип void * се получава съобщение за синтактична грешка;
булевият тип bool е допълнение към стандарта на C++; за променливите от тип bool се разпределя един байт и те могат да приемат две стойности – вградените булеви константи true или false;

например:

bool bool1 = true, bool2 = false;

при извеждане на променливи от тип bool true се извежда като 1, а

false като 0; например:

cout << bool1 << ‘\n’ << bool2 << endl;

променливи от тип int, double, указатели и др. могат да бъдат неявно преобразувани към тип bool – нулевите стойности се преобразуват към false, ненулевите към true;
10. Шаблони на класове.
потребителят може да използва понятието стек независимо от типа на данните, намиращи се в него; този тип трябва да бъде зададен едва тогава, когато стекът реално се създава; съществуват възможности за създаване на универсално програмно осигуряване, което може да се използва повторно – достатъчно е да се създаде общо описание на понятието стек и на основата на този родов клас да се създадат класове, които са специфични версии за конкретния тип данни; тази възможност се дава в C++ от шаблоните на класове;

шаблоните на класове често се наричат параметризирани типове, тъй като те имат един или повече параметри на тип, които определят настройката на родовия шаблон на класа за специфичен тип данни при създаване на обект; за да се използват шаблонни класове е достатъчно веднъж да се опише шаблона на класа; всеки път, когато се иска реализация на шаблонния клас за нов тип данни, това се съобщава на компилатора и той създава първичния код за конкретния тип данни – например шаблона на класа Stack може да служи като основа за създаване на многочислени класове Stack от необходимите за програмата типове – int, double, char и т.н.; описанието на шаблон на клас изглежда като традиционно описание на клас с тази разлика, че то се предшества от заглавието

template <списък_от_параметри_на_типовете>; тук списъкът е от идентификатори, предшествани от ключовата дума class; тези идентификатори означават параметрите на типовете и те се използват в дефиницията на класа и във функциите елементи;

в примера по-долу заглавието е template и T ще определя типа на данните елементи в стека;


#include

using std::cin;

using std::cout;

using std::endl;

template

class Stack {

public:

Stack (int = 10);



~Stack () { delete [] stackPtr; }

bool push (const T&);

bool pop (T&);

private:


int size;

int top;


T *stackPtr;

bool isEmpty () const { return top == -1; }

bool isFull () const { return top == size-1; }

} ;


template

Stack ::Stack (int s)

{ size = (s > 0) ? s : 10;

top = -1;

stackPtr = new T [size];

}

template



bool Stack ::push (const T&pushval)

{ if (!isFull())

{ stackPtr[++top] = pushval;

return true;

}

return false;



}

template

bool Stack ::pop (T &popval)

{ if (!isEmpty())

{ popval = stackPtr[top--];

return true;

}

return false;



}

void main ()

{ Stack doubleStack (5);

double f = 1.1;

cout << “Добавяне на елементи в doubleStack:\n”;

while (doubleStack.push (f))

{ cout << f << ‘ ‘;

f += 1.1;

}

cout << “\nСтекът е пълен. “ << f << “ не е добавен.\n”;



cout << “Премахване на елементи от doubleStack:\n”;

while (doubleStack.pop (f))

cout << f << ‘ ‘;

cout << “\nСтекът е празен.” << endl;

Stack intStack;

int i = 1;

cout << “Добавяне на елементи в intStack:\n”;

while (intStack.push (i))

{ cout << i << ‘ ‘;

i++;


}

cout << “\nСтекът е пълен. “ << i << “ не е добавен.\n”;

cout << “Премахване на елементи от intStack:\n”;

while (intStack.pop (i))

cout << i << ‘ ‘;

cout << “\nСтекът е празен.” << endl;

}
всяко описание на функция елемент, което е извън шаблона на класа се предшества от заглавието на този шаблон; описанието е стандартно с тази разлика, че при използване на името на класа винаги трябва да се задава списъка от параметри на типовете, заграден в < >; за разлика от шаблоните на функции, при създаване на обект на шаблонен клас изрично трябва да се посочат конкретните типове за които ще се създава този обект; в примера

по-горе, когато компилаторът срещне Stack doubleStack (5);

той ще генерира екземпляр на шаблона Stack, в който навсякъде параметъра на типа T ще бъде заменен с double;

обработките за doubleStack и intStack са почти идентични, което дава възможност за използване на шаблон на функция:


template

void testStack (Stack &s, T value, T increment, const char *sname)

{ cout << “Добавяне на елементи в “ << sname << “:\n”;

while (s.push (value))

{ cout << value << ‘ ‘;

value += increment;

}

cout << “\nСтекът е пълен. “ << value << “ не е добавен.\n”;



cout << “Премахване на елементи от “ << sname << ”:\n”;

while (s.pop (value))

cout << value << ‘ ‘;

cout << “\nСтекът е празен.” << endl;

}
главната функция би изглеждала така:

void main ()

{ Stack doubleStack (5);

Stack intStack;

testStack (doubleStack, 1.1, 1.1, “doubleStack”);

testStack (intStack, 1, 1, “intStack”);

}
11. Шаблони и нетипови параметри. Наследяване, приятелство и статични елементи.
в шаблоните на функции и класове има възможност да се използват обикновени или т.н. нетипови параметри; например в разгледания шаблон на класа Stack може да се добави нетипов параметър от тип int, който указва максималният брой на елементите в стека:

template

при дефинирането Stack s1;

компилаторът ще генерира шаблонен клас от тип Stack ;

фактическите стойности на нетиповите параметри трябва да са константни изрази, тъй като те се обработват по време на компилация;

в закритата част на новия вариант на шаблона на класа Stack вместо stackPtr може да се дефинира масив по следния начин:

T stackHolder [maxelements];

и в този масив да се съхраняват данните на стека; това ще острани загубата на време за динамично разпределяне на памет и ще изключи възможността за възникване на потенциално непоправима грешка по време на изпълнение, ако операцията new не успее да получи необходимия обем памет;


ако за специфичен тип данни е нужен клас, който не съответства на общия шаблон на класа, то той явно може да бъде определен, като по този начин отменя действието на шаблона за този тип;

например, ако е даден шаблон на клас:

template

class MyClass { } ;

и за конкретен тип, например char *, трябва да се извършват действия, които не съответстват на този шаблон, програмистът трябва да създаде нов клас с име MyClass
шаблоните и наследяването са свързани по следния начин:


  • шаблон на клас може да бъде производен от друг шаблон на клас; в този случай, на всеки възможен базов клас съответства фамилия от производни класове;

  • шаблон на клас може да бъде производен от нешаблонен клас;

  • нешаблонен клас може да бъде производен от шаблон на клас; в този случай на всеки възможен базов клас съответства точно един производен клас;

както знаем, функции и цели класове могат да бъдат обявени като приятели на нешаблонни класове; за шаблоните на класове също могат да бъдат установени отношения на приятелство;

приятелство може да бъде установено между шаблон на клас и глобална функция, между шаблон на клас и функция елемент на друг клас (възможно шаблонен) или между шаблон на клас и цял клас (възможно шаблонен); примери:

нека е дефиниран шаблон на клас X:

template

class X { } ;


ако в дефиницията на шаблона се обяви приятелска функция по следния начин: friend void f1 () ; то функцията f1 ще бъде приятелска за всеки шаблонен клас, получен от шаблона X;
нека в дефиницията на шаблона се обяви приятелска функция по следния начин: friend void f2 (X&); тогава за конкретен тип T, например float, приятелска за класа X ще бъде само функцията

f2 (X&); непосредствено преди описанието на тази функция в програмата трябва да се постави заглавието template ;


ако в дефиницията на шаблона X е обявена приятелска функция във вида: friend void A::f3 (); то функцията елемент f3 от класа A ще бъде приятелска за всеки шаблонен клас, получен от шаблона X;
нека в дефиницията на шаблона X е обявена приятелска функция във вида: friend void C::f4 (X&); тогава за конкретен тип T, например float, приятелска за класа X ще бъде само функцията елемент f4 (X&) на класа C;
ако в дефиницията на шаблона X е обявен приятелски клас по следния начин: friend class Y; то всички функции елементи на класа Y ще бъдат приятелски за всеки шаблонен клас, получен от шаблона X;
ако в дефиницията на шаблона X е обявен приятелски клас по следния начин: friend class Z; то за конкретен тип T, например float, всички функции елементи на класа Z ще бъдат приятелски само за класа X;
както знаем, в нешаблонен клас едно копие на статична данна елемент се използва от всички обекти на класа и статичната данна елемент трябва да се инициализира в областта на действие файл;

всеки шаблонен клас, получен от съответния шаблон, има собствено копие на всички статични данни елементи от шаблона – всички обекти от този клас използват своите статични данни елементи;

както при нешаблонни класове, статичните данни елементи на шаблонните класове трябва да бъдат инициализирани в областта на действие файл; всеки шаблонен клас получава собствено копие на всяка статична функция елемент на шаблона;
12. Списък. Стек. Опашка. Дек.
под списък ще разбираме крайно наредено множество от n еднотипни елементи x1, x2, …, xn; ако n = 0, списъкът е празен;

ако n > 0, x1 е първи елемент на списъка, xn е последен елемент на списъка; ако k > 1, казваме че xk-1 предшества xk; ако k < n, казваме че xk+1 следва xk;

всеки елемент на списъка се състои от краен брой полета, като данните в тях може да са от различен тип – числови, текстови, за връзка с други елементи и др.; със списъци могат да се изпълняват разнообразни операции:


  • търсене на елемент с номер k и осигуряване на достъп до съдържанието му;

  • включване на нов елемент в списъка, непосредствено преди или след елемента с номер k;

  • изключване на елемент с номер k от списъка;

  • обединяване на два или повече списъка в един списък;

  • разбиване на списък на няколко отделни списъка;

  • създаване на копие на списък;

  • определяне на броя на елементите на списък;

  • търсене на елемент с определено съдържание на дадено поле (търсене по ключ);

  • сортиране на елементите на списък във възходящ или низходящ ред по отношение на някакво поле (сортиране по ключ);

съществуват различни начини за представяне на списъци в зависимост от вида на операциите, които трябва да се извършват над елементите им; не съществува начин за представяне на списъци, при който всички операции над елементите на списъка и над самия списък като цяло се изпълняват еднакво ефективно;

най-често се използват списъци при които включването и изключването на елементи се извършва преди първия или след последния елемент на списъка;




Сподели с приятели:
  1   2   3   4




©obuch.info 2024
отнасят до администрацията

    Начална страница