Top Banner
Библиографиja 1. Dale, N., Weems, C., Headington, M “Programming and Problem Solving with C++”, Second Edition, Jones and Bartlett Publishers, 2000. 2. Horstmann “C++ Essentials”. 3. Kernighan, Ritchie “The C Programming Language”.
162

C++ на македонски

Dec 01, 2014

Download

Documents

Devil639

Универзитет “Гоце Делчев“ - Штип
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: C++ на македонски

Библиографиja

1. Dale, N., Weems, C., Headington, M “Programming and Problem Solving with C++”, Second Edition, Jones and Bartlett Publishers, 2000.

2. Horstmann “C++ Essentials”.

3. Kernighan, Ritchie “The C Programming Language”.

Page 2: C++ на македонски

(лекториран текст)

Универзитет “Гоце Делчев“ - Штип

Факултет за информатика

Д-р Владо Гичев

Основи на програмирање

ноември 2007

Штип

I. Програмирање и решавање на проблеми

Основни фази во компјутерското програмирање

Page 3: C++ на македонски

1

Во денешно време компјутерите се составен дел во сите области на нашето живеење. Името

компјутер доаѓа од англискиот збор computer и може да се дефинира како: компјутер е електронски

уред кој може да се програмира и кој може да процесира, чува и враќа податоци.

Целта на овој текст е накратко да ги покрие основните концепти на компјутерското

програмирање. Се поставува прашањето што се подразбира под поимот програмирање. Човековото

деjcтвување и неговите мисли се поврзани со логички низи од активности кои се вршат свесно или

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

активности:

1. ја подигаме дланката;

2. ја поместуваме раката кон десната страна од книгата;

3. го фаќаме десниот горен агол од листот;

4. го свртуваме листот, така што можеме да читаме на другата страница;

5. ја тргаме раката од книгата.

Или друг пример. Масовното производство се извршува преку операции кои се случуваат по точно

определен редослед.

Од горепосоченото може да дефинираме:

- програмирање е процес на планирање и извршување на одредени операции и настани;

- компјутерско програмирање е процес на планирање на низа чекори кои компјутерот треба да ги

следи;

- компјутерска програма е низа од инструкции кои компјутерот треба да ги изврши.

За да напишеме компјутерска програма, ние мора да поминеме низ три фази кои содржат свои

подфази.

1. Фаза на решавање на проблемот:

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

решението;

- генерално решение на проблемот (алгоритам), што значи развивање на логичка низа од

чекори кои го решаваат проблемот;

- верификација која подразбира следење на чекорите од алгоритамот за да се потврди дали

решението навистина го решава проблемот.

2. Фаза на имплементација:

- конкретно решение (компјутерска програма), што значи преведување на алгоритамот во

програмски јазик-кодирање;

- тестирање кое се состои од оставање на компјутерот да ги врши инструкциите и да даде

резултат. Овој резултат се проверува рачно и ако има грешки повторно се анализираaт

програмата и алгоритамот да се утврди од каде доаѓаат грешките и се прават корекции.

Откако програмата е напишана, влегува во третата фаза: одржување.

3. Фаза на одржување:

- користење на програмата;

Page 4: C++ на македонски

2

- одржување, што подразбира модификација на програмата, заради потребa од промени

или корекции на грешки кои се појавуваат при неговото користење.

Горенаведените три фази, заедно го сочинуваат животниот циклус (животниот век) на

програмата.

Во првата фаза, програмерот го анализира проблемот и развива генерално решение (алгоритам).

Алгоритам претставува чекор-по-чекор (step-by-step) процедура за решавање на проблемот во конечен

број чекори. Програмскиот јазик претставува множество од правила, симболи и клучни зборови кои се

употребуваат да се конструира компјутерска програма.

Освен горенаведените три фази, документацијата е важен дел од програмирањето.

Документацијата вклучува: пишани објаснувања на проблемот што се решава и организација на

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

употребува програмата. Во текот на животниот циклус на програмата, на неа може да работат повеќе

луѓе и цел на документацијата е овие програмери да ја разберат вашата програма.

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

луѓето што ја употребуваат и модификуваат.

Информации, податоци и програмски јазици

Откако програмата е напишана, на компјутерот тpeбa да му се дадат информации, односно

податоци кои се неопходни за да се реши проблемот. Информација е кое било сознание кое

може да се пренесе. На пример, тврдењето „квадратот е четириаголник чии страни се еднакви и заемно

нормални“ е информација.

Податок (data) е информација во форма која компјутерот може да ја употребува. На пример,

според горенаведеното тврдење и според должината на страната на квадратот како податок, компјутерот

може да ја определи површината и периметарот на квадратот.

Порано како податоци во компјутерите најчесто се употребувале броеви и букви. Освен овие

типови на податоци, модерните компјутери процесираат и звук, слики и сл.

Секој вид на податок може да биде претставен со комбинација од цифрите 0 и 1. Ова е бинарна

форма на претставување на податоците. Значи, за разлика од децималниот броен систем (база-10), кој ги

вклучува цифрите од 0 до 9 за претставување на броевите, бинарниот броен систем (база-2) ги вклучува

цифрите 0 и 1. Зборот bit (binary digit) се однесува на единична цифра 0 или 1 и претставува мерка за

информација. На пример, од 10 бита можат да бидат претставени 102 (1024) различни шеми од 0 и 1.

Byte е група од 8 бита со која можат да се претстават 28 (256) шеми од 0 и 1. Во компјутерот, секој

карактер (на пр. А, ?, с) обично се претставува со byte. Групите од 16, 32 и 64 бита се нарекуваат

зборови.

Бинарниот броен систем, сè уште се употребува за претставување на податоците во компјутерот.

Шемите на битови кои претставуваат податоци варираат од компјутер до компјутер. За среќа, во

денешно време податоците се внесуваат во облик на броеви, букви и симболи, кои компјутерот

автоматски ги конвертира во бинарна форма. Сите податоци во компјутерот, било да се работи за

инструкции или чисти податоци, се сместени во меморијата и се претставени во бинарна форма.

Page 5: C++ на македонски

3

Чистите податоци (броеви, букви и сл.) и инструкциите се разликуваат само по начинот на кој

компјутерот ги употребува. Значи, можно е компјутерот да ги процесира своите инструкции како

податоци.

Единствен програмски јазик кај првобитните компјутери бил т.н. машински јазик. Машинскиот

јазик е јазик кој е составен од бинарно кодирани инструкции кои се употребуваат директно од

компјутерот. Бидејќи различни компјутери употребуваат различни бинарни кодови за секоја

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

тие решаваат ист проблем. За олеснување на работата на програмерите, подоцна бил развиен assembly

јазикот. За разлика од машинскиот јазик, инструкциите во assembly јазикот не можат да бидат извршени

директно од компјутерот.

Едно од фундаменталните откритија во компјутерската наука е дека компјутерот може да ги

процесира сопствените инструкции во форма на податоци. Ова својство се употребува во асемблерот

(assemblеr) и компајлерот (compiler).

Асемблер (Assembler) е програма која го преведува асембли (assembly) јазикот во машински

код.

Иако напредок во однос на машинскиот јазик, асембли (assembly) јазикот е повторно врзан за

инструкциите на конкретниот компјутер. Конечно, компјутерските научници развија виши програмски

јазици кои се полесни за употреба, бидејќи во нив инструкциите се блиски до англискиот јазик.

Примери за виши програмски јазици се C++, FORTRAN, COBOL, Ada и др. За разлика од ниските

програмски јазици (машински и assembly) во кои кодирањето зависи од конкретната машина, вишите

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

извршува на различни машини. Ова е постигнато со стандардизација на вишите програмски јазици.

Компајлер (Compiler) е програма која го преведува вишиот програмски јазик во машински код.

Програмата напишана на виш програмски јазик се нарекува изворна програма. Компајлерот ги

третира инструкциите во програмата (инструкциите за влез на податоци, инструкциите за пресметување,

инструкциите за излез и др.) како влезни податоци и ја преведува изворната програма во машински

јазик, наречена објектна програма (сл.1). Значи, објектната програма е верзија на изворна програма во

машински јазик.

Page 6: C++ на македонски

4

I z v o r n ap r o g r a m a

O b j e k t n ap r o g r a m a

сл. 1

Составни делови на компјутерот

Најголем број од компјутерите имаат шест основни компоненти: мемориска единица,

аритметичко-логичка единица, контролна единица, влезни единици, излезни единици, периферни

единици за чување на податоци.

Мемориската единица е внатрешна единица за чување и манипулирање на податоци. Таа се

состои од мемориски ќелии (мемориски локации) кои имаат свои интерни адреси.

Делот од компјутерот што ги извршува инструкциите е наречен централна процесорска

единица (CPU) и таа има две компоненти: аритметичко-логичка единица и контролна единица.

Аритметичко-логичката единица е компонента на централната процесорска единица која ги

извршува аритметичките и логичките операции.

Контролната единица е компонента на централната процесорска единица која ги контролира

акциите на другите компоненти, така што инструкциите (програмата) се извршуваат во точен редослед.

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

процесираат (влез) и ги прикажуваат резултатите од тоа процесирање (излез).

Компјутерите може да имаат повеќе уреди, односно единици прикачени на нив. Сите од овие

уреди се познати под едно име – периферија. Такви уреди се: скенери, ЦД-ром, ДВД-ром, модеми,

дигитални камери, микрофони, слушалки итн.

Периферна единица е влезна, излезна или помошна единица за чување податоци која е

прикачена на компјутерот.

Помошна единица за чување податоци (Auxiliary storage device) е единица (уред) која ги чува

податоците надвор од главната меморија на компјутерот.

Физичките компоненти на компјутерот со еден збор се нарекуваат хардвер (hardware).

Множеството од програми со кои располага компјутерот се нарекува софтвер (software).

Оперативен систем е множество од програми кои ги управуваат сите компјутерски ресурси.

Page 7: C++ на македонски

6

(текстот е лекториран)

II. Синтакса и семантика на C++ и процес на програмски развој

Елементи на C++ програмите

Во претходната поглавје кажавме дека потпрограмите ни овозможуваат одвоено да пишуваме делови од

програмa. Потпрограмите во C++ јазикот се нарекуваат функции и C++ програмата е збир од една или повеќе

функции. На пример, ако сакаме програмата да ни пресметува квадрат и куб на одреден природен број, програмата

може да се состои од три функции (сл.2.1). Една главна функција (main) и две додатни функции:

- Square, која ќе пресметува квадрат на дадениот број и

- Cube, која ќе пресметува трет степен (куб) на дадениот број.

сл. 2.1

Програмскиот код во C++ за пресметување на квадрат и куб од бројот 27 е следниов (сл. 2.2):

Page 8: C++ на македонски

7

сл. 2.2

Во секоја од трите функции, инструкциите меѓу левата и десната заграда {и} го означуваат телото на

функцијата.

Извршувањето на програмата секогаш започнува со првата инструкција во main функцијата. Функциите

Square и Cube се примери на функции кои враќаат вредност (value-returning functions). Карактеристика на

функција која враќа вредност е таа да враќа само една вредност и тоа на местото од каде што е повикана. Зборот int

на почетокот на првата линија од функцијата Square - int Square (int n) покажува дека функцијата враќа целобројна

вредност на местото од каде што се повикува, т.е. Square(27) во функцијата main. Главната функција, main, е исто

така функција која враќа вредност и тоа 0, ако извршувањето поминало во ред, или друга вредност ако нешто при

извршувањето било погрешно. Оваа вредност main ја враќа на оперативниот систем.

Синтакса и семантика

Програмскиот јазик е множество од правила, симболи и специјални зборови кои се употребуваат при

креирањето на програмата. Постојат правила за синтаксата (граматика) и семантиката (логика).

• Синтакса - формални правила кои укажуваат како валидни инструкции се пишуваат во одреден

програмски јазик.

• Семантика - множество од правила кои го определуваат значењето (логиката) на инструкциите напишани

во програмскиот јазик.

Идентификатор (име на нешто во програмата) во C++ мора да биде барем буква или долна црта ( _ ) кои

можат, но не мораат, да бидат следени од низа букви, долни црти и цифри.

Следните се валидни програмерски дефинирани идентификатори во C++: suma_na_kvadrati, count, id_broj,

PI, totalScore.

Следните се невалидни програмерски дефинирани идентификатори во C++ :

40_casa името почнува со нумерички карактер,

Get Data се јавува недозволен, blank, карактер во името,

20-dena се јавува недозволен, -, карактер во името,

cena_vo_$ се јавува недозволен, $, карактер во името,

int резервиран збор во C++.

Последниот пример е резервиран збор во C++ и програмерот не може да го употребува како име на

идентификатор.

Податоци и типови на податоци

Компјутерската програма оперира со податоци кои можат да се чуваат интерно во меморијата, надворешно

на диск или лента, или да се внесуваат преку уред (како тастатура, скенер и др.). Во C++ секој податок мора да биде

Page 9: C++ на македонски

8

од специфичен тип. Типот на податокот определува како податокот е претставен во компјутерот и видовите на

процесирање кои компјутерот може да ги врши врз него.

Тип на податок

Конкретно множество од податочни вредности, заедно со множество од операции на тие вредности.

Типовите на податоци кои се употребуваат често се дефинирани внатрешно во C++. Примери на овие стандардни

(или built-in) типови се int (за целобројни броеви), float (за децимални броеви кои вклучуваат децимална точка) и

char (за карактери). Освен овие стандардни типови, C++ им дозволува на програмерите да дефинираат нивни

сопствени типови на податоци—programmer-defined (или user-defined) типови.

Тип на податок char претставува стандарден тип на податок и опишува податоци кои се состојат од еден

алфанумерички карактер-буква, цифра или специјален симбол:

‘A’ ‘a’ ‘3’ ‘$’ ‘+’ ‘-‘ ‘%’ ‘ ‘ и др.

Секој карактер во C++ е ограничен со апострофи. На пример, карактерот ‘3’ и интегер бројот 3 се чуваат на

различен начин во меморијата на компјутерот.

Вообичаена операција врз карактери е споредување на два карактера. Така ‘A’ е помало од ‘B’ , ‘3’ е

помало од ‘8’ итн. Собирањето на два карактера е можна но невообичаена операција.

Тип на податок string (стринг) претставува низа од карактери затворени во наводници. i На пример, следниве се стрингови во C++:

"Problem Solving" "C++" "Programming and " " . "

Стрингот мора да биде испишан комплетно во една линија. На пример, стрингот: „овој стринг е невалиден,

бидејќи е испишан на повеќе од една линија” е невалиден, бидејќи почнува на една линија, а завршува на друга и

компајлерот јавува грешка при компилација. Наводниците не се дел од стрингот, но служат само да го разграничат

стрингот од другите делови на C++ програмата.

Типот на податок string не е дел од C++ јазикот (не е built-in тип). Тој е програмски дефиниран тип на

податок кој е предефиниран во C++ стандардна библиотека, која претставува голема колекција на претходно

напишани функции и типови на податоци која секој C++ програмер може да ја користи.

Вообичаени операции на стрингови се:

- определување на должина на стринг;

- пребарување на стринг за конкретен карактер;

- поврзување на два или повеќе стринга во еден.

Именување на елемeнти: декларации Идентификаторите можат да се употребуваат за именување на константи и променливи. Вредностите на

константите не можат да се менуваат во текот на извршувањето на програмата, а вредностите на променливите

можат.

Декларација е инструкција во програмата која го поврзува името (идентификаторот) со описот на елементот

во C++ програмата. Во декларацијата ние го именуваме идентификаторот и опишуваме што тој претставува. На

Page 10: C++ на македонски

9

пример, декларацијата int count означува дека count е променлива од тип integer. Кога се декларира

променливата, компајлерот обезбедува нејзина локација во меморијата.

Значи, декларација е инструкција што го поврзува идентификаторот со константа, променлива,

програмерски дефиниран тип на податок или функција, така што програмерот се повикува на тој елемент преку

неговото име. Декларациите за константи, променливи, програмерски дефинирани типови на податоци и функции

се различни во C++ јазикот.

Променлива се дефинира како локација во меморијата, која се повикува преку идентификатор и која

содржи вредност која може да се менува.

Следната инструкција декларира sredenInicijal да биде променлива од тип char:

char sredenInicijal;

Во еден ред може да се декларираат повеќе променливи од ист тип. На пример:

int count, ocena, zbir;

што има ист ефект како и следниве три декларации:

int count;

int ocena;

int zbir;

Константи се единечните карактери (затворени во апострофи) и стрингови (затворени во наводници).

Примери за константи се:

‘A’ ‘8’ ‘$’ “3.14159” “Programming and Problem Solving with C++”.

Во C++ како и во математиката, константата е вредност која не се менува. Кога користиме актуелна

вредност на константата во програмата, велиме дека користиме литерална вредност или литерал.

Литерална вредност е вредност на константата запишана во програмата.

Освен како литерал, константата во програмата може да се јави и како именувана (симболична) константа.

Во овој случај се декларира име на константа и во декларацијата се доделува вредноста. Следното се декларации на

именувани константи:

const string STARS = "********"; const char BLANK = ' '; const string BOOK_TITLE = "Programming and Problem Solving with C++"; const string MESSAGE = "Error condition";

Добра програмерска практика е преку коментар да се опише што константата значи.

На пример:

const float MAX_HOURS =40.0; // Maksimalen broj na normalni casovi vo nedelata

const float OVERTIME =1.5; // Faktor za prekuvremena rabota

Извршни инструкции

Page 11: C++ на македонски

10

Доделување

Вредноста на променливата може да се постави или промени преку инструкцијата за доделување. На

пример, инструкцијата lastName = "Lincoln" доделува стринг "Lincoln" на променливата lastName.

Значи, инструкцијата за доделување е инструкција која ја доделува вредноста на изразот од десната страна,

во променливата од левата страна на операторот “=“.

Variable = Expression ;

Дадени декларации:

string firstName;

string middleName;

string lastName;

string title;

char middleInitial;

char letter;

Следниве инструкции на доделување се валидни:

firstName = "Abraham" ;

middleName = firstName;

middleName = "" ;

lastName = "Lincoln";

title = "President";

middleInitial = ' ';

letter = middleInitial;

а следниве не се валидни инструкции на доделување:

middleInitial = “A.” - стринг не може да се додели во променлива од тип карактер

letter = firstName; Стринг не може да се додели во променлива од тип карактер

firstName = Thomas; Thomas е недеклариран идентификатор

“Edison” = lastName; Само променлива може да се појави од левата страна на =.

lastName = ; Изразот од десната страна на = е испуштен.

Стрингов израз

Иако не може да се изврши аритметика на стрингови, типот на податок string (стринг) овозможува

специјална операција наречена конкатенација, која како оператор користи +. Резултат од конкатенацијата

(поврзувањето) е нов string (стриинг) кој се состои од првиот string (стринг) на кој му е прилепен вториот string

(стринг).

На пример, по дадените инструкции:

string bookTitle; string phrase1;

Page 12: C++ на македонски

11

string phrase2; phrase1 = "Programming and "; phrase2 = "Problem Solving"; можеме да напишеме

bookTitle = phrasel + phrase2;

при што вредноста која ќе се додели на bookTitle е "Programming and Problem Solving". Очигледно е дека за

конкатенацијата не важи комутативниот закон. Така

bookTitle = phrase2 + phrase1;

на bookTitle ќе и ја додели вредноста "Problem SolvingPrograniming and ". Output (Излез)

Инструкцијата

cout « "Hello";

на стандардниот излезен уред (обично монитор) ќе ги покаже карактерите Hello.

Променлива cout е предефинирана во C++ системите да означи output stream (излезен тек). Излезниот тек

претставува бескрајна низа од карактери кои одат кон излезниот уред. Добиениот излез од следниве две

инструкции:

cout « "The title is ";

cout « bookTitle + ", 2nd Edition";

е ист со излезот од инструкцијата

cout « "The title is " « bookTitle + ", 2nd Edition";

Ако вредноста на bookTitle е “American History” и во двата случаи како излез ќе добиеме

The title is American History, 2nd Edition

Ако во излезот сакаме да го прикажеме карактерот “, на пример, ако сакаме излез

Al "Butch" Jones тогаш инструкцијата во C++ е:

cout « "Al \"Butch\" Jones";

Обично инструкциите за излез се проследени со идентификаторот endl кoj означува крај на линија. На пример,

излезот од:

cout << ”Univerzitet \”Goce Delcev\”;

cout << “Stip”;

e

Univerzitet ”Goce Delcev”Stip

a izlezot od:

cout << ”Univerzitet \”Goce Delcev\” << endl;

cout << “Stip”;

e

Univerzitet “Goce Delcev”

Stip

Page 13: C++ на македонски

12

Конструкција на програма

C++ програмите се состојат од функции, од кои една функција мора да го носи името main. Програмата, исто

така, може да содржи декларации кои лежат надвор од која било функција. Синтактичката шема на програмата и на

функција се дадени на сликата што следува.

Сл. 2.3 Како што се гледа од сл.2.3, функцијата се состои од глава (heading) и тело (body) на функцијата.

Следното е пример на програма со само една функција (main).

Програмата започнува со коментар кој објаснува што тој работи. Веднаш по коментарот се појавуваат

следниве линии:

Page 14: C++ на македонски

13

#include <iostream>

#include <string>

using namespace std;

#include линиите му наложуваат на C++ системот да ја вметне во нашата програма содржината на датотеките

именувани како iostream и string. Првата датотека содржи информации кои & требаат на C++ програмата за да

направи излез. Втората датотека содржи информации за програмерски дефинираниот тип на податок string (стринг).

Потоа доаѓаат декларации на константи по кои следува функцијата main. Првата линија во функцијата е heading на

функцијата: резервираниот збор int, името на функцијата и потоа загради. Телото на функцијата вклучува

декларации на две променливи, a потоа следуваат извршни инструкции. На крајот, функцијата main враќа 0 на

оперативниот систем.

Нешто повеќе за излезот Креирање празни линии Вертикалните празни линии се контролираат со користење на манипулаторот endl во

излезната инструкција. Видовме дека низа од излезни инструкции продолжува да пишува карактери во иста линија,

додека endl не ја прекине линијата. На пример, низата од излезни инструкции:

cout « "Hi there, " « endl « endl;

cout « "Lois Lane." « endl;

креира output

Hi there

Lois Lane.

Како што се гледа со два endl манипулатори еден по друг, се креира празна линија на излезот. Истиот

излез ќе се добие со користење на следнава инструкција:

cout « "Hi there, " « endl « endl « "Lois Lane." « endl;

Вметнување бланко карактери во линијата. Хоризонталните празни места во излезот можат да се

контролираат со вметнување екстра-бланко карактери.

На пример, овој излез:

Може да се постигне преку инструкциите:

cout << “ * * * * * * * * * ” << endl << endl;

cout << “* * * * * * * * * *” << endl << endl;

cout << “ * * * * * * * * * ” << endl << endl;

Page 15: C++ на македонски

14

За да се појават празните карактери на излезот, тие треба да бидат затворени во наводници. На

пример, инструкцијата:

cout « '*' « ' * ' ;

дава излез

* *

додека

cout « '*' « “ “ << ' * ' ;

дава излез

* *

Page 16: C++ на македонски

16

III. Нумерички типови, изрази и output

Преглед на C++ типови на податоци Стандардните или built-in типови на податоци во C++ се организирани во едноставни типови, структуирани

типови и адресни типови (сл. 3-1 во референца 1). Оваа глава ги разгледува integral и floating типовите на податоци.

Интегрални типови на податоци Типовите на податоци char, short, int и long се познати како интегрални типови (или integer типови), затоа

што тие реферираат на integer вредности.

Во C++ наједноставна форма на integer вредност е низа од една или повеќе цифри:

22 16 1 498 0 4600

Запирки не се дозволени. Минус знакот кој претходи на integer вредност го прави integer-от негативен:

-378 -912

Исклучок е специјалниот тип на податоци

unsigned int

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

Овие типови на податоци се чуваат во ќелии во меморијата, при што на следната слика (сл.3.2) е

илустрирана должината на ќелијата во зависност од типот на податокот.

Page 17: C++ на македонски

17

Сл. 3.2

Програмерите скоро секогаш користат декларација int за манипулирање со целобројни вредности. Во

исклучителни случаи, кога се процесираат многу големи броеви (кои се надвор од рангот на int типот), треба да се

користи long декларацијата. На некои персонални компјутери рангот на int вредностите е од -32768 до +32767.

Почесто рангот на int вредностите е од -2147483648 до +2147483647. Ако програмата се обиде да пресмета

вредност надвор од овој ранг, резултатот е integer overflow.

Floating-Point типови на податоци

Floating-point (или floating) типовите се користат за претставување на децимални (real) броеви. Floating-point

броевите имаат целоброен дел и децимален дел со децимална точка меѓу нив. Може да се случи целобројниот или

децималниот дел (но не двата) да се испуштени од бројот. Еве неколку примери на float типови на податоци:

18.0 127.54 0.57 4. 193145.8523 .8

Исто како и интегралните типови (сл.5), така и float типовите имаат различна должина. Така float е со

најмала должина, поголем е double и најголема должина во меморијата зафаќа long double. Floating-point

вредностите во научна нотација се претставуваат со експонент. На пример, наместо да напишеме 3.504 x 1012, во

C++ ние пишуваме 3.504E12. E претставува exponent на база 10. Еве неколку примери на броеви во научна

нотација:

1.74536E-12 3.652442E4 7E20

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

На сличен начин како што деклариравме char и string типови, можеме да декларираме нумерички

типови.

Декларација на именувани константи

Во случај на декларации на именувани константи, литералите во декларацијата се нумерички наместо

стрингови. Во следните примери, првите четири се декларации на нумерички константи, а последните два се

примери на константи од char и string типови..

Page 18: C++ на македонски

18

Декларација на променливи

Нумеричките променливи се декларираат на ист начин како char и string променливите, освен што

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

int radius; // Radius na krug

float sum = 0.; // Zbir inicijaliziran na 0

float strana; // Strana na kvardat

int ocena; // Ocenka na studentot (od 6 do 10). 99 za kraj

double alpha; // Agol vo radijani

Page 19: C++ на македонски

19

Аритметички оператори и изрази

Изразите се состојат од константи, променливи и оператори. Следните се валидни аритметички изрази:

alpha + 2 rate -6.0 4 - alpha rate alpha * num

Основни (built-in) аритметички оператори во C++ се:

+ унарен плус

- унарен минус

+ собирање

- вадење

* множење

/ делење (делење на децимални броеви. Резултат: децимален број)

(делење на цели броеви. Резултат: цел број без остаток)

% модул (делење на цели броеви. Резултат: остатокот од делењето).

Унарен оператор. Оператор што има само еден операнд.

Бинарен оператор. Оператор што има два операнда.

Примери на аритметички изрази и нивни вредности:

Во горните примери, операндите се литерални вредности. Освен литерали, операндите можат да бидат и

константи или променливи. Примери:

Page 20: C++ на македонски

20

Во претпоследниот пример, променливата alpha се јавува од двете страни на инструкцијата за доделување,

=. Математички оваа равенка нема решение, но во програмирањето ваков вид на доделување се среќава многу често

и значи: зголеми ја старата вредност на alpha за 1 и резултатот додели го на новата вредност на alpha. Така што, ако

alpha било 3, новата вредност на alpha ќе биде 4.

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

Increment и Decrement оператори

Освен горенаведените аритметички оператори C++ располага и со increment и decrement оператори: ++ Increment

-- Decrement

Page 21: C++ на македонски

21

Овие се унарни оператори кои земаат една променлива како операнд. Како операнди можат да се јават

целобројни или децимални броеви и ефектот е да се додаде 1 на (или одземе 1 од) операндот. На пример, ако

вредноста на num е 8, инструкцијата

num++ ;

го зголемува num за 1 и по нејзино извршување вредноста на num е 9. Истиот ефект се постигнува со инструкцијата

num = num + 1;

но C++ програмерите го преферираат increment операторот.

++ и -- операторите можат да бидат употребени или како префикс оператори

++num;

или postfix оператори

num++ ;

Двете од овие инструкции имаат ист ефект, т.е. додаваат 1 на num.

C++ дозволува користење на ++ и -- во средина на поголем израз. На пример:

alpha = num++ * 3;

Во овој случај postfix формата не дава ист резултат со prefix формата.

Сложени аритметички изрази

Тука разгледуваме посложени изрази кои содржат повеќе од еден оператор и изразите кои содржат мешани

типови на податоци.

Правила на приоритет

Основните аритметички оператори (унарен +, унарен -, + за собирање, - за одземање, * за множење, / за

делење и % за модул) се подредени на ист начин како и математичките оператори во поглед на приоритетите на

извршување на операциите.

Највисок приоритетен степен: унарен + унарен -

Среден степен: * / %

Најнизок степен: + -

Кога аритметичкиот израз има неколку оператори со ист приоритетен степен, извршувањето се врши од

лево кон десно.

Следуваат неколку примери:

Page 22: C++ на македонски

22

Имплицитна и експлицитна конверзија на типовите на податоци

Integer и floating-point вредностите се чуваат различно во меморијата на компјутерот. Што се случува ако во

аритметичкиот израз или во инструкцијата за доделување помешаме integer и floating-point вредности?

Инструкција на доделување Ако имаме декларации:

int someInt ;

float someFloat;

тогаш изгледа дека инструкцијата за доделување

someFloat = 12;

ќе внесе integer вредност 12 во someFloat, но тоа не е точно. Компајлерот прво имплицитно (автоматски) го

конвертира 12 во 12.0 и тогаш доделува 12.0 во someFloat.

Инструкцијата

someInt = 4.8;

исто така предизвикува имплицитна конверзија, при што доделува вредност 4 на someInt.

Имплицитната конверзија од float во integer е опасна, бидејќи може да доведе до погрешни резултати кога

програмерот не сакајќи доделува float на integer.

someFloat = 3 * someInt + 2;

someInt = 5.2 / someFloat - anotherFloat;

Програмерот зa да ја нагласи намерата да прави конверзија, треба експлицитно да ја најави конверзијата.

На пример:

someFloat = float(3 * someInt + 2);

someInt = int(5.2 / someFloat - anotherFloat);

Долните две инструкции доведуваат до ист резултат во someInt

someInt = someFloat + 8.2;

someInt = int(someFloat + 8.2).

Разликата е во тоа што во втората инструкција се гледа намерата на програмерот, а во првата инструкција не

е јасно дали програмерот ја превидел имплицитната конверзија од float во integer.

Аритметички изрази

Имплицитна конверзија се јавува и во изрази кои содржат мешан тип на податоци

someInt * someFloat

4.8 + someInt - 3

Вакви изрази се нарекуваат изрази од мешан тип.

Израз од мешан тип е израз кој содржи операнди од различни типови на податоци.

Кога integer и floating-point вредност се поврзани со оператор, настанува имплицитна конверзија како што следува:

1. integer вредноста привремено се конвертира во floating-point вредност;

2. се извршува операцијата;

Page 23: C++ на македонски

23

3. резултатот е floating-point вредност.

Повикување на функција и библиотечни функции

Функции кои враќаат вредност (Value-Returning Functions)

На почетокот од втората глава прикажавме програма која се состои од три функции: main, Square и Cube. Да

се задржиме на следната инструкција од програмата:

cout « " and the cube of 27 is " « Cube(27) « endl;

Во оваа инструкција main му налага Cube да пресмета куб од 27 и да го врати резултатот назад на main.

Cube(27) е повик на функција.

Компјутерот привремено ја остава main функцијата да чека и ја стартува Cube функцијата. Кога Cube ја завршува

својата работа, компјутерот се враќа на main и продолжува таму каде што застанал.

Во горниот повик на функцијата, бројот 27 се нарекува аргумент (или актуелен параметар). Аргументот

овозможува функцијата која се повикува да работи за различни вредности. На пример, може да се напишат

инструкции како:

cout « Cube (4);

cout « Cube(11);

Листата на аргументи е начин да функциите комуницираат меѓу себе. Некои функции како Square и Cube,

имаат само еден аргумент во листата на аргументи. Некои функции како main, немаат аргументи, а некои функции

имаат два, три или повеќе аргументи во листата, кои се одделени со запирки.

Функциите кои враќаат вредност се користат во аритметички изрази на сличен начин како што се користат

и променливите. На пример:

someInt = Cube(2) * 10;

Ова се неколку карактеристики на функциите кои враќаат вредност:

• Повикот на функцијата се користи во израз. Тој не се појавува како издвоена инструкција во програмата.

• Функцијата пресметува вредност која е на располагање за користење во изразот.

• Функцијата враќа точно една вредност (резултат). Ни повеќе ни помалку.

Аргументот во функцијата не мора да биде литерал. Тој може да биде променлива, константа и аритметички

израз како:

alpha = Cube(int1 * int1 + int2 * int2) ;

Аргументот дури може да биде повик на функција. На пример:

alpha = Cube(Square(int1) + Square(int2));

Библиотечни функции

Следувaaт мал број на предефинирани функции кои се наоѓаат во некои стандардни библиотеки:

Page 24: C++ на македонски

24

Користењето на библиотечни функции е лесно. Следувa програмски сегмент кој користи библиотечна

функција:

Void функции

Досега, во оваа глава, разгледувавме само функции кои враќаат вредност. Во C++ постојат и функции кои

не враќаат вредност или т.н. void функции. На пример:

void CalcPay ( . . . )

{

:

}

CalcPay е пример на функција која не враќа вредност на својот повикувач. Таа само извршува некоја акција

и завршува. Void функција се повикува различно value-returning функција. Повикувањето на void функција е од

издвоена stand-alone инструкција.

CalcPay (payRate, hours, wages) ;

Значи, можеме да резимираме, Value-returning функција е функција која враќа една и само една вредност

на својот повикувач и се повикува од израз.

Void функција (процедура) е функција која не враќа вредност на својот повикувач и се повикува од

издвоена инструкција.

Page 25: C++ на македонски

25

Форматирање на Output

Да се форматира излезот значи да се контролира како тој се појавува на излезниот уред (монитор, принтер

или датотека). Досега видовме како се генерираат празни редови и празни места во еден ред. За понатамошна

контрола на излезот користиме т.н. манипулатори.

C++ стандардната библиотека содржи многу манипулатори, но сега ние разгледуваме само пет: endl, setw,

fixed, showpoint, and setprecision. Манипулаторите endl, fixed и showpoint доажаат со претпроцесор инструкцијата

#include <iostream>. Другите два манипулатора, setw и setprecision, бараат вклучување на датотеката iomanip во

претпроцесорот #include <iomanip>:

#include <iostream>

#include <iomanip>

using namespace std;

cout « setw(5) « somelnt;

Манипулаторот setw е кратенка од "set width" и овозможува контрола колку позиции на карактери му се

доделени на следниот податок што треба да оди на излез.

Аргументот на setw е integer израз наречен fieldwidth specification. Следниот податок ќе биде десно

подреден, пополнет со бланко позиции од лево за да се пополни fieldwidth спецификацијата. На пример:

Сетирањето на ширината на полето е еднократна акција, додека следниот податок не отиде на излез. По овој

излез, ширината на полето се ресетира на 0, што значи прошири го полето само онолку колку што му треба на

следниот податок што оди на излез.

cout « "Hi" « setw(5) « ans « num;

полето се ресетира на 0 откако излезе ans. Како резултат ние добиваме излез

Hi 337132

Page 26: C++ на македонски

26

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

формат.

cout « fixed « 3.8 * x;

Ако на излез оди цел број, C++ не ја прикажува децималната точка. На пример, вредноста 95.0 се покажува

на излезот како 95.

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

cout « showpoint « floatVar;

Манипулаторот setprecision го контролира бројот на децимални места на излезот. На пример, може да се

случи да излез од

cout « "Tax is $" « price * 0.05;

биде Tax is $17.7435

За да се прикаже бројот со две (заокружени) децимални места, инструкцијата за излез е

cout « fixed « setprecision(2) « "Tax is $" « price * 0.05;

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

Следната табела дава краток преглед на манипулаторите што ги разгледувавме во оваа глава:

Page 27: C++ на македонски

27

Page 28: C++ на македонски

29

(лекториран текст)

IV. Прогрaмски инпут

Откако програмaта е напишана, треба да се предвиди на кој начин ќе се

внесуваат податоците. Лошо решение е ако податоците се директно внесени како

литерали во програмата. Тогаш за процесирање на различни влезни вредности, треба

постојано да се менува програмата.

Приложување податоци во програмите

Една од најголемите предности кај компјутерите е што програмата може да се

употребува со различни групи на податоци. За да се направи тоа, треба податоците да

се чуваат посебно од програмата. Тогаш инструкциите во програмата ги копираат

вредностите од групата на податоци во променливите на програмата. По доделување на

вредностите на променливите, програмата може да изврши пресметувања (сл. 4.1

преземена од сл. 4-1 во референца 1).

сл. 4.1

Процесот на доделување вредности од надворешна група податоци се нарекува

input (инпут). Податоците за програмата можaт да дојдат од влезен уред или од

датотека која се наоѓа на некој периферен уред (најчесто диск).

Page 29: C++ на македонски

30

Влезен тек и еxtraction оператор (»)

Влезниот тек (input stream) може да се сфати како бескрајна низа од карактери

кои доаѓаат во програмата од влезниот уред. The concept of a stream is fundamental to

input and output in C++. За да користиме влезен или излезен тек треба да користиме

препроцесорска директива: #include <iostream>

Хедер (Header) датотеката iostream содржи дефиниција на два типа на податоци:

istream и ostream. Ова се типови на податоци кои претставуваат input и output тек

респективно. Хедер (Header) датотеката, исто така, содржи декларации како istream cin

и ostream cout.

Како што веќе видовме, вредностите се праќаат на излез cout со користење на

insertion (вметнувачки) operator («), што значи “стави на“: cout << 3 * price.

На сличен начин, влезните податоци се добиваат од cin со користење на

extraction операторот (») или “доби од“: cin>> cost.

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

кој е втипкан на тастатурата (на пример 425) и го чува во променливата cost.

Еxtraction операторот » има два операнда. Од лево е влезниот тек (во

наједноставен случај променливата cin). Десниот операнд е променлива во која се

внесува влезниот податок.

Операторот » може да се користи неколку пати во една инструкција за input

(внос). На пример, нема разлика меѓу инструкцијата:

cin » length » width;

и парот на инструкции:

cin » length;

cin » width;

За разлика од податоците специфицирани во излезната инструкција, кои можат

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

инструкција треба да бидат имиња на променливи.

Кога бара следна влезна вредност во текот, операторот » ги скока (игнорира)

т.н. whitespace карактери. Whitespace карактери се бланко и одредени непринтабилни

карактери, како на пример карактерот што означува крај на линија.

По игнорирање на whitespace карактерите, операторот » добива податоци од

влезниот тек. Ако програмaта бара карактер од тип char, влезот застанува штом еден

Page 30: C++ на македонски

31

карактер е влезен. Ако бараниот тип на податок е int или float, влезот застанува штом се

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

Читачки маркер и карактер за нова линија

Секоја влезна линија има невидлив карактер за нова линија (end-of-line

карактер) што му кажува на компјутерот каде една линија завршува и следната

почнува. За да ја најде следната влезна вредност, операторот » ја поминува границата

на линијата преку end-of-line карактерот. Карактерот за нова линија се генерира со

притискање на копчето Return или Enter. Исто така, овој карактер се генерира кога

програмата користи endl манипулатор во инструкцијата за излез. Овој карактер е

nonprintable контролен карактер кој системот го препознава како значење за нова

линија, без разлика дали се работи за input или output. Во C++ програма, на newline

карактер се реферира со користење на два симбола \n, backslash и n без бланко меѓу

нив. На пример, карактер за нова линија може да додели на char променлива

ch = '\n';

Page 31: C++ на македонски

32

Карактер за нова линија може да се вметне директно во string, како и кој било

принтабилен карактер:

cout « "Hello\n";

Последната инструкција има ист ефект како инструкцијата:

cout « "Hello" « endl;

Следуваат неколку примери на програмски инструкции, вредност на

променливите по извршена инструкција и позиција на маркерот во влезниот тек, пред и

по извршувањето на инструкцијата во програмата.

Читање на char карактери со get функцијата

Како што беше споменато, операторот » игнорира whitespace карактери (како

бланко и карактери за нова линија). Да претпоставиме дека chl и ch2 се char

променливи и програмaта ја извршува следнава инструкција: cin >> ch1 >> ch2.

Ако влезниот тек се состои од R 1 тогаш операторот >> доделува 'R' на chl, го

игнорира бланко карактерот и доделува ‘1' во ch2. Ако сакаме да направиме внос на

бланко карактер во променлива од тип char горната инструкција не работи.

Типот на податок istream обезбедува алтернатива на читање на карактери преку

get функцијата, која не ги игнорира whitespace карактерите. Бидејќи оваа функција

(метод во објектно-ориентираното програмирање) е вградена во типот на податок

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

користи dot нотација. Повикувањето на функцијата изгледа вака: cin.get(someChar).

Забележете дека повикувањето на get функцијата употребува синтакса на

повикување на void функција. Ефектот од горниот повик е внос на следниот карактер

од влезниот тек во променливата која се јавува како аргумент (someChar). Овој

карактер може да биде и непринтабилен карактер (на пример, бланко). Аргументот на

get функцијата мора да биде променлива.

Користејќи ја get функцијата, ние сега можеме да ги внесеме сите три карактери од влезната линија: R 1 Ние можеме да користиме три последователни повикувања на get функцијата: cin.get(ch1);

cin.get(ch2);

cin.get(ch3);

или можеме да ги користиме следниве инструкции:

cin » ch1;

cin.get(ch2);

cin » ch3;

Page 32: C++ на македонски

33

Освен get функцијата, влезниот тек има вградено и функција за прескокнување

карактери. Тоа е функција со два аргументa, која се повикува како во следниот пример:

cin.ignore(200, '\n');

Првиот аргумент е int израз, а вториот char вредност. Во овој пример,

функцијата му кажува на компјутерот да ги игнорира (скокне) следните 200 input

карактери или да ги игнорира карактерите до наоѓањето на карактер за нова линија

(newline карактер). Валиден е условот што прв се исполнува. Еве неколку примери кои

користат char променлива и три int променливи i, j и k:

Внос (читање) на string (стринг) податоци

За внос на карактери во string (стринг) променлива, повторно може да се

користи операторот ». Од порано знаеме дека овој оператор ги игнорира whitespace

карактерите. На пример, ако имаме ваков програмски сегмент:

string firstName;

string lastName;

cin » firstName » lastName;

и влезниот тек (stream) изгледа вака:

Mary Smith 18

тогаш влезната инструкција во програмскиот сегмент доделува четири карактери Mary

во firstName, пет карактери Smith во lastName и го напушта влезниот тек како

18

Page 33: C++ на македонски

34

Ако во стрингот сакаме да имаме празни (бланко) карактери, вносот со овој

оператор е неуспешен. Тогаш ја користиме функцијата getline. Повикот на оваа

функција изгледа вака:

getline(cin, myString);

Повикот на функцијата бара два аргумента. Првиот е влезниот тек (овде cin) и

вториот аргумент е string променлива. Оваа функција не скока whitespace карактери и

продолжува додека не го достигне end-of-line карактерот, ‘\n’. Ова значи дека getline

функцијата доделува цела влезна линија на вториот аргумент. На пример за следниот

сегмент:

string inputStr;

getline(cin, inputStr);

и влезна линија

Mary Smith 18

Резултатот од повикот на getline е сите 17 карактери од влезната линија (вклучувајќи и

бланко карактери) да се доделуваат на inputStr, а читачкиот маркер се позиционира на

следната линија.

Интерактивен Input/Output

Интерактивна програма е програма во којa корисникот директно комуницира со

компјутерот. Корисникот за да внесе податоци во интерактивна програма, програмерот

во својата програма треба да предвиди влезни промптови (input prompts) кои

претставуваат пораки кои се јавуваат на мониторот и му појаснуваат на корисникот

што треба да внесе. Освен влезните промптови, програмерот во својата програма треба

да предвиди т.н. echo printing што претставува прикажување на податоците кои

корисникот ги внел, така што корисникот ќе направи контрола на својот внос пред

податоците да се процесираат.

Во следниот програмски сегмент е прикажано правилно користење на промптови

и echo printing:

Page 34: C++ на македонски

35

Неинтерактивен Input/Output

Општ пример на неинтерактивен I/O на големи компјутерски системи е batch

процесирањето. Овој метод е ефективен, кога програмата на внос или излез има голема

количина на податоци. Кога програмата треба да чита многу податоци, вообичаено е

тие податоци да се внесат претходно во датотека на дискот. Ова му овозможува на

корисникот да нaправи промени или корекции во податоците пред да ja стартува

програмата. Ако програмата прави излез на голем број податоци, излезот се праќа во

датотека или на брз печатар. Програмите дизајнирани за неинтерактивен I/O не

прикажуваат промптови.

Датотеки (Files)

Датотека е именуван дел од периферниот уред кој чува збир на информации

(на пример, програмски код кој е внесен преку едиторот). Информациите во датотеката

обично се чуваат на периферен уред, најчесто диск. Нашите програми можат да читаат

податоци од датотека на ист начин како што читаат податоци од тастатура и можат да

го пишуваат излезот во датотека на ист начин како што го пишуваат излезот на

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

да се внесат преку едитор во датотека, отколку да се внесуваат интерактивно преку

тастатура. Исто така, кога програмата праќа голем број податоци на излез, излезот

полесно се анализира кога се впишува во датотека, отколку да се гледа на монитор.

Користење на датотеки

Page 35: C++ на македонски

36

Ако сакаме нашата програма да користи датотеки, треба да направиме четири

работи:

1. претпроцесорот треба да вклучи header file fstream;

2. да се декларираат датотечните текови (file streams ) што ќе бидат користени во

програмата;

3. да се припреми секоја датотека за читање или впис со користење на вградената

функција open;

4. да се специфицира името на датотечниот тек во секоја влезна или излезна

инструкција.

Вклучување (Including) на Header датотеката fstream.

Ова вклучување се постигнува со претпроцесорската директива:

#include <fstream>

Преку header датотеката fstream, C++ стандардната библиотека дефинира два

типа на податоци, ifstream и ofstream (кои означуваат input file stream и output file

stream).

Сите istream операции за кои зборувавме: extraction операторот (»), get

функцијата и ignore функцијата се исто така валидни за ifstream типот. Исто така, сите

ostream операции: insertion операторот («) и манипулаторите се валидни за ofstream

типот.

Декларирање на датотечните текови (file streams)

Во програмата stream се декларираат променливите на ист начин како што се

декларира која било променлива. Значи, прво се специфицира тип на податок и потоа

име на променлива:

int somelnt;

float someFloat;

ifstream inFile;

оfstream outFile;

ifstream inData; // Holds map distances in inches

ofstream outData; // Holds walking distances in miles

Page 36: C++ на македонски

37

Забележете дека ifstream типот се однесува само за влезни, а ofstream типот

само за излезни датотеки. Значи не може да се чита и пишува во иста датотека.

Oтворање на датотеки

Во нашиот пример, ние сакаме да читаме од датотека inData и да запишуваме во

датотека outData. Релевантните датотеки се отвораат со следниве инструкции:

inData. open ("walk.dat“);

outData.open("results.dat");

Двете инструкции се однесуваат на повикувања на функции. Во секој повик на

функција, аргументот е литерален стринг затворен во наводници.

Функцијата open прво ја поврзува stream променливата која се користи во

програмата со физичката датотека на дискот. Следната работа што ја прави open е во

зависност од тоа дали датотеката е влезна или излезна. Ако датотеката е влезна (input),

open функцијата го поставува читачкиот маркер на првиот податок во датотеката. Ако

датотеката е излезна, open функцијата проверува дали датотеката веќе постои. Ако

датотеката не постои, open креира нова празна датотека, а ако веќе постои, open ја

брише нејзината содржина и го поставува маркерот за впишување на почетокот на

празната датотека. Како вписот напредува, маркерот за пишување додава (допишува)

податоци додека не заврши на крајот на датотеката.

Специфицирање на File Streams во Input/Output инструкциите

Како што нагласивме, сите istream операции се исто така валидни за ifstream

типот и сите ostream операции се валидни за ofstream типот. Така, за да читаме од или

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

инструкции да ги замениме cin и cout со соодветните file текови (streams). На пример,

можеме да ја напишеме следнава инструкција:

inData>>distance1>>distance2>>distance3>>distance4>>scale;

која му кажува на компјутерот да чита податоци од датотеката inData наместо од cin.

На сличен начин сите излезни инструкции наместо cout ќе впишуваат во

outData.

outData<<“Total mileage for the day”<<totMiles<<“miles”

<< endl;

Run-Time внос на име на датотека

Page 37: C++ на македонски

38

Aргументот што го употребувавме во open функцијата беше литерален стринг.

На пример:

ifstream inFile;

inFile.open("datafile.dat");

Ако сакаме да ја направиме програмата пофлексибилна, име на датотека која

постои на дискот можеме да внесеме во време на извршување на програмата (run-time).

Општа техника е на корисникот да му се покаже промпт на кој тој ќе го впише името на

датотеката. Во принцип следниот код треба да го дозволи тоа:

ifstream inFile;

string fileName;

cout « "Enter the input file name: ";

cin » fileName;

inFile.open(fileName); // Compile-time error

Овој код генерира грешка во компилација. Проблемот е во тоа што open

функцијата не прима аргумент од тип string. Функцијата open очекува аргумент од тип

C string (така е наречен бидејќи потекнува од C јазикот).

За да горниот код работи коректно, стринг променливата треба да се

конвертира во C string. Типот на податок string располага со value-returning функција по

име c_str, која на стринг променлива може да се примени како што следува:

fileName. c_str()

Оваа функција враќа C string . Примарната намена на c_str функцијата е да им

дозволи на програмерите да повикуваат библиотечни функции кои очекуваат C strings

како аргументи.

Користејќи ја c_str функцијата, ние можеме да го кодираме run-tirne влезот на

името на датотеката како што следува:

ifstream inFile;

string fileName;

cout « "Enter the input file name: ";

cin » fileName;

inFile. open (fileName. c__str ()) ;

Пад на влезот

Да претпоставиме дека извршуваме програма. Програмата нè промптира да

внесеме integer, а ние внесуваме некои букви преку тастатура. Влезната операција паѓа

поради невалидни податоци. Во терминологијата на C++ велиме дека cin влегол во fail

state. Штом текот влезе во fail state секоја понатамошна I/O операција која го користи

Page 38: C++ на македонски

39

тој тек е null операција, т.е. нема никаков ефект. Но компјутерот не го прекинува

извршувањето на програмата, туку ги врши инструкциите со претходните вредности на

променливите. Најчеста причина за пад на вносот е невалиден внос. Честа причина е и

непостоење на влезната датотека од која треба да се чита.

Да претпоставиме дека програмата има int променливи i, j и k, чии моментални

вредности се 10, 20 и d 30 соодветно. Програмата ги извршува следниве две

инструкции:

cin>>i>>j>>k;

cout<<“i:”<<i<<“ j:”<<“ k:”<<k;

Ако на влез имаме:

1234.56 7 89

тогаш програмата дава output:

i: 1234 j: 20 k: 30

Да анализираме што се случило. Кога почнал да чита, на првиот податок cin

влегол во fail state, бидејќи за читање на int тип наишол на ‘.’ . До тогаш ја примил

само новата вредност на i. Откако влегол во fail state, секој обид за внос нема ефект и

другите две променливи остануваат со старите вредности.

Page 39: C++ на македонски

40

(лекториран текст)

V. Услови, логички изрази и контролна структура - селекција

Тек на контрола е редоследот по кој компјутерот ги извршува инструкциите во

програмата.

Текот на контролата, нормално, е секвенцијален (сл. 5.1). Тоа значи дека кога

една инструкција е завршена, контролата преминува на следната инструкција во

програмата.

сл. 5.1

Кога сакаме текот на контролата да биде несеквенцијален, ние користиме

контролни структури кои претставуваат специјални инструкции кои ја предаваат

контролата на инструкцијата, поинаква од онаа инструкција која доаѓа следна во

програмата.

Контролна структура е инструкција која се користи да го промени

секвенцијалниот тек на контролата.

Селекција

Контролната структура селекција (или разгранување) ја користиме кога сакаме

компјутерот да избере меѓу алтернативни акции. Правиме тврдење (assertion) кое е или

точно (true) или погрешно (false). Ако тврдењето е точно (true), компјутерот извршува

една инструкција (statement), ако е погрешно (false), тогаш извршува друга инструкција

(сл. 5.2). Способноста на компјутерот да решава проблеми е резултат на неговата

способност да прави одлуки и извршува различни низи од инструкции.

Page 40: C++ на македонски

41

сл. 5.2

Услови и логички изрази

Поставувањето прашања во C++ се сведува на составување на тврдења кои се

точни или погрешни. Компјутерот го цени (evaluates) тврдењето да провери дали е

точно (true) или погрешно (false).

Тип на податок (bool)

Во C++ типот на податок bool е built-in и се состои од само две вредности,

константите true (точно) и false (погрешно). Клучниот збор bool е кратенка од Boolean и

вредностите од овој тип се користат за тестирање на услови во програмата, така што

компјутерот може да направи одлука. Накратко bool податокот е поврзан со

контролната структура - селекција. Променливите од типот bool се декларираат како

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

bool dataOK; //True if the input data is valid

bool done; // True if the process is done

bool taxable; // True if the item has sales tax

Логички изрази

Во програмските јазици, тврдењата имаат форма на логички изрази (т.н.

Boolean изрази). Како што аритметичкиот израз е составен од нумерички вредности и

операции, логичкиот израз се состои од логички вредности и операции. Секој логички

израз има една од двете вредности: true или false.

Ова се неколку примери на логички изрази:

Page 41: C++ на македонски

42

- Boolean променлива или константа;

- израз по кој следи релационен оператор по кој следи израз;

- логички израз по кој следи логички оператор по кој следи логички израз.

Наједноставен логички израз е само променлива или константа од типот bool.

Променлива од типот bool може да прими само две вредности true или false. На пример,

ако dataOK е декларирана како променлива од типот bool, тогаш dataOK = true е

валидна инструкција на доделување.

Релациони оператори

Друг начин за доделување вредност на Boolean променлива е таа да се сетира

еднаква на резултатот на споредување два израза со релационен оператор. Релационите

оператори ја тестираат врската меѓу две вредности.

Во следниот програмски сегмент, lessThan е Boolean променлива, а i и j се int

променливи:

cin » i » j ;

lessThan = (i < j); // Споредува i и j со "помало од"

// релационен оператор и доделува

//true или false на lessThan

Следното се релации кои можеме да ги тестираме во C++:

= = Equal to (еднакво на)

! = Not equal to (не е еднакво на)

> Greater than (поголемо од)

< Less than (помало од)

>= Greater than or equal to (поголемо од или еднакво на)

<= Less than or equal to

Израз по кој следува релационен оператор, а по него повторно израз се нарекува

релационен израз. Резултатот на релационен израз е од типот bool. На пример, ако x е 5,

а y е 10, сите од следниве изрази имаат вредност true:

• x != y

• y > x

• x < y

• y >= x

• x <= y

Page 42: C++ на македонски

43

Освен нумерички вредности, може да се споредуваат и карактери. Ако x е 'M'

и y е 'R',

Вредноста на изразот x < y е true, бидејќи 'M' доаѓа пред 'R' во абецедата и во

ASCII (American Standard Code for Information Interchange) множеството на карактери.

Од друга страна, во ASCII големите букви доаѓаат пред малите. Така:

'M' < 'R' и

’m’ < ‘r’

имаат вредност true, но

'm' < 'R‘

има вредност false.

Секогаш треба да се споредуваат податоци од ист тип. Кога се споредуваат

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

someFloat >= float(somelnt)

Ако се споредуваат bool вредности со нумерички вредности, компјутерот

имплицитно ја конвертира вредноста false во 0, и true во 1. Значи, ако boolVar е bool

променлива изразот

boolVar < 5

добива вредност true, затоа што и 0 и 1 се помали од 5. Следуваат примери:

Треба да се внимава како релационен оператор место = =, по грешка не се стави

операторот за доделување =. Овие два оператора имаат многу различни ефекти во

програмата.

Споредување на стрингови

Како операнди на релациониот оператор можат да се јават два стринг објекта

(константи или променливи), како:

myString < yourString

или стринг објект и C string:

Page 43: C++ на македонски

44

myString >= "Johnson"

Меѓутоа, ако и двата операнда се од тип C string, ќе се добијат неточни

резултати. Споредувањето на стрингови ја следи редоследната низа во множеството на

карактери (на пример ASCII ). Кога компјутерот ја тестира врската меѓу два стринга,

тој почнува од првите карактери на стринговите, aко се еднакви, продолжува да ги

споредува вторите карактери итн. Тестирањето завршува кога двата карактерa не се

еднакви или кога и крајните карактери се еднакви. Ако сите карактери се еднакви,

тогаш и стринговите се еднакви, инаку стрингот кај кој карактерот е помал во ASCII е

помал стринг. На пример:

Ако се споредуваат два стринга со различна должина, при што карактерите на

пократкиот и подолгиот стринг до крајот на пократкиот стринг се еднакви, тогаш

пократкиот стринг се евалуира како „помал“. На пример, за word2 = “Small” изразот

word2 < "Smaller"

има вредност true.

Логички оператори

Во математиката, логичките оператори AND, OR и NOT земаат логички изрази

како операнди. C++ користи специјални симболи за логички оператори: && (за AND),

||за OR) и ! (за NOT). Со комбинирање на релациони оператори со логички оператори,

може да се направат посложени тврдења. На пример, сакаме да определиме дали

резултатот од испит е поголем од 90 и резултатот од колоквиум е поголем од 70. Во

C++, ние би го напишале изразот (тврдењето на овој начин):

Page 44: C++ на македонски

45

finalScore>90 && midtermScore> 70

Операторите && и || секогаш се појавуваат меѓу два изразa, т.е. тие се бинарни

оператори. Операторот NOT (!) е унарен оператор и тој зема само еден операнд. Тој

претходи на единичен логички израз и дава спротивен резултат од евалуацијата на

изразот. Ако (grade == 'A' ) е false, тогаш ! (grade == 'A' ) е true. Со NOT можеме да го

менуваме значењето на тврдењето. На пример:

!(hours > 40)

е еквивалентно на

hours <= 40

Следниве изрази се еквивалентни (изразите од левата колона со

соодветните изрази од десната колона).

Забележете го шаблонот: левиот израз е ист со десниот со додаден унарен

оператор ! како и релациони и логички оператори сменети во спротивни ( = = наместо !

= и | | наместо &&).

Логички оператори можат да оперираат на резултати од споредувања. Исто

така, може да оперираат на променливи од типот bool. На пример, наместо евалуирање

на сложениот израз:

isElector = (age >= 18 && district == 23,

може да се користат два меѓурезултата со вклучување на две дополнителни Boolean

променливи, isVoter и isConstituent:

isVoter = (age >= 18) ;

isConstituent = (district == 23);

isElector = isVoter && isConstituent.

Двете табели подолу ги прикажуваат резултатите од применување на

операторите:

&& и | | врз логички изрази (претставени овде со Boolean променливи x и y).

Page 45: C++ на македонски

46

Табела 5.1 Евалуација по скратена постапка

Го разгледуваме логичкиот израз: i == 1 && j > 2.

Некои програмски јазици користат полна евалуација на логичките изрази. Со

полната евалуација компјутерот прво ги евалуира двата подизраза (i == 1 и j > 2) пред

да оперира со && операторот и да добие краен резултат.

За разлика од полната, C++ користи скратена (или условна) евалуација на

логичките изрази. Евалуацијата се врши од лево кон десно и компјутерот престанува да

го евалуира изразот штом ја дознае вистинската вредност на целиот израз. На пример,

AND (&&) операторот запира со евалуација ако првиот израз што го евалуира е false

(табела 5.1). Во последниот пример, ако i = 9 со евалуација на подизразот i = = 1, како

false, целиот израз е false. При евалуација на израз со OR (| |) оператор, ако вредноста на

подизразот е true, јасно е дека целиот израз е true (табела 5.1). На пример:

Page 46: C++ на македонски

47

c <= d | | e==f

Ako c <= d јасно е дека целиот претходен израз е true.

Приоритет на оператори

Во следната листа е прикажан редоследот на приоритети на аритметички

релациони и логички оператори.

Како што може да се забележи во листата, унарните оператори имаат највисоко

ниво на приоритет и тие оперираат први во изразот. Најнизок приоритет има

операторот „доделување“ и тој оперира последен. Операторите на исто ниво на

приоритет се претставени во иста линија и приоритетот кај нив е од лево кон десно.

Релациони оператори со Floating-Point типови

Досега зборувавме само за споредување на int, char и string вредности. Тука

разгледуваме float променливи.

Не споредувајте еднаквост на floating-point броеви. Поради тоа што мали

грешки најверојатно можат да се појават во најмалите (најдесните) децимални места

при аритметичко оперирање со децимални броеви, ретко се случува два децимални

броја да бидат еднакви. На пример, го разгледуваме следниот сегмент кој користи две

float променливи именувани оneThird и x:

oneThird = 1.0 / 3.0;

x = oneThird + oneThird + oneThird;

Во овој момент очекуваме x = 1, односно евалуацијата на x = = 1 да е true. Но

поради лимитирана машинска прецизност, многу е веројатно дека вредноста на x не е 1,

туку некој број како 0.9999999. Затоа, тврдењето x = = 1 е false. Кога се оперира со = =

на децимални броеви, исправно е наместо тврдењето r = = s да се евалуира тврдењето

fabs(r - s) <= tol, каде што r и s се кои било floating-point броеви, а tol е толеранција која

зависи од природата на проблемот и обично е многу мал floating-point број (на пример,

tol = 0.0001).

Page 47: C++ на македонски

48

If Инструкција

If инструкцијата е основна контролна структура што овозможува разгранување

на текот на контролата. Со неа можеме да поставиме прашање и избереме пат на

акцијата: ако (if) одреден услов постои тогаш (then) изврши една акција инаку (else)

изврши друга акција. Во време на извршување на оваа инструкција, компјутерот

извршува само една од две акции.

If-Then-Else форма

Во C++, If инструкцијата може да се јави во две форми: If-Then-Else форма и If-

Then форма. Let's look first at the If-Then-Else. Here is its syntax template:

If инструкција (If-Then-Else форма)

if ( Израз )

Statement1A

else

Statement1B

Вредноста на израз е од тип bool. Во време на извршувањето (run time),

компјутерот го евалуира изразот. Ако вредноста е true, компјутерот ја извршува

инструкцијата StatementlA. Ако вредноста е false, се извршува StatementlB. StatementlA

често се нарекува then-клаузула; StatementlB, else-клаузула. Сликата 5-3 го илустрира

текот на контролата на If-Then-Else. На сликата, Statement2 е следната инструкција која

што следува по целата If инструкција.

Page 48: C++ на македонски

49

сл. 5.3

If-Then-Else формата не го користи зборот then. Значи, then клаузулата

(Statement1A) доаѓа веднаш по условот.

Следниот програмски сегмент прикажува како се пишува If инструкцијата во

програмата.

if (hours <= 40.0)

pay = rate * hours;

else

pay = rate * (40.0 + (hours - 40.0) * 1.5);

cout « pay;

Од аспект на инструкциите на компјутерот, горниот код вели: „Ако часовите се

помалку или еднакви на 40, пресметај регуларна плата и печати плата, но ако часовите

се повеќе од 40, пресметај регуларна плус прекувремена плата и печати плата“.

Инструкцијата за излез (печатење на монитор) е инструкција која не припаѓа на

контролната структура. Сликата 5-4 го покажува текот на контролата на оваа If

инструкција.

Сл. 5.4

Следниот програмски сегмент е превенција од делење со 0. if (divisor != 0)

result = dividend / divisor;

else

Page 49: C++ на македонски

50

cout « "Division by zero is not allowed." « endl;

Блокови (композитни инструкции)

Во последниот пример, да претпоставиме дека кога именителот е 0 сакаме да

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

result вредност 9999. Ни требаат две инструкции во иста гранка, но изгледа дека

синтаксата нè лимитира само на една инструкција. Ние сакаме низа од инструкции во

else-клаузулата. За да го постигнеме тоа, низата од инструкции ја затвораме во големи

загради и формираме блок (композитна инструкција)

{

____________________ инструкција 1

_____________________ инструкција 2

}

кого компајлерот го третира како една инструкција. За нашиот пример:

if (divisor != 0)

result = dividend / divisor;

еlse

{

cout « "Division by zero is not allowed." « endl;

result = 9999;

}

If-Then Форма

Некогаш се среќаваме со проблеми во кои ако одреден услов е исполнет треба

да извршиме некоја акција, а ако не не треба да извршиме ништо. Со други зборови,

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

задоволен. Ова може да се направи ако ја оставиме else гранката празна:

if (a <= b)

c = 20;

else

;

Page 50: C++ на македонски

51

Подобар начин е да го испуштиме else делот. Во овој случај ја добиваме If-Then

формата на If инструкцијата. Синтаксата изгледа вака:

If инструкција (If-Then форма)

if (Израз)

Инструкција

Сл. 5.5

Следното е пример за If-Then > if (age < 18)

cout « "Not an eligible ";

cout « "voter." « endl

Како што имавме случај со двете гранки на If-Then-Else, и гранката во If-Then

може да биде блок. На пример, пишуваме програма за пресметување на данок на личен

доход. Една од линиите на формуларот кажува „Извади го износот на линија 23 од

износот на линија 17 и внеси го резултатот на линија 24. Ако резултатот е негативен

внеси 0 и означи го полето 24А“. Овде можеме да ја користиме If-Then формата:

result = line17 - Iine23;

if (result < 0.0)

{

cout « "Check box 24A" « endl;

result = 0.0;

}

Page 51: C++ на македонски

52

Iine24 = result;

Што ќе се случи ако ги испуштиме левата и десна заграда во горниот код?

result = line17- Iine23; // Incorrect version

if (result < 0.0)

cout « "Check box 24A" « endl;

result = 0.0;

Iine24 = result;

И покрај индентацијата (вовлекување на инструкциите во then клаузулата,)

компајлерот ја зема оваа клаузула како клаузула со една инструкција, бидејќи не е

затворена во блокови. Ако резултатот е негативен (помал од 0), програмата работи

добро. Но ако почетниот резултат е позитивен, компјутерот ја скока then клаузулата и

продолжува на инструкцијата по If инструкцијата. Тоа е инструкцијата за доделување

која го сетира резултатот на 0. Така резултатот постојано добива вредност 0, без

разлика што е неговата пресметана вредност.

Вгнездени If инструкции

Не постојат ограничувања кои инструкции можат да бидат во If структурата, If

во If е валидно. If во If во If, исто така. Кога ставаме If во If, ние креираме вгнездена

контролна структура (nested control structure).

Главно, секој проблем кој вклучува повеќенасочна гранка multiway branch

(повеќе од два алтернативни патишта) може да биде кодиран користејќи вгнездени If

инструкции. На пример, за да испечатиме име на месец ако е даден неговиот реден број

ние треба да користиме низа од невгнездени If инструкции:

if (month == 1) cout « "January"; if (month == 2) cout « "February"; if (month == 3) cout « "March"; . . if (month == 12) cout « "December";

Page 52: C++ на македонски

53

Со ова решение, контролата мора да помине низ целата низа од If

инструкции, а исто така повеќе од една алтернатива (повеќе од еден услов) можат да

бидат задоволени.

Со користење на вгнезден If имаме:

if (month == 1) cout « "January"; elseif (month ==2) // Nested If cout « "February"; elseif (month ==3) // Nested If cout « "March"; elseif (month ==4) // Nested If

.

.

Со оваа варијанта, If структурата се прекинува кога условот е задоволен, а со

тоа се прифаќа само една алтернатива.

Припадност на else клаузулата во вгнезденa If структура

Кога имаме вгнезден If се поставува прашањето на кое If припаѓа else

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

if (average < 70.0)

if (average < 60.0)

cout « "Failing";

else

cout « "Passing but marginal";

If се појавува двапати, а else само еднаш. Правило е дека кога нема присуство

на блок инструкција, else припаѓа на првото претходно if кое не е спарено со else. Ако

во последниот програмски сегмент сакаме else да се однесува на надворешното if,

тогаш надворешната if-then клаузула треба да се претвори во блок:

if (average >= 60.0) // Correct version

{

if (average < 70.0)

cout « "Passing but marginal";

}

Page 53: C++ на македонски

54

else

cout « "Failing";

Овој програмски сегмент семантички е идентичен со претходниот.

Page 54: C++ на македонски

54

VI/ Циклус (Looping)

Во претходната глава наведовме дека текот на контрола во програмот може

да се разликува од физичкиот редослед на инструкциите. Физичкиот редослед е

редослед во кој инструкциите се јавуваат во програмот. Редоследот во кој ние

сакаме инструкциите да се извршуваат се нарекува логички редослед. If

инструкцијата е еден начин кој го прави логичкиот редослед различен од физичкиот

редослед. Контролната структура циклус е друг начин. Циклусот извршува една

или повеќе инструкции повеќе пати, се додека еден или група услови не се

исполнети.

Циклус (Loop) е контролна структура која предизвикува една или група

од инструкции да се извршуваат повторливо.

While инструкција

While инструкцијата како и If инструкцијата, тестира услов.

while ( Expression ) Statement

while (inputval != 25)

cin » inputval;

While инструкцијата е контролна структура циклус (looping). Инструкцијата што

треба да се изврши секој пат при поминувањето на циклусот се вика тело на

циклусот. Во горниот пример, тело на циклусот е влезната инструкција која чита

вредност за inputVal. While инструкцијата наложува да се извршува читањето на

inputVal повторно и повторно се додека влезната вредност не е еднаква на 25.

Page 55: C++ на македонски

55

Како и во условот на If инструкцијата, условот во While инструкцијата може

да биде израз од било кој тип на податок. Скоро секогаш условот е логички

(Boolean) израз. Ако не е, неговата вредност имплицитно се конвертира во тип bool

(вредност 0 false и ненулта вредност true). While инструкцијата кажува "Ако

вредноста на изразот е true, изврши го телото на циклусот и оди назад да го

провериш условот повторно. Ако вредноста на изразот е false скокни го телото и

изврши ја првата инструкција после циклусот."

Значи циклусот се извршува повторливо, се додека изразот кој се тестира е

true. Секако, ако изразот е false уште на влезот во циклусот, телото нема да се изврши

ниту еднаш. Сликата 6.1 го покажува текот на контролата на While инструкцијата, каде

Statementl е тело на циклусот и Statement2 е инструкција која следи после циклусот.

сл. 6.1

Телото на циклусот може да биде композитна инструкција (блок), што ни дозволува да

извршуваме било која група на инструкции повторливо. Најчесто ние користиме while

циклуси во следна форма:

while (Expression)

{

:

}

If се користи да избере акција меѓу повеќе опции, додека while се користи да повторува

одредена акција. Секој премин на контролата, низ телото на циклусот се вика

итерација. На следната сл.6.2 е прикажана разликата во текот на контролата за If-Then

и за While структурата.

Page 56: C++ на македонски

56

Сл. 6.2

Циклуси кои користат While инструкција

Во решавањето на проблеми, можеме да се сретнеме со два главни типови на

циклуси: циклуси контролирани од бројач, кои го извршуваат телото конкретен број

на пати, и циклуси контролирани од настан кои го повторуваат телото додека нешто

не се случи во циклусот.

Циклуси контролирани од бројач

Овие циклуси користат променлива која го контролира циклусот во тестот на

циклусот. Пред да влеземе во циклус контролиран од бројач, ние мора да ја

иницијализираме контролната променлива на бројачот и да ја тестираме. Потоа како

дел на секоја итерација на циклусот, мора да ја инкрементираме (зголемиме за 1)

контролната променлива на циклусот. На пример:

loopCount =1; // Inicijalizacija

while (loopCount <= 10) // Test

{

.

. // Akcii koi se povtoruvaat

loopCount = loopCount + 1; // Incrementacija

}

Page 57: C++ на македонски

57

Овде loopCount е контролна променлива на циклусот. Таа се иницијализира (поставува)

на 1 пред влезот во циклусот. While инструкцијата тестира израз

loopCount <= 10

и го извршува телото на циклусот се деодека изразот е true. Точките во телото на

циклусот означуваат низа од инструкции кои се треба да се повторуваат. Последната

инструкција во телото на циклусот е инкрементација која ја зголемува loopCount со

додавање 1 на нејзината претходна вредност.

Обично за инкрементација на променливата за 1, место последната инструкција во

блокот се користи познатиот оператор за инкремаентација ++, така да

loopCount = loopCount + 1;

обично се пишува како

loopCount++;

Последните две инструкции се еквивалентни и имаат исти ефект – го

зголемуваат loopCount за 1.

При дизајнирање на циклуси, програмерот треба да провери дали условот што треба да

се тестира е поставен коректно, пред While инструкцијата да почне. Програмерот исто

така треба да тестира дека условот се менува внатре во циклусот така да на крај

постане false, инаку лциклусот никогаш нема да заврши.

loopCount = 1; <—Променливата loopCount мора да биде иницијализирана.

while (loopCount <= 10)

{

loopCount++; <—loopCount мора да биде инкрементирана

}

Циклус кој никогаш не завршува се вика бесконечен (infinite) loop бидејќи теоретски

циклусот ќе трае вечно. Во горниот код, ако се испушти последната инструкција ќе се

добие бесконечен циклус затоа што loopCount е секогаш еден и While изразот е секогаш

true. Телото на горниот циклус ќе се изврши 10 пати. Ако сакаме телото да се изврши

11 пати, треба или да го иницијализираме бројачот на 0, или во While изразот 10 да го

замениме со 11.

Page 58: C++ на македонски

58

Циклуси контролирани од настан

Постојат неколку вида на циклуси контролирани од настан: sentinel-

контролирани, end-of-file-контролирани и flag-контролирани циклуси. Во сите од овие

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

циклусот се извршува.

Sentinel-контролирани циклуси

Циклусите често се користат да читаат и процесираат долги листи од податоци.

Секој пат кога телото на циклусот се извршува, нов податок се чита и процесира. Често

некоја специјална податочна вредност, наречена sentinel или trailer вредност, се

користи за да му сигнализира на програмот дека нема повеќе податоци за процесирање.

Циклусот се извршува се додека вредноста на податокот што се чита не е sentinel.

Циклусот завршува кога програмот препознае sentinel. Со други зборови, читањето на

sentinel вредност е настан кој го контролира извршувањето на циклусот.

Sentinel вредност мора да биде нешто што никогаш не се појавува во

нормалниот влез. На пример, ако програмот чита календарски датуми, February 31

може да се користи како sentinel вредност:

cin » month » day; // Procitaj podatok – Primarno citanje

while ( ! (month == 2 && day ==31) )

{

: // Procesiraj

cin » month » day; // Procitaj sleden podatok

}

Овој програмски сегмент работи коректно. Првата група податоци е вчитана и ако не е

sentinel, се процесира. На крајот на циклусот, следната група на податоци се вчитува и

одиме назад на следен тест. Ако новата група на податоци не е sentinel, се процесира

исто како и првата. Кога sentinel вредност е прочитана, While изразот станува false и

циклусот завршува (без да ја процесира sentinel вредноста).

Многу пати самиот проблем ја диктира sentinel вредноста. На пример, ако проблемот не

дозволува вредноста на податокот да биде 0, тогаш sentinel вредноста треба да биде 0.

Некогаш комбинација на вредности е невалидна. Комбинацијата February и 31 како

датум е таков случај. Некогаш рангот на вредности (на пример негативни броеви) е

sentinel. Кога се процесираат карактери, newline карактерот ( ' \n ' ) често служи како

sentinel. Следниот пример е програмски сегмент кој ги чита и ги печати сите карактери

во влезната линија (inChar е од тип char):

Page 59: C++ на македонски

59

cin.get (inChar) ; // Procitaj go prviot karakter

while (inChar != '\n')

{

cout « inChar; // Echo print

cin.get (inChar) ; // Procitaj go sledniot karakter

}

Кога бираме вредност што ќе ја користиме како sentinel, што се случува ако не постојат

невалидни податочни вредности? Тогаш можеме да внесуваме дополнителни вредности

во секоја итерација чија цел е да сигнализираат крај на податоците. На пример во

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

cin » dataValue » sentinel; // Vcitaj ja prvata vrednost

while (sentinel == 1)

{

: // Procesiraj

cin » dataValue » sentinel; // Vcitaj ja slednata vrednost

}

Втората вредност во секоја линија служи како sentinel и се користи да сигнализира дали

има уште податоци што треба да се вчитуваат. Во оваа група на податоци кога sentinel

вредноста е 0, нема повеќе податоци, а кога е 1 има уште податоци кои чекаат да бидат

прочитани.

Податоци Sentinel вредности

10 1

0 1

-5 1

8 1

-1 1

47 0

Во глава 5 зборувавме за честа грешка од користење на оператор на доделување (=)

наместо релационен оператор (==) во If услов. Истата оваа грешка може да се случи

при пишување на While инструкција. Да видиме што ќе се случи со претходниот

пример ако користиме погрешен оператор:

Page 60: C++ на македонски

60

cin » dataValue » sentinel;

while (sentinel =1) // Whoops

{

:

cin » dataValue » sentinel;

}

Оваа грешка креира бескраен циклус. While изразот сега е израз на доделување, а не

релационен израз. Вредноста на изразот е 1 (во тестот на циклусот интерпретиран како

true затоа што е ненулти), и неговиот страничен ефект е да стави 1 во sentinel,

заменувајќи ја вредноста на која беше сетирана променливата. Бидејќи While изразот е

секогаш true, цилусот никогаш не завршува.

End-of-File-контролирани циклуси

Видовме дека влезниот тек (cin или текот на влезна датотека) паѓа (оди во fail

состојба) ако:

(a) наиде на неприфатлив влезен податок,

(b) програмот се обиде да отвори непостоечка влезна датотека,

(c) програмот се обиде да чита преку маркерот end-of-file.

Овде го разгледуваме третиот од горните услови.

Откако програмот го прочитал последниот податок од влезната датотека, компјутерот е

кај крајот на датотеката (EOF). Во овој момент состојбата на текот е во ред. Но ако се

обидеме да читаме следен податок, текот паѓа (оди во fail state). За да напишеме циклус

кој внесува непознат број на податоци, можеме да го користиме паѓањето на влезниот

тек како форма на sentinel.

Во глава 5 беше опишано како да се тестира состојбата на I/O тек. Во логички израз го

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

if (inFile)

Во тест како овој резултатот е true ако последната извршена влезна операција успеала,

а false ако паднала. Во While инструкција, тестирање на состојба на текот работи на

истиот начин. Да претпоставиме дека имаме датотека која содржи integer вредности.

Ако во нашиот програм inData е името на датотечниот тек, следното е циклус кој чита и

ехо ги печати сите вредности во датотеката:

Page 61: C++ на македонски

61

inData » intval; // Читај ја првата вредност

while (inData) // Додека влезот е успешен

{

cout « intVal « endl; // Ехо-печати ја влезната вредност

inData » intval; // Читај ја следната вредност

}

Кога пишуваме EOF-контролирани циклуси како горниот, очекуваме дека крајот на

датотеката е причина за пад на текот. Но треба да имаме на ум дека било која влезна

грешка предизвикува пад на текот. Горниот циклус може да се прекине на пример ако

влезот падне поради невалидни карактери во влезните податоци. Овој факт ја

потенцира важноста на ехо излезот. Тој ни помага да верифицираме дека сите податоци

се вчитани коректно. EOF-контролираните циклуси се слични на sentinel-

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

да бидат прочитани. Во случај на sentinel-контролирани циклуси, програмот чита

додека не наиде на sentinel вредност. Со EOF-контролирани циклуси програмот чита

додека не наиде на крајот на датотеката. Се поставува прашање дали може да се

користи EOF-контролиран циклус кога читаме од стандарден влезен уред преку cin тек

наместо од датотека. Различни системи користат различни карактери да симулираат

EOF. Во UNIX оперативниот систем, овој карактер е Ctrl-D. Во MS-DOS оперативниот

систем, аналог на end-of-file е Ctrl-Z. Други системи користат слични контролни

карактери. Следното е програмски сегмент кој тестира EOF на cin тек во UNIX:

cout « "Vnesi integer (ili Ctrl-D za kraj): ";

cin » somelnt;

while (cin)

{

cout « somelnt « " dupliciran e " « 2 * somelnt « endl;

cout « "Sleden broj (ili Ctrl-D za kraj): ";

cin » somelnt;

}

Flag-контролирани циклуси

Flag ( знаме) е Boolean променлива која се користи да го контролира логичкиот

тек на програмот. Ние можеме да ја иницијализираме Boolean променливата на true

пред While циклусот, а потоа, кога сакаме да го прекинеме циклусот, ја ресетираме на

Page 62: C++ на македонски

62

false. На пример следниот код чита и сумира вредности додека влезната вредност е

позитивна:

sum = 0;

nonNegative = true; // Inicijaliziraj zname

while (nonNegative)

{

cin » number;

if (number < 0) // Testiraj vlezna vrednost

nonNegative = false; // Smeni ja vrednosta na znameto ako nastanot se pojavil

else

sum = sum + number;

}

Горниот код може да се напише и на алтернативен начин, кога знамето се

иницијализира на false а во тестот се користи операторот за негација !. На пример:

sum = 0;

negative = false; // Inicijaliziraj zname

while ( !negative )

{

cin » number;

if (number < 0) // Testiraj vlezna vrednost

negative = true; // Setiraj zname ako se pojavil nastan

else

sum = sum + number;

}

Телото на циклусот мора да изврши одредена задача за да циклусот биде корисен.

Следните три примери ја покашуваат примената на циклусите во броење, сумирање

и паметење на претходна вредност. Ова се задачи кои често се користат во циклуси.

Следниот програмски сегмент ги брои карактерите во влезниот тек до појавата на

карактерот ‘.’.

count = 0; // Inicijaliziraj brojac

cin.get(inChar); // Citaj go prviot karakter

while (inChar != ' . ')

{

count++; // Zgolemi go brojacot

cin.get(inChar); // Citaj go sledniot karakter

}

Page 63: C++ на македонски

63

Овој програмски сегмент е sentinel-контролиран каде како sentinel се користи

карактерот ‘.’. Променливата count во овој пример се нарекува итерациски бројач

бидејќи нејзината вредност е еднаква на бројот на итерации.

Итерациски бројач е променлива која се зголемува за 1 со секоја итерација.

Друга честа задача која се решава со циклуси е сумирање. Следниот пример ги

чита и собира првите 10 броеви од влезниот тек.

sum =0; // Inicijaliziraj ja sumata

count =1;

while (count <= 10)

{

cin » number; // Vnesi vrednost

sum = sum + number; // Dodaj ja vrednosta na sumata

count++;

}

Приметете дека ова е циклус контролиран од бројач бидејќи во програмот е јасно

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

тек.

Да погледнеме уште еден пример. Сакаме да ги броиме и собираме првите 10 непарни

броеви. Треба да го тестираме секој број за да видиме дали е парен или непарен. За ова

користиме modulus оператор. Ако number % 2 е 1, number е непарен; инаку е парен.

Ако влезната вредност е парен број, не превземаме акција, инаку додаваме 1 на

бројачот и ја додаваме вредноста во сумата. Овде користиме flag за контрола на

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

циклусот. Во програмскиот сегмент сите променливи се од тип int освен знамето (flag)

lessThanTen кој е од тип bool.

count =0; // Inicijaliziraj go brojacot na nastan

sum =0; // Inicijaliziraj ja sumata

lessThanTen = true; // Inicijaliziraj go znameto

while (lessThanTen)

{

cin » number; // Procitaj ja slednata vrednost

if (number % 2 == 1) // Dali e brojot neparen

{

count++; // Da — Zgolemi go brojacot

sum = sum + number; // Dodaj ja vrednosta na suma

Page 64: C++ на македонски

64

lessThanTen = (count <10) ; // Azuriraj go lessThanTen

}

}

Бројачот во овој пример е бројач на настани. Значи

Бројач на настани е променлива која се инкрементира секој пат кога се појави одреден настан.

Некогаш сакаме да ја памтиме претходната вредност на променливата. Да

претпоставиме дека сакаме нашиот програм да одреди колку пати е користен не-

еднакво операторот (! =) во датотека која содржи C++ програм. Ние можеме да го

направиме тоа со броење колку пати извичник (!) проследен со знак еднакво (=) се

појавува во датотеката. Еден начин да го направиме ова е да ја читаме влезната

датотека карактер по карактер, следејќи ги актуелниот и претходниот карактер.

Page 65: C++ на македонски

65

Горниот програм дефинира две променливи од тип char, една од тип int и

една од тип ifstream. Прво, со методот open програмот на влезниот тек inFile ја

доделува постоечката датотека “myfile.dat”. Ако “отварањето“ не успее,

програмот се прекинува со издавање на порака. Ако “отварањето“ успее, се

иницијализира бројачот и се врши иницијално читање на првите два карактера.

Циклусот е end-of-file контролиран. Бараниот настан се случува кога ќе се

појави комбинација од карактери ’!’ и ’=’. Тогаш бројачот се зголемува за 1.

Последните две инструкции во циклусот се:

- доделување на актуелниот карактер (currChar) на претходниот карактер

(prevChar) и

- читање на следен карактер.

Кога ќе биде достигнат EOF маркерот, циклусот излегува и се печати бројот на

појавувања на операторот содржан во променливата count.

Page 66: C++ на македонски

66

VII. Функции

Функционална декомпозиција и void функции

Да се потсетиме дека C++ јазикот работи со два вида на потпрограми

(функции):

- функции кои враќаат вредност (value returning) функции и

- функции кои извршуваат некоја задача и не враќаат вредност - void функции.

Програмата кој ја повикува функцијата (повикувач) го користи името на value

returning функцијата и листата на аргументи во израз. На пример:

y = 3.8 * sqrt(x);

Спротивно, void функцијата не враќа вредност на својот повикувач. Ниту таа се

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

Пишување на модули како void функции

Разгледуваме програма која користи наједноставна void функција. Сакаме да

напишеме програма која ќе ја прикажува следнава порака на мониторот:

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

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

Ova e proba na void funkcija!

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

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

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

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

Алгоритамот изгледа како што следи:

Main степен 0

Впиши две линии на ѕвездички Впиши “Ova e proba na void funkcija!” Впиши четири линии на ѕвездички

Print 2 lines степен 1

Впиши “************************” Впиши “************************”

Print 4 lines

Впиши “************************” Впиши “************************” Впиши “************************” Впиши “************************”

Page 67: C++ на македонски

67

Ако ги напишеме двата модула кои се на прв степен од модуларната програма

како void функции, тогаш нашата програма е како што следува:

//************************************************************************

// Test programа

// Ovаа programа koristi dve void funkcii i pecati “Ova e proba na void funkcija!”

//************************************************************************

#include <iostream>

using namespace std;

void Print2Lines();

void Print4Lines(); // Prototipovi (deklaracii) na funkcii

int main()

{

Print2Lines(); // Povik na funkcija

cout << “Ova e proba na void funkcija!” << endl;

Print4Lines(); // Povik na funkcija

return 0;

}

//************************************************************************

void Print2Lines() // Function heading

// Ovaa funkcija pecati dve linii od zvezdicki

{

cout << “************************” << endl;

cout << “************************” << endl;

}

//************************************************************************

void Print4Lines() // Function heading

// Ovaa funkcija pecati dve linii od zvezdicki

{

cout << “************************” << endl;

cout << “************************” << endl;

cout << “************************” << endl;

cout << “************************” << endl;

}

Во тест програмата, двете инструкции пред main функцијата се нарекуваат

функциски прототипови. Овие инструкции се неопходни, бидејќи правилото во C++ е

Page 68: C++ на македонски

68

прво да се декларираат идентификаторите пред да се користат. Значи, ние мора да го

информираме компајлерот однапред дека Print2Lines и Print4Lines се имиња на

функции кои не враќаат вредност и немаат аргументи. Оваа програма може да се

напише и со само една функција, која како аргумент ќе го користи бројот на линии кои

треба да се печатат. Еве ја таа верзија:

//************************************************************************

// Test_1 programа

// Ovаа programа koristi една void funkcија i pecati “Ova e proba na void funkcija!”

//************************************************************************

#include <iostream>

using namespace std;

void PrintLines(int); // Prototip na funkcija

int main()

{

PrintLines(2); // Povik na funkcija

cout << “Ova e proba na void funkcija!” << endl;

PrintLines(4); // Povik na funkcija

return 0;

}

//************************************************************************

void PrintLines(int numLines) // Function heading

// Ovaa funkcija pecati linii od zvezdicki, pri sto

// numLines specificira kolku linii da se pecatat

{

int brojac; // Promenliva za loop

brojac = 1;

while (brojac <= numLines)

{

cout << “************************” << endl;

brojac++;

}

}

Page 69: C++ на македонски

69

Во функцискиот heading на PrintLines е вклучена параметарска декларација.

Како што беше наведено претходно, две функции комуницираат меѓусебе преку

аргументи.

Параметар е променлива декларирана во функцискиот heading. Исто така, се

нарекува формален аргумент или формален параметар.

Аргумент е променлива или израз кој се користи во повикот на функција. Исто

така, се нарекува актуелен аргумент или актуелен параметар.

Во Test_1 програмата аргументот во првиот повик на функцијата PrintLines е

литералната вредност 2, а во вториот повик литералната вредност 4. Параметарот во

PrintLines функцијата е променливата numLines. Кога контролата на програмата

преминува на функцијата PrintLines, параметарот numLines се иницијализира на

вредност 2. Во PrintLines циклусот контролиран од бројач се извршува двапати и

контролата се враќа на main. При вториот повик, аргументот е 4 и циклусот во

PrintLines се извршува четири пати по што контролата се враќа во main функцијата.

Од ова доаѓаме до заклучок дека предноста од користење на функции е во тоа

што тие можат да се извршуваат од различни места на main. Ова заштедува напор и

време во кодирањето.

Да ја разгледаме функцијата main. Веднаш е очигледна функционалната

декомпозиција и во двете верзии. Дури и на некој кој не знае која е дефиницијата на

PrintLines, разгледувајќи ја само main ќе му биде јасно дека програмата печати 2 линии

па печати порака и печати 4 линии.

Синтакса и семантика на void функциите

Моделот (template) за повикување на void функција е следниов:

Име на функција (Листа на аргументи);

Листата на аргументи може да биде празна ( ) или да има еден или повеќе аргументи.

Повикот на void функција е инструкција која ја префрла контролата на void

функцијата. Во C++ оваа инструкција е името на функцијата, следено од листа на

аргументи.

Ако има два или повеќе аргументи во листата, тие треба да бидат разделени со

запирки. Кога се извршува повикот на функцијата, аргументите се доделуваат на

параметрите во согласност со нивната позиција и контролата се префрла на првата

извршна инструкција во телото на функцијата. Кога последната инструкција во

функцијата е извршена, контролата се враќа на местото од каде функцијата беше

повикана.

Page 70: C++ на македонски

70

Декларации и дефиниции на функции

Декларацијата на функцијата му го соопштува на компајлерот: името на

функцијата, типот на податокот на вредноста која функцијата ја враќа (или void или тип

на податок како int или float) и типовите на податоците на параметрите кои функцијата

ги користи.

Во Test_1 програмата се прикажани вкупно три декларации на функции. Првата

декларација, коментирана како прототип на функција, не го вклучува телото на

функцијата. Останатите две декларации за main и PrintLines функциите, вклучуваат

тела на функциите. Функциската декларација во која не е вклучено телото на

функцијата се нарекува функциски прототип, а декларациите кои вклучуваат и тело

на функција се нарекува функциска дефиниција.

Функциски прототип (за void функција)

Шаблонот (template) за прототип на void функција е следниов:

void Име на функција (листа на параметри);

Сè што е bold во шаблонот е обврзувачко да се наведе во прототипот, а сè што е

во Italic е опционо (необврзувачко). Значи листата на параметри може да биде празна,

но заградите се обврзувачки. Како што може да се види од шаблонот, во прототипот

нема вклучено тело (body) на функцијата и декларацијата завршува со точка-запирка

карактер. Ако во листата има аргументи, шаблонот за листата на аргументи е:

тип на податок & име на променлива , тип на податок& име на променлива …

Амперсанд карактерот (&) прикачен на типот на податок, означува дека

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

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

повикувачот. За ова ќе зборуваме подоцна. Во прототипот на функцијата, листата на

параметри мора да ги специфицира типовите на податоците на параметрите, а нивните

имиња се опциони. На пример:

Void DoSomething (int, float);

или

Void DoSomething (int dolzina, float agol);

И двата прототипа се исправни C++ инструкции. Во втората варијанта

компајлерот ги игнорира имињата на променливите.

Функциска дефиниција (за void функција)

Шаблонот (template) за дефиниција на void функција е следниов:

Page 71: C++ на македонски

71

Void име на функција (листа на параметри)

{

instrukcija

.

.

.

}

Од шаблонот се гледа дека функцискиот heading не завршува со карактерот;

како што тоа е случај со функцискиот прототип. Исто така, листата на параметри мора

да ги вклучи имињата на параметрите покрај нивните типови. Параметрите во листата

може да бидат напишани во една линија или секој параметар во посебна линија.

Следното е template за листа на параметри во функциската дефинција:

тип на податок & име на променлива,

тип на податок & име на променлива,

.

.

Локални променливи

Локална променлива е променлива декларирана во блок на која не може да &

се пристапи од место надвор од блокот. Затоа што телото на која било функција е блок,

која било функција може да вклучи декларација на променлива во своето тело. Овие

променливи се локални променливи, бидејќи на нив може да им се пристапи само

внатре во блокот. На пример, ако пробаме да печатиме вредност на локална

променлива од друга функција, компајлерот ќе пријави грешка. За разлика од

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

нарекуваат глобални променливи.

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

се извршува. Во моментот кога се повикува функцијата, се креира мемориски простор

за нејзините локални променливи. Кога функцијата завршува и контролата се враќа

назад на повикувачот локалните променливи се уништуваат. Значи при секој повик на

функцијата, нејзините локални променливи стартуваат со недефинирани вредности. За

да се заштитиме од несакани резултати, мора да ги иницијализираме локалните

променливи внатре во самата функција.

Следниот програмски сегмент илустрира функциска декларација и декларација

на локални променливи:

Page 72: C++ на македонски

72

#include <iostream>

using namespace std;

void TryThis( int, int, float); // Funkciski prototip

int main( ) // Funkciska definicija

{

int int1; // Lokalni promenlivi za main

int int2;

float someFloat;

.

.

TruThis (int1, int2, someFloat); // Povik na funkcija so tri argumenti

.

.

}

void TryThis ( int param1, // Funkciska definicija so tri parametri

int param2,

float param3)

{

int i; // Lokalni promenlivi za TryThis

float x;

.

.

.

}

Return инструкција

Функциите кои враќаат вредност на повикувачот, враќањето на вредност го

вршат преку return инструкцијата. Така main враќа вредност 0 или некоја друга

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

Void функциите не враќаат вредност. Контролата се враќа од функцијата на

повикувачот по извршување на последната инструкција во телото на функцијата.

Алтернативно, постои втора форма на return инструкцијата и таа изгледа вака:

return;

Page 73: C++ на македонски

73

Оваа инструкција е валидна само за void функциите. Може да се појави каде

било во телото на функцијата и предизвикува контролата веднаш да ја напушти

функцијата и да се врати на нејзиниот повикувач. На пример:

Void SomeFunc ( int n )

{

if (n > 50)

{

cout << “Vrednosta e nadvor od rangot”;

return;

}

n = 412 * n;

cout << n;

}

Во овој пример има два начина контролата да ја напушти функцијата. Прво,

вредноста на n се тестира и ако n > 50, функцијата печати порака и веднаш ја напушта

функцијата. Ако n е помало или еднакво на 50, then клаузулата на if инструкцијата се

прескокнува, се извршува инструкцијата на доделување и последната инструкција,

излез на монитор, по што контролата ја напушта функцијата и се враќа на нејзиниот

повикувач (caller).

Друг начин на кодирање на оваа функција е If – Then – Else структурата:

Void SomeFunc ( int n )

{

if (n > 50)

cout << “Vrednosta e nadvor od rangot”;

else

{

n = 412 * n;

cout << n;

}

}

Оваа верзија е single-entry, single-exit пристап, што значи контролата влегува во

функцијата само во една точка (првата извршна инструкција) и излегува само од една

точка (крајот на телото на функцијата).

Page 74: C++ на македонски

74

Параметри

C++ поддржува два вида на параметри: вредносни параметри и референтни

параметри. Со вредносен параметар, кој се декларира без амперсанд (&) на крајот од

името на типот на податок, функцијата прима копија од вредноста на аргументот. Со

референтен параметар, кој се декларира со додавање на амперсанд на името на типот на

податок, функцијата прима локација (мемориска адреса) на аргументот од повикувачот.

На пример:

Void Primer(int& param1, // Referenten parametar

int param2, // Vrednosen parametar

float param3) // Vrednosen parametar

Кога се користат едноставни типови на податоци како: int, char, float итн.,

вредносен параметар е default (претпоставен) вид на параметар.

Вредносни параметри

Во Тest_1 програмата, функцискиот heading на PrintLines функцијата е

void PrintLines( int numLines ).

Параметарот numLines е вредносен параметар, затоа што името на неговиот тип

на податок (int) не завршува со &. Ако функцијата се повикува со користење на

аргумент lineCount,

PrintLines(lineCount);

тогаш параметарот numLines prima kopija od vrednosta na lineCount. Во овој момент има

две копии на податокот, една во аргументот lineCount и една во параметарот numLines.

Ако инструкција внатре во функцијата PrintLines ја смени вредноста на

параметарот numLines, оваа промена не се пренесува на аргументот lineCount (постојат

две копии на податокот). Значи, со користење на вредносни параметри се исклучува

можноста од ненамерна промена на аргументот. Како вредносни аргументи можат да се

појават константи, променливи, па дури и комплицирани изрази. За PrintLines

функцијата сите следни повици се валидни:

PrintLines(3);

PrintLines(lineCount);

PrintLines(2*abs(10-someInt));

Мора да има ист број на аргументи во повикот на функцијата, колку што има

параметри во функцискиот heading. Исто така, секој аргумент треба да има ист тип на

податок како параметарот на истата позиција.

Page 75: C++ на македонски

75

Видовме дека вредносните параметри не ја менуваат вредноста на аргументот

во повикувачот. Исто како и локалните променливи, вредносните параметри се

уништуваат кога функцијата ја враќа контролата.

Ако сакаме да ја зачуваме и вратиме новата вредност на аргументот добиена во

функцијата, наместо вредносни ние треба да употребуваме референцни параметри.

Референцни параметри

Референцен параметар е параметар кој се декларира со додавање на амперсанд

& на името на типот на податок. Во овој случај, функцијата може да ја смени вредноста

на аргументот. Во овој случај при повик на функцијата, таа прима мемориска адреса на

аргументот, а не копија од неговата вредност. Има само една копија од информацијата

и таа се користи и од повикувачот и од повиканата функција. Да погледнеме еден

пример. Алгоритамот (дизајнот) е:

main ниво 0

Добивање податок за температура

Печатење на активноста

Get temperature

Промптирање на корисникот да ја внесе температурата ниво 1

Читање на температурата

Echo print на температурата

Print activity ниво 1

Печати „Препорачана активност е “

if temperature > 30

print “plivanje.“

else if temperature > 15

print “tenis.“

else if temperature > 0

print “golf.“

else if temperature > -15

print “skijanje.“

else

print “sedenje doma“

Page 76: C++ на македонски

76

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

//***********************************************************

// Activity program

// Ovаа programа ja prikazuva soodvetnata aktivnost za dadena temperatura

//***********************************************************

#include <iostream>

using namespace std;

void GetTemp( int& ); // funkciski prototipovi

void PrintActivity ( int );

int main ( )

{

int temperature; // nadvoresna temperaturа

GetTemp (temperature); // povik na funkcija

PrintActivity (temperature); // povik na funkcija

return 0;

}

//***********************************************************

void GetTemp ( int& temp ) // referencen parameter

// Ovaa funkcija promptira (prasuva) za vnos na temperature,

// cita vnos vo temperature i echo prints temperature

{

cout << “Vnesi nadvoresna temperature:” << endl;

cin >> temp;

cout << “Momentalnata temperature e “ << temp << endl;

}

//***********************************************************

void PrintActivity ( int temp ) // vrednosen parametar

// Za dadena vrednosta na temp, ovaa funkcija pecati poraka

// koja indicira soodvetna aktivnost

{

cout << “Preporacana aktivnost e “;

Page 77: C++ на македонски

77

if (temp > 30)

cout << “plivanje.” << endl;

else if (temp > 15)

cout << “tenis.” << endl;

else if (temp > 0)

cout << “golf.” << endl;

else if (temp > -15)

cout << “skijanje.” << endl;

else

cout << “sedenje doma.” << endl;

}

Во оваа програма аргументите во двата функциски повика имаат име

temperature. Параметарот во GetTemp е референцен параметар со име temp, а

параметарот во PrintActivity е вредносен параметар, исто така, со име temp.

Главната функција, main, & кажува на GetTemp каде да ја стави

температурата со давање (пренесување) на мемориската локација на

променливата temperature при повикот на функцијата. Овде мора да користиме

референцен параметар, за да функцијата GetTemp знае каде во меморијата да ја

постави вредноста на температурата.

Во PrintActivity имаме вредносен параметар. Кога PrintActivity се

повикува, main праќа копија од вредноста на променливата temperature со која

PrintActivity работи. Во овој случај е оправдано да се користи вредносен

параметар, затоа што логиката на PrintActivity е само да ја користи како влез, а

не ja менува вредноста на temperature.

Page 78: C++ на македонски

78

VIII. Домен и животен век на функции

Како програмата станува поголема и покомлицирана, се зголемува бројот на

идентификаторите во програмата. Некои од овие идентификатори ги декларираме во

блоковите. Други идентификатори, на пример имиња на функции, ги декларираме

надвор од кој било блок. Во оваа глава ги испитуваме правилата по кои функцијата

може да пристапи на идентификатори кои се декларирани надвор од нејзиниот блок.

Домен на идентификаторите

Како што видовме во претходната глава, локални променливи се оние кoи ce

декларирани во блок, како на пример во тело на функција. На локалните променливи не

може да им се пристапи од локација надвор од блокот што ги содржи. Истото правило

на пристап се однесува и на именуваните константи. На локалните константи може да

им се пристапи само во блокот во кој се декларирани. Кој било блок, не само телото на

функција, може да содржи декларации на променливи и константи. Во следниот

пример, if инструкцијата содржи блок кој декларира локална променлива n.

if (alpha > 3)

{

int n;

cin >> n;

beta = beta + n;

}

Како со која било локална променлива, на променливата n не може да & се

пристапи од која било инструкција надвор од блокот кој ја содржи декларацијата на

променливата.

Домен е регионот од програмскиот код каде е легално да се користи

идентификаторот.

Доменот на идентификаторот може да биде локален и глобален.

Локалниот домен на идентификаторот деклариран во блок се протега од

местото на декларација до крајот на тој блок. Глобалниот домен на идентификаторот

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

крајот на целата датотека која го содржи програмскиот код. C++ функцијата има

глобален домен. Штом името на функцијата е декларирано, функцијата може да биде

повикана од која било друга функција во програмата. Во C++ не постои локална

функција т.е. не смее да се вгнезди дефиниција на функција во друга функција.

Page 79: C++ на македонски

79

Глобални променливи и константи се оние декларирани надвор од сите

функции. На пример:

int gama; // Globalna promenliva

int main ( )

{

gama = 3;

.

.

}

void SomeFunc ( )

{

gama = 5;

.

.

}

Во горниот програмски сегмент, на глобалната променлива gama може да & се

пристапи директно со инструкции во main и SomeFunc. Кога функцијата декларира

локален идентификатор со исто име како и глобалниот идентификатор, локалниот

идентификатор има предност во функцијата. На пример:

#include <iostream>

using namespace std;

void SomeFunc( float );

const int a = 17; // Globalna konstanta

int b; // Globalna promenliva

int c; // Globalna promenliva

int main ( )

{

b = 4; // Dodeluvanje vrednost na globalna b

c = 6; // Dodeluvanje vrednost na globalna c

SomeFunc(42.8);

return 0;

}

void SomeFunc( float c )

{

float b;

Page 80: C++ на македонски

80

b = 2.3; // Dodeluvanje vrednost na lokalna promenliva b

cout << “a = “ << a; // Pecati globalno a (17)

cout << “ b = “ << b; // Pecati lokalno b (2.3)

cout << “ c = “ << c; // Pecati lokalno c – formalen parameter (42.8)

Во овој пример, функцијата SomeFunc & пристапува на глобалната константа a,

но декларира своја локална променлива b и параметар c. Излезот ќе биде:

a = 17 b = 2.3 c = 42.8

Локалната променлива b има предност во однос на глобалната променлива b и

параметарот c има предност во однос на глобалната променлива c.

Освен локалниот и глобалниот пристап, C++ дефинира што се случува кога

блоковите се вгнездени во други блокови. Сè што е декларирано во блок кој содржи

вгнезден блок е нелокално за внатрешниот блок. Така глобалните идентификатори се

нелокални за сите блокови во програмата. Нелокален идентификатор во однос на

даден блок е идентификатор деклариран надвор од тој блок.

Правила на домен

Правилата на домен на идентификатори се:

1. Името на функција има глобален домен. Функциските дефиниции не можат да

бидат вгнездени во функциски дефиниции.

2. Доменот на функциски параметар е идентичен со доменот на локална

променлива декларирана во најнадворешниот блок на телото на функцијата.

3. Доменот на глобална променлива или константа се протега од нејзината

декларација до крајот на датотеката, освен ако не настане правилото 5.

4. Доменот на локална променлива или константа се протега од нејзината

декларација до крајот на блокот во кој е декларирана. Овој домен вклучува

вгнездени блокови, освен ако не настане правилото 5.

5. Доменот на идентификаторот не вклучува вгнезден блок кој содржи локално

деклариран идентификатор со исто име. Локалните идентификатори со исто

име имаат предност.

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

#include <iostream>

using namespace std;

void Block1( int, char& );

Page 81: C++ на македонски

81

void Block2( );

int a1; // Globalna promenliva

char a2; // Druga globalna promenliva

int main( )

{

.

.

}

// **********************************************************************

void Block1( int a1, // Sprecuva pristap na globalnata a1

char& b2 ); // Ima ist domen kako c1 i d2

{

int c1; // Lokalna promenliva za Block1

int d2; // Druga lokalna promenliva za Block1

}

// **********************************************************************

void Block2( )

{

int a1; // Go sprecuva pristapot kon globalnata a1

int b2; // Lokalna promenliva za Block2; nema konflikt

// so b2 vo Block1

while (…)

{ // Block3

int c1; // Lokalna promenliva za Block3; nema konflikt so

// c1 vo Block1

int b2; // Go sprecuva nelokalniot pristap kon b2 vo Block2

// Nema konflikt so b2 vo Block1

.

.

}

}

Page 82: C++ на македонски

82

Животен век на променлива

Животниот век на идентификатор е временскиот период во текот на

извршување на програмата за која на идентификаторот му е доделен мемориски

простор.

Локалната променлива добива мемориски простор во моментот кога контролата

влегува во функцијата. Додека функцијата се извршува, локалните променливи се

„живи“. Кога функцијата завршува, на локалната променлива & се одзема

меморискиот простор. Во случај на глобални променливи, меморискиот простор се

доделува само еднаш (кога програмата почнува да се извршува) и се одзема само еднаш

(кога целиата програма завршува).

Во C++ автоматска променлива е онаа на која & се доделува (allocate)

мемориски простор при влез во блокот, а и се уништува (deallocate) меморискиот

простор при излез од блокот. Статичка променлива е онаа чиј мемориски простор

останува доделен сè додека трае програмата. За да декларираме статичка променлива,

го користиме резервираниот збор static, како што е наведено во примерот:

void SomeFunc( )

{

float SomeFloat; // Se unistuva koga izvr[uvaweto na funkcijata zavrsuva

static int someInt; // Ja zadrzuva vrednosta pri sekoj povik na funkcijata

.

.

}

Обично е подобро да се декларира локална статичка променлива, отколку да се

користи глобална променлива. Како и кај глобалната променлива, меморијата на

статичката променлива е доделена за целото време на извршување на програмата. За

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

неа не може да & се пристапува од друг блок.

Иницијализација во декларација

Обично во нашите програми, прво ја декларираме променливата, а потоа во

посебна инструкција & доделуваме иницијална вредност. C++ дозволува декларацијата

и иницијализацијата да се вршат само со една инструкција. Иницијализацијата

извршена на овој начин се нарекува иницијализација во декларација. На пример:

Page 83: C++ на македонски

83

int sum = 0;

Автоматска локална променлива се иницијализира со конкретна вредност

секојпат кога контролата влезе во блокот каде што е променливата. На пример:

Void SomeFunc( int someParam )

{

int i = 0; // Se inicijalizira sekojpat pri vlez na kontrolata

int n = 2 * someParam+3 // Se inicijalizira sekojpat pri vlez na kontrolata

.

.

}

Обратно, иницијализацијата на статичка променлива, без разлика дали е

глобална или локална, експлицитно декларирана со резервираниот збор static, се

случува само еднаш и тоа во моментот кога контролата првпат ја извршува

декларацијата. Следното е пример во кој две локални статички променливи се

иницијализираат само еднаш (првиот пат кога е повикана функцијата).

Void AnotherFunc( int param )

{

static char ch = ‘A’; // Se inizijalizira samo pri prviot vlez

static int m = param + 1; // Se inizijalizira samo pri prviot vlez

.

.

}

Иако иницијализацијата & дава на променливата почетна вредност,

променливата може да се менува во текот на извршување на програмата.

Дизајнирање на врската (interface) меѓу две функции и странични ефекти

Протокот на податоци низ интерфејсот на две функции (кога една функција

повикува друга функција) може да се јави во три форми и тоа:

- влез на податоци во функцијата која се повикува;

- излез на податоци од функцијата која се повикува;

- влезно-излезни податоци во (од) функцијата која се повикува.

Секој податок што е влезен треба да биде кодиран како вредносен параметар.

Податоците во другите две категории (излезни и влезно-излезни) мора да бидат

Page 84: C++ на македонски

84

кодирани како референцни параметри. Единствениот начин повиканата функција да ги

депонира резултатите во функцијата-повикувач е да ги има адресите на аргументите.

Илустративно, начинот на комуникација на аргументите е прикажан во следнава

табела:

Проток на податок за параметарот на

повиканата функција

Механизам на проследување на аргументи

Влезен (податок кој влегува) По вредност

Излезен (податок кој излегува) По референца

Влезно-излезен (податок кој влегува во

функцијата, се процесира и излегува)

По референца

Влезно-излезните објекти кои управуваат со текот на податоци (датотеки, cin,

cout) мора да бидат проследени по референца.

Страничен ефект е кој било ефект од една функција врз друга, кој не е дел од

експлицитно дефинираниот интерфејс меѓу двете функции.

Страничните ефекти некогаш се предизвикани од комбинација на референцни

параметри и невнимателно кодирање во функцијата.

На пример, инструкцијата за доделување доделува привремен резултат во некој

од референцните параметри со што ја менува вредноста на аргументот и во функцијата-

повикувач. За да се заштитиме од вакви несакани ефекти, влезниот параметар треба да

прима аргументи проследени по вредност, а не по референца.

Страничен ефект може да се појави и кога функцијата пристапува на глобална

променлива. Грешка во функцијата може да предизвика вредноста на глобалната

променлива да биде променета на неочекуван начин, предизвикувајќи грешки и во

други функции кои & пристапуваат на глобалната променлива. Симптомите од грешка

поради страничен ефект (side effect) обично водат во погрешен правец, бидејќи

грешката се појавува во еден дел од програмата, а фактички е предизвикана во друг

дел.

За да се избегнат вакви грешки, единствениот надворешен ефект што

функцијата треба да го има е да ја пренесе информацијата преку добро структуиран

интерфејс на листа на параметри (сл. 8.1). Ако функциите пристапуваат на нелокални

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

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

делови на програмата и не можат да се појават штетни странични ефекти.

Page 85: C++ на македонски

85

main i drugi f unkci i

t i pi ~na f unkci ja

Li sta na parametr iKomuni kaci ski

i nter f ejs

strani ~ni ({ tetni )ef ekt i

sl . 8.1

За разлика од глобалните променливи кои не се препорачуваат поради

страничните ефекти, се препорачува именуваните константи да се декларираат

глобално. Предноста е во тоа што при одржување на програмата, вредноста на

Page 86: C++ на македонски

86

константата се менува само на едно место. Од друга страна, константите како податоци

што не се менуваат, не можат да предизвикаат штетни странични ефекти.

Функции кои враќаат вредност (Value-Returning Functions)

Претходно се сретнавме со функции кои враќаат вредност. Такви функции се

функциите од стандардната библиотека на C++: sqrt, abs, fabs и други. Од перспектива

на функцијата-повикувач, главната разлика меѓу функцијата која враќа вредност и void

функцијата е начинот на кој тие се повикуваат. Повикот на void функција е комплетна

инструкција, додека повикот на функција која враќа вредност е дел од израз. Од

перспектива на дизајн, функциите кои враќаат вредност се користат кога треба да се

врати само еден резултат од функцијата.

Како пример да погледнеме функција која за даден датум го пресметува и враќа

редниот број на денот во годината во ранг 1-365 за прост година или 1-366 за престапна

година.

int Day( /* in */ int month, // Broj na mesecot 1-12

/* in */ int dayOfMonth // Den vo mesecot 1-31

/* in */ int year ) // Godina na pr. 2007

{

int correction = 0; // korekcionen faktor za prestapna godina i za

// meseci so razlicna dolzina

// Test za prestapna godina

if (year % 4 == 0 && (year % 100 != 0 | | year % 400 == 0)

if (month >= 3) // Ako datumot e po 29 fevruari

correction = 1; // dodaj eden za prestapna godina

// Korekcija za meseci so razlicna dolzina

if (month == 3)

correction = correction – 1;

else if (month == 2 | | month == 6 | | month == 7)

correction = correction + 1;

else if (month == 8)

correction = correction + 2;

else if (month == 9 | | month == 10)

Page 87: C++ на македонски

87

correction = correction + 3;

else if (month == 11 | | month == 12)

correction = correction + 4;

return (month – 1) * 30 + correction + dayOFMonth;

}

Првата работа која може да се забележи е дека дефиницијата на функцијата

личи на дефиниција на void функција, освен што насловот (heading) започнува со тип

на податок (во овој случај int) наместо зборот void. Втора работа по која се разликуваат

овие два вида на функции е return инструкцијата, која е присутна во секоја функција

која враќа вредност. Во овој случај return инструкцијата на крајот од функцијата ја

враќа вредноста на integer изразот кој следува по клучниот збор return. Функцијата која

враќа вредност, вредноста ја враќа не преку параметар, туку преку инструкцијата return.

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

нарекува тип на функцијата и тој го претставува типот на податок на резултатот

вратен од функцијата.

Шаблонот (template) за функција која враќа вредност е:

Тип на податок - Име на функција ( Листа на параметри )

{

.

.

.

}

Како што се гледа од шаблонот, типот на податок е опционен (необврзувачки) и

ако е испуштен во насловот на дефиницијата, по default се усвојува int. Лоша

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

дефинира, исто како и кај void функциите. Параметрите, ако постојат, се раздвојуваат

со запирки. Заградите се задолжителни дури и ако нема параметри. Еве неколку

примери на функции кои враќаат вредност. Следната функција пресметува и враќа nx

каде x и n се целобројни вредности.

int Power ( /* in */ int x, // Osnova na stepen

/* in */ int n) // Eksponent

// Ovaa funkcija presmetuva x na stepen n.

Page 88: C++ на македонски

88

// Preduslov: n >= 0 && ( x na n ) <= INT_MAX

// Rezultat: Vrednosta na funkcijata == x na n

{

int result; // Cuva rezultat

result = 1;

while (n > 0)

{

result = result * x;

n--;

}

return result;

}

Следната функција пресметува факториел. По дефиниција n...21!n ⋅⋅⋅= и 0! = 1

Int Factorial( /* in */ int n ) // Broj cij faktoriel sakame da go presmetame

// Ovaa funkcija presmetuva n!

// Preduslov: n >=0 && n! <= INT_MAX

// Rezultat: Vrednost na funkcijata == n!

{

int result; // Cuva proizvodi

result = 1;

while (n > 0)

{

result = result * n;

n--;

}

return result;

}

Повикот на функцијата Factorial може да биде следниов:

combinations = Factorial (n) / (Factorial (m) * Factorial (n-m));

Page 89: C++ на македонски

89

Дизајнирање на врската (interface) и странични ефекти

Бидејќи функциите кои враќаат вредност враќаат само една вредност, постои

само еден податок кој е излезен. Сите други податоци од интерфејсот се влезни.

Враќање на повеќе од еден податок од функцијата со модификување на листата на

параметри е страничен ефект и не е препорачливо да се прави. Ако функцијата треба да

враќа повеќе од една вредност, тогаш треба да се користи void функција.

Исклучок од оваа констатација е кога во функцијата треба да се предаде тек

(stream) за да функцијата го тестира неговиот статус. C++ дозволува тек (од датотека

или од тастатура) да се пренесе само како референцен аргумент.

Се поставува прашање кога да се користи функција која враќа вредност, а кога

void функција. Еве неколку препораки:

1. ако модулот треба да врати повеќе од една вредност или треба да модификува

некој од аргументите на функцијата повикувач (caller) користите void функција;

2. ако модулот мора да изврши I/O не користете функција која враќа вредност;

3. ако модулот треба да врати само една вредност и таа вредност е од тип bool,

функција која враќа вредност е соодветна;

4. ако модулот треба да врати само една вредност и таа вредност треба да биде

веднаш користена во израз, функција која враќа вредност е соодветна;

5. кога не сте сигурни кој вид функција да употребите, користете void функцијa;.

6. ако и void функцијатa и функцијатa која враќа вредност се прифатливи,

користете ја онаа со која се чувствувате посигурни.

Page 90: C++ на македонски

90

Page 91: C++ на македонски

90

IX/ Дополнитени контролни структури

Во претходните глави ги разгледавме C++ инструкциите за секвенца, селекција,

циклус и подпрограми – функции. Во некои случаи разгледавме повеќе варијанти за

имплементирање на овие структури. На пример селекцијата може да биде

имплементирана со повеќе варијанти на If структурата, If-Then, If-Then-Else, If-Then-

Else If.

Во оваа глава воведуваме пет нови инструкции кои се згодни за програмирање.

Switch Инструкција

Switch инструкцијата е контролна структура селекција која ни овозможува да

листаме поголем број на разгранувања на текот на контролата. Switch е сличен со

вгнездената If инструкција. Вредноста на switch изразот—израз чија вредност се

споредува со лабелите прикачени на гранките, определува која гранка ќе се извршува.

На пример:

Во овој пример, letter е switch изразот. Инструкцијата може да се чита "Ако letter е 'X',

изврши ја Statementl и излези од Switch инструкцијата, продолжувајќи со Statement5.

Ако letter е 'L' или 'M', изврши ја Statement2 и продолжи со Statement5. Ако letter е ’S',

изврши ја инструкцијата Statement3 и продолжи со Statement5. Ако letter не е ниеден од

споменатите карактери, изврши ја Statement4 и продолжи со Statement5." Break

инструкцијата предизвикува моментален излез од Switch инструкцијата. Понатаму ќе

видиме што се случува ако ја изоставиме Break инструкцијата. Switch изразот е израз

чија вредност определува која switch лабела е селектиран. Switch изразот не може да

биде floating-point или string израз.

Page 92: C++ на македонски

91

Синтактичкиот шаблон за Switch изразот е:

IntegralOrEnumExpression е израз од integral тип - char, short, int, long, bool или од enum

тип (за enum тип ќе зборуваме во следната глава). Опционата SwitchLabel пред

инструкција (Statement) е или case лабела или default лабела:

Во case лабелата, ConstantExpression е integral или enum израз чии операнди мора да

бидат литерали или именувани константи. Следното се примери на константни integral

изрази (каде CLASS_SIZE е именувана константа од тип int):

Типот на податок на ConstantExpression ако е потребно имплицитно се конвертира

(коерцира), за да биде ист со типот на податок на switch изразот. Во нашиот воведен

пример кој ја тестира вредноста на letter, следниве се case лабели:

case 'X' :

case 'L' :

Page 93: C++ на македонски

92

case 'M’ :

case 'S' :

Како што покажува примерот, на една инструкција може да и претходат повеќе лабели.

Секоја case вредност може да се појави само еднаш во дадена Switch инструкција. Исто

така може да има само една default лабела во Switch инструкцијата.

Текот на контролата во Switch инструкцијата оди на следен начин. Прво се

евалуира switch изразот. Ако оваа вредност се совпаѓа со една од вредностите за case

лабелите, контролата оди на инструкцијата кој следува после соодветната лабела. Од

таму контролата продолжува секвенцијално додека не биде прекината или со Break

инструкција или со крајот на Switch инструкцијата. Ако вредноста на switch изразот не

се совпаѓа со ниту една case вредност тогаш ако постои default лабела контролата оди

на таа лабела, а ако нема default лабела сите инструкции во Switch се прескокнуваат и

контролата едноставно продолжува на инструкцијата која ја следи целата Switch

инструкцијата.

switch (grade) { case ‘A’ :

case ‘B’ : cout << “Good work”;

break;

case ‘C’ : cout << “Average work”;

break;

case ‘D’ :

case ‘F’ : cout << “Poor work”;

numberInTrouble++;

break; // Ne e neophoden

}

Горниот програмски сегмент подразбира дека валидни карактери за grade

се ‘A’, ‘B’, ‘C’, ‘D’ и ‘F’ и не води сметка за погрешен внос на карактер.

Следниот код извршува и контрола на содржината на grade:

switch (grade) { case ‘A’ :

case ‘B’ : cout << “Good work”;

break;

Page 94: C++ на македонски

93

case ‘C’ : cout << “Average work”;

break;

case ‘D’ :

case ‘F’ : cout << “Poor work”;

numberInTrouble++;

break;

default : cout << grade << “Ne e validna opisna ocena.”

break;

}

Switch контролата се применува за определување на контролата на текот кога

постојат повеќе од две алтернативи. Да се потсетиме дека истото го прави и If-

Then-Else-If контролата. Последниот програмски сегмент со If-Then-Else-If

варијантата изгледа вака:

if (grade ==’A’ | | grade == ‘B’)

cout << “Good work”;

else if (grade ==’C’)

cout << “Average work”;

else if (grade ==’D’ | | grade == ‘F’)

{

cout << “Poor work”;

numberInTrouble++;

}

else

cout << grade << “Ne e validna opisna ocena.”;

Што се случува ако во претпоследниот програмски сегмент ја испуштиме

секаде инструкцијата break. Тогаш го имаме следниов код:

switch (grade) { case ‘A’ :

case ‘B’ : cout << “Good work”;

case ‘C’ : cout << “Average work”;

Page 95: C++ на македонски

94

case ‘D’ :

case ‘F’ : cout << “Poor work”;

numberInTrouble++;

default : cout << grade << “Ne e validna opisna ocena.”

}

Ако вредноста на grade во горниот пример е ‘A’, ќе го имаме следниов излез:

Good workAverage workPoor workANe e validna opisna ocena.

Do-While инструкција

Do-While инструкцијата е контролна структура циклус во која условот на

циклусот се тестира на крајот (дно) на циклусот. Овој формат гарантира дека телото на

циклусот се извршува барем еднаш. Синтактичкиот шаблон за Do-While е следниот:

do

Statement;

while (Expression);

Statement е или единечна инструкција или блок. Исто така треба да се примети дека do-

while завршува со точка-запирка карактер (;).

Разгледуваме неколку примери решени со while и do-while варијанта:

while варијанта do-while варијанта

DataFile >> inputChar; do

While (inputChar != ‘.’) dataFile >> inputChar;

DataFile >> inputChar; while (inputChar != ‘.’);

Во горниот пример while варијантата бара примарно читање за да го евалуира while

изразот, додека во do-while првото читање се врши внатре во циклусот пред

евалуацијата која е на крајот од циклусот.

Сега разгледуваме програмски сегмент кој контролира корисникот во

интерактивен програм да не внесе негативен број. Се додека корисникот не внесе

позитивен број програмот го враќа назад на внос. Вакви одбранбени механизми често

се применуваат во пракса.

While варијанта:

cout << “Vnesete pozitiven cel broj: ”;

Page 96: C++ на македонски

95

cin >> broj;

while (broj <= 0)

{

cout << “Brojot mora da bide pozitiven” << endl;

cout << “Vnesete pozitiven cel broj: ”;

cin >> broj;

}

Do-while варијанта:

do

{

cout << “Vnesete pozitiven cel broj: ”;

cin >> broj;

if (broj <=0)

cout << “Brojot mora da bide pozitiven” << endl;

} while ( broj <= 0);

Разликата меѓу овие две варијанти е во тоа што за while варијантата мора два пати да се

напише промпт и инструкција за внос, додека во do-while, промптот и инструкцијата за

внос се пишуваат само еднаш, но евалуација се врши два пати.

Do-While може да се користи за имплементација на циклус контролиран од

бројач ако однапред знаеме дека телото на циклусот ќе се изврши барем еднаш.

Следните се две верзии на циклус кој сумира броеви од 1 до n.

While варијанта: Do-While варијанта:

sum = 0; sum = 0;

counter = 1; counter = 1;

while (counter <=n) do { { sum = sum + counter; sum = sum + counter; counter++; counter++; } } while (counter <=n); Do-While варијантата е иста со While варијантата за сите позитивни вредности на n. За n = 0, While варијантата нема да се изврши и вредноста на sum останува 0 и после циклусот. Do-While варијантата се извршува еднаш и вредноста на sum после извршувањето на циклусот е 1.

Page 97: C++ на македонски

100

For инструкција Во C++ For инструкцијата е фактички компактна форма на while инструкцијата.

Компајлерот фактички ја преведува For инструкцијата во еквивалентен while циклус како

што е прикажано на сл. 8.1.

Сл. 8.1

Синтактичкот шаблон е следниов

Expressionl е While условот. InitStatement може да биде едно од следниве: null инструкција

(само точка-запирка карактер), инструкција за декларација (која секогаш завршува со

точка-запирка), или израз кој завршува со точка-запирка. Значи секогаш има карактер (; )

пред Еxpressionl.

Најчесто For инструкцијата е напишана така да InitStatement ја иницијализира

променливата која го контролира циклусот и Expression2 ја инкрементира или

Page 98: C++ на македонски

101

декрементира променливата која го контролира циклусот. Следните два циклуса се

извршуваат по 50 пати:

for (loopCount = 1; loopCount <= 50; loopCount++) . . . for (loopCount = 50; loopCount >= 1; loopCount--) . .

.

Како и другите контролни структури и For циклусите може да се вгнездуваат. На

пример цикличната структура:

for (i = 1; i <= 7; i++)

{

for (j = 1; j <= i; j++)

cout << j;

cout << endl;

}

ќе го даде следниов излез:

1

12

123

1234

12345

123456

1234567

Page 99: C++ на македонски

102

Break и Continue Инструкции

Break инструкцијата која ја видовме кај Switch контролната структура, исто така се

користи со циклуси. Таа предизвикува моментален излез од највнатрешната вгнездена

структура (Switch, While, Do-While, или For инструкција во која се појавува). Значи ако

break е во циклус кој е вгнезден во друг циклус, контролата излегува од внатрешниот

циклус, но не и од надворешниот.

Еден од најчестите случаи на користење break со циклус е да се дизајнира

бесконечен циклус. Да претпоставиме дека треба да внесеме 10 пара од цели броеви, да

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

пар броеви. Да претпоставиме дека првиот број од секој пар мора да биде помал од 100, а

вториот мора да биде поголем од 50. Исто така после секој влез сакаме да ја тестираме

состојбата на влезниот тек за EOF. Следното е кодот кој ги озвршува сите овие работи:

loopCount = 1;

while (true)

{

cin >> num1;

if ( !cin | | num1 >= 100 )

break;

cin >> num2;

if ( !cin | | num2 <= 50 )

break;

cout << sqrt(float(num1+num2)) << endl;

loopCount++;

if (loopCount > 10)

break;

}

Овој циклус содржи три различни точки на излез кога некој од трите услови:

1. состојбата на влезниот тек е false или num1 >= 100,

2. состојбата на влезниот тек е false или num2 < 50,

3. loopCount добил вредност 11

е исполнет.

Page 100: C++ на македонски

103

Алтернатива на овој дизајн е дизајнот со вгнездена If контрола кој ги тестира горе

наведените услови на излез. Ваквата If контрола е посложена и затоа дизајнот со

бесконечен циклус и break бара пишување на помалку код и е помалку подложен на

грешки.

Continue инструкцијата се користи поретко. За разлика од break инструкцијата која

и наложува на контролата моментален излез од циклусот во кој се наоѓа break, continue

инструкцијата не го прекинува циклусот, туку само наложува прескокнување на

инструкциите после continue и враќање на while тест изразот.

Page 101: C++ на македонски

104

X. Прости типови на податоци

Вградени (Built-In) и кориснички дефинирани (User-Defined)

Вградени (built-in) типови на податоци

Прост (атомски) тип на податок е тип на податок во кој секоја вредност е атомска

(неделива).

Друг начин на опис на прост тип е дека тоа е тип кај кој само една вредност може

да биде поврзана со променлива од тој тип. Обратно, структуиран тип е оној кај кој цело

множество на вредности е поврзано со една променлива од тој тип. На пример, променлива

од тип string претставува множество од карактери на кои им е дадено едно име. На сл. 10.1

е прикажан дел од сл. 3-1 кој ги вклучува само простите типови на

податоци

i n t e g r a l

char short int long bool enum float double long double

f l o a t i n g

p r o s t i t i p o v i

слика 10.1

Типот enum не е прост тип како int и float, но тоа е механизам со кој може да се

дефинираат прости кориснички дефинирани типови на податоци. Integral и floating-point

типовите едноставно реферираат на целобројни и децимални броеви со различни должини.

Page 102: C++ на македонски

105

Во C++ должините се мерат во бајтови (bytes). Постои функција sizeof (datatype) која

ја дава должината на одреден тип на податок (datatype). Должините на типовите на

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

функцијата sizeof, иако аргументот е ист. Покрај овие варијации, C++ гарантира дека

следниве тврдења се точни:

dolg. bita 32najmalku e long

dolg bita 16najmalku eshort

dolg bita 8najmalku echar

double) long(sizeof)double(sizeof)float(sizeof

)long(sizeof)bool(sizeof1

)long(sizeof(int)sizeof)short(sizeof)char(sizeof1

≤≤

≤≤

≤≤≤=

Интегрални типови

Рангови на вредности

Ранговите на вредности се machine-dependant (различни за различни машини).

Следниве вредности се однесуваат на една конкретна машина.

Тип Должина Минимална вредност Макс. вредност податок (бајти) char 1 -128 127 unsigned char 1 0 255 short 2 -32768 32767 unsigned short 2 0 65535 int 2 -32768 32767 unsigned int 2 0 65535 long 4 -2147483648 2147483647 unsigned long 4 0 4294967295

Во C++ системите постои header датотека climits од која може да се определат

минималната и максималната должина. На пример:

#include <climits>

using namespace std;

.

.

cout << “Max. long = “ << LONG_MAX << endl;

cout << “Min. long = “ << LONG_MIN << endl;

Page 103: C++ на македонски

106

Литерални константи

Во C++ валидни bool константи се true и false. Целобројните константи можат да

бидат специфицирани во три бази: децимална (база 10), октална (база 8) и хексадецимална

(база 16). Како што децималниот броен систем има 10 цифри (од 0 до 9), окталниот броен

систем има 8 цифри (од 0 до 7), а хексадецималниот систем се состои од 0, 1, 2, 3, 4, 5, 6, 7,

8, 9, A, B, C, D, E, F, кои одговараат на децималните вредности од 0 до 15. Во овој курс,

ние оперираме само со децималната база.

Floating-point типови

Рангови на вредности

Во табелата што следува се дадени ранговите на вредности на трите floating-point

типови. За секој тип се дадени минималната позитивна вредност (број многу близок до 0) и

максималната вредност. Негативните вредности имаат ист ранг со негативни вредности.

Тип Должина Мин. позитивна вредност Макс. вредност податок (бајти) float 4 3.4Е-38 3.4Е+38 double 8 1.7Е-308 1.7Е+308 long double 10 3.4Е-4932 1.1Е+4932

Ранговите се зависни од машината и овие вредности се однесуваат за конкретна

машина. Во C++ системите постои header датотека cfloat од која може да се определат

минималните и максималните должини на floating-point типовите на податоци.

Литерални константи

Floating-point константите во C++ се од тип double (double precision). Ако во float

променлива се додели литерална константа, настанува имплицитна конверзија од double во

float. Ако инсистираме константата да биде од тип float, тогаш на крајот од константата

прикачуваме f или F. Следната табела прикажува константи од различни тпови.

Константа Тип Забелешка

6.83 double По default floating-point константите

се од тип double.

6.83F float Експлицитните float константи завршуваат со F

6.83L long double long double константите завршуваат со L.

Page 104: C++ на македонски

107

4.35E-9 double Експоненцијална нотација со значење 4.35x10-9

Други C++ оператори

Освен основните, C++ располага со огромен број други оператори кои ретко се

среќаваат во другите програмски јазици. Овде наведуваме неколку:

- комбинирани оператори на доделување

+ = додај и додели

- = извади и додели

* = помножи и додели

/ = подели и додели

% = пресметај модул и додели

<< = помести лево и додели

>> = помести десно и додели

- инкремент и декремент оператори

+ + пре-инкремент н.пр. ++ i

+ + пост-инкремент н.пр. i ++

- - пре-декремент н.пр. -- j

- - пост-декремент н.пр. j --

Bitwise оператори (главно за манипулирање со битови и мемориски адреси)

<< лево поместување

>> десно поместување

& bitwise AND

| bitwise OR

^ bitwise EXCLUSIVE OR

~ complement

Други оператори

( ) cast

sizeof должина на операнд (или тип на податок) во бајти

? : условен оператор

Оператор на доделување и изрази на доделување

C++ има неколку оператори на доделување. Знакот = е основен оператор на

доделување. Кога овој оператор се комбинира со своите два операнда, се формира израз на

Page 105: C++ на македонски

108

доделување. Секој израз на доделување има вредност и страничен (side) ефект. На

пример:

delta = 2 * 12

има вредност 24 и страничен ефект ставање на таа вредност во delta.

Во C++ кој било израз станува инструкција кога ќе се додаде знакот точка-запирка

(;). Сите следни примери се C++ инструкции:

23;

2 * (alpha + beta);

delta = 2 * 12;

Првите две инструкции немаат никаков ефект на извршување на програмата и се

бескорисни. Третата инструкција е корисна, бидејќи има страничен ефект – ставање

вредност 24 во променливата delta.

Значи, израз на доделување е C++ израз со (1) вредност и (2) страничен ефект на

ставање на вредноста во мемориска локација; инструкција на доделување е инструкција

формирана со додавање на знакот ; на крајот од изразот.

Освен основниот оператор за доделување = , во C++ има неколку комбинирани

оператори (+=, -=, *=, /= и др.) кои како страничен ефект имаат доделување. На пример:

Инструкција Еквивалентна инструкција

i += 5 i = i+5

flVar *= 1. – x flVar = flVar * (1. – x)

Комбинираните оператори на доделување имаат и добри и лоши страни. Од една

страна го скратуваат кодирањето, а од друга страна не се толку прегледни како нивните

еквивалентни инструкции.

Инкремент и декремент оператори

Операторите инкремент и декремент (++ и --) оперираат само на променливи. Да

претпоставиме дека променливата brojac содржи вредност 8. Изразот ++brojac означува

преинкрементирање. Прво се извршува страничниот ефект инкрементирање на brojac, така

што резултантната вредност на изразот е 9. Изразот brojac++ означува

постинкрементирање. Вредноста на изразот е 8 и потоа се случува страничниот ефект

Page 106: C++ на македонски

109

инкрементирање на brojac. Следниот код ја илустрира разликата меѓу пре- и пост-

инкрементирање:

int1 = 14;

int2 = ++int1;

// Во овој момент int1 има вредност 15 и int2 има вредност 15

int1 = 14;

int2 = int1++;

// Во овој момент int1 има вредност 15, а int2 има вредност 14

Cast (експлицитна конверзија) операција

Кога оперира со мешани типови на податоци во: инструкциите за доделување во

изразите, во трансфер на аргументи и во враќање на вредност на функција, C++ врши

имплицитна конверзија (coercion). Наместо да се потпираме на имплицитната конверзија,

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

(casting) со што програмерот укажува на својата намера да го конвертира типот на податок.

Во C++ операцијата casting може да има две форми. На пример:

intVar = int(floatVar); // Функциска нотација

intVar = (int) floatVar; // Префикс нотација. Потребно е типот да биде во загради

C++ програмерите обично ја користат функциската нотација, но таа има едно мало

ограничување. Имено, не може да се користи кога името на типот на податоци се состои од

повеќе од едно име. На пример:

myVar = unsigned int (someFloat); // ова е погрешно

myVar = (unsigned int) someFloat; // ова е точно

Sizeof оператор

Ова е унарен оператор кој ја дава должината на неговиот операнд во бајтови.

Операндот може да биде име на променлива

sizeof someInt

Или име на тип на податок затворен во загради

sizeof (float)

На конкретна машина може да се најдат должините на различни типови на

податоци со користење на следниов код:

Page 107: C++ на македонски

110

cout << “Size of a short is “ << sizeof (short) << endl;

cout << “Size of an int is “ << sizeof (int) << endl;

cout << “Size of a long is “ << sizeof (long) << endl;

Условен (conditional) оператор ? :

Овој оператор, за разлика од унарните и бинарните оператори кои оперираат на

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

операнди. Неговата синтакса е:

Izraz1 ? Izraz2 : Izraz3

Прво компјутерот го евалуира Izraz1. Ако вредноста на евалуацијата е true, тогаш

вредноста на целиот израз е вредноста на Izraz2. Инаку вредноста на целиот израз е

вредноста на Izraz3. Значи, само еден од Izraz2 и од Izraz3 се оценува).

Класичен пример за примена на овој оператор е во променлива max да се стави

поголемата вредност од вредностите на две променливи a и b. Користејќи if инструкција

овој проблем го решаваме со следниов код:

if (a > b)

max = a;

else

max = b;

Со користење на ? : операторот, можеме да ја користиме следнава инструкција на

доделување.:

max = (a > b) ? a : b;

Еве уште еден пример. Апсолутната вредност на број x е дефинирана како:

<−

≥=

0 xako x

0 x ako xx

За да ја пресметаме апсолутната вредност на променлива x и да ја сместиме во

променлива y, можеме да користиме условен оператор како што следува:

y = (x >= 0) ? x : -x;

Page 108: C++ на македонски

111

Податоци од тип char

Видовме дека во променлива од тип char можеме да чуваме карактери како ’A’ , ’е’

’3’ и др. Но од друга страна, типот char е интегрален тип и во него можеме да чуваме и

податоци од целоброен тип како:

char counter;

counter = 3;

Се поставува прашање како компјутерот ја знае разликата меѓу целоброен тип и

карактер кога податоците се сместени во мемориската ќелија. За компјутерот не постои

разлика.

Секој карактер има два вида на репрезентација:

- екстерна репрезентација е принтабилна (карактер) форма на вредноста на податокот.

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

мемориската единица.

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

ch = ‘A’;

ако машината користи ASCII карактер сет, компајлерот ја преведува константата ‘A’ во

цел број 65. Така можеме да напишеме

ch = 65;

Горните две инструкции имаат ист ефект: доделување 65 во ch. Меѓутоа, втората

инструкција не се препорачува, бидејќи на машина која користи друг карактер сет

целобројната константа означува друг карактер.

Да погледнеме уште еден програмски сегмент:

// Овој пример претпоставува користење на ASCII карактер сет

int someInt = 97;

char someChar = 97;

cout << someInt << endl;

cout << someChar << endl;

Кога овие инструкции ќе се извршат, излезот е:

97

а

Значи, иако двете променливи интерно содржат вредност 97, типот на податок ја

определува принтабилната (екстерна) вредност на двата карактерa.

Следниот код ги печати карактерите од А до G:

Page 109: C++ на македонски

112

for ( ch = ‘A’; ch <= ‘G’; ch++)

cout << ch;

Множествата од карактери (character set) вклучуваат како принтабилни, така и

контролни (непринтабилни) карактери. Ако гледаме во ASCII табелата на карактери, може

да се забележи дека принтабилни карактери се оние со целобројни вредности во

интервалот меѓу 32 и 126. Останатите карактери (од 0 до 31 и карактерот 127) се

контролни карактери. За да ги приспособи контролните карактери за користење, C++

воведува втора форма на char константа, escape sequence. Escape sequence (escape

секвенца) се состои од backslash (\) и еден или повеќе карактери. Следниве се контролни

(непринтабилни) карактери во C++:

\n Нова линија

\t Хоризонтален tab

\v Вертикален tab

\b Backspace

\r Carriage return

\f Form feed

\a Alert (ѕвоно или beep)

\\ Backslash

\’ Апостроф

\” Наводници

\0 Нулти карактер (сите битови се 0)

На пример, извршувањето на инструкцијата

cout << ‘\a’;

ќе предизвика звук beep.

Is… функции и конвертирање од мали во големи букви и обратно

Во header датотеката <cctype> има вградено повеќе кориснички функции кои

вклучуваат манипулирање со карактери.. Такви се is.... функциите:

isalnum(ch)

isalpha(ch)

iscntrl(ch)

isdigit(ch)

Page 110: C++ на македонски

113

islower(ch)

isupper(ch)

.

.

Сите овие функции се функции кои враќаат вредност. Тие за аргумент земаат

карактер (char), а враќаат целобројна вредност (int). Ако вредноста која ја враќа

функцијата е цел број различен од 0, вредноста на изразот кој се евалуира е true, а ако е 0

вредноста на изразот е false. Овие функции често се употребуваат при контрола на внос и

евалуација на логички изрази. На пример, следната функција контролира дали внесениот

карактер е нумерички и ако е, го претвoра во тип на податок integer.

#include <cctype>

using namespace std;

.

.

.

void GetNum (/*out */ int& numVal)

{

char inpCh;

bool neNum = false;

do

{

cout << “Vnesi cel broj od 1 do 5: “;

cin >> inpCh;

if ( ! isdigit (inpCh))

neNum = true; // Vneseniot karakter ne e numericki

else

{

numVal = int (inpCh – ‘0’);

if ( numVal < 1 | | numVal > 5)

neNum = true; // Karakterot e cifra, no ne e vo rangot

}

if (neNum)

cout << “Nevaliden broj. Probajte povtorno” << endl;

Page 111: C++ на македонски

114

} while (neNum);

}

Освен is…(ch) функциите, header датотеката <cctype> ги содржи функциите

toupper(ch) и tolower(ch).

Функцијата toupper(ch) за аргумент зема карактер и враќа:

- еквивалентен uppercase (голема буква) карактер ако ch е lowercase (мала буква)

карактер;

- го враќа аргументот ch ако ch не е lowercase (мала буква) карактер.

Функцијата tolower(ch) за аргумент зема карактер и враќа:

- еквивалентен lowercase (мала буква) карактер ако ch е uppercase (голема буква)

карактер;

- го враќа аргументот ch ако ch не е uppercase (голема буква) карактер.

Пристап на карактер во стринг

Ако е даден string објект (константа или променлива) може да се пристапи до

карактер од тој стринг.

ch = inputStr[2]

на ch и го доделува третиот карактер од стрингот inputStr (тука треба да се внимава на

фактот дека во C++ првиот карактер на стрингот одговара на индекс 0)

На пример, во некој дел од кодот, корисникот треба да одговори со “Da” или “Ne”.

Притоа корисникот може да го внесе одговорот со сите комбинации од мали или големи

букви. Една од можностите е секоја комбинација од одговорот да се испитува со долга if

контролна структура. Друго поелегантно решение е да се испитува само првиот внесен

карактер и изгледа вака:

string inputStr; . . . cout << “Vnesete Da ili Ne: ”; // Prompt cin >> inputStr; // Korisnikot vnesuva podatok if (toupper (inputStr[0]) == ‘D’) // toupper go pretvora prviot karakter vo golema { // bukva i ispituva dali e ‘D’. . . }

Page 112: C++ на македонски

115

else if (toupper (inputStr[0]) == ‘N’) // ako ne e ‘D’ toupper go pretvora prviot karakter { // vo golema bukva i ispituva dali e ‘N’ . . } else // Prviot karakter ne e ni ‘D’ ni ‘N’ PrintErrorMsg( ); // Otpecati greska.

Кориснички-дефинирани прости типови

Една од предностите на C++ е што му дозволува на програмерот да креира свои

типови на податоци.

Typedef инструкција

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

Синтаксата е: typedef - Постоечко име на податок - Ново име на податок.

Пред да bool типот на податок стане вграден во C++, многу програмери користеле

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

typedef int Boolean;

const int TRUE = 1;

const int FALSE = 0;

.

.

Boolean dataOK;

.

.

dataOK = TRUE;

Во овој код, typedef инструкцијата му наложува на компајлерот за секоја појава на

зборот Boolean да го стави типот на податок int.

Енумератион тип на податок

C++ му дозволува на корисникот да дефинира нов прост тип на податок со листање

на литерални вредности кои го сочинуваат доменот на типот. Овие литерални вредности

мора да бидат идентификатори, а не броеви. Идентификаторите се разделени со запирка и

Page 113: C++ на македонски

116

листата е затворена во големи загради Овој тип на податок, дефиниран на овој начин, се

нарекува enumeration тип. На пример:

enum Days {SUN, MON, TUE, WED, THU, FRI, SAT};

Оваа декларација креира нов тип на податок по име Days. Ова е нов тип на податок

и е различен од кој било постоечки тип. Вредностите во типот на податок Days - SUN,

MON, TUE,… се нарекуваат енумератори. Овие вредности се поодредени така што SUN <

MON < TUE <….

Бидејќи енумераторот мора да биде C++ идентификатор, следниве декларации се

погрешни:

enum Vowel {‘A’, ‘E’, ‘I’, ‘O’, ‘U’}; // идентификаторот мора да почне со буква или ’_’.

enum Places {1st, 2nd, 3rd}; // идентификаторот мора да почне со буква или ’_’.

Да претпоставиме дека правиме програма за ветеринарна станица и сме креирале

тип Animals. Декларираме две променливи од овој тип како што следува:

enum Animals {GLODAR, MACKA, KUCE, PTICA, VLEKAC, KONJ, KRAVA, OVCA};

Animals inPatient;

Animals outPatient;

Да разгледаме неколку операции кои можат да бидат извршени на променливи од

enumeration тип.

Доделување

inPatient = DOG;

outPatient = inPatient;

Горните две инструкции на доделување се валидни, бидејќи на променлива од тип

Animals & се доделува: во првиот случај, литерална вредност од доменот на типот на

податок Animals, а во вториот случај, променлива од тип Animals.

Следната инструкција не е валидна:

inPatient = 2;

Имплицитна конверзија (coercion) е дозволена од enumeration во integer тип.

Обратната имплицитна конверзија (од integer во enumeration тип) не е дозволена. Така:

someInt = DOG; // дозволено

inPatient = 2; // грешка

Page 114: C++ на македонски

117

Инкрементација

Да претпоставиме дека сакаме да ја зголемиме вредноста на inPatient, така што

inPatient ја добие следната вредност во доменот на Animals. Пишуваме инструкција

inPatient = inPatient + 1; // Погрешно

Горната инструкција е погрешна од следнава причина: изразот од десната страна е

во ред, бидејќи ќе настапи имплицитна конверзија од enumeration во integer тип и десната

страна има вредност од тип integer. Кога оваа вредност треба да & се додели на

променливата од левата страна, треба да настапи имплицитна конверзија од integer во

enumeration тип на податок, а тоа не е валидно.

Бидејќи инкремент операцијата е еквивалентна на горниот израз, од истите

причини

inpatient++ е невалидно.

За да ја избегнеме несаканата коерција (имплицитна конверзија), користиме

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

inPatient = Animals(inPatient + 1); // Точно

Инкрементирањето на променлива е посебно корисно во циклуси. На пример:

for (patient = GLODAR; patient <= OVCA; patient=Animals(patient + 1))

вака креираниот циклус е валиден. Единствено треба да се има предвид дека по

завршувањето на циклусот, променливата patient е надвор од рангот. Ако сакаме повторно

да ја користиме променливата patient мора да & доделиме вредност која е во рангот на

типот Animals.

Споредување

Кога се споредуваат две вредности, релацијата се определува врз основа на

нивниот редослед во листата на енумератори. На пример, изразот:

inPatient <= PTICA

ќе има вредност true ако inPatient има вредност GLODAR, MACKA, KUCE или PTICA.

Вредности од тип enumeration може да се користат во switch инструкција:

switch (inPatient)

{

case GLODAR :

case MACKA :

case KUCE :

case PTICA : cout << “Metalen kafez”;

Page 115: C++ на македонски

118

break;

case VLEKAC : cout << “Staklen kafez”;

break;

case KONJ :

case KRAVA :

case OVCA : cout << “Stala”;

}

Влез и излез

Влезно-излезниот тек (stream) е дефиниран само за основните типови на податоци

(int, float, char итн). Вредностите на типовите enum мора да одат на внос и излез

индиректно. На пример, програмaтa може да го чита видот на животно како стринг и потоа

да му додели една од константите во enum типот. На пример:

#include <cctype>

#include <string>

.

.

string animalName;

.

.

cin >> animalName;

switch (toupper(animalName[0]))

{

case ‘G’ : inPatient = GLODAR;

break;

case ‘M’ : inPatient = MACKA;

break;

case ‘K’ : if (toupper(animalName[1] = = ‘U’)

inPatient = KUCE;

else if (toupper(animalName[1] = = ‘O’)

inPatient = KONJ;

else

inPatient = KRAVA;

break;

Page 116: C++ на македонски

119

case ‘P’ : inPatient = PTICA;

break;

case ‘V’ : inPatient = VLEKAC;

break;

default : inPatient = OVCA;

break;

}

Враќање на вредност на функција

Претходно напишавме switch инструкција за да конвертираме стринг во вредност

од тип Animals. Сега пишуваме функција која враќа вредност од тип Animals.

Animals StrtoAnimal ( /*in*/ string str )

{

switch (toupper(str[0]))

{

case ‘G’ : return GLODAR;

case ‘M’ : return MACKA;

case ‘K’ : if (toupper(str[1] = = ‘U’)

return KUCE;

else if (toupper(str[1] = = ‘O’)

return KONJ;

else

return KRAVA;

case ‘P’ : return PTICA;

case ‘V’ : return VLEKAC;

default : return OVCA;

}

}

Во оваа функција улогата на break инструкцијата ја презема return инструкцијата.

Повикот на функцијата StrtoAnimal од main се врши како што следува:

enum Animals {GLODAR, MACKA, KUCE, PTICA, VLEKAC, KONJ, KRAVA, OVCA};

Animals StrtoAnimal (string);

.

.

Page 117: C++ на македонски

120

int main( )

{

Animals inPatient;

Animals outPatient;

string inputStr;

.

.

cin >> inputStr;

outPatient = StrtoAnimal (inputStr);

.

.

}

Кориснички дефинирани header датотеки

Често одреден кориснички дефиниран тип на податок може да биде користен во

повеќе програми. Така, податокот:

enum Months

{

JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE,

JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER

};

чиј ранг се имињата на месеците во годината, може да се користи во повеќе програми.

Наместо во секојa програмa да го дефинираме овој тип на податок, пpaктичнo е тој

да се чува во посебна датотека, да речеме months.h. Потоа со користење на #include

директивата, оваа датотекa може да се вклучи во којa било програмa на сличен начин како

и системските header датотеки iostream, cmath и други. Вклучувањето на нашата датотека

би било со директивата:

#include “months.h”

Во овој случај препроцесорот ја бара датотеката во актуелниот програмски

директориум.

Page 118: C++ на македонски

121

XI. Структуpирани типови на податоци

Во третата глава на сл. 3.1 беа илустрирани типовите на податоци кои се среќаваат

во C++. Во оваа глава ќе ги разгледаме структуpираните типови на податоци: struct и union.

Едноставни спрема структуpирани типови на податоци

Во претходната глава ние испитувавме едноставни или атомски типови на

податоци. Вредноста на едноставните типови на податоци не може да биде разбиена на

помали делови. На пример, секоја int вредност е цел број кој не може да биде понатаму

декомпониран. Спротивно, структуpиран тип на податок е таков податок во кој секоја

вредност е множество од компонентални делови. Целото множество од вредности има

единствено име, а на секоја компонента од множеството може да & се пристапи

индивидуално.Пример на структуиран тип на податок во C++ е типот на податок string, кој

претставува множество од карактери. На секоја од компонентите на стрингот може да &

се пристапи индивидуално со користење на израз, како на пример myString[3], кој му

пристапува на карактерот кој се наоѓа на позиција 3 во стрингот.

Простите типови на податоци (built-in или кориснички дефинирани) претставуваат

компоненти на структуираните типови на податоци.

Слогови (C++ struct)

Слогот е хетероген структуpиран тип на податок. Под зборот хетероген

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

типови на податоци. Секоја компонента на слогот се нарекува поле (field) на слогот и

секое поле во состав на слогот си има свое име. Слогот се нарекува структура, а секое

поле од слогот се нарекува член (member) на структурата. Секој член на структурата има

име на членот.

Record (structure во C++) е структуpиран тип на податок со постојан број на

компоненти на кои може да им се пристапи по име. Компонентите можат да бидат

хетерогени (со различен тип на податок).

Field (member во C++) е компонента на структурата. Синтактичкиот шаблон за

декларација на структура во C++ е:

Page 119: C++ на македонски

122

struct TypeName

{

MemberList

};

каде TypeName е идентификатор кој дава име на новодефинираниот тип на податок, а

MemberList е дефиниран како:

DataType MemberName;

DataType MemberName;

.

.

Како што се гледа, синтаксата на листата од членови MemberList е идентична со

низа од декларации на променливи.

Како пример користиме struct за да опишеме студент на одреден предмет. Во

слогот сакаме да ги чуваме: името и презимето, просекот пред ислушувaњето на

предметот, оценката од задачи од програмирање, оценката на квизови (кратки тестови),

оценката од краен испит и оценката за предметот.

// Deklaracii na tipovi

enum GradeType (A, B, C, D, F);

struct StudentRec

{

string firstName;

string lastName;

float gpa; // Grade point average

int programGrade;

int quizGrade;

int finalExam;

GradeType courseGrade;

};

// Deklaracii na promenlivi

Page 120: C++ на македонски

123

StudentRec firstStudent;

StudentRec student;

int grade;

Од примерот и од синтактичкиот шаблон треба да се забележи дека struct

декларацијата завршува со карактер точка-запирка (;). FirstName, lastName, gpa,

programGrade, quizGrade, finalExam и courseGrade се имиња на членови во struct типот

StudentRec. Ниеден од овие членови на структурата не е поврзан со мемориска локација, сè

додека променлива од тип StudentRec не е декларирана. StudentRec е само шаблон за struct

(сл. 11.1). firstStudent и student се променливи од тип StudentRec.

firstName

lastName

gpa

programGrade

quizGrade

finalExam

courseGrade

сл. 11.1

Пристапување до индивидуални компоненти

За да се пристапи до индивидуален член на struct променлива, прво се наведува

името на променливата проследено со точка и потоа името на членот. Ваквиот израз се

нарекува селектор на членот (member selector). Синтактичкиот шаблон е следниот:

StructVariable.MemberName

Оваа синтакса за селектирање индивидуални компоненти на struct, често се

нарекува dot нотација. За да пристапиме на просекот на првиот студент, gpa, правилна

синтакса е:

firstStudent.gpa

За да пристапиме на резултатот од испит на некој студент, синтаксата е:

Student.finalExam

Page 121: C++ на македонски

124

Member selector е израз кој се користи да пристапи на компонентите на struct

променлива. Тој се состои од име на struct променлива и име на член на структурата

разделени со точка.

Компонентата од структурата на која & е пристапено со member selector се третира

како која било променлива од ист тип. Може да биде користена во изрази на доделување,

како аргумент во функција итн.

Со следниот програмски сегмент ќе ја демонстрираме примената на member

selector. Користејќи ја нашата променлива student, следниот сегмент чита оценка од краен

испит; прави збир од оценката од програма, оценката од квиз и оценката од краен испит и

потоа доделува описна оцена на резултатот.

cin >> student.finalExam;

grade = student.finalExam + student.programGrade +

student.quizGrade;

if (grade >= 900)

student.courseGrade = ‘A’;

else if (grade >= 800)

student.courseGrade = ‘B’;

.

.

.

Агрегатни операции на структури

Освен пристапување на индивидуални компоненти на struct променлива, во некои

случаи можеме да користиме агрегатни операции.

Агрегатна операција е операција на структура на податоци како целина,

спротивно на операција на индивидуалните компоненти на структурата на податоци.

Следнава табела ги дава агрегатните операции кои се дозволени врз struct променливи.

Агрегатна операција Дали е дозволена на struct ?

I/O Не

Доделување Да

Аритметичка Не

Page 122: C++ на македонски

125

Споредување Не

Функциски аргумент Да по вредност и по референца

Враќање (return) како функциска вредност Да

Во согласност со табелата, доделувањето е дозволено со тоа што двете променливи

мора да бидат од ист тип. На пример, за следниве декларации:

StudentRec student;

StudentRec anotherStudent;

инструкцијата

anotherStudent = student;

ја копира целата содржина од struct променливата student на променливата anotherStudent

член по член.

Од друга страна, агрегатните I/O (влезно-излезни) операции не се дозволени:

cin >> student; // Ne e dozvoleno

Struct променливата мора да се внесува и печати член по член. На пример:

cin >> student.firstName; // Dozvoleno

cin >> student.lastName;

.

.

.

Предавањето на struct тип како функциски аргумент е дозволена агрегатна

операција. На пример, сакаме да провериме дали оценката на студентот на конкретниот

предмет се совпаѓа со неговиот просек (grade point average-gpa). За таа цел, од неговиот

слог треба да го отчитаме неговиот просек и да го заокружиме на цел број. На 4 одговара

А, на 3 одговара B, на 2 одговара C, на 1 одговара D и на 0 одговара F.

Овој проблем го решаваме со функција која како аргумент користи променлива од

тип StudentRec, а враќа bool вредност true или false.

bool Consistent ( /* in */ StudentRec aStudent)

{

int roundedGPA = int (aStudent.gpa+0.5)

switch (roundedGPA)

{

case 0 : return (aStudent.courseGrade == ‘F’);

Page 123: C++ на македонски

126

case 1 : return (aStudent.courseGrade == ‘D’);

case 2 : return (aStudent.courseGrade == ‘C’);

case 3 : return (aStudent.courseGrade == ‘B’);

case 4 : return (aStudent.courseGrade == ‘A’);

}

}

Декларирањето на struct тип и декларирањето на една или повеќе променливи од

тој тип може да биде извршено во само една инструкција како:

struct StudentRec

{

string firstName;

string lastName;

.

.

.

} firstStudent, student;

Исто така, ако се испушти името на типот (struct ) се формира анонимен тип:

Struct

{

int firstMember;

float secondMember;

.

.

.

} someVar;

Овде someVar е променлива од анонимен тип. Други променливи од овој тип не

можат да бидат декларирани, бидејќи типот нема име. Тоа значи дека someVar не може да

учествува во ниту една агрегатна операција.

Page 124: C++ на македонски

127

Хиерархиски слогови

Хиерархиски слог (record) е слог во кој барем една од компонентите сама за себе

е слог.

На пример, продавница за машини чува информации за сите свои машини. За

секоја машина постои описна информација, како: идентификационен број, опис на

машината, датум на купување и цена.

Исто така, постои статистичка информација која вклучува: број на денови вон

погон, честота на расипување и датум на последен сервис.

Прво да го погледаме нехиерархискиот слог:

struct MachineRec

{

int idNumber;

string description;

float failRate;

int lastServicedMonth;

int lastServicedDay;

int lastServicedYear;

int downDays;

int purchaseDateMonth;

int purchaseDateDay;

int purchaseDateYear;

float cost;

};

Типот MachineRec има 11 членови. Поради многуте детали, тешко е брзо да се

оцени што претставува слогот. Со реорганизирање во хиерархиски слог, добиваме подобра

слика што претставува слогот. Можеме да ги поделиме информациите во две групи:

информации кои се менуваат и информации кои не се менуваат. Исто така, постојат два

датума кои треба да се чуваат. Ова нè наведува да декларираме два нови типа и тоа тип

што ќе манипулира со датуми:

Page 125: C++ на македонски

128

struct DateType

{

int month;

int day;

int year;

};

и тип на податок кој ги опишува статистичките податоци:

struct StatisticsType

{

float failRate;

DateType lastServiced;

int downDays;

};

така што, главниот слог ги вклучува двата горенаведени типa на податоци:

struct MachineRec

{

int idNumber;

string description;

StatisticsType history;

DateType purchaseDate;

float cost;

};

MachineRec machine;

На овој начин, содржината на слогот е поочигледна. На компонентите на слогот им

се пристапува на сличен начин како кај нехиерархиски слогови со dot нотација. На пример,

machine.purchaseDate

machine.purchaseDate.month

machine.history.last Serviced.year

};

Page 126: C++ на македонски

129

XII. Низи (Arrays)

Во многу проблеми, структурите на податоци имаат многу компоненти кои е тешко

да се процесираат, ако секоја од компонентите има различно име. Во оваа глава

разгледуваме типови на податоци – низи.

Еднодимензионални низи

Ако сакаме да внесеме 1000 целобројни вредности кои сакаме да ги печатиме во

обратен редослед од редоследот на внесувањето, можеме да ја напишеме следнава

програма:

//************************************************************************

// ReverseNumbers program

//************************************************************************

#include <iostream>

using namespace std;

int main( ) { int value0; int value1; int value2; . . int value999; cin >> value0; cin >> value1; cin >> value2; . . cin >> value999; cout << value999 << endl; cout << value998 << endl; cout << value997 << endl; . . cout << value0 << endl; return 0; }

Page 127: C++ на македонски

130

Оваа програма има преку 3000 линии (1000 за декларации + 1000 за внос + 1000 за

излез) и во него се користат 1000 променливи. Очигледно е дека имињата на променливите

се слични и тие се од ист тип. Би било практично ако ставиме број во променлива-бројач и

користиме For циклуси кои одат од 0 до 999 и потоа од 999 назад до 0. На пример, ако

името на бројачот е number, можеме да замениме горните 2000 I/O инструкции со следните

4 линии од код:

for (number = 0; number < 1000; number++)

cin >> value[number];

for (number = 999; number >= 0; number--)

cout << value[number] << endl;

Овие четири линии од код се коректни во C++, ако гo декларираме value да биде

еднодимензионална низа која е множество од променливи од ист тип, при што првиот дел

од името на променливата е ист, по што следува индекс затворен во средни загради.

Декларацијата на еднодимензионална низа е слична со декларацијата на проста

променлива, со еден исклучок: мора да се декларира должината на низата. За горниот

пример:

int value[1000];

Оваа декларација креира низа од 1000 компоненти, сите од тип int. Првата

компонента има вредност на индексот 0, втората компонента 1 и така натаму сè до

последната компонента која има индексна вредност 999.

Следува комплетниот код на ReverseNumbers програмата кој користи низа.

//************************************************************************

// ReverseNumbers program

//************************************************************************

#include <iostream>

using namespace std;

int main( ) {

int value[1000];

int number;

Page 128: C++ на македонски

131

for (number = 0; number < 1000; number++)

cin >> value[number];

for (number = 999; number >= 0; number--)

cout >> value[number] << endl;

return 0;

}

Како структура на податоци, низата се разликува од struct во две суштински

карактеристики:

1. низата е хомогена структура на податоци (сите компоненти се од ист тип на

податоци), додека struct е хетерогена структура (нејзините компоненти може да

бидат од различни типови на податоци);

2. на компонента на низата се пристапува преку позицијата на компонентата во

низата, додека на компонента во struct се пристапува со dot нотација преку името

на членот.

Декларирање на низи

Еднодимензионална низа е структуирано множество од компоненти (елементи на

низата) на кои може да им се пристапи индивидуално со специфицирање на позицијата на

компонентата преку индексот. Синтактичкиот шаблон за декларирање на

еднодимензионална низа е:

Тип на податок- Име на низа [ConstIntIzraz]

Компонентите на низата може да бидат од каков било тип на податок, но овде ние

разгледуваме само компоненти од прости типови на податоци. ConstIntIzraz е целоброен

(интегер) израз составен само од литерали или именувани константи. Тој мора да има

вредност поголема од 0. Ако вредноста на ConstIntIzraz е n рангот на вредности на

индексот е 0 до n-1, а не од 1 до n. На пример, декларациите:

float angle[4];

int testScore[10];

креираат низи прикажани на следната слика (сл. 12.1):

Page 129: C++ на македонски

132

angle[0]

angle[1]

angle[2]

angle[3]

angle testScore

testScore[0]

testScore[1]

testScore[2]

testScore[3]

testScore[4]

testScore[5]

testScore[6]

testScore[7]

testScore[8]

testScore[9]

sl. 12.1

Низата angle има четири компоненти, секоја од нив со можност да чува една float

вредност. Низата testScore има вкупно десет компоненти, сите од тип int.

Пристап на индивидуални компоненти

За да пристапиме на компонента од низа, го пишуваме името на низата по кое

следи израз во средни загради. Вредноста на изразот специфицира на која компонента

сакаме да & пристапиме. Синтактичкиот шаблон за пристап кон компонента на низа е:

Име на низа [IndexIzraz]

IndexIzraz може да биде константа, променлива, аритметички израз или повик на

функција. Изразот мора да резултира со целобројна вредност, инаку настанува имплицитна

конверзија (коерција) во интегер. Наједноставна вредност на IndexIzraz е константа.

Користејќи ја angle низата, секвенцата на инструкции за доделување:

angle[0] = 4.93;

angle[1] = -15.2;

angle[2] = 0.5;

angle[3] = 1.67;

ги полни компонентите на низата (сл.12.2).

Page 130: C++ на македонски

133

4.93

-15.2

0.5

1.67

angle[0]

angle[1]

angle[2]

angle[3]

angle

sl . 12.2

Секоја компонента на низата, на пример angle[2], може да биде третирана на ист

начин како која било проста променлива од тип float. На пример:

angle[2] = 9.6; доделување на вредност

cin >> angle[2]; вчитување вредност во неа

cout << angle[2]; печатење на нејзината вредност

y = sqrt(angle[2]); аргумент на функција

x = 6.8 * angle[2] + 7.5; користење во аритметички израз

Да ги погледнеме индексните изрази кои се покомплицирани од константите. Да

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

инструкцијата

int value[1000];

и ги извршуваме следниве две инструкции:

value[counter] = 5;

if (value[number + 1] % 10 != 0)

.

.

Page 131: C++ на македонски

134

Во првата инструкција на компонентана низата е доделен бројот 5. Ако counter е 0,

бројот 5 е доделен на првата компонента од низата. Ако counter е 1, бројот 5 се доделува на

втората компонента од низата итн.

Во втората инструкција, изразот number + 1 селектира компонента од низата.

Компонентата на која & е пристапено се дели со 10 и се проверува дали остатокот е

ненулти. Ако number + 1 е 0, ја тестираме првата компонента од низата, ако number + 1 е 1

ја тестираме втората компонента итн.

String класата дозволува пристап на индивидуални карактери во стрингот:

string aString;

aString = “Hello”;

cout << aString[1]; // Pecati ‘e’

Иако личи на низа, типот на податок string е класа. Користејќи ја C++ техниката

operator overloading, на операторот [ ] му дава друго значење – селектирање на компонента

на стринг, покрај неговото стандардно значење – селектирање на елемент на низа.

Индекси надвор од границите на низата

Нека е дадена декларацијата

float alpha[100];

Рангот на вредности на индексот е 0 до 99. Ако извршиме инструкција

alpha[i] = 62.4;

кога i е помало од 0 или i е поголемо од 99, тогаш пристапуваме кoн мемориска локација

надвор од низата. C++ не проверува дали индексот на низата е во декларираните граници.

На пример, ако i = 100 во горната инструкција, компјутерот доделува 62.4 во мемориската

локација која следува по крајот на низата и со тоа ја уништува вредноста која била

содржана на таа локација.

Индекс на низата надвор од граници е вредност на индексот во C++ кој е или

помал од 0 или поголем од должината на низата – 1.

Алгоритмите кои процесираат низи често користат For циклуси за процесирање на

елементите еден по еден. Следувa алгоритам кој ги сетира на 0 стоте елементи од низата

alpha (i е int променлива):

Page 132: C++ на македонски

135

for (i = 0; i < 100; i++)

alpha[i] = 0.0;

Иницијализирање на низа во декларација

Видовме дека C++ дозволува иницијализирање на променлива во декларација:

int delta = 25;

Вредноста 25 се нарекува иницијализатор. Исто така, може да се иницијализира

низа во декларација, користејќи специјална синтакса за иницијализаторот. Се специфицира

листа на иницијални вредности за елементите на низата, елементите се разделуваат со , и

листата се затвора во големи загради:

int age[5] = {23, 10, 16, 37, 12};

Во оваа декларација, age[0] се иницијализира на 23, age[1] на 10 итн. Ако во

горниот пример се специфицираат повеќе од пет вредности, компајлерот ќе сигнализира

синтактичка грешка. Ако се специфицираат помалку од пет вредности, останатите

елементи се иницијализираат на 0. Статичка низа (било да е глобална или декларирана

како static во блок) се иницијализира само еднаш. Автоматска низа (локална недекларирана

како static) се реиницијализира секојпат кога контролата минува низ декларацијата.

Интересна карактеристика на C++ e дека е дозволено да се изостави должината на

низата кога таа се иницијализира во декларација:

float temperature[ ] = {0.0, 112.37, 98.6};

Компајлерот ја открива должината според бројот на иницијални вредности во

листата.

Агрегатни операции на низи

Во претходната глава дефиниравме агрегатни операции како операции на

структури на податоци како целина. C++ не дозволува агрегатни операции на низи. За

декларациите:

int x[50];

int y[50];

не е дозволено агрегатно доделување:

Page 133: C++ на македонски

136

x = y; // Nedozvoleno

Доделувањето се врши компонента по компонента:

for (index = 0; index < 50; index++)

x[index] = y[index];

Не е дозволено агрегатно споредување:

if (x == y) // Nedozvoleno

Ниту извршување на агрегатни I/O на низи:

cout << x; // Nedozvoleno

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

x = x + y; // Nedozvoleno

Конечно, не е дозволено да се врати цела низа како вредност од функција која враќа

вредност:

return x; // Nedozvoleno

Единствена агрегатна операција што може да се изврши врз низа е да се предаде

низата како аргумент на функција. Тогаш функцијата добива пристап на целата низа.

Примери на декларирање и пристапување на низи

Како прв пример, разгледуваме број на жители во една зграда која има 100 станови.

const int BUILDING_SIZE = 100; // Broj na stanovi

int occupants[BUILDING_SIZE]; // occupants[i] e broj na stanari vo stan i-1

int totalOccupants; // Vkupen broj na stanari vo zgradata

int counter; // brojac na ciklusot i indeksna promenliva

На пример, ако occupants[0] = 3 кажува дека во првиот стан има 3 станари,

occupants[1] = 5 кажува дека во вториот стан живеат 5 станари итн. Ако овие вредности се

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

зградата:

totalOccupants = 0;

for (counter = 0; counter < BUILDING_SIZE; counter++)

Page 134: C++ на македонски

137

totalOccupants = totalOccupants + occupants[counter];

Може да се забележи дека интервалот на counter е од 0 до 99, при што се кумулира

бројот на станарите од првиот стан (counter = 0) до последниот (стоти) стан (counter = 99).

Најчесто индексот во низата е од тип int. Меѓутоа C++ дозволува користење и на

enumeration тип на податок. Следниот пример прикажува низа во која индексите се

вредности од enumeration тип.

enum Drink {ORANGE, COLA, BEER, APPLE, CHERRY, LEMON};

float salesAmt[6]; // Niza od 6 float broevi koja se indeksira so tip Drink

Drink flavor; // Promenliva za indeks

Енумераторите ORANGE, COLA,…, LEMON имаат интерна интегер

репрезентација од 0 до 5, salesAmt е низа од 6 float вредности кои означуваат цена на секој

вид на пијалак. Следниот код ги печати цените кои се содржат во низата.

for (flavor = ORANGE; flavor <= LEMON; flavor = Drink( flavor + 1 ))

cout << salesAmt[flavor] << endl;

Во последниот пример разгледуваме низа од описни оценки (A, B, C, D, F) на 10

студенти.

const int NUM_STUDENTS = 10;

char grade [NUM_STUDENTS]; // Niza od 10 oceni

int idNumber;

Следуваат неколку примери кои покажуваат како низата може да се користи.

cin >> grade[2]; // Go cita sledniot printabilen karakter i go smestuva vo element od

//nizata so indeks 2.

grade[3] = ‘A’; // Dodeluva karakter ‘A’ na element od nizata so indeks 3.

IdNumber = 5; // Dodeluva 5 na indeksnata promenliva idNumber.

Grade[idNumber] = ‘C’ // Dodeluva karakter ‘C’ na komponenta od nizata indeksirana so

// idNumber (vo ovoj slucaj 5)

Page 135: C++ на македонски

138

for (idNumber = 0; idNumber < 10; // Za ovoj ciklus izlezot moze da bide

idNumber++) // FBCAFCAACB

cout << grade[idNumber];

for (idNumber = 0; idNumber < 10; // Za ovoj ciklus izlezot e pocitliv

idNumber++)

cout << “Student “ << idNumber

<< “ Grade “ << grade[idNumber]

<< endl;

Во последниот пример излезот би бил:

Student 0 Grade F

Student 1 Grade B

.

.

Student 9 Grade B

Предавање на низи како аргументи

По default (ако не е наведено со &) променливите од прости типови се пренесуваат

по вредност. Ако сакаме во функцијата да ја промениме вредноста на променливата, ја

пренесуваме по референца (прикачуваме & на типот на податок на променливата во

листата на параметри на функцијата). На пример:

int SomeFunc( float param1, // Prenos po vrednost

char& param2) // Prenos po referenca

{

.

.

}

Низите како аргументи секогаш се пренесуваат по референца и притоа не се

користи знакот & за да реферира дека низата се пренесува по референца. Кога низата се

пренесува како аргумент, нејзината базна адреса (base address), која е мемориска адреса

Page 136: C++ на македонски

139

на првиот елемент од низата, се испраќа на функцијата. Тогаш функцијата „знае“ каде е

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

елемент од низата. Следната е C++ функција која сетира на нула float низа со произволна

должина:

Void ZeroOut( /*out*/ float arr[],

/*in*/ int numElements)

{

int i;

for (i = 0; i < numElements; i++)

arr[i] = 0.0;

}

Во листата на параметри, декларацијата на arr не вклучува должина на низата. Дури

и да се вклучи должина, компајлерот ја игнорира. Компајлерот го интересира само дали

arr е низа, а не и нејзината должина. За да работи циклусот во функцијата, мора како

аргумент да се пренесе уште еден аргумент кој го означува бројот на елементи во низата –

numElements. Повикувачкиот код може да ја повика ZeroOut за float низа со која било

должина. Следниот код ја повикува функцијата да сетира на 0 две низи со различни

должини.

void ZeroOut (float[ ], int); // Prototip na funkcijata

.

.

int main( )

{

float velocity[30];

float refractionAngle[9000];

.

.

ZeroOut(velocity, 30);

ZeroOut(refractionAngle, 9000);

.

.

Page 137: C++ на македонски

140

}

Со прости променливи, пренесувањето на аргумент по вредност ја штити вредноста

на променливата во повикувачот од несакана промена во функцијата. Низите мора да се

пренесуваат по референца. За да се заштити низата во повикувачот од несакани промени,

во функцијата таа се декларира како константа. На пример, следната функција копира една

низа во друга.

void Copy( /*out*/ int destination[],

/*in*/ const int source[],

/*in*/ int size)

{

int i;

for (i = 0; i < size; i++)

destination[i] = source[i];

}

Зборот const гарантира дека кој било обид да се промени содржината на source

низата ќе резултира со грешка при компилација.

Низи од слогови

Како пример, да дефинираме книга за оценки, која претставува множество од

слогови (записи) на студенти како што следува:

const int MAX_STUDENTS = 150;

enum GradeType {A, B, C, D, F};

struct StudentRec

{

string stuName;

float gpa;

int examScore[4];

GradeType courseGrade;

};

StudentRec gradeBook[MAX_STUDENTS];

Page 138: C++ на македонски

141

Елемент од gradeBook е селектиран со индекс. На пример, gradeBook[2] е третата

компонента од низата gradeBook. Секоја компонента од gradeBook е слог (struct) од тип

StudentRec. За да пристапиме на courseGrade на третиот студент го користиме следниов

израз:

GradeBook[2].courseGrade

Компонентата на слогот gradeBook[2].examScore е низа. За да пристапиме на првата

компонента од низата, пишуваме:

GradeBook[2].examScore[0]

Следниот код ги печати имињата на секој студент:

for (count = 0; count < MAX_STUDENTS; count++)

cout << gradeBook[count].stuName << endl;

Дводимензионални низи

Дводимензионалните низи се користат за претставување елементи во табели со

редови и колони. На компонента во дводимензионалните низи може да се пристапи со

специфицирање на индексите на редот и на колоната во кои се наоѓа елементот.

Дводимензионална низа е множество од компоненти, сите од ист тип

структуирани во две димензии. На секоја компонента & се пристапува со пар на индекси

кои ја претставуваат локацијата на елементот.

Синтактичкиот шаблон за декларирање на дводимензионална низа е:

Тип на податок Име на низа [ConstIntExpression] [ConstIntExpression]

На пример:

const int NUM_ROWS = 100;

const int NUM_COLS = 9;

.

.

float alpha [NUM_ROWS] [NUM_COLS];

Овој пример ја декларира alpha како дводимензионална низа (матрица) на која сите

компоненти се од тип float. Инструкцијата

alpha[0] [5] = 36.4;

Page 139: C++ на македонски

142

доделува вредност 36.4 на елементот во ред 0 и колона 5.

Да погледнеме неколку примери.

int hiTemp [52] [7];

е декларација за матрица со 52 реда и 7 колони. Нека редовите ги претставуваат неделите

во годината, а колоните деновите во неделата. Во секое поле од матрицата се чува

највисоката температура во соодветниот ден. Ако сакаме да ги печатиме највисоките

температури во деновите од третата недела од годината, тоа го постигнуваме со следниов

код:

int hiTemp [52] [7];

int day;

for (day = 0; day < 7; day++)

cout << ‘ ‘ << hiTemp[2] [day];

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

enum Colors {RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET};

enum Makes {FORD, TOYOTA, HYUNDAI, JAGUAR, CITROEN, BMW, FIAT, SAAB};

const int NUM_COLORS = 7;

const int NUM_MAKES = 8;

float crashRating[NUM_COLORS] [NUM_MAKES]; //Verojatnost na sudir vo funkcija od

. //boja i model

.

.

crashRating [BLUE] [JAGUAR] = 0.83;

crashRating [RED] [FORD] = 0.19;

Процесирање на дводимензионални низи

Процесирање или пристапување на податоци во матрица може да се врши на еден

од следниве четири начина:

- случаен (random) пристап;

- пристап по редови;

- пристап по колони;

- пристап по цела матрица.

Page 140: C++ на македонски

143

Случаен пристап имаме кога пристапуваме на случаен елемент од матрицата со давање

произволни вредности на индексите. Разгледуваме четири примери на процесирање на

матрица:

1. сумирање на редови;

2. сумирање на колони;

3. иницијализација на матрицата со нули;

4. печатење на матрица.

Пред да ги разгледаме овие програмски сегменти, декларираме некои заеднички

константи

и променливи:

const int NUM_ROWS = 50;

const int NUM_COLS = 50;

int arr[NUM_ROWS] [NUM_COLS]; // Dvodimenzionalna niza

int row; // indeks za red

int col; // indeks za kolona

int total; // promenliva za sumata

Сумирање редови

Ако сакаме да направиме сума од елементите во четвртиот ред од матрицата, тоа го

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

total = 0;

for (col = 0; col < NUM_COLS; col++)

total = total + arr[3] [col];

cout << “Row sum “ << total << endl;

Ако сакаме да направиме суми за два реда, третиот и четвртиот, тоа го правиме со

вгнезден циклус:

for (row = 2; row < 4; row++)

{

total = 0;

for (col = 0; col < NUM_COLS; col++)

total = total + arr[row] [col];

cout << “Row sum: “ << total << endl;

Page 141: C++ на македонски

144

}

На ваков начин може да се сумираат сите редови од матрицата.

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

(rowsFilled, colsFilled), тоа го изведуваме на следниов начин:

for (row = 0; row < rowsFilled; row++)

{

total = 0;

for (col = 0; col < colsFilled; col++)

total = total + arr[row] [col];

cout << “Row sum: “ << total << endl;

}

Сумирање на колони

Ако сакаме да ги сумираме колоните од една блок матрица со димензии (rowsFilled,

colsFilled), тоа го вршиме на следниов начин:

for (col = 0; col < colsFilled; col++)

{

total = 0;

for(row = 0; row < rowsFilled; row++)

total = total + arr[row] [col];

cout << “Column sum: “ << total << endl;

}

Во овој случај, обратно од претходниот, надворешниот циклус ги контролира

колоните, а внатрешниот редовите.

Иницијализирање на матрица

Ако матрицата е со мали димензии, наједноставно е таа да се иницијализира во

декларацијата. За да иницијализираме матрица од два реда и три колони да изгледа вака:

14 3 -5

0 46 7

Page 142: C++ на македонски

145

можеме да ја користиме следнава декларација:

int arr[2] [3] =

{

{14, 3, -5},

{0, 46, 7}

}

Во оваа декларација иницијализаторската листа се состои од два елемента, од кои

секој е иницијализаторска листа. Првата внатрешна иницијализаторска листа го

иницијализира првиот ред, а втората листа го иницијализира вториот ред.

Ако матрицате е голема, непрактично е да се иницијализира во декларација. На

пример, ако матрицата е 100 реда со 100 колони, и вредностите се различни, најзгодно е да

се внесат во датотека од каде ќе се отчитуваат во run-time. Ако вредностите се исти,

вообичаен пристап е да се користи вгнезден циклус. Ако сакаме да ги сетираме на 0 сите

елементи од (NUM_ROWS, NUM_COLS) матрица, постапуваме на следниов начин:

for (row = 0; row < NUM_ROWS; row++)

for (col = 0; col < NUM_COLS; col++)

arr[row] [col] = 0;

Печатење на матрица

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

#include <iomanip> // Za setw( )

.

.

for (row = 0; row < NUM_ROWS; row++)

{

for (col = 0; col < NUM_COLS; col++)

cout << setw(15) << arr[row] [col];

cout << endl;

}

Пренесување на матрица како аргумент

Page 143: C++ на македонски

146

Кога дводимензионална низа (матрица) се пренесува како аргумент, повторно

базната адреса на матрицата на повикувачот се испраќа на функцијата, но не може да не се

наведе должината на двете димензии. Може да не се наведе должината на првата димензија

(број на редови), но мора да се наведе должината на втората димензија (број на колони).

Ова е така, затоа што матрицата во меморијата се чува во еднодимензионална низа. На

пример, ако имаме матрица beta за да го лоцира beta[1] [0], функцијата мора да го знае

бројот на колони. Така ако бројот на колони е 4, beta[1] [0] ќе дојде на на петтата локација

сметајќи ја базната адреса како прва локација. Значи во декларацијата на параметарот за

матрица, секогаш треба да биде наведена втората димензија. На пример:

void AnotherFunc (/* inout */ int beta[ ] [4])

{

.

.

.

}

Понатаму, бројот на колони на матрицата која повикувачот ја праќа на функцијата,

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

За да се избегнат проблемите со несогласување на должините на аргументите и

параметрите, се користи инструкцијата typedef, која дефинира тип дводимензионална низа

и потоа да ги декларира и аргументот и параметарот да бидат од тој тип.

На пример:

const int NUM_ROWS = 10;

const int NUM_COLS = 20;

typedef int ArrayType[NUM_ROWS] [NUM_COLS];

typedef инструкцијата наложува каде да се појави податокот ArrayType да биде заменет со

int.

Следната функција доделува иницијална вредност на сите елементи од матрица со

број на колони еднаков со NUM_COLS.

void Initialize (/*out*/ArrayType arr, // Matrica koja treba da se inicijalizira

/*in*/ int initVal) // Inicijalna vrednost

{

int row;

int col;

Page 144: C++ на македонски

147

for (row = 0; row < NUM_ROWS; row++)

{

for (col = 0; col < NUM_COLS; row++)

arr[row] [col] = initVal;

}

Page 145: C++ на македонски

147

(lektoriran tekst)

XV. Поинтери, динамички податоци и референцни типови

Поинтерите и референцните типови на податоци се атомски типови на податоци,

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

од другите атомски (прости) типови на податоци, како што се: integral, enum и float.

Променлива од еден од овие два типа на податоци не содржи вредност, туку мемориска

адреса на друга променлива. Адресните типови имаат две главни намени:

- тие можат да ja направат програмaтa поефикаснa во смислa на брзинатa и

користењeтo на меморијатa и

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

Поинтери

Поинтер тип на податок е атомски тип на податок кој се состои од неограничено

множество од вредности кои адресираат или покажуваат на локација на променлива од

даден тип.

Меѓу операциите дефинирани на поинтери, можат да се наведат операциитe на

доделување и тестирање за еднаквост.

Поинтер променливи

Зборот pointer не се користи во декларирање на поинтер променлива. Наместо него

се користи симболот *. Декларацијата: int* intPtr означува дека intPtr е променлива која

содржи адреса на int променлива. Следните се синтактички шаблони за декларирање на

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

Тип на податок* променлива;

Тип на податок *променлива1, *променлива2, ...;

Синтактичкиот шаблон покажува две форми, едната за декларирање на една

променлива и втората за декларирање на повеќе променливи во една декларација. Во

првата форма за компајлерот е сеедно каде ѕвездичката е поставена, така што следниве две

декларации се еквивалентни:

Page 146: C++ на македонски

148

int* intPtr;

int *intPtr;

Во согласност со синтактичкиот шаблон, ако се декларираат повеќе променливи во

една инструкција, на почетокот од името на секоја од променливите треба да се прикачи

ѕвездичка. Инаку, само првата променлива ќе биде третирана како поинтер променлива. На

пример декларацијата: int* p, q е еквивалентна на int* p; int q.

За да се избегнат грешки, најсигурно е секоја променлива да се декларира во

посебна инструкција. Ако се дадени декларациите: int beta; int* intPtr можеме да

направиме intPtr да покажува на beta со користење на унарниот оператор &.

intPtr = &beta;

Со оваа инструкција, мемориската адреса на beta се доделува на поинтер

променливата intPtr. Да претпоставиме дека intPtr и beta се лоцирани во мемориските

адреси 5000 и 5008 респективно. Со последната инструкција, мемориската адреса на beta

(5008) се доделува на поинтер променливата intPtr. Ова доделување илустративно е

прикажано на следната слика (сл. 15.1). Бидејќи во општ случај мемориските адреси не му

се познати на програмерот, односот меѓу поинтерот и променливата на која тој покажува

се илустрира со правоаголници и стрелки како на слика 15.2.

Page 147: C++ на македонски

149

Memor i ja

.

.

.

.

.

.

.

A dr esa I me na pr omenl i va

5000

5008

5008 intPtr

beta

sl . 15.1

in tP tr b e ta

sl . 15.2

За да се пристапи на променлива кон која покажува поинтерот, користиме унарен *

оператор и операцијата се нарекува дереференцирање. Така изразот

*intPtr

ја означува променливата кон која покажува поинтерот. Во нашиот пример intPtr покажува

на beta и инструкцијата

Page 148: C++ на македонски

150

*intPtr = 28

го дереференцира intPtr и доделува вредност 28 на beta. Оваа инструкција претставува

индиректно адресирање на beta. Машината прво пристапува на поинтерот intPtr, потоа ја

користи неговата содржина да ја лоцира beta. Обратно, инструкцијата

beta = 28

претставува директно адресирање на beta. Директното адресирање е како отворање на

поштенско сандаче (на пример сандаче 15) и наоѓање на пакет, додека индиректното

адресирање е како отворање на поштенско сандаче и наоѓање порака која кажува дека

пакетот е во поштенско сандаче 23.

Директно адресирање е пристапување на променливата во еден чекор со

користење име на променливата.

Индиректно адресирање е пристапување на променливата во два чекорa со

користење на поинтер кој ја дава локацијата на променливата.

Продолжувајќи со нашиот пример, имајќи ја предвид сл. 15.1, инструкциите:

*intPtr = 28;

cout << intPtr << endl;

cout << *intPtr << endl;

ќе дадат излез:

5008

28

Разгледуваме програмски сегмент кој вклучува повеќе променливи од тип поинтер:

enum ColorType {RED, GREEN, BLUE};

struct PatientRec

{

int idNum;

int height;

int weight;

};

int alpha;

ColorType color;

PatientRec patient;

Page 149: C++ на македонски

151

int* intPtr = &alpha;

ColorType* colorPtr = &color;

PatientRec* patientPtr = &patient;

Променливите intPtr, colorPtr, patientPtr се поинтер променливи кои покажуваат кон

променливи од различен тип.

Изразот *intPtr ја означува променливата кон која покажува intPtr. Променливата

кон која покажува intPtr може да содржи која било целобројна (интегер) вредност. Изразот

colorPtr означува променлива од тип ColorType. Таа може да ги содржи само вредностите

RED, GREEN или BLUE. Изразот patientPtr означува struct променлива од тип PatientRec.

Изразите (*patientPtr).idNum, (*patientPtr).height и (*patientPtr).weight означуваат idNum,

height и weight членови на *patientPtr.

Може да се забележи дека кај изразите за пристап на членови во слог,

дереференцирањето на слогот (struct) се става во загради по што следува dot нотацијата за

пристап на член од слогот. Заградите се неопходни, бидејќи dot операторот има повисок

приоритет на извршување од дереференцирањето. Така *patientPtr.weight би било

интерпретирано погрешно како *(patientPtr.weight).

Освен dot операторот, C++ располага со алтернативен оператор за селектирање на

членови во слогови, унии и класи. Тоа е -> или arrow операторот. По дефиниција:

PointerIzraz -> Ime na Clen

е еквивалентно на

(*PointerIzraz).Ime na Clen

Значи:

patientPtr -> weight

е еквивалентно со

(*patientPtr).weight

Се поставува прашање која од горните две варијанти да се користи при селекција

на членови на структури. Се препорачува dot нотацијата да се користи кога левиот операнд

е структура, класа или унија, а arrow нотацијата кога левиот операнд е поинтер кон

променлива од тип структура, класа или унија.

Ако имаме декларирано низа од поинтери кои покажуваат на низа од тип PatientRec

PatientRec* patPtrArray[20];

Page 150: C++ на македонски

152

тогаш изразот

patPtrArray[3] -> idNum

му пристапува на идентификациониот број на четвртиот пациент.

Следните инструкции се валидни:

*intPtr = 250;

*colorPtr = RED;

patientPtr -> idNum = 3245;

patientPtr -> height = 64;

patientPtrArray[3] -> idNum = 6356;

patientPtrArray[3] -> height = 73;

patientPtrArray[3] -> weight = 185;

Поинтер изрази

Порано видовме дека аритметичките изрази можат да содржат променливи,

константи, оператори, симболи и загради. Слично и поинтер изразите се составени од

поинтер променливи, поинтер константи одредени дозволени оператори и загради.

Во C++ постои само една дозволена литерална поинтер константа: вредноста 0.

Поинтер константата 0, наречена null pointer покажува кон ништо. Инструкцијата:

intPtr = 0;

доделува null pointer во intPtr. Оваа инструкција не предизвикува intPtr да покажува кон

мемориска локација 0. Null поинтерот не покажува кон ниту една мемориска адреса и затоа

шематски го означуваме со следниот знак:

intPtr

Наместо користење на константата 0, повеќе програмери ја користат именуваната

константа NULL која доаѓа од стандардната header датотека cstddef:

#include <cstddef>

.

.

intPtr = NULL;

Page 151: C++ на македонски

153

Како што е случај со другите именувани константи, идентификаторот NULL ја

прави програмата подокументирана. Грешка е да се дереференцира null поинтер, затоа што

тој не покажува кон никаква мемориска локација. Тој се користи само како специјална

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

if (intPtr == NULL)

DoSomething ( );

Иако 0 е единствена литерална константа од тип поинтер, постои друг поинтер

израз кој може да се цени како како константен поинтер израз, а тоа е име на низа без

индексни загради. Вредноста на овој израз е базната адреса на низата. Дадени декларации:

int arr[100];

int* ptr;

Инструкцијата на доделување

ptr = arr;

има ист ефект како и

ptr = &arr[0];

Овие две инструкции ја доделуваат базната адреса на arr во ptr. Име на низа без

загради е поинтер израз. Да го разгледаме кодот кој ја повикува функцијата ZeroOut за да

ги сетира на 0 компонентите на низа чија должина е дадена како втор аргумент на

функцијата:

int main( )

{

float velocity[30];

.

.

ZeroOut (velocity, 30);

.

.

}

Во функцискиот повик, името на низата без загради е поинтер израз. Вредноста на

овој израз е базната адреса на низата velocity. Оваа адреса се испраќа на функцијата. Ние

можеме да ја напишеме функцијата ZeroOut на еден од два начина. Првиот начин ни е

Page 152: C++ на македонски

154

познат од порано – декларирање на првиот параметар како низа со неспецифицирана

должина.

Void ZeroOut (/* out */ float arr[ ],

/* in */ int size)

{

int i;

for (i = 0; i < size; i++)

arr [i] = 0.0;

}

Алтернативно, првиот параметар може да го декларираме како float*, затоа што

параметарот ја зема адресата на float променлива (адресата на првиот елемент од низата).

Void ZeroOut (/* out */ float* arr,

/* in */ int size)

{

.

. // Teloto na funkcijata e isto kako vo prethodniot primer

.

}

Како и да го декларираме првиот параметар во функцијата, резултатот е ист за C++

компајлерот. Во ZeroOut функцијата arr е проста променлива која покажува на почетокот

на актуелната низа од повикувачот (сл.15.3).

Иако arr е поинтер променлива во ZeroOut функцијата, уште можеме да прикачиме

индексен израз на името arr:

arr[i] = 0.0;

Индексирањето на поинтер променлива е можно поради следното правило во C++:

Индексирањето е валидно за кој било поинтер израз, не само за име на низа (меѓутоа,

индексирање на поинтер има смисла само ако поинтерот покажува на низа).

Page 153: C++ на македонски

155

v e l o c i t y

[ 0 ]

[ 1 ]

[ 2 ]

f u n k c i j am a i n

f u n k c i j aZ e r o O u t.

.

.a r r

s i z e

s l . 1 5 .3

Следната табела ги листа најчестите операции кои можат да бидат применети на поинтери:

Оператор Значење Пример Забелешка

= Доделување ptr = &someVar; Освен за null pointer ptr1 = ptr2; Двата операндa мора ptr = 0; да бидат од ист тип * Дереференцирање *ptr ==, !=, <, Релациони ptr1 == ptr2 Двата операндa мора <=, >, >= оператори да бидат од ист тип ! Логичко не !ptr Резултатот е true, ако

операндот е 0 (null pointer), инаку e false

[ ] Индекс или ptr[4] Индексиран поинтер треба супскрипт да покажува на низа -> Селекција на ptr -> height Селектира член од struct, член класа или унија кон кои покажува поинтерот

Page 154: C++ на македонски

156

На пример, логичко не (!) може да биде користено да тестира за null поинтер:

if ( !ptr )

DoSomething( );

што е еквивалентно со

if (ptr == NULL)

DoSomething( );

Треба да се има на ум дека операциите во горната табела се однесуваат на

поинтери, а не на променливи на кои поинтерите покажуваат. На пример, ако intPtr1 и

intPtr1 се поинтер променливи од тип int*, тестот:

if ( intPtr1 == intPtr2)

споредува поинтери, а не променливи на кои тие покажуваат. Со други зборови

споредуваме адреси, а не интегер вредности. Ако сакаме да ги споредуваме променливите

на кои покажуваат поинтерите, инструкцијата е:

if ( *intPtr1 == *intPtr2)

Постојат и други операции врз поинтери како ++, --, +, -, +=, -=. Овие се оператори

на поинтери кои покажуваат на низа. На пример, изразот ptr++ предизвикува поинтерот да

покажува на следниот елемент од низата, ptr + 5 покажува на елемент од низата кој е за 5

елементи над елементот од низата на кој претходно покажувал поинтерот итн.

Динамички податоци

Во претходниот текст опишавме две категории на податоци во C++: статички и

автоматски податоци. Стаички податоци се глобалните променливи и локалните

променливи кои експлицитно се декларирани со клучниот збор static. Животниот век на

статичките променливи е ист со животниот век на програмата. Сите други локални

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

во кој се декларирани.

Со помош на поинтерите, C++ обезбедува трета категорија на податоци: динамички

податоци. Динамички податоци се променливи креирани за време на егзекуција на

програмата со помош на специјални операции. Во C++ овие операции се new и delete.

Page 155: C++ на македонски

157

Операцијата new има две форми, една за алоцирање на единечна променлива и една за

алоцирање на низа. Следното е синтактички шаблон:

new DataType

new DataType [IntExpression]

Првата форма се користи за креирање на единечна променлива од тип DataType.

Втората форма креира низа чии елементи се од тип DataType, при што бројот на елементи

на низата е даден со IntExpression. Следното се примери кои ги даваат двете форми:

int* intPtr;

char* nameStr;

intPtr = new int; // Kreira promenliva od tip int i ja dodeluva nejzinata adresa na

// intPtr

nameStr = new Char[6]; // Kreira niza od 6 char elementi i ja dodeluva nejzinata bazna

// adresa na nameStr

Операторот new прави две работи: креира неиницијализирана променлива (или

низа) од бараниот тип и враќа поинтер на оваа променлива (или на базната адреса на

низата). Променливите креирани со new се наоѓаат во слободниот дел на меморијата (heap)

кој е резервиран за динамички променливи. Динамичката променлива е неименувана и не

може да биде директно адресирана. Таа мора да биде индиректно адресирана преку

поинтер вратен од new операторот.

#include <cstring> // Za funkcija strcpy( )

.

.

.

int* intPtr = new int;

char* namestr = new char[6];

*intPtr = 357;

strcpy(namestr, “Ben”);

Библиотечната функција strcpy бара два аргумента кои претставуваат базни адреси

на две char низи. За првиот аргумент, ја пренесуваме базната адреса на динамичката низа

во слободната меморија, а за вториот аргумент компајлерот ја пренесува базната адреса на

Page 156: C++ на македонски

158

неименуваната локација, каде C стрингот “Ben” е лоциран. Следниве слики (15.4а и 15.4b)

го илустрираат горниот код.

[0] [1] [2] [3] [4] [5]

[0] [1] [2] [3] [4] [5]

?

? ? ? ? ? ?

intPtr

intPtr

nameStr

nameStr

357

'B' 'e' 'n' '\0' ? ?

a) int* intPtr = new int;char nameStr = new char[6];

b) *intPtr = 357; strcpy(nameStr, "Ben");

Free store

Free store

sl . 15.4

Динамичките податоци можат да бидат уништени во кое било време на

егзекуцијата на програмата, кога веќе нема потреба од нив. Операторот delete се користи да

уништи динамичка променлива. И овој оператор има две форми во зависност од тоа дали

брише единечна променлива или низа. Синтактичкиот шаблон е:

delete Pointer

delete [ ] Pointer

Користејќи го претходниот пример, можеме да ги уништиме (деалоцираме)

динамичките податоци на кои покажуваат intPtr и nameStr со инструкциите:

delete intPtr;

delete [ ] nameStr;

По извршување на овие инструкции, вредностите на intPtr и nameStr се

недефинирани. Тие можат, но и не мораат уште да покажуваат на деалоцираните податоци.

Пред да ги користиме овие поинтери повторно, мора да им се доделат нови вредности (т.е.

да им се доделат нови мемориски адреси). Операцијата delete не го брише поинтерот. Таа

ја брише променливата на која покажува поинтерот.

Кога се користи операторот delete, треба да се имаат на ум две работи:

Page 157: C++ на македонски

159

1. извршување delete на null поинтер е безефектна инструкција;

2. операторот delete мора да се приложи само на поинтер вредност која била добиена

претходно од new операторот.

Второто правило е важно. Ако се приложи delete на произволна мемориска адреса,

која не е во слободната меморија, резултатот е недефиниран. Конечно, треба да се има на

ум дека причината за користење на динамички податоци е да се заштеди мемориски

простор. Затоа е важно по завршувањето на користење на динамичката променлива таа да

биде избришана. Ако се чуваат динамичките променливи кога веќе не се потребни,

настанува ситуација наречена протекување на меморијата (memory leak). Ако ова се

случува често, може да останеме без меморија.

Да погледнеме уште еден пример на користење на динамички податоци.

int* ptr1 = new int; // Kreira dinamicka promenliva

int* ptr2 = new int; // Kreira dinamicka promenliva

*ptr2 = 44; // Dodeluva vrednost na dinamicka promenliva

*ptr1 = *ptr2; // Kopira edna dinamicka promenliva vo druga

ptr1 = ptr2; // Kopira eden pointer vo drug

delete ptr2; // Unistuva dinamicka promenliva

Секоја инструкција од овој пример е графички поткрепена на сл. 15.5. На сл. 15.5d,

по извршување на претпоследната инструкција од горниот програмски сегмент, може да се

забележи дека променливата на која претходно покажуваше ptr1 е уште присутна. Меѓутоа,

на неа не може да & се пристапи, бидејќи не постои поинтер кој покажува (реферира) на

неа. Ваква изолирана променлива се нарекува непристапен објект и е причина за

мемориско течење (memory leak). Исто така, на сл. 15.5е може да се забележи дека по

последната инструкција ptr1 покажува на променлива која повеќе не постои. Ваков

поинтер се нарекува висечки поинтер. Ако програмата подоцна се обиде да го

дереференцира ptr1, резултатот е непредвидлив.

Непристапен објект е динамичка променлива во слободниот мемориски простор

на која не реферира ниеден поинтер.

Висечки поинтер е поинтер кој покажува на променлива која веќе е уништена

(деалоцирана).

Page 158: C++ на македонски

160

sl . 15.5

?

?

ptr1

ptr2

INICIJALNI USLOVIa) int* ptr1 = new int; int* ptr2 = new int;

ptr1

ptr2

ptr2

ptr2

ptr1

ptr1

*ptr1

*ptr2

*ptr1

*ptr2

*ptr2

*ptr1

ptr1

ptr2

ptr2

ptr1

b) *ptr2 = 44;

c) *ptr1 = *ptr2;

d) ptr1 = ptr2;

e) delete ptr2;

?

?

?

44

44

44

44

44

44

Непристапниот објект и висечкиот поинтер се логички грешки. За да се избегнат

овие несакани појави во горниот код, треба да се вметнат две нови инструкции како што

следува. И овој код графички е поткрепен на сл. 15.6

Page 159: C++ на македонски

161

int* ptr1 = new int;

int* ptr2 = new int;

*ptr2 = 44;

*ptr1 = *ptr2;

delete ptr1; // Izbegnuva formiranje na nepristapen objekt

ptr1 = ptr2;

delete ptr2;

ptr1 = NULL; // Izbegnuva formiranje na visecki pointer

sl . 15.6

?

?

p tr1

p tr2

IN IC IJA L N I U S L O V I

a ) in t* p tr1 = ne w in t; in t* p tr2 = n ew in t ;

p tr1

p tr2

p t r2

p tr2

p t r1

p tr1

*p tr1

*p tr2

* p tr1

* p tr2

*p tr2

* p tr1

p tr1

p tr2

p tr2

p tr1

p tr2

p tr1

*p tr2

d) de le te p tr1

?

*p tr1*p tr2

?

g) p tr1 = N U L L ;

p t r1

p tr2

b ) *p t r2 = 44 ;

c ) * p tr1 = *p t r2 ;

e ) p tr1 = p tr2 ;

f) d e le te p tr2 ;

?

?

?

44

4 4

4 4

44

Page 160: C++ на македонски

162

Референцни типови

Исто како и поинтер променливите, референцните променливи содржат адреси на

други променливи. Изразот

int& intRef;

декларира дека intRef е променлива што може да содржи адреса на int променлива.

Следува синтактички шаблон за декларирање на референцна променлива:

Тип на податок& Променлива;

Тип на податок &Променлива, &Променлива,....;

Иако и референцните променливи и поинтерите содржат адреси на податочни

објекти, има две фундаментални разлики. Прво, операторот за дереференцирање (*) и

операторот за доделување адреса (&) не се користат со референцни променливи. По

декларирање на референцна променлива, компајлерот ја дереференцира секоја појава на

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

Користење на референцна променлива Користење на поинтер променлива

int gamma = 26; int gamma = 26;

int& intRef = gamma; int* intPtr = &gamma;

// intRef pokazuva na gamma // intPtr pokazuva na gamma

intRef = 35; *intPtr = 35;

// gamma ==35 // gama == 35

intRef = intRef + 3; *intPtr = *intPtr + 3;

// gamma == 38 // gamma == 38

Како што може да се забележи, сè што се работи на intRef, фактички се случува на gamma.

Втора разлика меѓу референцна и понтер променлива е дека компајлерот ја третира

референцната променлива како константен поинтер. Значи, единствена операција што се

случува на референцна променлива е нејзината иницијализација. Во овој контекст, C++ ја

дефинира иницијализацијата како:

a) експлицитна иницијализација во декларација;

b) имплицитна иницијализација со предавање на аргумент на параметар;

c) имплицитна иницијализација со враќање на вредност на функција.

На пример, инструкцијата:

Page 161: C++ на македонски

163

intRef++;

не ја инкрементира променливата intRef, туку променливата кон која intRef покажува,

затоа што компајлерот имплицитно ја дереференцира секоја појава на името intRef.

Референцен тип е прост (атомски) тип на податок кој се состои од неограничено

множество на вредности од кои секоја е адреса на променлива од дадениот тип.

Единствена операција дефинирана на референцна променлива е иницијализација, по која

секоја појава на променливата имплицитно се дереференцира.

Вообичаено користење на референцните променливи е да предадат ненизови

аргументи по референца. Да претпоставиме дека програмерот сака да ја промени

содржината на две float променливи со повикот на функција:

Swap( alpha, beta);

Постојат две опции за дизајнирање на функцијата Swap.

Првата опција е функцијата повикувач да ги прати мемориските адреси нa alpha и

beta експлицитно

Swap(&alpha, &beta);

при што функцијата мора да ги декларира параметрите да бидат поинтер променливи:

void Swap (float* px, float* py)

{

float temp = *px;

*px = *py;

*py = temp;

}

Втората опција е да се користат референцни променливи за да се елиминира

потребата од експлицитно дереференцирање:

void Swap (float& x, float& y)

{

float temp = x;

x = y;

y = temp;

}

Во овој случај, функцискиот повик не бара оператор & за аргументите:

Swap (alpha, beta);

Kompajlerot implicitno generira kod da gi prati adresite, a

ne содржината na alpha и beta.

Page 162: C++ на македонски

Содржина Глава 1. Програмирање и решавање на проблеми .................................................1

Глава 2. Синтакса и семантика на C++ и процес на програмски развој ..............6

Глава 3. Нумерички типови, изрази и output ……………………………............16

Глава 4. Програмски input ............................. ……………………………............29

Глава 5. Услови, логички изрази и контролна структура селекција ..................40

Глава 6. Циклус .......................................................................................................55

Глава 7. Функции ....................................................................................................66

Глава 8. Домен и животен век на функции ..........................................................78

Глава 9. Дополнителни контролни структури......................................................90

Глава 10. Прости типови на податоци ................................................................104

Глава 11. Структуирани типови на податоци ....................................................121

Глава 12. Низи .......................................................................................................129

Глава 13. Поинтери, динамички податоци и референцни типови ...................147