Programski Jazik C (II Izdanie) (Brajan V. Kernigan & Denis M. Richi) PDF

You might also like

You are on page 1of 350

S.

II 16945
db142

ПРОГРАМСКИ
ЈАЗИК

Брајан В. Керниган
Денис М. Ричи

'-' tamina
Ш (о. dp . G5,2.JSро11о Ѕ . /; 1(>9'-tS-- 8d1v 42.
cwr зз z. .;l~н. ..-

Програмски јазик
е
Второ издание

Брајан В. Керниган • Денис М . Ричи

.,Издавањето н а оваа кн ига е дел од програмата н а Владата на Република Македонија


за преведување н а500 ст ручни, науч ни книги и учеб ници од кои се учи н а врвните,
најдобри и најреномира ни универзитети во САД и Европската Унија, органИзирано
од страна на Министерството за информатичко оп штество".
Брајан В. Керниган
Денис М. Ричи

Програмски јазик
е

Второ издание
Authorized translation from the English language edition, entitled С PROGRAMMING LANGUAGE,
2nd Edition, 0131 103628 by KERNIGHAN, BRIAN W.; RIТCHIE, DENNIS, published by Pearson
Education, lnc, publishing аѕ Prentice Hall, Copyright © 1988, 1978 by Bell Telephone Laboratories,
lncorporated, by Prentice Hall, lnc., Upper Saddle River, NJ 07458

All the rights reserved. No part of this book may be reproduced or transmitted in any form or by
any means, electronic and mechanical, including photocopying, recording or by any information
storage retrieval system, without permission from Pearson Education, lnc. MACEDONIAN language
edition published by ARS LAMINA DOO, Copyright ©2009

Овластен превод од изданието на Англиски јазик под насл ов Програмски Јазик С, второ
издание, 0131103628 од Керниган, Брајан В.; Ричи , Денис; издадено од Pearson Education,
lnc., објавено како Prentice Hall, авторски права ©1988, 1978 од Bell Telephone Laboratories,
lncorporated, од Prentice Hall, lnc., Upper Saddle River, NJ 07458

Сите права се заджани. Ниту еден дел од оваа книга не смее да биде препечатуван или
пренесуван во било каква форма или со било как ви средства, електронски или механички,
вклучувајќи и фотокопирање, документирање или да биде сочуван во систем за повторно
пронаоѓање без писмена согласн ос т од издавачот.

Македонска издание 2009, авторски п рава © Арс Ламина ДОО, Скопје

Преведувач :
Стојан Котев

Стручен соработник :
м-р Миле Јова нов

CIP- Каталогизација за публикација


Национална и ун иверзитетска библиотека .Св . Климент Охридски ~ Скопје

004.432. 2с

КЕРНИГАН, Брајан В.
Програм ск и јазик С 1 Брајан В. Керниган, Денис М. Ричи. - Скопје
: Арс Ламина , 2009. - XIV, 350 стр. : илустр. ; 2б см

На наспор. насл. стр.: The С programming language 1 Brian W.


Kerпighan , Denis М . Ritchie.- Регистар

ISBN 978-б08-4535-48-5
1. Ств. н асл. на на спор. н асл. стр. 2. Ричи , Денис М. [avtor]. -
1. Kerпighan, Brian W. види Керниган, Брајан В .
.,..,...- · а~ Компјутерско програмирање - С (програмс ки јазик)
/ .. COB~SS.MK-ID 80939530
ФА "''<' 0::1 Zl\ [ '. >~"Tf'Q T( X ,..o,p А И
v -- t.o~ ·.• ..' ."-.:""" - ::.>..l('l :·or w-oи
.... " ':"Ј Г 1 L
БИБЛИОТЕК А

Сиг._Ј · .Е.. ,ff;;~f:J.!:' ~r}л. 42.


Инв. бp{p_/:.l!J
5jr. rp ,2 ~ 01
/2otto Содржина

Предrовор xi
Предrовор кон првото и3дание xii

Вовед 1

Глава 1 - Краток вовед во ја3ИКОТ е 5


1.1 По четок 5
1.2 Променливи и аритметички изрази 8
1.3 Исказот for 14
1.4 Симболички константи 16
1.5 Влез и излез на знаци 17
1.5.1 Копирање на датотека 17
1.5.2 Броење на знаци 19
1.5.3 Броење на линии 21
1.5.4 Броење на зборови 22
1.6 Низи 24
1.7 Функции 27
1.8 Аргументи - повикување по вредн ост 31
1.9 Низи од зна ц и 32
1.1 О Надворешни променливи и делокруг 35

Глава 2 - Типови, оператори и и3ра3и 41


2.1 Имиња на променливите 41
2.2 Податочни типови и нивна големина 42
2.3 Константи 43
2.4 Декларации 47
2.5 Аритметички оператор и 48
2.6 Релациони и логички о ператори 49
2.7 Конверзија на типови 50
2.8 Оператори за инкрементирање и декрементирање 54
2.9 Битски оператори 57
2.1О Оператори и изрази за доделување на вредност 59
2.11 Условни изрази 61
2.12 П риоритет и редослед на евалуирање 62

v
Vl Програмски јазик С Содрж ина

Глава З - Контрола на текот 65


3.1 Наредби и блокови 65
3.2 if- else 65
3.3 else- if 67
3.4 switch 69
3.5 Циклуси- while и for 71
3.6 Циклус do- wh ile 74
3.7 break и continue 76
3.8 goto и ознаки 77

Глава 4- Функции и структура на nporpaмa 79


4.1 Основи на функции 79
4.2 Функции кои враќаат нецелобројни вредности 83
4.3 Надв о р е шни променливи 86
4.4 Правила на делокругот (анг. ѕсоре) 93
4.5 3аглавишни датотеки 95
4.6 Статички променливи 97
4.7 Регистарски променливи 98
4.8 Блоковска стру кту ра 98
4.9 Иницијализација 99
4.1 О Рекурзија 101
4.11 е претпроцесор 103
4.11.1 Вклучу вање на датотеки 106
4.1 1.2 Макрозамена 104
4.11.3 Условно вклучува ње 107

Глава Ѕ - Покажувачи и низи 109


5.1 Покажувачи и адреси 109
5.2 Покажувачи и функциски аргументи 111
5.3 Покажува чи и низи 114
5.4 Адресна аритметика 117
5.5 По кажува чи кон знаци и функции 121
5.6 Ни з и од пока жувачи ; Покажувачи кон покажувачи 125
5.7 Повеќедимензионални низи 129
5.8 Иницијализација на низи од покажувачи 132
5.9 Покажувачи наспроти повеќедимензионални низи 133
5.1 О Аргументи од командна линија 134
5.11 Покажу вачи кон фу нкции 139
5.12 Комплицирани де кл арац ии 142

Глава 6- Структури 149


6.1 Основни поими за структур ите 149
6.2 Структури и функции 152
6.3 Низи од структури 155
Програмски јазик С Содржина vн

6.4 Покажувачи кон структури 160


6.5 Само-референцирачки структури 162
6.6 Пребарување на табела 168
6.7 typedef 170
6.8 Унии 172
6.9 Битови полиња 174

Гnава 7 - Вnез и изnез 177


7.1 Стандарден влез и излез 177
7.2 Форматиран излез- printf 179
7.3 Листи на аргументи со променлива должина 182
7.4 Форматиран влез- scanf 183
7.5 Пристап до датотеки 187
7.6 Справување со грешки- stderr и exit 190
7.7 Линиски влез и излез 192
7.8 Раз ни функции 194
7.8.1 Операции со стрингови 194
7.8.2 Тестирање на класата на з накот и конверзија 194
7.8.3 ungetc 195
7.8.4 Извршување на команда 195
7.8.5 Менаџирање со меморија 195
7.8.6 Математички функции 196
7.8.7 Генерирање на случаен број 197

Гnава 8- Системски интерфејс на UNIX 199


8.1 Дескриптори на датотеки 199
8.2 Примитивен 1/0- read и write 200
8.3 open, creat, close, unliпk 202
8.4 Случаен пристап- elseek 205
8.5 Пример- имплементација на fopen и getc 206
8.6 Пример - листање на содржина на директориуми 21 О
8.7 Пример - Алокатор (3аземач) на меморис ки п ростор 216

Додаток А - Референтен прирачник 223


А1 Вовед 223
А 2 Л ексички конвенции 223
А2.1 Белези 223
А2 . 2 Коментари 224
А2.3 Идентификатори 224
А2.4 Клучни зборови 224
А2 .5 Константи 225
А2.5 . 1 Целобројни константи 225
А2.5 .2 3наковни константи 226
А2.5.3 Реални константи 227
Vlll Програмски јазик С Содржина

А2.5.4 Ен ум ерациски константи 227


А2.6 Стрингови константи 227
А3 Синтаксичка нотација 228
А4 Значење на идентификаторите 228
А4.1 Класа на мемориски простор 228
А4.2 Основни податочни типови 229
А4.3 Изведени типови 230
А4.4 Квалификатори на типови 230
А5 Објекти и л вредности 231
Аб Конверзии 231
А6. 1 Интегрално нагорно претопување (промоција) 231
А6.2 Интегрални конверзии 231
Аб.З Цели и реални броеви 232
Аб.4 Реални типови 232
А6.5 Аритметички конверзии 232
Аб.б Покажувачи и цели броеви 233
А6.7 void 234
А6.8 Покажувачи кон void 234
А7 Изрази 235
А7.1 Генерирање на покажувачи 235
А7.2 Примарни изрази 236
А7.3 Постфиксни изрази 236
А7.3.1 Референци кон низа 237
А7.3.2 Функциски повици 237
А7.3.3 Референци на структура 238
А7.3.4 Постфиксно инкрементирање 239
А7.4 Унарни оператори 239
А7.4.1 Оператори за префиксно инкрементирање 239
А7.4.2 Оператор за адресирање 240
А7.4.3 Оператор за индирекција (дереференцирање) 240
А7.4.4 Операторот унарен плус 240
А7.4.5 Операторот унарен минус 240
А7.4.6 Оператор за единично комплементирање 241
А7.4.7 Оператор за логичка негација 241
А7.4.8 Оператор sizeof 241
А7.5 Претопувања (casts) 241
А7.6 Мултипликативни оператори 242
А7.7 Адитивни оператори 242
А7.8 Оператори за поместување 243
А7.9 Релациски оператори 243
А7.1 О Оператори за еднаквост 244
А7.11 Битски оператор И 245
А7.12 Битски оператор Исклучиво ИЛИ 245
А7.13 Битскиоператор ИЛИ 245
Прогр амски јазик С Содржина 1х

А7.14 Логички оператор И 245


А7 . 15 Логички оператор ИЛИ 246
А7 . 1 б Условен оператор 246
А7.17 Изрази за доделување 246
А7.18 Оператор запирка 247
А7.19 Константни изрази 248
А8 Декларации 248
А8 . 1 Спецификатори за класи на мемориски простор 249
А8.2 Спецификатори на тип 250
А8.3 Декларации на структура и унија 25 1
А8.4 Енумерации 255
А8.5 Декларатори 256
А8 .6 Значење на деклараторите 257
А8.6.1 Декларатори на покажувачи 257
А8.6.2 Декларатори на низи 258
А8 .6.3 Функци ски декларатори 259
А8.7 Иницијализација 261
А8 . 8 Имиња на типови 263
А8 .9 typedef 264
А8 . 1 О Екви валентност на типови 265
А9 Наредби 266
А9.1 Означени (анг. labeled) наредби 266
А9.2 Изразни наредби 266
А9.3 Сложени наредби 267
А9.4 Наредби за избор 267
А9.5 Наредби за п овторување 268
А9.6 Наредби за скок 269
А 1О Надвор е шни декларации 270
А 10.1 Функциски дефи н и ци и 270
А 10.2 Надворешн и декла рации 272
А 11 Делокруг и поврзување 273
А 11.1 Лексички дело круг 273
А 11 .2 Поврзување 274
А 12 Претпроцесирање 275
А 12.1 Триграф секвенци 275
А 12.2 Спојување на линии 276
А 12.3 Дефиниција на макроа и нивно проширување 276
А 12.4 Вклучување на датотеки 279
А 12.5 Условна компилација 279
А 12.6 Линиска контрола 281
А 12.7 Генерирање на грешка 28 1
А 12.8 pragma 281
А 12.9 Празна директива 282
А 12.1 О Предефинирани имиња 282
х Програмск и ја зик С Содржина

А 13 Граматика 282

Додаток Б - Стандардна библиотека 291


Б1 Вле з и излез:<stdio.h> 29 1
Б 1.1 Операции со датотеки 292
Б 1.2 Форматиран излез 294
Б 1.3 Форматиран влез 297
Б 1.4 Функции за влез и излез на знаци 299
Б1.5 Функции за директен вле з и излез 300
Б1 .6 Функции за позиционирање на датотека 300
Б 1.7 Фу нкции за грешка 30 1
Б2 Проверка на класата на знак: <ctype.h> 302
Б3 Функции за стрингови : <string.h> 302
Б4 Математички функции: <math.h> 304
БЅ Корисни функции: <stdlib.h> 305
Бб Дијагностика : <assert.h> 308
Б7 Променливи листи на аргументи: <stdarg.h> 309
Б8 Нелокални скокови: <setjump.h > 309
Б9 Сигнали : <signal.h > 31 О
Б 1О Функции за датум и време: <time.h> 311
Б 11 Граници дефинирани со имnлементацијата: <limits.h> и <f1oat.h> 313

Додаток В - Преглед на промените 317

Индекс 321
Предговор

Од издавањето на "Програмски јазик е во 1978 година, светот на компју­


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

јазик на оперативниот систем UNIX.


Сите три фактори, растечката популарност на С, промените во јазикот со те­
кот на годините и креирањето на компајлери од страна на тие што не учеству­
ваа во неговиот дизајн, влијаеја да се укаже на потребата за попрецизна и посо­
времена дефиниција на јазикот, отколку таа што беше презентирана во првото
издание на оваа книга . Во 1983 година, Американскиот национ ален институт за
стандарди (aнг.A merican National Standard lnstitute - ANSI) формираше комитет,
чија цел беше да се направи " недвосмислена и машински - независна дефини ­
ција на јазикот С". Резултат од сето тоа е ANSI стандардот за С.
Стандардот ги формализира конструкциите кои беа посочени, но не и опи­
шани во првото издание, како што се доделувањето на структурите и енумера ­

циите. Истиот одредува нов начин на декларирање н а функциите кој овозмо­


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

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


е дефиниран од ANSI стандардот. Иако ги забележа вме местата кај кои јазкот
еволуираше, одлучивме исклучително да пишуваме во новиот облик. Во најго­
лем дел разликите се не з нач ител ни ; највпечатлива измена е н овиот начин на
декларирање и дефинирање на функциите. Модерните компајлери веќе ги под­
држуваат најголемиот дел од деловите на стандардот.
Се обидовме да ја задржиме концизноста од првото изда ние. С не е голем
јазик, п а не е добро кога е објаснет со голема книга . Го доработивме претставу­
вањето на критичните можности како што се покажувачите, кои ја претставу-

Xl
xii Предговор

ваат сржта на програмирањето во С. Ги прочистивме оригиналните п римери и


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

ите во зборови и обратно. Како и претходно и овде сите примери беа тестирани
директно од текстот, т.е. во форма препознатлива за компјутерите.
Додатокот А, упатството за работа, не го претставува стандардот, туку е наш
обид да се прикаже неговата сушти на во пократок облик. Предвиден е да биде
лесно разбирлив од страна на програмерите, но не и да се користи како дефи­
ниција од страна на креаторите на компајлери - таа улога недвосми слена му
припаѓа на самиот ста ндард. Додатокот Б претставува преглед н а можностите
на стандардната библиотека. Ова , исто така, е наменето за пов икување од стра­
на н а програмерите, а не имплементирачите. Додатокот В нуди краток преглед
на измените во однос на оригиналното издание.

Како што рековме во предговорот на првото издание, " С станува покори­


сен како што расте и искуството во работата со него ". После десетгодишно до­
п олнител но искуство, се уште го мислиме тоа . Се надеваме дека оваа книга ќе
ви помогне да го научите С и успешно да го користите.
Длабоко сме задолжени на пријателите кои помогнаа при изработката на
второто издание. Џон Бентли Даг Гвин, Даг МекИлрој, Питер Нилсон и Роб Пајк
ни дадоа коментари речиси на секоја страна од првичниот ракопис. Се заблаго­
даруваме на Ал Ахо, Денис Алисон, Џо Кемпбел, Г.Р. Емлин, Карен Фортгенг, Ален
Холјуб, Ендрју Хјум, Дејв Кристол, Џон Линдерман, Дејвид Просер, Џин Спафорд
и Крис ван Вајк заради внимател ната контрола на напишаниот текст. Со своите
забелешки ни помогнаа и Бил Чествик, Марк Керниган, Енди Кен инг, Робин Лејк,
Том Лондон, Џим Риидс, Кловис Тондо и Питер Вајнбергер. Дејв Просер одгово­
ри на голем број детаљни прашања во врска со ANSI стандардот. Постоја но го
користевме С ++ преведувачот на Бјарн Штрауструп за локално тестирање на
нашите прог рами, а Дејв Кристол ни го обезбеди ANSI С компајлерот со кој беа
направени за вршните тестирања. Рич Дрекслер многу ни помогна при префр­
лањето на ракописот во електронска форма . Н а сите ним и скрено им се забла­
годаруваме.

Брајан В. Керниган
Денис М . Ричи
Предrовор кон првото издание

С е програ мски јазик со општа намена карактеризира н со мал број на изра­


зи, модерна контрола на текот и структурите на податоц и, како и голем број на
оператори. С не е "многу виш" јази к, ниту, пак, е " голем", и не е специјализиран
за некоја посебна област на примена. Меѓутоа, општоста и отсуството на огра­
ничувања го прават поприфатлив и поефикасен од останатите, под претпостав­
ка помоќни, програмски јазици.
Оригинално С беше дизајниран за, и имплемантиран на UNIX оперативен
систем на DEC PDP - 11, од Денис Ричи. Оперативниот систем, С компајлерот и
сите UNIX-oви апликации (вклучително и цело купниот софтвер кој беше корис­
тен во подготовката на оваа книга ) се напишани во С. Исто така, постојат про­
дукциски компајлери за неколку други машини, вклучувајќи ги IBM System/370,
Honeywell 6000 и lnterData 8/ 32. С, сепак, не е непосредно поврзан со некој по­
себен хардвер или систем, а и лесно е да се напишат програми кои без никакви
измени може да се користат на која било машина која поддржува С.
Оваа книга е предвидена да му помогне на читателот да научи да програми­
ра во С. Содржи основни поставки, кои ќе овозможат на новите корисници да
за почнат со работа во најкраток можен рок, посебни поглавја за секоја важна
тема и упатство за работа. Најголемиот дел од работата се темели на читање, пи­
шување и разработка на примерите, а не на штуро претставување на правила.
Во најголем број случаи, примерите се целосни програми, а не изолирани дело­
ви од програма. Сите примери се проверени директно од текстот, кој е во облик
разбирлив за компјутерите. Покрај претставувањето на начини како оптимално
да се употребува јазикот, се трудевме, таму каде што беше можно, да при кажеме
корисни алгоритми и принципи на добар стил и издржан дизајн .
Книгата не претставува вовед во програмирање; зема предвид де ка имате
познавања од основните програмски концепти како променливи, искази за

доделување, циклуси и функции. Сепак, книгата, и вака како што е, дозволува


nочетник да може да ја чита и да го научи јазикот, иако многу би помогнале
консултации со колеги кои се подобро запознаени со С .
Нашето искуство покажува дека С е подобен, експресивен и сенаме нски ја­
зик за голема група на програми. Лесен е за уче ње, му се зголемува корисноста
како што расте искуството со него. Се надеваме дека оваа книга ќе ви помогне

X lll
xiv Предговор кон првото издание

во неговото правилно користење.

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


кон оваа книга и кон нашето задоволство истата да ја пишуваме. Посебно Мајк
Бјанки, Џим Блу, Стју Фелдман, Даг Мекилрој, Бил Руум, Боб Росин, и Лери Рос­
лер внимателно ги прочитаа неколкуте верзии. Исто така , се заблагодаруваме
на Ал Ахо , Стив Бум, Ден Дворак, Чак Хели, Дебие Хели, Маерион Херис, Ри к
Холт, Стив Џонсон, Џон Меши, Боб Митз, Ралф Мјуха, Питер Нелсон, Елиот Пин­
сон, Бил Плогер, Бил Плогер, Џери Спивак, Кен Томпсон, Питер Вајнбергер за
нивните коментари кои беа од помош во различни периоди и на Мајк Леск и
Џои Осани на сесрдната помош во обработката на текстот.

Брајан В . Керниган
Денис М . Ричи
Вовед

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


систем UNIX на којшто и беше развиен , бидејќи и системот и поголемиот број
на програми кои работат на UNIX се напишани во е . Јазикот, сепак, не е вр­
зан за еден единствен оперативен систем или машина (компјутер); и иако се
нарекува ,.јазик за системско програмирање " затоа што се користи во пишу­
вање на компајлери и оперативни системи, подеднакво добро се користи и за
п рограмирање во други области . Повеќето значајни идеи на е потекнуваат од
јазикот BePL развиен од Мартин Ричардс. Влијанието на BePL врз е е остварено
индиректно преку јазикот В кој во 1970 година беше напишан од Кен Томпсон за
првиот UNIX систем на компјутерот DEC PDP-7.
BePL и в се јазици .,без податочни типови". Наспроти нив, е нуди цел дијапа ­
зон податочни типови. Основните типови се знаците, како и цели броеви и ре­
ални броеви во повеќе големини. Дополнително, постои хиерархија на изведе­
ни типови податоци креирани преку покажувачи, полиња, структури и унии.

Изразите се состојат од оператори и операнди; кој било израз вклучително и


доделувањето на вредност или повик на функција, може да биде исказ ( наред­
ба). Покажувачите овозможуваат машински-независна адресна аритметика .
е нуди основни конструкции за контрола на текот кои се потребни за до ­
бро структуирани програми: групирање на искази, донесување на одлука (if-
else), избор на еден од повеќе случаи (swi tch), повторување со проверка за
завршување на почетокот(whilе) или на крајот( dо), потоа, ран излез од ци клус
пред крајот (break).
Функциите можат да враќаат вредности од основните типови , структу р и ,
унии, или покажувачи. Која било функција може да се повикува рекурзивно.
Локалните променливи во општ случај се .,автоматски ", или се креираат со се­
кое ново повикување. Дефинициите на функциите не може да се вгнездени, но,
променливите можат да се декларираат структурирани во блокови. Функциите
на една е програма може да се наоѓаат во различни изворни датотеки, кои
се компајлираат поодделно. Променливите може да бидат внатрешни за некоја
функција, надворешни (кои се видливи само во рамките на една изворна дато­
тека) или достапни на целата програма.
Претпроцесорс ката фаза извршува макрозаменувања во изворниот текст
на програмата, вклуч у вање на други изворни датотеки и нивното соодветно
2 Вовед

компајлирање.
С е јазик од релативно "ниско ниво". Ваквата карактеризација не претставу­
ва недостаток, туку означува дека С работи со истите видови на објекти со кои
работат и самите компјутери, а тоа се знаците, броевите и адресите. Наведените
објекти може да се комбинираат и преместуваат со помош на аритметички и
логички оператори имплементирани од постојни машини.
С не нуди операции кои работат директно со сложени објекти како што се
стринговите (низи од знаци) , множествата, листите или низите . Не постојат опе­
рации кои обработуваат цела низа или стринг, иако структурите може да бидат
копирани како целина. С не дефинира ниту една друга можност за алоцирање
на мемориски простор освен статичката дефиниција и дисциплината на скла­
дот (анг. stack), која е овозможена со локалните променливи од функциите; нема
хип (анг. heap) ниту собирање на ѓубре (анг. garbage collection). На крај, самиот С
не нуди влезно/ излезни можности; во него не постојат READ или WRITE искази,
ниту вградени методи за пристап до датотеките. Сите овие механизми од пови­
соко ниво мора да бидат обезбедени од функции кои се повикуваат експлицит­
но. Речиси сите имплементации н аС имаат релативно стандардна колекција од
такви функции.
Слично, С нуди само директен, единичен контролен тек: тестови, циклуси ,
групирање и потпрограми , но не и мултипрограмирање, паралелни операции,

синхронизација или корутини.


Иако непостоењето на некои од овие карактеристики може да изгледа како
сериозен недостаток ("Значи, за да споредам два стринга морам да повикам
функција?"), одржувањето на јазикот на скромна големина нуди реални пред­
ности. Бидејќи С е релативно мал јазик, лесен е да се опише на мал простор и
да се научи брзо. Еден програмер со полно право може да очекува да го знае,
да го разбира и редовно да го употребува целиот јазик.
Долга низа години, единствена дефиниција за С беше референтниот при­
рачник од првото издание на оваа книга. Американскиот национален институт
за стандарди (ANSI) во 198З годин а основа здружение кое се стремеше кон мо­
дерна и целосна дефиниција на С . Се очекува AN$1 стандардот, или ANSI С да
биде одобрен 1988 година. Сите карактеристики на стандардот се веќе подржа­
ни од модерните компајлери.
Стандардот се базира на оригиналниот референтен прирачник. Јазикот е про­
менет незначително; една од целите на стандардот беше да се осигура дека по­
веќето постојни програми ќе останат применливи и понатаму, или, во колку тоа не
успее, компајлерите да даваат предупредување за нивното поинакво однесување.
За повеќето програмери, нај з начајн а та промена е во новата синтакса за
декларирање и дефинирање на функциите. Декларацијата на функцијата сега
може да вклучи опис на аргументите на функцијата; синтаксата на дефиницијата
се менува за да соодветствува. Оваа дополнителна информација му олеснува
на компајлерот при откривањето на грешките кои настануваат заради несоод­
ветство на аргументите; според нашето искуство , тоа е многу корисен додаток

кон јазикот.
Програмски јазик С 3

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


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

Втор значаен придонес кон стандардот е дефиницијата на библиотека која


го следи С . Таа одредува функции за пристап кон оперативниот систем (на
пример, читање и запишување во датотека ), форматирање на влез и излез, ало­
кација на меморијата, работа со стрингови и слично. Збирката од стандардни
заглавја осигурува еднозначен пристап до декларациите, функциите и пода­
точните типови. Програмите што ја користат оваа библиотека при интеракција
со машината на која се извршуваат, се сигурни во компатибилноста. Голем број
библиотеки се многу слични со моделот на "стандардната влезно/излезна биб­
лиотека" на UNIX системот. Оваа библиотека беше оп ишана во првото изда ние
и беше, исто така, во масовна употреба и на другите системи. И овој пат, многу
програмери нема да уочат некои позначителни измени.

Поради тоа што податочните типови и контролните структури кои се обез­


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

Иако С е компатибилен со можностите на повеќето компјутери , тој е незави­


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

шуваат без побарува ња за промени во хардверот. Стандардот обезбедува екс­


плицитност на сите аспекти на преносливос та , а пропишува и множество ко н­

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


С не е стро го типизиран јазик, но како што се ра зв ив а ше, н его в ата контрола
врз типовите стануваше поцврста. Оригиналната дефиниција наС не одобрува,
но дозволува замена на покажувачи и цели броеви; после подолг период и тоа
беше отстранета и стандардот сега бара соодветни декларации и експлицитни
конверзии, кои веќе беа наметнати од добрите компајлери. Новиот начин на
декларирање на функциите, исто така, е чекор во таа насока. Компајлерот ќе
предупреди на пове ќето грешки во типовите, но не постои авто матска претво­
рање на неусогласените податочни типови . С, сепак, ја з адржу ва основната
филозофија дека програмерите знаат што прават; тој само бара од нив експли­
цитно да ги изразат намерите .

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


погрешен приоритет; некои делови од синтаксата можел е да бидат и подобри.
И покрај се, С се докажа како еден од најекспресивните јазици за голем број
различни апли ка ции .
4 Вовед

Книгата е организира на на следниот начин :


Поглавјето 1, е четиво за основните својства на е. Целта е, читателот да за ­
почне со работа во најкраток можен рок, бидејќи веруваме дека најдобар начин
за учење на ја з икот е пишувањето на програми во него. Четивото, сепак, под­
разбира активно познавање на основните елементи на програмирањето; тука
нема да најдете појаснувања во врска со компјутерот, компајлирањето, ниту,
пак, објаснување што значи n=n+l. Иако се обидовме, таму каде што беше мож­
но, да прикажеме корисни програмски техники, книгата нема намера да биде
учебник за структури на податоци и алгоритми; кога бевме принудени да изби­
раме, се концентриравме на јазикот.
Поглавјата од
2 до б разгледуваат различни аспекти на јазикот е со повеќе
детали, многу поформални отколку Поглавјето 1, иако и тука акцентот е на
целосните програмски примери, а не на изолирани фрагменти. Поглавјето 2
ги обработува основните податочни типови, оператори и изрази. Поглавјето 3
ја обработува контролата на текот: if-else , switch, while , for итн. Пог­
лавјето 4 ги покрива функциите и програмските структури- надворешни про­
менливи, правила на опсегот , повеќе изворни датотеки и слично- и, исто така ,
споменува и датали од претпроцесорот. Поглавјето 5 ги дискутира покажува ­
чите и адресната аритметика. Поглавјето б зборува за структурите и униите.
Поглавјето 7 ја опишува стандардната библиотека, која на оперативниот
систем му обезбедува едноставен интерфејс. Оваа библиотека е дефини рана со
ANSI стандардот и треба да е поддржана на сите машини кои поддржуваат е , за
да можат, про грамите кои ја користат за влез/ излез и друг пристап кон опера­
тивниот систем, да се префрлат без измени од систем на систем .
Поглавјето 8 го опишува поврзувањето меѓу е програмите и оперативни от
систем UNIX, концентрирајќи се на влезно/излезните операции, датотечниот
систем и алокацијата на мемориски простор. Дел од ова поглавје е специфичен
за UNIX системите, но и програмерите кои ги користат останатите системи мо­
жат да најдат корисен материјал, вклучително и делумен поглед во тоа како е
имплементирана една верзија од стандардната библиотека со некои забелешки
за портабилноста.
Додатокот А содржи референтен прирачник . Основниот приказ на синта кса ­
та и семантиката на е е самиот ANSI стандард. Овој документ претежн о е наме­
нет за пишувачите на компајлери. Овде, референтниот прирачник ја прикажува
дефиницијата на јазикот поконцизно, а не на вообичаениот начин. Додатокот Б
е резиме на стандардната библиотека и е попотребен за корисниците на истата
отколку за тие што ја имплементираат. Додатокот В дава краток преглед за из­
мените во однос на оригиналниот ја з ик. Во случај на сомнеж, сепа к, ста ндардот
и сопствениот компајлер се нај компетентни авторитети за ја з икот.
Глава 1: Краток вовед во ја3ИКОТ е

Да започнеме со брз вовед во С . Намерата ние да ги при кажеме основните


елементи на јазикот низ реални програми , но без да навлегуваме во детал и,
правила и исклучоци. Во овој момент, не се обидуваме да бидеме опширни ,
ниту прецизни (иа ко е планирано примерите да се точни) . Сакаме на најбрз
можен начин да ве доведеме на ниво на кое ќе можете сами да пишувате корис­
ни програми, а за да го направиме тоа , потребно е да се концентрираме на ос­
новите: променливи и константи, аритметика, контрола н а текот, функции, и
основи на влез и излез. Од оваа глава намерно ги изоставуваме карактеристи ­
ките наС кои се потребни за пишување на поголеми програми . Тука пр ипаѓаат
покажувачите , структурите, повеќето оператори од богатото множество од
оператори во С , неколку наредби за контрола на текот и стандардната библи­
отека.

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


може да се најде комплетен приказ на која било карактеристика на јази кот , а
туторијалот, каков што е краток, може да ве наведе во погрешна насока . И би­
дејќи прикажаните примери не ги користат сите можности наС, тие не се так а
концизни и елегантни какви што би можеле да бидат. Се обидовме да ги мини­
мизираме таквите последици. Друг недостаток е тоа што во подоцнежн ите гла ­
ви со причина се повторува нешто од оваа глава . Сметаме де ка повторувањето
ќе ви помогне повеќе отколку што ќе ве нерви ра .
Во секој случај, искусните програмери би требало да бидат во можност
да го извлечат најважното од оваа глава, во сооднос со нивните потреби .
Почетниците треба да го надополнат читањето со пишување на мали прогр а­
ми , слични на приложените . И двете групи можат д а го користат материјалот
од оваа глава како основа над која ќе се наддаваат подеталните информации ,
кои следат од почетокот на Глава 2.

1.1 Почеток

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


грами во истиот. Првата програма што ќе ја напишеме, е иста за сите јазици :

Да се испишат зборовите

5
6 Краток вовед во јазикот С Глава 1

hello, world

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


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

Во С, програмата за испишување на "Hello, world" изгледа вака

#include <stdio.h>

main()
{
printf("hello, world\n");

Како ќе ја покренете оваа програма за виси од системот што го користите.


Како специфичен пример, на UNIX оперативен систем можете да креирате про­
грама во датотека чие име завршува со". е ", како што е hello. е и кој а поната ­
му ќе се компајлира со командата

се hello.c

Ако никаде не сте направиле грешка, во смисла да сте пропуштиле знак или
сте напишале некоја буква погрешно, компајлирањето ќе биде тив ко изведено
и ќе се создаде извршна датотека имен у вана а. out. Ако ја подигнете а . out со
отчукување на командата

a.out

ќе се испечати

hello, world

Сега да ја објасниме програмата . Една С програма, без оглед на големина­


та, се состои од функции и променливи. Една функција содржи наредби (искази)
кои ги определуваат операциите што истата треба да ги изврши , а во промен­
ливите се сместуваат вредности , кои се користат во текот на прес метување то.

Функциите во С изгледаат слично на подрутините и функциите во Фортран или


на функциите и процедурите во Паскал . Нашиот пример е функција со име
main. Се разбира , имате слобода на функциите да им дадете имиња по ваша
желба, но името "main" има посебна намена - вашата програма за почну ва со
извршување од почетокот на функцијата main. Ова значи дека секоја пр о грама
мора да има " main" некаде во себе.
Функцијата main обично ќе повикува други функции кои ќе помогнат во неј­
зините пресметки; тоа ќе бидат некои функции кои вие ги имате напишано,
101 Почеток 7

а други готови од веќе понудени те би блиотеки о Првата линија на програмата

#include<stdiooh>

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


1 излезна библиотека ; оваа линија се појаву ва на почетокот на многу е изв орни
датотеки о Стандардната библиотека е опишана во Глава 7 и Додаток Б о
Еден од начините на размена на податоци помеѓу функции е повикува чката
функција да обезбеди листа на вредности , наречени аргументи , кон функција­
та која се повикува о Малите загради после името на функцијата, ја заградуваат
листата на аргументи о Во нашиот случај, main е дефинирана како функц ија која
не очекува никакви аргументи , нешто што е претставена со празна листа () о

#include <stdiooh> вклучува информација за стандардната


библиотека

main ()
дефинира функцuја наречена
main која нема аргументи

наредбите во main се омеiени со голелш


printf( "hello , world\ n " ) ; заградu

main ја повикува библuотечната


функција priпtf
за да ја испеч ати оваа секвенца од знаци
\n претставува знак за нова линија

Првата програма во С

Наредбите во функциите се затворени со големи загради { } о Функцијата


main содржи само една наредба

printf ( " hello, world \ n " ) ;

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


аргументи во мали загради о На таков начин , ја повикуваме printf функцијата
со аргумент " hello , world \n" о printf е библиотечна функција која печати
излез, во овој случај стрингот од знаци п омеѓу наводниците о
Секвенца од з наци во наводници , како што е "hello , world \ n ",
се нарекува стринг или стринг константа о За сега, стринговите ќе
ги користиме само како аргументи на printf и некои други функции о
Секвенцата од стрингот \n, во е претставува знак за нова линија, кој при
8 Краток вовед во јазикот е Глава 1

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


ставите \n (потенцијално корисен експеримент) , ќе уочите дека после печа­
тењето на излезат не постои ниту една линија вишок . Морате да употребите \n
со цел, знакот за нов ред да се при клуч и на аргументот од printf ; ако напише­

те нешто како

printf ( "hello , world


\\) ;

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


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

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

#inelude <stdio.h>

main()
{
printf( "Hello ," ) ;
printf( " world" ) ;
printf( " \n " );

што ќе произведе идентичен излез .


Забележете дека \n репрезентира само еден единствен знак. ееквенца за
излез од облик \n обезбедува општи проширлив механизам за претставување
на тешките за типкање или на невидливите знаци. Помеѓу другите знаци кои
е ги обезбедува се \ t за таб , \b за бришење на знак наназад , \ " за двоен на­
водник и \\ за самиот контраниз (анг. backslash) . Комплетната листа се наоѓа
во Поглавје 2 . 3

Вежба 1-1 . Извршете ја програмата "Hel1o, world" на вашиот систем.


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

Вежба 1-2 . Експериментирајте за да откриете што се случува кога знаковната


низа во аргументот на printf содржи \е, каде е е некој знак кој не беше спом­
нат погоре.

1.2 Променливи и аритметички изрази

еледната програма ја користи формулата ос = (5/ 9) (°F-32) за да ја отпечати


табелата на температури во Фаренхајтови степени како и нивните Целзиусови
еквиваленти:
1.2 Променливи и аритметички израз и 9

о -17
20 -6
40 4
60 15
80 26
100 37
120 48
140 60
160 71
180 82
200 93
220 104
240 115
260 126
280 137
300 148

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


main. Подолга е од онаа што печатеше " hello , world" , но не е комплицира­
на. Воведува неколку нови идеи, вклучувајќи коментари , декларации , про­
менливи, аритметички изрази , циклуси , и форм атиран излез.

#include <stdio.h>
/* nечатеае на Фарнхајт-Целзиус табела
за fahr =О , 20 , . .. , 300 */
main ()
{
int fahr , celsius ;
int lower, upper, step;

lower = О ; /* долна граница на тенnературната схала */


upper = 300 ; /* горна граница */
step = 20; /* големина на чехор */

fahr = lower;
while (fahr <= upper) {

celsius = 5 * (fahr-32) 1 9 ;
printf("%d\t%d\n", fahr, celsius);
fahr = fahr + step ;

Д~ете линии

/* nечатеље на Фарнхајт-Целзиус табела


за fahr =О , 20 , ... , 300 */
10 Краток вовед во јазикот С Глава 1

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


Кои било знаци помеѓу /* и * 1 се игнорираат од компајлерот; слободно мо­
жат да се користат, со цел, програмата да изгледа поразбирлива . Комента р ите
може да се стават секаде каде што може да се стави празно место , табулатор
или знак за нова линија. Во С, сите променливи мора да се декларираат пред
да можат да се користат, обично на почетокот на функцијата пред која било
извршна наредба. Декларацијата ги најавува својствата на променливите ; се
состои од име и листа на nроменливи, како во

int fahr , celsius;


int lower, upper, step;

Податочниот тип int означува дека наведените променливи се цели бро­


еви; за разлика од float, кој означува реален број, т. е број кој може да има
децимален дел. Опсегот и на int, и на floa t за виси од машината која ја корис­
тите; 16-битни int , кои се во опсег од -32678 до +32767, се среќаваат често,
исто како и 32-битни int. float вредностите во општ случај се со 32-битна
големина , со најмалку 6 значајни цифри и распон во општ случај помеѓу 10- 38
и 10+38 •

Покрај int и float, С обезбедува неколку други основни податочни типови:

char знак - еден бајт


short "краток" цел број
long "долг" цел број
double реален број со двојна nрецизност

Големината на овие објекти, исто така, за виси од машината. П остојат и низи,


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

функции кои ги враќаат. Сите нив ќе ги сретнеме во текот на овој ку рс.


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

lower = О;
upper = 300;
step = 20;

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


видуална наредба завршува со точка-запирка ("; " ) .
Секоја линија од табелата се пресметува на идентичен начин , така што ко­
ристиме циклус кој повторува по еднаш за секоја излезна линија ; тоа е уло гата
на while цикл усот

while (fahr <= upper) {


102 Променливи и аритметички изрази 11

while циклусот функционира на следниот начин: Првин , се проверува условот


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

Потоа, условот повторно се тестира и ако е вистинит тогаш телото, пак, се из ­


вршува о Кога условот ќе стане невистинит (fahr го надмине upper) , тогаш
циклусот завршува , а извршувањето продолжува на следната наредба после
него о Бидејќи нема понатамошни наредби во оваа програма , истата завршува о
Телото на while може да го сочинуваат повеќе изрази затворе ни во загра ­
ди, како во програмата за температурите, или, пак, еден израз без за град и
како овде

while (i < ј)
i = 2 * i ;

И во двата случаја, наредбите контролирани од циклусот while, секо гаш ќе


ги вовлекуваме за еден таб (кое го прикажавме со четири празни места) , со
цел да бидете во состојба за миг да определите кои наредби се опфатени со
циклусот о Вовлекувањето (таканареч ено назабување н а програмскиот код) ја
истакнува логичк ата структура на програмата о Иако е компајлерите не водат
сметка за тоа како изгледа програмата , правилното назабување е важно зара­
ди нејзината прегледност о Наша препорака е да се пишува само една наред­
ба по линија и да се користат празни места помеѓу операторите заради јасно
диференцирање на групите о Местоположбата на заградите е помалку важна ,
иако некои луѓе имаат страстни убедувања за истото о Ние одбравме еден од
неколкуте популарни стилови о Вие одберете стил кој ви е најпогоден и поната ­
му користете го до следно о

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


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

ливата celsius со следнава наредба:

celsius =5 * (fahr-32) 1 9 ;

Причината за множењето со 5и потоа делење со 9 наместо ед н овремено мно ­


жење со 5/9 е од причина што во е, како и во многу други јазици, целобројно­
то делење го отсекува(анго truncate) резултатот: децималниот дел се отфрла о
Бидејќи и 5 и 9 се цели броеви, 5/ 9 ќе врати резултат о, така што сите темпера­
тури од Целзиусовата скала ќе бидат поставени на нула о
Овој пример, исто така, малку подобро покажува како функционира printf о
printf е општонаменска функција за форматирање на излез , која што ќе ја
објасниме во д етаљи во Глава 7 о Нејзиниот прв аргумент е стрин г од знаци што
треба да се испечатат, каде секој % го покажува местото каде еден од другите
(вториот, третиот, о о о) аргументи треба да бидат заменети и во која форма
треба да се отпечатат о На пример, %d с пецифицира целоброен аргумент , така
што наредбата
12 Краток вовед во јазикот С Гл ава 1

printf( "%d\ t %d\n", fahr , ce1sius) ;

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


и ce1sius , со табулатор ( \ t) помеѓу нив.
Секоја % конструкција од првиот аргумент на printf се спарува со соодв ет­
ниот втор аргумент, трет аргумент итн. ; истите мора точно да се совпаѓаат по
број и по тип, во спротивно испечатениот резултат ќе биде погрешен .
Напомена , printf не е дел од јазикот С ; ВО самиот С не постојат дефиниции за
влез или излез . printf е само корисна функција од стандардната библиотека на
функции , кои во нормален случај се достапн и н а С програмите. Однесува њето на
printf е дефи нирано со ANSI стандардот , па нејзините својства би требало да бидат
исти за кој било компајлер или библиотека, кои се во согласност со стандардот.
Со цел да се концентрираме на самиот С , не ма да зборуваме многу за влез­
ни и излезни операции се до Глава 7 . Детаљното форматирање ќе го одложиме
дотогаш. Ако имате потреба за внесува ње на броеви, прочитајте ја ди скусијата
за функцијата scanf во Поглавје 7 . 4 . scanf е слична на printf , со таа ра зли ка
што наместо печатење на излез таа врши читање од влез .

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


Наједноставниот е поврзан со изгледот на излезот, бидејќи броевите не се по­
рамнети во десно . Тоа е лесно да се разреши ; ако на секој %d од printf му
придружиме број за шири на , отпечатените броеви ќе се порамнат во дес но
како што треба. На пример, за печатење на првиот број од секоја линиј а во
поле со ширин а од три цифри , а на вториот со шест , можеме да на пишеме

printf( "%3d %6d\ n", fahr , ce1sius) ;

при што на излез ќе се добие :

о -17
20 -6
40 4
60 15
во 26
100 37

Постои посериозен проблем: од причина што користевме целобројна арит­


метика, вредностите на Целзиусовите температури не се многу прецизн и; на
пример, 0 °F изнесува -17 . 8°С , а не -17 . За да се добијат поточ н и резултати,
наместо целобројна , би требало да користиме аритметика на реалн и броеви.
Еве ја втората верзија :
1.2 Променливи и аритметички изрази 13

Иinclude <stdio.h>

/* принтаље на Фарнхајт-Цепзиус табепа


for fahr =О, 20, ... , 300; реапно-бројна верзија*/
main()
{
fioat fahr, celsius ;
fioat lower, upper, step ;

lower = О; /* допна граница на температурната скапа */


upper = 300; /* горна граница */
step 20 ; /* големина на чекор */

fahr lower;
while (fahr <= upper) {
celsius = (5.0/9.0) * (fahr-32.0);
printf( "%3.0f %6.lf\n", fahr , celsius) ;
fahr = fahr + step ;

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


вите fahr и celsius се од тип fioat, а изразот за конверзија е напишан поп ри ­
родно. Во претходната верзија не бевме во прилика да користиме 5/ 9, бидејќи
делењето на цели броеви би резултирало со резултат нула. Децимална то чка
во некоја константа сугерира дека станува збора за реален број , па 5 . О/9. о не
резултира со нула од причина што тоа е делење на два реални броја.
Ако еден аритметички оператор има целобројни операнди, тогаш се изве­
дува опера ција над цели броеви . Ако аритметичкиот оператор има еден реа­
лен број како операнд и еден цел број, целиот број ќе се претвори во реален за
да може операцијата да се изврши како што треба. Ако напишеме (fahr-32) ,
32 автоматски ќе биде претворен во реален број. Сепак, пишувањето реално­
бројни константи со експлицитно наведена децимална точка, дури и кога имаат
целобројни вредности, ја истакнува нивната реална природа за читателите.
Детаљните правила за тоа кога се врши претворање на целите броеви во
реални, се дадени во Поглавје 2. 3. Засега , уочете дека наредбата :

fahr = lower ;

и условот

while (fahr <= upper)

исто така, функционираат на очекуваниот начин- int-oт се претвора во fioat


пред да се изврши операцијата. Спецификаторот за конверзија во printf ,
%3. Of , сугерира дека реалниот број (во случајов fahr) треба да се испечати
14 Краток вовед во јазикот С Глава 1

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


ток. Параметарот %б . l f опишува дека вториот број (celsius) треба да биде
отпечатен со најмалку шест знаци, со една децимала п осле децималната точ ­
ка. Излезат е од следниот облик:

о - 17 . 8
20 -б . 7
40 4.4

Ширин ата и прецизноста не мора да бидат наведени: % бf означува дека


бројот ќе биде во шири на од најмалку б знаци; %. 2f одредува два знака после
децималната точка, но ширината не е нагласена ; %f нагласува , единст вено,
дека станува збор за број со подвижна децимална точка .

%d печати како декаден цел број


%бd печати како декаден цел број, во ширина барем од б знаци
%f печати како реален број
%бf печати како реален број, во ширина барем од б знаци
%· 2f печати како реален број 1 со најмногу 2 знака после децималната точка
% б . 2f печати како реален број 1 во шири на барем од б зна ци 1 со
два знака после децималната точка

Меѓу другото, printf, исто така, ги преnознава и следниве спецификатори: % о за


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

Вежба 1-3 . Модифицирајте ја програмата за температурна конв ерзија да пе­


чати и заглавје над табелата

Вежба 1-4. Напишете програма за печатење табела од Целзиусова во


Фаренхајтова скала .

1.3 Иска3от for

Постојат мноштво различни начини да се напише програма за о кретн а за­


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

#include <stdio.h>
/* print Fahrenheit-Celsius table */
main ()
1.3 Исказот for 15

int fahr ;

for (fahr = О ; fahr <= 300 ; fahr = fahr + 20)


printf ("%3d %6 .lf\n" , fahr, (5 . 0/9. О)* (fahr-32)) ;

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


мена е елиминирањето на најголемиот број од променливите ; останува само
fahr, која сега е од тип int. Долната и горната граница и чекорот се појавува­
ат само како константи во for циклусот, кој е нова конструкција, а изразот кој
ја пресметува температурата во Целзиусови степен и сега се јаву ва како трет ар ­
гумент во printf функцијата, наместо како посебна наредба за доделување .
Последната промена е пример за едно општо п равила- во кој било контекст
каде што е дозволено да се користи вредност на променлива од одреден тип ,

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


мент на функцијата printf мора да има реална вредност што ќе соодветствува
на спецификаторот %6. lf, тука може да се употреби кој било израз со реален
број како резултат .
Наредбата for е циклус кој претставува генерализиран облик на while .
Ако ги споредите со while, кој веќе го обработивме, начинот на кој for функци­
онира, би требало да биде разбирлив. Во заградите , има три делови одвоени
со точка-запирка. Првиот дел, иницијализа цијата

fahr =О
се извршува само еднаш, пред да се влезе во телото на циклусот . Вториот дел
е условот кој го контролира циклусот :

fahr <= 300

Овој услов се евалуира; ако е вистинит, се извршува телото на циклусот (во


случајов една printf наредба) . Потоа се извршува чекорот

fahr = fahr + 20

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


невистинит. Исто како и кај while, телото на циклусот може да биде претста­
вена од една наредба или, пак, група од наредби затворени во големи загради.
Иницијализацијата , условот и чекорот , може да бидат какви било изрази .
Изборот помеѓу for и while е слободен, во зависност од ситуацијата .
Обична, for се употребува во циклуси за кои иницијализацијата и условот се
едноставни и логички поврзани наредби, бидејќи е покомпактен од while , а
контролните изрази се наоѓа а т заедно на едно место.
16 Краток вовед во јазикот С Глава 1

Вежба 1-5 . Да се модифицира програмата за претворање на температури,


така што ќе ја печати табелата во обрате н редослед 1 т . е. од 300 степен и кон о.

1.4 Симболички константи

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


пред да ја напуштиме засекогаш. Лоша практика е да се употребуваат " вол­
шебни броеви " како 300 и 20 во една програма; тие нудат малку информација
за некој кој можеби подоцна ќе ја чита програмата и тешки се за систематска
промена. Еден начин за справување со волшебни броеви, е да им се зададат
имиња кои ќе имаат смисла. Една tdefine линија дефинира симболичко име или
симболичка константа да биде една конкретна низа од знаци

idefine име текст за замена

Понатаму , секоја појава н а име (не во наводници и не како дел од друго име)
ќе биде заменета со соодветната, текст з а замена. Име има ист облик како
и имињата на променливите: секвенца од букви или цифри што започнува со
буква. Текстот за замена може да биде каква било низа од знаци; не е огра­
ничен само на броеви .

tinclude <stdio.h>

idefine LOWER О 1* долна IОраница на 'l'aбena'l'a */


idefine UPPER 300 /* горна IОраница */
tdefine ЅТЕР 20 /* големина на чехор */

/* печа'l'еље на Фаренхај'l'-Целзиус 'l'абела */


ma.in ()
{
int fahr ;

for (fahr = LOWER ; fahr <= UPPER; fahr = fahr + ЅТЕР)


printf ("%3d %6 . lf\n" 1 fahr , (5 . 0/9.0)*(fahr-32)) ;

Величините LOWER , UPPER и ЅТЕР се симболички константи , неп роменливи,


па не се појавуваат во декларации . Имињата на симболичките константи по
конвенција се пишуваат со големи букви заради нивно лесно разл икување од
променливите кои се напишани со мали букви. Забележете дека после една
#define линија, не следи точ ка-запирка.
1.5 Влез и излез на знаци 17

1.5 Bne3 и И3nе3 на 3наци

Ќе разгледаме сега една фамилија од сродни програми за процесирање


на знаковни податоци . ќе увидите дека повеќето програми се само проши­
рени верзии на прототиповите кои ќе ги дискутираме овде . Моделот за влез
и излез, којшто е подржан од стандардната библиотека , е мошне едноставен.
Внесувањето на текст или неговото печатење, без разли ка од каде потекну­
ва и каде оди на излез, е разрешено преку потоци (анг. streams) од знаци .
Текстуален поток претставува секвенца од знаци поделени во линии ; секоја
линија се состои од нула или повеќе знаци проследени со знак за нов ред.
Библиотеката е одговорна да осигура дека с екој влезен ил и излезен поток од ­
говара на овој модел; С програмер кој ја користи библиотеката нема потреба
да води грижа за тоа како се претставен и линиите надвор од рамките на про ­

грамата .

Стандардната библиотека обезбедува неколку функц ии за едновремено ч и ­


тање и запишување на еден знак, од кои getchar и putchar се наједностав­
ните . Со секое повикување, getchar го чита наредниот знак на влез од некој
текстуален поток и ја враќа неговата вредност. Т . е ., после

е = getchar () ;
променливата е ја содржи вредноста на последниот влезен знак. Знаците во
општ случај доаѓаат од тастатура; влез од датотеки е обработен во Глава 7 .
Функцијата putchar печати еден знак при секое нејзин о повикување :

putchar(c) ;

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


ран . Повиците до putchar и printf може да се испреплетени; излезат ќе
одговара на редоследот на нивното повикување .

1.5.1 Копирање на датотека

Со помош на getchar и putchar , можете да напишете изненадувачка коли­


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

прочитај знак
while (знакот не е знак за крај на датотека)
испрати го на излез штотуку прочитаниот знак

прочитај знак
18 Краток вовед во јазикот е Глава 1

Претворањето на ова во С код изгледа вака:

#inelude <stdio.h>

/* хопирај влез на излез , 1-ва верзија*/


тain ()
{
int е;

е =
getehar () ;
while (е != EOF)
putehar(e);
е = getehar () ;

Релациониот оператор ! = значи " не еднакво на ".


Се она што изгледа како знак од тастатура или на екр а н , како и се друго , вна­
трешно е меморирано како низа од битови. Податочниот тип cha.r е специјал но
наменет за чување на такви знаковни податоци , но може да се користи и кој бил о
целоброен тип . Ние користиме int од едноставна , но мошне важна причи на.
Проблем е да се разграничи крајот на еден податоч е н влез од валидните
податоци. Решение за тоа е getehar да врати некоја пре познаmи ва вредност
во случаи кога влезот веќе завршил, вредност која не може да бv.де поt. еш ана
со некој реален знак . Таа вредност е наречена EOF, со значење " крај на дато­
теката (анг. end of file)". Мора да декларираме е да биде од тип доволно голем
да ја чува вредноста на кој било знак кој ќе го добие од getchar . Не t. о еме да
користиме char бидејќи е мора да биде доволно голем за да го npифani BOF и
кој било друг ehar. Поради тоа користиме int.
EOF е цел број дефиниран во <stdio. h>, но конкретната број на вредност
не е важна се додека не е идентична со вредноста на некоја ehar вредност .
Користејќи симболичка константа, ние сме сигурни дека ништо во програмата
нема да зависи од некоја специфична бројна вредност.
Програмата за копирање би била напишана пократко од некој искусен С
програмер. Во С, секое доделување на вредност, како

е = getchar ()
е израз и има некоја вредност. Тоа е вредноста на левата страна после доделу­
вањето. Ова значи дека доделувањето може да се појави како дел од по голем из­
раз. Ако доделувањето на еден знак е се стави внатре во условниот дел на еден
while циклус, програмата за копирање може да се напише на следниов начин:
1.5 Влез и излез на знаци 19

#inelude <stdio.h>

/* хоnираље влез на излез, 2-ра верзија */


main()
{
int е;

while ((е= qetehar()) != EOF)


putehar(e) ;

Циклусот while зема еден знак, го доделува на е и потоа проверува дали зна ­
кот е знак за крај на датотеката. Ако не е, се извршува телото наwhile циклу­
сот, кој го печати знакот. Потоа
while се повторува. Кога конечно ќе се стигне
до крајот на влезот, завршува while циклусот , а со тоа и програмата main .
Оваа верзија се концентрира на влезот- сега има само едно повикување до
qetehar - а програмата е пократка . Резултантната програма е покомпактна
и, кога еднаш ќе навикнете на ваков облик на пишување на кодот, истиот ќе
ви биде полесен за читање. Со овој стил на пишување ќе се среќавате често.
(Сепак, во пишувањето на ваков начин може да се претера , што во чести си­
туации доведува до замрсен код, нешто што ние ќе се обидеме да го избегнеме
овде.)

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


услов, се потребни . Приоритетот на операторот ! =е поголем од оној на опе­
раторот =, што значи дека во отсуство на загради првин ќе се изврши рела цио­
ната проверка ! =. Така , изразот

е = qetehar() != EOF

е еквивалентен н а

е = (qetehar() != EOF)

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


во зависност од тоа дали qetehar има вратено вредност за крај на датотека.
(Повеќе за ова во Глава 2.)

Вежба 1- б. Да се провери дали изразот qetehar () ! = EOF враќа 1 или о.

Вежба 1-7. Да се напише програма која ја печат и вреднос та на EOF.


20 Краток вовед во јазикот С Глава 1

1.5.2 Броење на знаци

Следната програма брои знаци; слична е на програмата за копирање

#include <stdio.h>
/* броеае знаци на влез ; 1-ва верзија */
main()
{
long nc ;

nc = О;
while (getchar() != EOF)
++nc ;
printf( "%ld\n", nc) ;

Наредбата

++nc ;
н е запознава со нов оператор, ++ , кој означува инкрементирање (зголемување
за еден). Наместо него можете да напишете nc = nc + 1, но ++nc е поконцизно ,
а честопати и поефикасно. Постои и соодветен -- оператор за декрементирање
{ намалување за еден). Операторите ++ и -- можат да се сретнат во префиксна
(++nc) и постфиксна форма (nc++) ; овие две форми во изразите резултираат
со различни вредности, како што ќе биде покажано во Глава 2, но и ++nc и nc++
го инкрементираат nc. Во моментов ќе се задржиме на префиксната форма.
Програмата за броење, наместо воint , го акумулира својот резултат во long
променлива . long целите броеви се со големина од најмалку 32 бита. Иако на
некои машини, int и long се со иста големина , на други int е со големи на од 16
битови , со максимална вредност од 327 67 , што овозможува релативно мал влез
да направи пречекорување кај бројач од тип int. Конверзната спецификација
%1d му сугерира на printf дека соодветниот а ргумент е цел број од тип long.
Можно е да се оперира дури и со поголеми броеви ако се користи double
(float со двојна прецизност) . Исто така, наместо while ќе користиме for ци­
клус, со цел, да илустрираме алтернативен начин на пиш у вање на циклус.

#include <stdio.h>

/* броеае знаци на влез ; 2-ра верзија */


main()
{
double nc ;

for (nc = О; gechar() != EOF ; ++nc)

printf( "%. 0f\ n ", nc) ;


1.5 Влез и излез на знаци 21

printf користи %f и за float и за double; %.0f го отстранува печатењето на


децималната точка и децималниот дел после неа, кој е нула .
Телото на овој for циклус е празно, бидејќи целата работа се завршува во
деловите за проверка и инкрементација. Но граматичките правила наС бараат
наредбата for да има тело. Изолираната точка-запирка , наречена празен ис­
каз, е таму за да го задоволи тоа барање. Ја ставивме во посебна линија за да
ја направиме поуочлива.
Пред да ја напуштиме програмата за броење на знаци, забележете дека ако
влезот не содржи знаци, проверките кај while и for паѓаат на првиот повик
до getchar и програмата резултира со нула, што е и точниот одговор. Ова е
мошне важно . Една од убавите работи кај while и for е тоа што проверките се
прават на врвот на циклусот , пред да се премине со извршување на телото .

Ако нема што да се работи, тогаш и не се работи, дури и ако тоа подразбира
да не се помине низ телото на циклусот. Програм ите треба интелигентно да
ги разрешуваат случаите каде податочниот влез има должина н ула. Ци клусите
while и for обезбедуваат добра работа на програмите и при тие граничн и ус­
лови.

1.5.3 Броење на линии

Следната програма врши броење на линии на податочен влез. Како што


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

jinelude <stdio.h>

/* броеае на пинии во податочен влез */


main()
{
int е, nl;

nl =О;
while ((е= getchar()) != EOF)
if (е = '\n')
++nl;
printf( "%d\n", nl);

Телото на while циклусот сега се состои од една if наредба, која го контролира


инкрементот ++nl. if наредбата врши проверка на условот во заградите, и ако е
вистинит, се извршува наредба што следи (или група наредби во големи загради) .
Повторно ги вовлековме наредбите со цел да покажеме кој кого контролира.
22 Краток вовед во јазикот С Глава 1

Изразот двојно еднакво,=, е С нотација за "еднакво на" (исто со единично


= кај Паскал и . EQ. кај Фортран) . Овој симбол се користи за да се разграничи
релацијата за еднаквост од единичното =кое кај С се користи како оператор за
доделување. Едно предупредување: новите програмери во С обично пишуваат
= а мислат на =. Како што ќе видиме во Глава 2, резултатот од таквата постап­
ка, обично е легален израз, та ка што компајлерот не јавува никакво предупре­
дување .

Еден знак напишан во апострофи претставува целобројна вредност еднак­


ва на број ната вредност што знакот ја има во машинското м ножество на зна ­
ци. Истиот се нарекува знаковна константа, иако во ствар ност тоа е само
алтернативен начин на запишување на мал цел број. Така, на пример, ' А ' ,
е знаковна константа ; во ASCII мн ожеството, нејзината вредност е 65 и тоа
е внатрешна претстава на знакот А. Се разбира , ' А' треба да се преферира
во однос на 6 5: неговото значење е очигледно , и е независно од конкретното

множество знаци.

Излезните секве нци кај стр и нговите конс тант и, исто така, се легални и кај
з наковните константи, па '\n' оз н ачува вред ност за знак за нова линија, кој е
10 во ASCII. Треба да се забележи дека '\n ' претставува еден знак и во изра­
зите се третира како целобројна вредност; од друга страна, "\n" претставува
стринг константа која содржи само еден знак. Прашањето за разликите помеѓу
стри нговите и знаците ќе биде дискутирано понатаму во Глава 2 .

Вежба 1-8 . Да се напише програма која ќе брои знаци за празно наместо


(бланко- знаци, табови и знаци за нова линија).

Вежба 1-9. Да се напише програма која ќе го копира н ејзи ни от влез на нејзи­


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

Вежба 1-10. Да се напише програма која ќе го копира сопствениот влез на


сопствениот излез, притоа, заменувајќи го секој таб со \t , секој знак за бри­
шење знак наназад (анг. backspace) да се замени со \b и секој контра низ со\\ .
Табовите и з наците за бришење н аназад ,тоа ги прави видливи на недвосмис­
ле н н ачин .

1.5.4 Броење на 3борови

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


и знаци, со неформална дефиниција дека, збор претставува која било секве нца
од знаци која не содржи празно место, таб, или знак за нов ред. Ова е соголе­
ната верз ија на UNIX програмата wc.
1.5 Влез и излез на знац и 23

#inelude <stdio.h>

#define IN 1 /* внатре во збор */


#define OUT О /* надвор од збор */

/* брои линии , зборови , и знаци во влез */


main ()
{
inte, nl, nw , ne, state ;

state=OUT;
nl = nw = ne = О ;
while ( (е= getehar () ) ! = EOF) {
++n.C;
if (е= '\n')
++nl ;
if (е=='' 11 е= '\n' 11 e=::' \t' )
state =ОUТ;
else if (state=OUТ)
state = IN ;
++nw ;

printf ( "%d %d %d\n " , nl, nw , ne) ;

Секојпат кога програмата ќе наиде на првиот знак од некој збор, ќе избро­


име еден збор повеќе. Променливата state бележи дали програмата тековно
се наоѓа во збор или не; почетно земаме дека " не е во збор " , и И доделув аме
вредност оuт . Ги претпочитаме симболичките константи IN и оuт во однос на
бројните вредност о и 1, бидејќи истите, програмата ја прават појасна за ч и­
тање. Во мала програма како оваа , тоа и не ни е така важно , но во поголем и
програми, подобрувањето на јасноста на програмата се исплатува з а додатно
вложениот н а пор на нејзино пишување од почеток во тој стил . Исто така, пона­
таму ќе откриете дека полесно е да се направат обемни промени кај програмите
во кои волшебни броеви се јавуваат единствено како симболички константи .
Линијата

nl = nw = ne = О ;

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


клучок, туку е последица на фактот дека доделувањето претставува израз во
кој вредностите и доделувањата се асоцирани оддесно спрема лево. Исто би
било и да напишевме
24 Краток вовед во јазикот С Глава 1

nl = (nw = (ne =0)) ;


Операторот 1 1 означува логичко ИЛИ, па линијата

if (е='' 11 е= '\n' 11 е= '\t')

се интерпретира како "ако е е бланко или е е знак за нов ред или е е таб " .
(Потсетете се дека \t е видлива претстава на знакот таб . ) Постои и соодве­
тен оператор && за л оги чко И ; неговиот приоритет е поголем од (веднаш пред)
приоритетот на 11 • Изразите поврзани со && и 11 се пресметуваат од лево кон
десно и се гарантира дека пресметката ќе застане во моментот кога вистини­
тоста или неви стинитоста на изразот ќе станат познати. Ако е е бланко-знак ,
тогаш нема потреба да се тестира дали е знак за нова линија или таб , така што
тие проверки не се прават. Овде тоа и не изгледа важно , но е значајно во по­
комплексни ситуации, како што ќе видиме набргу.
Примеров , исто така, го прикажува else, кој специфицира алтернативна ак­
ција доколку е невистинит условот од if . Општиот облик е

if (израз)

наредба 1
else
наредба 2

Се извршува една и само една од двете наредби асоцирани со еден if-


else . Ако израз е вистинит израз, тогаш се извршува наредба 1 ; во спротивно
се, извршува наредба2• Секоја наредба може да биде единична наредба или по­
веќе наредби оградени во големи загради. Кај програмата за броење зборо­
ви, наредбата која следи после else , е if кој ги контролира двете наредби во
заградите.

Вежба 1-11 . Како вие би ја тестирале програмата за броење на зборови? Каков


податочен влез е најпогоден да открие грешки, доколку има такви во програма ­
та?

Вежба 1-12 . Напишете програма која го печати нејзиниот влез, еден збор по ли­
нија о

1.6 Низи

Да напишеме програма која ќе го брои појавувањето на секоја цифра , на


невидливите знаци ( празно место, таб, нов ред) и на сите останати знаци о
Ова е вештачко, но ни овозможува да илустрираме неколку аспекти на С во
рамките на една програма .
1.6 Низи 25

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


згодно да се користи низа која ќе ги чува бројот на појавувања на секоја цифра ,
отколку десет посебни променливи за истата намена. Еве една верзија на про­
грамата:

#inelude <stdio . h>

1* броев.е на цифри, не:аидливи знаци, други *1


main()
{
int е, i, nwhite, nother ;
int ndigit[10];

nwhite = nother = О;
for (i = О; i < 10 ; ++i)
ndigit[i] = О;

while ((е= qetehar()) != EOF)


if (е>= '0' && е<= '9')
++ndiqit[e-'0'] ;
else if (е = ' ' 11 е == '\n' 11 е '\t')
++nwhite;
else
++nother;

printf ("diqits =");


for (i = О ; i < 10; ++i)
printf(" %d", ndigit[i]);
printf(" , white ѕраее = %d, other %d\n",
nwhite, nother);

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

diqits = 9 3 О О О О О О О 1, white ѕраее = 123, other = 345

Декларацијата

intndigit[10];

ја декларираndiqi t како низа од десет цели броеви. Индексите на н изите


во С секогаш започнуваат од нула , п а конкретните елементи се
ndiqi t [о] ,
ndigit[1], ... , ndiqit[9]. Тоа се рефлектира и кај for циклусите ко и ја
иницијализираат и печатат низата .
Оваа конкретна програма се заснова врз карактеристиките на знаковната
репрезентација на броевите. На пример , условот
26 Краток вовед во јазикот С Глава 1

if (е>= '0' &&е<= '9')

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

е- '0'

Ова функционира само ако '0', '1', ... , '9' имаат последователни растеч­
ки вредности. За среќа, тој услов го исполнуваат сите знаковни множества .
По дефиниција, знаците се само мали цели броеви, па во аритметичките
изрази, ehar променливите се идентични со int променливите. Ова е при­
родно и погодно; на пример изразот е- 'О' е целоброен израз со вредност
помеѓу о и 9, што за виси од вредноста на е, а со тоа е и погоден како индекс
на низата ndigit.
Одлуката за тоа дали еден знак е цифра, невидлив знак, или нешто друго се
изведува преку секвенцата

if '0' && е<= '9')


(е>=

++ndigit[e-'0 ' ] ;
elseif (е=' ' 11 е== '\n' 11 е== '\t')
++nwhite;
else
++nother;

етруктурата

if (услов )
1
наредба 1
else if (услов )
2
наредба 2

else
наредба.

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


одлука. Условите се евалуираат по редослед, почнувајќи од врват се доде­
ка не ~е најде услов кој задоволува; притоа се извршува соодветната наредба
и цемта конструкција завршува. (Секоја наредба може да биде составена од
повеќе поднаредби затворени во големи загради) Ако не е исполнет ниту еден
од условите, се извршува (доколку е присутна) наредбата која следи по по­
следниот else. Во случај да не се наведени последниот else и наредба, како
во програмата за броење на зборови, тогаш не се прави ништо. Може да има
произволен број на групи
1.7 Функци и 27

else if (услов)
наредба

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


Во зависност од стилот, препорачливо е да се форматира оваа конструкција
на начин кој што го покажавме тука; ако секој if беше вовлечен во однос на
претходниот else , при долга низа на услови ќе немаше да има доволно место
за пишување на кодот.

Наредбата switch, која ќе биде објаснета во Глава 4 , обезбедува уште еден


начин да се претстави повеќекратно разгранување, кој посебно е погоден во
случаи кога условот кој се проверува е некој целоброен или знакове н израз што
одговара н а една или на множество од констант ни вредности . За контраст , ќе
ја претставиме switch верзијата на оваа п рограма во Поглавје 3 . 4 .

Вежба 1-13. На пишете програма за печатење на хис тограм од должините на


зборовите во нејзини от влез. Не претставува тешкотија да се н а црта хистогра­
мот со хоризо нтално поставени прачки; вертикалната ориентација е поголем
предизвик .

Вежба 1-14 . Напишете програма за печатење на хистограм од честотата на


различните знаци од нејзиниот влез.

1.7 Функции

Во С, функциите се еквивалентни на суб ру тините или функциите од


Фортран, или на процедурите и функциите во Паскал. Една функција обезбе­
дува з годен н ачин на е нкап сулација на некоја пресметка , која потоа може да се
користи, без да се води грижа за нејзината имплементација . Со правилно ди­
зајнира ни фун кции, можно е да се игнорира размислувањето за тоа како се за­
вршува некоја работа; доволна е перцепцијата да се знае што било завршено.
С обезбедува лесно, згодно и ефикасно користење на функциите ; често ќе се
среќавате со кратки функции дефинирани и повикани само еднаш, само затоа
што истите ја пој аснуваат читливоста н а некој дел од кодот.
Досега користевме функции како printf , getchar и putchar кои беа обезбе­
дени за нас ; сега е време да напишеме неколку сопствени. Бидејќи С нема екс­
поненцијален оператор како што е** во Фортра н , ќе го илустрираме начинот на
дефинирање на функција со пишување функција power (m, n) за дигање на целиот
број m, на целобројниот позитивен степен n . Односно, вредноста на power (2 ,
5) е 32. Наведената функција не е практична , бидејќи се оперира само со пози­
тивни степе н и од мали цели броеви, но е доволно добар пример за илустрација
(Стандардната библиотека содржи функција pow (х , у) која пресметува хУ)
Еве ја функцијата power и програмата main каде истата се тестира , па може­
те да ја видите целата структура одеднаш
28 Краток вовед во јазикот С Глава 1

#include <stdio.h>

int power(int m, int n);

/* тест за функцијата power */


main ()
{
int i ;

for (i=O; i<lO; ++i)


printf ("%d %d %d\n", i, power (2, i) , power ( -3, i)) ;
return О;

/* power: степенуваае на осно•ата на стеnен n ; n>=O*/


intpower(intbase, intn)
{
inti,p;

p=l;
for (i=l ; i<=n; ++i)
р=р *base;

returnp;

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

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


{
декларации

искази (наредби)

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


или повеќе изворни датотеки, но ниту една функција не може да биде поделе­
на во повеќе датотеки . Ако изворната програма се наоѓа во повеќе датотеки ,
времето на компајлирање и вчитување на програмата ќе трае нешто подолго ,
отколку кога истата би била сместена цела во една датотека, но тоа е прашање
на оперативниот систем, а не карактеристика на јазикот . Засега, ќе претпоста­
виме дека и двете функции се наоѓаат во иста датотека , na ќе ни важи сето тоа
што го nроучивме досега во врска со функционирањето на е програмите .
Функцијата power два пати се повикува во рамките на main, во линијата

printf (" %d %d %d\n", i , power (2 , i) , power (-3, i)) ;


1.7 Функции 29

Секој повик кон power предава два аргумента, при што секојпат функцијата
враќа цел број кој треба да се форматира и печати. За еден израз, power (2 , i)
претставува цел број, на ист начин како што се константата 2 и променливата
i. (Не сите функции даваат целоброен резултат ; ќе го продискутираме тоа во
Гл ава 4.)
Првата линија од самата power

int power (int base, int n)

ги декларира типовите на параметрите и нивните имиња, како и типот на ре­

зултатот кој ќе го враќа функцијата.


Имињата кои ги користи power за свои параметри, се локални за power , и
се невидливи за која било друга функција: другите рутини може да ги користат
истите имиња без можност за конфликт. Ова, исто така, е вистина и за промен­
л ивите i и р: променливата i од power на никој начин не е поврзана со истои­
м ената променлива од ma.in.
Обична, ќе го користиме терминот параметарl за променливите спомена­
ти во листата на функциите при нивната дефиниција 1 а аргумент за вредноста
која се користи при повик на функцијата.
Понекогаш се користат термините формален аргумент и актуелен аргу­
мент за да се направи истото разграничување .

Вредноста која ја пресметува power се препраќа на main преку наредбата


return. После return може да следи каков било израз:

return израз;

Една функција не мора да врати вредност; наредбата return напишана без


никаква вредност по неа, врши контрола 1 но не враќа корисна вредност кон
својот повикувач. Тоа е случај и при"паѓање од работ" на функција, со доаѓање
до завршната голема затворена заграда. И повикувачката функција може да ја
и гнорира вредноста вратена од друга функција.
Можеби забележавте дека постои return наредба на крајот од main .
Бидејќи main е функција како и сите други функции, може да врати вредност
кон оној што ја има повикана и истата има ефект во околината во која таа била
повикана и извршена. Обична, повратна вредност нула означува нормален
тек на извршување; ненулта вредност сигнализира дека нешто не е во ред.

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


return во нашите main функции, но отсега натаму ќе ја вклучиме и неа 1 како
потсетување дека програмите треба да враќаат статус кон нивната повикувачка
околина.

Декларацијата

intpower(intbase, intn);
30 Краток во вед во јаз и кот е Глава 1

пред самиот main 1 означува дека power е функција која очекува два
int аргу­
мента и која враќа int. Оваа декларација 1 наречена
npomomun на функција­
та 1 мора да се совпаѓа со дефиницијата и употребите на power. Погрешно е
доколку дефиницијата на функцијата или некој нејзи н повик не се совпаѓаат со
нејзиниот прототип .
Имињата на параметрите не мора да се совпаѓаат. Навистина 1 за еде н
функциски прототип имињата на параметрите се опционал ни 1 па кај прототи­
пот можевме да напиш еме и:

intpower (int 1 int) ;

Уредно одбрани имиња ја прават програмата документира на, па ќе ги корис­


тиме често.

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


напишана во ANSI С и програма напиша на во постарите верзии наС 1 е во начи­
нот на кој се претставени и дефинирани функциите. Во оригиналната дефини­
ција на е 1 функцијата power ќе беше напишана на овој начин:

/* power : стеnенуваље на основата на стеnен n ; n>=O* /


/* (верзија во стар-стил) */
power(base n) 1

int base 1 n ;

int i , р;

р = 1;
for (i = 1 ; i <= n ; ++i)
р = р * base ;
return р ;

Параметрите се именувани во рамка на заградите 1 а нивните типови се декла­


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

земаат за тип int. (Телото на функцијата е исто како и во претходн иот случај)
Декларирањето на power на почетокот од програмата ќе изгледаше вака:

int power () ;

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


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

и нивниот број. Стариот тип на декларирање се уште е во упот реба во ANSI


1.8 Аргументи - повикување по вредност 31

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


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

Вежба 1-15 . Напишете ја одново програмата за конверзија на температурите


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

1.8 Аргументи - повикување по вредност

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


те кои користат други јазици, посебно тие што користат Фортран. Во е , сите
функциски аргументи се предаваат по "вредн ост" . Тоа значи дека повиканата
функција ги предава вредностите од аргументите на привре мени променливи,
а не на оригиналните. Тоа води до некои особини, различни од тие што се ка ­
рактеристични за " повикување преку референца ", кај јазиците како Фортран
или var параметрите во Паскал, каде повиканата ру тина пристапува н а ориги­
налниот аргумент, а не на некоја локална копија.
Основна разлика е во фактот дека во е , повикан а та функција не може ди­
ректно да ја измени променливата од функцијата повикувач; таа може само да
ја измени својата привремена копија .
Повикување преку вредност е предност, а не недостаток . Тоа обично води
кон покомпактни програми со помал број на променливи , бидејќи параметри­
те може да се третираат како погодно иницијализирани локал ни променливи
во повиканата функција. На при мер, еве една верзија на power кој а ја користи
о ваа карактеристика:

/* power: стеnенуваље на основата на стеnен n; n>=O ; верзија 2 */


int power(int base, int n)

int р;
for (р = 1; n > О ; --n)
р =р * base;
return р;

Параметарот n се користи како привремена променлива , а вредноста му се на ­


малува (nреку for циклус кој брои наназад) додека не дојде до нула; не пос­
тои потреба од променлива i. Што и да е направено со n внатре во телото на
функцијата power , нема никакво влијание на аргументот со кој што power беше
првично повикан а.

Кога е п отреб но тоа, можно е да се изведе измена на променлива во рутина


која се повикува. П овикува чот мора да ја обезбеди адресата на про менл ивата
која треба да се менува (всушност, покажувач кон променливата) , а повика­
ната функција да го декларира својот параметар како покажува ч и индиректно
32 Краток вовед во јазикот ( Глава 1

преку него да пристап и до променливата. За покажувачите повеќе ќе зборува­


ме во Глава ѕ.
Кај низите приказната е поинаква . Кога името на низата се користи како ар­
гумент , вредноста која се предава на функцијата е локацијата т. е адресата на
почетокот на низата - нема копирање на елементите на низата. Со индекси ­
рање на таа вредност, функцијата може да пристапи и изврши промена на кој
било елемент на низата. Ова ќе биде тема на делат што следува.

1.9 Низи од знаци

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


ристењето на знаковните низи и фу нкциите кои манипулираат со нив , ќе напи­
шеме програма која чита множество на текстуални линии и ја печати најдолгата
од нив. Костурот на програмата е прилично едноставен :

while ( има друга л инија )


if ( е подолга од претходната најдолга)
за чувај ја
зачувај ја нејзи ната должи на
испе чати ја најдолгата ли н ија

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


на делови . Еден дел чита нова линија , друг ја складира , а останатите го кон­
тролираат процесот.

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


шеме на истиот начин. Според тоа, најнапред да напишеме посебна функција
qetline која ја зема следната линија од податочен влез. Ке се обидеме да на­
правиме функција која би била употреблива и во некој друг контекст. Во најма­
ла рака, qetline треба да враќа сигнал за можен крај на датотека; покорисно
би било да ја враќа должината на линијата или нула доколку прочита крај на да­
тотека. Нула е прифатлива вредност за крај, бидејќи таа вредност нема смисла
како должина на линија. Секоја линија има барем еден знак; дури и линија која
содржи само знак за нов ред, има должина 1.
Кога ќе пронајдеме линија која е подолга од тековната најдолга , мора да
ја зачуваме н екаде . Тоа е обезбедено со друга функција , сору , која ја копира
новата линија на сигурно место . За крај , потребна ние програма main која ќе
ги контролира функциите getline и сору. Решението изгледа вака :
1.9 Низи од знаци 33

#include <stdio.h>
#define МAXLINE 1000 /* махсимапна должина на влезот */

int getline(char line[] , int maxline) ;


void copy(char to[], char from[]) ;

/* печати најдопrа влезна линија */


main()
{
int len ; /* допжина на тековна линија */
int max ; /* максимапна должина досега */
char line[МAXLINE] ; / * тековна линија на влез*/
char longest~] ; /* најдо.пrата .nинија се чува 'rУМ-* /

max = О ;
while ((len = getline(line, МAXLINE)) >О)
if (len > max) {
max = len ;
copy(longest , line);

if (max > О) /* беше прочитана линија */


printf(" %s ", longest) ;
return О ;

/ * getline: чита пинија и ја сместува во ѕ , враха допжииа на линијата* /


int getline(char ѕ[] , int lim)

int е, i;

for (i=O ; i < lim-1 && (c=getchar())!=EOF && c!=' \n'; ++i)
s[i] = е;
if (е= '\n' )
s[i] =е ;
++i ;

s[i] = ' \0 ';


return i ;

/ * сору: IСОШ!рај го ufran" во uto"; DрЕ!'1'110С'МВуваме дека uto" е Д0ВQ11И0 Г0118«:>*/


void copy(char to[], char from[])
{
int i ;

i = О;
while ( (to [i] from[i]) != ' \ 0')
++i ;
34 Краток вовед во јазикот е Глава 1

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


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

враќаат. Во функцијата getline , аргументите се претставени со линијата

int getline (char ѕ [] , int lim)

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


проме нлива lim. Причина за наведувањ е на величината на низа при де клара ­
ција, е резервирање на меморија за неа . Должината на низата ѕ не е од важ­
ност за getline , бидејќи големина на ѕ се п оставува во самата main. getline
ја користи return наредбата за да врати вредност на оној кој ја повикува, на
начин како што тоа го правеше power . Оваа линија, исто така , покажува дека
getline враќа вредност од тип int; бидејќи кога не е наведено int се под­
разбира како тип кој функцијата треба да го врати, наведувањето на истиот во
де клара циј ата може да се проп ушти .
Некои функции враќаат корисна вредност; дру ги, како сору , се користат за
обработка на податоци, но не враќаат никаква вредност. Типот на функцијата
сору е void , што експлицитно наведува дека истата не враќа никаква вред­
ност .

getline поставува знак '\ 0' (пи/lзнак , чија вредност е нула) на крајот од
стрингот што истата го креира, со што оз начува негов крај. Оваа конвенција се
користи и во е ; кога стринг константа како што е

"hello\n"

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


содржи знаците од стрингот, а завршува со '\ о ' како ознака за крај.

епецификацијата % ѕ во форматот во функцијата printf очекува дека соод­


ветниот аргумент е стринг претставен во ваква форма. Функцијата сору , исто
така , се потпира на тоа дека нејзиниот влезен аргумент зав ршува со '\0 ', и го
копира тој з на к во излезниот аргумент.
Вреди да се спомне дека и така мала програма како оваа има неколку осет­
ливи креативни проблеми . На пример, што б и се случило кога main б и на­
ишла на линија чија должина ја надминува максималната? Функцијата getline
функционира безбедно, во тоа дека го стопира собирањето штом се наполни
низата , дури и ако не се појави знак за нова линија . Тестирајќи ја должината
и последниот вратен знак, main може да одреди дали линијата е предолга и
по желба да се справи со тоа. Во интерес на просторот го игнориравме овој
проблем.
1.1 о Надвореш ни променливи и делокруг 35

Не постои начин , за оној кој ја користи функцијата getline , однапред да


знае за тоа колкава е должината на влезната линија , п а getline мора внатрешно
да прави проверка за пречекорување (анr. overflow) . Од друга страна, оној кој ја
користи сору веќе знае (или може да знае) која е должината на стринговите , па
затоа одлучивме во оваа функција да не пишуваме код за проверка од грешки.

Вежба 1-16 Преправете ја главната рутина од програмата за печатење на нај­


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

Вежба 1-17 Напишете програма за печатење на сите влезни линии подолги од


во знаци.

Вежба 1-18 Напишете програма за отстранување на непотребните бланко­


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

Вежба 1-19 Напишете функција reverse (ѕ) која го превртува стрингот ѕ.


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

1.1 О Надвореwни променливи и деnокруг

Променливите во main, како што се line, longest , итн., се при ват ни


или лdкални за main . Бидејќи се декларирани внатре во main , ниту една друга
функција нема директен пристап до нив . Истото важи и за променливите во
другите функции; на пример, променливата i во getline нема никаква врска
со променливата i од функцијата сору . Секоја локална променлива се креи­
ра само при повик на една функција и исчезнува во моментот на излегување
од таа функција. Тоа е причината поради која таквите променливи се познати
како автоматски променливи , во согласност со терминологијата на дру гите
јазици. Затоа, понатаму ќе го користиме терминот автоматски за референци­
рање на тие локални променливи (Во Глава 4 . ќе зборуваме за мемориската
класа static , со која локалните променливи ги задржуваат своите вредности
помеѓу функциските повици .)
Бидејќи автоматските променливи доаѓаат и си одат со секое пови кува ње
на една функција , тие не ја задржуваат вредноста од еден до друг повик , и
мора експлицитно да се постават на некоја вредност при секое влегување во
функцијата . Во случај да не се направи тоа, ќе содржат случајни вредности тн.
ѓубре.
Како алтернатива за автоматските променливи , можно е да се дефинираат
променливи кои се надворешни (анг. external) за сите функции , т . е, промен­
ливи до кои може да се пристап и по име од сите функции. (Овој механизам е
36 Краток вовед во јазикот С Глава 1

сличен на фортрановите СОМК>N променливи или на паскаловите променли ви


декларирани во надворешниот блок) Бидејќи до надворешните променливи
може да се пристапни глобално, истите може да се користат наместо листи од
аргументи , за комуникација со податоци помеѓу функциите. Уште повеќе , би­
дејќи надворешните променливи се присутни постојано , т . е. не се креираат и
не исчезнуваат со повикување на функциите и излегување од нив, тие ги задр­
жуваат своите вредност и после излегување од функциите кои нив ги поставиле.
Една надворешна променлива мора да биде дефинирана, точно еднаш,
надвор од доменот на сите функции ; со што за неа се резервира постој ана ме­
мориска локација. Променливата мора, исто така, да биде декларирана внатре
во секоја функција која сака да пристап и до неа; тоа го поставува типот на про­
менливата. Декларацијата може да биде преку експлицитна extern наредба
или имплицитна, разбирлива од контекстот. Со цел , конкретно да дискути­
раме , да ја презапишеме програмата за наоѓање најдолга линија на влез, но
овојпат line , longest и max да бидат декларирани како надворешни промен­
ливи. Тоа бара промена во повику вањата , декларациите , а и телата на сите
три функции .

#include <stdio.h>

#define МAXLINE 1000 /* махсимапна големина на влез */

int max ; /* теховна махсимална големина */


char line[МAXLINE]; /* теховна •лезна линија */
char longest[МAXLINE]; /* најдолгата пинија се чува туха* /

int getline(void);
void copy(void);

/ * печатеи.е на најдолгата впезна пинија ; специјапизирана верзија*/


main()
{
int len ;
extern int max ;
extern char longest[] ;

max = О ;
while ((len = getline()) >О)
if (len > max) {
max = len ;
сору () ;

if (max > О) /* прочитана е линија */


printf("%s ", longest) ;
return О ;
1.1 о Надворешни nроменливи и делокруг 37

/* getline : епецијапизираиа верзија */


int getline(void)

int е , i ;
extern ehar line[] ;

for (i = О; i < МAXLINE - 1


(e=getehar)) != EOF != '\n'; ++i)
" line[i] = е ; " е

if (е - '\n' ) {
line[i] = е;
++i;

line[i] = '\0';
return i;

/* еору: епецијапизирана верзија */


void eopy(void)
{
int i ;
extern ehar line[], longest[] ;

i = О;
while ((longest[i] line [ i] ) ! = '\ О ' )
++i ;

Надворешните nроменливи во main , getline и еору се дефинирани од


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

резервираат мемориски nростор. Синтаксички , надворешните дефиниции


изгледаат исто како и дефинициите на локалните nроменливи, но бидејќи се
nојавуваат надвор од функциите , променливите се надворешни . Пред да може
една функција да користи една надворешна променлива , мора да и биде nо­
знато нејзиното име; декларацијата е иста како и претходно освен за додаде­
ниот клучен збор extern.
Во одредени ситуации, extern декларацијата може да се и сnушти. Ако де­
финицијата на надворешната променлива , во изворната датотека се наnрави
nред нејзина у nотреба во н~која функција, тогаш не nостои nотреба од extern
декларација во таа функција. Поради тоа extern декларациите во main,
getline и еору се излишни. Всушност , оnшта nрактика е да се nостават дефи­
нициите на сите надворешни nроменливи на nоч етокот на изворната датотека

и со тоа да се и сnушти декларирањето на сите надворешни променливи во сите

функции.
Доколку nрограмата се состои од nовеќе изворни датотеки , nроменлива де ­
финирана во "датотекаl ", а се користи во "датотека2", и "датотекаЗ ",
38 Краток вовед во јазикот С Глава 1

тогаш има потреба од extern декларации во "датотека2", и "датотекаЗ",


за да се поврзат нејзините појави таму о Вообичаена практика е да се соберат
extern декларациите на променливите и функциите во посебна датотека, ис­
ториски наречена заглавје (анго header), која се вклучува со #include директи­
ва на почетокот од секоја изворна датотека о Суфиксот о h е карактеристичен за
имињата на заглавјата о Функциите од стандардната библиотека, на пример, се
декларирани во заглавја како <stdio о h> о За тоа ќе стане збор повеќе во Глава
4, а за самата библиотека во Глава 7 и Додаток Б о
Бидејќи специјализираните верзии на getline и сору немаат аргументи,
логиката не наведува дека нивните прототипови декларирани на почетокот на

датотеката, б и биле getline () и сору () о Сепак, заради компатибилност со


постарите С програми, стандардот ја зема празната листа на аргументи како
декларација од стар стил и ја укинува проверката на сите аргументи во листата;
за празни листи мора експлицитно да се користи зборот void o Пове ќе за ова
во Глава 4 о
Би требало да забележите дека во оваа глава внимателно ги употребуваме
зборовите дефиниција и декларација, кога зборуваме за надворешни п ромен ­
ливи о Зборот "дефиниција" се однесува на местото каде што една променлива
е креирана и каде и е доделен мемориски простор; "декларација" се однесува
на местата каде е најавена природата на променливата, без да се резервира
мемориски простор за неа о

Патем речено , постои тенденција цела програма да се сведе на работа со


extern променливи, бидејќи тие ја поедноставуваат комуникацијата - листи­
те од аргументи се кратки, а променливите секогаш се таму кога ви требаат о
Но надворешните променливи секогаш се тука , дури и кога не ви требаат о
Програмирање засновано само на надворешни променливи е нож со две ос­
трици, бидејќи води до програми во кои врските помеѓу податоците воопшто
не се јасни - променливите можат да бидат променети неочекувано и од не ­
внимание , а самата програма е тешка за модифицирање о Поради тоа, втората
верзија на програмата за најдолга линија е полоша во однос на првата, делум­
но од овие причини , а делумно бидејќи ја упропастува општоста на две корис­
ни функции , со тоа што ги запишува во нив имињата на променливите со кои
тие манипулираато

Можеме да кажеме дека досега го обединивме се она што се нарекува јадро


на Со Со овие градбени блокови, можно е да се напише корисна програма од
разумна големина и веројатно би била добра идеја ако си дозволите доволно
време да ја спроведете на дело о Вежбите кои следат, предлагаат малку поком­
плексни програми од тие што ги обработивме досега о

Вежба 1-20 о Напишете програма detab која ги заменува табовите на влез со


соодветниот број на бланко- знаци за да се пополни просторот до наредниот
таб-завршеток о Претпоставете фиксно множество на таб-завршетоци, да рече­
ме n о Дали n треба да биде променлива или симболички параметар?
101о На двореш ни про менли ви и делок ру г 39

Вежба 1-210 Напишете програма entab која заменува стрин гови од бла н ко­
знаци со минимален број табови и бла нко-з наци кои заземаат еднаков прос­
тор о Користете исти табулатори како и за detab о Доколку и та булатор и бла н ко
биле доволни да се достигне следниот таб-завршеток , на кој знак треба да му
се даде предност?

Вежба 1-22 о Напишете програма која ги " прекршува " долгите линии ко и до­
аѓаат на влез, на две или повеќе по кратки линии после последн иот знак кој
не е бланко, а кој се јавува пред n-тата влез н а колона о Уверете се дека ваша ­
та програма интелигентно ги обработува долгите линии и во случај кога нема
бланко-знаци и табулатори пред специфицираната колона о

Вежба 1-23 о Напишете програма која ќе ги отстра ни сите коментари од кодот


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

Вежба 1-24 о Напишете програма за проверка на основните синтакс ички греш ­


ки во кодотна една е програма , како што се несоодветство на заградите ( големи
и мали, лева со десна) о Не заборавајте на наводниците ( еди ни ч н и и двојни) ,
контраниз секвенците и коментарите о (О ваа е тешка програма, доколку се
обидете да ја направите со целосна општост) о
Глава 2: Типови , оператори и И3ра3и

Променливите и константите се основните податочни објекти со кои се ма­


нипулира во една програма. Декларациите ги најаву ваат променливите кои
треба да се користат , од кој тип се и можеби кои се нивните почетни вредности.
Операторите специфицираат што треба да се направи со нив . Израз ите ком­
бинираат променливи и константи за да произведат нови вредности. Типот на
објектот го определува множеството вредности ко и и стиот може да ги прими и
какви операции може да се извршат над него . Овие градбени блокови се пред­
мет на оваа глава .

ANSI стандардот направи мали промени и проширувања на основн и те пода­


точни типови и изрази . Сега постојат означени (анг. signed) и неозначен и (а нг.
unsigпed ) облици на сите целобројни типови и нотации за неозначени констан­
ти и хексадекадни знаковt-Ји константи. Операциите со подвижна точка можат
да се наnрават со единична прецизност ; исто така, постои и long double тип за
проширена прецизност. Стринговите константи можат да се поврзуваат во те­
кот на компајлирањето . Енумерациите сега се дел од јазикот . Објектите може
да се декларираат со const, што оневозможува нивна промена. Правилата за
автоматски претворања помеѓу аритметичките типови се проширени , со цел ,
да обезбедат операции за побогатото множество податочни типови .

2.1 Имиња на променливите

Иако не спомнавме во Глава 1 , постојат некои ограничувања во врс ка со


имињата со кои може да се именуваат променливИте и симболичките констан­
ти . Имињата се состојат од броеви и букви ; првиот знак мора да биде бук ва.
Знакот за подвлечено "_ " се третира како буква ; понекогаш е корисен за по­
добрување на читливоста на променливите со долги имиња. Меѓутоа, немојте
да започнувате имиња на променливи со знак за подвлечено, бидејќи голем
број од рутините на библиотеката користат та ква нотација. Постои разли ­
ка помеѓу мала и голема буква , па така х и х се две сосема различни имиња .
Традиција во С е да се користат мали букви за имиња на променливи , а са мо
големи букви за имиња на симболички константи.
Најмалку првите 31 знак од некое внатрешно име се значајни. За и м ињ а-

41
42 Типови, оператори и изрази Гла ва 2

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


од 31, бидејќи надворешните имиња може да се користат од асембле рите
(анг. assemblers) и вчитувачите (анг. loaders) врз кои јазикот нема контрола. За
надворешни имиња, стандардот гарантира еднозначност само на првите 6 зна ­

ци. Клучните зборови како if , else, int, float, итн. , се резервирани: не


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

2.2 Податочни типови и нивна rоnемина


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

char еден бајт, може да чува еден знак од локалното множество на знаци
int цел број , обична ја рефлектира природната големината на целите
броеви на локалната машина
float број со подвижна точка со единична прецизност
double број со подвижна точка со двојна прецизност

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


основните типови. short и long се применуваат врз целите броеви

short int sh;


long int counter ;

""'
Зборот int може да се испушти во декларациите од овој тип , што и обична се
прави.

Целта е дека short и long би требало да обезбедат различна големина на


целобројните променливи каде што за тоа постои причина; int нормално ќе ја
има природната големина соодветна за една конкретна машина. short често
е со должина од 16 битови, а int е со должина од 16 или 32 бита. Секој компај­
лер има слобода во изборот на соодветните должини во зависност од сопстве­
ниот хардвер, со единствено ограничување дека short и int имаат должина

барем од 16 битови, long е барем 32 бита , и short не е поголем од int , кој,


пак, не е поголем одlong.
Квалификаторитеsigned и unsigned може да се придружат на char или на
која било целобројна вредност. unsigned броевите секогаш се позитивни или
еднакви на нула и за нив важат законите на аритметиката со модул 2n , каде n е
бројот на битови на податочниот тип . Така, на пример, ако char вредностите
се 8- битни, тоа значи дека променлива од тип unsigned char има вредности
2.3 Константи 43

помеѓу О и 255, додека онаа од тип signed char помеѓу -128 и 127 (кај
машини со двоен комплемент) . Дали char вредностите се означени или не­
означени зависи од машината, но знаците кои може да се печатат секогаш се

позитивни.

Типот 1ong doub1e специфицира реален број со зголемена прецизност .


Како и кај целобројните објекти, големините на реалните објекти се дефини­
рани со имплементацијата; float, doub1e, и 1ong doub1e можат да претстават
една, две или три различни големини.

Стандардните заглавја <1imi ts. h> и <float. h> содржат симболички кон­
станти за сите овие големини, заедно со другите својства на машината и ком­
пајлерот. За нив се зборува во Додатокот Б.

Вежба 2-1 . Напишете програма која ќе го определи опсегот на char, shorт ,


int и 1ong променливите, во двете варијанти, означени и неозначени, пре ку
печатење на соодветните вредности од стандардните заглавја и преку директ­
но пресметување. Потешко доколку нив ги пресметувате: определете го опсе­
гот на различните типови за реални броеви.

2.3 Константи

Целобројна константа како 1234, е int вредност. 1ong константа се пишу­


ва со завршно 1 (ел) или L, како во 123456789L; целобројна константа која
е преголема за да биде сместена во int, исто така, ќе биде земена за 1ong.
Неозначените константи се пишуваат со завршно u или u, а суфиксот u1 ил и UL
означува unsigned 1ong.
Реално, бројните константи содржат децимална точка (123. 4) или експо­
нент ( 1е-2) или обете; нивниот тип е doub1e, освен во случаите кога имаат
некој суфикс. Суфиксите f или F означуваат float константа; 1 или L означува­
ат 1ong doub1e.
Вредноста на еден цел број наместо во декадна, може да биде специфици­
рана во октална или хексадекадна нотација. Водечка о (нула) во некоја це­
лобројна константа означува октална претстава на бројот; водечки Ох или ох
означува хексадекадна претстава. На пример, декадниот запис 31 може октал­
но да се запише како 037, или хексадекадно како Ox1f или OxlF. Окталните и
хексадекадните константи можат да завршуваат со L, и во тој случај се 1ong ,
или со u кое ги прави unsigned: OXFUL е unsigned Jong константа со декадна
вредност 15.
Една знаковна константа претставува цел број, напишан со еден знак смес­
тен во апострофи, како што е 'х' . Вредноста на знаковната константа е број­
ната вредност на знакот во знаковното множество на компјутерот. На пример,
во ASCII знаковното множество, знаковната константа ' О ' има вредност од
48, која на никој начин не е поврзана со бројната вредност о. Ако напише­
ме 'о' наместо број на вредност 48, која за виси од знаковното множество ,
44 Тиnови, оператори и изрази Глава 2

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


за читање. Знаковните константи учествуваат во нумерички оnерации како и
другите целобројни тиnови, иако најчесто се уnотребуваат во сnоредба со дру­
гите знаци.

Кај знаковните и стринговите константи, некои знаци можат да се nретстават


како излезни секвенци, како што е \n (знак за нова линија) ; таквите секвенци
наликуваат на двозначни, но реnрезентираат само еден знак. Доnолнително,
група битови со nроизволна големина може да се заnише како

'\ооо'

каде ооо е една од трите октални цифри (О •• 7) или како

'\xhh '

каде hh е една или nовеќе хексадецимални цифри (О .. 9 , а .. f , А . . F) . Така б и


можеле да наnишеме

#define VТАВ '\013' /* ASCII вертихален таб */


#define BELL '\007' 1* ASCII знах за ѕвоно* /
"
или во хескадекадно

#define VТАВ '\xb' 1* ASCII вертихален таб */


#define BELL ' \х7' 1* ASCII знах за ѕвоно*/

Ова е комnлетното множество од излезни секвенци:

\а знак за аларм (ѕвоно) \\ контран и з

\b бришење место наназад \? nрашални к

\f НОВ ЛИСТ \' аnостроф


\n нова линија \" наводни к

\r нов ред (анг. carriage \ооо октален з а n и с

return)
\t хоризонтален таб \xhh хексадекаден заn и с

\v вертикален таб

Знаковната константа
'\0' го nретставува знакот со вред ност нул а , нултиот
(анг. null) знак. Често, наместо о се nишува
'\0' за да се поте н ци р а неговата
природа во некој израз, но бројната вредност му е само о .
Константен израз е израз кој содржи само константи . Та кв ите и зрази мо­
жат да бидат евалуирани за време на комnајлирањето , пред да се стартува
програмата и во согласност со тоа да се користат на секое место каде може да
2.3 Константи 45

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

#define МAXLINE 1000


charline[МAXLINE+1];

или

#define LEAP 1 / * во престапни години*/


intdays[31+28+LEAP+31+30+31+30+31+31+30+31+30+31];

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


како во случајов:

" Јаѕ sum string"

или

" " 1* nразен стринг * 1

Наводниците не се дел од стрингот и служат, единствено, да го разгран и­


чат. Истите излезни секвенци кои се користат кај знаците , важат и за стрин­
говите ; \" претставува знак за наводници. Стринг константите може да се
надоврзат(анг. сопсаtепаtе) за време на компајлирање:

" hello , " " world"

е исто што и

" hello, world"

Ова е корисно кога има потреба од пишување на долги стрингови кои се проте­
гаат на повеќе редови во кодот .
Технички , една стринг константа претставу ва низа од знаци. Внатрешната
репрезентација на стрингот поседува празен знак '\ о' кој се наоѓа на крајот ,
така што потребната физичка меморија е за еден поголема од бројот на зна­
ците кои се напишани помеѓу наводниците. Таа репрезентација означува дека
не постои ограничување за тоа колкава должина може да има еден стринг , но

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


на истиот. Функцијата strlen (ѕ) , од стандардната библиотека , ја враќа дол­
жината на нејзиниот стринг аргумент ѕ, не вклучувајќи ја терминалната н ула
'\о ' . Еве ја нашата верзија :
46 Типови, оператори и изрази Глава 2

/* strlen: врати ја должината на ѕ */


int strlen(char ѕ[])

int i;
while (ѕ [i] != '\0')
++i;
return i;

strlen и другите функции за обработка на стрингови се декларирани во стан­


дардното заглавје <string. h> .
Бидете внимателни при разграничувањето на знаковна константа со стринг
константа кој содржи само еден знак: 'х' не е исто што и "х " . Првото е цел
број, ја содржи бројната вредност на буквата х во знаковното множество на
компјутерот. Другото, пак, е низа од знаци која содржи еден знак (буквата х)
и '\0'.
Постои уште еден вид на константи, т. н. енумерациски константи.
Енумерацијата претставува листа од целобројни константи, како, на пример,

enumboolean {NO, УЕЅ};

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


освен ако вредностите не им се зададат експлицитно. Во случај да не се наве­
дени сите вредности, ненаведените вредности ја продолжуваат прогресијата
почнувајќи од последната наведена вредност, на начин покажан во следниот
пример:

enumescapes { BELL= '\а', ВАСКЅРАСЕ= '\b', ТАВ= '\t',


NEWLINE= '\n', VТАВ= '\v', RETURN= '\r'} ;

enummonths { JAN= 1, FEB, МАR , APR, МАУ, JUN,


JUL, AUG, ЅЕР, ОСТ, NOV, DEC } ;
/* FEB е 2, МАR е 3, итн */

Имињата од различни енумерации мора да се разликуваат. Вредностите во


рамките на една енумерација не мора да се различни.
Енумерациите обезбедуваат згоден начин за придружување константни
вредности со конкретни имиња, како алтернатива на #define, со таа предност
што вредностите можат автоматски да се генерираат за вас. Иако променливи­
те од типот enum може да бидат декларирани, компајлерите немаат потреба да
проверат дали тоа што вие го зачувувате во една таква променлива претставува

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


топати нудат можност за проверување п а најчесто се подобри од #define дирек­
тивите . Покрај тоа, програмата за анализа на изворниот код (анг. debbuger) е
во можност да ги отпечати вредностите на енумерациската променлива во нив­

ната симболичка форма.


2.4 Декл а рации 47

2.4 Декларации
Сите променливи мора да се декларираат пред нивната употреба, иако од­
редени декларации може да се из ведат имплицитно според содржината. Едн а
декларација специфицира тип и содржи л иста од една или повеќе променливи
од тој тип , како во

int lower , upper , step;


ehar е , line [1000] ;

Променливите може да се распоред ени ни з декларациите н а произволен на­


чин . Горенапишан ото можеш е да се нап и ше и како

int lower ;
int upper ;
int step ;
char е;

c har line[1000] ;

Овој начин одзема повеќе простор , но е посоодветен за додавање н а коментар


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

ehar еѕс= ' \\ ' ;


int i =О ;

int limi t =МAXLINE + 1 ;


float ерѕ = 1 . Ое-5 ;

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


цијата се прави само еднаш, концептуално пред да за почне из вршувањето н а
програмата, а иницијализаторот мора да биде константен израз . Една експли­
цитно иницијализ и рана автоматска променлива се иницијализира секогаш кога
се влегува во функцијата или во блокот во кој таа се наоѓа ; иницијализаторот
може да биде каков било израз. Надворешните и статичките променливи се
иницијализираат на о . Автоматските променливи за кои нема наведено екс­
плицитен иницијализатор имаат недефинирани (т.е. ѓубре ) вредности.
Квалификаторот const може да се примени во деклара цијата на секоја про­
менлива за да с пецифицира дека нејзината вредност нема да се менува . За
една низа квалификаторот const покажува дека нејз ините елеме нти нема да
се менуваат.
48 Типови, оператори и изрази Глава 2

const double е= 2 . 71828182845905 ;


const char msg [] = " warning";

Декла рацијата const може да се користи и со аргументи од низи , за да оз­


начи дека функцијата не ја менува таа низа:

int strlen (const char []) ;

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


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

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

Бинарни аритметички оператори во е се+ , - , *, 1, и модул операторот %.


Целобројното делење го отсекува можни от остаток . Изразот

х % у

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


полн делител на х . На пример, една година е престап на ако е делива со 400 ;
во спротивно истата е престап на ако е д елива со 4, но не и делива со 100.
З н ачи

if ( (year %4 =О && year %100 != 0) 11 year %400 ==О)


printf ( "%d is а leapyear\n " , year) ;
else
printf ( "%d is not а leap year \ n ", year) ;

Операторот % н е може да се примени врз float или double . Насоката н а отсе­


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

чаите кога има пречекорува ње на горна или долна граница . (анг. overflow и
uпderflow)
Бинарните + и - имаат еднаков приоритет , кој е по низок од приоритетот на
*, 1, и %, кои, пак, се со понизок приоритет од унарните + и -. Аритметичките
оп е ратори се асоцијативни од лево кон десно .
Табелата 2 . 1 на крајот од оваа глава, ги сумира приоритетот и асоцијатив­
н оста н а сите оператори во е .
2.6 Релациони и логички оператори 49

2.6 Реnациони и nоrички оператори

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

> >= < <=

Сите се со еднаков приоритет . Единствено операторите за проверка на еднак­


вост =-=-и ! =се со понизок приоритет од нив .
Релационите оператори имаат понизок приоритет од аритметичките , па
израз како i < 1im-1 се пресметува како i < (1im-1) , што е и за очекување.
Поинтересни се логичките оператори && и 1 1• Изразите поврзани со &&
или 11 се евалуираат од лево кон дес но , а евалуацијата престанува во оној мо­
мент кога ќе стане позната вистинитоста на изра зот. Повеќето С програми се
потпираат на овие правила . За пример , даден е циклусот од влезната функција
get1ine која ја напишавме во Глава 1:

for (i=O ; i < 1im-1 && (e=getehar ()) ! = '\n' && е ! = EOF ; ++i)
s[i] =е ;

Пред читањето на нов знак, потребно е да се провери д али има доволно место
за истиот да се смести во низата ѕ, поради што, најпрвин, мора да се провери
условот i < 1im - 1 . Значи, ако проверката е негативна , отпаѓа потребата да
читаме нов знак.

Исто така, би било погрешно , ако е се тестира за EOF , пред да биде повика­
на функцијата getehar; поради тоа, повикот за доделување мора да се изведе
пред да се провери содржината на е.

Приоритетот на && е повисок од оној на 1 1, додека и двата оператора се со


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

изразите како

i < 1im-1 && (e=getehar()) != ' \n' && е !=EOF

не постои потреба од додатни за гради . Но, бидејќи приоритетот на ! =е пови­


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

(e=getehar()) != '\n'

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

редување со '\n'.
По дефиниција , бројната вредност на релационен или логички израз е 1 ако
релацијата е вистинита и о кога е невистинита .
Унарниот оператор за негација ! ги претв ора сите ненулти о п ера нди во о , а
нултите во 1. Негацијата често се употребува во конструкции од обликот
50 Типови, оператори и изрази Глава 2

if ( !valid)

како замена за

i f (valid == О)
Тешко е да се рече која од двете форми е подобра. Конструкциите како
!valid се читаат ( " ако не е валидно " ) , но покомплицираните изрази можат
да бидат тешки за разбирање.

Вежба 2-2. Напишете циклус еквивалентен за горенапишаниот for ци­


клус, без користење на && или 1 1.

2.7 Конверзија на типови

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


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

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


се изгуби инфо рмација , како што е, на п ример, претворањето на цел број во
реален број кај изрази од облик f + i. Изразите кои немаат смисла, како ко­
ристење на float за индексирање , не се дозволени. Изра зи те во кои може да
дојде до загуба н а информација, како при доделување на подолг интегрален
тип во пократок , или при доделување на реален тип кон int, може да повле­

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


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

/* atoi : хонверзија на ѕ во цел број */


int atoi(char ѕ[])

int i , n;

n = О;
for (i = О ; s[i] >= '0' && s[i] <= '9 ' ; ++i)
n =
10 * n + (ѕ [i] - '0') ;
return n ;

Како што збо рувавме во Глава 1, изразот


2.7 Конверзија на типов и 51

s[i]- '0'

ја дава бројната вредност на знакот складиран во ѕ [i] , но, само затоа што
вредностите 'о' , '1' , итн. формираат континуирана секвенца во растечки
редослед.

Друг пример за конверзија на char во int е функцијата 1ower, која за зна­


ковното м ножество ASCII, тран сформира голема во мала бу ква . Ако зн а кот н е
е голема буква, 1ower го враќа непроменет .

/ * lower: хонве/зија на е во мапа буква ; важи само за ASCII */


int lower(int е}

if (е>= ' А' && е <= 'Z'}


return е + 'а' - 'А' ;
е1ѕе
return е;

Овој код, единствено, функциони ра за ASCII , бидејќи кај ова множество соод­
ветните знаци за голема и мала буква се наоѓаат на фиксно растојание од по ­
четокот до крајот на азбуката , а помеѓу нив нема никакви други знаци осв ен
букви. Но, тоа не е вистина и за знаковното множество EBCDIC, така што оваа
функција во тој случај е бесмислена .
Стандардното заглавје <ctype. h >, опишан во Додаток Б , дефинира фа­
милија на функции кои обезбедуваат тестови и кон верзии, независни од зна­
ковното множество. На пример , функцијата tolower е портабилна замена за
функцијата lower која претходн о ја разгледавме. Слично на тоа , проверката

е >= 'О' & & е <= ' 9'

може да се замени со

isdigit(c}

Отсега натаму ќе ги користиме функциите дефинирани во <ctype . h>.


Постои една чувствителна точка во врска со претворањето на char во int .
Јазикот не специфицира дали променливите од тип char , се означени (анг.
sigпed) или неозначени (анг. uпsigned) величини. Дали конверзијата на еден
char во int, некогаш може да ре зул тира со негати в на вредност? Одговорот
варира од машина до машина, рефлектирајќи ги разликите во архите ктурата.
Кај некои машини char чиј најлев бит е 1 ќе б иде претворен во негативен цел
број ("знаковна екстензија"} . На други, пак, знакот се промовира во цел број
само со додавање на нулти битови од левата страна , што с екогаш резултира со
позитивна вредност.

Основната дефиниција на С гарантира дека ниту еден знак од стандард но-


52 Типови, оператори и изрази Глава 2

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


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

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


даток треба да се чува во ehar променлива, секогаш треба да се специфицира
signed или unsignedдeклapaциja на таа променлива.
Релациски изрази како i > ј, и логички изрази поврзани со && и 11, доколку
се вистинити, седефинирани да имаат вредност 1, а О во спротивен случај. Така
доделувањето

d = е >= 'О' & & е <= '9'

го поставува d за 1 во случај кога е е цифра и о кога не е цифра. Сепак , функ­


циите како isdigit за "вистина" можат да вратат каква било ненулта вредност.
Во делот за услов на if, while, for , итн . " вистината " , исто така, е означена
од која било ненулта вредност , па така ова не прави разлика
Имплицитните аритметички кон верзии функционираат според очекувања­
та . Општо , кај оператор кој има два операнда од различен тип (бинарен опе­
ратор), како + или -, пред почетокот на операцијата, " понискиот" тип нагорно
се претопува во "повисок" тип. Резултатот е од " повисокиот" тип. Делот б од
Додаток А точно ги прецизира правилата за конверзија. Во случај кога нема
unsigned операнди, следното множество пра вила би било задоволително:

- Ако кој било од операндите е long double , претвори го другиот во long


double.
- Во спротивно, ако кој било од операндите е double, претвори го другиот
во double.
-Во спротивно , ако кој било од операндите е fioat , претвори го другиот
BOfioat.
- Во спротивно, претвори ehar и short int, во int.
-Потоа, ако кој било од операндите е long , претвори го и другиот во long.

Забележете дека fioat променливите во еден израз не се претвораат во


double по автоматизам; тоа е промена во однос на оригиналната дефиниција.
Општо земено, математичките функции како тие во <math . h> користат двој на
прецизност. Главната причина за кори с тење на fioat е заштеда на мемориски
простор при користење на големи низ и, или , почесто, заштеда на процесор­

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

Правилата за конверзија се покомплицирани кога се вклучени и unsigned


операнди. Проблем претставува тоа што споредбите помеѓу означени и не­
означени вредности зависат од машината , бидејќи зависат од големините на
различните целобројни типови. На пример, да претпоставиме дека int типот
2.7 Конверзија на типови 53

е16- битен, а long типот 32. Тогаш -lL < lU, бидејќи lU, којшто е unsigned
int, се претвора во signed long. Но -lL > lUL бидејќи -lL се конвертира во
unsigned long, па поради тоа изгледа како голем позитивен број.
Конверзиите можат да се вршат и за време на доделувањата ; вредноста од
десната страна се претвора во типот кој е од левата , т. е. во типот на резулта­
тот.

Знак се претвора во цел број , било со знаковна екстензија или без неа ,
како што беше опишано nогоре.
Долгите цели броеви се претвораат во кратки или во знаци со отфрлање на
вишокот високи битови. Така во

inti;
ehar е ;

i =е ;
c=i ;

вредноста на е останува непроменета. Ова е вистина без разлика дали има или
нема инволвирано знаковна екстензија. сепак, промена на редоследот на
доделувањата може да доведе до загуба на информација .
Ако х е float и i е int, тогаш обете, тогаш и х = i и i = х резултираат со
конверзија ; float во int доведува до отфрлање на децималниот дел. При nре­
творање на double во floa t, во зависност од имплементацијата , конверз ијата
на вредноста се прави со заокружување или со отсекување .

Бидејќи аргумент во функцискиот повик nретставува израз , конверзија на


типови настанува и кога аргументите се nредаваат на функциите. Во отсуство
на функциски прототип , ehar и short се nретвораат во int , а float станува
double. Тоа е причината зошто ги деклариравме функциските аргументи за
int и double дури и кога функцијата треба да се повика со ehar или floa t.
Конечно, ексnлицитни кон верзии на тиnови може да бидат наметнати во
кој било израз, nреку уnотреба на унарниот onepamop за претопување (анг.
cast) . Во конструкцијата

(име на т и п) израз

израз се претвора во именуваниот тип во согласност со погорните nравила за

конверзија . Вистинското значење на оnераторот за претоnување е еквивалент­


но со доделувањето на израз кон nроменлива од специфичен тип , која потоа
ќе се употребува наместо целата конструкција. На nример , библиотечната
рутина sqrt очекува аргументи од тиn double, na во случај на аргумент од
друг тип, нејзиниот резултат не може да се nредвиди . ( sqrt е декларирана во
<math . h>.) Така , ако n е цел број, може да уnотребиме

sqrt ( (double) n)
54 Типови , оператори и изрази Глава 2

со цел , да извршиме претворање на n во doub1e вредност, пред истиот д а се

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


продуцира вредност на n од соодветниот тип; врз самиот n не се вршат ни­
какви проме ни . Операторот за претопув ање има ист приоритет како и другите
унарни о ператори, како што е сумира но во табелата н а крајот од оваа глава .
Ако аргументите се декларирани во функциски прототип , што е и правилно,
декларацијата п редизвикува автоматско претвора ње н а сите аргументи во мо­
ментот кога ќе биде по викана функциј ата. Така , ако е даден прототип на sqrt

d oub1e sqrt (doub1e)

повикот

root2 = sqrt (2)

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


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

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


на псевдослучај ни броеви и функција за иницијали зација на неговиот зачеток
(а нг. seed); следниот код илустрира употреба на операторот за претопување:

unsigned 1ong int next = 1;


/ * rand : врах а nсевдоспучаен цел број во оnсег од О .. 32767 */
int rand(void)

next = next * 1103515245 + 12345 ;


return (un signed int) (next/65536) % 32768 ;

/ * srand : nоставува зачетох за rand()* /


void srand(unsigned int seed)
{
n ext = seed ;

Вежба 2-3 . Напишете функција htoi (ѕ) , која претвора стри нг од хексадекад­
ни цифри (вкл уч ително и опционалните Ох или ОХ) во негов целоброен екви ­
валент . Дозволени цифри се од О до 9, од а до f, и од А до F.

2.8 Оператори 3а инкрементирање и декрементирање

С обезбедува два невооби чаени оператора за инкрементирање и декре­


ментирање на променливите . Операторот за инкрементирање ++ додава 1 на
2.8 Оператори за ин креме нти рање и декрементирање 55

неговиот операнд, додека операторот за декрементирање -- одзема 1 . Н ие


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

if (е= '\n')
++nl ;

Необичниот асnект се должи на тоа што++ и-- можат да се користат и ка ко


nрефиксни оператори (nред nроменливата , на пример, ++n) и како n остфикс­
ни оnератори (nосле nроменливата: n++) . И во двата случ аја , к рајниот ефект
е зголемување на n . Но кај изразот ++n зголемувањето на n настанува пред не­
говата вредност да биде искористена , додека кај n++ , n се згол емува п осле
користењето на неговата вредност . Тоа значи дека во контекс т каде што се ко­
ристи вредноста на променливата , ++n и n++ се различни. Ако n е 5, тогаш

x=n++ ;

го nоставува х на вредност 5 но,

x=++n;

го nоставува х на вредност б . И во двата случаја, n станува б. Оnераторите за


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

ви ; изразот од облик (i+j) ++не е дозволен .


Во контекст каде што не се користи вредноста , туку само ефектот на згол е­
мување, како во

if (е== '\n')
nl++ ;

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


ации во кои се употребува едната или другата форма . На nр и мер , да ја р азгле­
даме функцијата squeeze (ѕ , е) , која ги отстранува сите nојави на знакот е од
стрингот ѕ:

1* squeeze : rи брише сите е од ѕ *1


void squeeze (char ѕ [] , int е)
{
int i, ј;

for (i=j=O ; s[i] != ' \0' ; i++)


i f (s[i] !=е)
ѕ [ ј ++] =ѕ [ i] ;

ѕ[ј] = ' \0 ';


56 Типови , оператори и и з рази Глава 2

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


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

i f (s[i] !=е) {
ѕ[ј] =s[i] ;
ј++ ;

Друг пример на слична конструкција доаѓа од функцијата qetline која ја


напишавме во Глава 1 . Во неа може да извршиме измена

if (е= '\n ' )


s[i] =е ;

++i ;

со покомпактниот облик

if (е= '\n ' )


ѕ [i++] =е ;

Како трет пример , да ја разгледаме стандардната функција streat (ѕ , t) ,


која го надоврзува стрингот t на крајот од стрингот ѕ. streat претпоставува
дека ѕ има резервирано доволно простор да ја чува резултантната вредност .
Како што веќе напишавме, strcat не враќа вредност; верзијата од стандардната
библиотека враќа покажувач кон резултантниот стринг.

/* strcat: ~ ro t на JCP&jor на ѕ ; ѕ н:ра да е ДСЕ10Ј1Н0 Г0ЈЈ1!М */


void streat(ehar ѕ[] , ehar t[])
{
int i , ј ;

i = ј = О;
while (s[i] != '\0') /* најди крај на ѕ */
i++ ;
while ((s[i++] = t[j++]) != '\0' ) /* коnирај t */

Како што секој елемент од t се копира во ѕ , постфиксниот оператор ++се при­


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

Вежба 2-4. Напишете алтернативна верзија на squeeze (ѕ1 , ѕ2) која ги от­
странува сите знаци од стрингот ѕ1 кои се содржат во ѕ2 .

Вежба 2-5 . Напишете функција any ( ѕ 1, ѕ2) , која ја вра ќа прв ата локација в о
стрингот ѕ1, каде се појавува кој било од знак од ѕ2 , или -1 ако ѕ1 не содржи
знаци од ѕ2 . (Функцијата од стандардната библиотека strpbrk ja извршува и с­
тата работа , но враќа покажувач кон ло кацијата . )

2.9 Битски оператори

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


врз целобројни операнди т . е , char, short, int и long , во означена и не­
означена варијанта .

& битско И
битско ИЛИ
битско исклучително ИЛИ
<< лево поместување (анг. shift)
>> десно поместување

единичен комплемент (унарен)

Битскиот И оператор & често се користи за да се зама с кира н е кое мн ожеств о


битови, на пример,

n =n & 0177

ги поставува сите , освен првите 7 ниски битови од n, на о.


Битскиот ИЛИ оператор 1 се употребува за вклучување на битовите :

Х= Х 1 SET_ON ;

ги поставува на вредност 1 битовите од х , кои имаат вредност 1 во ЅЕТ_ ON .


Битскиот исклучително ИЛИ оператор"' става единица на местата во кои би­
товите на двата операнда се разликуваат, и нула таму каде што с е и сти.

Мора да се прави разлика помеѓу битските оператори & и 1 и логичките опе­


ратори && и 1 1, кои имплицира ат евал уација на вистината од лево-кон-десно.
На пример , ако х е 1 иуе 2, тогаш х & у е нула, додека х && у е единица.
Операторите за поместување <<и>> изведуваат лево и десно поместување
во битовите на нивниот лев операнд за број на места даден со десниот опе­
ранд , кој мора да биде ненегативен . Така х << 2 ја поместува вредноста на х
за две места , сместувајќи нули во испра знетите битови ; тоа е еквивалентно
на множење со 4. Поместувањето в о де с но на unsigned вредност се кога ш ре-
58 Ти пови, оператори и изрази Глава 2

зулти ра со п о п ол ну ва њ е на испразнетите места с о о . Десно поместување на


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

вање") на други .
Унарниот оператор- враќа единичен комплемент на еден цел број ; т. е. го
трансформ ира секој 1-бит во о-бит и обратно .

х =х & - 077
/

ги наместува nоследните 6 битови од х на нула. Забележете дека х & -077 не


за виси од должината на зборот , што е значајна предност пред , да речеме, х &
0177700 , кој претп оставува дека х е 16-битна вел ичина . Преносливата форма
не вкл учува дополнителна це на , бидејќи - 077 е константен израз кој може да
се евал у ира за време на компајлирањето .
Како илустрациј а за некои од битските опе ратори , да ја разгледаме функ­
цијата getbi ts (х , р, n) која враќа (десно порамнето) n-битна поле од х
кое за почнува на поз и циј а р . Се претпоставува де ка пози цијата на бит о е на
десниот крај и дека n и р се доволно мал и и позитивни вредн ости. Н а пример,
getbi ts (х , 4 , 3) ги враќа трите битови кои се на местата 4 , 3, и 2, десно
пора мнети

1* getbi ts :
земи n битови од по зи ција р */
uns ignedgetbits (unsignedx , intp , intn)

return (х >> (p+1- n)) & - ( - О<< n);

Изразот х >> (p+1- n) го поместува посакуваното поле на десниот крај од збо­


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

<< n сместува нули во н ајдесн ите n битови ; понатамо шно комплементирање на


тоа, со - креира маска со единици во најдесните n битови.

Вежба 2- 6 . Н апишете функција setbits (х , р, n, у) која го враќа х со n бито­


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

Вежба 2- 7 . Напишете функција invert (х , р , n) која го враќа х со n битови


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

Вежба 2 - 7. Напишете функција rightroot (х, n) која враќа вредност на це­


лиот број х ротиран во десно за n позиции.
2.10 Оnератори и изрази за доделување на вредност 59

2.1 О Оператори и изрази за доделување на вредност

Израз како

i=i+2

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


наnише во nокомnактна форма

i +=2

Оnераторот +=се нарекува onepamop за доделување вредност.


Повеќето бинарни оnератори (оnераторите ка ко+ кои имаат лев и десен
о nеранд) имаат соодветен оnератор за доделување on=, каде on е еден од

+ - * 1 %<< >> & л 1

Ако изр и изр се изрази , тогаш


1 2

е еквивалентно со

со таа разлика што изр 1 се nресметува само еднаш. Забележете ги заградите


околу изр :
2

х *=у+ 1

оз начува

х=х * (у+ 1)

а не

х=х*у+1

Како nример ќе ја наnишеме функцијата bitcount , која ги брои битовите­


единици , кои се nојавуваат во нејзиниот целоброен аргумент.
60 Типови, оператори и изрази Глава 2

1* bitcount: ги брои 1-битовите во х */


int bi tcount (unsigned х)
{
intb ;

for (b=O; х !=О ; х>>=1)

if (х & 01)
b++ ;
returnb ;

Декларирањето на аргументот х за unsigned обезбедува дека при десно по­


местување, испразнетите битови се исполнуваат со нули , а не со вредноста на
битот за знак, независно од машината на која се извршува програмата .
Освен краткоста и концизноста, операторите за доделување имаат п ред­
ност во комуникацијата со оној што ја чита програмата. Навистина , полесн о е
да се каже " додај 2 на i" или "з големи го i за 2 ", отколку "земи го i , додај 2 и
врати го резултатот, пак, во i ". Од таа причина изразот i += 2 е почитлив од i =
i + 2. Покрај тоа , за комплексни изрази како

yyval [yypv [рЗ+р4] + yypv [pl+p2] ] += 2

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


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

навистина , се исти. Употребата на оператори за доделување може да доведе до


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

while ( (е= getchar () ) ! = EOF)

Останатите оператори за доделување (+=, -=, итн . ) можат да се с ретнат во


изразите , иако нивната употреба е поретка.
Во сите такви изрази, типот на изразот за доделување е ист со ти п от на ле­
виот операнд, а вредноста е таа после доделувањето .

Вежба 2-9. Во броен систем со двоен комплемент , х &= (х-1) ја брише нај­
десната битска единица во х. Објаснете зошто. Искористете го тој за клучок за
да напишете побрза варијанта на bi tcount.
2011 Условни изрази 61

2.11 Усnовни и3ра3и

Наредбите

if(a>b)
z=a;
else
z=b;

го пресметуваат z како максимум од а и b о Условниот израз, напишан со тер­


нарниот оператор "?:" обезбедува алтернативен начин да се напише оваа и
други конструкции слични на-неа

изр 1 ? изр 2 : изр 3

изразот изр пресметува најпрв о Ако резултатот е ненулти (вистина)


1 , тогаш се
евалуира изразот изр и неговата вредност е вредност за условниот израз о Во
2
спротивно, се евалуира изр , и вредноста на условниот израз е еднаква на не­
3
говата о Се евалуира само едниот од изразите изр 2 и изр3 о Така, за да се постави
во z максимумот од а и од b б и можеле да напишеме

z =· (а >b) ? а: b; /* z =max(a, b) */

Да забележиме дека условниот израз навистина е израз и дека може да се


користи на начин ист како и кои било други изрази о Ако изр 2 и изр 3 се од разли­
чен тип, типот на резултатот се определува по правилата за претворање, кои

претходно ги разгледавме во ова поглавје о На пример, ако f е float и n е int,


тогаш изразот

(n>O) ?f: n

е од тип float, без оглед на тоа дали nе позитивен или не е о


Заградите околу првиот израз се непотребни поради нискиот приоритет на
операторот?: , кој е малку над операторот за доделување о Сепак, ја препора­
чуваме нивната употреба бидејќи ја зголемуваат видливоста на условниот дел
од изразото

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


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

for (i=O; i<n; i++)


printf("%6d%c", a[i], (i%10=9 11 i=n-1)? '\n' ' ');
62 Типови , оператори и изрази Глава 2

Знак за нова линија се печати после секој десети елемент, како и после n-тиот .
Сите други елементи се проследени со едно бланко . Овој израз можеби изгле­
да ком пли цирано, но е многу п окомпактен откол ку еквивалентниот i f/ else.
Друг добар пример би бил

printf( " Youhave %ditem%s . \n" , n , n==l ? ""; "ѕ" ) ;

Вежба 2-10. Повторно н-апишете ја функцијата l ower, која големите букви ги


претвора во ма л и, со у потреба на условен израз наместо if-else.

2.12 Приоритет и редослед на евалуирање

Табелата 2 . 1 ги сумира правилата за предимство (приоритет) и асоцијатив­


ност на сите оператори, вклучувајќи ги и тие кои не беа досега споменати .
Операторите во иста линија имаат ист пр иоритет ; редовите се н аредени во
опаѓачки приоритет, така што , н а пример * , 1 и %имаат исти п риоритет , кој
е повисок од приоритетот на бинарните + и -. "Операторот" () се однесува
на функциски пов ик. Операторите -> и . се користа т за пристап до членови
на структури; за ни в ќе зборуваме во Глава б, заедно со sizeof (големина на
објект) . Глава ѕ ги обработува * (индирекција преку покажувач) и & (адреса
на објект) , а Глава з операторот ',' (за пирка) .

ТАБЕЛА 2.1- Приоритет и асоцијативност на onepamopume

Оператори Приоритет
() []-> . лево ко н десно

! - ++ -- + - * (type) sizeof десно кон лево

* 1% лево кон десно

+- лево кон десно

<<>> лево кон десно

<<= >>= лево ко н десно

== != лево ко н десно

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

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

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

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

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

?: десно ко н лево

= += - = *= / = %= &= л= 1= <<= >>= десно кон лево

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

Унарните + , -, и * имаат повисок приоритет од нивните бинарн и форми.


2012 Приоритет и редослед на евалуирање 63

Забележете дека приоритетот на битските оператори & , л и 1е понизок од


приоритетот на= и ! =о Ова наведува на заклучок дека изразите за битско тес­
тирање од облик

i f ( (х & МАЅК) == 0)
мораат да бидат целосно омеѓени со загради о
С, како и повеќето јазици, не го специфицира редоследот по кој се евал уи ­
раат операндите на еден оператор о (Исклучок претставуваат операторите && ,
11 , ? : и ' ,' о) На пример, во израз како

x=f() +g();

f може да биде пресметано пред g, и обратно; поради тоа, ако која било од f
и g изврши промена на променлива од која зависат и двете , може да се случи
х да за виси од редоследот на пресметувањето о За решавање на тој проблем
потребно е меѓурезултатите да се чуваат во привремени променливи о

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


не е јасно специфициран , па изразот

printf ("%d %d\n", ++n, power (2 , n)); 1* ПОГРЕШНО * 1

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


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

++n ;
printf( "%d%d\n", n, power(2, n));

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


инкрементирање и декрементирање, доведуваат до "дополнителни влијанија"
- променливата се менува заради евалуација на некој израз о Во сите изрази во
кои се манифестираат странични појави, може да се јават суптилни зависности
во поглед на редоследот по кој се ажурираат променливите кои учествуваат во
изразот о Еве една незгодна ситуација

a[i] = i++;
Прашање е дали индексот ја содржи старата или новата вредност на i о
Компајлерите можат да го интерпретираат ова на различни начини и гене ри ра­
ат различни одговори во зависност од интерпретацијата о Стандардот намерно
ги остава ваквите прашања како недефинирани о Дали и кога ќе се појава т до­
полнителните влијанија (доделување на вредност на променливи) во рамките
на еден израз е оставено да за виси од компајлерот , бидејќи најдобриот ре­
дослед строго зависи од машинската архитектура. (Стандардот специфицира
дека сите дополнителни влијанија кај аргументите се случуваат пред повику­
вање на функцијата, но дури и тоа не би помогнало во случај демонстрира н со
функцијата printf, напиша на малку погоре.)
Поуката е дека пишување на код , кој за виси од редоследот на евалуација ,
е лоша програмерска практика за кој било јазик. Нормално, потребно е да се
знае кои нешта треба да се одбегнуваат , но ако немате познавање како тие се
извршуваат на различни машини , не треба да бидете предизви кани да ги ко­
ристите предностите на поединечните конфигурации.
Глава 3: Контрола на текот

Исказите (наредбите ) за контролата на текот во еден јазик , го посочуваат ре­


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

3.1 Наредби и блокови

Изразот од облик х =о или i++ или printf {... ) станува исказ (наредба) кога
ќе биде проследен со точка-запир ка, како во

х=О ;
i++ ;
printf{ ... ) ;

Во С, точка-запирка означува крај на наредба , а не сепаратор како во јазиците


од тип на Паскал.
Големите загради { и } се употребуваат за групирање на деклараци и и
изрази во сложена наредба, или блок, за да бидат тие синтаксички еквивалент­
ни на една наредба. Очигледен пример за ова се заградите кои ги оградуваат
наредбите на функциите ; уште еден пример се заградите околу повеќекратни
наредби после if , else , while , или for . {Променливите можат да се декла­
рираат внатре во кој било блок; за тоа повеќе ќе збо руваме во Глава 4. ) Не
се става точка-запирка после затворена голема заграда која означува крај на
блок.

3.2 if-else

Наредбата if - else се користи за изразување на одлуки. Фор малната син­


такса е

65
66 Контрола на текот Глава 3

if(uзpoз)
норедбо
1
else
норедбо 2

каде else делат е опционален. Првин, се евалуира изразот; ако е вистин ит


(т . е. , ако изрозот има ненулта вредност) , се извршува норедба 1 • Ако е невис­
тинит (изрозот е нула) и ако постои else дел , тогаш се извршува норедба2 •
Бидејќи if, едноставно, врши споредба на бројната вредност на еден из­
раз, можно е да се направат одредени кратења на кодот. На пример, доволно
е да се напише

if (izraz)

наместо

if (izraz !=О)

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


Бидејќи делат else може да биде опционален, постои двосмисленост кога
else ќе се проп у шти да се напише во вгнездена if секвенца . Тоа е разрешено
така што else се придружува на најблискиот if кој не поседува else дел . На
пример,

if (n>O)
if(a>b)
z =а ;

else
z =b ;

else се однесува на внатрешниот if , како што е покажа но со вовлекувањето .

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


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

if (n>O) {
if(a>b)
z=a ;

else
z =b ;

Двосмисленоста посебно е очигледна во ситуации како оваа


3.3 else- if 67

if (n>O)
for (i=O ; i<n; i++)
if (ѕ [i] >О) {
printf(" . .. " ) ;
return i;

else / *ПОГРЕШНО*/
printf ( " error --n is negative\n") ;

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


компајлерот тоа не го разбира и го асоцира else со внатрешниот if. Вакви
видови на грешки се тешки за детекција ; добра идеја е да се користат големи
загради, секаде на сите места, каде што се употребуваат вгнезден и if наред­
би. Патем , забележете го присуството на точка-запирка после z =а во

if (а >b)
z =а ;

else
z=b;

Тоа е од причина што граматички , наредба следи после if , а крајот на наред­


бите како ,.z = а;"секогаш се означува со точка-запирка .

3.3 else- if

Појавата на конструкцијата

if (израз)

наредба
else if (израз)

наредба
else/ff (израз)
наредба
else if (израз)

наредба
else
наредба

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


Последователните if наредби се најчест начин за пишување на пове ќенасочни
одлуки. Изразите се пресметуваат редоследно; Ако израз е вистина, се извр­
шува соодветната наредба која му е придружена и со тоа се прекинува целиот
синџир. Како што наведовме повеќе пати досега, кодот за секоја наредба може
68 Контрола на текот Глава 3

да биде единична наредба 1 или група од повеќе наредби омеѓени со големи


загради о

Последниот else дел 1 ги покрива случаите кои не одговараат на ниту еден


од претходните услови 1 или, пак, некоја општа акција која треба да се изврши
кога не се задоволени претходните услови о Понекогаш не постои општа ак­
ција; во тие случаи не се пишува последниот

else
наредба

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

вање на "невозможен" услов о

За да илустрираме тринасочна одлука 1 ќе напишеме функција за бинарно


преба рување 1 која проверува дали и на која позиција вредноста х се појавува
во сортираната низа v о Елементите на v мора да бидат наредени во растечки
редослед о Функцијата ќе враќа позиција (број помеѓу о и n- 1 ) каде х се поја­
вува во v 1 и -1 во случај кога не се појавува о Бинарното пребарување функ­
ционира , така што, првин, се споредува влезната вредност на х со средниот

елемент на низата v о Ако х е помало од средната вредност, пребарувањето се


фокусира на првата половина од низата , во спротивно на втората о И во двата
случај а 1 х се споредува со средишниот елемент на одбраната половина о Овој
процес на делење на половина и споредување 1 трае се додека не се пронајде х
или големината на поделбите не стане о о

/* binsearch: nроиаоѓа х во v[O] <= v[1] <= о о о <= v[n-1] */


int binsearch(int Х 1 int v[] 1 int n)

int low , high, mid;

low = О;
high = n - 1 ;
while (low <= high) {
mid = (low+high)/2 ;
if (х < v[mid])
high = mid - 1 ;
els~ if (х > v[mid])
low = mid + 1 ;
else /* nозицијата е nронајдена */
return mid ;

return -1 ; /* нема nојава на вредноста што се бара */

Фундаменталната одлука во секој циклус овде е дали х е помало, поголемо или


еднакво на средишниот елемент v [mid] ; најсоодветна и природна употреба
за else- if о
3.4 switch 69

Вежба 3-1. Нашето бинарно пребарување и зведув а две споредби внатре в о


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

3.4switch

swi tch е наредба за повеќенасочни одлуки која проверува дали еден израз
по вредност се поклопува со една од повеќето константни целобројни вред­
ности и во согласност со тоа го насочува разгранува њето во сак а ната н а сока.

switch ( израз ) {
саѕе конст- израз : наредби
саѕе кон ст-израз: наредби
defaul t : наредби

Секоја опција (анг. саѕе) е означена со една или повеќе целобројн и константи
и ли константни израз и . Ако вредноста на изразот одго в а ра на не која од саѕе
опциите , тогаш извршувањето на кодот почн ува од таа опција . Сите саѕе из­
рази мора да бидат различни. Опцијата означена к а ко defaul t се из в р шув а
кога ниту една од опциите не е задоволена . Употребата на defaul t делот е
опционална ; ако истиот не се напише и ако ниту една од наведените опции

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


defaul t делат може да се појават во произволен редослед.
Во Глава 1 , напишавме програма за броење на појавувањата на секоја ци­
фра , празно место и сите други знаци , со користење на секвенцата if . . . else
if . . . else . Подолу е истата програма , сега напишана со swi tch :
Наредбата break предизвикува моментен излез од swi tch конструкцијата .
Бидејќи саѕе опциите служат само како ознаки (aнг. label), отка ко ќе се изврши
кодот од една опција , извршувањето npeoza на следната , освен ако не е презе­
мена експлицитна акција за прекин. break и return се вообичаените нач и ни
за излегување од swi tch наредбата. Наредбата break , исто така, може да се
користи и за моментален излез од циклусите while , for и do , случаи што и ќе
ги разгледаме во ова поглавје.
Проаѓањето н Из опциите е меч со две острици . Позитивна е што дозволу­
ва повеќе саѕе случаи да се асоцираат со една конкретна акција , како што се
цифрите во нашиот пример. Но, исто така, имплицира дека во нормален случ ај
секој саѕе треба да завршува со break наредба, со цел да се спречи премин во
несакани подрачја. Преоѓањето од еден од случаите на друг не е робустно што
го прави склон на дезинтеграција кога програмата се преправа . Со искл учо к н а
повеќекратните ознаки за еден случај , преоѓањата б и требало да се користат
рационална , просл едени со коментари.
70 Контрола на текот Глава 3

#include <stdio. h>


main() / *брои цифри, nра5ии места, друrи* /
{
intc, i, nwhite , nother , ndigit[lO];

nwhi te = nother = О ;
for (i=O ; i<lO ; i++)
ndigit[i] =О ;
while ((е= getchar ()) ! = EOF) {
switch (е) {
саѕе '0': саѕе '1' : саѕе ' 2': саѕе ' 3 ': саѕе ' 4':
саѕе '5': саѕе ' 6 ': саѕе ' 7 ': саѕе '8 ': саѕе ' 9' :
ndigit[c-'0 ' ]++ ;
break;
саѕе ' ':

саѕе ' \n':


саѕе '\t':
nwhite++ ;
break;
default:
nother++ ;
break ;

printf( "digits=" ) ;
for (i=O ; i<lO; i++)
printf( "%d" , ndigit[i]);
printf ( " , white ѕрасе = %d , other = %d\n",
nwhite , nother) ;
return О ;

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


опција (во нашиот случај defaul t) , иако тоа логично не е потребно . Следниот
пат кога некоја друга опција ќе се додаде на крајот од овој switch, овој начин на
дефанзивно програмирање ќе се покаже оправдан.

Вежба 3-2. Напишете функција еѕсаре (ѕ , t) која прави претворање на зна­


ци от типот на знаците за нова линија и таб во видливи излезни секвенци како
\n и \ t, додека го...___
копира стрингот t во ѕ. Користете swi tch. Напишете, исто
така , функција и за другата насока, која врши претворање на излезните секвен-
ци во вистинските знаци.
3.5 Циклуси - while и for 71

3.5 Цикпуси- while и for

Со циклусите while и for веќе се с р етна вме во претходните глави. Во

while (израз)

наредба

првин, се проверува изразот. Ако вредноста му е различна од нула, се извр­


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

for (изрl; изр2; изрЗ)


наредба

е еквивалентна со

uзpl ;
while (uзр2 )
наредба
изр З ;

освен во случаите каде се појавува continue, кои ќе ги разгледаме во Поглавје 3 . 7.


Граматички , трите компоненти на for циклусот се изрази. Најчесто uзp l
и изрЗ се доделувања или функциски повици , а изр2 е релациски израз. Кој
било од трите делови може да се пропушти да се напише , иако точка-запир ка
знаците треба да останат. Ако недостасува uзpl или изрЗ, не се прави ништо , а
ако недостасува изр2, се зема за точен , па

for ( ;; )

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


прекинат на друг начин , на пример, преку break или return.
Дали ќе се користи for или while за виси од самиот програмер .
На пример , во
/
while ((е = getchar()) ' ' 11 е == '\n' 11 е = ' \t ' )
; /* nресхохнуваае на знаците за nразно место */

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


for се преферира во сл учаи кога има едноставна иницијализација и некаков
72 Контрола на текот Глава 3

инкремент , бидејќи ги чува контролните изрази видливи и блиску едни до дру­


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

for (i=O ; i<n; i++)

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


аналогно на оо циклусот од Фортран или паскаловиот for . Направената ана­
логија, сепак, не е совршена , бидејќи индексната променлива i си ја здржува
вредноста при прекин на циклусот од која било причина. Бидеј ќи компонен­
тите на for се произволни изрази, for циклусите не се ограничени само на

аритметички прогресии . Сепак, лош начин на програмирање е форсирањето


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

Како пообемен пример, ќе ја наведеме функцијата atoi за претворање на


стринг во негов нуме рички еквивалент. Оваа е нешто поопшта од онаа што ја
разгледавме во Глава 2; се справува со опционални водечки п разни места како
и со произволен з нак+ и -. (Глава 4 ја покажува atof , која ја прави истата
конверзија за реалните броеви.)
Структурата на програмата ја рефлектира и формата на влезните податоци:

прескокни ги празните места, ако ги има

земи го знакот, ако го има

преземи го целобројниот дел и претвори го

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


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

#include <ctype. h>

/* atoi : nретвораље на ѕ во цел број , верзија 2 */


int atoi (char ѕ [])
{
inti , n, sign ;

for (i =О ; isзp&ce(s[i]) ; i++) /* пресхохни rи nразните места*/


1
sign = (s[i] == '-' ) ? -1 : 1 ;
i f (s[i] == '+' 11 s[i] == '-') /* nресхохни го знахот (+/-)*/
i++ ;
for (n=O; isdigit(s[i]) ; i++)
n=10*n+ (s[i]- '0');
return sign *n ;
3.5 Циклуси - while и for 73

Стандардната библиотека обезбедува попотполна функција strtol за претво­


рање на стрингови во долги цели броев и; погледни Поглавје 5 во Додаток Б
Предноста од чувањето на централизирана контрола на циклусот е поочиг­
ледна во случаите каде што се јавуваат неколку вгнездени циклуси . Следната
функција што ќе ја разгледаме се нареку ва шел - sort и служи за сортирање
на низа од цели броеви . Основната идеја на овој алгоритам за сортирање , из­
мислен во 1959 година од Д . Л. Шел, е во тоа што во раните фази се спореду­
ваат далечните елементи, а не соседните како nри едноста вните сортирањ а.

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

/* shellsort : ги сортира v[O) . . . v[n-1) во растечки редослед*/


void shellsort(int v[) , int n)
{
int qap , i, ј, temp ;

for (qap = n/2; qap > О ; qap /= 2)


for (i = qap; i < n ; i++)
for (j=i-qap ; ј>=О && v[j)>v[j+qap) ; j-=qap) {
temp = v[j) ;
v[j) = v[j+qap) ;
v[j+qap] = temp ;

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


помеѓу елементите кои се споредуваат , намалувајќи го за фактор два , почн у­
вајќи од n/2 се додека не стане нула . Средишниот циклус им пристапу ва на
елементите. Внатрешниот, пак , ги сnоредува nap по nap елементите кои се на
растојание qap и ги разменува сите што не се во правилен редослед. Бидејќи
qap евентуално ќе се сведе на единица , сите елементи на крај ќе се наредат по
точниот распоред. Забележете како општоста на for овозможува надворешн и­
от циклус да се сведе на истата форма како и другите , иако во случајов таа не е
аритметичка прогресија .
Последен С оператор е заnирката (',' ), која мошне често се уnотребува
во for изразите . Два израза, раздвоени со запирка , се евалуираат од л е во
кон десно , а типот и вредноста на резултатот се типот и вредноста на десн иот

операнд. Така, во една for наредба , можно е да се сместат повеќе изрази во


различните делови, на nример, при процесирање на два индекса паралелно.

Ова е илустрирано во функцијата reverse (ѕ) , која го превртува стрингот ѕ.


74 Контрола на текот Глава 3

#inelude <string.h>

/* reverse: превртува етринг ѕ */


void reverse(ehar ѕ[])
{
int е, i, ј;

for (i = О, ј = strlen(s)-1; i < ј; i++, ј--) {


е = ѕ [i] ;
s[i] = ѕ[ј];

ѕ[ј] =е;

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


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

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


е во конструкциите кои меѓусебно строго се поврзани, како во for циклусот од
функцијата reverse и во макроата каде пресметките од повеќе чекори треба
да претставуваат еден единствен израз . Израз со запирка може згодно да се
искористи за размена на елементите во reverse, при што размената може да

се третира како една операција :

for (i=O, j=strlen(s)-1; i<j; i++ , ј--)


e=s[i], s[i] =ѕ[ј] , ѕ[ј] =е;

Вежба 3-3. Напишете функција expand (ѕ1 , ѕ2) која ги проширува кратките
нотации од облик a-z во стрингот ѕ1 во еквивалентни комплетни листи abe ...
xyz во ѕ2. Допуштени вредности се и мали и големи букви како и цифри. Треба
да може да се третираат и влезни конструкции од тип а- b- е или а- zO - 9 или
-а- z . Нека појавата на- на почеток или на крај се третира буквално.

\
З.б Цикnуси do- while
Како што веќе се запознавме во Глава 1, циклусите while и for го тестираат
условот за прекин на почеток од циклусот. Спротивно на нив, кај третиот ци­
клус во С, do- while, проверката се врши на крајот, после секој премин низ
телото на циклусот ; телото секогаш се извршува барем еднаш .
Синтаксата на do е

do
наредба
while (израз) ;
3.6 Циклуси Do- while 75

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


вистинит, наредба , пак, се извршува, итн. Во моментот кога изразот ќе стане
невистинит, циклусот се прекинува . Со исклучок во смислата на споредбата,
do- while е целосно еквивалентна на наредбата repeat- until од Паскал.
Искуството покажува дека do- while се користи многу помалку отколку for
и while. Сепак, одвреме- на време, таа е потребна, како што ќе видиме во
следната функција i toa , која врши претворање на еден цел број во стринг
(спротивно од atoi) . Постапката е малку покомплицирана отколку што изгле­
да на прв поглед , бидејќи едноставните методи за генерирање на цифрите , ги
генерираат во погрешен редослед. Ние одбравме да го генерираме стрингот
наназад , а потоа да го превртиме.

/* itoa: претвораље на n во знаци во ѕ */


void itoa(int n, char ѕ[])
{
int i, sign;

if ((sign =n) <О) /* зачувуваље на знакот(+/-) */


n = -n; /* направи го n nозитивен */
i = О;
do { /* генерирај ги цифрите во обратен редослед*/
s[i++] =n % 10 + '0'; /* земи ја следната цифра */
} while ((n/= 10) >О); /* избриши ја*/
i f (sign < О)
ѕ [i++] = '-, ;

s[i] = '\0';
reverse(s) ;

Во овој случај примената наdo - while е потребна, или барем погодна, од


причина што во низата ѕ мора да се вметне барем еден знак, дури и ако n е
нула. Исто така , користиме големи загради околу единствената наредба од те­
лото на циклусот, иако немаме потреба од тоа, за да го спречиме погрешниот
заклучок кај читателите дека делот while е почеток на нов while циклус.

Вежба 3-4 . Кај бројна репрезентација со двоен комплимент, нашата верзија


на i toa не се справува со најголемиот негативен број, вредноста на n еднаква
на - (2гоnемина_на_збор- 1 ) • Објаснете зошто. Модифицирајте ја за да ја печати таа
вредност коректно, без разлика на која машина се извршува програмата.

Вежба 3-5. Напишете функција itob (n, ѕ, b) која врши претворање на цел
број n во знаковна репрезентација со основа b, сместена во стрингот ѕ. На при­
мер, i tob (n, ѕ, 16) го форматира ѕ како хексадекадна репрезентација на n.
76 Контрола на текот Глава 3

Вежба 3-6 . Наnишете верзија н а itoa , која наместо два , nрифаќа три ар­
гументи. Третиот аргумент е минималната шири н а н а резултантниот стрин г.
Добиениот резултат мора да биде пополнет со nразни места во случај кога ши­
рината на добие ниот број е nомала од минималната nредвидена ширина на
стрингот. (т . е резултатот треба да биде nорамнет во десно. )

3.7 break и continue

Понекога ш е nогодно да се има можност да се nрекине цикл усот, пред да се


nровери условот на nочетокот или крајот. Наредбата break обезбедува nред­
времен nрекин за for , while и do , како и кај swi tch. П овик на break nредиз­
в икува моментален пре кин на највнатре шн иот цикл ус или swi tch .
Следната функција , trim, ги отстранува п разните места , табовите и знаци­
те за н ова линиј а од крајот на стрингот , користејќи break за nрекин на извр­
шува њето на циклусот, во моментот кога ќе се пронајде најдесниот небла нко,
нетаб и не- " нова линија " знак .

/ * trim: отстранува празни места , табови и знаци за


нова линија од храјот на ѕ */
int trim(char ѕ[])

int n ;

for (n= strlen(s)-1 ; n>= О; n- -)


if (s[n] != ' ' && s[n] != '\t ' && s[n] != '\n ')
break ;
ѕ [n+l] = '\0';
return n ;

strlen ја враќа должината на стрингот . for ц иклусот за п очнува од крајот


и пребарув а наназад додека не дојде до знак кој не е бла нко или таб или нова
линија . Цикл усот се nрекинува nри пронаоѓање н а таков знак или ако n стане
nомал од нула. (односно , кога ќе се измине целиот стрин г) . Треба да се утвр­
ди дека ова е точ н о дури и кога стри нгот е пра зе н или содржи само зна ц и за

nразно место.

Н аредбата continue е nоврзана со break, н о се користи многу nомалку ;


предизвикува nредвремено nр екинува ње на тековното и nреминување кон

следното nовторување за for , while или do циклусите. Кај while и do ова зна­
чи моментална проверка на усл овот н а циклусот ; кај for , ко н тролата се nре­
фрла н а че корот за инкреме нтирање . На редба та c ontinue се користи само во
циклуси , а не кај swi tch. Уnотреба на continue во swi tch во циклус ќе nре­
дизвика n реми н ување кон следното повторување н а циклусот.
3.8 goto и озна ки 77

Како пример ќе го земеме овој фрагмент, кој ги процесира само ненегатив­


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

for (i =О ; i <n ; i++){


if (a[i] < О) /* скокни rи нега~иани~е елемен~и */
continue ;
... /* обрабо~и ги пози~ивни~е */

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

3.8 goto и ознаки

С нуди неограничена злоупотреба на goto наредба и ознаки кон кои се раз­


гранува . Формално, наредбата goto никогаш не е потребна и во пра ктика ре­
чиси секогаш е лесно да се напише код без неа . Ние во оваа книга никаде не
користевме goto.
Сепак, постојат мал број ситуа ции во кои goto може да си најде место.
Најчесто за напуштање на процесирање во некоја длабоко вгнездена структу­
ра, како што е прекин на два или повеќе вгнездени циклуси одеднаш . Во такви
случаи не може да се користи break, бидејќи истата се однесува само на најв­
натрешниот циклус. Поради тоа :

for ( ... )
for ( ... )

if (disaster)
gotoerror ;
}

error :
/* исчис~и го нередо~ */

Ваква организираност е згодна ако кодот за справување со грешката не е три ­


вијален и доколку грешките можат да се појават на неколку места.
Една ознака има иста форма како име на променлива и се проследува со
знак за две точки. Може да биде прикачена на која било наредба во рамките на
функција во која се наоѓа и goto . Делокругот (ан г. ѕсоре) на ознаката е целата
функција.
Како друг пример, ќе го разгледаме проблемот за определување дали две
низ и а и b имаат заеднички елемент. Едно решение би било
78 Контрола на текот Глава 3

for (i=O; i<n ; i++)


for (ј=О ; j<m ; ј++)
i f (a[i] =b[j])
goto found ;
/* немаи најдено заеднички елемент */

found :
/* најдов еден : a[i] -- b[j] * /

Код кој вклучува goto секогаш може да се напише без него 1 иако можеби по
цена на некоја променлива или проверка пове ќе. На пример 1 кодо1 за преб а­
ру вање на низата може да се напише како :

found = О ;
for (i = О ; i < n && !found; i++)
for (ј = 0 ; ј < m && !found; ј++)
if (a[i] == b[j])
found = 1 ;
if (found)
/* најдов еден: a[i-1] b[j-1] * /

е1ѕе
/* ненам најдено 5аеднички елемент */

Со мал број на исклучоци 1 како тие што ги спомнавме овде 1 код кој се потпи­
ра на goto наредби во општ случај е потежок за разбирање и за одржување 1
отколку код кој не содржи goto наредби . Иако не сме догматс ки настроен и во
овој поглед 1 сепак, изгледа дека goto наредбите треб а да се избегнуваат колку
што е можно повеќе, ако не и во целост .
Глава 4: Функции и структура на програма

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


и им овозможуваат на луѓето да надградуваат над она што претходно веќе било
на правен о од други, наместо постоја но почну вање од нула. Соодветните функ­
ци и ги кријат деталите на операцијата од деловите на програмата кои немаат
потреба да бидат запознаени со нив, прочистувајќи ја целината и намалувајќи
ја тешкотијата при правење промени .
С беше дизајниран да ги направи функциите ефикасни и едноставни за корис­
тење; Програмите во С обично се состојат од многу мали функции, а не од неколку
поголеми. Една програма може да е сместена во една или повеќе изворни датотеки.
Истите може да се компајлираат посебно, а се вчитуваат заедно со претходно ком­
пајлираните функции од стандардната библиотека. Сепак. нема да влегуваме подла­
боко во оваа проблематика , бидејќи деталите варираат од систем до систем.
Декларацијата и дефиницијата на функциите во С се места каде што ANSI
стандардот направи најголеми промени. Како што веќе спомнавме во Глава 1,
сега постои можност за декларирање на типовите на аргументите, при декла­

рирање на функцијата. Синтаксата на дефинирање на функциите, исто така, е


изменета, за да си соодветствуваат меѓусебно декларациите и дефинициите .
Тоа на компајлерот му отвора можност за откривање на многу повеќе греш ки
отколку порано. Уште повеќе, ако аргументите се правилно де кл а рирани, со­
одветните конверзии на типовите се изведуваат автоматски.

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


имињата ; конкретно бара постоење само на една дефиниција за секој над во­
решен објект. Иницијализацијата е поопшта : автоматските низи и структури
сега можат да се иницијализираат.
С преtпроцесорот, исто така, е подобрен. Олеснувањата за користење на
новиот претпроцесор вклучуваат покомплетно множество на условни директи­

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


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

4.1 Основи на функции

За почеток да дизај нираме и напишеме програма која ќе ја печати секоја


влезна линија, која во себе содржи некој одреден "облик" или стринг. (Ова е

79
80 Функции и структура на програма Глава 4

специјален случај на наредбата grep во UNIX) . На пример , ќе ги бараме л ин и­


ите коишто во себе содржат "ould"

Ah Love! could you and I wi th Fa te conspire


То grasp this sorry Scheme of Things entire ,
Wouldnot we shatter it to bits -- and then
Re-mould i t nearer to the Heart' ѕ Desire!

ќе доведе до резултат:

Ah Love! could you and I wi th Fa te conspire


Wouldnotwe shatter it tobits - - and then
Re-~ould i t nearer to the Heart' ѕ Desire!

Програмата е разделена на три дела

while (nостои следна линија)


if (линијата го содржи обликот)
испечатија

Иако сето ова е возможно да <.е изведе и во рамките на функциј ата main ,
подобар на чин е да се направат функции за секој дел од nрограмата. Полесно е
да се работи со три мали целини отколку со една голема, од причина што незн а­
чајните детали можат да се скријат во функциите , а можноста од несака н и инте­
ракции е минимизирана. А деловите може да се употребат и во други програми.
"Додека nостои следна линија ", е getline , функција која ја напишавме во
Глава 1 , а " испечати ја" , всушност е printf која некој друг веќе ја обезбедил
за нас. Тоа значи дека единствено што мораме да наnишеме е рутина која ќе
донесува одлука дали една линиј а t:одржи појавување на обликот или не.
Тој пробл ем можеме да го решиме со функцијата strindex (ѕ , t) која ја
враќа п оз ицијата или индексот во ни зата ѕ каде за n очнува ни затаt , а - 1 ако
ѕ не ја содржи t. Бидејќи низите во С започнуваат со индекс нул а, сл едува
дека индексите ќе бидат или нула или позитивни , па вредноста -1 , може да
се користи како сигнал за грешка. Во случај подоцна да ни затреба некоја по­
софистицирана сnо редба на облици , измените ќе бидат локализира ни само
на функцијата strindex , додека д ругите делов и на програмата ќе оста нат не­
допрени ( ста ндардн ата библиотека обезбедува функција strstr која е слична
на strindex , но враќа пока жувач нам есто индекс).
После толкаво дизајнирање, доп олн увањето на програмските детали е ед ­
ноставно. Овде е дадена целата програма, за да видите како сите делови се
склопени заедно. Засега , обликот кој ќе се пребарува е константен стринг , што
не е најоnшт меха низам. ќе се навратиме за момент кон расправа околу и ни­
цијализација на низа од знаци , а в о Глава ѕ ќе покажеме како обликот може да
се нап рави парамета р кој ќе добие вредност за време на извршување на про-
401 Основи на функци и 81

-рамата о Исто така , тука е малку поинаква верзијата на функцијата qetline ;


чожеби б и било поучно да ја споредите со верзијата од Глава 1 о

tinclude <stdio oh>


tdefine МAXLINE 1000 /* ма~симапна допжина на впеѕна nинија */

int getline(char line[], int max)


int strindex(char source[] , char searchfor[]) ;

char pattern[] = " ould"; /* обпи~от ~ој се бара*/

/* rи наоѓа сите пииии во ~ои се наоѓа обликот */


main ()
{
char line[МAXLINE] ;
int found = О ;

while (qetline(line , МAXLINE) > О)


if (strindex(line, pattern) >= О) {
printf( "%s ", line) ;
found++;

return found ;

/* qetline: ѕеми пинија во ѕ , врати ја допжината */


int getline(char ѕ[], int lim)

int е, i ;

i = О;
while (--lim >О && (c=qetchar()) '= EOF && е ! = '\n ' )
s[i++] = е ;
if (е == '\n ' )
s[i++] = е ;
s[i] = '\0' ;
return i ;
82 Функции и структура на програма Глава 4

/* strindex: враха позиција на t во ѕ, -1 ако го нема */


int strindex(char ѕ[] , char t[])

int i , ј , k;

for (i = О ; s[i] != '\0 '; i++) {


for (j=i , k=O ; t[k] != ' \0' && ѕ [j]=t[k]; ј++ , k++)

if (k >о && t[k] == '\ 0 ')


return i;

return -1;

Секоја функциска дефиниција е од облик

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


{
декл арации и искази

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

dummy() {}

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


како место за чување, додека се развива програмата. Во недостаток на повра­
те н-тип , по автоматизам се претпоставува int .
Програма претставува множество од дефиниции на променливи и фу нкции.
Комуникацијата помеѓу функциите се одвива преку аргументи и вредности,
кои ги враќаат функциите, како и преку надворешни променливи. Функциите
може да се појават во произволен редослед во изворната датотека , а изворна­
та програма може да се расчлени на повеќе датотеки , се дури една функција е
во една датотека.

Наредбата return е механизам за враќање на вредност од повиканата функ­


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

return израз;

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

Повикувачката функција е слободна во интерпретацијата на вратената вред­


ност. Уште повеќе , после return наредба , израз не е задолжителен; во таков
случај, функцијата не враќа никаква вредност кон повикувачот. Контролата го
4.2 Функции кои враќаат нецелобројни вредности 83

враќа текот кај повикувачот без ника ква вредност кога извршувањето ќе "дој­
де до работ" на функцијата со доаѓање до завршната голема затворена загра­
да. Случајот во кој една функција при повикување од едно место враќа некоја
вредност, а од друго не, иако ненелегален, веројатно, укажува на постоење на
проблем. Секако, ако функцијата не успее да врати вредност , нејзината "вред­
ност" сигурно е ѓубре.
Програмата за пребарување на облик враќа статус од функцијата main ,
бројот на пронајдените поклопувања . Таа вредност може да се користи од око­
лината од која била повикана програмата .
Механизмот на компајлирање и вчитување на една С про грама , која е
сместена во повеќе изворни датотеки, варира од систем до систем. Кај сис­
темот UNIX, на пример , наредбата се спомната порано, ја извршува таа рабо­
та. Претпоставете дека три функции се сместени во три датотеки , со имиња
main. е , getline . е и ѕ trindex. е . Тогаш наредбата

се main. е getline. е strindex . е

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


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

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


може сама повторно да се искомпајлира , па резултатот да се вчита во веќе пре­
тходно креираните објектни датотеки со наредбата се

се main. е getline . о strindex. о

Наредбата се ги користи конвенциите за именување". е и " . о" заради разли­


кување на изворните од објектните датотеки.

Вежба 4-1. Напишете функција strindex (ѕ, t) КОЈа Ја враќа позицијата на


последното (најдесното) појавување на t во ѕ , а -1 ако го нема.

4.2 Функции кои враќаат нецеnобројни вредности

Досега функциите кои ги креиравме во на шите примери или не враќаа вред­


ност (беа void) или враќаа int. Што се случува ако функцијата мора да врати не­
кој друг тип? Многу математички функции, на пример, sqrt, sin и соѕ враќаат
double ; други специјализирани функции враќаат други типови. Заради илу­
страција како треба да се постапува со такви функции, ќе ја напишеме функ­
цијатаatof (ѕ) , која ја претвора низата ѕ во нејзи н еквивалент на реален број
со двојна прецизност. Функцијата atof е проширена варија нта од функцијата
atoi која беше обработена во Глава 2 и з. Таа се справува со можниот предзнак
и децимална точка, со постоење или непостоење на целоброен или децима­
лен дел . Варијантата која ја приложуваме не е кој знае колку добра рутина за
84 Функции и структура на програма Гла ва 4

конверзија на влезот, бидеј ќи тогаш би барала многу повеќе простор од тој кој
планиравме да го отстапиме. Стандардната библиотека содржи функција atof
декларирана во заглавјето <ma th. h>.
Најнапред , самата atof функција мора да декларира тип на вредност кој ќе
го враќа , бидејќи не станува збор за цел број. Името на типот му претходи на
името на функцијата

iinclude <ctype. h>


/* atof : хоиаер~ира с~инг ѕ ао број од ~иn double * /
doub1e atof(char ѕ[])
(
doub1e val , power ;
int i , sign ;

for (i =О ; isspace(s[i]) ; i++) / * :rи nресхохнува празните места* /

sign = (s[i] == ' - ' ) ? -1 : 1 ;


if (s[i] = ' + ' 11 s[i] == ' - ' )
i++ ;
for (va1 = 0 . 0; isdigit(s[i]) ; i++)
va1 = 10.0 * va1 + (s[i] - ' 0 ' ) ;
if (ѕ [i] == ' .' )
i++ ;
for (power = 1 .0; isdigit(s[i]) ; i++)
va1 = 10.0 * va1 + (s[i] - ' 0 ');
power *= 10 ;

return sign * va1 1 power ;

iinc1ude <stdio.h>

idefine МAXLINE 100

/ * едиос~авен халхула~ор */
main ()
(
doub1e sum, atof(char []) ;
char 1ine[МAXLINE] ;
int get1ine(char 1ine[] , int max) ;

sum = О ;
while (getline(line, МAXLINE) > О)
printf( "\t %g\n", sum += atof(line)) ;
return О ;
4.2 Функции кои враќаат нецелобројни вредности 85

Второ, исто така, важно, повикувачк ата рутина мо ра да знае дека atof
враќа вредност која не е in t. Еден од начините да се обезбеди тоа , е да се
направи јасна декларација на atof фун к цијата во повикувачката рути на .
Декларацијата е покажана со пример во оваа едноставна програма - калкула ­
тор (Кој а одвај ја бива за средување на банкарската сметка). Таа вчитува еден
број по линија, кому може да му претходи предзнак, и ги собира броевите ,
печатејќи ја тековната сума после секој влез
Декларацијата

double sum, atof (char []) ;

вели дека сумата е променлива од тип double и дека atof е функција која на
влез зема еден аргумент од тип char [] , а враќа double . Функцијата а tof мора
да биде декларирана и дефинирана конзистентно . Ако atof и неј зи ниот повик
во main се неускладени по прашање на типот во истата изворна датотек а , ком­
пајлерот ќе ја открие грешката . Но, ако (што е поверојатно) , atof се преведе
посебно, таа неускладеност нема да биде откриена, п а а tof ќе врати double ,
која функцијата main ќе ја третира како int , а ние ќе добиеме резултати кои се
бесмислени.
Ако се земе предвид она што го кажавме дека д екла рац иите мора да од го­
вараат на дефинициите, ова може да ви изгледа из ненадувач ки. Причината да
може да дојде до неускладеност е во тоа што ако нема функциски прототип,
функцијата имплицитно се декларира со своето прво појавување во изразот,
како што е

swn += atof (line)

Ако недекларирано име се појави во израз , а после него следи лева заграда,
тогаш се подразбира дека станува збор за име на функција, за функцијата се
претпоставува дека враќа int, додека за неј зи ните аргументи не се претпо­
ставува ништо. Уште повеќе, ако декла рацијата на функцијата нема аргументи
како во

double а tof () ;

тоа подразбира ништо да се претпоставува во врска со аргументите на функ­


цијата atof; сите проверки на nараметрите се исклучени . Ова специјално зна ­
чење на nразна листа на аргументи е со намера да дозволи компајлирање на
постарите С програми на новите компајлери. И покрај тоа не е nрепорачлива
нејзина употреба во новите програми. Ако функцијата зема аргументи, тогаш
декларирајте ги, во спротивно користете void .
Сега, кога функцијата atof е nравилно декларирана, можеме да ја напише­
ме atoi (која претвора стринг во int) преку atof :
86 Функции и структура на програма Глава 4

1* atoi: претвора стринг ѕ во цел број користејќи ја atof */


int atoi (char ѕ [])
{
double atof (char ѕ []) ;

return (int) atof (ѕ) ;

Обрнете внимание на структурата на декларациите и наредбата return.


Вредноста на изразот во

return израз;

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


Поради тоа, ако вредноста на функцијата atof , која е од тип double , се појави
во наредбата return , автоматски се претвора во int, бидејќи функцијата atoi
враќа int. Оваа операција податокот може да го направи неупотреблив , на
што голем број компајлери ќе ви дадат предупредување. Претопувањето по­
кажува дека операцијата е намерна и го отстранува можното преду предува ње
(анг. wa rпing).

Вежба 4- 2. Проширете ја atof , така што ќе може да се справи и со научна


нотација од облик 123. 45е-б, во која бројот со подвижна точка е проследен со
е или Е и можен експонент со предзнак .

4.3 Надворешни променливи

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


се или променливи или функции . Придавката "надворешен" се користи во
контраст на придавката "внатрешен", која ги опишува аргументите и про­
менливите дефинирани внатре во функциите. Надворешните променливи се
дефинирани надвор од функциите , со цел да се користат од повеќе функции.
Функциите секогаш се дефинираат како надворешни , бидејќи е не дозволува
функциите да се дефинираат во рамки на други функции. Во општ случај надво­
решните променливи и функции имаат особина сите референци кон нив кои се
со исто име, дури и од функциите што се компајлирани посебно, да се рефе­
ренци кон исто нешто. (етандардот ова го нарекува надворешно поврзување .)
Во тој поглед , надворешните променливи се аналогни на соммоN блоковите во
Фортран или на променливи од надворешниот блок во Паскал. Подоцна ќе ви­
диме како се дефинираат надворешни променливи и функции кои се видливи
само во рамките на една изворна датотека .
Бидејќи надворешните променливи глобално се достапни, тие обезбедува­
ат алтернатива за податочното комуницирање помеѓу функциите, кое во нор-
4.3 Надворешни променливи 87

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


ги враќаат. Секоја функција може да пристап и до надворешна променлива со
повик кон неа преку име, под услов тоа име претходно да било декларирано.
Доколку има потреба голем број на променливи да се делат од различни
функции, надворешните променливи се посоодветни и поефикасни, во спо­
редба со долгите листи со аргументи. Сепак, како што спомнавме во Глава
1, ваквото размислување не треба да биде земено здраво за готово, бидејќи
може да има негативен ефект врз структурата на програмата , во која ќе има
премногу податочни врски помеѓу поедините функции.
Надворешните променливи се корисни поради нивниот поголем делокруг
и време на живот. Автоматските променливи се внатрешни во однос на функ­
цијата; нивниот живот за почнува во моментот на влез во функцијата и завршу­
ва при излез од неа . Надворешните променливи, од друга страна, се трајни,
п а ги задржуваат вредностите од еден до друг функциски повик. Поради тоа,
ако две функции треба да делат исти податоци , а не се повикуваат меѓусебно,
често најзгодно е заедничките податоци да се чуваат во надворешни променли­
ви, а не да се предаваат преку функциски аргументи.
Да ја проучиме оваа проблематика со разгледување на еден поголем при­
мер . Задача ни е да напишеме програма за калкулатор кој ги користи опера­
торите +, -, * и 1. Наместо нормална (инфиксна) , ќе користиме инверзна
полска нотација, бидејќи нејзината имплементација е поедноставна и полес­
на. (Инверзна полска нотација се користи од некои џебни калкулатори и во
јазици како Фортран и Постскрипт. )
Кај инверзната полска нотација, секој оператор ги следи неговите операн­
ди; нормален израз како

(1-2) * (4+5)

се внесува во облик

12-45+*

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


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

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


stack); кога ќе се вчита оператор, од купот се одземаат соодветен број операн­
ди и резултатот, пак, се враќа во купот. Во погорниот пример, 1 и 2 се ставаат
во купот, потоа се заменуваат со нивната разлика -1 . Понатаму, во купот се
ставаат 4 и 5, за да се заменат со нивниот збир 9 после операцијата собирање .
Потоа, двата резултата -1 и 9 во купот се заменуваат со резултатот од нивното
множење, кој е -9. На крај се зема вредноста која е на врв на купот и се печати
кога ќе се детектира крајот на влезната линија. Така , структурата на програма­
та е определена со цик11ус кој ги изведува соодветните операции врз операнди­
те и операторите, во редоследот по кој што се појавуваат .
88 Функции и структура на n ро грама Гла ва 4

while ( следниот оператор или операнд не е индикатор за крај_на_датотека)


i f ( број)
стави на купот

els e if ( опера тор)

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

else if ( нов ред )


земи од кипот и печати
else
грешка

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


плементираме механизам за детекција и справување со грешки , кодот ќе ста не
долг , na така, подобро е да ги сместиме во nосебни функции, отколку да го
nовторувам е нивниот код низ целата nрограма. Исто така, имаме nотреба од
посебна функција која од влез ќе го фаќа следниот оnератор или оnеранд.
Гл а вната одлу к а околу дизајнот, што досега не ја сnомн а вме , е n ра шањето
каде ќе се наоѓа купот и кои рутини ќе му nристаn у ваат директно . Една м ож­
ност е да го чуваме во функцијата main, па да го предаваме него и позиција­
та на неговиот врв , како аргум енти на функциите кои маниnулира ат со него .
Меѓутоа , 1114in нема потреба да има информација за nроменливите кои го кон­
тролираат купот ; единствени оnерации кои се извршуваат во main се земање и
дода вање , од и во куnот. Така , одлучивме да го чуваме купот и информацијата
асоциран а со него во посебни надворешни nр оменливи до ко и nристап имаат
функци ите рор и push , но н е и самата main .
Преслик ува ње то на о ваа на ц рт -шем а во код е едноста вн о . Докол ку р а з­
мислувам е во н асо к а да ја см естиме целата nрограма во една датотека , таа б и
изгл едал а сличн о н а о ва:

#include -oвu,и
#define -oв u,и

Функциски декларации за main

1114in () { . . . )

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

voidpush ( double f) { . .. }
double рор (void) { . . . }
intgetop(chars[] ) { . . . }

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


4.3 Надворешни променливи 89

#include <stdio.h>
#include <stdlib.h> /* for atof() */

#define МАХОР 100 /* НАХсииа.лна rо.nемина на оnеранд или оnератор * 1


#define NUМВER '0' /* сиrнал деха е nронајден број */

int getop(char []);


void push(double);
double pop(void);

/* инверзен nолсхи халхулатор*/


main ()
{
int type;
double ор2;
char ѕ[МАХОР];

while ((type = getop(s)) != EOF) {


switch (type) {
саѕе NUМВER:

push(atof(s));
break;
саѕе ' +' :
push(pop() + рор());
break;
саѕе '*':

push(pop() * рор());
break;
саѕе '-':
ор2 = рор () ;
push(pop() - ор2) ;
break ;
саѕе '/':
ор2 = рор () ;
i f ( ор2 ! =
О . О)
push(pop() 1 ор2);
else
printf("error: zero divisor\n" );
break;
саѕе '\n ':
printf("\t%. 8g\n", рор()) ;
break;
default:
printf("error: unknown coпunand %s\n", ѕ) ;
break;

return О ;
90 Функции и структура на програма Глава 4

Подоцна ќе ја разгледаме можноста програмата да ја разделиме на две и по­


веќе одделни датотеки. Функцијата ID4in содржи циклус во која има еден голем
swi tch кој одлучува за типот на операторите и операндите; далеку потипична
употреба на swi tch 1 отколку онаа што ја прикажавме во Поглавје з. 4.
Бидејќи +и * се комутативни оператори , редоследот по кој се земаат операн­
дите не е важен 1 но за - и 1 треба да се прави разлика помеѓу лев и десен опе­
ранд. Во

push(pop() - рор()); /*ПОГРЕШНО*/

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


рор . Со цел да осигураме правилен редослед на извршување, неопходно е да
се земе првата вредност од купот и да се зачува во некоја помошна променли­
ва, како што направивме во main.

#define МAXVAL 100 /* махсииапна дпабочина ма хупот val */

int ѕр = О; /* спедната слободна nозиција во хуnот */


double val[МAXVAL] ; /* хупот со вредности*/

/* push : го става f на врв на xyn */


void push(double f)
{
if (ѕр < МAXVAL)
val[sp++] f; =
else
printf("error: stack full, can 1 t push %g\n" , f);

/* рор : зема вредност од врв на купот */


double pop(void)
{
if (ѕр > О)
return val[--sp] ;
else {
printf( " error: stack empty\n" ) ;
return 0.0;

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


било функција . Па така, куnот и неговиот индекс, кои треба да бидат заеднички
за push и рор, треба да се дефинираат надвор од овие функции. Но, самата
main не се обраќа директно кон купот и неговата позиција- нивната репрезен­
тација може да биде скриена.
Да се свртиме кон имплементацијата на getop, т . е . функцијата која го фаќа
следниот операнд или оператор. Задачата е лесна. Прескокни ги бланко-зна-
4.3 Надворешнипроменливи 91

ците и табовите. Ако следниот знак не е декадна или хексадекадна цифра ,


врати ја. Во спротивно, вчитај го стрингот од цифри {кој можеби вклучува и
децимална точка) , и врати NUМВER, сигнал дека сигурно бил вчитан број.

#inelude <etype . h>

int geteh{void) ;
void ungeteh{int);

/* getop: земи го следниот оператор или нуиерички операнд */


int getop{ehar ѕ[])

int i, е;

while { {ѕ [О] е = geteh {) ) - ' ' 11 е '\t l )

s[l] = '\0' ;
if {!isdigit{e) && е != '. ' )
return е ; /* не е број */
i = О;
if {isdigit(e)) /* вчитај го целобројниот дел*/
while {isdigit{s[++i] =е= geteh{)))

if (е== ' .1 ) /* вчитај го децималниот дел* /


while {isdigit{s[++i] =е= geteh{)))

s[i] = '\0';
if {е != EOF)
ungeteh(e);
return NUМВER;

Што претставуваат geteh и ungeteh? Често се случува програмата да не


може да определи дали има прочитано доволно податоци , се додека не про­
чита премногу од нив. Еден пример е вчитувањето на знаците од кои се состои
бројот: се додека не се дојде до знак кој не е цифра , не знаеме дека бројот е
целосен. Но кога ќе дојде до тоа, програмата веќе има вчитано еден знак по­
веќе , знак за кој не е подготвена .
Проблемот би се решил ако беше возможно прочитаниот знак да се "отпро­
чита " . Тогаш , секогаш кога програмата ќе прочита еден знак повеќе 1 би го вра­
тила назад на стандарден влез 1 п а остатокот од кодот ќе се однесува како знакот
никогаш да не бил прочитан. За среќа 1 лесно е да се симулира "одземањето" на
знакот 1 со пишување на две кооперативни функции. geteh го доставува след­
ниот знак кој треба да се разгледува ; ungeteh ги памти знаците вратени на влез,
така што следните повици до getch ќе ги вратат нив пред да се чита нов влез .
Нивното функционирање е едноставно. ungeteh ги става врате ните знаци
во споделен бафер - низа од знаци. geteh чита од тој бафер ако во него има
92 Функции и структура на програма Глава 4

нешто, а ја повикува getchar ако е баферот празен. Исто така, треба да има
индексна променлива која ќе ја чува позицијата на тековниот знак во баферот.
Бидејќи пристапот до баферот и индексот се заеднички за getch и ungetch
и бидејќи треба да ги задржуваат своите вредности и помеѓу функциските по­
вици, тие треба да се декларираат како надворешни за двете рутини . Та ка,
getch , ungeteh и нивните заеднички променливи може да се напишат како:

#define BUFSIZE 100

char buf[BUFSIZE]; /* бафер за ungetch */


int bufp = О; /* следна слободна nозиција во buf */

int getch(void) /* земи (можеби вратен) знак*/

return (bufp > О) ? buf[--bufp] : getehar() ;

void ungetch(int е) /* врати ro знакот назад на влез */

if (bufp >= BUFSIZE)


printf( " ungeteh : too many characters\n" );
else
buf[bufp++] = е ;

Стандардната библиотека содржи функција ungetc која обезбедува враќање на


еден знак; ќе ја разгледуваме во Глава 7. За вратените знаци користевме низа,
а не еден знак, за да илустрираме еден поопшт пристап.

Вежба 4-3. Со помош на дадената рамка, лесно е калкулаторот да се проши­


ри. Додајте модул (%) оператор и можност за работа со негативни броеви .

Вежба 4-4. Додајте команди за печатење на елементот од врват на купот, без


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

Вежба 4-5 . Додајте пристап до функциите од библиотеката како sin , ехр , и


pow. Погледнете ја <math . h> во Додаток Б , Дел 4.

Вежба 4-6. Додајте команди за справување со променливи. (Лесно е да се


понудат 26 променливи со имиња од еден знак.) Додајте променлива за вред­
носта која била печатена последна .

Вежба 4-7 . Напишете рутина ungets (ѕ) која назад на влез ќе вра ќа цел
стринг. Треба ли ungets да има информација за buf и bufp , или, единствено,
треба да ја користи ungetch?
4.4 Правила на делокругот (анг. ѕсоре) 93

Вежба 4-8. Претпоставете дека никога ш н ема да има повеќе од еден знак за
враќа ње на влез. Модифицирајте ги getch и ungetch соодветн о.

Вежба 4-9. Нашите getch и ungetc h не се с праву ваат правилно со враќање


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

Вежба 4-10. Една алтернативна организација користи getline за читање на


цел а линија од влез ; тоа ги прави getch и ungetch непотребн и. Преуредете
го калкулаторот та ка што ќе го користи овој п ристап .

4.4 Правила на делокругот (анг. ѕсоре}

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


С програма, не мора да се компајлираат истовремено; извор ни от текст на про ­
грамата може да се чува во неколку датотеки, а претходно компајлираните ру­
тини може да се вчитуваат од библиотеки .
Помеѓу прашањата кои се од интерес се:

- Како да се пиш уваат декларациите , така што променливите ќе се деклари ­


раат правилно за време на компајлирање?
- Како да се организираат деклараци ите, за при вчиту вање н а програмата ,
нејзините делови правилно да се поврзат .
- Како да се организираат декларациите, за да постои само една копија од
НИВ?

-Како се и ницијализи раат надворешн ите променливи?

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


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

Делокругот на едно име, е дел од програмата во чии рамки тоа име може
да се користи. За автоматска променлива декларирана на почетокот на фун к­
цијата, делокругот е функцијата во која нејзиното име било декларирано.
Локалните променливи со исти им и ња во различни функции се неповрзан и.
Истото важи и за пара метрите на функциите, кои имаат природа на локални
променливи.

Делокру гот на една надворешна променл ива или функција за почнува од мо­
ментот во кој е декларирана, па се до крајот на датотеката која се компајлира.
На пример, во main , ѕр , val , push и рор се дефинираат во една датотека, по
наведени от редослед, т . е .
94 Функции и структура на програма Глава 4

main () { ...

int ѕр= О;
double val [МAXVAL] ;

void push (double f) {

double рор (void) { ... }

тогаш променливите ѕр и val може да се користат во push и рор, едноставно,

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


Но овие имиња не се видливи во main, како што не се видливи ниту рор и push.
Од друга страна, ако е потребен пристап кон една надворешна променли­
ва, пред таа да биде дефинирана, или, пак, е дефинирана во различна изворна
датотека од онаа во која треба се користи, се наложува употреба на деклара­
цијата extern.
Важно е да се направи разлика помеѓу декларацијата на надворешна про­
менлива и нејзината дефиниција. Декларацијата ги најавува својствата на про­
менливата (главно, нејзиниот тип) ; дефиницијата резервира мемориски прос­
тор. Ако следниве линии

int ѕр;
double val [МAXVAL] ;

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


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

extern int ѕр;


extern double val [] ;

за остатокот од изворната датотека декларираат дека ѕр е од тип int , val е


низа од тип double (чија големина е определена на друго место) , но тие ниту
ги креираат променливите, ниту резервираат меморија за нив.
Во сите датотеки кои ја сочинуваат програмата, мора да има само една де­
финиција за надворешните променливи; другите датотеки може да содржат
extern декларација за да пристапат до нив. (Може да има extern декларации
и во датотеката која ја содржи дефиницијата) . Големината на низата мора да
биде наведена во нејзината дефиниција, но е опционална при extern декла­
рирање.

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


ниција.
Иако не е својствено организацијата на оваа програма , функциите push и
рор може да се дефинираат во една датотека, а променливите val и ѕр да се
4.4 Правила на делокругот ( анг. ѕсоре) 95

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


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

во датотека 1:

extern int ѕр;


extern double val [] ;

voidpush (double f) {

double рор (void) { ... }

во датотека2:

intsp= О;
double val [МAXVAL] ;

Бидејќи extern декларациите во датотекаl лежат понапред и надвор од функ­


циските дефиниции , тие се однесуваат на си те функции ; едно множество на
деклара ции е доволно за сите од датотекаl . Истата оваа организација би била
потребна и ако дефиницијата на ѕр и на val следеше по нивната употреба во
една датотека .

4.5 3аглавишни датотеки

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


датотеки, како што би било во случај ако секоја од компонентите е значително
поголема. main функцијата би се нашла во една датотека, која ќе ја нарече­
ме main . е; push , рор и нивните променливи б и отишле во втора датотека ,
staek. е ; gettop во трета , gettop . е . На крај , geteh и ungeteh би ги смес­
тиле во geteh . е ; Нив ги одделуваме од останатите бидејќи тие б и дошле од
одделно компајлирана библиотека во една реална програма .
96 Функции и структура на програма Глава 4

calc.h:
#define NUМВER 'О '
voidpush (double) ;
double рор (void) ;
intgetop(char []) ;
int getch (void) ;
void ungetch (int) ;

main.c: getop . c: stack . c:


#include <stdio. h> #include <stdio . h> #include <stdio. h>
#include <stddlib. h> #include<ctype . h> #include " calc . h "
#include "calc.h" #include " calc.h" #define МAXVAL 100
#defineМAXТOP 100 getop() { intsp=O;
main() { double val [МAXVAL] ;
voidpush(double) {
getch . c:
#include<stdio.h> double рор (void) {
#define BUFSIZE 100
char buf [BUFSIZE] ;
intbufp=O ;
int getch (void) {
{

void ungetch (int) {

Има уште едно нешто за кое треба да се грижиме- дефинициите и деклараци­


ите кои се споделуваат помеѓу датотеките. Сакаме да го централизираме тоа
колку што е можно повеќе, така што ќе имаме само една копија за средување
и одржување, како што напредува програмата. Во согласност со ова , ќе го
сместиме тој заеднички материјал во заглавишна датотека (заглавје) calc. h ,
која ќе биде вклучена во зависност од потребата . (Ставката #include е опиша­
на во Глава 4 . 11) . Резултантната програма во овој случај изгледа вака:
Постои рамнотежа помеѓу желбата секоја датотека да има пристап само до
информацијата која и е потребна и практичната реалност, каде што е потешко
одржувањето на повеќе заглавја. Се до некоја средна големина на п рограма­
та , веројатно, најдобро е да се има едно заглавје, кое што содржи се што треба
да биде споделено помеѓу било кои два дела од програмата; тоа е одлуката
која треба да се направи тука. За многу поголема програма , би била потребна
поголема организација и повеќе заглавја.
4.6 Статички променливи 97

4.6 Статички променливи

Променливите ѕр и val во staek . е , и buf и bufp во geteh. е, се за приват­


на употреба од функциите во нивните соодветни изворни датотеки и не е пред­
видено да бидат пристапени од што било друго . Декларацијата statie, ставе ­
на кај надворешна променлива или функција , го ограничува делокругот на тој
објект до остатокот на изворната датотека која се компајлира. Значи, надво ­
решна statie обезбедува начин да се скријат имињата како што се buf и bufp
во комбинацијата geteh-ungeteh, кои мора да се надворешни за да може да
бидат споделувани, но и невидливи за корисниците на geteh и ungeteh.

Статичкото зачувување во меморија се специфицира со додавање префикс


statie на нормалната декларација . Ако само двете функции и двете промен­
л иви се компајлираат во една датотека , ка ко овде :

statie ehar buf [BUFFSIZE] ;


statie int bufp =О ;

int geteh (void) { ... }

void ungeteh (int е) {... }

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


bufp и тие променливи нема да влезат во конфликт со истоименуваните про­
менливи во другите датотеки од истата програма. На ист начин , променливите
кои push и рор ги користат за манипулација со стекот, може да бидат скриени
со декларирање на ѕр и val за статички.
Надворешната statie декларација, најчесто, се користи кај променливите ,
но, исто така, може да се употреби и кај функциите. Нормално , имињата на
функциите се глобални , видливи за кој било дел од целата програма . Во случај
ако функцијата се декларира како статичка , нејзиното име е невидливо надвор
од датотеката во која што е декларирана .
Статичкото декларирање може да се изврши и врз внатрешни променливи.
Внатрешните статички променливи се локални за одредена функција , како што
се и автоматските променливи, но за разлика од нив тие постојано живеат и не
се креираат и уништуваат со секое повторно активирање на функцијата . Тоа
значи дека внатрешна статичка променлива обезбедува приватен, постојан
мемориски простор во рамките на една функција.

Вежба 4-11. Модифицирајте ја getop, за да не мора истата да ја користи


ungeteh. Помош: Употребувајте внатрешна statie променлива .
98 Функции и структура на програма Глава 4

4.7 Регистарски променливи

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


биде интензивно користена. Идејата е регистарските променливи да се сме­
стат во машинските регистри , што би резултирало со помал и и побрзи програ­
ми . Сепак , компајлерите може да го занемарат советот.
Регистарс ката де кларацијата изгледа вака :

register int х ;
register char е;

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


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

f (register unsignedm , register long n)


{
register int i;

Во практика постојат ограничувања на регистарските променливи , кои за­


висат од реалноста на употребуваниот хардвер . Само неколку променливи од
секоја функциј а може да се чуваат во регистрите , и само на неколку одредени
типови тоа им е дозволено. Вишокот регистарски декларации се безопасни,
од причина што зборот register се игнорира за одвишни и недозволени де­
кларации. Исто , не е возможно да се земе адреса на регистарска променлива
(ќе биде аргументирано во Глава 5) , без оглед на тоа дали променливата, во­
општо, е сместена во регистар. Кон кретните ограничувања за бројот и типот на
регистарските променливи варира од машина до машина .

4.8 Бnоковска структура

С не е блоковско - структуриран јазик како Паскал и нему сличните, би­


дејќи функциите не може да бидат дефинирани во рамките на другите функ­
ции. Од друга страна, променливите може да се дефинираат на блоковско -
структуриран начин во рамките на функцијата. Декларацијата на променливи­
те (вклучително и иницијализацијата) може да следи после голема отворена
заграда која означува почеток на кој било сложен израз, а не само почеток на
функција . Променливите декларирани на овој начин ги покриваат идентично
именуваните променливи од надворешните блокови и живеат се до соодветна­
та голема затворена заграда. На пример во
4.8 Блоковска структура 99

if ( n > О ) {
int i; /*декларација на ново i* /

for (i = О; i < n; i++)

делокругот за променливата i е делот од if во кој се влегува кога е исполнет


условот; ова i нема никаква врска со кое било i надвор од блокот. Една авто­
матска променлива декларирана и иницијализирана во блок, се иницијализи ­
ра одново со секое ново влегување во блокот. static променлива се иницијали­
зира само при првото влегување во блокот .
Автоматските променливи, вклучително и формалните параметри, исто
така, ги кријат истоименуваните надворешни променливи и функции. На при­
мер, во декларациите

intx;
inty;

f(doublex)
{
doubley;

во рамките на функцијата f, појавувањата на параметарот х се однесува на


параметарот деклариран како double, додека надвор од f, се однесува на
надворешниот int. Истото важи и за променливата у.
Без разлика на стилот, најдобро е да се одбегнуваат имиња на променливи
кои кријат имиња од надворешен делокруг; можноста за конфузија и грешка е
премногу голема .

4.9 Иницијапизација

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


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

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


променливи се гарантира дека се иницијализирани на нула; автоматските и ре­
гистарските променливи имаат недефинирани (т. е. ѓубре) иницијални вред­
ности .

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


додавање еднакво и некој израз после името на променливата:
100 Функции и структура на програма Глава 4

intx=1;
char squote = '\' ' ;
long day = lOOOL * бОL * бОL *24L ; /*милисекунди /ден*/

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


та; иницијализацијата се врши еднаш, концептуално пред да за почне и з вршу­
вањето на програмата. Кај автоматските и регистарските променливи, тоа се
прави повторно со секој повик на функција или влез во некој блок .
За автоматските и регистарс ките променливи , иницијализаторот не е огра­
ничен само на константни вредности; тоа може да биде израз кој вклучува пре­
тходно дефинирани вредности , дури и функциски повици . На пример, ини­
цијализациите на програмата за бинарно пребарување од Поглавјето 3 . 3 може
да бидат напишани како

intbinsearch(intx , intv[] , intn)


{
intlow=O;
int hight =n - 1 ;
intmid;

наместо

int low , high, mid;

low= О ;
high= n - 1 ;

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


скратување на изразите за доделување . Која форма ќе се употребува е пра ­
шање на стил . Ние, генерално, користевме експлицитни доделувања, бидејќи
иницијализаторите во декларирањето потешко се уочуваат и се наоѓаат пода­
леку од местата на употреба.
Една низа може да се иницијализира ако после декларацијата се додаде лис­
та од иницијални вредности затворени во загради и одделени меѓусебно со
запирки . На пример , за иницијализ а ција на ни з ата days с о бројот на де нови
во секој месец :

int d4ys [] = { 31, 28, 31, 30, 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31} ;

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


истата со броење на иницијалните вредности, кои се 12 во случајов.
Ако бројот на иницијални вредности за една низа е помал од наведената
4.10 Реку рзија 101

должина , ненаведените вредности ќе бидат поста вени на н ула во слу чај на


надворешни, статички и автоматски п роменливи . Преголем број на иниција­
лизатори доведува до грешка. Не постои начи н да се специфицира повтору­
вање на некој иницијализатор , ниту, пак, да се иниција лизира елемент од сре­
дината на низата без притоа да се наведат неговите претходници .
Низите од знаци се специјален случај на иницијал и зација ; може да се кор ис­
ти стринг наместо загради и запирки:

char pattern [] =" ould";


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

charpattern[] = { ' о', ' u' , ' 1 ' , ' d' , '\0 ' };

Во овој случај, должината на низата е пет {четири знаци плус терминирач ­


киотзнак '\0').

4.1 О Рекурзија

Функциите во С може да се употребуваат рекурзив но ; односно , една фун к­


ција може да се повикува себеси директно или индиректно. Ќе го разгледаме
печатењето на еден број како стринг . Како што спомнавме порано , цифрите се
генерираат во погрешен редослед: цифрите на позиции со ниска вредн ост се
достапни пред цифрите на позиции со висока вредност, но нивното пе чатење
мора да оди обратно .
Постојат две решенија за овој проблем. Едно е цифрите да се зачуваат во
една низа во редоследот по кој се генерираат , а потоа низата да се испечати во
обратен редослед, како што направивме со функцијата itoa во Поглавје з . 6.
Алтернативното решение е рекурзивно , во кој printd првин се повикува себе­
си за да се справи со водечките цифри , а потоа ги печати ц ифрите кон крајот.
Меѓутоа, и оваа верзија може да падне кај најголемиот негативен број.
iinclude <stdio . h>

/* printd : nечатеае на n дехадно */


void printd(int n)
{
i f (n < 0) {
putchar ( ' -');
n = -n ;

if (n 1 10)
printd(n 1 10) ;
putchar(n % 10 + ' 0 ' ) ;
102 Функции и структура на програма Глава 4

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


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

ви, кое е независно од претходното множество. Така за printd (123) првата


printd добива аргумент n=123. Таа предава 12 на вториот printd, кој, пак,
предава 1 на третиот. printd од третото ниво печати 1 , па извршувањето се

враќа на второто ниво. Тамошното printd печати 2, па извршувањето про­


должува на првото ниво. Тука printd печати 3 и извршувањето на програмата
завршува.

Друг добар пример за рекурзија е quicksort , алгоритам за сортирање раз­


виен од Ч. А. Р. Хоаре во 1962 година. За дадена низа се одбира еден елемент,
а останатите се делат на две подмножества -тие што се помал и од елементот на

една страна, поголемите или еднаквите на него на друга страна. Истата проце ­
дура се извршува рекурзивно за двете подмножества . Во моментот кога едно
подмножество ќе брои помалку од два елемента, не му е потребно понатамош­
но сортирање; тоа ја запира рекурзијата.
Нашата верзија на quicksort алгоритмот, не е најбрзата што постои, но е
една од н ај едноста вните. Го користиме среднишниот елемент од секое подм ­
н ожество за партиционирање .

/* qsort : сортирај v[left] . .. v[right] во растечхи редоспед */


void qsort(int v[] , int left , int right)
{
int i, last ;
void swap(int v[] , int i , int ј) ;

if (left >= right) /* не nрави ништо ахо низата


содржи nомалху од два елемента */
return;
swap(v, left, (left + right)/2) ; /* nрефрли го
елементот за nартиционираље во v[O] */
last = left ;
for (i = left + 1 ; i <= right ; i++) /* nартиционираље */
if (v[i] < v[left])
swap(v , ++last, i);
swap(v , left, last) ; /* врати ја вредноста на
nартиционирачхиот елемент */
qsort(v , left , last-1) ;
qsort(v , last+1 , right) ;

Операцијата за замена ја преместивме во посебна функција swap , бидејќи се


појавува трипати во qsort .
4.11 е nр етп р оцесор 103

/* swap: размена на v[i] и v[j] */


void swap(int v[] , int i, int ј)
{
int temp ;

temp v [i] ;
v[i] v[j] ;
v[j] temp ;

Стандардната библиотека вклучува везрија на qsort, која што може да сортира


објекти од каков било тиn.
Рекурзијата не штеди на меморија, бидејќи купот од вредности кои се про­
цесираат мора да се зачува некаде . Н и ту, nак, е побрза . Но, рекурзивниот код
е nокомпактен и честопати полесен да се напише и разбе ре откол ку н еговиот
нерекурзивен еквивалент. Рекурзијата е посеб но згодна за рекурзивно дефи ­
нираните податочни структури, како што се дрвата, за кои ќе разгледаме убав
пример во Поглавје б . б.

Вежба 4-12. Адаптирајте ја идејата имплементирана во printd , за да напи­


шете рекурзивна верзија на i toa ; односно претворете цел број во стринг со
повикување на рекурзивна рутина.

Вежба 4-13 . Напишете рекурзивна верзија на reverse (ѕ) којашто огледал­


но го превртув а стрингот ѕ.

4.11 е претпроцесор

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


претпроцесор како прв чекор во компајлирањето. Двете најчесто употребу­
вани структури се #include , за да се вклучи сод ржината на една датотека за
време на компајлирање и #define за да се заме ни некој симбол со а лтернативна
се квенца од з наци . Другите конструкции о пишани во ова п оглавје вклучуваат
услов но компајлирање и макроа со аргументи.

4.11.1 Вклучување на датотеки

Вклучува њето на датотеките овозможува пол ес но с пр авување со (помеѓу


другото) колекции од #define директиви и декларации. Секоја изворна линија
во форма
104 Функции и структура на nрограма Глава 4

#include "име - на- датотека"

или

#include <име- на- датотека>

се за менува со содржината на датотеката име- на - датотека. Ако име - на - датотека се


наведе во наводници, пребарувањето за датотеката обична заnочнува каде што се на­
оѓа изворната nрограма; ако не се nронајде таму, или ако нејзиното име е омеѓено со
< и >, пребарувањето продолжува на nредефинираните места со имплементацијата .
Вклучената датотека и самата може да содржи други #include директиви.
Честопати има повеќе #include линии на nочетокот на изворната датоте­
ка за да се вклучат заедничките #define искази и extern декларации, или да
се овозможи nристаn до декларации на функциски nрототиnови за функции
кои се наоѓаат во стандардната библиотека, во заглавја како што е, на nример,
<stdio.h>. (Прецизно кажано, не мора да станува збор за датотеки ; детали­
те за тоа како се nристаnува до заглавјата ќе зависат од имnлементацијата) .
#include е згоден начин да се врзат декларациите заедно кај големите
програми. Гарантира дека на сите изворни датотеки ќе бидат доставени исти­
те дефиниции и декларации на променливи, на тој начин елиминирајќи еден
посебн о незгоден тип на грешка. Нормално, кога една при клуч на датотека се
модифицира, сите датотеки кои зависат од неа мора да се nрекомпајлираат.

4.11.2 Макрозамена

Дефи ницијата има форма

#define име текст за замена

Таа n овику ва макрозамена од наједноставен тип- следните појави на белегот име


ќе бидат замен ети со текст_за_замена. Името употребено во #define има иста
форма како и име на променлива. Текстот за замена е произволен . Н ормално ,
текстот за замена е nретставен со остатокот од линијата, но долгите дефиниции
може да се протегаат на неколку линии меѓусебно поврзани, со nоставување на \
на крајот од секоја ли нија што треба да биде продолжена. Делокругот на името
дефинирано со #define е од nочетокот на дефиницијата до крајот на изворната
датотека што се компајлира . Една дефиниција може да користи nретходни дефи­
ниции. Замените се прават само за белезите и не се прават во рамките на стрин­
гови омеѓени со двојни на водници, на nример, ако УЕЅ е некое дефинирано име
замен ата нема да се наnрави во случаите како во printf ( " УЕЅ " ) или УЕЅМАN.

Секое име може да се дефинира со каков било текст за замена. На nример:

#define forever for (; ; ) / * бесконечен цих.пус */


4.11 е претпроцесор 105

дефинира нов збор, forever, за бесконечен циклус.


Можно е да се дефинираат макроа со аргументи, па текстот за замена може
да биде различен при различни повикувања на макрото. Како пример , ќе де­
финираме макро наречено max:

#definemax(A , В) ((А)> (В)? (А) : (В))

Иако наликува на функциски повик , употребата на max резултира со in-


line (вметнат при компајлирање) код. Секое појавување на формален пара­
метар (во случајов А и В) ќе биде заменето со соодветно доделениот аргумент .
Поради тоа линијата

x=max(p+q, r+s) ;

ќе се замени со линијата

х = ( (p+q) > (r+s) ? (p+q) : (r+s)) ;

Се додека аргументите се употребуваат конзистентно, ова макро ќе може


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

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


евалуираат два пати; ова е проблематично во случај да у потребувате додатни
операции како оператори за влез , излез или инкрементирање. На пример:

max(i++, ј++) /* ПОГРЕШНО */

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


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

#define square(x) х * х /* ПОГРЕШНО */

се повика како square ( z + 1) .


Нема сомнение, макроата се корисни. Еден практичен пример доаѓа од
заглавјето <stdio. h>, во кое getchar и putchar често се дефинирани како
макроа за да се избегне преоптоварување за време на извршување на функ­
циски повик за секој процесиран знак. Функциите во <ctype. h>, исто така,
често се имплементирани како макроа.

На имињата може да им биде откажана дефиницијата преку #undef наред­


бата, честопати, со цел, да се оси гураме дека една рутина , навистина, е функ­
ција , а не макро:
106 Функции и структура на програма Глава 4

#undef qetchar

int qetchar (void) { ... }

Формалните параметри не се заменуваат во цитирани стрингови ( поставени


во наводници). Сепак, ако на името на параметарот му претходи# во текстот
за замена, комбинацијата ќе биде експандирана и во стринг во наводници со
замена на параметарот со конкретниот аргумент. Ова може да се комбинира со
надоврзување на стринговите и да се направи, на пример, макро за отстрану­

вање на грешки при печатење.

#define dprint (expr) printf (#expr" = %q\n", expr)


Кога ќе се повика оваа линија, како во

dprint (х/у)

ова макро ќе биде експандирано како

printf ("х/у" "= &q\n", х/у) ;

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

printf ( "х/у = &q\n", х/у) ;

Во конкретниот аргумент, секоја појава на "се заменува со\" и секој\ со\\ ,


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

Претпроцесорскиот оператор ##обезбедува начин за надоврзување на кон­


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

повторно се проверува. На пример, макрото paste ги спојува неговите два а р­


гумента:

#define paste (front, back) front ## back

па paste (name, 1) го креира симболот namel.


Правилата за вгнездена употреба на## се сложени; други детали може да се
најдат во Додаток А .

Вежба 4-14. Дефинирајте макро swap (t,x,y) кое прави замена на двата ар­
гумента од тип t. (Ќе ви помогне блоковска структура) .
4 .11 е претпро цесор 107

4.11.3 Усnовно вкnучување

Можно е да се контролира текот н а самото претпроцесирање со условни


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

ни за време на компајлирањето.
Линијата iif евалуира константен целоброен изра з (кој н е мора да вк­
лучува sizeof , претопувања или enum константи). Ако изразот е ненул­
ти , линиите кои следат се до #endif или #elif или #else ќе бидат вкл уче­
ни. Претпроцесор с киот израз #elif наликува на else-if . И з разот defined
(name) во #if е 1 ако name е дефинирано , во спротивно е о .
На пример , з а д а се осигураме де ка содржината на датотеката hdr . h е вклу­
чена само еднаш , содржината на датотеките се оградени со усл ов од следниов

облик:

#if !defined(НDR)
#define НDR

/* содржината hdr.h е сместена тука */

#endif

Првото в клучување на hdr. h го дефинира името НDR ; сите последователни вк­


лучувања ќе детектираат дека тоа име веќе е дефинирано и ќе nрескокнат долу
до изразот #endif . Сличен стил може да се користи за да се избегне в клуч у­
вање на датотеки nовеќе пати. Ако овој стил се користи конзистентно, тогаш
секое заглавје може во себе да ги вклучи другите заглавја од кои за виси , без
потреба корисникот на заглавјето да се справува со таа ме ѓузав исн ост.

Оваа секвенца го проверува името ЅУЅТЕМ за да одлуч и која верзија н а за­


главјето да ја вклучи:

#if ЅУЅТЕМ==SYSV
#define НDR "sysv. h "
#elif ЅУЅТЕМ = BSD
#define НDR " bsd.h"
#elif ЅУЅТЕМ = МЅDОЅ
#define НDR " msdos . h "
#else
#define НDR " default . h "
#endif
#include HDR
\08 Функции и структура на програма Гл ава 4

Директивите ftifdef и iifndef се специјализирани форми кои проверуваат


дали едно име е дефинирано . Првиот пример од ftif по го ре можеше да се
напише и како

ftifndefНDR
ldefineНDR

/* содржината hdr . h е сместена туха */

iendif
Глава 5: Покажувачи и низи

Покажувач е променлива која содржи адреса на друга променлива о Во С покажу­


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

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

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


гите можни начини о Покажувачите и низите се тесно поврзани ; ова поглавје, исто
така, ја проучува таа поврзаност и објаснува како истата да се искористи о
Покажувачите некогаш во комбинација со goto наредбата создаваа програ­
ми кои беа готово невозможни за разбирање о Ова е сигурно вистина кога тие
се користат невнимателно, а мошне е лесно да се креираат покажувачи кои по­

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


може да се искористат да се постигне јасност и едноставност о Овој аспект ќе се
обидеме да го илустрираме овде о
Главната промена во ANSI е стандардот е разјаснувањето на правилата за
тоа како се манипулира со покажувачите, нешто што во практика веќе го при­
менуваат добрите програмери и што го наметнуваат добрите компајлери о Исто
така, типотvоid* (покажувач кон void) е замена за char* како соодветен тип

за генерички покажувач о

5.1 Покажувачи и адреси

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


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

дуално или во поврзани групи о Една вообичаена ситуација е дека, секој бајт ·
може да биде char , пар од еднобајтни клетки може да се третира како short
цел број, а четири соседни бајти формираат long цел број о Покажувач е група
на клетки (обично две или чети ри) кои во себе може да чуваат адреса о Така,
ако е е char и р е покажувач кој покажува кон него , ситуацијата б и можеле да ја
презентираме на следниов начин :

109
110 Покажувачи и низи Глава 5

Унарн иот оператор & ја дава адресата на еден објект, па така наредбата

р= &е ;

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


ко н "с . Операторот & се однесува единствено на објекти во меморијата: про­
менливи и елементи на ни з и . Н е може да се при ме ни на изрази , ко н станти,
или register проме нливи.
Унарниот оператор * се нарекува onepamop за индирекција или onepamop
за дереференцирање; кога ќе се при мени над некој покажувач , го пристапува
објектот кон кој покажува покажувачот . Да претпоставиме дека х и у се цели
броеви, а ip е покажувач кон int. Наредната вештачка секвенца п окажува
како се де кларира по кажувач и како се користат & и *:

int х =
1, у =2, z[10] ;
int *ip ; /* ip е nокажувач кон int * /

ip = &х; /* ip сега nокажува кон х */


у = *ip; / * у е сега 1 */
*ip = О; /* х е сега О */
ip = &z[O] ; /* ip сега nокажува кон z[O] */

Декл арациите н а х, у, и z се исти како што беа и досега. Декларацијата на по­


кажу вачот ip ,

int* р ;

е осмислен а како мнемоничка; таа вели дека изразот *ip има int вредност.

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


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

doub1e*dp , atof(char*) ;

оз нач ува дека во некој из раз *dp и atof (ѕ) имаат double вредности , и дека
аргументот на atof е по кажува ч ко н char.
Исто така , треба да забележите дека еден п окажувач е обврзан да покажува
кон некој одреден вид на објект: секој покажувач п окажува ко н специфичен
податочен тип. (Има еден исклучок на ова: " пока жу вач кон void" се кори сти
за чување вредност на кој било друг тип на покажувачи , но самиот не може
да биде дереференциран. ќе се навратиме на оваа проблематика во Поглавј е
5.11)
Ако ip покажува кон целиот број х, * ip може да се појав и во сите контексти
во кои може и х, п а така
5.2 Покажувач и и функциски аргументи 111

*ip=*ip+10 ;

го зголемува *ip за 10 .
Унарните оператори * и & се врзуваат посилно отколку аритметичките , па
така доделувањето

y=*ip+1

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

*ip += 1

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

++*ip

(*ip)++

Заградите во последниот случај се неоп ходни; без нив, изразот б и го инкре­


ментирал ip, а не вредноста кон која тој покажува , од причин а што унарните
оператори како * и ++ се асоцираат оддесно кон лево .

За крај , бидејќи покажувачите се променливи, тие може да се користат и без де­


референцирање. На пример , ако iqe друг покажувач кон int вредност, наредбата

iq= ip ;

ја копира содржината на ip во iq, после што iq покажува кон она кон што по­
кажува и ip.

5.2 Покажувачи и функциски аргументи

Бидејќи во С , функциите ги предаваат аргументите по вредност, не постои


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

swap(a, b) ;

каде swap е функција дефинирана со


112 Покажувачи и низи Глава 5

void swap(int х, int у) /* ПОГРЕШНО */


{
int temp ;

temp = х;
х = у;

у = temp;

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


рутината во која е повикана. Горнава функција само прави замена на копиите на а и b.
За да се постигне посакуваната замена, повикувачката програма тре-ба да
предаде покажувачи кон вредностите што треба да се заменат:

swap (&а, &b) ;

Бидејќи операторот & ја дава адресата на некоја променлива , &а претста­


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

voidswap(int*px , int*py) / * смена на *рх и *ру */


{
int"temp;

temp= *рх ;

*рх= *ру;

*ру= temp;

Сликовито :

во повикува чот:

а :

b :
5.2 Покажувачи и функциски аргументи 113

Покажувачите овозможуваат функцијата да nристапи и промени објекти


во функцијата од која била повикана. Како пример ќе ја земеме функцијата
getint, која врши неформатирана конверзија на влезот , преку прекинување
на влезниот поток од знаци, во целобројни вредности, еден цел број по по­
вик. getint ќе треба да ја врати вредноста која ја прочитала и да сигнализира
крај во случај кога нема ништо на влез . Овие вредности треба да се вратат пре­
ку посебни патишта, од причина што, без разлика која вредност ќе се користи
како сигнал за EOF, истата таа вредност може да биде и вредност која дошла од
податочен влез.
Едно решение е getint да го врати EOF статусот, како функциска return
вредност, додека предавањето на конвертираниот број кон повикувачката
функција да се направи преку покажувачки аргумент. Оваа е варијантата која
се користи и кај функцијата scanf ; nогледнете го Поглавје 7 . 4 . Следниот ци­
клус врши пополнување една низа, со цели броеви добиени преку повици кон
getint:

intn, array[SIZE], getint(int *) ;

for (n= О; n< SIZE && getint (&array[n]) !=EOF; n++)

Секој повик го поставува array [n] на следниот цел број кој доаѓа на пода­
точен влез , а потоа го зголемува n . Забележете дека од клуч на важност е да
се предаде адресата на array[n] како параметар на getint . Не постои друг
начин getint да го прати конвертираниот број назад кон повикувачот. Нашата
верзија на getint враќа EOF за сигнализирање на крај на податочниот влез,
нула, во случај кога следниот влезен податок не е број, и позитивна вредноста,
во случај кога има валиден број на податочен влез.

linclude <ctype . h>

int getch (void) ;


void ungeteh (int);

/* getint: земи ro наредниот број од влез •о *pn */


int getint (int *pn)
{
int е, sign ;

while (isspace (е= getch ())) / * nреехохни nрuни места */

if (!isdigit(e) &&е '=EOF&&c !='+ ' &&е!='-') {


ungetch (е) ; 1* не е број */
return О ;
114 Покажувачи и низи Глава 5

sign= (е= ' -')? -1 : 1 ;


i f (е= ' +' 11 е=='- ' )
e=geteh() ;
for (*pn =О; isdigit (е) , е= geteh ())
*pn = 1О * *pn + (е - ' О ' ) ;
*pn *= sign ;
i f (е !=EOF)
ungeteh(e) ;
return е;

Во телото на getint , *pn се користи како обична int променлива. Исто така,
ги користевме функциите geteh и ungeteh (опишани во Поглавје 4. З) за да
може дополнител ниот, знак кој мора да се прочита , да се врати назад на пода­
точниот влез.

Вежба 5-l . Како што е напиша на , getint ги третира случаите во кои се поја­
вуваат знаците + и - без цифра п осле нив , како валидна претста ва за н ула.
По правете ја така што ќе го враќате таквиот зна к назад на влез.
Вежба 5-2. Напишете функција getfloat , аналогн а на getint , која враќа ва­
лиден реален број. Каков тип на резултат треба да врати getfloat?

5.3 Покажувачи и НИ3И

Во С, постои строга врска помеѓу покажувачите и низите, доволно строга


што покажувачите и низите треба да се разгледуваат и дискутираат паралелно.
Која било операција која може да се постигне со индексирање на низа , може да
се постиг н е и со употреба на покажувачи. Во општ случај верзијата со пока жу­
вачи ќе биде побрза , но малку потеш ка за разбирање.

Декларацијата
inta[10] ;
дефинира низа со должина 10 , односно блок од 10 последователн и објекти
именувани какоа[О], а[1] ,

... , а [9] .

а :

а[О]а[1] а[9]

Нотација та а [i] се однесува на i-тиотелемент од ни зата. Акора се декларира


како покажувач ко н цел број

.....
'\

··.
503 Покажувачи и низи 115

int *ра;

тогаш доделувањето

ра = &а[О];
го поставува ра како покажувач кон нултиот елемент на а ; т о е о , ра ја содржи
адресата на а [о] о

ра:

а[О]

Сега доделувањето

х= *ра;

ја копира содржината на а [о] во променливата х о


Ако ра покажува кон некој посебен елемент на низа, тогаш по дефиниција
ра+1ќе покажува кон следниот, ра +i покажува кон i-тиот елемент после ра ,
а ра- i кон i-тиотелемент пред него о Па, акора покажува кон а [О], * (ра+1)
се однесува на а [1], ра +i на адресата на а [i], а* (pa+i) на вредноста на
a[i] о

а [О]

Овие забелешки се вистинити , без разлика од кој тип или големина се про­
менливите во низата а о Значењето на "додавање 1 на покажувач" и воопш­
то целата аритметика на покажувачите, е дека ра+1 покажува кон следниот

објект, а pa+i покажува кон i-тиот објект после ра о


Врската помеѓу индексирањето и аритметиката на покажувачите е мошне
блиска о По дефиниција, вредноста на една променлива или израз од тип низа
е адресата на нултиот елемент од низата о Па така, после доделувањето
116 Покажувачи и низи Глава 5

ра =&а [О] ;

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


цијата на почетниот елемент, доделувањето ра = &а [i] може да се запише и
како

ра=а ;

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


а [i] може да се напише како * (a+i) . При евалуацијата на а [i] , компајле­
рот веднаш го претвора во облик * (a+i) ; двете форми се еквивалентни.
Примената на операторот & на двете ст ран и од ова равенство , доведува до
заклучок дека &а [i] и a+i , исто така , се идентични: a+i е адресата на i - тиот
елемент после а . Од друга страна, ако ра е покажувач , изразите можат да го
користат со индекс ; pa[i] е иде нтично со* (pa+i) . Накратко речено , еден
изра з кој содржи име на ни за и индекс е еквива лентен со изразот напишан како
покажувач и растојание (анг. offset).
Постои една разлика помеѓу низа и покажувач која секогаш мора да се има пред­
вид. Покажувачот претставува променлива , па така, ра =а и ра++ се валидни изра­
зи. Но низите не се променливи ; конструкциитеод облика=ра и а++се нелегални.
Кога едно име на низа се предава како аргумент на функција, она што всуш­
ност се предава , е локацијата на почетниот елеме нт . Внатре во повиканата
функција, овој аргумент е локална променлива , п а така параметар име н а низа
како е покажувач, односно , променлива која содржи адреса. Може да го ко­
ристиме тој факт за да напишеме друга верзија на strlen, која ја пресметува
должината на еде н стринг .

/* strlen : враха допжина на стринr ѕ */


int strlen(char *ѕ)

int n ;

for (n = О ; *ѕ != ' \0 ', ѕ++)


n++ ;
return n ;

Бидејќи ѕ е п о кажувач, неговото инкрементирање е соврше но л егалн о;


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

strlen( " hello, world" ) ; /* string хонстанта */


strlen(array) ; /* char array[lOO] ; */
strlen (ptr) ; /* char *ptr ; */
5.3 Покажувачи и низи 11 7

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

char ѕ [] ;
и

char *ѕ;

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


поексплицитно декларира дека променлив ата е покажувач. Кога име на низа
се предава како аргумент на една функција 1 функциј ата по потреба може да
смета дека добила или низа или покажувач 1 и соодветно да манипулира. Може
дури да ги користи и двете нотации 1 ако се тие соодветни и јасни .
Можно е дел од некоја низа да се предаде како аргумент на функција 1 со
предавање на покажувач кој покаж у ва на почетокот на поднизата. На пример 1
ако а е низа и двата израза

f(&a[2])

f(a+2)

на функцијата f и ја предав аат адресата на поднизата која за почнува со елемен­


тот а [ 2] . Зборувајќи за f 1 нејзината декларација може да биде од облик

f(intarr[]) { ... }

или од облик

f (int *arr) { ... }

Па 1 се додека ја разгледуваме внатрешноста на функцијата f 1 фактот дека


параметарот се однесува само на дел од н екоја низаl сосема ни е неважен.
Ако сме сигурни дека елементот постои 1 возможно е н изата да се индекси ­
ра наназад ; р[-1] 1 р[ -2) 1 итн . 1 си нтаксички се легални и се однесуваат н а

елементите кои претходат на р [о] . Се разбира 1 не е легално да се референци­


раат објекти кои не се наоѓаат во границите на низата .

5.4 Адресна аритметика

Ако р е покажувач кон ел емент од некоја ни за , тогаш р ++ го инкреме нтир а


да покажува на следниот елеме нт 1 а p+=i го поместува да покажува на i -тиот

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


11 8 Покажувачи и низи Глава 5

ноставните форми на покажувачка или адресна аритметика. С е конзистентен


и регуларен во пристапот на адресната аритметика; интеграцијата на покажу­
вачите, низите и адресната аритметика е една од предностите на овој јазик. Да
ја илустрираме со пишување на едноставна програма за доделување на мемо­
риски простор . Програмата ќе има две рутини. Првата , alloc (n) , враќа по ­
кажувач кон n последователни знаковни позиции, кои може да се користат од

повикувачот на alloc за чување на знаци. Втората , afree (р) , го ослободува


претходно доделениот мемориски простор, со намера истиот да може да се ко­

ристи подоцна. Рутините се " рудиментирани " бидејќи повикувањата до afree


мора да се направат во обратен редослед од повиците до alloc. Всушност, ме­
морискиот простор менаџиран од alloc и afree , претставува куп (ан г. stack)
или листа во која последниот запишан елемент прв излегува. Стандардната
библиотека обезбедува аналогни функции кои ги немаат споменатите ограни­
чувања ; во Поглавје 8 . 7 ќе покажеме начин како тие да се имплементираат.
Најл есна имплементација би била alloc да предава делови од голема низа
од знаци која ќе ја повикуваме со allocbuf . Оваа низа ќе биде приватна за
alloc и afree . Бидејќи работат со покажувачи, а не со индекси на низи, ниту
една од рутините нема потреба да го знае името на таа низа, која може да биде
декларирана како static во изв орната датотека која ги содржи alloc и afree ,
и со тоа да биде невидлива надвор од неа. Во практични имплементации, оваа
низа не ни мора да има име ; таа би можела да се добие со повик до malloc или
со барање до оперативниот систем за добивање покажувач кон некој неимену­
ван мемориски блок.

пред повикот ко н alloc allocp : "'-...

allocbuf :L.. -1--L---LI---'-1----'-1--~--_____.


+-- -+-----
се користи -. слободн о ------'~

posle повикот кон alloc


allocp : "'-...

allocbuf :1 11 1
~~==~==~с~е~к~о~р~ис~т~и~::::::~~;.:::сс~л~об~о~д~н~о-=~~

Понатаму, потреб на ни е информација за тоа колкава е тековната зафате­


ност на allocbuf . Ние користиме покажувач наречен allocp , кој покажува
кон следниот слободен елемент. Кога alloc ќе побара n знаци , проверува
дали има останато доволно простор во allocbuf. Ако е така, alloc ја враќа
тековната вредност на allocp (Т. е. , почетокот на слободниот блок) , па го
инкрементира за n места, за да покажува кон следната слободна област. Во
случај да нема доволно простор, alloc враќа нула. Един ствено што прави
afree (р) е да го постави allocp на пока жувачот р ако р е во allocbuf .
5.4 Адресна аритметика 119

#define ALLOCSIZE 10000 / * голеии:на на расnоложив nростор */

static char allocbuf[ALLOCSIZE ] ; / * аростор резервираи за alloc */


static char *allocp = allocbuf ; / * спедиат а слободна nозиција */

char *alloc(int n) /* враха nо~ажувач ~ои n знаци* /


{
if (allocbuf + ALID:SIZE - allocp >е= n) { / * има дово.пно nростор */
allocp += n;
return allocp - n ; /* старото р */
else /* иеиа доволно nростор */
return О;

void afree (char *р) /*ослободи го nро с торот на ~ој nо~ажува р */


{
if (р
>= allocbuf && р < allocbuf + ALLOCSIZE)
allocp=p;

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


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

static char *allocp = allocbuf ;

го дефинира allocp како покажувач кон знаци и го поставува да покажува кон


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

static char *allocp = &allocbuf [ О ] ;

бидејќи името на ни зата е и адреса на нултиот елемент.


Про верка та

if (allocbuf + ALLOCSIZE - allocp >= n) { /* има доволно nростор*/

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


Во потврден случај, новата вред н ост на allocp ќе биде најмногу за еден п о­
голема од крајот на низата allocbuf. Во случај кога барањето може да се за ­
доволи , alloc враќа покажувач кон почетокот н а блокот од знаци ( забележе­
те ја декларацијата во самата функција) . Во спротивно , alloc мора да врати
некаков сигн ал дека нема преостанато доволно место. С гарантира дека нула
120 Покажува чи и низи Глава 5

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


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

Покажувачите и целите броеви меѓусебно се некомпатибилни . Нулата е


единствениот исклучок: константата нула може да се додели на еден покажу­

вач и еден пока жува ч може д а се споредува со константата нула . Често наместо
нула , се користи симболичката константа NULL, како потсетник кој појасно ја
истакнува посебноста на нулата како вредност за покажувач. NULL е дефини­
ран во <stdio . h>. Отсега па н ата му ќе користиме NULL .
Проверки те од облик

if (allocbuf + ALLOCSIZE - allocp >= n) { /* има доаолно nростор*/

if (р >= allocbuf && р < allocbuf + ALLOCSIZE)

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


вачите може да се споредуваат меѓусебно во одредени ситуации. Ако р и q по­
кажуваат кон елементи на иста низа, релациите како=, ! =, <, >=, итн. , се
валидни. На пример,

p<q

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


елементот кон кој покажува q. Секој покажувач може валидно да се споредув а
за еднаквост и нееднаквост со нула . Но однесувањето е недефинирано за а рит­
метика или сп оредби со покажувачи кои не покажуваат кон елементи од една
иста низа. (Постои ед ен и склучок: адресата н а првиот елеме н т после к рај от
на ни зата може да се ко ристи во адресна аритметика)

Втор о, веќе забележавме де ка покажувач и цел број може да се соб ираат и


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

p+n

ја означува адресата на n-тиот елемент после елементот кон кој по кажу ва р.


Ова е вистинито без разлика н а типот на објектот кон кој покажува р ; n се ска­
лира соодветно со големината на објектот кон кој п окажува р , определена од
декларацијата на р . Ако еден int е со должина од четири бај ти , int-oт ќе биде
скалира н за чет ири .

Одземањето на покажува чите, исто така, е валидна операција: ако р и q по­


кажуваат ко н елеме_нт од иста низа и р < q, тогаш q-p+l е бројот на елементи
од р до q . Овој факт може да се искористи за да се напише уште една верзија
на strlen :
1
5.4 Адресна аритметика 121

/* strlen: враха должина на стринг ѕ */


int strlen (char *ѕ)
{
char *р= ѕ;
while (*р != '\0')
р++;

returnp- ѕ;

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


првиот знак од стрингот. Во while циклусот , секој знак се и спитува одделно се до­
дека не се дојде до '\0' на крајот . Бидејќи р покажува кон знаци , секој п ов ик на
р++ го поместува р кон следниот знак во стрингот, а р - ѕ го дава бројот на знаци
кои биле изминати , т. е. , самата должина на стрингот . (Бројот на знаци во еден
стринг може да б иде преголем за чување во int. Заглавјето <stdd.ef. h> дефи ни ра
тип ptrdiff_ t кој е доволно голем да чува разлика помеѓу вр едностите на два пока­
жувача. Ако бевме претпазливи ќе користевме size_ t тип како повратна вредност
за функцијата strlen, за да ја направиме соодветна на стандардната библиотека .
size_ t е од неозначен целоброен тип кој го враќа операторот sizeof . )
Адресната аритметика е конзистентна: во случај да работевме со променливи од
тип float, кои зафаќаат повеќе меморија од променливи од тип char и ако р беше
покажувач кон float, р++ ќе го помести р за да по кажува кон следниот flo a t еле­
мент. Поради тоа би можеле да напишеме друга верзија на alloc , која наместо со
знаци , ќе работи со реални броеви , едноставно, со мала промена на декларациите
од char во float низ функциите alloc и afree. Сите манипулации со покажувачите
автоматски ја земаат предвид големината на објектите кон кои покажуваат.
Валидни операции со покажувачи се доделувањата на покажувачи од ист тип,
собирање и одземање на покажувач со цел број , одземање или споредување на
два покажувача кои покажуваат кон елементи на иста низа и доделување и сп ореду­

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


се валидни собирањето на два покажувача, обидите за нивното множење , делење ,
поместување во лево или десно и маскирање. Не може да се собираат со floa t ил и
double броеви , ниту, пак, (со исклучок на void*) да се направи доделување по­
меѓу покажувачи од различни типови без соодветно претопување.

5.5 Покажувачи кон 3наци и функции

Константен стринг, напишан како

"Јаѕ sum string"

претставува низа од знаци . Во внатрешното претставу вање низ и од знац и за вр­


шуваат со нулов знак '\О; за да може програмата да го најде крајот . Порад и тоа
122 Покажувачи и ни з и Глава 5

големината на доделениот мемориски простор е за еден поголема од бројот на


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

printf (" hello , world\n");

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


покажувач кон знак; printf прифаќа покажувач кон почетокот на низата од
знаци. Односно , до константниот стринг се пристапува пр еку по кажувач кој
покажува кон неговиот прв елемент . Стринговите константи не мора да бидат
функциски аргументи. Ако pmessaqe ја декларираме како

char *pmessaqe ;

тогаш изразот

pmessaqe = " now is the time";

и доделува на pmessaqe покажувач кон ни за од знаци. Не станува збор за


копирање на стрингот ; се работи единствено со покажувачи. С не обезбедува
никак ви оператори за обработка на цели низи од знаци како еден објект.

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

char amessaqe[] = " now is the time " ; /* низа */


char *pmessaqe = " now is the time"; /* похажуаач * /

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


лизирачка низа од знаци заедно со '\ 0' . Индивидуалните з наци во низата
може да се менуваат, но amessaqe секогаш ќе се однесува на истиот мемориски
простор. Од друга страна, pmessaqe е пока жувач, почетно поста вен да п ока­
жува кон константен стринг ; покажувачот може да се модифицира понатаму во
кодот , но резултатот е недефиниран ако се обидете да ја менувате содржината
на стрингот.

pmessaqe: ~~--~-~lnow is the t ime\0

amessage: lnow is the time\0

ќе илустрираме повеќе аспекти на покажувачите и низите преку проучување


на верзиите на две корисни функции , преземен и и адаптирани од стандардна­
та библиотека . Првата функција еstrcpy (ѕ , t) , која го копира стрингот t во
стрингот ѕ. Би било убаво да можевме да напишеме ѕ = t, но тоа не ни врши
5.5 Покажувачи кон знаци и функции 123

никаква работа , бидејќи она што се прави во ваков случај е копирање на пока­
жувачот, а не на самите знаци . За да се копираат знаците, ќе ни биде потребен
циклус. Првин ќе ја при кажеме верзијата со низ и:

/* strcpy: копирај го t во ѕ ; верзија со иидексирани иизи */


void strcpy(char *ѕ , char *t)
{
int i ;

i = О;
while ((s[i] = t[i]) != ' \0 ' )
i++;

За споредба , еве ја и верзијата на strcpy кој а користи пока жув а чи

/* strcpy : коnирај ro t во ѕ ; верзија со покажувачи*/


void strcpy(char *ѕ, char *t)
{
while ( (*ѕ *t) != '\0 ' )
ѕ++ ;
t++ ;

Бидејќи аргументите се предаваат по вредност , strcpy може да ги користи па ­


раметрите ѕ и t како што сака . Овде тие претставуваат соодветно иницијали ­
зирани покажувачи , кои се поместуваат долж низите , знак по знак, се додека
'\о ' , што го означува крајот на t, не биде ископирана во ѕ.
Во практика, strcpy немаше да биде напиша на како што покажавме овде.
Искусните С програмери би претпочитале

/* strcpy : коnирај ro t во ѕ , верзија со покажу8ачи 2 */


void strcpy(char *ѕ, char *t)
{
while ( (*ѕ++ = *t++) != '\0')

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


Вредноста на *t++ е знакот кон кој покажувал t, пред t да биде и нкременти­
ран ; постфиксниот оператор ++ не ја менува вредноста на t се доде ка не се
предаде знакот кон кој t покажува . На ист начин, знакот се запишува во стара­
та позиција пред да биде зголемен ѕ . Тоа е и знакот кој се споредува со ' \о' во
контролниот дел на циклусот. Крајниот ефект е копирање на знаците од t во ѕ ,
вклучителносотерминалниотзнак ' \0' .
124 Покажувачи и низи Глава 5

Конечно, забележете дека споредбата со '\о' е одвишна , бидејќи пра­


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

/* strcpy: хопирај го t во ѕ; верзија со nохажувачи З */


void strcpy(char *ѕ , char *t)
{
while (*ѕ++ = *t++)

Иако ова на прв поглед може да ви изгледа необично, на оваа програмер­


ска финеса ќе треба да се навикнете , од причина што ќе ја среќавате мошне
често во понатамошните С програми. Функцијата strcpy која е дефинирана
во стандардната библиотека (<string. h>) како функциска вредност го враќа
целниот стринг. Втората рутина која ќе ја проучиме е strcmp (ѕ, t) , која ги
споредува стринговите ѕ и t и враќа негативен резултат , нула или позитивен
резултат во случаите кога ѕ е лексикографски помало , еднакво или поголема
од t . Резултатот се добива со одземање на првите знаци кои се разликуваат .

/* strcmp: врати <О ахо s<t, О ахо s==t, >0 ахо s>t */
int strcmp(char *ѕ , char *t)

int i ;
for (i = О ; s[i] == t[i]; i++)
i f (ѕ [i] = '\0')
return О ;
return s[i] - t[i] ;

Верзијата со покажувачи:

/* strcmp: врати <О ахо s<t, О ахо s==t, >0 ахо s>t */
int strcmp(char *ѕ, char *t)

for ( ; == *t ; ѕ++ , t++)



if = '\0 ' )
(*ѕ
return О;
return *ѕ - *t ;

Бидејќи++ и-- може да се сретнат во префиксна и постфиксна форма , мож­


на е појава и на други комбинации помеѓу*, од една страна, и++ и--, од друга
страна , иако тие се поретки. На пример,
5.6 Низи од покажувачи; Покажу вачи кон пок ажувачи 125

*--р

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


парот изрази

*р++ = val ; /* додај val на хуnот */


val = *--р ; /* отстрани го врвот на хуnот во val * /

претставуваат стандардни идиоми за додај (анг. push) и отстрани (анг. рор) опе­
рации кај купови; погледни го Поглавје 4 . 3.
Заглавјето <string . h> содржи декларации за функциите спомнати во ова
поглавје и голем број на други функции за работа со стрингови од стандардната
библиотека.

Вежба 5-3. Напишете верзија со покажувачи на функцијата strcat која ја по­


кажавме во Глава 2: strcat (ѕ , t) го копира стрингот t на крајот од ѕ .

Вежба 5-4. Напишете функција strend ( ѕ , t) , која враќа 1 во случаите кога t


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

Вежба 5-5 . Напишете верзии на следниве функции од стандардната библио­


тека: strncopy, strncat , и strncmp , кои оперираат врз првите (најмногу) n
зн а ци од нивните аргументни стрингови. На пример , strncpy (ѕ , t, n) копи­
ра најмногу n знаци од t во ѕ. Целосниот опис на функциите може да се најде
во Додаток Б.

Вежба 5-6 . Преуредете ги програмите од претходните глави така што намес­


то низи ќе користите покажувачи. Добри можности нудат функциите getline
(Глава 1 и4), atoi , itoa и нивните варијанти (Глави 2, 3 и 4), reverse
(Глава 3) и strindex и getop (Глава 4) .

5.6 Низи од покажувачи; Покажувачи кон покажувачи

Бидејќи пока жувачите во суштина се променливи , тие може да се чуваат во


низ и исто како и другите типови променливи . Да го илустрираме к ажаното со
пишување на програма која ќе сортира мн ожество од текстуални лин и и по ал ­
фабетски редослед 1 слично на функцијата sort од оперативниот систем UNIX.
Во Глава з 1 го претставивме Шел-сорт (анг. Shell sort) функцијата кој сор­
тираше низа од цели броеви 1 а во Глава 4 се подобривме со воведување на
quicksort . Истите алгоритми ќе функционираат и тука , со таа разлика што
126 Покажувачи и низи Глава 5

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


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

Токму овде ќе го воведеме поимот за низи од по кажувачи . Ако линиите,


кои треба да се сортираат, се складираат од-крај-до-крај во една д олга низа
од знаци, тогаш кон секоја линија може да се пристап и преку покажувач кон
н ејзиниот прв знак. Дв е линии може да се споредуваат со пр едав ање на нивни ­
те покажувачи како аргументи на функцијата strcmp . Во случај кога треба да се
заменат две неподредени л и нии , се врши замена н а покажувачите од ни зата ,

а не на самите текстуални линии .

На тој начин ги елиминираме проблемите на комплицирано менаџирање со


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

вчитај ги сите линии од податочен влез


сортирајги
испечати ги во подреден редослед

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


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

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


да изгради низа од покажувач и кон ли ниите. Исто така, ќе треба да го б рои
бројот на влезни линии , затоа што таа информација е потребна во деловите за
сортирање и печатење . Бидејќи влезната функција може да се справи само со
конечен број на влезн и линии, ќе треба да врати нек аков сигн ал во случај да се
појават преrолем број линии на влез. Единственото нешто што треба да прави
излезната ру ти на , е да ги п е чати линиите во редослед во кој тие се под редени
во низата од по кажувачи .
5.6 Низи од покажувачи ; Покажувачи кон покажувачи 127

#include <stdio.h>
#include <string.h>

#define МAXLINES 5000 /* махсимален број на линии за сортираи.е* 1

char *1ineptr[МAXLINES] ; /* nокажувачи кон текстуалните лимии */

int readlines(char *1ineptr[], int nlines) ;


void writelines(char *1ineptr[], int n1ines) ;

void qsort(char *1ineptr[] , int 1eft, int right) ;

/* сортираи.е на влезните линии */


main ()
{
int nlines ; / * број на nрочитани линии*/

if ((nlines = readlines(lineptr , МAXLINES)) >=О) {


qsort(lineptr, О, nlines-1) ;
writelines(lineptr , nlines) ;
return О ;
else {
printf( " error: input too big to sort\n");
return 1 ;

#define МAXLEN 1000 / * максинална должина на влезните линии* /


int getline(char * , int);
char *alloc(int);

/* readlines: чита линии од nодаточен влез*/


int readlines(char *lineptr[], int maxlines)

int len , nlines;


char *р , line[МAXLEN];

nlines = О;
while ((len = getline(line , МAXLEN)) > 0)
if (nlines >= maxlines 1 1 р = alloc(len) NULL)
return - 1 ;
else {
line[len-1] = ' \0 ' ; /* избриши го знакот за нов ред */
strcpy(p , line) ;
lineptr[nlines++] = р ;

return nlines ;
128 Покажувачи и низи Глава 5

/* writelines: nечатеае на текстуапките линии */


void writelines(char *lineptr[], int nlines)
{
int i;

for (i = О; i < nlines ; i++)


printf( " %s\n" , lineptr[i]) ;

Функцијатаgetline е истата од Поглавје 1 . 9. Главната промена е деклара­


цијата за lineptr :
char *lineptr [МAXLINES]

означува дека lineptr е низа со МAXLINES бр ој на елементи , од кои секој


елемент е покажувач кон char . Односно , lineptr [i] е покажувач кон знак,
*lineptr [i] е знак кон кој истиот покажува , т. е., првиот знак од i-тата
складирана текстуална линија. Бидејќи променливата lineptr сама за себе
претставува име на низа, та а може да се третир а како покажувач , на ист начин

како во претходните примери, па wri telines м оже да биде напишана како

/* writelines : nечатење на текстуалките линии */


void writelines(char *lineptr[] , int nlines)
{
while (nlines-- > О)
printf(" %s\n", *lineptr++) ;

Иницијалн о , *lineptr покажува кон првата линија ; секој инкремент го по­


местува до следниот покажувач, додека nlines одбројува надолу .
Со влезот и и злезат под контрола , можеме да продолжиме со сортирање­
то . Quicksort алгоритамот од Глава 4 има потреба само од мал а модифика­
ција: треба да се променат деклара циите и операцијата за споредување ќе тре­
ба да се изведе преку повик до strcmp . Но , алгоритамот во суштина ќе остане
ист, што ни влева доверба дека и ќе функционира правилно.

/* qsort : сортирај v[left] ... v[right] во растечки редослед*/


void qsort(char *v[] , int left, int right)
{
int i , last ;
void swap{char *v[], int i , int ј) ;

if (left >= right) / * не nрави ништо ако кизата содржи


5.7 Пове~едимензионалнинизи 129

nомалху од два елемента */


return;
swap(v 1 left 1 (left + right)/2);
last = left;
for (i = left+1; i <= right; i++)
if (strcmp(v[i] v[left]) <О) 1

swap(v 1 ++last 1 i) ;
swap(v left last);
1 1

qsort(v 1 left 1 last-1);


qsort(v 1 last+1 1 right);

Соодветно 1 и swap рутината има потреба само од тривијални промени:

1* swap: размена на v [i] и v [ј] */


void swap (char *v [] int i int ј)
1 1

{
char *temp;

temp=v[i];
v[i] =v[j];
v[j] =temp;

Бидеј~и секој поединечен елемент на v (Т. е. lineptr) е покажувач кон char 1

соодветно и temp треба да биде од истиот тип 1 со што ~е може да се изведе


нивно меѓусебно копирање.

Вежба 5-7. Повторно напишете ја функцијата readlines да меморира линии


во низа која е добиена од main наместо да се повикува alloc за одржување на
меморискиот простор. Колку е побрза програмата?

5.7 Повеќед~мен3ионални ни3и


С обезбедува правоаголни пове~едимензионални низ и 1 иако во практика
тие се користат многу помалку отколку низите од покажувачи. Во овој дел 1 ~е
објасниме некои од нивните карактеристики .
Ќе го разгледаме проблемот на конверзија на датум, од ден во месецот 1 во
ден од годината и обратно. На пример 1 1-ви март е 60-rиот ден од непрестап­
на година, а 61-иот од престап на. За да ги изведеме претворањата 1 да дефи­
нираме две функции: day_of_year која ~е врши претворање од месец и ден ,
во ден од годината, додека month_ day 1 пак, ~е претвора ден од годината во
соодветните ден и месец. Бидеј~и последната функција како краен резултат
има две вредности 1 аргументите ден и месец ~е бидат покажувачи :
month_day (1988 60, 1 &m 1 &d)
130 Покажувачи и низи Глава 5

ги поставува m на 2 и d на 29 (29-ти февруари) о


И двете функции имаат потреба од идентична информација , табела од
бројот на денови за секој месец ( "септември има триесет денови о о о " ) о
Бидејќи бројот на денови во месец варира за престапни и непрестапни години,
полесно е да ги раздвоиме информациите во два реда на една дводимензио­
нална низа , со цел да можеме да водиме сметка што се случува со февруари за
време на пресметката о Во продолжение следат низата и функциите потребни
за изведување на трансформациите:

staticchardaytab[2] [13] = {
{0, 31, 28, 31, 30, 31, 30 , 31 , 31, 30 , 31 , 30, 31} '
{0 , 31, 29, 31 , 30 , 31 , 30, 31 , 31, 30, 31 , 30, 31}
};

/* day_of_year: пресмет:ка на ден од годината за даден месец и ден*/


int day_of_year(int year, int month , int day)

int i , leap ;

leap = year%4 == О && year%100 != О 1 1 year%400 О;


for (i = 1 ; i < month; i++)
day += daytab[leap] [i];
return day;

/* month_day: пресметха на ден и месец , за даден ден од годината */


voidmonth_day(intyear , intyearday , int *pmonth , int *pday)
{
int i, leap;

leap = year%4 ==О && year%100 ! =О 11 year%400 =О ;


for (i = 1; yearday > daytab[leap] [i] ; i++)
yearday -= daytab [ leap] [ i] ;
*pmonth=i ;
*pday = yearday;

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


чајот со leap , изнесува или нула (невистина) или еден (вистина) , па истата
може да се користи како индекс за низата daytab о Низата daytab треба да биде
надворешна и за day_of_year и за month_ day , со што и обете ќе бидат во мож­
ност да ја користат о Ја деклариравме за char за да ја илустрираме легитимната
употреба на char за чување на мали незнаковни цели броеви о
5.7 Повеќедимензионални низи 131

daytab е првата дводимензионална низа со која се среќаваме досега. Во


С 1 дводимензионална низа всушност претставува еднодимензионална низа 1

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

daytab[i] [ј] /* [редица] [копона] */

а не

daytab[i,j] /* ПОГРЕШНО */

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


тираат на ист начин како и во другите програмски јазици. Елементите се мемо­
рираат по редици, така што најдесниот индекс , или колоната, варира најбрзо
ако до елементите се пристапува по редоследот на кој се меморирани .
Една низа се иницијализира со листа од иницијални вредности омеѓена со
загради; секој ред од дводимензионалната низа се иницијализира со соодвет­
на подлиста. Ја започнавме низата daytab со првата колона поставена на вред­
ност нула, за да илустрираме природна претстава на месеците со броеви ко и
одат од 1 до 12, а не од о до 11. Бидејќи меморискиот простор не претставу ва
проблем, оваа репрезентација е појасна , отколку онаа во која ќе мораше да
вршиме калибрирање на индексите.
Ако дводимензионална низа треба да се предаде како аргумент на функ­
ција, декларацијата на параметарот мора да вклучува број на колони ; бројот
на редици не е релевантен, бидејќи она што се предава, како и поран о , е по­
кажувач кон низа од редици, каде секој ред е низа од 13 int вредности. В о
конкретниов случај , станува збор за покажувач кон објекти кои се низи од 13
int вредности. Поради тоа, ако треба низата daytab да се предаде како а р гу­
мент на функцијата f, декларацијата на f би изгледала вака:

f(intdaytab[2] [13]) { ... }

исто така, би можела да изгледа и вака

f(intdaytab[] [13]) { ... }

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

f (int (*daytab) [13]) { ... }

што означува дека параметарот е покажувач кон низа од 13 цели броеви.


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

int *daytab[13]
132 Покажувачи и низи Глава 5

означува низа од 13 покажувачи кон цели броеви. Општо кажано, слободна


е само првата димензија (индекс) од една низа; сите останати мора да бидат
наведени.

Поглавје ѕ .12 навлегува подлабоко во оваа проблематика.

Вежба 5-8. И кај двете функции (day_of_year иmonth_day) не е изведена


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

5.8 Иницијаnи3ација на НИ3И од покажувачи

Да го разгледаме проблемот на пишување функција month_name (n), која


ќе враќа покажувач кон стринг кој го содржи името на n-тиот месец од годи­
ната. Ова е идеална ситуација за примена на внатрешна static низа. month_
name содржи приватна низа од стрингови и при повик враќа покажувач кон
соодветниот стринг. Овој дел ја обработува иницијализацијата на таа низа од
стрингови.

Синтаксата е слична на претходните иницијализации:

/* month_name: враќа ние на n-тиот месец од годината */


char *month_name(int n)
{
static char *name[] = {
"Illegal month",
"January" , "February", "М&rch" ,
"April ", "Мау " , "June " ,
" July", " August" , " September",
" October", " November" , " December"
};

return (n< 1 11 n > 12) ? name[O] name [n] ;

Декларацијата на name, која претставува низа од покажувачи кон знаци, е


иста како и за lineptr во примерот со сортирањето. Иницијализаторот е лис­
та од стрингови ; за секој од нив има доделена соодветна позиција во низата.
Знаците од i-тиот стринг се складирани некаде во меморијата, а покажува­
чот кон нив е складиран во name [i] . Бидејќи големината на низата name не е
наведена, компајлерот автоматски ги брои иницијализаторите и го пополнува
соодветниот број.
5.9 Покажувачи наспроти повеќедимензионални низи 133

5.9 Покажувачи наспроти повеќедимен3ионални ни3и

Почетниците во С понекогаш се збунети од разликата помеѓу дводимензио­


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

int а [10] [20] ;


int *b [10] ;

тогаш и а[З] [4] и b[З] [4] се синтаксички легални референци кон една int
вредност. Но а е вистинска дводимензионална низа: 200 локации со int-го­
лемина се резервираат, а конвенционалната правоаголна индексна пресметка

20*row+col се користи за пронаоѓање на елементот а [row] [со!] . Меѓутоа за


b , дефиницијата резервира 10 покажувачи кои не се иницијализираат воопш­
то; иницијализирањето мора да се изведе експлицитно, било статички, било
преку код. Претпоставувајќи дека секој елемент од b покажува кон низа од 20
елементи, тогаш ќе се резервираат места за двесте int променливи, плус 10
клетки за покажувачите. Предност на низите од покажувачи , е тоа што реди­
ците од низата може да бидат со различни должини. Односно, секој елемент
од b не мора да покажува кон низа од 20 елементи; некои може да покажуваат

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

name:

illegal month\0 1

Jan\0 1

Feb\0 1

Каr\0 1

Иако оваа дискусија обработуваше случај со целобројни вредности , се­


пак, низите од покажувачи најчеста употреба наоtаат во случаите каде што е
потребно складирање на стрингови со различни должини, како во функцијата
month_ name. Споредете ја декларацијата и сликата за низа од покажувачи:

char *name[] = { "Illegalmonth", "Jan" "Feb" "Mar"};


1 1

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

charname[] [15] ={ "Illegalmonth" "Jan" "Feb"


1 1 1 "Маr " };
134 Покажувачи и ни з и Гл а ва 5

aname :
1~llegil montБ\O Jan\0 Feb\0 Маr\0
о 15 30 45

Вежба 5-9. Повторно напишете ги рутините day_of_year и month_day да ко­


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

5.1 О Арrументи од командна линија

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


аргументи од командна линија , при за п оч нување со нејзиното извршу вање.
Кога се повикува рути н ата main , тоа се прави со д ва ар гуме нта . Првиот (по
конвенција наречен arqc , е кратенка од arqument count) , е бројот на аргу­
ме нти со кои е повикана програмата; вториот (argv , од arqument vector) ,
е по кажувач ко н н иза од стри нгови, која ги сод ржи ар гументите , п о еде н аргу­
мент за секој стринг. Ние по об ичај користиме повеќе ни воа на покажу вачи за
да ма нип улираме со такви те с трингови.

Н аједно ста вн ата илустрациј а за ова е програмата echo , која ги печати соп­
ствените аргуме нти од кома ндн а л и ниј а во една текстуална л инија , одделени
меѓу себе со п разни места. Така , командата

echo hello, world

печати

hello , world

По ко нвенц ија, arqv[O] е името со кое се повикува п рограмата , што значи


дека мини малната вредн ост што може да ја има argc е 1 . Ако argc има вред­
но ст 1, тоа зна ч и дека после името н а програмата нема никакви други ар гуме н­

ти н а команд на линија. Во примерот п огоре ,argc има вредност з, а arqv [ о ] ,


argv[l], иargv[2]ce" echo", " hello", и"wоrld" соодветно. Првиот опци­
онале н аргумент е argv[l] , додека последни от е argv[argc-1] ; додатно на
ов а , ста нда рдот дефинира дека argv[argc] е NULL покажувач.
5.10 Аргументи од ко ма ндна ли нија 135

Првата верзија на echo ја третира argv како ни за од знако вни покажу ва чи:

#include <stdio . h>


/* echo арrуненти од хонандна линија ; 1 - ва верзија */
main(int argc , char *argv[])
{
int i ;
for (i = 1 ; i < argc ; i++)
printf ( "% ѕ % ѕ", argv[i ], (i < argc-1 ) ? "") ;
printf( " \n " ) ;
return О ;

Бидејќи argv е покажувач кон низа од покажувачи , наместо со индексите на


низата , можеме да манипулираме со самиот покажувач ко н неа . Следн ата ва ­
ријанта е базирана н а зголемување на argv, кој е покажувач кон char , додека
argc одбројува наназад:

#include <stdio . h>


/* echo арrуненти од хонандна пинија ; 2-ра верзи ја */
main(int argc , char *argv[])
{
while (--argc > 0)
printf( "%s %s ", *++argv, (argc > 1) ? "" ) ;
printf( " \n") ;
return О ;

Бидејќи argv е покажувач кон почетокот на низата од аргуме нтните стрингови ,


неговото инкрементирање (++argv) го по ставу ва да п окажува ко н прв ич ниот

argv[1] наместо argv[O]. Секое п оследователно инкрементирање го поме с­


тува на следниот аргумент; *argv е пока жува ч до тој ар гу ме нт . Во исто вре­
ме , argc се декреме нтира ; кога ќе дојде до нул а, н ема повеќе а ргументи за
печатење. Алтернативно, можеме да напишеме printf наредба на следниов
начин

printf ( (argc > 1) ? "% ѕ " : "% ѕ ", *++argv) ;

Ова покажува дека форматирачкиот аргумент на printf , исто така, може да


биде израз. Како втор пример , ќе н а правиме некои подобрувања на програ ­
мата за пронаоѓање на облици, од Поглавје 4 . 1 . Ако се сеќавате, таму обл и ­
кот кој се бара го поставивме длабоко во внатрешноста на п рограмата, што е
едн о очигледно незадоволител н о ре ш е ни е. Правејќи па р ал ела со UNIX -o вa тa
програма grep, можеме да ја подобриме програмата па критериумот, кој треба
да се пронајде , ќе го наведеме како прв аргумент од командна л инија.
136 Покажувачи и низи Глава 5

linclude <stdio.h>
linclude <string . h>
ldefine МAXLINE 1000

int getline(char *line , int max);

/* find : nечатене на Ј1ИНИИ'1'8 :кси oдr'CIIaPU'1' на o6nиJcoor зададеи со 1-ИО'l' арrумеtп *1


main(int argc , char *argv[))
{
char line[МAXLINE) ;
int found = О ;
if (argc != 2)
printf( " Usage : find pattern\n" );
else
while (getline(line, МAXLINE) >О)
if (strstr(line , argv[l)) != NULL)
printf( "% s " , line);
found++ ;

return found;

Функцијата од стандардната библиотека strstr (ѕ , t) враќа покажувач кон


првата појава на стрингот t во рамките на стрингот ѕ, или NULL ако нема таква
појава . Функцијата е декларирана во <string. h> . Моделот може да се објас­
ни сега, за да може да се илустрираат понатамошните конструкции со покажу­

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


Едниот вели " печати ги сите линии освен оние кои се поклопуваат со наведе ­
ниот критериум "; другата вели " пред секоја испечатена линија да се вметне
бројот на линијата ".
Општоприфатена конвенција за С програмите на UNIX системите , е дека ар­
гумент кој почнува со знакот минус, означува опционално знаменце (анг. flag)
или параметар. Ако одбереме -х (за " исклучок") за да сигнализираме инвер­
зија и - n ("број" ) за да обезбедиме нумерирано печатење , тогаш командата

find -х -n pattern

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


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

find -nx pattern

Во продолжение следи нашето решение:


5.10 А р гум енти од ком андн а линија 137

#inelude <stdio . h>


#inelude <string.h>
#define МAXLINE 1000

int getline(ehar *line 1 int max) ;

/* find: tеоатеве на Ј1ИН5И1'8 xat одn:8С1Р8АТ на~ зададеи со 1-испо аргумент */


main(int arge 1 ehar *argv[])
{
eharline[МAXLINE];

long lineno = О;
int е 1 exeept =О 1 number =О 1 found =О ;

while (--arge >О && (*++argv) [О]= '- 1


)

while (е= *++argv [О])


switeh (е) {
еаѕе ' х ':

exeept= 1 ;
break ;
еаѕе ' n ' :

number= 1 ;
break ;
default :
printf ("find: illega1 option %e\n" , е) ;
arge =О ;
found= -1 ;
break;

if (argc != 1)
printf (" Usage : find -х - n pattern\ n " ) ;
else
while (getline(line , МAXLINE) >0) {
lineno++ ;
if ( (strstr (line 1 *argv) ! =NULL) != except) {
if (number)
printf( " %ld: " 1 lineno) ;
printf( " %s ", line) ;
found++;

return found ;

argc се декрементира , а argv се инкрементира пред секој опци о н ален а р гу­


мент . На крајот на цикл усот , ако не се направени никакви греш к и , argc оз н а ­
чува колку аргументи преостануваат да се обработат, а argv покажува кон п р-
138 Покажувачи и низи Глава 5

виот од ни в. Поради тоа argc би требало да биде 1 , а *argv да покажува кон


критериумот. Забележете дека *++argv е покажувач кон аргументен стринг,
na така ( *++argv) [о] е неговиот nрв знак. ( Алтернативна и валидн а форма
би била * *++argv.) Бидеј ќи [] врзува поцврсто отколку * и ++ , заградите се
неопходни ; без нив изразот би се nресметал како *++ (argv [о] ) . Всушност,
тоа е варијантата која ја уnотребивме во внатрешниот циклус, каде задача ни
е да " шетаме" долж конкретен аргументен стринг. Кај внатрешниот циклус ,
изразот *++argv[O] го инкрементира nокажувачот argv[O]!
Ретки се случаите во кои се среќаваат изрази со nока жувачи кои се поком ­
nлицирани од веќе спомнатите; во такви случаи, би било nоинтуитивно да се
наnрави нивно расчленување на два или три поедноставни чекори .

Вежба 5-10. Напишете програма expr, која ќе врши евалуација на обратен


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

expr234+ *

пресметува 2 * (3+4 ) .

Вежба 5-11. Модифицирајте ги nрограмите entab и detab (истите од вежби­


те во Гл ава
1) да прифаќаат листа од tab знаци како аргументи. Да се користат
основните tab поставки во случај да не се наведени никакви аргументи.

Вежба 5-12 . Обопштете ги entab и detab да ги прифаќаат

entab - m +n

што би означувало таб стап на секоја п-та колона, почнуваЈКИ од колона m.


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

Вежба 5-13 . Напишете програма tail , која ги печати последните n линии од


нејзиниот влез. Во оnшт случај,
n е поставен , да речеме, на вредност 10, но
може да биде изменет со опционален аргумент за да

tail -n

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


разлика колку се неразумн и влезните вредности за n. Напишете ја програмата
така што истата оптимално ќе го користи меморискиот простор ; линиите треба
да се складираат на начин како што тоа беше при кажано во програмата за сорти ­
рање оД-поглавје 5 . 6, не во дводимензионална низа со фиксна големина/
5.11 Покажувачи кон функции 139

5.11 Покажувачи кон функции

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


се дефинираат покажувачи кон нив, на кои може да и м се доделува вредноста,
може да се постават во низа, да се предадат како аргументи на други функ ции,
да бидат вратени од други функции , итн. Ние ќе го илустрираме тоа со моди ­
фицирање на процедурата за сортирање, напишана пора н о во рамките на оваа
глава, така што за зададен опционалниот аргумент -n, сортирањето на влез­

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


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

Лексикографското споредување на две линии, како и порано , се прави со


strcmp; исто така, ќе ни биде потребна и рутина nшncmp , која споредува две ли­
нии врз основа на нивната нумеричка вредност и ќе враќа исти резултати , како и
strcmp. Овие функции се декларирани пред main , а покажувач кон соодветната
функција ќе се предава како аргумент на qsort. Во случајов го занемаривме валиди­
рањето на аргументите , за да можеме да се концентрираме на главните прашања .

#include <stdio. h>


#include <string. h>

#define МAXLINES 5000 /* максимален број на линии за сортираље */


char*lineptr[МAXLINES] ;/*nокажувачи кон текстуапни линии* /

int readlines (char *lineptr [] , int nlines) ;


voidwritelines(char*lineptr[] , intnlines) ;

void qsort (void *lineptr [] , int left , int right ,


int (*comp) (void * , void *)) ;
in t numcmp (char * , char *) ;

1*сортира влезни линии */


main ( int argc, char * argv [] )
{
intnlines ;/ *бpoj на nрочитани линии од влез *1
int numeric =О; 1* 1 за нумеричко сортираље */

if (argc>l&&strcmp(argv[l] , " -n" ) ==О)

numeric= 1 ;
140 Покажувачи и низи Глава 5

if ( (nlines = readlines (lineptr, МAXLINES)) >=О)


qsort ( (void**) lineptr, О, nlines-1 ,
(int (*) (void* ,void*)) (numeric? numcmp: strcmp));
writelines(lineptr, nlines) ;
return О ;
} else {
printf ("input toobig to sort\n" ) ;
return 1 ;

Кај повикот во qsort, strcmp и numcmp се адреси на функции. Бидеј ќи за нив


е позната дека се функции, операторот & не е потребен, на ист начин како што
не е потребен и кај низите. Ја напишавме qsort така што ќе може да обрабо­
тува каков било податочен тип, а не само стрингови . Како што е декларирано
со функцискиот прототип, qsort очекува низа од покажувачи , два цели броја
и функција со два аргумента кои се покажувачи . За аргументите се користи
генеричкиот покажувач од тип void* . Кој било тип на покажувач може да биде
претопен во тип void * и потоа да биде претопен наназад без да има загуба
на информација , па така ќе ја повикаме qsort се претопување на аргументите
во void* тип. Детаљното претопување на функциските аргументи ги прето­
пува аргументите на функцијата за споредба. Во општ случај тоа нема никакво
влијание на актуелната репрезентација , но ја уверува компајлерот дека се е во
најдобар ред .

/* qsort: сортираае на v[left] . . . v[right] во растечки редоспед */


void qsort(void *v[] , int left, int right,
int (*comp) (void * , void *))

int i, 1ast;
void swap(void *v[], int , int) ;

if (left >= right) /*не~ нt11ro iDD _ . , _ Ј88 n::I81IICY QЦ дN 8111НН1а */


return;
swap(v , left, (left + right)/2);
last = left;
for (i = left+1 ; i <= right ; i++)
i f ( (*comp) (v[i], v[left]) < О)
swap(v, ++last, i);
swap(v, left, last);
qsort(v , left, last-1, comp) ;
qsort(v , last+1 , right , comp) ;

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


параметар на qsort е
5.11 Покажувачи кон функции 141

int (*comp) (void *, void *)

кој означува дека comp е покажувач кон функција која има два void* аргумента
и враќа int. Употребувањето на comp во линијата

i f ( (*comp) (v[i], v[left]) <О)

е конзистентно со декларацијата : comp е покажувач кон функција , *comp е са­


мата функција, и

(*comp) (v[i] , v[left])

е повикот до неа . Заградите се потребни заради правилно асоцирање на ком­


понентите ; без нив

int *comp(void * void *) /* ПОГРЕШНО */

означува дека comp е функција која враќа покажувач кон int вредност, што со­
сема ја менува смислата . Веќе ја покажавме strcmp, која споредува два стрин­
га . Ќе ја претставиме и nwncшp , која споредува два стринга по првата број на
вредност, која се добива преку повик до atof :

ftinclude <stdlib. h>

/* nwncшp: нумеричка споредба на sl и ѕ2 */


int nwncшp (char *sl, char *ѕ2)
{
double vl , v2 ;

vl = atof (sl) ;
v2 = atof (ѕ2) ;
if (vl < v2)
return -1;
else if (vl > v2)
return 1;
else
return О ;

Функцијата swap , која разменува два покажувача, е идентична на она што


веќе го презентиравме во оваа глава, со исклучок на тоа што декларациите се
променети во void * .
142 Покажувачи и низи Глава 5

void swap (void *v [] , int i , int ј ;)


{
void *temp;

temp=v[i] ;
v[i] =v[j];
v[j] = temp ;

Цела низа од различни опции може да бидат додадени на програма за сор­


тирање; некои од нив претставуваат вежби за предизвик,

Вежба 5-14. Модифицирајте ја програмата за сортирање да се справува со


знаменце -r , што индицира сортирање во обратен (опаѓачки) редослед.
Обезбедете -r да функционира во комбинација со - n.

Вежба 5-15 . Додајте ја опцијата - f која ја игнорира разликата помеѓу мали и


големи букви; на пример, а и А при споредба се еднакви .

Вежба 5-16. Додајте опција -d ( "директориумски редослед" ), која прави


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

Вежба 5-17 . Додајте можност за обработка на пол е , за да може сортирање


да се изврши на полиња во линиите, при што секое поле се сортира според не ­

зависно множество на опции . (Индексот на оваа книга беше сортиран со -df


параметар за инде ксната категорија и - n за страните)

5.12 Компnицирани декnарации

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


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

та употреба на заградите . Разликата помеѓу

int *f () ; 1* f : фунхција хоја ара:ќа поха.жувач хон int */

int (*pf) () ;/*pf: похажувач хон фунхција хоја вра:ќа int */


5.12 Ком плициранидекл ара ции 143

само го илустрира тој пр облем: * е префи ксе н оператор и има п онизок пр ио­
ритет од () , п а заградите се потребни за да се обезбеди правилна асоција ција.
Иако, навистина, комплицираните деклара ции мошне ретко се појавуваат
во практика, потребно е да знаете како истите да ги разберете , а ако е потреб­
но тоа, и да ги напиш ете . Добар начин е декларациите да се синтетизираат во
мали чекори со употреба на typedef , за која ќе зборуваме во Поглавје 6. 7 .
Ка ко алтернатива, во овој дел ќе презентираме п ар програми кои прават пре ­
творање од е во псевдокод и обратно . Псевдокодот се чита од лево кон десно .
Првата , dcl, е посложена. Врши претворање на е декларација во псевдо ­
код , како во наведе ните пример и :

char **arqv
arqv : пока жувач ко н п о к ажувач кон char
int (*daytab) [13]
daytab: покажувач ко н низа [ 13] од int
int *daytab[13]
daytab: ни за [13] од покажувачи кон int
void *comp ()
comp : функција која враќа покажу вач ко н void
void (*comp) ()
comp: покажувач кон функција која враќа void
char (* (*х()) [ )) ()
х : функција која враќа покажувач ко н н иза [] од покажувач и кон
функција која враќа char
char (* (*х[3]) ()) [5]
х : низа [3) од покажувач кон функција која враќа покажувач
кон низа [5] од char

dcl се бази ра на граматика која наведува декларатор, компл етн о нав еден
во Додаток А, Поглавј е 8 .5; ова е неговата п оеднос таве на форма

dcl: опционал *ѕ direct-dcl


direct-dcl: name
(dc/)
direct-dcl()
direct-dcl{onцucкa големина]

Кажано со збо рови, деклараторот dcl е direct- dcl на кој може да му п ретходат
некол ку * . Direct- dcl , од друга страна , може да бид е име,
dcl во мали за град и,
direct-dcl следен од мали загради, или direct-dcl следен од средн и загради со
о пцис ка величи н а.

Оваа граматика може да се искористи за парсирање на декларации. На при­


ме р, да го разгледаме деклараторот
144 Покажувачи и низи Глава 5

(*pfa[]) ()

( • pta о ) ()

name

dir-dcl
1

dir-dcl
1

dcl
1

dir-dcl
1
dir-dcl
1
dcl

pfa ќе биде идентификувана како име и поради тоа како direct-dcl . Потоа pfa [ Ј ,
исто така, е direct-dcl. Пoтoa, *pfa [] се препознава како dcl, па (*pfa []) како
direct-dcl. Понатаму, (*pfa []) () е direct-dcl, па со тоа и dcl. Парсирањето може
да го илустрираме со парсирачко дрва како ова (каде direct-dcl е кратко запи­
шано како dir-dc[) :
Срцето на dcl програмата се пар функции , dcl и dir- dcl , ко и парси раат
декларација врз основа на оваа граматика . Бидејќи граматиката е дефинирана
рекурзивно , функциите, како што ги препознаваат деловите на декларација­
та, рекузривно се повикуваат меѓу себе ; програмата е наречена рекурзи в но­
с пуштачки парсер .

1* dcl: nарсира дехпаратор*/


void dcl (void)
{
int ns ;

for (ns=O ; qettoken() = ' * '; ) / *count*'s*/


ns++ ;
dirdcl() ;
while (ns-- >О)
strcat(out , " pointer to " ) ;

/ * dirdcl : nарсира дирехтен дехпаратор */


5012 Комплицирани декларации 145

void dirdcl (void)


{
int type;

if (tokentype=' ( ') { / * ( dcl ) */


dcl ();
if(tokentype!= ' ) ' )
printf ("error : missing) \n");
else if(tokentype==NAМE) 1* име на nромеНЈIИВа */
strcpy (name, token) ;
else
printf( " error : expectednameor (dcl)\n") ;
while((type=gettoken())==PARENSI ltype==BRACКETS)
if (type==PARENS}
strcat(out, "functionreturning" ) ;
else{
strcat(out ,"array") ;
strcat(out, token} ;
strcat(out , "of" );

Бидејќи програмите овде се наменети да бидат илустративни, а не беспре­


корни, воведовме значителни рестрикции во функцијата dcl o Таа може да се
справува само со едноставни податочни типови како char или int , но не и со

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


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

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

#include <stdiooh>
#include <string oh>
#include <ctype oh>

#define МАХТОКЕN 100

enum NАМЕ, PARENS , ВRАСКЕТЅ };

void dcl (void} ;


void dirdcl(void) ;

int gettoken(void) ;
146 Покажувачи и низи Глава 5

int tokentype ; /* тип на последниот белег */


char token[МAXTOКEN]; /*последниот белег во низата */
char name[МAXTOКEN] ; / * име на идентификаторот */
char datatype[МAXТOКEN] ; / * data type = char , int , итн . */
char out[lOOO]; /*излезен стринг*/
main() /* претвораље на декларацијата во псевдокод * /
{
while (gettoken() != EOF) { /* првиот белег во
линијата е типот на подат окот */
strcpy(datatype , token) ;
out[O] = '\0';
dcl() ; /* го парсира останатиот дел на линијата * /
if (tokentype != '\n')
printf( " syntax error\n" ) ;
printf( "%s : % ѕ %s\n", name , out, datatype) ;

return О;

Функцијата gettoken ги прескокнува п разните места и табовите , потоа го


пронаоѓа следниот белег кој доаѓа на влез ; " белег " е име , nap на мали загра­
ди , nap на сред ни загради кои можеби вклучуваат и број , или кој било друг
единичен знак .

int gettoken(void) /* врати го следниот белег */

int е , getch (void) ;


void ungetch (int) ;
char *р = token ;

while ( (c=getch() ј == ' ' 11 е= '\t ')

if(c== '('){
if ( (c=getch()) = ' ) ' )
strcpy (token , " () " ) ;
return tokentype = PARENS ;
} else {
ungetch(c) ;
return tokentype = ' (' ;
} elseif (е= ' [ ' ) {
for (*р++=с ; (*p++=getch()) != ' ] '; )

*р = '\0' ;
return tokentype = ВRАСКЕТЅ ;
} else if (isalpha (е)) {
5.12 Комплициранидекларации 147

for (*р++ =е ; isalnum (е= geteh ()) ; )


*р++ =е;

*р= '\0';
ungeteh(e) ;
return tokentype = NАМЕ ;
} else
return tokentype =е;

geteh и ungeteh ги раз гледавме во Глава 4 .


Полесно е да се оди во обратна насока, посебно ако не се грижиме за гене­
рирање на одвишни мали загради. Програмата undel претвора псевдокод од
облик "х е функција која враќа покажувач кон низа од покажувачи кон функција
која враќа ehar" , што ќе го изразиме како

x()*[]*()ehar

во
ehar ( * ( *х () ) []) ()

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


цијата gettoken . undel ги користи истите надворешни променливи како и del .
/* undel : претвораље на nеевдоход во дехларации */
main()
{
int type ;
ehar temp [МАХТОКЕN] ;

while (gettoken () ! = EOF)


strcpy(out, toke n) ;
while (( type = gettoken () ) ! = '\n ' )
if (type == PARENS 11 type = ВRАСКЕТЅ)
streat(out , token) ;
elseif (type= '*') {
sprintf (temp, " ( * % ѕ) ", out) ;
strepy(out , temp) ;
} elseif (type=NAМE) {
sprintf(temp , " %ѕ % ѕ " , token, out) ;
strepy(out , temp) ;
} else
printf ( " invalid input at %s\n", token);

return О;
148 Покажувачи и низи Глава 5

Вежба 5-18 . Направетеја dcl да биде отпорна на грешки во влезот.

Вежба 5-19. Изменетеја undcl така што нема да додава одвишни загради на
декларациите.

Вежба 5-20. Проширете ја dcl да се справува со декларации со аргументи од


функциски тип , квалификатори како const итн .
Глава 6: Структури

Структурата претставува колекција од една или повеќе најчес то разнород­


ни променливи , групирани заедно под едно име со цел да образуваат некава
логичка целина. (Во некои програмски јазици , ка ко на пример Пас кал , ст ру к­
турите се познати под името "запиС" (анг. record) . ) Структурите помагаат во
организирањето на комплицираните податоци , посебно во големи програми ,
бидејќи овозможуваат група поврзани променливи да се третираат како една
целина, а не како посебни ентитети.
Традиционален пример за структура е платниот список на вработените :
вработениот е опишан од множество на атрибути како што се име , адреса ,
матичен број, плата итн . Некои од овие атрибути можат и самите за себе да
бидат структури: името има неколку делови, исто и адресата , па дури и пла­
тата . Друг пример , покарактеристичен за С , е од полето на графиката : една
точка како целина претставува пар од координати , еден правоаголник, п а к, е

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

Главната промена направена со ANSI стандардот е во насока на дефинирање


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

6.1 Основни поими за структурите

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


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

• (4,3)

(0,0)

149
\50 Структури Глава б

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

structpoint {
intx ;
inty ;
};

Клучниот збор struct не воведува во декларацијата на структурата, која


претставува листа од декларации , оградени со големи загради. Опционално,
име наречено таг на структурата (ознака на структурата, анг, structure tag)
може да следи после клучниот збор struct (како што е point во примерот) .
Тагот именува ваков тип на структура , па како таков понатаму може да се ко­
ристи како кратенка за делот од декларацијата ограден со заградите.
Променливите именувани внатре во структурата се нарекуваат членови на струк­
турата . Членовите на структурата и нејзиниоттаг може да имаат исто име како и една
обична (која не е член на структура) променлива, без притоа да настанат конфли­
кти , бидејќи разликата меѓу нив е очигледна од самиот контекст . Уште повеќе , исти
имиња на членови може да се среќаваат во две сосема различни структури, иако
логично би било со исти имиња да се означуваат само сродни објекти.
struct декларација дефинира тип. Десната голема заграда која означува
крај на листата на членови, може да биде проследена со листа на променливи,
исто како и кај основните типови. Односно,

struct { ... } х, у, z;

с интакс ички е аналогно со

intx, у, z;

во смисла дека и двата и з раза ги декларираат х , у и z како променливи од не­

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


Декларација на структура која не е проследена со листа на променливи не
зафа ќа мемориски простор ; единствено што прави е опишување на шаблон
или форма на некаква структура. Но, ако во декларацијата се воведе и таг на
структурата , тој подоцна може да се искористи за дефинирање на инстанци од
таа структура. На пример, за погоредадената декларација на point,

structpointpt;

дефинира променлива pt која е структура од податочниот тип struct point.


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

struct pointmaxpt = { 320 , 200 } ;


6.1 Основни nоими за структурите 151

Автоматска структура може да се иницијализира, исто така, и со доделување


или со nовикување на функција која враќа структура од соодветниот тиn.
Пристаnот до nоедини членови од структурата се изведува nреку конструк­
ција од облик

име_на_структурата . име_на_ членот

Оnераторот за членство во структура " . " го nоврзува името на структурата


со имињата на нејзините членови . На пример, ако сакаме да ги исnечатиме
координатите на точката pt, би наnишале :

printf( " %d,%d", pt . x, pt.y) ;

или ако сакаме да го nресметаме нејзиното растојание од координатниот nоче­


ток (О, 0):

double dist, sqrt (double) ;

dist =sqrt ( (double)pt. х * pt . x + (double)pt . у * pt. у);

Структурите може да се вгнездуваат. Тоа ќе го илустрираме nреку дефини­


ција на структура правоаголник, кој ќе го дефиниран како nap од две, спро ­
тивни по дијагонала, точки

pt2

ptl
--~~----------------~Х

struct rect {
struct point ptl;
struct point pt2 ;
};

Структурата rect содржи две point структури. Ако screen го декларираме како

struct rect screen ;


152 Структури Глава б

тогаш конструкцијата screen. ptl . х се однесува на координатата х 1 од nро­


менливата ptl 1 која е членка на
screen.

6.2 Структури и функции

Единствените легални оnерации nоврзани со структурите, се нивно коnи­


р а ње или доделување како целина 1 земање на нивната адреса nреку оnера­

торот & и nристаnувањето до нивните членови. Коnирањето и доделувањето


вклучува и нивно nредавање како аргументи на функции и можност за нивно
враќање како функциска вредност. Структури не може да се сnоредуваат .
Структура може да се иницијализира со л иста од константни вредности за чле­
новите; автоматска структура може да се иницијализира и со доделување .
ќе ги разгледаме структурите со nишување на функции за маниnулација со
точки и nравоаголници. Постојат најмалку три можни nристаnи : nоединечно
nредавање на комnонентите 1 nредавање на целата структура 1 или nредавање

на nокажувач кој nокажува кон неа. Секој од нив има nредности и мани .
Првата функција makepoint 1 на влез nрима два целобројни аргументаl а
како функциска вредност враќа point структура:

/* makepoint: креирај точка од зададеки х и у компоненти */


struct point makepoint(int Х1 int у)
{
struct point temp ;

temp . x = х ;
temp.y = у ;
return temp;

Забележете дека нема конфликт nомеѓу имињата на аргументите и членови­


те на структурата со исто име ; навистина 1 таквата нотација само ја nотенцира
нивната тесна nоврз аност.

Сега makepoint може да се користи за динамичка иницијализација на која


било структура или, nак, да обезбеди аргументи од тиn структура за некоја
функција :

struct rect screen ;


struct point mi.ddle ;
structpointmakepoint(int 1 int) ;

screen. ptl = makepoint (О О) ; 1

screen .pt2 =makepoint(XМAX 1 УМАХ);


middle = makepoint ( (screen. ptl. х + screen . pt2. х) / 2 1
( screen. ptl . у + screen. pt2 . у) / 2) ;
6.2 Структури и функции 153

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


На пример ,

/* addpoints: собираае на точки */


struct addpoint (struct point pl, struct point р2)
{
pl.x +=р2 .х ;
pl . у += р2 . у ;
returnpl ;

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


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

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


Како друг пример, функцијата ptinrect, тестира дали една точка се наоѓа
во внатрешноста на еден правоаголник, со усвоена конвенција дека правоа­
голникот ги вклучува неговата лева и долна страна но не и горната и десната.

1* ptinrect:врати еден &Jto р nриnаѓа на r , во CIIpO'l'ИJIHO О *1


int ptinrect(struct point р, struct rect r)

return р.х >= r.ptl.x && р.х < r.pt2.x


&& р.у >= r.ptl.y && р.у < r.pt2 . y ;

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


при што координатите на точката ptl се помали од координатите на pt2 .
Следната функција враќа правоаголник за кој гарантира дека е во договорената
(каноничната) форма :

ldefine min(a, b) ((а) < (b) ? (а) (b))


ldefine max(a, b) ((а) > (b) ? (а) (b))

/* canonrect: ханонизирај rи хоординатите на еден


прсuаоаrопних *1
struct rect canonrect(struct rect r)
{
struct rect temp ;

temp.ptl.x = min(r.ptl . x , r . pt2.x) ;


teшp.ptl.y = min(r.ptl . y, r.pt2.y) ;
teшp.pt2.x = max(r.ptl.x, r.pt2 . x);
teшp.pt2.y = max(r.ptl.y , r.pt2.y);
return temp ;
154 Структури Глава б

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


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

structpoint *рр ;

означува дека рр е покажувач кон структура од тип struct point. Ако рр по­
кажува кон point структура, *рр е самата таа структура, а (*рр) . х и (*рр) . у
се нејзините членови . За да ја користиме рр , б и можеле да напишеме, на при­
мер,

structpoint origin , *рр ;

рр= &origin ;

printf( "originis (%d ,%d) \n", (*рр) .х, (*рр) . у) ;

Употребата на заградите кај (*рр).х е задолжителна поради повисокиот приори­


тет на операторот за пристап . од операторот за дереференцирање *. Изразот
*РР . х означува * (рр. х) , кој во овој случај е невалиден бидејќи х не е пока­
жувач.

Покажувачите кон структури се користат толку често што за нив е обезбедена


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

р- > член_на_структура

референцира до некој конкретен член. (Операторот -+: е минус проследен со


знак>). Така , наместо погорното би можеле да запишеме

printf ("origin is (%d ,%d) \n", рр ->х , рр->у) ;

И . и -> асоцираат од лево кон дес но , па ако имаме

struct rect r , *rp = &r ;

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

r.ptl.x
rp->ptl.x
(r .ptl) . х
(rp->ptl) .х

Структурните оператори . и -> , заедно со о ператорите за функциски повик


() и индексирање [] , се на врват од х иерархијата на приоритети , п а со тоа и
нивн ото врзување е најцврсто. На пример , ако е дадена декларацијата
6.3 Низи од структури 155

struct {
int len ;
char *str ;
} *р ;

Тогаш

++p->len

го инкрементира len, а не р, бидејќи соодветната претстава со за град и е


++(р-> lеп). За да го смениме редоследот на врзувањето можеме да користиме
мали загради: (++р) ->len го инкрементира р , пред да се пристапи кон len,
додека (р++) ->len го инкрементира р после пристапот до len. (Користењето
на заградите во последниот случај е непотребно. )
На ист начин, *p->str пристапува до вредноста кон која покажува str ;
*p->str++ го зголемува str откако ќе пристапи до вредноста кон која покажу­
ва str (исто како *ѕ++) ; (*p->str) ++ја зголемува вредноста кон која пока­
жува str ; а *p++->str го зголемува р , после пристапот до вредноста кон која
покажува str .

6.3 Низи од структури

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


клучен збор од јазикот С. Ни треба низа од стрингови за да ги чуваме зборови­
те и низа од цели броеви за чување на изброените појавувања. Една варијанта
е да користиме две паралелни низи, keyword и keycount , како во случајов:

char *keyword[NКEYS];
int keycount[NКEYS];

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


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

char *word;
intcount ;

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


structkey {
char *word;
int count;
} keytab [NКЕУЅ] ;
156 Структури Глава б

декларира структурен тип key, дефинира низа keytab како низа од структури
од тој тип и резервира мемориски простор за нив. Секој елемент од низата е
структура . Ова, исто така, може да се запише и како

structkey {
char *word;
int count ;
};

ѕ truct key keytab [NКЕУЅ] ;

Бидејќи структурата keytab содржи константно множество на имиња,


најлесно е да се декларира како надворешна променлива и да се иницијализи­
ра еднаш засекогаш во моментот на нејзиното дефинирање. Таа иницијализа­
ција е аналогна на претходните- дефиницијата се проследува со листа на ини­
цијализатори оградени со големи загради .

structkey {
char *word;
intcount;
} keytab [ ] = {
"auto", О ,
" break", О,
" саѕе ", О,
"char", О,
"const", О,
" continue", О,
" defaul t", О ,
1* ... *1
"unsiqned", О,

"void", О,
" volatile", О,

"while", О
};

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


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

{ " auto", О } ,
{ "break", О } ,
{ " саѕе ", О } ,
6.3 Низи од структури 157

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


присутни и кога тие претставуваат едноставни променливи или стрингови . Ка ко
и обично, бројот на записи во низата keytab ќе биде автоматски одреден ако има
присуство на иницијализатори и ако средните загради [] се оставени за п разни .
#inc1ude <stdio . h>
#inc1ude <ctype . h>
#inc1ude <string.h>

Jdefine МAXWORD 100

int getword(char * , int) ;


int binsearch(char * , struct key * , int) ;

/* пребројуваае на хпучни зборови од јазикот С*/


main ()
{
int n ;
char word[МAXWORD] ;

whi1e (getword(word, МAXWORD) != EOF)


if (isa1pha(word[O]))
if ((n= binsearch(word, keytab , NКЕУЅ)) >=О)
keytab[n] .count++ ;
for (n = О ; n < NКЕУЅ ; n++)
if (keytab [n] . count > 0)
printf (" %4d %s\n",
keytab[n] .count , keytab[n] .word);
return О ;

/* binsearch: прокаоrа nојавуѕав.е на word во табепа tab[O] ... tab[n-1] */


int binsearch(char *word, struct key tab[] , int n)

int cond ;
int 1ow, high , mid;
1ow = О ;
high = n - 1 ;
whi1e (1ow <= high) {
mid = (1ow+high) 1 2 ;
if ((cond = strcmp(word, tab[mid] .word)) <О)
high = mid - 1 ;
е1ѕе if (cond > О)
1ow = mid + 1 ;
е1ѕе
return mid ;

return -1 ;
158 Структури Глава б

Програмата за броење клучни зборови започнува со дефинирање на ни­


зата keytab о Читањето на влезни податоци се врши во рутината main, преку
постојано повикување на функцијата qetword, која чита збор по збор о Секој
збор се пребарува во keytab со помош на верзијата на функцијата за би нарно
пребарување која ја напишавме во Глава з о Листата на клучни зборови во табе­
лата мора да биде сортирана во растечки редослед о
Набрзо, ќе ја при кажеме функцијата qetword ; за сега ќе биде доволн о да ка­
жеме дека секој повик кон qetword резултира со збор, кој се копира во низата
именувана како нејзин прв аргумент о
Вредноста NКЕУЅ е бројот на клучни зборови во keytab о Иако броењето
би можеле да го изведеме и на рака , сепак, многу е полесно и посиrурно таа
работа да ја препуштиме на една машина, посебно ако листата е подложна на
промени о Една можност е да ја терминираме (завршиме, го означиме крајот
на) листата на иницијализатори со нулти покажувач, а потоа да ј а изминуваме
keytab се до нејзиниот крај о
Но тоа е губење на време , бидејќи големината на низата целосно се пре­
сметува за време на компајлирањето о Големината на низата е определена со
големината на еден запис, помножен со бројот на записи , п а бројот на за писи
ќе го најдеме како однос од

големина на (анго size of) keytab 1 големина на strиct key

С обезбедува унарен оператор наречен sizeof кој се користи за определу­


вање на големината на каков било објект и функционира за време на компајли­
рање о Изразите

sizeof објект
и

sizeof (mиn име)

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


тип , во бајти о (Строго речено, sizeof враќа неозначена целобројна вредност
чиј тип, size_ t е дефиниран во заглавјето <stddef о h> о) Објект може да биде
променлива, низа или структура о Име на тип може да биде името на некој ос­
новен тип како што се int или double , или , пак, некој изведен тип како што се
структурите и покажувачите о

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


низата поделена со големината на еден елемент о Оваа пресметка се користи во
#define директива за да се постави вредноста на NКЕУЅ :

tdefine NКЕУЅ (sizeof keytab 1 sizeof (struct key))


6.3 Низи од структури 159

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


ната на еден конкретен елемент:

#define NКЕУЅ ( sizeof keytab 1 sizeof (keytab [О] ) )

Предноста на ова запишување е тоа што не е потребна негова промена во


случај на промена на податочниот тип.
sizeof не може да се користи во рамките на /fif директива, бидејќи пре­
тпроцесорот не ги парсира имињата на типовите. Но кај /fdefine, изразот не
се евалуира од страна на претпроцесорот, па споменатиот код во случајов е
сосема легален.

Да се навратиме на функцијата
getword . Напишавме една поопшта варијан­
та на функцијата
getword, отколку што тоа го бара програмата , но не е ком­
плицирана. getword го прифаќа следниот "збор " кој доаѓа на влез , при што
за збор се смета стринг од букви и цифри кој започнува со буква, или еден знак
кој не е празно место . Вредноста на функцијата е првиот знак од зборот или
EOF за крај на датотека или самиот знак во случај кога тој не е буква.

/* getword: земи го следниот збор или знах од впезот */


int getword (ehar *word, int lim)
{
int е , geteh (void) ;
void ungeteh (int) ;
ehar *w = word;

while (isspaee (е= geteh ()))

if (е !=EOF)
*w++= е;
if (! isalpha (е))
*w= '\0' ;
return е ;

for ( ; --lim>O;w++)
if (! isalnum(*w = geteh ()))
ungeteh(*w);
break;

*w = '\0' ;
return word [О] ;

getword ги користи функциите geteh и ungeteh кои ги напишавме во Глава4.


Кога ќе престане собирањето на алфанумерички знаци, getword веќе има про-
160 Структури Глава б

читан о еден знак повеќе. Повикот кон unqetch тој знак го враќа на влез каде
што ќе го очекува следниот повик. Getword, исто така, ги користи isspace
за прескокнување на п разните места, isalpha за идентификација на буквите
и isalnum за идентификација на бројки и букви; сите тие се функции од стан­
дардното заглавје <ctype. h>

Вежба 6-1 . Нашата верзија на qetword не се справува правилно со знакот за


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

тролни линии . Напишете подобра верзија .

6.4 Покажувачи кон структури

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


структури, да ја напишеме повторно програмата за пребројување на клуч­
ни зборови од јазикот С , но овој пат преку покажувачи наместо преку инде­
кси на низи . Надворешната декларација на keytab не се менува, но main и
binsearch имаат потреба од модификации .

Jinclude <stdio.h>
linclude <ctype.h>
linclude <strinq.h>
Jdefine МAXWORD 100

int qetword(char *, int);


struct key *binsearch(char *, struct key *, int) ;

/* преброј~а.е на ЈСЛУЧНИ зборови од јuижот С ; аерзија со по~чи */


main()
{
char word[МAXWORD];
struct key *р ;

while (qetword(word, МAXWORD) != EOF)


if (isalpha(word[O]))
if ((p=binsearch(word, keytab, NКЕУЅ)) != NULL)
p->count++;
for (р = keytab ; р < keytab + NКЕУЅ ; р++)
if (p->count > 0)
printf( " %4d %s\n" , p - >count, p->word);
return 0;
6.4 Покажувачи кон структури 16 1

/* binsearch: nронајди nojaa}'8a&e на збор во tab[O] . .. tab[n-1] */


struct key *binsearch (char *word , struck key *tab , int n)
{
int cond ;
struct key *1ow = &tab [О] ;
struct key *high = &tab [n] ;
struct key *mi.d;

while (low < high)


mi.d = 1ow + (high-low) 1 2 ;
if ((cond=strcmp(word , mi.d->word)) <О)
high =mi.d ;
е1ѕе if (cond >О)

low=mi.d+ 1 ;
else
return mi.d ;

return NULL ;

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


binsearch мора да индицира дека наместо цел број , истата ќе вра ќа покажувач
кон struct key ; тоа е наведено и во функцискиот прототип и во binsearch .
Ако binsearch го пронајде бараниот збор, таа враќа покажувач кон него ; за
неуспешно пребарување, враќа NULL.
Второ, кон елементите на keytab сега се пристапува преку покажувачи .
Ова бара значителни промени во binsearch .
Иницијализаторите за 1ow и high сега се покажувачи кон почетокот и непо­
средно после крајот на табелата. Пресметката на средишниот елемент овој пат
н е може да се направи со

mid = (low+high) 1 2 /* ПОГРЕШНО */

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


па така ако high- low е бројот на елементите , тогаш

mid=low+ (high- low) / 2

го поставува mi.d на средишниот елемент помеѓу low и high .


Најзначајната промена што треба да се направи, е да се приспособи алго­
ритамот да не генерира невалиден покажувач и да не се обидува да пристапи
кон елемент кој е надвор од низата . Проблемот е дека &tab [-1] и &tab [n) се
надвор од границите на низата tab . Првиот случај е строго невалиден , доде ­
ка вториот е нелегално да се дереференцира. Сепак , дефиницијата на јази кот
гарантира правилно функционирање н а адресната аритметика, дури и кога таа
162 Структури Глава б

го вклучува првиот елемент после крајот на една низа (Т. е. &tab [n])
Во main запишавме :

for (р = keytab; р < keytab + NКЕУЅ; р++)


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

мува р со точната вредност потребна да го намести на следниот елемент на ни­


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

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


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

struct {
char е;
inti ;
};

може да случи да бара осум бајти, а не пет . Операторот sizeof ја враќа точ­
ната големина .
На крај, да фрлиме поглед на форматот на програмата : кога една функција
враќа комплициран податочен тип како што е покажувач кон структура, како во

struct key *binsearch (char *word , struct key *tab, int n}

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


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

structkey *
binsearch (char *word , struct key *tab , int n}

Сето тоа, се разбира, е прашање на вкус; одберете ја посакуваната форма и


понатаму држите до неа .

6.5 Самореференцирачки структури

Да претпоставиме дека сакаме да се справиме со поопштиот проблем на


броењето на појавувањата на сите зборови кои доаѓаат на влез . Бидејќи лис­
тата на зборовите не е однапред позната, н е сме во можност соодветно да ја
сортираме и пребаруваме . И не можеме да изведуваме линеарно пребарување
за секој пристигнат збор, да видиме дали веќе бил прифатен; времетраењето
6.5 Самореференцирачки структури 163

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


вање, најверојатно, ќе расте квадратно во однос на бројот на зборовите. ) Како
треба да се организираат податоците за да се справиме ефикасно со произвол­
на листа на зборови?
Едно решение е множеството на пристигнатите зборови да се сортира
постојано, ставај ќи го секој збор на неговата точна позиција по редоследот во
кој пристигнува . Ниту ова не треба да се изведе преку поместување на зборо ­
ви те во линеарна низа - и тоа одзема премногу време. Наместо тоа ќе корис­
тиме податочна структура наречена бинарно дрва.
Дрвото содржи по еден "јазол " (едно теме) за секој различен збор ; секој
јазол содржи

- Покажувач кон текстот на зборот,


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

Ниту еден јазол не смее да има повеќе од две деца ; може да има едно или
без деца.
Јазлите се организирани така што за кој било јазол, неговото лево поддрво
содржи зборови кои се лексички помал и од неговиот, додека десното поддрво
содржи само поголеми. Даден е приказ на дрвото добиено од реченицата" now
is the t ime for all good men to come to the aid of their party".

now

18
/"-the
/"-
for men of/ """ time
all/
/""-
a.id come
"""
good "
pa.rty / """
their to

За да откриеме дали еден нов збор веќе се наоѓа во дрвото, за почнуваме со


коренот и го сnоредуваме новиот збор со зборот кој се чува во тој ја зол. Ако се
совпаѓаат, прашањето се одговара nотврдно . Доколку новиот збор е помал ,
го продолжуваме барањето кај левото дете на тековниот јазол , во спротивно се
префрламе на десното. Ако нема деца во бараната насока, новиот збор не е во
дрвото, а најденото празно место, всушност, е и најсоодветното место за смес­
тување на новиот збор . Овој процес е рекурзивен , бидеј ќи пребарувањето од
164 Структури Глава б

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


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

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


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

struct tnode { /* јазол на дрвото */


char *word; /* nохажувач хон техст */
int count ; /* број на појавувааа */
struct tnode *left; /* лево дете */
struct tnode *right ; /* десно дете */
};

Рекурзивната декларација на јазолот може да ви изгледа необична, но е ко­


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

struct node *left;

ја декларира променливата left како покажувач кон tnode, а не самата како


структура tnode .
Понекогаш е потребна варијација кај себереференцирачките (себеповику­
вачките ) структури : две структури кои покажуваат една кон друга. Еден начин
тоа да се изведе б и бил:
struct t {

struct ѕ *р ; /* р похажува хон ѕ */


};
struct ѕ {

struct t *q; /* q похажув хон t */


};

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


готовите функции како getword, кои ги напишавме претходно. Рутината main
вчитува зборови со помош на getword и ги сместува во дрвото со помош на
addtree .

linclude <stdio. h>


iinclude <ctype. h>
iinclude <string. h>

idefine МAXWORD 100


struct tnode *addtree (struct tnode * , char *) ;
void treeprint (struct tnode *);
intgetword(char *, int);
6.5 Самореференцирачки структури 165

/* броеае на појавуаааа на апезни зборови */


main ()
{
struct tnode *root ;
char word[МAXWORD] ;

root=NULL;
while (getword(word, МAXWORD) != EOF)
i f (isalpha(word[O]))
root = addtree (root , word);
treeprint(root) ;
return О;

Функцијата addtree е рекурзивна. main презентира збор на јазолот од нај­


високото ниво ( коренот) на дрвото . Во секоја фаза , тој збор се споредува со
зборот кој се чува во тековно посетениот јазол, и во зависност од резултатот на
споредбата се врши посетување на левото или десното поддрво со рекурзивен
повик до функцијата addtree. Евентуално, зборот се совпаѓа со некој од збо­
ровите кои веќе се наоѓаат во дрвото (nри што се инкрементира соодветниот
бројач) , или се доаѓа до нулти покажувач , што индицира дека за новиот збор
треба да се креира нов јазол и истиот да се додаде во дрвото. Во случај да се
креира нов јазол, addtree враќа покажувач кон него кој се вметнува во роди­
телскиот јазол .

struct tnode *talloc (void) ;


char *strdup (char *) ;

1* add tree :
.цо,ца ј ј азоп w, на нпи по.ц р * 1
struct treenode *addtree (struct tnode *р, char *w)
{
int cond;

if (р =NULL) { / *нов прис'l'ИrНат збор • /


= talloc () ; 1* хреирај нов јазоп */
р
p->word = strdup (w) ;
p->count = 1 ;
p->left .. p->right = NULL;
} else if ( (cond= strcmp(w, p->word)) = 0)
p->count++; 1• повторен збор • /
else if (cond< О) / * ахо е пotuUI , прати во пево по.цдрао */
=
p->left addtree (p->left , w) ;
else / *axo е погопем , прати во ,цесно по.ц.црво*/
p->right =addtree (p->right , w) ;
returnp;
166 Структури Глава б

Меморијата за новиот ја зол се резервира со помош на рутината talloc,


која враќа покажувач кон слободно место погодно за чување на јазол од дрво­
то , а функцијата strdup го копира зборот на тоа место . (Набрзо ќе ги разгле­
даме и тие рутини . ) Бројачот се иницијализира, а двете деца се поставуваат
на вредност NULL . Овој дел од кодот се изведува единствено во листовите на
дрвото, кога се додава нов јазол. Во случајов (не баш мудро од наша страна)
ги прескокнавме проверките за грешки на вредностите добиени од функциите
strdup и talloc .
Функцијата treeprint го печати дрвото во сортиран редослед ; за секој
јазол, го печати неговото лево поддрво (сите зборови помал и од зборот во те­
ковниот јазол) , потоа самиот збор 1 а потоа и десното поддрво (сите поголеми
зборови). Ако се чувствувате несигурни во врска со тоа како функционира ре­
курзијата , симулирајте ја работата на treeprint врз дрвото кое го спомнавме
малку погоре.

/* treeprint: in-order печатеае на црво р */


void treeprint(struct tnode *р)
{
if (р != NULL) {
treeprint(p->left) ;
printf( "%4d %s\n", p->count , p->word) ;
treeprint(p->right) ;

Една практична забелешка: Ако дрвото стане " небалансирано" 1 во случај


кога зборовите не пристигаат во случаен редослед, времето на извршување на
програмата може да порасне многу . Во најлош случај 1 ако зборовите присти­
гаат веќе п одредени, оваа програма извршува прескапа операција на линеар­
но пребарување. Постојат генерализации за бинарно дрво , кои не страдаат од
наведениот проблем , но за нив овде нема да зборуваме.
Пред да го напуштиме овој пример, ќе направиме кратка дигресија на еден
проблем поврзан со мемориските алокатори. Секако дека во една програма
е пожелно да има са мо еден мемориски алокатор, дури и ако тој резервира
меморија за различни објекти. Но ако алокаторот обработува барања да рече­
ме за 1 char покажувачи и покажувачи кон struct tnode , се наметнуваат две

прашања. Прво 1 како да се задоволат барањата на повеќето реални машини,


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

Побарувањата за порамнување во општ случај се задоволуваат лесно, по


цена на малку загубен простор, осигурувајќи дека алокаторот секога ш враќа
покажувач кој ги з адоволува сите ограничувања . Функцијата alloc од Глава
ѕ . не гарантира некое посебно порамнување , па ќе ја користиме функцијата
6.5 еамореференцирачкиструктури 167

од стандардната библиотека mallo c , која го гарантира тоа . Во Глава 8 , ќе по ­


кажеме уште еден начин на користење на функцијата malloc.
Прашањето за де кларацијата на тип за функција каква што е malloc не е по­
годно за сите јазици во кои се прави сериозна провер ка н а типот . Во е , пра ­
вилно е да се декларира malloc да враќа покажувач кон void , а потоа истиот
експлицитно да се обликува преку претопување во покажувач од посакуваниот
тип. Функцијата D~Alloc и сродните функции н а неа се декларирани во стан­
дардното заглавје <stdlib. h>. Така, talloc може да се напише како:

#include <stdlib. h>

/ * talloc : креира јазол (tnode) */


struct tnode *talloc (void)
{
return (struct tnode *) malloc (sizeof (struct tnode)) ;

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

char *strdup(char *ѕ) /* прааи коnија од ѕ */


{
char *р ;

р =
(char *) malloc(strlen(s)+l) ; /* +1 f o r '\0 ' */
if != NULL)

strcpy(p , ѕ) ;
return р ;

malloc враќа NULL доколку нема расположив простор ; strdup ја пренесува таа
вредност понатаму, препуштајќи го справувањето со греш ките на повикувачот .
Меморијата резервирана со повик кон malloc се ослободува за п о вто рна упо ­
тр еба со повик кон функцијата free ; поглед н ете ги Глава 7 и Глава 8 .

Вежба 6-2 . Напи шете програма која чита е програма и ги печати во алфабет­
ски редосл ед сите групи на имиња н а променливи, што се иде нтични во првите

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


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

Вежба 6-3. Напи шете " индекс на текст" кој печати листа н а сите зборови од
еден докуме нт и за секој збо р листа од броевите на линиите в о кои тој се поја ву­
ва. Отстранете ги непотребн ите зборови како што се сврзниците, предлозите
и прилозите .
168 Структури Глава б

Вежба 6-4 . Напишете програма која ги печати различните зборови од влезот


сортирани во опаѓачки редослед според фреквенцијата на појавување. Нека
на секој збор му претходи соодветниот број .

6.6 Пребарување на табеnа

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


бела (анг. table-lookup) со цел да илустрираме повеќе аспекти на структурите.
Овој код е типичен пример за тоа што се наоѓа во рутините за управување со
табели од симбол и на еден макропроцесор или компајлер. На пример 1 да ја
разгледаме директивата idefine. Кога ќе наидеме на линија од тип

idefine IN 1

името IN и текстот за замена 1 се меморираат во табела . Подоцна 1 кога името


IN ќе се појави во израз како

state =IN;

тоа ќе биде заменето со 1.

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


install (ѕ 1 t) го складира името ѕ и текстот за замена во табела; ѕ и t се само
стрингови. lookup (ѕ) ја пребарува табелата барајќи го ѕ и враќа покажувач
кон местото каде што ќе го најде.или NULL ако не е таму.
Алгоритамот е хеш-пребарување - името од влезот се претвора во мал
ненегативен цел број 1 кој потоа се користи за да се индексира во низа од по­
кажувачи. Елемент од низата покажува кон почето кот од поврзаната листа на
блокови кои ги опишуваат имињата кои ја имаат таа хеш вредност. Тој е NULL
ако ниедно име не се хеширало со таа вредност .

defn
6.6 П реба рување на табела 169

Еден блок од листата nретставува структура која содржи nокажувачи кон


името, текстот за замена и сл едниот блок во листата . Крајот на листата е мар­
киран со нулти nокажувач .

struct nlist { /* запис во табела : */


struct nlist *next ; /* следен запис во синџирот */
char *name ; /* дефинирано име */
char *defn; /* техст за ~амена */
};

Низата на покажувачите е

idefine НASHSIZE 101

static struct nlist *hashtab[НASHSIZE] ; /* табела на


nохажувачи */

Хеш функцијата , која се користи и од lookup и од install , ја додава вред­


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

/* hash : форкирај хеш вредност ~а стрииrот ѕ */


unsigned hash(char *ѕ)
{
unsigned hashval ;
for (hashval = О ; *ѕ != '\0 '; ѕ++)
hashval = *ѕ + 31 * hashval ;
return hashval % НASHSIZE ;

Аритметиката со п оз итивни вредности (анг. unsigпed) обезбедува ненега тив­


ност на хеш вредноста .
Хеш nроцесот произведува почетен индекс во низата hashtab ; ако стр ин­
гот може да се најде некаде , тој ќе биде во листата од блокови која за nочнува
таму. Пребарувањето се изведува од функцијата lookup. Доколку lookup нај­
де заnис кој веќе постои , враќа пока жувач кон него , во спротивно враќа NULL .

/* lookup : најди ro ѕ · во hashtab */


struct nlist *lookup(char *ѕ)
{
struct nlist *np ;

for (np = hashtab[hash(s)] ; np != NULL ; np = np->next)


if (strcmp(s, np->name) О) ==
return np ; /* најдено */
return NULL ; /* ненајдено */
170 етруктури Глава б

for циклусот во lookup е стандарден идиом за изминување долж поврзана

листа:

for (ptr=head; ptr !=NULL; ptr=ptr->next)

install ја користи lookup за да определи дали името кое се инсталира


веќе постои; ако е така , новата дефиниција ќе ја замени старата. Во спротив­
но се креира нов запис . install враќа NULL ако поради која било причина не
може да се резервира меморија за нов запис.

struct nlist *lookup(char *) ;


char *strdup(char *) ;

/* install: смести (name, defn) во hashtab */


struct nlist *install(char *name, char *defn)
{
struct nlist *np;
unsigned hashval;

if ((np = lookup(name)) ==
NULL) { /*не е најдено */
np = (struct nlist *) malloc(sizeof(*np)) ;
if (np = NULL 11 (np->name = strdup (name) ) =
NULL)
return NULL;
hashval = hash(name);
np->next = hashtab[hashval] ;
hashtab[hashval] = np ;
else /* веќе е најдено */
free ( (void *) np->defn) ; /* оспободн ro претходниот dfn * 1
if ((np- >defn = strdup(defn)) == NULL)
return NULL ;
return np ;

Вежба 6-5 . Напишете функција undef која ќе отстрани име и дефиниција од


табелата која се одржува со lookup и install.

Вежба 6-6. Имплементирајте едноставна верзија на претпроцесорот #define


(на пример, без аргументи) соодветна за користење со е програми, базирана
на рутините од оваа лекција. Може да ви помогнат getch и ungetch.

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

typedef int Lenqth ;

креира име Lenqth како синоним за int. Типот Lenqth може да се користи
за декларации, за претопување итн., точно на ист начин како што тоа би го
правел типот int:

Lenqth len, maxlen ;


Lenqth * lenqths [] ;

На сличен начин декларацијата

typedef char *Strinq ;

креира име Strinq кое е синоним за char*, т. е. знаковен покажувач кој п о­


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

Strinqp, lineptr[МAXLINES] , alloc (int) ;


intstrcmp(Strinq , Strinq) ;
р =
(Strinq) malloc (100) ;

Забележете дека типот што се декларира со typedef стои на местото за име


на променлива, а не веднаш после зборот typedef . Синтаксички , typedef е
како мемориските класи extern , sta tic итн . Со typedef ќе користиме имиња
со голема почетна буква за да ги разграничиме од стандардните типови во С.
Како покомплициран пример, би можеле да направиме typedef имиња за
јазлите на дрво спомнати порано во оваа Глава:

typedef struct tnode *Treeptr ;

typedef struct tnode /* јазол на дрво */


char *word; /* похажува хон техст */
int count ; /* број на појавуваља */
struct tnode *left ; /* лево .цете */
struct tnode *riqht ; /* .цесно .цете */
Treenode ;

Со ова креиравме нов тип на клучен збор наречен Treennode (структура)


и Treeptr (покажувач кон структура) . Во тој случај рутината talloc мож е да
ја запишеме како

Treeptr talloc (void)


{
return (Treeptr) malloc(sizeof(Treenode));
172 Структури Глава б

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


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

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


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

typedef int (*PFI) (char *, char *) ;

го креира типот PFI, во значење " покажувач кон функција со два аргумента,
која враќа int", која може да се користи во контекст како

PFistrcmp,numcmp;

од програмата за сортирање од Глава ѕ.


Освен од чисто естетски причини, постојат две главни причини за корис­
тење на typedef. Првиот е параметризирање на програмата поради проблеми
со преносливоста. Ако typedef се користи за податочни типови, кои зави­
сат од арх итектурата, при преместување на програмата потребна е промена
само на тие тиnови. Вообичаена ситуација е да се користат
typedef имиња за
различни целобројни величини, na потоа да се наnрави соодветно множество
на избори од short, int и long за секоја nлатформа. Типови како size_t и
ptrdiff_t од стандардната библиотека се такви nримери.
Втората nримена на typedef е да обезбеди подобра документација за една
програма - тиn наречен тreeptr може да биде nолесен за разбирање отколку
оној деклариран како по кажува ч кон комnлицирана структура.

6.8 Унии

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


објекти од различни типови и големини, додека компајлерот води грижа за го­
лемината и барањата за nорамнување . Униите обезбедуваат начин за маниnу­
лација со различни видови на податоци во рамките на една мемориска област,
без да се вклучува во nрограмата каква било информација зависна од платфор­
мата. Тие се аналогни на variant заnисите во Паскал.
Како nример кој би можел да се најде во менаџерот со симболната табела на ком­
nајлерот, преmоставете дека константата може да биде int, float или знаковен
nокажувач . Вредноста на конкретна константа мора да биде зачувана во променли­
ва од соодветен тиn , а, сеnак, најсоодветно за менаџментот на табелата би било ако
вредноста зафаќа исто количество меморија и се чува на исто место без разлика од
нејзиниот тип . Ова е и намената на униите- една променлива која може легитимно
да чува еден од неколку различни тиnови . Синтаксата се базира на структури:
6.8 Унии 173

union u _ tag {
intival ;
fioat fval;
char *sval;
} u ;

Променливата u ќе биде доволно голема да ја чува вредноста на најголеми­


от од трите типови; нејзината големина за виси од имплементацијата . Кој било
од овие типови може да се додели на u и потоа да се користи во изрази се до­
дека нејзината употреба е конзистентна : типот којшто се зема мора да биде од
типот којшто бил складиран најпоследен. Одговорност на програмерот е да
води грижа за тоа кој тип моментално се чува во унијата; резултатите зависат
од имплементацијата.
Синтаксички до членовите на унијата се пристапува со

име_на_унија. член

или

покажувач_кон_унија- > член

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


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

if (utype == INТ)
printf( "%d\n " 1 u.ival) ;
if (utype-= FLOAT)
printf( " %f\n" 1 u.fval) ;
if (utype = SТRING)
printf ("% s\n", u . sval) ;
else
printf ( " bad type %d in utype\n", utype) ;

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


Нотацијата за пристап до член на унија која е сместена во структура (или об­
ратно) е идентична со нотацијата која се користи кај вгнездени структури. На
пример, за структурна низа дефинирана како
174 Структури Глава б

struct {
char *name ;
intflags;
intutype ;
union {
intival;
float fval;
char *sval;
} u;
} symtab [NЅУМ] ;

до членот ival се пристапува со

symtab[i] .u.ival

а до првиот знак од стрингот sval со

*symtab[i] . u.sval

или

symtab[i] .u.sval[O]

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


имаат растојание нула, структурата е доволно голема да го чува " најшироки­
от" член, а порамнувањето е соодветно за сите типови на унијата . Истите опе­
рации кои се дозволени за структурите се дозволени и за униите: доделување

или копирање како единка, земање на адреса и пристап до член.

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


прв член ; па така унијата u опишана погоре може да се иницијализира само со
целобројна вредност.
Заземачот (алокаторот) на меморија во Глава 8 покажува како една у нија
може да се користи, со цел на една променлива да и се наметне конкретен вид

на мемориски опсег.

6.9 Битови полиња

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


да се пакуваат неколку објекти во еден машински збор; вообичаено се употре­
бува множество од еднобитови знаменца во апликациите од тип на компајлер­
ската симбол на табела . Надворешно наметнатите формати на податоци , како
што се интерфејсите кон хардверските уреди, исто така, често имаат потреба
да пристапат до делови од збор.
6.9 Битови полиња 175

Замислете фрагмент од компајлерот кој манипулира со табелата на симбо­


ли. Секој идентификатор во програма има некоја информација поврзана со
него, на пример, дали е клучен збор или не, дали е дефиниран како надворе­
шен или статички итн. Најкомпактен начин да се кодира таква информација е
множество од еднобитни знаменца во рамките на еден char или int.
Вообичаен начин да се направи ова, е да се дефинира множество од " мас­
ки " кои одговараат на одредени битови позиции , како во

#define КEYWORD 01
#define EXTRENAL 02
#define STATIC 04

ИЛИ

enwn { КEYWORD = 01 , EXТERNAL = 02, STATIC = 04 } ;

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


товите е прашање на нивно наоѓање преку операторите за поместување, мас­
кирање и комплементирање кои беа опишани во Глава 2.
Одредени идиоми имаат честа употреба:

flags 1= EXTERNAL 1 STATIC ;

ги вклучува (поставува на 1) EXТERNAL и STATIC битовите во flags , додека

flags &=- (EXTERNAL 1 STATIC) ;

ги исклучува (поставува на нула), и

i f ( (flags & (EXТERNAL 1 STATIC) ) =О)

е вистина ако двата бита се о.


Иако овие изрази лесно се учат, како алтернатива С нуди можност за де­
финирање и пристап до полињата во рамките на еден збор директно , а не со
помош на битови логички оператори.
Битово поле или накратко само поле , претставува множество од соседни
битови во рамките на една имплементациски дефинирана мемориска единица,
која ќе ја нарекуваме "збор" . Синтаксата на дефиницијата на поле и пристапот
до него е базирана на структури. На nример , табелата на симбол и од директи­
вата #define која ја разгледавме nогоре , може да се замени со дефиниција од з
битови полиња :
176 Структури Глава 6

struct {
unsigned int is_ keyword : 1;
unsigned int is_extern : 1;
unsigned int is_static : 1;
} flags;

Ова дефинира променлива наречена flags којашто содржи три еднобитни


полиња. Бројот којшто ги проследува двете точки ја претставува широчината
на полето во битови. Полињата се декларираат како unsigned int за да се оси­
гура нивната ненегативност.

До поедини полиња се пристапува на ист начин како и до членовите на дру­


гите структури: flags. is_ keyword, flags. is_ extern, итн . Полињата се од­
несуваат како мали цели броеви и може да учествуваат во аритметички изрази
како и другите цели броеви. Така, поприродно ќе беше ако претходните при­
мери ги напишавме како

flags. is_extern =flags. is_static = 1;

за поставување на битовите на 1;

flags. is _ extern = flags. is_ static =О;

за поставување на битовите на о; и

if (flags. is_extern ==О && flags. is_static ==О)

за нивно споредување.

Речиси се што е поврзано со полињата, е зависно од локалната имплемен­


тација. Дали едно пол е може да ја надмине границата на зборот или не, за виси
од имплементацијата. Полињата не мора да бидат именувани; неименуваните
полиња (само знакот две точки и број за широчина) се користат за пополну­
вање. Посебна широчина О може да се користи за да се наметне порамнување
со следната граница на зборот.
Полињата кај некои архитектури се доделуваат од лево на десно, а кај други
од десно на лево. Тоа значи дека иако полињата се корисни за одржување на
внатрешно дефинирани податочни структури, прашањето, кој крај претставу­
ва почеток мора да се разгледа внимателно при разделување на надворешно

дефинирани податоци; програмите кои зависат од такви нешта не се пренос­


ливи. Полињата може да се декларираат само како int; заради портабилност
потребно е експлицитно да се наведе signed или unsigned. Тие не се низи и не
поседуваат адреси, па операторот & не може да се примени над нив.
Глава 7: Влез и излез

Влезните и излезните можности не се дел од самиот јазик е 1 затоа и не ги


нагласивме во нашето досегашно презентирање о Сепак 1 програмите стапу­
ваат во интеракција со нивната околина на покомплициран начи н од тој што
го прикажавме претходно о Во оваа глава ќе ја опишеме стандардната библи­
отека 1 множеството функции кои овозможуваат влез и излез 1 справување со
стрингови 1 менаџирање со меморија 1 математички рутини и разни други сер­
виси за е програми о Најмногу ќе се концентрираме на влезот и излезат о
ANSI стандардот прецизно ги дефинира овие библиотечни функции 1 така
што истите може да постојат во соодветна форма на кој било систем каде што
постои е о Програмите кои ги сведуваат н и вните интеракции со системот на ру­
тините кои ги нуди стандардната библиотека може да се преместуваат од еден
на друг систем без никаква промена о
Својствата на библиотечните функции се специфицирани во повеќе од де­
сетина заглавја; веќе видовме неколку од овие 1 вклучувајќи ги <stdio о h> 1
<string о h> 1 и <ctype о h> о Нема да ја при кажеме целата библиотека овде би­
дејќи повеќе сме заинтересирани за пишување на е програмите кои ја корис­
тат о Библиотеката е опишана детаљно во Додаток Б о

7.1 Стандарден вле3 и и3ле3

Како што веќе кажа вме во Глава 1 1 библиотеката имплементира едноставен


модел на влез и излез на текст о Текстуален поток се состои од секвенца од ли­
нии ; секоја линија завршува со знак за нов ред о Ако системот не работи на тој
начин 1 библиотеката прави што било, што е потребно за да направи да изгледа
дека е така тоа о На пример 1 библиотеката може да ги претвора знаците за нов
ред (анго carriage return) и linefeed во знак за нова линија во влезот и повторно
назад во излезат о Наједноставниот механизам за влез е да се чита знак по знак
од стандардниот влез 1 во нормален случај тастатурата 1 со getchar :

int getchar (void)

177
178 Влез и излез Глава 7

при секој повик, getehar го враќа наредниот знак кој доа ѓа на вл ез, или EOF
кога доаѓа до крај на датотека. Симболичката константа EOF е дефинирана во
<stdio.h>. Вредноста обична и е -1 , но проверките треба да бидат пишува­
ни изразен и преку EOF, за истите да бидат независни од нејзината вредност.
Во многу околини, датотеката може да биде заменета со тастатура со корис­
тење на конвенцијата за редирекција на влез<; ако програмата prog користи
getehar , тогаш командната линија

prog <infile

прави програмата prog да чита знаци од infile . Промената на влезоте направе­


на така што самата prog не е свесна за промената ; конкретно, низата " <infile"
не е вклучена во аргументите од командна линија во argv . Промената на вле­
зот, исто така, е невидлива ако влезот доа ѓа од друга програма преку механизам
на цевка; кај некои системи, командната линија

otherprog 1 prog

ги извршува и двете програми otherprog и prog и го сnроведува (а нг. pipes)


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

intputehar (int)

се користи за излез: putehar (е) го става знакот е на стандардниот излез,


за кој се подразбира дека е екранот. putehar го враќа наnишаниот зн ак, или
EOF ако се јави грешка. Повторно, излезот обично може да биде насочен кон
некоја датотека со >fi lename: ако prog користи putehar ,

prog >outfile

наместо на стандарден излез ќе го заnише нејзиниот изл ез во датотека outfile.


Ако е nоддржано сnроведување,

prog 1 anotherprog

го носи излезот од prog , на влез на anotherprog.


Излезот кој го дава printf, исто така, си го наоѓа nатот до стандардниот
излез . Повикувањата до putehar и printf може да бидат испреnлетени - из­
лезот го следи редоследот по кој се направени nовиците .
Секоја изворна датотека која пови кува библиотечна функција за влез/из­
лез, мора да ја с одржи линијата

linelude <stdio. h>


7.2 Форматиран излез - printf 179

nред nрвата референца. Кога името е ставена во загради < и >се прави преба­
рување за заглавјето во стандардно множество од места (на пример, на UNIX
системи, обична е директориумот /usr/include) .
Многу програми читаат само една влезна низа и запишуваат само една
излезна низа ; за такви програми, влезот и излезат со getchar , putchar, и

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


Ова особено е точно во случаите каде пренасочувањето се користи за повр­
зува ње на излезат од една програма со влезот на следната . На пример , да ја
разгледаме програмата подолу, која го преобразува нејзиниот влез во мали
букви:

#include <stdio. h>


#include <ctype. h>

main() /* lower: хокверзија на впезот во мапи бухви */


{
intc

while ((е= getchar ()) ! = EOF)


putchar(tolower(c)) ;
return О ;

Функцијата tolower е дефинирана во <ctype.h>; ги претвора големите во


мали букви и ги вра ќа другите знаци непроменети . Како што спомнавме прет­
ходно, "функциите" како getchar и putchar во <stdio. h> често пати се јаву­
ваат како макроа, со тоа избегнувајќи го непотребното трошење на време при
функциски повик за секој знак . Ќе покажеме како се прави ова во Поглавје в . ѕ .
Без разлика како <ctype . h> функциите се имплементирани на дадена машина ,
програмите кои ги користат немаат информација за множеството знаци.

Вежба 7- 1 . Напишете програма која ги претвора големите во мали букви или


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

наоѓа во argv [О] .

7.2 Форматиран излез - printf

Излезната функција printf преведува внатрешни вредности во знаци. Во пре­


тходните глави ја користевме printf неформално . Описот овде ги покрива најо­
пштите примени, но не е комплетен; за целосната при казна, видете го Додаток В .

intprintf (char *format, argl, arg2 , ... ) ;


180 Влез и излез Глава 7

printf ги конвертира 1 форматира и ги печати нејзините аргументи на стан­


дарден излез под контрола на fortnAt. Го враќа бројот на испечатени знаци.
Форматираната низа содржи два типа на објекти: обични знаци 1 кои се ко­
пираат на излезната низа 1 и спецификации за конверзија 1 од кои секоја пре­
дизвикува конверзија и печатење на следниот последова телен аргумент на
printf . Секоја спецификација за конверзија за почнува со % и завршува со знак
за конверзија. Меѓу% и знакот за конверзија може да има 1 редоследно:

- Знак минус , кој специфицира лево порамнување на конвертираниот аргумент.


- Број кој ја специфицира минималната ширин а на полето. Конвертираниот
аргумент ќе биде испечатен во поле широко најмалку колку оваа вредност.
Ако е потребно ќе биде дополнет од лево (или десно, ако е побарано лево
порамнување) за да се дополни ширината на полето.
-Точка , која ја одделува ширината на полето од прецизноста.
- Број , прецизноста, која го специфицира максималниот број на знаци кои
ќе се печатат од стринг, или бројот на цифри по децималната запирка на деци­
мална вредност , или минималниот број на цифри за цел број .
- h ако целиот број треба да се печати како short, или 1 (буква ел) ако се
печати како long .

Знаците за конверзија се прикажани на табела 7 .1 . Ако зна кот, кој следи после
%, не е според спецификација за конверзија, однесувањето е недефинирано.

Табела 7 . 1 Основни конверзии со printf


3нак Тип на аргументот 1 се испишува како

d, i int; декаден број


о int ; неозначен октален број (без водечка нула)
х, Х int; неозначен хексадецимален број (без водечки Ох или ОХ), со
користење на abcdef или AВCDEF з а 10 1 • • • , 15
u int ; неоз начен декаден број
е int; еден з нак
ѕ char* ; печати знаци од низата додека не се појави '\О ' или точно
колку бројот на знаци дадени со прецизноста.
f double ; [-] m. dddddd, со бројот на d-овци е за дадена
преци зноста (предефинирана вредност е 6) .
е , Е double ; [-]m . dddddde+- xxili [-]m .ddddddE+- xx , собројот на
d-овци е зададена преци з носта (nредефиниран а вредност е 6) .

g, G double ; користи % е или % Е ако експоне нтот е помал од -4 или


поголем или еднаков на п ре цизноста; инаку се користи %f .
Завршни нули или завршна децималн а точка не се печ атат .

Р void* ; пока жувач (репрезентација зави с на од имплеме нтац ија)


% не се конвертира ниеден аргумент; печати %
7.2 Форматиран излез- printf 181

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


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

printf (" %. *ѕ " , 1114Х , ѕ) ;

Повеќето од конверзиите кои ги обезбедува форматирачката низа , беа илу­


стрирани во претходните глави. Еден исклучок е прецизноста бидејќи се одне­
сува на низи. Следната табела го покажува ефектот на различните специфика­
ции во печатењето на " hello , world" (12 знаци) . Ставивме две точки пред и
после секое поле за да се види како тоа се проширува .

:%ѕ: : hello, world :


:%10ѕ: :hello, world:
:%.10ѕ: : hello, wor:
:% -lOs : : hello, world :
:%.15ѕ: :hello , world:
: %- 15ѕ: :hello, world
: %15.10ѕ: hello, wor:
:%ѕ-15.10: : hello , wor

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


колку аргументи следат и кој е нивниот тип. Ако не постои доволен број на
аргументи или ако се од погрешен тип, printf ќе се збуни и вие ќе добиете
погрешен резултат . Треба, исто така, да бидете свесни за разли ката меѓу овие
два повика :

printf(s); 1* ОТ КАЖУВА ако ѕ со држи % *1


printf( " %s " ,s) ; /* БЕЗБЕДН О */

Функцијата sprintf ги прави истите операции како и printf , но го зачуву­


ва излезот во низа :

int sprintf (char *strinq, char *forlll4t, arql , arq2, ... ) ;

sprintf ги форматира аргументите во arql, arq2 , итн . , според полето


forlll4t како претходно, но го сместува резултатот во стринг , н аместо на стан ­
дардниот излез; стрингот мора да биде доволно голем за да го прими резулта­
тот.

Вежба 7-2 . Напишете програма која ќе печати произволен влез на разумен на ­


чин . Како минимум, треба да печати неграфички знаци во октален или хекса­
децимален броен систем според локалниот начин, и да ги подели долгите
текстуални линии .
182 Влез и излез Глава 7

7.3 Листи на арrументи со променnива доnжина

Овој дел содржи имплементација на минималната верзија на printf, за да


при каже како да се напише функција која процесира аргументна листа со про­
менлива големина во преносна (портабилна) форма . Бидејќи, главно, сме за­
интересирани за процесирањето на аргументите , minprintf ќе ја процесира
низата за форматирање и аргументите, но ќе го повикува вистинскиот printf
во случаите каде е потребна конверзија на форматот . Соодветната декларација
за printfe

intprintf(char*fmt , ... )

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


менти може да варира. Декларацијата ... може да се појави само на крајот од
листата со аргументи . Haшиoтminprintf е деклариран со

voidminprintf (char *fmt, ... )

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


Незгодата сега е како mi.nprintf да се движи низ листата со аргументи, кога
таа листата нема ниту име. Стандардното заглавје <stdarg . h> содржи множе­
ство од макродефиниции кои дефинираат како се одвива движењето низ лис­
тата од аргументи . Имплементацијата на ова макро ќе варира од машина на
машина , но интерфејсот кој го нуди е универзален.
Типот va_list се користи за декларација на променлива која ќе се однесува
на секој аргумент ; кај minprintf , оваа променлива е наречена ар , за " по­
кажувач кон аргумент " . Макрото va_surt го иницијализира ар да покажува
на првиот неименуван аргумент. Потребно е да се повика пред да се користи
ар. Мора да има најмалку еден именуван аргумент ; последниот именуван ар­
гумент се користи од страна на va_start за да започне со работа.
Секој повик на va_arg враќа еден аргумент и го поместува ар на следниот ;
va_arg користи име на тип з а да одреди кој тип да го врати и колкав чекор тре­
ба да преземе. Конечно , va_end прави какво било чистење што е потребно.
Мора да биде повикан пред да заврши програмата .
Овие својства ја формираат основата на нашиот поедноставен printf :
7.4 Форматиран влез - scanf 183

iinclude <stdarg.h>
/* minprintf : минииалиа фуикциј а printf со промеилива
листа на аргументи */
void minprintf(char *fmt , . .. )
{
va_list ар ; / * nокажува кои секој од беѕимеките arg */
char *р, *sval ;
int ival ;
double dval ;

va_start(ap, fmt) ; /* нека пронеиливата ар nокажува


на првиот аргумент без име */
for(p=fmt ; *p ; p++){
if(*p!='% ' ){
putchar (*р) ;
continue ;

switch(*++p){
саѕе ' d ':
ival=va_arg(ap , int);
printf ("%d " 1 ival) ;
break ;
саѕе ' f ':
dval=va_arg(ap , double) ;
printf ("%d ", dval) ;
break ;
саѕе ' ѕ ' :

for(sval=va_arg(ap , char *) ; *sval ; sval++ )


putchar(*sval) ;
break ;
default:
putchar(*p) ;
break ;

va_end(ap) ; /* ѕачисти на крај */

Вежба 7 - З . Ревидирајте го minprintf за да се справите со што пове ќе мож­


ности на printf .

7.4 Форматиран вnез- scanf

Функцијата scanf е еквивалент за влез на printf 1 која овозможува многу


од истите опции за конверзија , но во сnротивна насока.
184 Влез и излез Глава 7

int scanf (char *format 1 • • • )

scanf вчитува знаци од стандарден влез 1 ги интерпретира според специфика­


цијата во fori~~At и ги складира резултатите во останатите аргументи. Форматот
на аргументите е опишан подолу; останатите аргументи 1 кои сите мора да би­
дат покажувачи 1 покажуваат каде треба да се зачува соодветниот конверти­
ран влез. Како и кај
printf 1 овој дел претставува сумирање на најкорисните
карактеристики наscanf 1 а не исцрпен список на сите карактеристики.
Функцијата scanf запира кога ќе ја исцрпи нејзината форматирачка низа 1
или кога некој од податоците на влез нема да се поклопи со наведената кон ­
тролна спецификација. Како вредност го враќа бројот на успешно поклопени
и доделен и објекти. Ова може да се користи за одлучување колку обје кти биле
најдени. На крајот од датотеката се враќа EOF ; забележете дека ова е различно
од нула 1 што значи дека следниот знак кој доаѓа на влез 1 не одговара на прва­
та спецификација во формат низата . Следниот повик на scanf го продолжува
пребарувањето веднаш зад последниот знак кој веќе бил обработен .
Исто така, постои функција sscanf која чита од низа 1 наместо од стандар ­
ден влез:

int sscanf (char *string 1 char *format 1 argl, arg2 1 ••• )

чита од полето string 1 според спецификацијата во полето format и ги зачу­


вува резултатите во argl 1 arg2 1 итн . Сите аргументи после format 1 мора да
бидат покажувачи .
Форматирачкиот стринг обична содржи спецификации за конверзија 1 кои
се користат за контрола на конверзијата на влезот. Форматирачката н иза може
да содржи:

- Бланко или табови кои не се игнорираат


-Обични знаци (не%) 1 за кои се очекува да одговараат на следниот непра-
зен знак од влезната низа .
- Спецификации за конверзија се конструкции кои содржат знак %1 опци­
онален знак за забрана на доделување * 1 опционален број за спецификација
на максимална широчина на пол е 1 опционални h 1 1 или L кои ја покажуваат
широчината на целта 1 и знак за конверзија .

Спецификацијата за конверзија ја насочува конверзијата на следното влез­


но поле . Нормално резултатот се сместува во променливата кон која покажува
соодветниот аргумент. Ако е индицирана забрана за доделување илустрирана
со знакот * 1 тогаш се прескокнува влезното поле; не се прави никакво доде­

лување. Влез но поле се дефинира како низа од непразни знаци ; се протега


до следното празно место или доколку е специфицирана неговата широчина ,
до неговиот крај . Ова не наведува на тоа дека scanf ќе чита и надвор од гра­
ниците на редот или линијата 1 за да го најде својот влез 1 бидејќи знаците за
7.4 Форматиран влез- scanf 185

нова линија се nразни места. (Пусти знаци се бланко, нова линија, carriage
return, вертикален таб и formfeed .)
Знакот за конверзија индицира интерnретација на влезното поле.
Соодветниот аргумент мора да биде покажувач , што и се бара од семантиката
на повикување по вредност во е. Знаците за конверзија се прикажани во табе­
ла 7.2.
Табела 7.2 : Основни scanf конверзии

Знак Податок на вле3; Тип на аргументот


d Цел број во декаден запис; int*
i Цел број; int
о Цел број во октален запис (со и без водечка нула)
u Неозначен цел број; unsiqned int*
х
Цел број во хексадекаден запис (со или без почетна Ох или ОХ)
е Знаци; char *.Следните влезни знаци (иницијално 1) се сместени
на означеното место. Нормалното прескокнување преку п разните
места е исклучено; за да се пр очита следниот непразен знак се

користи % 1ѕ

ѕ Низа од знаци (без наводниц и ); char *, покажува кон низа од


знаци доволно долга за стрингот и за завршна '\0' која ќе биде
додадена.

e , f,g Реален број со опционален предзнак , опционална децимална


точка и опционален експонент ; float*
% Знак% ; не се прави никакво доделување.

Знаците за конверзија d, i, о, u, и х може да му претходат на h, за да се


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

Како прв при мер, ќе го земеме рудиментираниот калкулатор од Глава 4 , кој


сега може да се напише со scanf , која ќе ја изведува конверзијата на подато­
ците кои доаѓаат на влез:

#include <stdio.h>

main(){ /*едноставен калкулатор */


{
double sum , v;

sum=O ;
while(scanf( "% l f" , &v)==1)
printf("\t%.2f\n", sum+=v);
return О;
186 Влез и излез Глава 7

Да претпоставиме дека сакаме да ги читаме влезните линии кои содржат по­


датоци со форма

25 Dec 1988

Изразот за функцијата scanf изгледа вака

int day, year ;


char monthname [20] ;

scanf ( "%d %ѕ %d ", &day , monthname , &year) ;

Операторот & не се користи со monthname, бидејќи самото име на ни зата по


прир ода е покажувач.

Букви може да се појават во форматирачката низа на scanf ; тие мора соод­


ветно да одговараат на истите з наци во влезот. Така, може да читаме податоци
во форма mm/dd/yy со scanf наредбата:

intday, month , year ;

scanf("%d/%d/%d", &month , &day , &year) ;

scanf ги игнорира бланко и таб знаците од нејзината форматирач ка низа.


Понатаму, при ба рањето влезни податоци ги прескокн ува празните места
(блан ко, табови, нови линии , итн.) За да се вчита влез чиј формат не е фик­
сен , често е најдобро да се чита линија по линија, па потоа да се раздвојува
со sscanf . На пр име р, да претпостав име дека сакаме да читаме линии кои
може да содржат датум во една од формите погоре. Во тој случај би можеле да
напишеме

while (getline (line , sizeof (line)) >О)


if (sscanf (line , "%d % ѕ %d " , &day, monthname , &year) == 3)
printf ( " valid : %s\n", line); 1* 25 Dec 1988 form */
elseif (sscanf(line , "%d/%d/%d ", &month , &day , &year) =3)
printf ( " valid : %s\n ", line) ; 1* mm/dd/yy form *1
else
printf ( " invalid: %s\n" , 1ine) ; 1* invalid form * /

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


функции . Следниот повик до која било влезна функција ќе започне со вчиту­
вање на први от знак непрочитан од scanf.
Последно предупредува ње : аргументите на scanf и sscanf мора да бидат
покажу вачи . Досега најч еста та грешка е пишување на :
7.5 Пристап до датотеки 187

seanf ( "%d" , n) ;

наместо

seanf ( "%d " , &n);

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

Вежба 7-4. Напишете приватна верзија на seanf аналогна на minprintf од


претходниот дел .

Вежба 7-5 . Напишете го повторно постфиксниот калкулатор од Глава 4. така


што за изведување на влезните и бројните конверзии ќе ги користи seanf и/
или sseanf .

7.5 Пристап до датотеки

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


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

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


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

eatx . cy . e

ја печати содржината на датотеките х. е и у. е (и ништо друго) на стандардниот


излез .

Прашањето е како да се постигне именуваните датотеки да бидат прочита­


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

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


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

и некои преговори со оперативниот систем (детаљи кои не не засегаат) , и


враќа покажувач кој ќе се користи во следните читања или запишувања од/во
датотеката .
Овој покажувач , наречен покажувач кон датотека, покажува кон структу­
ра која содржи информација за датотеката, како што е локацијата на баферот ,
тековната nозиција на знакот во баферот , дали од датотеката се чита или се
188 Влез и излез Глава 7

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


не мора да ги знаат деталите , затоа што дефинициите добиени од <stdio . h>
вклучуваат структурна декларација наречена FILE. Единствената де кларација
потребна за покажувач кон датотека е дадена со следниот пример

FILE *fp ;
FILE * fopen ( char *n&llle, char *mode) ;

Ова кажува дека fp е покажувач кон FILE и fopen враќа покажувач на FILE.
Забележете дека FILE е име на тип , како int , а не таг (ознака) на структура­
та; се дефинира со typedef. (Детали за тоа како fopen може да се имплемен­
тира на UNIX систем дадени се во Поглавје 8. ѕ)
Повикот за fopen во програма е

fp = fopen (name, mode) ;

Првиот аргумент од fopen е низа од знаци која го содржи името на датотека­


та. Вториот аргумент е полето mode, исто така, низа од знаци , која го покажува
начиинот на кој ќе се користи датотеката. Дозволивите начини ги вклучуваат
читањето ("r" ), запишувањето ( " w" ) и дополнувањето ( " а " ) . Некои систе­
ми прават разлика меѓу текст и бинарни датотеки ; за вториот случај, мора да
биде додадено " b " во содржината на полето mode.
Ако се обидеме да отвориме датотека за запишување или додавање , која не
постои, тогаш (доколку е тоа можно) истата се креира. Отворање на постој­
на датотека за запишување прави старата содржина да биде избришана , до­
дека при отворање на датотека за додавање, старата содржина се задржува.

Обид за читање од датотека која не постои резултира со грешка, а причините


за грешка може да бидат и други, како, на пример, обид за читање од датотека
за која немаме обезбедено дозвола од системот. Во случај на грешка, fopen
ќе врати NULL. (Грешката може да биде попрецизно идентификувана ; видете
ја дискусијата за функциите за справување со грешки на крајот од Поглавје 1 во
Додаток Б . )
Следното нешто коешто е потребно да се направи е да се најде начин да се
прочита или запише во датотеката , откако веќе истата е отворена. getc го
враќа следниот знак од датотеката ; потребен му е покажувач кон датотека , за
да знае од која датотека треба да чита.

int getc (FILE *fp)

getc го враќа следниот знак од потокот кон кој покажува fp ; враќа EOF за крај
на датотеката или во случај на грешка.
putc е излезна функција:

intputc(intc , FILE.*fp)
7.5 Пристап до датоте ки 189

putc го запишува знакот е во датотеката fp и го враќа запишаниот знак, или


EOF ако се случи грешка . Како и getehar и putehar, gete и pute наместо како
функции, може да се дефинираат како макроа.
Кога се стартува е програма, околината на оперативниот систем е одговор­
на за отворање на три датотеки и обезбедување на покажувачи кон нив . Овие
датотеки се наречени : стандарден влез, стандарден излез, и стандардна

грешка; соодветните покажувачи кон датотеки се наречени stdin, stdout и

stderr, а се декларирани во <stdio . h> . Нормално stdin е поврзан со тас­


татурата, а stdout и stderr се поврза ни со екранот, но stdin и stdout може

да бидат пренасочени кон други датотеки или цевки, како што е опишано во
Поглавје 7 . 1 .
getehar и putehar може да се дефин ираат преку gete , pute , stdin , и
stdout како што е прикажа но:

idefine getehar () gete (stdin)


idefine putehar (е) pute ((е) , stdout)

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


те fseanf и fprintf. Овие се идентични на seanf и printf , со таа ра злика
што првиот аргумент е покажувач кон датотека кој ја специфицира датотеката
од која се чита или во која се запишува ; вториот аргумент е форматирачкиот
стринг.

int fseanf (FILE *fp, ehar *format, ... )


int fprintf (FILE *fp, ehar *format, ... )

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


програма eat за надоврзување на датотеки . Одбран е дизајнот кој се покажал
како згоден за многу програми . Ако постојат аргументи од кома ндна линија ,
тие се интерпретираат како имиња на датотеки и се обработуваат во редослед
во кој се земаат. Ако нема аргументи, се обработува стандардниот влез.
190 Влез и и злез Глава 7

#include <stdio.h>
/* cat: кадоврзува датотеки , верзија 1 */
main(int argc, char *argv[])
{
FILE *fp;
void filecopy(FILE * , FILE *)

if (argc == 1) /* нема арrуиенти ; хопира стакдардек


влез */
filecopy(stdin, stdout) ;

else
while(--argc >О)
if ( (fp = fopen(*++argv, " r " )) NULL) { ==
printf( " cat : can' t open %s\n, *argv) ;
return 1 ;
else {
filecopy(fp, stdout) ;
fclose(fp) ;

return О;

/* filecopy: хопира датотеха ifp во датотеха ofp */


void filecopy(FILE *ifp, FILE *ofp)
{
int е;

while ((е= getc(ifp)) != EOF)


putc(c, ofp);

Покажувачите кон датотеки stdin и stdout се објекти од тип FILE *. Но тие се


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

int fclose (FILE *fp)

е спротивна на fopen, ја прекинува врската меѓу покажувачот на датотека и


надворешното име кое било воспоставено со fopen и го ослободува покажува­
чот за да може да се користи за поврзување со друга датотека. Бидејќи повеќе­
то оперативни системи имаат некоја граница за бројот на датотеки кои може да
бидат отворени истовремено од една програма, добра идеја е да се ослободат
покажувачите кон датотеките, кога повеќе не се потребни , како што п равевме
кај cat . Постои и друга причина за употреба на fclose врз излезна датотека ­
го чи сти баферот каде putc го упатува излезот. fclose се повикува автоматски
7.6 Справу вање со грешки- stderr и exit 191

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


не се потребни, може да се затворат stdin и stdout. Нивно повторно отвора­
ње се прави со библиотечната функција freopen. )

7 .б Справување со греwки - stderr и exit

Справувањето со грешките кај cat не е идеално . Проблемот е во тоа што,


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

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


втор излезен поток, наречен stderr на ист начин како што се stdin и stdout .
Излез напишан на stderr нормално се појавува на екранот дури и кога стан­
дардниот излез е пренасочен .

Да ја преправиме cat за да ги пишува нејзините пораки за грешка на стан­


дардната грешка .

#include <stdio.h>

/* cat: надоврзува датотеки, верзија 2 */


main(int argc, char *argv[])
{
FILE *fp;
void filecopy(FILE *, FILE *);
char *prog = argv [О] ; 1 * nрограмско име за греDПСИ *1

if (argc == 1 ) /* нека арrументи; копирај стандарден впез */


filecopy(stdin, stdout);
else
while (--argc > 0)
if ( (fp = fopen(*++argv, "r")) == NULL) {
fprintf(stderr, "% ѕ: can ' t open %s\n" ,
prog, *argv) ;
exit(l);
} else {
filecopy(fp, stdout) ;
fclose(fp) ;

if (ferror (stdout) ) {
fprintf (stderr, "%ѕ: error wri ting stdout\n", prog) ;
exit(2);

exit(O) ;
192 Влез и излез Глава 7

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


излез кој произлегува од fprintf оди до stderr , па така го наоѓа патот до
екранот наместо да исчезне во некој цевковод или во некоја излезна датотека.
Го вклучивме името на програмата, од arqv [О] , во пораката, па така ако оваа
порака се користи со други, се идентификува изворот на гр ешката .
Второ , програмата ја користи ста ндардната библиотечна функција exi t,
која го прекинува извршувањето на програмата во моментот на нејзина пови­
кување. Аргументот на exi t е достапен за кој било процес кој го повикал , п а
така успехот или неуспехот на програмата може да се тестира од друга програ ­

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


значи дека се е добро; ненулти вредности обична сигнализираат абнормални
ситуации. exi t го повикува fclose за секоја отворена излезна датотека , за да
го исчисти кој било бафериран излез.
Во рамките на главната програма (main) , return expr е еквивалентно со
exi t (expr) . exi t ја има предноста што може да биде повикана од други функ­
ции и тие повици до неа може да бидат најдени во програмата за пребарување
по облик, обработена во Глава ѕ.
Функцијата ferror враќа ненулта вредност ако се случила грешка на низата fp.

int ferror (FILE *fp)


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

Функцијата feof (FILE * ) е аналогна на ferror ; враќа ненулта вредност


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

int feof (FILE *fp)


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

7.7 Линиски вnе3 и И3Ле3

Стандардната библиотека овозможува рутина за влез fgets која е слична на


getline фун кцијата која ја користевме во претходните глави :

char *fgets (char *line, intmaxline, FILE *fp)


fgets ја вчитува следната влезна линија (вклучувајќи го и з накот за нова ли ­
нија) од датотеката fp во низата од знаци line ; најмногу maxline- 1 знак ќе
биде прочитан . Резултантната линија е завршува со '\0'. Нормално, fgets
враќа поле line ; при крај на датотека или при грешка враќа NULL . (Нашата
getline ја враќа должина та на линијата , што е покорисна вредност ; нула зна­
чи крај на датотека .)
7.7 Линиски влез и излез 193

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


нова линија) во датотека:

int fputs (ehar *line, FILE *fp)


враќа EOF ако се случи грешка , а во спротивно ненегативна вредност.
Библиотечните функции gets и puts се слични на fqets и fputs , но оперираат на
stdinиstdout. Забунаправифактотдека, gеtзгобрише '\n', арutзгододава .
За да покажеме дека нема ништо посебно во врска со функциите fqets и fputs ,
ќе ги при кажеме истите , копирани од стандардната библиотека на нашиот систем :

/* fqets : преземи најкногу до n знаци од датоте~а iop */


ehar *fqets(ehar *ѕ, int n , FILE *iop)
{
reqister int е ;
reqister ehar *еѕ;

еѕ = ѕ ;

while (--n> О && (е= qete(iop)) != EOF)


if ((*еѕ++ =е) == '\n ' )
break ;
*еѕ = '\0 ';
return (е == EOF && еѕ ѕ) ? NULL ѕ;

/* fputs : запиши етрииг ѕ во датоте~а iop */


int fputs (ehar *ѕ , FILE *iop)
{
int е;

while (е= *ѕ++)


pute (е , iop) ;
return ferror ( iop) ? EOF : О ;

Стандардот специфицира дека ferror враќа ненула за грешка ; fputs враќа


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

/* qetline: вчитува лииија , враха допжина */


int qetline (ehar *line, intmax)
{
if (fqets (line, max, stdin) =NULL)
return О ;
else
return strlen (line) ;
194 Влез и излез Глава 7

Вежба 7-6 . Напишете програма за споредба на две датотеки 1 печатејќи ја пр­


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

Вежба 7-7. Модифицирајте ја програмата за барање на облик 1 од Глава 5 1


така што влезн ите податоци да ги зема од множество именувани датотеки . Во
случај да нема именувани датотеки како аргументи 1 тогаш да го земе влезот
од стандарден влез. Треба ли името на датотеката да се печати кога ќе се најде
линија што се совпаѓа?

Вежба 7-8. На пишете програма за печатење на множество од датотеки 1 за­


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

7.8 Разни функции

Стандардната библиотека овозможува широк опсег на функции. Ова пог­


лавје е краток преглед на најкорисните. П овеќе детали и многу други функции
може да се најдат во Додаток Б .

7.8.1 Оnерации со стринrови

Веќе ги спомнавме функциите за стрин гови strlen 1 strcpy 1 streat 1 и


strcmp 1 кои се наоѓаат во <string. h> . Кај следните 1 ѕ и t се ehar * 1 а е и n
се int.

strcat (ѕ t) 1 додај го t на крајот од ѕ


strneat(s 1 t 1 n) додај n знаци од t на крајот од ѕ
strcmp (ѕ 1 t) врати н е гати вна 1 нула 1 или позитивна вредност за

s<t 1 s==t 1 s > t


strncmp(s 1 t 1 n) исто како strcmp 1 но само за првите n знаци

strcpy (ѕ 1 t) ко пирај го t во ѕ
strncpy(s 1 t 1 n) копирај најмногу n зн аци од t во ѕ
strlen(s) врати ја долж ин ата на ѕ
strchr(s 1 c) врати покажувач кон првиот е во ѕ 1 или NULL ако го н ема

strrehr(s 1 e) врати п окажувач кон последниот е во ѕ 1 или NULL а ко го нема

7 .8.2 Тестирање на класата на знакот и конверзија

Неколку функции од <c type . h > и зведуваа т тестови на знаци и нивни кон­
верзии. Во следниот дел 1 е е int кој може да се претста в и како unsigned char
или EOF. Функциите враќаат int .
7.8 Разни фун кци и 195

isalpha (е) ненула ако е е алфабетски , о ако не е


isupper (е) ненула ако е е голема буква , О ако н е е
islower {е) ненула ако е е мала бу ква , о ако не е
isdigit (е) ненула ако е е цифра, о ако не е
isalnum(e) н е н ула ако isalpha (е) или isdigit(e) , О ако н е е
isspaee (е) ненулаакосебланко, таб , новред, return , fonnfeed, вертикалентаб
toupper (е) го враќа е претворено во голема буква
tolower (е) го враќа е претворен о во мала буква

7.8.3 ungetc

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


функцијата ungeteh која ја н апи ш авме во Глава 4 ; се н ар е кува ungete .

int ungete (int е, FILE *fp)

го става знакот е назад на датотеката fp и вра ќа или е, или


EOF за грешка.
Се гарантира само еден повратен знак по датотека. ungete може да се користи
со која било од влезните функции како seanf , gete , или g etehar .

7 .8.4 И3врwуваtbе на команда

Функцијата system ( ehar * ѕ) ја изв ршува кома ндата која се сод ржи во стрин­
гот ѕ , потоа се враќа на извршување то на тековната програма. Сод ржината н а
ѕ строго за виси од локалниот оперативен систем . Како тр ивија лен пр име р , на
UNIX системи, исказот

system( " date" ) ;

предизвикува извршување на програ мата date ; го п е ч ати датумот и времето


н а стандарден излез . system враќа системски зависен целоброен статус од и з­
вршената команда. Кај UNIX с истемот, вратениот статус е вредноста врате н а
СО exit.

7 .8.5 Менаџираtbе со меморија

Функциите ma.lloe и ealloe зафаќаат блоко ви од меморија на динамички


начин.

void *malloe (size_t n)


196 Влез и излез Глава 7

враќа покажувач до доволно слободен простор за низа од n објекти од специ ­


фиц.ираната големина , или NULL ако барањето не може да биде задоволено.
Меморијата е иницијализирана на н ула.
Покажувачот вратен од malloc или calloc го има соодветната подреду­
вање за објектот што е во прашање, но мора да биде претопен во соодветниот
тип, како

int *ip;

ip = (int *) calloc (n, sizeof (int)) ;

free (р) го ослободува просторот покажуван од р , каде што р бил оригинално


добиен со повик кон malloc или calloc . Не постојат ограничувања на редо­
следот по кој се ослободува простор, н о е огромна грешка да се ослободи неш­
то што не е добиено со повик на malloc или calloc .
Исто така, грешка е да се користи нешто откако било ослободено.
Вообичаен, но неточен дел од код, е овој циклус кој ослободува објекти од
листа:

for (p=head; р !=NULL ; p=p->next) /* ПОГ РЕШНО */


free(p) ;

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

for (p=head; р !=NULL ; p=q)


q=p->next;
free(p) ;

Поглавје 8 . 7 ја прикажува имплементацијата на заземач на меморија како


malloc , во кој зафатените блокови може да бидат ослободени во кој било ре­
дослед.

7 .8 .6 Математички функции

Постојат повеќе од 20 математички функции декларирани во <math . h> ;


овде се некои од тие кои се почесто користени. Секоја зема еден или два
double аргумента и враќа double.
7.8 Разни функции 197

sin(x) синус од х , х е во р адиј ани


соѕ (х) к ос ину с од х, х е в о радијани
atan2(y , x) арку с т анге нс од у/х , во ради ја н и
ехр(х) експоненцијал на функција ех
log(x) приро ден ( со ос н ова е ) л о гаритам о д х (х>О)
loglO(x) стандарден ( со осн ова 10) логаритам од х (х>О )

pow(x,y) хУ

sqrt (х) квадр атен корен од х (х>О)


fabs (х) апс олу тна вредн ос т на х

7.8.7 Генерирање на случаен број

Функцијата rand () пресметува секвенца од псевдо случајни цели броеви во


опсегот од о до RAND_ МАХ кој е дефиниран во <stdlib . h>. Еден начин за креи ­
рање на случајни децимални броеви поголеми или еднакви на нула, но помал и
од 1 е

idefine frand() ( (double) rand() 1 (RAND_МAX+1 . 0))

(Ако вашата библиотека веќе овозможува функција за децимални случајни бро­


еви , веројатно е дека ќе има подоб ри статистички својства од наведе ната. )
Функцијата srand (unsigned) ја иницијализи ра функцијата rand .
Преносната имплементација на rand и srand предложена од стандардот се
појавуаа во Погл а вје 2.1 .

Вежба 7-9. Функциите како isupper може да се импл ементираат за да се за ш­


теди простор или време. Истражете ги и двете можности.
Глава 8: Системски интерфејс на UNIX

Оперативниот систем UNIX ги н уди неговите услуги преку м ножест во на сис­


темски повици, кои претставуваат функции во рамките н а оперативниот сис­
тем, што може да се пови куваат од корис нички креирани програми . Ова пог­
лавје објаснува како во програмите се користат некои од поважните системски
повици . Ако користите UNIX, ова ќе ви биде од полза , бидејќи некогаш е важно
да се вклучат системските повици, за да се максимизира ефикасноста, или да
се пристап и до некоја услуга која не е вградена во библиотеката. Дури и ако го
користите е на друг оперативен систем, би требало да прони к нете во е програ­
мирањето пре ку разгл едување на овие при ме ри ; иако деталите варираат, сли­

чен код може да се најде на кој било друг систем. Бидеј ќи во голем број случаи
ANSI библиотеката на е е моделирана на погодностите кои ги нуди UNIX, овој
код, исто така, може да ви помогне и во разбирањето на самата библиотека.
Поглавјето е поделено на три големи делови; влез/излез, датотечен систем
и резервирање (алокација) на меморија. Првите два дела претпоставуваат
скромно познавање на надворешните карактеристики на UNIX системите.
Глава 7 се однесуваше на влезно /излезниот интерфејс кој е униформен за
сите платформи . На секој оперативен систем рутините од стандардната библи­
отека мора да се пишуваат во согласност со погодностите кои ги обезбедува тој
систем. Во наредните поглавја ќе ги о пишеме UNIХ-овите системски повику­
вања за влез и излез и ќе демонстрираме како делови од стандардната библио­
тека може да се имплементираат со нив .

8 . 1 Дескриптори на датотеките

Кај оперативниот систем UNIX, целоку пни от влез и излез се прави со читање
или запишување во датотеки , бидејќи сите периферни уреди, дури и тастатура ­
та и мониторот, се интерпретираат како датотеки од страна на датотечниот сис­

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


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

199
200 Системски интерфејс на UNIX Глава 8

ќе имате потреба да ја креирате истата или да ја отстраните нејзината претход­


на содржин а. Системот проверува дали вие дозволено тоа (Дали постои да­
тотеката? Дали имате дозвола од системот да пристапите д о неа?) и ако помине
се во најдобар ред на програмата и враќа мал ненегативен цел број наречен
дескриптор на датотеката. Без разлика дали се изведува читање или запи­
шување врз една датотека, за идентификација не се користи нејзиното име, туку
нејзиниот дескриптор. (Дескрипторот е сличен н а датотечниот покажувач кој
се користи од стандардната датотека, или справувачот (анг.
handle) со датотека
кај MS-DOS . ) Сите информации поврзани со отворената датотека се одржу­
ваат од системот; корисн ичката програма се обраќа до датотеката само преку
нејзиниот дескриптор .
Бидејќи влезно/излезните операции кои инволвираат тастатура и екран
се вообичаени, постојат специјални аранжмани за да го поедностават таквиот
пристап . Кога кома ндниот интерпретер (таканаречен школка, анr. she11)
извршува некоја програма, се отвораат три датотеки, со дескриптори о, 1, и 2,
наречени стандарден влез, ста ндарде н излез и стандардна грешка . Ако про­
грамата ја чита о , а запишува во 1 и 2, може да извршува влезно/излезни опе­
рации без да води сметка за отворање на датотеки.
Корисникот на програма може да го пренасочи 1/0 (Input/Output- Влез/
Излез) кон и од датотеки со< и>:

prog <infile >outfile

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


ните доделувања за датотечните дескриптори о и 1 со наведените датотеки. Во
нормален случај, деск рипторот 2 останува поврзан за мониторот, со цел пора­
ките за грешка да се печатат таму . Слич н о разм ислување важи и за влезот и из­
лезат врзани преку цевка (а нг. pipe) . Програмата не знае од каде доаѓа влезот
ниту каде оди излезат, се додека ги користи датотеката о за влез и датотеките 1
и 2 за излез .

8.2 Примитивен 1/0- read и write

Влезот и и злезат ги користат системските повици read и write, до кои од


С про грамата се пристапува преку две функции, наречени соодветно read и
wri te. Кај двете, првиот аргуме нт е дескриптор од датотека. Вториот аргумент
е знаковна низа која означува од каде доаѓаат и каде одат податоците во вашата
програма. Третиот аргумент е бројот на бајти кои треба да се префрлат.

int n_ read = read(int fd , char *buf, int n) ;

int n written = write(int fd, char *buf, int n) ;


8.2 Примитивен 1/0 - read и write 20 1

Секој повик го враќа бројот на бајти ко и биле префрлени . При читање,


бројот на вратени бајти може да биде помал од бројот на п·обара ни . Повратна
вредност о означ ува крај на датотека, а -1 н е каква грешка . При запишува ње,
вратената вредност е бројот на запишани бајти ; сигнал за грешка е нееднак­
воста помеѓу бројот на запишани и бројот на побарани бајти .
Во еден повик може да се прочита или запише произволен број на бајти .
Највообичаени вредности се 1, што значи еден знак по повик ( " небафери ра­
но " ), како и броеви како 1024 или 4096, што одговараат на голем ината на фи­
зичките блокови кај периферните уреди. Поголемите големини се поефи касн и,
поради помалиот број на направени системски повикувања.
Имајќи го предвид претходно кажаното, може да напишеме едноставна
програма која ќе го копира својот влез на свој от и злез, екв и ва лентна на про гра ­
мата за копирање датотеки напишана во Глава 1 . Оваа програма ќе копира што
било, во што било, бидејќи влезот и излезот може да се пренасочат кон која
било датотека или уред.

ftinclude " syscalls.h"

main() /* хопира од влез на излез */


{
char buf[BUFSIZ];
int n;

while ((n= read(O, buf , BUFSIZ)) > 0)


write(1 , buf , n) ;
return 0 ;

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


под име syscalls. h за да може да ја вкл учиме во програмите од оваа глава .
Меѓутоа, ова име не е стандардно . Параметарот BUFSIZ, исто така, е дефини­
ран во syscalls. h; неговата големина е соодветна за локалниот систем. Ако
BUFSIZ не е цел делител од големината на датотеката, некој повик кон read ќе
врати и помал број на бајти да бидат запишани од wri te; следниот пови к кон
read ќе врати нула.
Поучно е да се види како read и write може да се користат за конструкција
на рутини од повисоко ниво како getchar, putchar, итн. Како пример е дадена
верзија на getchar која изведува небафериран влез, читајќи знак по знак од
стандардниот влез .
202 Системски интерфејс н а UNIX Глава 8

#include " syscallsoh"

/ * g etchar : небафериран зпез , знах по знах */


int getchar(void)

char е;

return (read(O , &е , 1) -- 1) ? (unsigned char) е EOF ;

е мора да биде char, бидеј ќи read има п отреба од покажувач кон знак о
П ретопувањето на е во unsigned char кај повратниот израз, ја елиминира мож­
носта за каков било пробл ем околу знаков ни от додато к (предзнакот) о
Втората верзија на getchar, го цепка нејзиниот влез на поголеми порции и
ги предава з наците, еден п о ед ен о

# include " syscalls oh "


/* getchar : верзија со просто баферираље */
int getchar(void)

static char buf [BUFSIZ] ;


static char *bufp = buf ;
stati c int n = О ;

if (n == 0) { / * б аферот е празен* /
n = read (O , buf , sizeof buf) ;
bufp = buf ;

re t urn ( --n >= О) ? (u n sign ed char) *bufp++ EOF;

Ако овие верзии на getchar беа компајл ирани со вклуче на <stdio о h>, ќе
беш е потребно да се нап рав и #undef на името getchar, во случај и стото да
било имплеме н тирано како макро о

8.3 Open, creat, close, unlink

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


користење на датотеки тие мора е ксплицитн о да се отворат во случај да има
потреба да се ч ита или запишува во ни в о Постојат два системски по вика за таа
н амена, open и creat [ sic] о open е с лична на fopen опиша на во Глава 7, со
таа разлика што наместо покажува ч кон структура FILE, враќа дескриптор на
датоте ка, кој во суштина е int вредност о Во случај на грешка, open враќа - 1 0
8.3 Open, creat, close, unlink 203

#include <fcntl.h>

int fd ;
int open(char *name , int fiags , int perms) ;

fd = open(name, fiags , perms) ;

Како и кај fopen, аргуме нтот name е з наковен стринг кој содржи име н а дато­
тека . Вториот аргимент fiags е int кој специфицира на кој начин да се отвори
датотеката; главните вредности се :

О RDONLY отвори само за читање

О WRONLY отвори само за запишување

О RDWR отвори з а читање и за запишување

Кај системите system v UNIX, овие константи се дефинирани во <fcntl. h> ,


а кај верзиите на Berkley (BSD) во заглавишната датотека <sys/file. h> .
За да се отвори една датотека за читање

fd = open(name , O_RDONLY,O);

За примените на open кои ќе ги раз гледаме, аргументот perms секогаш има


вредн ост нула.

Обид за отворање на датотека која не постои резултира со грешка. За кре­


ирање на нова датоте ка и повторно за пиш ување во н екоја постојна , се користи
системскиот повик crea t.

int creat(char *name, int perms) ;

fd = creat(name , perms) ;

враќа дескриптор кога е успешно креирана датотека и -1 во спротивен случај.


Ако датотеката веќе постои, creat ќе ја скрати нејзи ната должина на нула , со
тоа ослободувај ќи ја претходната содржина на датотеката; повик на creat врз
веќе постојна датотека, не се смета за грешка .
Ако датотеката не постои , creat ќе ја креира со привилегиите наведени со
аргументот perms. Кај датотечниот систем на UNIX, резервирани се девет би­
тови на информација врзана за датотека, кои контролираат привилегии, како
читање, запишување, и извршување , за еден сопстве ник, група на сопственици

и за сите останати. Следи дека трицифрените октални броеви се соодветни за


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

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

За илустрација, покажа на е поедноставена верзија на UNIX-oвaтa програ­


ма ср, која копира една во друга датотека. Н а шата верзија ко п ира само една
204 Системски интерфејс на UNIX Глава 8

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


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

#include <stdio . h>


#include <fcntl . h>
#include "syscalls.h"
#define РЕRМЅ 0666 /* 'l>m!l&e и заrицуваве за cacliiE5aot, rpyna, и с:им OC'1'llllir1И */

void error(char *, ... ) ;

/* ср: го хопира fl во f2 */
main(int argc , char *argv[])
{
int f1 , f2 , n ;
char buf[BUFSIZ] ;

if (argc != 3)
error("Usage : ср from to " );
if ((f1 = open(argv[1] , O_RDONLY , О)) -1) ==
error( " cp : can' t open % ѕ " , argv[1]) ;
if ((f2 = creat(argv[2] , РЕRМЅ)) -1) ==
error( " cp : can ' t create % ѕ , mode % 0Зо " ,
argv[2], РЕRМЅ) ;
while ((n= read(f1 , buf , BUFSIZ)) > 0)
if (write(f2, buf, n) !=n)
error( " cp : write error on fi.le % ѕ ", argv[2]) ;
return О ;

Оваа програма креира излезна датотека со фиксни привилегии 0666. Со сис­


темскиот повик stat, опишан во Поглавј е 8. б, може да се определат пр и виле ­
гиите на веќе постојна датотека и истите да се предадат на копијата .
Забележете дека функцијата error се повикува со променливи листи, слич­
но како printf. Имплементацијата на error, илустрира употреба на друг член
од фамилијата printf . Функцијата од стандардната библиотека vprintf е
слична на printf, со исклучок дека променливата листа на аргументи е заме­
нета со единствен аргумент, кој се иницијализира со повик кон ма крото va_
start . Слично, vprintf и vsprintf соодветствуваат на fprintf и sprint .
Постои ограничување (вообичаено околу 20) за бројот на датотеки кои може
да бидат отворени во рамките на една програма. Односно, секоја програма што
има намера да обработува повеќе датотеки мора да биде подготвена на преиско­
ристување на датотечните дескриптори. Функцијата close (int fd) ја раскину­
ва врската помеѓу дескрипторот и отворената датотека и го ослободу ва дескрип­
торот за употреба со некоја друга датотека ; соодветствува на функцијата fclose
од стандардната библиотека, со таа разлика што не поседува бафер кој треба да
се испразни . Прекинувањето на програмата со повик на exit или преку наред­
бата return од главната програма ги затвора сите отворени датотеки.
8.4 Случаен пристап - lseek 205

#include <stdio . h>


#include <stdarq.h>

/* error: исnечати nopaxa за rрешха и умри */


void error(char *fшt, ... )
{
va_list arqs;

va_start(arqs, fmt) ;
fprintf(stderr , "error: " ) ;
vprintf(stderr, fmt , arqs);
fprintf(stderr , " \n" ) ;
va_end(arqs);
exit(l) ;

Функцијата unlink (char* name) ја отстранува датотеката name од датотечни ­


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

Вежба 8-1. Напишетеја повторно програмата саtод Глава 7 , користејќи read,


wri te, open и close на местото на нивните еквиваленти од стандардната биб­
лиотека. Направете тестови за да ја определите релативната брзина на двете
верзии.

8.4 Сnучаен пристап - lseek

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


wri te за почнува на позиција во датотеката која следи веднаш после претход­
ниот таков повик. Сепак, кога е тоа потребно, од датотеката може да чита или
во неа да се запишува во произволен редослед . Системскиот повик lseek,
обезбедува можност за движење низ една датотека, без читање или запишу­
вање на никакви податоци.

lonq lseek(int fd , lonq offset , int oriqin) ;

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


дескриптор е fd, земено релативно во однос на локацијата наведена во origin .
Следното читање или запишување ќе за почне од таа позиција. origin може да
биде о, 1, или 2 за да специфицира дека вредноста на offset треба да биде пре­
сметана во однос на почетокот, на тековната позиција, или на крајот на датоте­
ката, соодветно. На пример, при надодавање во датотеката (пренасочувањето
>> во командниот интерпретер на UNIX, или " а " за fopen), се оди до крај на
датотеката, пред да се запише во неа . :
206 Системски интерфејс на UNIX Глава 8

lseek(fd, OL, 2);

За враќање на нејзи ниот почеток, (" премотува ње"),

lseek(fd , OL , О) ;

Забележете го аргументот OL; истиот можеше да се на пише и како ( long) о,


или само како о, ако функцијата lseek е правилно ,цекларирана.
Со користење на lseek, возможно е датотеките да се третираат како низи,
по цена на побавен пристап. На пример, след ната функција чита произволен
број на бајти, од произволно место на една датотеката. Го враќа бројот на про­
читани знаци , или -1 во с л у ч ај на грешка.

#include "syscalls . h "

/*get: чита n бајти од nозиција роѕ */


int get(int fd , long роѕ , char *buf , int n)

if (lseek(fd , роѕ, 0) >= 0) /* get to роѕ */


return read(fd , buf , n) ;
else
return -1 ;

Вратената вредност од lseek е од тип long и ј а дава н овата п озиција во да­


тотеката, или -1 ако настане н екоја гре шка. Функцијата fseek од стандардната
библиотека е лична со lseek, со таа разлика што п рвиот аргументе е FILE* и во
случај да настане грешка, вратената вредн ост е различна од нула .

8.5 Пример - импnементација на fopen и getc

ќе илустрираме како некои од овие делови формираат цел ина, преку приказ
на една имплементациј а н а рут ините од стандардната библиотека, fopen и getc .
Спомнете си дека датотеки те од стандардната библиотека се опи шани пре­
ку по кажувачи кон д атотеки, а н е пр еку дескриптори . Покажувач ко н датотека
претставува покажувач кон структура која содржи некол ку делови информација
за датотеката: покажувач кон бафер, со што датотеката може да биде чита на во
големи бло кови; бројач за бројот на преостан ати знаци во баферот; покажувач
кон наредната позиција на знак во баферот; датотечен дескриптор; и з наменца
кои ги опишуваат пр ивилегиите на читање /за пишување, статус н а грешки, итн.
Податоч ната с труктура која о пишува едн а датотека, е сместена во за гл авј ето
<s tdio . h>, кое мора да биде вклучено ( со #incl ude) во секоја изворна да­
тотека што користи рутини од стандардната влезно/излезна библиотека. Таа,
и сто така , е вкл учена во функциите од таа библи отека. Во следниот извадок
8.5 Пример - имnлементација на fopen и getc 207

од стандардното заглавје <stdio . h>, имињата кои се предвидени за посебна


употреба само за функции од библиотеката, започнуваат со знак за подвлече­
но, со што е помалку веројатно дека ќе дојдат во конфликт (ќе се поклопат) со
имиња од корисничката програма. Оваа конвенција се користи од сите рутини
на стандардната библиотека.

#define NULL О
#define EOF {-1)
#define BUFSIZ 1024
#define OPEN_ МАХ 2 О /* мuа:ие.пен број на е,цк:врЕ!1ЕН> C1I!q)E!IИ wn<лe»t* /

typedef struct iobuf


int cnt ; / * преостанати знаци */
char *ptr ; /* nозиција на следен знак * /
char *base ; /* локација на бафер */
int flag ; /* мод на датотечен nристаn */
int fd; /* датотечен дескриnтор */
FILE ;
extern FILE _iob[OPEN_МAX] ;

#define stdin (&_iob[O])


#define stdout {&_iob[1])
#define stderr {&_iob[2])

enum _ flags {
_READ = 01 , / * датотека отворена за читање * /
WRITE = 02, /* датотека отаорена за заnишуваље */
UNВUF = 04, /* датотеката е небаферирана */
EOF = 010 , /* EOF се nојави во оваа датотека */
ERR = 020 /* грешка се појави во оваа датотека */
};

int _fillbuf{FILE *);


int _fiushbuf{int , FILE *);

#define feof{p) {(p)->fiag & _EOF) !=О)


#define ferror{p) {{p)->flag & _ERR) != 0)
#define fileno{p) ({p)->fd)

#define getc{p) (--{p)->cnt >=О \


? {unsigned char) *(p)->ptr++ : _fillbuf{p))
#define putc(x , p) {--{p)->cnt >=О \
? *{p)->ptr++ = {х) : _fiushbuf{{x) , р))
#define getchar{) getc{stdin)
#defme putcher{x) putc{{x), stdout)
208 Системски интерфејс на UNIX Глава 8

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


и го враќа знакот. (Спомнете си дека долга tdefine директива се продолжува со кон­
траниз) Ако бројачот стане негативен, тогаш, getc ја повикува функцијата _fillbuf
за повторно да го наполни баферот, ја реиницијализира содржината на структурата и
враќа знак. Знаците се враќаат како unsigned, што обезбедува нивна позитивност .
Иако нема да зборуваме околу детали те, ја вклучи вме дефинициј ата на putc
за да покажеме дека функционира на многу сличен начин ка ко и getc, повику­
вајќи функција _flushbuf кога ќе се наполни нејзиниот бафер . Исто така, вклу­
чивме макроа за пристап до на станата грешка, до статусот за крај н а датотека та
како и до нејзиниот дескриптор.
Сега може да се напише функцијата fopen . Поголемиот дел од неа опфаќа опера­
ции за отворање на датотеката и позиционирање на вистинското место, како и поста­

вување на битовите-знаменца да ја покажуваат соодветната состојба. fopen не ало­


цира простор за баферот; тоа се прави од_fillbuf при првото читање на датотеката .

Иinclude <fcntl . h>


Иinclude " syscalls . h "
fdefine РЕRМЅ 0666 / * RW за соnствених, rpyna , други */
FILE *fopen(char *name, char *mode)
{
int fd ;
FILE *fp ;

if (*mode != ' r ' && *mode != ' w' && *mode != 'а')
return NULL ;
for (fp = _iob ; fp < _iob + ОРЕN_МАХ; fp++)
if {(fp->flag & (_READ 1 _WRITE)) ==О)
break ; /* found free slot */
if (fp >= iob + ОРЕN_МАХ) /* n o free slots */
return NULL ;

if ( *mode = 'w' )
fd = creat(name , РЕRМЅ) ;
else if (*mode == ' а') {
if ((fd = open(name, O_WRONLY , 0)) -- - 1)
fd = creat(name , РЕRМЅ) ;
lseek(fd , OL , 2);
else
fd = open(name, O_RDONLY, 0) ;
if (fd == -1) /* couldn't ассеѕѕ name */
return NULL ;
fp->fd = fd ;
fp->cnt = О ;
fp->base = NULL ;
fp- >flag = (*mode -- ' r ' ) ? READ _WRITE ;
return fp ;
8.5 Пример- имплементација на fopen и getc 209

Оваа верзија на fopen не се справува с о сите модови за пристапни привилегии


кои ги нуди стандардот, иако додавање на некои од нив не б и зафатило премно­
гу код . Конкретно, нашата Fopen не препознава дека " b" сигнализира бинарен
пристап, бидејќи тоа е бесмислено на UNIX системите, ниту " + " кој овозможува
и читање и запишување .

Конкретно, прв и от повик до getc наидува на нулта вредност на бројачот,


што наметнува пови к до_fillbuff . А ко_fillbuff утврди дека датотеката не е
отворена за читањ е, во истиот момент враќа EOF. Во спротивно, се обидува да
алоцира бафер (во сл у чај да треба читањето да биде баферира но) .
Кога ќе се воспостави баферот, _ fillbuff ја повикува read за да го наполни,
ги поставува бројачот и покажувачите и го вра ќа знакот кој е на почетокот од
баферот . Следните пови кувања до_fillbuf ќе утврдат алоциран бафер.

iinclude " syscalls . h "

/* _fillbuf : апоцира и пополнува бафер за влеѕ */


int _fillbuf(FILE *fp)

int bufsize ;

i f ( {fp->flag& (_READ I_EOF_ERR))


!= _READ)
return EOF ;
bufsize = (fp- >fiag & _UNВUF) ? 1 : BUFSIZ ;
if (fp->base == NULL) /* сеуште нема бафер */
if ((fp->base = (char *) malloc(bufsize)) == NULL)
return EOF ; /* не може да обезбеди бафер */
fp->ptr = fp->base;
fp->cnt = read(fp->fd , fp->ptr, bufsize) ;
if (--fp->cnt < О) {
if (fp->cnt == -1)
fp- >fiag 1= _EOF ;
else
fp->fiag 1= _ERR ;
fp->cnt = О ;
return EOF ;

return (unsigned char) *fp->ptr++;

Единствено што остана е да се обја сн и како за почнува сето ова . Низата _ iob
мора да биде дефин и рана и иницијализирана за stdin, stdout и stderr:

FILE iob[OPEN_МAX] = { /* stdin , stdout, stderr */


-
{ о, (char * ) о, (char *) о, _READ , о } ,
{ о, (char *) о, (char *) о, _WRITE , 1 } ,
{ о, (char *) о, (char *) о , _WRITE , 1 _UNВUF , 2 }
} ;
210 Системски интерфејс на UNIX Глава 8

Иницијализацијата на делат flag од структурата покажува дека stdin е предви­


ден за читање, stdout за запишува ње и stderr за небаферирано за пишува ње.

Вежба 8-2 . Повторно напиш ете ги fopen и _fillbuf со полиња , на мес то со


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

Вежба 8-3. Дизајнирајте и напишете ги функциите _ flushbuf, fflush и fclose.

Вежба 8-4 . Функцијата од стандардната библиотека

int fseek(FILE *fp , long offset , int origin)

е идентична н а fseek fp е по кажувач ко н датотека , а н е


со таа разлика што

датотечен дескр иптор , к ако и што враќа


int ста тус, а н е позиција. Н апи шете
ј а fseek. Осигурете се дека вашата fseek соодветно коо рдинира со бафери­
рањето напра вено за другите функции од библиотеката .

8.6 Пример- Листање содржина на директориуми

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


утврдување н а информација околу некоја датотека, а не што таа содржи. Пример
за тоа е командата ls за листање на директориумите во UNIX- ги печ а ти имињата
на датотеките во еден директориум, и во случај на потреба, други информации
како големини, привилегии, итн . Аналогна е командата dir во мѕ-ооѕ .
Бидејќи директориум во UNIX се третира к ако датотека, един стве но што тре­
ба ls да направи е истиот да го прочита, како би дошла до имињата на датоте­
ките во н его. Но, за да се п р иста п и до дру ги информации околу една датотека,
н а приме р, нејз ината гол емина, потребно е да се кор исти системски повик. На
други системи , системски п ови к може да биде потребен, дури и за приста п до
имињата на датотек ите; н а пример, таков е случајот со мѕ-ооѕ . Он а што сака­
ме е да се обезбеди пристап до саканата информација, на н ачин релативно не­
зави сен од системот, иако имплементацијата може да биде в исоко зави сна од
системот.

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


Функцијата fsize е специј ал на форма на ls која ги печати големи ните на сите
датотеки наведени во нејзината листа од аргументи кои доа ѓаат од командна
линија. Ако еде н од датотеките е директориум, fsize се п рименува себеси ре­
курзивно н а тој директориум. Во слу чај вооп што да нема а ргуме нти , го проце­
сира те ковн иот директо р и ум.

Да започнеме со кра ток п реглед врз структурата н а UNIХ-овиот датотече н


с и стем. Директориум е датотека која содржи л и ста н а ими њ а од датотеки и
не каква информација за тоа каде ти е се лоцирани. "Локација та" е ин декс во
8.6 Пример- Листање содржина на директориуми 211

друга табела наречена " inode list". " inode" за една датотека е место каде се
чува целата информација врзана за некоја датотека, со исклучок на нејзиното
име. Еден директориумски запис обично се состои од два елемента, име на
датотеката и број на inode-oт .
За жал, форматот и точната содржина на еден директориум не се исти на
сите верзии на системот. Така, ќе ја поделиме таа задача на два дела со цел да
ги изолираме непреносливите делови. Надворешното ниво дефинира струк­
тура наречена Dirent и три рутини opendir, readdir и closedir за да се

обезбеди пристап до името и бројот на inode-oт во еден директориумски за­


пис кој е независен од системот. Ке ја напишеме fsize со тој интерфејс . Потоа
ќе покажеме како истите да се имплементираат на систем и кои користат иста
директори умска структура како version 7 и v UNIX; варијантите се оста вени за
вежбање .
Структурата Dirent ги содржи inode бројот и името. Максималната должи­
на на името е NАМЕ_МАХ, чија вредност зависи од системот. opendir вра ќа по­
кажувач кон структура наречена DIR, која е аналогна на FILE структурата што
се користи од readdir и closedir. Оваа информација се зачувува во д атотека
наречена dirent. h.

#define NАМЕ МАХ 14 /* најголема должина на име на датотека*/


/* заииси од системот */

typedef struct { /* nренослив директориуиски запис*/


long ino ; /* број на inode*/
char name[NAМE_МAX+1] ; /*име+ терминален знак '\0' */
Dirent;

typedef struct /* минииален DIR : без баферираае , итн.*/


int fd ; /* датотечен дескриптор за директориум*/
Dirent d ; /* директориуискиот запис*/
DIR ;

DIR *opendir(char *dirname) ;


Dirent *readdir(DIR *dfd);
void closedir(DIR *dfd);

Системскиот повик stat зема име на датотека и ја враќа целата информација


што ја содржи inode - oт за таа датотека, или -1 во случај на грешка. Односно,

char *name;
struct stat stbuf;
int stat(char *, struct stat *) ;

stat(name, &stbuf) ;
212 Системски интерфејс на UNIX Глава 8

ја исполнува структурата stbuf со inode информацијата за името на датотека­


та. Структурата што ја опишува вредноста вратена од stat се наоѓа во <ѕуѕ/
stat. h>, и во општ случај изгледа вака:

struct stat /* inode информацијата вратена од stat*/

dev t st_dev ; /* inode уред* /


ino t st_ino ; /* inode број */
short st_mode ; /* битови за модот*/
short st_nlink ; /* број на врсхи ~он датоте~а* /
short st_uid; /* ~орисничхи идентифи~атор на соnствени~от* /
short st_ gid ; / * корисничхи идентифи~атор на груnата* /
dev t st_rdev ; /* за сnеција.пни датоте~и*/
off t st_size ; /* големина на датоте~ата во знаци*/
t~e_t st_atime ; /* време на nоследен nристаn */
time_t st_mtime; /* време на nоследна модифи~ација */
time t st_ctime; /* време на nоследна промена на inode-oт */
};

Повеќето од овие вредности се објаснети со коментарите. Податочните типови


како dev_t и ino_t се дефи нирани во <sys/ types .h>, која, исто така, мора да
биде вклуч ена .
За писот ts_ mode содржи м ножество знаме нца за опис на датотека . Н ивните
дефиниции, исто така, се вклучени во <sys/ types. h>; потребен ние само делот
кој се справува со датотечниот тип :

#define Ѕ IFМТ 0160000 /* тиn на датотека*/


#define Ѕ IFDIR 0040000 /* дире~ториум *1
#define Ѕ IFCHR 0020000 /* nосебен зна~*/
#define Ѕ IFВLK 0060000 /* nосебен бло~ */
#define Ѕ IFREG 0010000 /* реrуларно */

/* .. . */

Сега сме подготвени да ја напишеме програмата fsize . Ако модот доби ен


од fstat наведува дека датотеката не е дир е кториум, тогаш големината ние

доста п н а и може веднаш да се п е чати. Ако името претста вува директориум, то­
гаш истиот мора да го обработиме датотека по датотека; истиот може да содржи
и п од-директориуми , па процесот е рекурз ивен .

Главната рутина (main) се справува со аргументите од командна линија ; го


предава секој аргуме н т н а функцијата fsize .
8.6 Пример - Листање содржина на директориуми 213

#include <stdio . h>


#include <string . h >
#include " syscalls . h "
#include <fcntl.h> /* знаменца за читање и заnиwуваље */
#include <sys/types.h> /* дефиниции ка nодат очки тиnови */
#include <sys/stat.h> /* струхтура вратена од stat */
#include " dirent.h"

void fsize(char *)

/* nечатеае на име ка датотеха * /


main(int argc , char **argv)
{
if (argc == 1) /* nрвичко : теховек директориум */
fsize (". " ) ;
else
while (--argc > О)
fsize(*++argv) ;
return О ;

Функцијатаfsize ја печати големината на датотеката. Ако датотеката е ди­


ректори ум, тогаш, fsize прво ја п ов икува dirwalk за да се справи со сите да ­
тотеки во него . Забележете како знаме нцата имен ува н и ѕ_IFМТ и ѕ_ IFDR од
<sys/stat.h> се користат за определување дали една датотека е директориум .
Заградите се п отребн и, бидејќи приоритетот на & е понизок од он ој на= .

int stat(char *, struct stat *) ;


void dirwalk(char * , void (*fcn) (char *) );

/* fsize: nечати име на датотехата " name " */


void fsize(char *name)
{
struct stat stbuf ;

if (stat(name, &stbuf) == -1) {


fprintf(stderr , " fsize : can ' t ас се ѕѕ %s \ n ", name) ;
return ;

if ((stbuf. st_mode & Ѕ IFМТ) = S_IFDIR)


dirwalk(name , fsize) ;
printf( "%81d %s \ n " , stbuf.st_size , name);

Функцијата dirwalk е општа рутина која применува функциј а врз секоја да­
тотека во еден директори ум. Го отвора директо риу мот, поминува н из датоте ­
ките во него, повикувајќи ја функцијата за секоја од нив, потоа го затвора ди рек­
ториумот и се вра ќа. Бидејќи fsize ја п овикува dirwalk за секој директор иум,
двете фу нкции рекурзивно се повик уваат една со д руга.
214 Системски интерфејс на UNIX Глава 8

#define МАХ_РАТН 1024

/* dirwalk: nримени ја fcn врз сите датотехи во dir */


void dirwalk(char *dir 1 void (*fcn) (char *))
{
char name[МAX_PATH] ;
Dirent *dp ;
DIR *dfd ;

if ((dfd = opendir(dir)) NULL) { ==


fprintf(stderr 1 " dirwalk: can 1 t open %s\n" 1 dir) ;
return ;

whi1e ((dp = readdir(dfd)) != NULL) {


if (strcmp(dp->name 1 " . " ) ==О 11 strcmp
(dp->name 1 " •• " ))

continue ; /* ski p self and parent */


if (strlen(dir)+strlen(dp->name)+2 > sizeof(name))
fprintf(stderr 1 " dirwalk : name % ѕ % ѕ too long\n" 1

dir 1 dp->name) ;
else {
sprintf(name 1 "% ѕ/ % ѕ " 1 dir 1 dp->name);
( *fcn) (name) ;

closedir(dfd);

Секој повик до readdir вра ќа покажувач кон инфо рмација за наредната


датоте ка , или NULL кога нема преоста нато повеќе датотеки . Секој директори­
ум секогаш содржи записи за себе, наречени ".",и за неговиот родител " . . ";
овие мора да бидат прескокнати, или програмата ќе врти до бесконечност .
Се до ова ни во, кодот е независен од то а како се форма тирани ди ре ктор и ­
уми те. Сл едниот чекор е да се претстават минималните верзии на opendir,
readdi r и closedir з а еден посебе н систем . Рутините што следат се за систе­
мите v ersion 7 и System v UNIX; тие ја користат директориумската информација
од заглавјето <sys/ dir . h>, која и згледа н а следниов на ч ин :

#ifndef DIRSIZ
#define DIRSIZ 14
#endif
str uct direct { /* дирехт ориумсхи заnис */

ino_t d_ino ; /* број на inode */


char d_name[DIRSIZ] ; / * допгите иниља не содржат '\0 1 */
};
8.6 Пример - Л истање содржина на директориуми 215

Некои верзии на системот дозволуваат м ногу подолги имиња и имаат по­


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

Типот ino_t еtypedef кој го опишува индексот во листата на inode -oви .


Истиот е од тип unsigned short за системите кои обично ги користиме, но тоа
не е тип на информација која треба да се вметне во една програма; за различни
системи може да биде различна, така што подобро е да се користи typedef .
Целосното множество на " системски" типови се наоѓа во <sys /types . h>
opendir го отвора директориумот, потврдува де ка датотеката е дире ктори ­
ум (овој пат преку системскиот повик fstat, кој наликува на stat освен во тоа
што се применува врз датотечен дескриптор ) , алоцира директориумска стру к ­

тура и ја запишува информацијата:

int fstat(int fd , struct stat *);

/* opendir: отвора дирехториум за readdir nовихувааа* /


DIR *opendir(char *dirname)
{
int fd;
struct stat stbuf ;
DIR *dp;

if ((fd = open(dirname , O_RDONLY , 0)) == -1


11 fstat(fd , &stbuf) == -1
1 1 (stbuf.st_mode & Ѕ_IFМТ) != S_IFDIR
11 (dp = (DIR *) malloc(sizeof(DIR))) -- NULL)
return NULL ;
dp->fd = fd ;
return dp ;

closedir ја затвора директориумската датотека и ја ослободув а меморијата:

/* closedir: затвора дирехториум отворен со opendir */


void closedir(DIR *dp )
{
if (dp) {
close{dp->fd) ;
free(dp);

На крај, readdir ја употребува read за читање на секој директориумски за­


пис . Ако местото за директориуми не е тековно во употреба (бидејќ и н екоја
датотека била отстра нета) , inode бројот е нула, а таа локација се прескокнува.
Во спротивно, inode бројот и името се сместуваат во static структура, а еден
покажувач кон неа се враќа на корисникот. Секое пови кување ја пребриш ува
информацијата од претход ни от.
216 Системски интерфејс на UNIX Глава 8

#include <sys / dir .h> /* покалиа директориумска структура * /


/* readdir: р едо спе дно чита директориумски записи */
Dirent * readdir (DIR *dp)
{
struct dir ect di rbuf; /* покаnна директориумска
струкоrура *1
static Dirent d ; / * враќа : преносна структура */

while (read(dp->fd , (char *) &dirbuf , sizeof(dirbuf))


== sizeof(dirbuf)) {
if (dirbuf . d_ino == 0) /* slot not in use */
continue ;
d . ino = dirbuf. d_ino ;
s t r ncpy( d . narne, dirbuf.d_name, DIRSIZ) ;
d . name (DIRSIZ] = '\ 0' ; /* осигуруаа крај */
return &d ;

return NULL ;

Иако програмата fsize е помалку специјализирана , сепак, илустрира не­


колку значајни идеи. Прво, многу од програмите не се "системски п рограми";
ти е само користат и нформации кои се одржуваат од страна на оперативниот
систем. За такви програ ми, најзначајн о е репрезентацијата на информацијата
да се пој авува само во станда рдните заглавја, а програмите да ги вклучуваат ис­
тите нам е сто да ги вметнуваат декларациите во себе . Вториот заклучок е дека
со внимани е е возможно да се креира интерфејс за објекти зависни од системот,
кој сам за себе е релативн о независен од системот. Функциите од стандардната
библиотека се добри примери за тоа.

Вежба 8-5. Пром е нете ја програмата fsize да ја печати останатата информа­


ција содржана во inode записот .

8.7 Пример - Алокатор (Заземач) на мемориски простор

Во Глава ѕ, обработивме многу огранич ен куп-ориентиран алокатор.


Верзијата што ќе ја нап и шеме сега е без ограничувања. Повикувањата до
malloc и free може да се направат по кој било редослед; malloc прави пови­
кувања кон оператив ниот систем за да алоцира повеќе меморија во согласност
со потребите. Овие рутини илустрираат некои работи кои се земаат предвид
при п и ш увањето н а код зависе н- од-машин ата на начин релативно незави­

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

структу ри , у нии и typedef.


Наместо да ал оцира од вком пајлирана низа со фиксна големина, malloc
ќе бара простор од оперативниот систем сп оред потребите. Бидејќи и други
807 При м е р- Ало катор (Заз е мач) на мемориск и просто р 2 17

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


на овој алокатор, просторот кој го управува malloc може да биде испрекинат о
Така, слободната меморија на malloc се чува како листа од слободн и блоков и
(.,листа слободни ") о Секој блок содржи големина, покажувач кон нареден бл ок
во листата и самиот простор о Блоковите се чуваат во растечки редосл ед спо ­
ред мемориската адреса, а последниот блок (највисоката адреса ) покажува
кон првиот о

Сло~днi~:Јn4=91··· · · ·
...... .
о .. 00 .. uве uве uве
.. ... .. ..
00 .. .... о use
. ...... . .
.. 00 00 00 о

С:=Ј слободна, во сопственост на maiJoc


[Ш}l'Ѕе] зафатена, во сопственост на malloc
1 : ::::::: 1 незафатена од malloc

Кога ќе се направи барање, листата слободни се пребарува се доде ка не се


пронајде доволно голем блок о Овој алгоритам се нарекува .,прво погодно" (анго
" first fit" ), во контраст на.,најпогодно"(а нго "best fit " ) кој го бара најмалиот блок
кој ќе го задоволи барањето о Во случај блокот да е со точно бараната големина,
се откинува од листата и се враќа на корисни кот о Ако блокот е преголем , тој се
разделува и соодветната количина се враќа на корисникот, додека остатокот
останува во листата слободни о Ако не се пронајде доволно голем блок, се з ем а
друго големо парче од оперативниот систем и се врзу ва во л и стата сл обод н и о
Ослободувањето, исто така, предизвикува пребару вање низ листата слобод­
ни, со цел да се н ајде соодветно место каде ќе се вметн е блокот кој се ослободу­
ва о Ако блокот кој се ослободува, е сосед на слободен блок од која било страна,
двата се споју ваат за да формираат еден поголем бл ок, со цел да се избегне
преголема фрагментација на меморијата о Определувањето на соседство е лес­
но бидејќи листата слободни се одржува во растечки редослед на адресите о
Еден проблем, на кој укажавме во Глава 5, е да се обезбеди мемориј ата која
се враќа од malloc, да се порамни соодветно за објектот кој ќе биде зачу ван
во неа о Иако машините варираат, за секоја машина постои еден најограничен
тип: ако најограничениот тип може да се смести на одредена адреса, ќе можат
и сите други типови о На некои машини, најограничен тип е double; на д руги
int или, пак, long о
Еден слободен блок содржи покажувач кон наредниот блок во с инџ ирот,
запис за големината на блокот, и самиот слоб оден простор; контролната ин­
формација што е на почетокот се нарекува "заглавје" о За да се поедностави
порамнувањето, сите блокови се цел број пати од голем ината на загла вјето и
218 Системски интерфејс на UNIX Глава 8

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


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

typedef long Al.ign ; / * за nорамиуаав.е хон оrраничуаав.ата


на long* /

union header /* заrлааје на блох*/


struct {
union header *ptr ; /* наредниот блох
(ако nостои) во листата слободни */
unsigned size ; /* големина на овој блох */
ѕ ;

Align х ;/ * накетнуваље на nоракнуаање на блохови */


};

typedef union header Header ;

Полето Align никогаш не се користи; единствено служ и за да го порамни


секое заглавје според најлошото мож но о граничување .
Кај malloc, бараната големина во знаци се заокружува на соодветниот број
од големини на заглавје; блокот кој се алоцира содржи уште една единица, за
самото заглавје, и таа е вредноста запишана во полето size од самото за главје.
Покажувачот вратен од malloc покажу ва кон слободното место, а не ко н са­
мото заглавје. Корисникот може да направи сешто со бараниот простор, но
ако што било се запише надвор од границите на алоцираниот простор, листата
најверојатно ќе се измеша .

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

SIZe
.
-.....__адреса КОЈа се предава на корисникот

Блок доделен од malloc

Полето за големина е потребно бидејќи блоковите контролирани од malloc


не мора да бидат континуирани - не е возможно да се пресметуваат големини
со п окажувачка аритметика .
8.7 Пример- Алокатор (За зем ач) на мемориски простор 219

Променливата base е потребна за започнување. Ако freep е NULL, како што


е случај при првиот повик на m.alloc, тогаш се креира дегенерирана листа сло­
бодни; истата содржи еден блок со големина нула, и покажува кон себеси. Во
секој случај, листата слободни потоа се пребарува . Барањето за слободно место
со посакуваната големина започнува од точката (freep) каде бил пронајден по ­
следниот блок; оваа стратегија помага во одржувањето на хомогена листа . Ако
се пронајде премногу голем блок, десниот крај се враќа на корисникот; на тој на ­
чин заглавјето од оригиналот треба само да ја промени својата големина. Во се­
кој случај, покажувачот кој се враќа на корисникот покажува кон слободно место
во рамките на блокот, кое за почнува една единица после заглавјето.

static Header base ; /* nразна листа за nочеток */


static Header *freep = NULL ; /* nочеток на листа слободни*/

/* malloc: оnштонаменсхи алокатор на меморија */


void *malloc(unsigned nbytes)
{
Header *р , *prevp ;
Header *moreroce(unsigned) ;
unsigned nunits ;

nunits = (nbytes+sizeof(Header)-1)/sizeof(header) + 1;
if ((prevp = freep) ==
NULL) {/*се уште нема листа
на слободни * 1
base . s . ptr = freeptr = prevptr = &base;
base . s.size = О ;

for (р = prevp->s.ptr ; ; prevp = р , р = p->s . ptr) {


if (p->s.size >= nunits) { /* доволно голема */
if (p->s . size == nunits) /* точно */
prevp->s . ptr = p- >s . ptr ;
else { /* алоцирај го десниот крај */
p->s.size -= nunits ;
р += p->s.size ;
p->s.size = nunits ;

freep = prevp;
return (void *) (р+1) ;

if (р = freep) /* измината е целата листа на слободни*/


if ((р = morecore(nunits)) == NULL)
return NULL ; /* нема останато ниту еден */

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


за тоа како го прави тоа ва рираа т од систем до систем . Бидејќи барањето мемо-
220 Системски интерфејс на UNIX Глава 8

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


при секој повик кон malloc, така morecore побарува барем NALLOC единици;
овој поголем блок ќе се дели според потребите . После поставувањето на по­
лето за големина,morecore ј а вметнува н а сцена додатната меморија со повик
кон free.
UNIX- овиот системски повик sbrk (n) враќа покажувач кон n дополнител­
ни бајти меморија. sbrk враќа - 1 ако нема простор, иако NULL би можел да
биде подобар дизајн. Вредноста -1 мора да се претопи во char* за да може
да се споредува со вратената вредност . Повторно, претопувањата ја прават
функцијата релативно имуна на деталите за покажувачката репрезентација на
различни машини. Сепак, постои една претпоставка, дека споредувањето на
покажувачи кон различни блокови вратени од sbrk секогаш ќе има смисла . Тоа
не е загарантирано со стандардот, кој дозволува споредувања само на покажу­
вачи кои припаѓаат на иста низа . Така, оваа верзија на malloc е преносна само
за машини во кои општото споредување на покажувачи има смисла.

#define NALLOC 1024 /* мининален број на единици кој


би се побарувал */

/* morecore: бараље повеќе менорија од системот */


static Header *morecore(uпsigned nu)

char *ср , *sbrk(int) ;


Header *up ;
if (nu < NALLOC)
nu = NALLOC;
ср = sbrk(nu * sizeof(Header)) ;
if (ср == (char * ) - 1) / * воопшто нема простор */
return NULL ;
up = (Header *) ср ;
up- >s.size = nu ;
free ( (void *) (up+1)) ;
return freep ;

Самата free е последното нешто . Ја пребарува листата слободни, почну­


вајќи кај freep, барајќи место каде ќе се вметне слободниот блок. Тоа е или
помеѓу два постојни блока или на крајот на листата. Во секој случај, ако блокот
кој се ослободува е соседен на некој друг, тие се комбинираат. Единствените
проблеми се одржувањето на покажува чите да покажуваат кон вистинските
нешта и големините да бидат точни.
8.7 Пример - Алокатор (Зазема ч ) на мемориски простор 22 1

/* free: смес~ува блох ар во листата слободни */


void free(void *ар)
{
Header *bp, *р;

bp = (Header *)ар - 1; /* nохажувач хон заглавје


на блох */
for (р = freep ; ! (bp > р && bp < p->s.ptr) ; р = p->s.ptr )
if (р >= p->s.ptr && (bp > р 11 bp < p->s . ptr))
break; /* ослободен блох на nочеток= :крај на CUI»fATa*/

if (bp + bp->size == p->s.ptr) { /* nридружи на горен nbr * /


bp->s.size += p->s.ptr- >s.size;
bp->s.ptr = p->s.ptr->s.ptr;
else
bp->s.ptr = p->s.ptr;
if (р + p->size ==
bp) { /* nридружи на долен nbr */
p->s . size += bp->s.size;
p->s . ptr = bp->s.ptr;
else
p->s . ptr = bp;
freep = р;

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


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

на многу мал дел од програмата. Користењето на typedef и union се справу­


ва со порамнувањето (под претпоставка дека sbrk обезбедува соодветен по­
кажувач) . Претопувањата овозможуваат конверзиите на покажувачите да се
прават експлицитно и дури се справуваат со еден лошо дизајниран системски
интерфејс. Иако деталите овде се поврзани со мемориска алокација, општиот
пристап е применливи на други ситуации.

Вежба 8-6. Функцијата од стандардната библиотечна calloc (n, size) враќа по­
кажувач кон n објекти со големина size, со меморија иницијализирана на нула.
Напишете ја calloc, употребувајќи ја malloc или модифицирајќи ја.

Вежба 8-7 . malloc ги прифаќа барањата за големина без да ја провери нив­


ната веродостојност; free верува дека блокот кој треба да го ослободи содржи
валидно поле за големина. Подобрете ги овие рутини така што ќе се занимава­
ат повеќе со проверка на грешки.

Вежба 8-8. Напишете рутина bfree (р, n) која ќе ослободи произволен блок р
од n знаци во листата слободни одржува на од malloc и free . Со користење на
bfree, корисникот може во секое време да додаде статичка или надворешна
низа кон листата слободни.
Додаток А: Референтно упатство

А1. Вовед

Ова уnатство го оnишува јазикот С како што е сnецифициран со nредлог-текстот


поднесен за одобрување до ANSI на 31 октомври 1988 година , како "Американски
национален стандард за информациски системи - nрограмски јазик С, Хз . 159-
1989. " Ова уnатство е интерnретација на nредложениот стандард , а не самиот
стандард, иако водевме грижа да го наnравиме сигурен водич за јазикот.
Во најголемиот дел, овој документ ја следи широката форма на стандардот,
кој, nак, ја следи содржината на nрвото издание од оваа книга, иако организа­
цијата по малку се разликува. Освен во преименувањето на неколку nродук­
ции, и неформализирањето на дефинициите за лексичките белези или за nре­
тnроцесорот, граматиката на јазикот што овде е nриложена е еквивалентна со
таа што ја дефинира стандардот.
Низ целото упатство , коментарите се вовлечени и напишани со помал фонт ,
како што е слу чајот овде. Најчесто, овие коментари ги нагласуваат нештата во
кои ANSI стандардот за С се разликува од јазикот кој го дефинира првото изда­
ние на оваа книга, или од подобрувањата кои дополнително беа воведени од
некои компајлери .

А2. Лексички конвенции

Една nрограма се состои од една или повеќе единици за превод (преведување)


кои се зачувани во датотеки. Се nреведува во неколку фази кои се опишани во
А12 . Првите фази изведуваат лексички трансформации од ниско ниво, ги извр­
шуваат директивите од линиите на кодот кој заnочнува со знакот 1, како и де­
финирањето на макроата и нивната експанзија . Откако ќе за врши nретnроцеси­
рањето од А12, nрограмата е редуцирана на ниво на секвенца од белези.

А2.1 Белези

Постојат шест класи на белези: идентификатори, клучни зборови, констан­


ти , стрингови, оnератори и разните сеnаратори. Бланко знаците, хоризон -

223
224 Референтно упатство Додаток А

талните и вертикалните табови, знакот за нова линија , нова страна и комента­


рите опишани подолу (заедно именуван "празно место") се игнорираат освен

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


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

д2.2 Коментари

Знаците /* означуваат почеток на коментар кој се завршува со знаците * 1.


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

д2.3 Идентификатори

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


на идентификаторот мора да биде буква; знакот за подвлечено _ се смета за
буква. Постои ра злика помеѓу големите и малите букви. Идентификаторите
можат да имаат каква било должина, а за внатрешните идентификатори зна­
чајни се барем првите 31 знак; кај некои имплементации тој број е поголем.
Внатрешните идентификатори ги вклучуваат претпроцесорските макроимиња
и сите други имиња кои не поседуваат надворешно поврзување (All. 2) .
Променливите со надворешно пов рзување се поограничени : кај н ив некои им­
плементации може да го сведат само на шест бројот на почетни знаци се значај­
ни и може да ги и гнорираат разликите во големината на буквите.

д2.4 Кnучни 3борови

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


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

auto double int struct


break else long switch
саѕе enum register typedef
char extern return u nion
const float short unsigned
continue for signed void
default goto sizeof volatile
do if static while
Ао2 Лексички конвенции 225

Некои имплементации, исто така, ги резервираат и зборовите fortran и asm o


Клучните зборови const, signed и vo1ati1e се нови воведени со AN $1
стандардот; enwn и void се нови воведени од првото издание на книгата, н о
во општа употреба; entry, некогаш беше резе рвиран збор, но никогаш не се
употребуваше, па веќе не е во групата на резервиран и зборови о

А2.5 Константи

Постојат неколку видови на конста нти о За секоја постои податочен тип ;


Параграф А4 о 2 ги обработува следниве основни типови:

константа:

целобројна-константа
знаковна - конста нта

реална -ко нстанта

енумераци ска - констант а

А2.5. 1 Целобројни константи

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


та дека е запишана во октална нотац ија а ко за почнува со водечка о, во сnро ­
тивно се смета за декадна о Окталните константи не ги содржат цифрите 8 и 9о
Секвенца од цифри која започнува ох или оХ се смета дека е хексадекаден цел
број о Хексадекадните цифри ги вклучуваат буквите од а до f или од А до F за
претставување на вредности од 10 до 15 о

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


за да се означи дека е неозначена (анrо unsigned) о Исто така, може да биде
проследена и со буквата 1 или L за да се означи дека станува збор за 1ong кон­
станта о

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


наставка о (види Параграф А4 за подетална дискусија на типовите) о Ако е без
наставка и ако е декадна, вредноста на константата може да биде 11ретставе­
на со еден од овие податочни типови : int, 1ong int 1 unsigned long int o
Ако нема наставка и ако е октална или хексадекадна 1 вредноста на константата
може да биде претставена со еден од овие типови: int , unsigned int 1 1ong
int , unsigned 1ong int o А ко има наставка u или u во тој случај unsignedint ,
unsigned 1ong int о Ако има настав ка 1 или L тогаш 1ong int , unsigned 1ong
1

int о Ако целобројната константа има наставка UL , тогаш таа е од тип unsigned
1ongo
Објаснувањето за типовите н а целоброј ните константи оди подалеку од првото
издание, кое единст ве но ги сметаше големите целобројни константи како да
се од ти п 1ong о Наставк ите U се нови о
226 Референтно упатство Додаток А

А2.5 .2 Знаковни константи

Знаковната константа претставува секвенца од еден или повеќе знаци


омеѓена со апострофи како на пример 'х' . Вредноста на знаковната константа
која има само еден знак , е бројната вредност на знакот во знаковното множе­
ство на компјутерот во време на извршување на програмата . Вредно ста на по­
веќезнаковна константа за виси од имплементацијата .
Знаковните константи не содржа т зна к ' или знак за нова линија; за да може
да ги претставиме нив , како и некои дру ги слични знаци , може да се ко р и стат

следниве излезни секвенци :

нова линија N L (LF) \n контра н из \\


хоризонтален таб нт \t пра шалник ? \?
вертикален таб VT \v апостроф \'
бришење наназад вѕ \b наводник \"
враќање на почеток на редот CR \r октален бр ој 000 \ ооо

НОВ ЛИСТ FF \f хексадекаден број hh \ xhh


звуЧен аларм BEL \а

Секвенцата \ооо содржи контраниз знак проследен со 1, 2 или з октални ци­


фри , кои ја определуваат вредноста на посакуваниот зна к. Чест пример за
ваква конструкција е \О (не проследена со цифра) , којашто го определува
знакот NUL . Се квенцата \xhh содржи контраниз проследен со х, п а проследен
со хексадекадни цифри , кои ја определуваат вредноста на посакуваниот знак.
Не постои ограничување за бројот на цифрите , но однесувањето во случаите
каде вредноста на резултантниот знак ја преминува вредноста на најголемиот
знак, е недефинирано. И за окталните и за хексадекадните излезни знаци , ако
имплеметацијата го третира типот char како означен (анг. slgned) , вредноста е
значајно-проширена и се смета ка ко да е претопена во тип char. Во случаите
во кои знакот кој што следи после\ не припаѓа на претходно наведените знаци,
однесувањето е недефинирано.
Кај некои имплементации, постои проширено множество на з наци кои не
можат да бидат претставени со типот char . Константа која припаѓа на ова про­
ширено множество се запишува со водечко L, на пример, L ' х', и се нарекува
широка (анг.wide) знаковна константа . Таквата константа е од тип wchar_ t ,
сложен тип дефиниран во стандарното заглавје <stddef. h> . Како и кај обич­
ните з наковни константи, може да се користат октални и хексадекадни излезни

секвенци ; ефектот не е дефиниран ако наведената вредност ја преминува мак­


сималната дефин и рана со wchar_ t.
Некои од овие излезн и секвенци се нови, посебно хе ксаде кадната знаковна
репрезентација . Широките зна ци, исто така, се нови . Знаковните множества
кои вообичаено се користат во Аме рика и За падна Европа можат да се енко­
дираат, да се вклопат со типот char ; главната причина за додавање на типот
wchar_ t беше да се сместат азиските јази ци .
А.2 Лексич к и конвенции 227

А2.Ѕ.З Реални константи

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


точка , децимале н дел , е или Е , опционално означен (со+ или -) целоброен
експонент и опц ионална наставка за тип која може да биде f , F, 1 или L .
Целобројниот и децималн иот дел се состојат од секв ен ца на цифри . Може да
се случи кој било од нив (но н е и двата одедн а ш) да недостасува; може да не­
до стасува дец ималната точка, може да недостасува е зад но со експо нен тот (но
не и двете одеднаш) . Типот се о пр едел у ва според на ставката; F или f се смета
за float , L или 1 за 1ong doub1e , во спроти в но за doub1e .
Суфиксите кај реал н ите констан ти се н о ви .

А2.5.4 Енумерациски константи

Идентификаторите декла риран и како енумератори (види Параграф Ав . 4)


се константи од тип int.

А2 .6 Стрингови константи

Стри нговите конс та н ти се секвенци од знаци оградени со д војни нав одн иц и


како во " .. . ". Стри н гот има ти п " низа од знаци" и static класа н а мемориски
простор (види го Параграф А4 подолу) и е иницијализирана со н аведе н ите
знаци . Дали идентични стринг константи ќе се земат за р азл ични за виси од им­
плементацијата , а случаите во кои програмата ќе се обиде да направи п ромена
на една стринг константа , се недефинира н и .
Соседни стри нг константи се надоврзуваат во еден единствен стри нг . П осле
надоврзувањето , на крајот од резултантниот стринг се додава \ 0, со цел про­
грамите што го скенираат стрингот да можат да го определат н е говиот крај.
Стринговите константи не содржат знак за нова линиј а или з нак за наводник ;
со цел да ги претставиме нив , ги употребуваме истите излез ни секвенци кои се
достапни и за зн ако вните константи .

Како и кај знаковните константи, стри нговите константи кои припаѓаат на


п ро ширено з наковно множество се за п ишува ат со L , како во L "x". Стрин говите
кои содржат широки знаци имаат тип " низа од wchar_ t ". Надоврзувањето н а
обичен и широк стринг се недефинира ни .
Сп е цифика цијата дека стринговите конста нт и н е м ора да б идат различни , и за­
б раната за нивн а модификација, се н ови воведени со ANS I стандардот , како
што е и надоврзувањето на соседни стр и н гови константи. Широките стри нго­
ви кон ста нти, исто така, се нов и .
228 Референтно упатство Додаток А

А3. Синтаксичка нотација

Во синтаксичката нотациј а која се користи во овој пр ирач ник синта ксичките


категории се означени со курзивен тип , а константните зборови и знаци во пе­
чатарсхи стил . Алтернати в ните категории обична се распоредуваат во оддел­
ни редови ; во мал број случаи, поголема множество од кратки алтернативи се
презентира во еден ред, обележано со изразот " еден од ". Опционален тер­
минирачки и нетерми нирачки симбол ја носи вредноста на индексот " opt", па
така, на при мер,

{ изразорt }

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


на во Пара граф АlЗ.
За ра злика од граматик ата nриложе н а во првото издание на оваа кни га, гра­
матиката во ова и здание експлицитно ги објас нува nр ио ритетот и а социјатив­
н оста н а о пе ратор ите .

А4. Значење на идентификаторите

Идентификатори , или имиња, се однесуваат на најразлични нешта: функ­


ции ; тагов и на структури , унии и енумерации; членови на стр уктури или

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


Еде н објект , по некогаш наречен п роменлива, претставува локација во мемо­
ријата, и неговата интерп рета ција за ви си од два главни атрибута : од неговата
класа на мемориски простор и од неговиот mun. Мемориската класа го опре­
делува животни от век на меморијата придруже на со именуваниот објект ; ти­
пот го определува типот на вредноста која се чува од именуваниот објект. Едно
име, исто така , има и делокруг, т . е. областа од програмата во која егзистира
објектот, и врска која што определува дали истото име во друг дело круг се од­
несува на истиот објект или функција. Делокругот и поврзувањето се објаснети
во Параграф Al l .

А4. 1 Класа на мемориски простор

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


статички. Неколку клучни зборови , заедно со контекстот во кој објектот е де­
клариран , ја специфицираат н еговата мемориска класа. Автома тските објекти
се локал ни за еден блок и се ослободуваат п ри излез од бпо кот . Ако н е се спе­
цифицира мемориска класа, или ако е наведе н клучниот збор auto , тогаш де­
кларациите во рамките на блокот автоматс ки креираат објекти . Објектите де­
кларирани како register се автоматски и (ако е тоа возможно ) се с кладираат
А.4 Значење на идентификаторите 229

во регистрите на компјутерот.
Статичките објекти можат да бидат локални во однос на еден блок или надво­
решни во однос на сите блокови , но и во двата случаја ги задржуваат нивните
вредности при излез и повторен влез во функциите и блоковите . Во рамките
на еден блок, вклучувајќи го и блокот кој го обезбедува кодот на функцијата ,
статичките објекти се декларираат со кл учниот збор static. Обј ектите декла­
рирани надвор од сите блокови, на исто ниво со функциските дефиниции, се­
когаш се статички. Тие можат да се направат л о калн и во посебна един ица за
преведување , со користење на клучни отзбо р sta t i c; тоа им дава в натрешно
поврзување. Тие стануваат глобални за цела програма ако при нивната декла­
рација експлицитно не се наведе мемориска класа , или, пак, со ко ристење н а
клучниот збор extern ; тоа им дава надворешно поврзување .

А4.2 Основни податочни типови

Постојат неколку фундаментални типо ви. Ста ндардн ото за главје <l imi ts.
h> опишано во Додаток Б ги дефинира најголеми те и најмал ите можни вр еднос­
ти за секој тип во рамките на една л окал н а имплементација . Броев ите да дени
во Додаток Б ги прикажуваат најмалите прифатл ив и го лем и ни .
Објектите декларирани како знаци (char) се доволно големи да го склади­
раат кој било член од знаковното множество на компјутерот . Ако не1<0ј знак
од тоа множество се складира во char објект, негова та вредност е една ква
на целобројниот код за тој знак и тој код е ненегативен . Други величин и може
да се чуваат во char променливи , но можниот опсег на вредн остите, а п осеб­
но дали тие вредности се оз начени , за виси од имплементацијата на локалната
платформа . Неознач ените знаци декларирани како uns igned char зафаќаат
исто количество на меморија како и обичните знаци, н о тие секогаш се нене­
гативни ; експлицитно означените знаци , дефинирани како signed c har, исто
така, зафаќаат исто количество на мемориј а како и обичните знаци.
Типот unsigned char не беше обработен во пр вото издан ие на оваа к н ига,
но се употребуваше во н еа . Ти пот
signed char е нов.
Покрај
char типовите , достапни се најмногу три големи ни на цели броеви ,
декларирани како short int, int и long int . Обичните int објекти имаат
природна големина која за виси од локалната архитектура ; ос танатите гол еми­
ни се воведени за да задоволат одредени специј ални потреб и . Подол гите .цели
броеви обезбедуваат простор со голе,_, барем колку и просторот за пократки­
те цели броеви, но во зависност од локалната имплементација об ич ните цели
броеви може да бидат еквивалентни и на кратките и на долгите цели броеви.
Сите int типови претставуваат исклучително означе ни вредности , освен ако
тоа не е наведено поинаку .

Неозначените цели броеви, декларирани со кл уч ниот збор u nsigned , се


приклонуваат на аритметика со модул 2" каде n е бројот на битски во репре­
зентацијата, na поради тоа аритметиката на неозначени величини ни ко г;з ш н е
230 Референтно упатство Додаток А

предизвикува пречекорување (анг. overflow) . Множеството на ненегативни


вредности кои може да се чуваат во еден означен објект е подмножество од
вредностите кои може да се чуваат во соодветен неозначен објект , и репрезен­
тацијата за вредностите кои се поклопуваат е идентична.
Кој било реален број со единична прецизност (float) , реален број со двој­
на прецизност (double) и реален број со екстра прецизност (long double)
можат да бидат синонимни, но оние со повисока прецизност се барем прециз­
ни колку тие пред нив.
Типот long double е нов . Првото издание дефинираше тип long float кој
беше еквивалентен со double; таа декларација е повлечена.
Енумерациите се уникатни податочни типови кои имаат целоброј ни вред­
ности; со секоја енумерација се асоцира и множество од именувани констан­
ти (Параграф АЅ . 4) . Енумерациите се однесуваат како целите броеви, но во
општ случај компајлерот издава предупредување кога на еден објект од кон­
кретна енумерација му се доделува нешто што е различно од нејзините кон­
станти.

Бидејќи објектите од овие типови може да се интерпретираат како броеви,


за нив ќе велиме дека се аритметички типови. Типовите char и int од сите
големини, со или без знак , како и енумерациските типови ќе бидат нареку­
вани под заедничко име интегрални типови . Типовите float, double и long
double ќе бидат нарекувани реални типови.
Типот void означува празно множество на вредности. Се користи како по­
вратна вредност кај функциите кои не генерираат резултат .

А4.3 Изведени тиnови

Освен основните типови , постои концептуално бесконечна класа од изве-


дени типови образувани од основните типови на следниот начин:
низи од објекти од одреден тип ;
функции кои враќаат објекти од одреден тип;
покажувачи кон објекти од одреден тип ;
структури кои содржат секвенца на објекти од разл ични типови ;
унии способни во еден момент да содржат еден од неколку објекти од раз­
лични типови .

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


рекурзивно.

А4.4 Квалификатори на типови

Тиnот на еден објект може да има и додатни квалификатори. Декларирањето


на еден објект како const означува дека неговата вредност нема да може да се
менува; неговото декларирање како volatile означува дека објектот поседу-
А.б Конверзии 23 1

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


фикатор не влијае на опсегот н а вредности или на аритметичките карактерис­
тики на објектот . Квалификаторите се дискутирани во Параграф Аѕ . 2.

АЅ Објекти и л вредности

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


(анг. lvalue) е израз кој се однесува на тој објект. Очигледен пример за изр аз
со л вредност е декларација на идентификатор со соодветен тип и мемориска
класа. Постојат оператори кои враќаат л вредности ; ако Е е израз од тип по­
кажувач, тогаш *Е е израз за л вредноста која се однесува на објектот кон кој
покажува Е . Терминологијата "л вредност" доаѓа од изразот за доделување El
= Е2 во кој левиот операнд El мора да биде л вредн ост израз. Дискусијата за
секој оператор специфицира дали тој очекува л вредност операнди и дали како
резултат враќа л вредност .

Аб Конвер3ии

Н екои оператори, во зависност од нив ните операнди , можа т да пред изви ­


каат конверзија на вредноста на еден операнд, од еден во друг тип . Ова погла­
вје го објаснува резултатот што е за очекување од таквите конверзии. Параграф
А б. 5 ги сумира конверзиите кои се потребни за повеќето обични оператори;
секој оператор ќе биде подробно објаснет.

Аб.1 Интегрално нагорно претопување (промоција)

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

спротивно неговата вредност се претвора во unsigned int. Овој процес се


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

Аб.2 Интегрални конвер3ии

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


со пронаоѓање н а најмалата ненегативна вредност која е конгруентна со таа
вредност, модуло најголемата вредност (зголемена за еден) што може да се
претстави со неозначениот тип . Кај двокомплементната претстава тоа е ек­
вивалентно со кратење во лево ако битскиот шаблон од неозначениот тип е
232 Референтно упатство Додаток А

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

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


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

Аб.З Цели и реаnни броеви

Кога вредноста на еден реален тип се претвора во интегрален тип, се отфр­


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

интегрален ти п се недефинирани. Конкретно, резултатот од претворање на


негативни реални вредности во неозначени интегрални типови не е специфи­
циран.

Кога вредн ост на интегрален тип се претвора во реален, и таа вредност не


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

А6.4 Реаnни типови

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


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

та. Кога · р еален број со погол ема прециз ност се претвора во тип со пониска
прециз ност и вредноста е во репрезентативниот опсег резултатот може да биде
следната поголема или помала репрезентативна вредност . Во случај во кој ре­
зултатот е надво р од о псегот однесувањето е неде финирано.

Аб.Ѕ Аритметички конверзии

Многу оператори предизвикуваат конверзија и го добиваат типот на резул­


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

-Прво, ако кој бил о од оп ера ндите е од тип long d o uble , друг иот се пре­
твора во long double .
-Инаку , ако кој било операнд е од тип double, другиот се претвора во
double .
-Инаку, ако кој било операнд е од тип float , другиот се претвора во float .
-И наку , и врз двата о п еранда се изведуваат инте грални пр омоции; по -
А .б Конверзии 233

тоа ако едниот операнд е од тип unsigned long int , другиот се претвора во

unsigned long int.


-Инаку, ако едниот операнд е од тип long int , а другиот е од тип unsigned
int, резултатот зависи од тоа дали вредностите на unsigned int може да се
претстават со long int ; unsigned int опера ндот се претвора во
ако е така

long int ; long int .


во спротивно и двата се претвораат во

-Инаку, ако едниот операнд е од тип long int , другиот се претвора во


long int .
-Инаку , ако кој било операнд е од тип unsigned int , другиот се претвора
во unsigned int.

-Инаку, и двата операнда имаат тип int.


Овде има 2 промени. Прво, аритмети ката врз floa t оnеранди nретn очита
единична прецизн ост, а не двојна ; nрвото издание сnецифицираше дека це­
лата аритметика на реални броеви е со двој на nрецизн ост . Второ , при ком­
би нирање на пократките н еозначени тиnови со поголеми означен и типови ,
не се гарантира unsigned карактеристика на резултатот . Во првото издание
секогаш доминираа неозначените тиnови . Н ов ите nравила се за нија нса nо­
комплицирани, но на некој начин ги намалуваат изненаду вањата што можат
да се случат кога неозначена вредн ост ќе се сретне со оз начена . Не оч е кувани
р езултати, сепак, можат да се случат во случај кога неозначен изра з ќе се сnо­
реди со означен израз од иста големина.

Аб.б Покажувачи и цели броеви

Израз од интегрален тип може да се додаде на, или да се одземе од пока­


жувач ; во таков случај и нтегралниот израз се конвертира по спецификациите
одредени со дискусијата за операторот за собирање (Параграф А7 . 7) .
Два покажувача кон објекти од ист тип во иста низа може да се одземаат ;
резултатот се конвертира по спецификациите одредени со дискусијата за опе­
раторот за одземање (Параграф А 7 . 7) .
Цел оброен констан те н израз со вредност о, или та ков ист израз претопен
во тип void* , може да се конвертира , преку претопување, доделу вање или
споредување во покажувач од кој било тип . Ова произведува нулти по кажувач
кој е еднаков со друг нулти покажувач од истиот тип , но нееднаков со кој било
покажувач кон функција или објект .
Кај покажувачите се дозволени и некои други кон верзии , но тие зависат од
имплементацијата. Мора да бидат специфицирани со експлицитен оператор за
конверзија на типови , т . н . cast (оператор за претопува ње) (Параграф А7. ѕ
и А8 .8 ).
Пока жувач може да се претвори во интегрален тип кој е доволно голем за
да го чува ; потребната големина за виси од имплементацијата. Функциите за
мапирање, исто така зави са т од имплементацијата.
Покажувач кон еден тип може да се претвори во покажувач од друг тип.
Резултантниот покажувач може да предизвика адресни исклучоци ако субј ект-
234 Референтн о упатство Додаток А

ниот покажувач не се однесува на објект, кој е соодветно порамнет во мемо­


ријата . Се гарантира дека покажувач кон објект може да се претвори во пока­
жувач кон објект чиј што тип бара помала или најм ногу една ква количина на ме­
мориско порамнување и да се конвертира наназад без загуба на информации ;
нотацијата за " порамнувањето " за виси од имплементацијата, но објектите од
тип char имаат најмалку стриктни побарувања за порамнување. Како што е оп­
ишано во Параграф А б. е , покажувач може да се претвори во тип void* и назад
без никаква загуба на информации.
За крај, покажувач кон функција може да се претвори во покажувач кон
функција од друг тип. Повикувањето на функцијата специфицирана со моди­
фицираниот покажувач за виси од имплементацијата ; сепак, ако конвертира­
ниот покажувач се врати назад во оригинал ни от тип, резултатот е идентичен

н а оригиналниот по кажувач .

A6.7void

Вредноста (која не постои) на еден void објект не може да се користи на ка­


ков било начин, и не е можно да се изведе ниту експл ицитна , ниту имплицит­
на конверзија кон каков било н e-void тип. Бидејќи еден void израз означува
непостојна вредност, таков израз може да се користи само во ситуации каде
не се бара вредност , на пример, како во израз (Параграф А9. 2) , или како лев
операнд на операторот " запирка " (,) (Параграф А7 .18) .
Еден израз може да се претвори во тип void преку негово претопување. На
пример, претопување во void го документира ослободувањето на вредноста
од еден функциски повик кој се користи како наредба која е израз .
void не постоеше во првото издание на оваа книга , но отто гаш е влезен во
редовна примена.

А6.8 Покажувачи кон void

Кој било покажувач кон објект може да се претвори во тип void* без загу­
ба на информации . Ако резултатот се претвори назад во тип на оригиналниот
покажу вач , резултатот е оригиналниот покажувач. За разлика од конверзиите
од покажувач кон покажувач, дискутирани во Параграф А б. б , кои во о пшт
случај бараат експлицитно претопување, покажувачите можат да се доделат на
и од покажувачи од тип void* и може да се споредуваат со нив.
Оваа интерпретација на покажувачите од тип void* е нова; претходно пока­
жувачите од тип char* играа улога на генерички покажувач. ANSI стандардот
посебно ја нагласува интеракцијата помеѓу void* покажувачите и објектните
покажувачи во доделувањата и релациите, додека кај интеракциите помеѓу
п окажувачите од другите ти п ови претопувањето експлицитно се наведува .
А.7 Изрази 235

А7.И3ра3и

При о ритетот на израз ните о ператори е ист со редоследот на главните под­


делови на ова поглавје, прво е даден тој со највисокиот приоритет о Та ка, на
пример, изразите за кои се вели дека се оnерандите на+ (Параграф А 7 о 7) се
тие изрази дефинирани во Па ра граф А7 ol до А7 о б о Во рамките на секој поддел
операторите и маат ист приоритет о Ле ва ил и десна асоцијативност е наведена
во секој nоддел за операторите кои се дискутир аа т та му о Гра матиката дадена
во Пара граф АlЗ ги вклучува nриоритетот и асоцијативноста на операторите о
Приоритетот и асоцијативноста на операторите се наnолно објаснети, но
редоследот на евалуа цијата на изразите, со некои исклучоци е недефинира­
на , дури и кога подизразите в клучуваат дополнителни (неочекувани) дејства о
Од н осно, ако дефиницијата на о nераторот не га рантира дека не говите о перан­
ди се евалуираат во конкретен редослед, имnлементацијата е слободна да ги
евалуира оnеранди те во кој било редослед о Сепак, секој оnератор ги комби­
нира добиените вредности од неговите оnера нди на начин комnатибилен со
nарсирањето на изразот во кој се nојаву ва о
Ова правило ја ук инува претходната слобода за преуредување на изразите
со оператори кои се математички комутативни и асоциј ативни, но може да
потфрли во намерата да биде пресметковно асоцијатив но о Про мената влија е
само в р з пресметките со реални броеви во близи н а н а гра ниците на нивната
прецизност и во ситуации каде е можн о п речекорување о

Справувањето со преч екорувањата, nрове рките при делењето и д ру гите


исклучоци кои се среќаваат nри nресметувањето на изразите не се дефинира­
ни од страна на јазикот о Најголемиот број nостој ни имnлементации на С го и г­
норира а т пр е ч екору ва њ ето во nресметките на оз н а чен и интегрални изрази и

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


лучокот на делење со о и со сите реалноброј ни искл учоци варира од имnлемен­
та ција до имплементација; понекогаш се разрешува со воведување на фу нкц ии
од нестандардн и библиотеки о

А7.1 Генерирање на покажувачи

Ако тиnот на еден из раз или п оди з ра з за некој тип Т е "низа од Т", тога ш
вредноста на изразот е покажувач кон првиот објект од н изата и тиnот на из­
раз от е променет во " nокажувач кон Т" о Оваа конверзија не се случува ако
изразот е оnерандот од унарниот о пе рато р & или од++ , -- , sizeof, или како

лев оnеранд на оператор за доделува ње и операторот за доста n "о " о Слично


на тоа израз од тип " функција која враќа Т", освен кога се користи како опе­
ранд кај оnераторот & , се претвора во " nокажува ч кон функција која вра ќа Т" о
236 Референтно упатство Додаток А

А7.2 Примарни изрази

Примарни изрази се идентификаторите, константите , стринговите или из­


разите во мали згради

примарен-израз:

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

стринг

(израз )

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


соодветно деклариран како што е наведено подолу . Неговиот тип се наведу­
ва со неговата декларација . Еден идентификатор претставува л вредност ако
се однесува на некој објект (Параграф Аѕ) и ако неговиот тип е аритметички,
структура , унија или покажувач.
Константата претставува примарен - израз. Нејзиниот тип за виси од нејзина­
та форма, како што е наведено во Параграф А2. ѕ.
Стрингот претставува примарен-израз. Неговиот тип е оригинално " низа од
char" (за широки стрингови , " низа од wchar_ t " ) , но следејќи го правилото да­
дено во Параграф А7 .1, ова обична е модифицирано во " покажувач кон char"
(wchar_ t) и резултатот е покажувач кон првиот знак во стрингот . Конверзијата
може да не се изведе кај одредени иницијализатори , видете Параграф Аѕ .7.
Еден израз ограден со мали загради е примарен-израз, чиј што тип и вред ­
ност се идентични на израз без загради. Приоритетот на заградите не влијае на
тоа дали изразот претставува л вредност.

А7 .3 Постфиксни изрази

Операторите во постфиксните изрази групираат лево кон десно .

постфиксен-израз:
примарен-израз

постфиксен-израз {израз]
постфиксен-израз (листа-на-аргументи изразиор)
постфиксен-израз.идентификатор
постфиксен-израз- >идентификатор
постфиксен-израз++
постфиксен-израз--

листа-на-аргументи-изрази:

израз- за-доделување
листа-на-аргументи-изрази, израз-за-доделување
А.7 Изрази 237

А7.3.1 Референци кон ни3а

Постфи ксен израз проследен со израз во средни загради претставува


постфиксен израз кој озн ачува референца кон индекс н а низа. Еден од двата
израза мора да има тип " покажувач кон 1"', каде Т е не кој п одаточе н тип, а
вториот мора да биде интегрален ти п ; типот на индексниот израз е Т. Изразот
El [Е2] е идентичен (по дефиниција) со * ( (El) + (Е2)) . Погледнете во
Параграф А8 . б. 2 за понатамошна дискусија .

А7.3.2 Функциски повици

Функцискиот повик е постфиксен израз наречен функциски означувач , про ­


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

за доделување одделени со запирка (Параграф А7 .17), која ги сочинува ар­


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

extern int идентификатор (};

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


Постфиксниот израз (после можна експлицитна декларација и генерирање на
покажува ч , Параграф А7 . 1) мора да биде од ти п " покажувач кон функција
која враќа Т" за некој тип Т и вредноста на функцискиот п овик има вредност Т .
Во првото и здание тип от беше ограничен на " функциски " и експлицитен *
оп ератор бе ше потребен за п о вик пре ку покажу вачи кон функции . ANSI стан­
дардот ј а прифати практиката на не кои постојни ко мпајлери дозволу вај ќи иста
синтакса за обичните повици кон фу нкции и за функции специфи ци рани од по­
кажувачи . Постарата синтакса се уште е во у потреба .
Терминот аргумент се користи за изрази предадени преку функциски по­
вик ; терминот параметар се користи за вл езен објект (или неговиот иденти­
фикатор) добиен преку функциска дефиниција , или опи ша н во едн а фун кциска
декларација. Термините "актуелен аргумент (параметар)" и " формален ар ­
гумент (параметар) " соодветно н е кога ш се кори стат за да се напр а ви истото

ра з граничување .

При подготвувањето за повикот кон една фун кција , се прави копија од се­
кој аргумент; целокупното предавање н а аргуме нт ите е строго по вредност .
Функција може да ја менува вредноста на нејзините параметарски објекти кои
се копии од аргументните изрази , но тие промени не можат да влијаат н а вред­
н остите од аргуме нтите. Меѓутоа , возможно е д а се предаде покажува ч при
што се подразбира дека фун кцијата може да ја менува вред носта на објектот
кон кој покажува п окажувачот.
П остојат два стила по кои можат да се де кларираат функциите. Со новиот
238 Референтно упатство Додаток А

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


та ; таква декларација, исто така, се нарекува и функциски прототип . По стари­
от стил, параметарските типови не се наведени. Функциската де кларација е
образложена во Параграф А8. б. з и Параграф А10. 1 .
Доколку функциската декларација во делокругот на еден повик е во стар
стил, тогаш се применува основна промоција на аргументите за секој аргумент
што следи: интегрална промоција (Параграф Аб . 1) се извршува врз секој
аргумент од интегрален тип , а секој float аргумент се претвора во doub1e.
Резултатот од повикот е недефиниран ако бројот на аргументи не се сложува со
бројот на параметри во дефиницијата на функцијата , или ако типот на еден ар­
гумент после промоцијата не се сложува со типот на соодветниот параметар.
Согласувањето на типовите зависи од тоа дали функциската дефиниција е од
стар или од нов стил. Ако е од стар стил, тогаш споредбата е помеѓу промови­
раниот тип од аргументите на повикот и промовираниот тип на параметарот,

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


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

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


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

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


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

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


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

А7 .3.3 Референци на структура

Постфиксен израз проследен со точка проследена со идентификатор,


претставува постфиксен израз . Изразот од првиот операнд мора да биде струк­
тура или унија, а идентификаторот мора да именува член на структурата или
унијата. Вредноста е именуваниот член од структурата или унијата, а неговиот
тип е типот на членот. Изразот е л вредност ако првиот и з р аз е л вредност и
ако типот на вториот израз не е од тип низа .
А.7 Изрази 239

Постфиксен оnератор nроследен со стрелка (составена од- и>) nроследена


со идентификатор nретставува постфиксен-израз. Изразот од nрвиот операнд
мора да биде nокажувач кон структура или унија, а идентификаторот мора да
именува член на структура или унија . Резултатот се однесува кон именуваниот
член на структурата или унијата кон која nокажува изразот со nокажувачот , а
тиnот е тиnот на членот ; резултатот е л вредност доколку типот не е низа.

Така изра зот Е1->МОЅ е ист како (*Е1) . моѕ. Структурите и у ниите се обра-
ботени во Параграф АВ. з.
Во прв ото издание на оваа книга веќе постоеше правило дека име на член во
таков израз мора да припаѓа н а структурата или у нијата спомната во постфикс­
ниот израз ; сепак, постоеш е забелешк а дека ова прав ило н ема да се наметну­
ва. Понов ите компајлери и ANSI инсистираат на неговата примена.

А7.3.4 Постфиксно инкрементирање

Постфиксен израз nроследен со ++ или --nретставува nостфиксен израз .


Вредноста на изразот е вредноста на о nерандот. Откако ќе се запомни вред­
носта, оnерандот се инкрементира ++или декрементира --. Операндот мора
да биде л вредно ст ; видете ја дискусијата на адитивни оператори (Параграф
А7. 7) и оnераторите за доделување (Параграф А7 . 17) за nонатамошни огра ­
ничувања на оnерандот и детали за оnерацијата. Резултатот не е л вредност .

А7.4 Унарни оператори

Израз со унарни оnератори rpynиpa оддесно кон лево.

у нарен- израз:

постфиксен-израз
++унарен-израз

--унарен-изра з

унарен-оператор и зраз-за -претопуваН>е

sizeof унарен-израз
sizeof (име-на- тип )

унарен -оператор: еден од

& "_ + --!

А7.4.1 Оператори за префиксно инкрементирање

Унарен израз nроследен со++ или --оnератор претставува унарен израз.


Оnерандот е инкрементиран ++ или декрементиран --за 1 . Вредноста на из-
240 Референтно упатство Додаток А

разот е вредноста после инкрементирањето (декрементирањето) . Операндот


мора да биде л вредност; погледнете ја дискусијата за адитивни оператори
(Параграф А7. 7) и оператори за доделување (Параграф А7 .17) за поната­
мошни огра нишува ња на операндите и детали за операцијата. Резултатот не е
л вредност.

А7.4.2 Оператор за адресирање

Унарниот оператор & ја зема адресата на неговиот операнд. Операндот


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

А7.4.3 Оператор за индирекција (дереференцирање)

Унарниот оператор *означува индирекција и враќа објект или функција кон


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

А7.4.4 Операторот унарен плус

Операндот на унарниот оператор +мора да има аритметички тип и резулта­


тот е вредноста на операндот. Врз интегрален операнд се применува интеграл­
на промоција. Типот на резултатот е типот на промовираниот операнд.
Унарниот +е нов оператор воведен со ANSI стандардот. Беше додаден заради
симетрија со унарниот- .

А7.4 .5 Операторот унарен минус

Операндот на унарниот оператор - мора да биде од аритметички тип и ре­


зултатот е н егативот од неговиот операнд . Врз интегрален операнд се приме­
нува интегрална промоција. Негативот кај неозначена величина се пресметува
со одземање на промовираната вредност од н ајголемата вредност на промови­
раниот тип и се додава 1; но негативна нула е нула. Типот на резултатот е типот
н а промовираниот операнд.
А.7 Изрази 241

А7.4.6 Оператор 3а единично комплементирање

Операндот на операторот- мора да биде од интегрален тип, а резултатот


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

ранд во соодветниот неозначен тип, применувајќи- и конвертирајќи го назад


во означен тип. Типот на резултатот е типот на промовираниот операнд .

А7 .4.7 Оператор за лоrичка неrација

Операндот на операторот ! мора да има аритметички тип или да биде пока­


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

спротивно о . Типот на резултатот е int .

А7.4.8 Оператор sizeof

Операторот sizeof враќа број на бајти потребни да се складира во мемо­


рија објект од типот на неговиот операнд . Операндот е или израз кој не се ева­
луира, или име на податочен тип оградено со мали загради. При примена на
sizeof врз char резултатот е 1; при негова примена врз низа , резултатот е

вкупниот број на бајти во низата . Применет врз структура или унија резулта­
тот е бројот на бајти во објектот , вклучувајќи го и потребното порамнување за
објектот да се смести во низата : големината на низата од n елементи е n пати од
големината на еден елемент. Операторот не може да се примени врз операнд
од функциски тип или врз некомплетен тип или врз битско поле . Резултатот е
неозначена интегрална константа; конкретниот тип е дефиниран со имплемен­
тацијата. Стандардното заглавје <stddef . h> (погледни Додаток Б) го дефини­
ра овој тип како size_ t .

А7 .5 Претопувања

Унарен израз на кој му претходи име на тип ограден со мали загради предиз­
викува конверзија на вредноста од изразот во именуваниот тип.

израз-за-претопувањ е:

унарен-израз

(име-на-тип) израз-за- претопување


242 Референтно упатство Додаток А

Оваа конструкција се нарекува претопување . Имињата на типовите се опиша­


ни во Параграф АЅ. 8. Ефектите од конверзиите се опишани во Пар аграф Аб.
Израз со претопување не претставува л вредност .

А7.6 Мултипnикативни оnератори

Мултипликативните оператори * , 1, и %групираат од лево кон десно .

мултипликатив ен-израз :

мултипликативен-израз * израз-за-пр етопување


мултипликативен-израз 1 израз-за - претопување
мултипликативен-израз % изра з -за - претопување

Операндите на * и 1 мора да бидат од аритметички тип ; операндите н а %


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

Бинарниот оператор * означува множење.


Бинарниот оператор 1 враќа количник, а операторот %враќа остаток, од
делењето на првиот операнд со вториот ; ако вториот операнд е о , резултатот

е недефиниран . Во спротивно, секогаш важи дека (a/b*b+a%b е еднакво на


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

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

А7.7 Адитивни оператори

Адитивните оператори + и - групираат од лево кон десно. Кога операндите


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

адитивен -израз:
мултипликатив е н - израз

адитивен -израз + мулт ипликат ивен -израз


адитивен-израз - мултипликативен-израз

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


низа и вредноста на кој било интегрален тип може да се додаваат . Вториот се
конвертира во адресно растојание преку негово множење со големината на
објектот кон кој покажува покажувачот . Сумата е покажувач од истиот тип како
и оригиналниот покажувач и покажува кон друг објект од истата низа , на со­
одветно растојание од почетниот објект . Така, ако р е покажувач кон објект на
низа, изразот p+l е покажувач кон следниот објект во ни зата. Ако збирниот
А.7 Изрази 243

покажувач покажува надвор од границите на низата со исклучок на првата ло­

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


Нова е одредбата за покажувачи те кои малку излегуваат од гр аница та
на низата. Таа обезбедува стандарден за изминување низ елементите
на некоја низа .
Резултат од операторот- е разликата на операндите. Вредност од кој било
интегрален тип може да се одземе од покажувач, а потоа се применуваат исти­

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


Ако се одземат два покажувача кон објекти од ист ти п , резултатот е озна­
чена интегрална вредност која го претставува поместува њето помеѓу пока­
жуваните објекти; покажувачи кон последователни објекти се разликуваат за
1. Типот на резултатот е дефиниран како ptrdiff_ t во стандардното за главје
<stddef. h>. Вредноста е недефинирана освен ако покажувачите не п о кажува­
ат кон објекти од истата низа; меѓутоа ако р покажува кон последниот елемент
на една н и за, тогаш (р+ 1) -р има вредност 1.

А7.8 Оператори 3а поместуван.е

Операторите за поместување << и >> групираат од лево кон десно . И кај


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

uзраз-за-поместување:

адuтивен-израз
израз-за-поместување << адитивен-израз
израз-за-поместување >> адитивен-uзраз

Вредноста на Е1<<Е2 е Е1 (интерпретиран како битска низа) лево поместен за


Е2 битски; во отсуство на пречекорување тоа е еквивалентно на множење со 2Е 2 •
Вредноста на Е1>>Е2 е Е1 десно поместен Е2 битски позиции. Поместувањето во
десно е еквивалентно со делење со 2Е2 , ако Е1 е неозначен или ако има ненегатив­
на вредност; во спротивно резултатот е дефиниран со имплементацијата.

А7 .9 Релациски оператори

Релациските оператори групираат од лево кон десно, но тој факт е бескори­


сен; a<b<c се парсира како (a<b) <е, а (a<b) се евалуира во о или 1.
244 Референтно упатство Додаток А

релациски-израз:

израз-за-поместување

релаци ски- израз < израз -за - п оместувањ е


релациски-израз > и зраз-за-поместување
релациски-израз <= изра з -за - п оместува НЈ е
релаци ски-израз >= израз -за - п оместување

Операторите < (помал о) , > (поголема) , <= (помал о или еднакво) и >= (по­
големо или еднакво) враќаат о доколку наведената релација е невистин ита и
1 доколку е вистинита о Типот на резултатот е int о Вообичаените аритмети чки
кон верзии се изведуваат врз аритметички операнди о Покажувачите од објекти
кон ист тип (игнорирајќи какви било квалификатори) може да се споредуваат;
резултатот за виси од релативните локации во адресниот простор на пока жува­

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


ист објект; ако два покажувача покажуваат кон ист прост објект , тие се споре­
дуваат како еднакв и; ако покажува ч и те се членови од иста структура , покажу­

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


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

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

ните и ндекси о Ако р покажува кон последниот член од една н и за тогаш p+l се
споредува како поголема од р, иа ко p+l покажу ва на дво р од низата о Во спро­
тивно, споредувањето покажу вачи е недефинираноо
Овие п равила мал ку ги либерализи раат о граничувањата наведени во прво­
то издание дозволувајќи споредба на п окажувачи кон разл ичн и членови н а
структ ура ил и ун ија о Исто така , го легализираат споредувањето со покажувач
кој е за еде н елемент надвор од крајот н а н екоја ни за о

А7.10 Оператори 3а еднаквост

израз -за-екви валенција:


релациски-израз

израз -за - е квиваленција == р ел ациски - израз


израз -за-еквиваленција!= р елациски - израз

Операторите = (еднакво) и != (н еедн акво) се а налогни н а релациските


оператори освен што и маат п о низок п р иоритет о (Така a<b=c<d е e=l секогаш
кога a<b и c<d имаат в ист и нита вредн ост) о
Операторите з а еднаквост ги следат и с тите правила како и релац иските опе­
ратори , но имаат и додатни можности: покажувач може да се споредува со

константен интегрален израз со вредност о , или со покажувач кон voido Види


Параграф Абобо
А.7 Изрази 245

А 7.11 Битски оnератор И

И-израз:
израз-за - еквиваленција
И-израз & израз-за-еквиваленција

Се изведуваат вообичаените аритметички кон верзии ; резултатот е б итска


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

А7.12 Битски оnератор искпучитеnно ИЛИ

Исклучително -ИЛИ - израз:


И-израз
Исклучително-ИЛИ-израз & И-израз

Се изведуваат вообичаените аритметички конв ерзии; резултатот е битска


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

А7.13 Битски оnератор ИЛИ

ИЛИ-израз:
Исклучително-ИЛИ-израз
ИЛИ-израз 1исклучително-ИЛИ-израз

Се изведуваат вообичаените аритметички конверзии; резултатот е битска


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

А7.14Лоrички оператор И

логичко-И-израз:
ИЛИ-изр аз
логичко -И- израз && ИЛИ-израз

Операторот && групира од лево кон десно. Враќа 1 ако и двата оператора
се ра злични од о , во спротив но о. За разлика од & , && гара н тира е ва луација
од лево кон десно: се евалуира првиот о перанд вклучувајќи ги сите стра н ични
ефекти ; ако е еднаков н а о , вредноста на изразот е о. Во спротивно се евалуи­
ра д есниот опера нд и ако е ед наков на о вредноста н а изразот е о, во с п роти в-
246 Реф ерентно уп атство Додаток А

но 1 . Операндите н е мора да имаат исти ти п , но секој мора да биде аритметич­


ки тип или да биде покажувач. Резултатот е int .

А7.1 5 Лоrички оператор ИЛИ

логички - ИЛИ-и зраз:


лог ички - И-израз
логички-ИЛИ- израз 1 1 логички -И-израз
Операторот 11 групира од лево кон десно. Враќа 1 ако кој било од операн­
дите е различен од о, во с проти вно о. За разлика од 1 , 1 1 гарантира евалу­
а ција од лево кон десно: се евалуира првиот операнд вклучувајќи ги сите стра­
ничн и ефекти; ако не е еднаков на о, вредн оста на изразот е 1. Во спротивно
се евалуира десниот операнд и ако н е е еднаков на о вредн оста на изразот е

1 , во сп ротив н о о. Операндите не мора да бидат од ист ти п , но секој мора да


биде аритметички тип или да биде покажувач. Резултатот е int.

А7 .1 б Условен оператор

условен-израз:

логички-ИЛИ-израз
логички -ИЛИ-израз ? израз: условен-израз

Првиот израз се евал уира вклучувајќи ги сите с транич н и ефекти; ако е неед­
наков на о, резултат е вредн оста на вториот израз, во спротивно, вредноста

н а третиот израз. Се евалуира само еден од вто риот или третиот операнд. Ако
вториот и третиот операнд се аритметички, тога ш се изведуваат вообичаените
аритметич ки конверзии за да се доведат до заедни чки тип , и тој тип е тип на
резултатот . Ако и двата се void , или структури или унии од ист тип , или пока­
жувачи кон објекти од ист тип, резултатот има вредн ост на заедничкиот тип .
Ако еде н од нив е покажувач , а други от ко н с танта о , нулата се претвора во тип
покажувач, и резултатоте од тој тип . Ако еден од нив е покажувач кон void , а
другиот е н екој друг покажувач , д ругиот покажувач се претвора во покажувач
кон v oid , и тоа е типот на резултатот . При с поредување н а п окажувачи , кои
било квал и фикатори во типот кон кој покажува покажувач от се незначај ни , но
резултантни от тип ги наследува квалификаторите и од д вете стра н и на усло ­
вот.

А 7.17 И3ра3и 3а доделување

Постојат неколку оператори за доделување; Сите групираат оддесно кон


лево.
А.7 Изрази 247

израз -за- д оделув ање:


услов ен -израз

унарен-израз оператор-за-доделување израз- з а-доделување

операт ор-за-доделување: еден од


*= 1= % = + = -= <<= >>= &= л= 1=

Сите имаат потреба од л вредност како лев операнд, и истата мора да биде
променлива : не смее да биде низа , не смее да биде од некомплетен тип , или
функција. Исто така , типот не смее да биде квалифику ван со const; ако е струк­
тура или унија , не смее да содржи член или потчл ен квалификуван со const .
Типот на изразот за доделување е определен со неговиот лев операнд, а негова­
та вредност е вредноста која е з ачувана во леви от операнд после додел увањето.
При едноставно доделување со =, вредно ста на изразот ја за мену ва она а
на објектот кој е посочен од л вредноста. Мора да биде в истина барем едно од
овие тврдења: двата операнда се од аритметички тип , во кој случај десниот
операнд се претвора во типот од лев о со додел увањето ; или двата опе ранд а

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

е покажувач кон void, или левиот операнд е покажу вач, а десниот констан ­

тен израз со вредност нула ; или двата операнда се покажу вачи кон фу н кци и
или објекти чии типови се исти со исклучок на можната отсутност на const или
volatile кај десниот операнд. Израз во форма El ор= Е2 е еквивалентен с о
El = El ор Е2 со таа разлика што El се евал у ира само еднаш .

А7.18 Операторзапирка

израз:

израз-за-доделување
израз , израз-за-доделување

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


та од левиот израз се отфрла . Типот и вредноста на резултатот се о пределени
со типот и вредноста на десниот операнд . С ите дополнителн и дејства од ева­
луирањето на левиот операнд се завршуваат пред почетокот на евалуира ње­

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


значење, на пример, во аргументните листи на функциите ( Параграф А7.3 .2 ) и
иницијализаторските листи ( Параграф А8.7), потребната синтаксичка единица е
израз за доделување, па операторот запирка се појавува само кај груп и рања
во загради, како, на пример,

f(a, (t=З , t+2) , е)

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


248 Рефе рентно упатство Додаток А

А7.19 Константни и3ра3и

Синтаксички, константен израз претставува израз ограничен на подмноже­


ство од оператори :

кон с тан тен - израз:

условен - израз

Изрази кои се евалуираат во константа потребни се во неколку ситуации:


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

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


ментирање или декрементирање, функциски повици , или оператори запир­
ка; исклучок е операторот sizeof. Ако се бара константниот израз да биде
интегрален, неговите операнди мора да се состојат од цел број, енумерација ,
знак и реални константи; претопувањата мора да специфицираат интегрален
тип, и сите реални константи мора да се претопат во цели броеви. Оваа потре­
ба ги исклучува низите, дереференцирањето, деадресирањето, и операциите
со членови на структурите. (Сепак, на секој операнд му е дозволен sizeof.)
Дозволена е поголема широчина за константните изрази на иницијализа­
торите ; операндите можат да бидат од кој било тип на константа и унарниот
& оператор може да се примени на надворешни или статички објекти , и врз
надворешни и статички низи индексирани со константен израз. Унарниот опе­
ратор & може да се примени имплицитно по изглед од неиндексирани низи и

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


адреса од претходно декларирани надворешни или статички обје кти плус/ми­
нус константа.

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


#if; sizeof изрази, енумерациски константи и претопувања н е се дозволе­
ни. Види П араграф 15.5.

А8 Декларации

Декларациите ја специфицираат интерпретацијата дадена на секој иден­


тификатор; не секогаш резервираат меморија асоцирана со идентифика­
торот. Декларациите кои резервираат меморија се наречени дефиниции.
Декларациите ја имаат формата
А.8 Декларации 249

декларација:
декларациски-спецификатори инит -декларатор- листа.Р,;

Декларациите во инит-декларатор-листа ги содржат идентификаторите кои


се декларираат ; декларациски (те) спецификатори се состојат од секв е н ца на
типови и спецификатори на мемориските класи .

декларациски-спецификатори :
спецификаторu- на-мемориска-класа декларациски-спе цификатори.Р,
спецификатор - на -тип декларациски-спецификатори opt
квалификатор-на-тип декларациски-спецификатори ор<

инит-декларатор-листа:
инит-декларатор
инит-декларатор-листа, инит-декларатор ·

инит-декларатор:
декларатор
декларатор = иницијализатор
Деклараторите ќе бидат разгледани подоцна ( Параграф А8.5); ги содржат
имињата кои се декларираат. Една декларација мора да има баре м еден д е ­
кларатор, или спецификаторот на неговиот тип мора да декларира структурен
таг , униски таг или чл е но в ите од една ен умерац ија ; п разни де кл арации не се
дозволени.

А8.1 Спецификатори за класи на мемориски простор

Спецификатори на мемориските класи се:

спецификатор-на-меморuска-класа:
auto
register
static
extern
typedef

Значењето на мемориските класи е разгледано во Параграф А4. 4.


Спецификаторите auto и register на декларираните објекти им ја даваа т
автоматската мемориска класа и можат да се користат само во рамките на функ­
ции. Таквите декларации , исто така , служат како дефиниции и резервираат
меморија. Една register декларација е еквивалентна со auto декларациј а,
но посочува дека до декларираните објекти ќе биде пристапувано често. Само
250 Референтно упатство Додаток А

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


подобни за тоа ; ограничувањата зависат од имплементацијата. Меѓутоа, ако
еден објект се декларира како register, унарниот оператор & не може да се при­
мени врз него , ниту експлицитно, ниту имплицитно.

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


ран како register, кој всушност се зема како auto.
Спецификаторот static имја дава на декларираните објекти статичката ме­
мориска класа и може да се користи било внатре, било надвор од функции.
Внатре во функција , овој спецификатор алоцира меморија и служи како дефи­
ниција; за неговото дејство надвор од функција види Параграф All. 2.
Декларација со extern, користена внатре во функција означува дека ме­
моријата за декларираниот објект е дефинирана на друго место; за неговото
дејство надвор од функција види Параграф All. 2.
Спецификаторот typedef не резервира меморија и е наречен спецификатор на
мемориска класа само од синтаксички причини ; образложен е во Параграф АЅ. 9.
Најмногу еден спецификатор на мемориска класа може да биде зададен во
една декларација. Во случај да не е зададен ниту еден се користат следниве
правила: објекти декларирани внатре во функција се земаат како auto ; функ­
ции декларирани внатре во функции се земаат како extern; објекти и функции
декларирани надв ор од функција се земаат за static со надворешно поврзу­
вање; види Параграф AlO - Параграф All.

А8.2 Спецификатори на тип

Спецификатори на тип се:

спецификатор-на-тuп:
void
char
short
int
long
float
double
signed
unsigned
структурен - или-униски-спецификатор
енумерациски-специфuкатор
typedef-uмe

Најмногу еден од зборовите long или short може да биде наведен заедно
со int; значењето е исто и ако int не се спомене. Зборот long може да биде
наведен заедно со double . Најмногу еден од signed или unsigned може да се
А.8 Декларации 251

наведе заедно со int и л и кој било од неговите short или long варија ции, или
со char. И двете може да се појават сам и и во тој случај се подразбира int.
Спецификаторот signed е корисен за наметнува ње знак на char објекти ; може
но не мора да се користи со другите интегрални ти пови . Во спротивно најмно­
гу еден спецификатор на тип може да биде зададен во една деклара ција . Ако
истиот недостига од декл а рацијата, се з ема дека е int.
Исто така, типовите може да бидат квалифи кувани, со цел да назначат посеб­
ни карактеристики на објектите кои се декларираат.

квалификатор-на-тип:
const
volatile

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


тип. Еден const објект може да биде инициј али зира н, но потоа не може да
му се доделува вредност. Нема сема нти ка независна од имплеме нтацијата за
volatile објекти.
Карактеристиките const и volatile се нови со ANSI стандардот . Целта на
const е да објави објекти кои може да се сместат во меморија само за читање
и, можеби, да ги зголеми мож ностите за оnти мизација. Целта н а volatile
е да наметне имnлементација за nоти с нување на оnтимиза ција која во друг
случај б и можела да се случи . Н а nример , за машина со мемориски-маnи ра н
влез/излез , nокажувач кон ре гистер на уред може да се декларира како nо­
кажувач кон volatile со цел да го сn речи комnајлерот од отстра нувањето
на очигл едно одви шни референци nреку nокажувачот. Осве н што треба да
дијагностицира ексn лицитни об иди за n роме ни н а const објекти еден комnај­
лер може да ги игн ори р а овие квалификатори .

АЅ.З Декларации на структура и унија

Структура е објект кој се состои од секвен ца на именувани чле н ови од раз­


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

структурен-или-униски -спецификатор:
структура-или-унија идентификаторopt { структурна-декларациска-листа }
структура-или-унија идентификатор

структура-или-унија :
struct
union
252 Референтно упатство Додаток А

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


структурата или унијата:

структурна-декларациска -листа:
структурна-декларација
структурна-декларациска-листа структур на-декларација

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

спецификатор-квалификатор-листа:
тип -спецификатор спецификатор-квалификатор-листа opt
тип-квалификатор спецификатор-квалификатор-листа opr

структурна-декларациска-листа:
структурен-декларатор
структурна-декларациска-листа, структурен-декларатор

Вообичаено, структурен декларатор е само декларатор за член на струк­


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

структурен-декларатор:
декларатор
декларатор •Р' : константен- израз

Спецификатор на тип во форма

структура-или-унија идентификатор { структурна-деклара циска-листа

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


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

структура -или-унија идентификатор

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


се специфицира некомплетен mun. Објекти со некомплетен структурен или
униски тип може да се спомнат во контексти каде не е потребна нивната голе­
мина, на пример, во декларации (не дефиниции) , за специфицирање на по­
кажувач или за креирање на typedef 1 но не обратно. Типот станува комплетен
при појава на последователен с п ециф икатор со тој таг 1 кој содржи и деклара-
А.8 Декларации 253

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


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

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


Структура не може да содржи член од некомплетен тип . Поради тоа не е
возможно да се декларира структура или унија која содржи инстанца од себе­
си . Меѓутоа, освен давањето име на структурниот или унискиот тип, таговите
дозволуваа т дефин иција на себеповикувачки структури; структура или унија
може да содржат покажувач кон инстанца од себе , бидејќи може да се декла­
рираат покажувачи кон некомплетни типови.

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

структура-или-унија идентификатор;

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


ри . Дури и ако идентификаторот е структурен или униски таг кој е веќе деклариран
во надворешен делокруг (Параграф All . l) , оваа декларација креира идентифи­
катор за таг од нова структура со некомплетен тип или унија во тековниот делокруг .
Овој нејасе н и з раз е нов со ANSI. Наменет е да се с прави со заемно рекурзи в­
ни структури декларирани во внатр ешен делокруг, а чии тагови можеби се
веќе декларирани во надворешен дело круг.
Структура или унија с пецифицирана со листа, но без таг, креира уникатен
тип ; може да се референцира директно само во декларацијата од која истата
е и дел .

Имињата на членовите и таговите не се конфликтни едни со други или со обич­


ни променливи . Име на член не може да се појави двапати во иста структура или
унија, но исто име на член може да се користи во различни структури или унии.
Во првото и здание на оваа книга, имињата на структурните и униските чле­
нови не беа асо цирани со ни в н иот родител . Меѓутоа, оваа асоцијација ста на
вообичаена во компајлерите многу пред ANSI стандардот.
Член на структура или у нија кој не е поле може да има каков било тип на
објект. Член кој е поле (кој не мора да има декларатор и со тоа може да биде
неимен уван) е од т ип int, unsigned int или signed int и се интерпретира
како објект од интегрален тип со специфицирана должина во битски; дали
едно int nоле се третира како означено за виси од имnлементацијата. Соседни
членови од структурите се пакуваат во мемориски единици кои зависат од им­

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


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

мемориска единица , тоа може да биде поделено помеѓу единици или, па к, еди­
ницата може да биде проширена. Неименувано поле со ш ирочи на о , намет­
нува такво nроширување, па следното поле ќе за почне на работ од следната
алокациска единица .

ANSI стандардот ги направи полињата дури и повеќе завис ни од импле ме н­


та цијата отколку пр вото издани е. Препорачливо е да се читаат правилата на
ја зикот за складирање на битски п ол иња како " зависни од имплементација­
та ", без квалификација. Структури со битс ки полиња може да се користат
254 Референтно упатство Додаток А

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


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

една структура. Ако покажувач кон структура се претопи во тип на покажувач


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

Унија може да се смета како структура кај која сите членови започнуваат на
мемориско растојание о од почетокот и чија големина е доволна да чува кој
било од нејзините членови . Најмногу еден од членовите може да биде с клади­
ран во унијата во кое било време. Ако покажувач кон унија се претопи во тип
на покажувач кон член, резултатот се однесува на тој член .
Едноставен пример на структурна декларација:

struct tnode {
char tword[20] ;
intcount;
struct tnode *left;
struct tnode *right ;

кој содржи низа од 20 знаци, еден цел број и два покажувача кон слични струк­
тури. Еднаш кога ќе се зададе оваа декларација , декларацијата

struct tnode ѕ, *ѕр;

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

Со тие декларации, изразот

sp->count

се однесува на полето count од структурата кон која покажува ѕр ;

s . left

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

s.right->tword[O]

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

Воопшrо, член на унија може да биде разгледуван само во случај кога на унијата и
била доделена вредност со користење на истиот тој член. Сепак, една посебна гаран-
д о8 Декларации 255

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


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

union {
struct {
int type ;
} n;
struct {
int type ;
int intnode ;
} ni ;
struct {
int type ;
floa t fl.oa tnode ;
} nf ;
} u;

uonf о type =FLOAT ;


u о nf о floa tnode =3 о 14 ;

if (uono type == FLOAT)


о о о sin(u onf ofl.oatnode)

А8.4 Енумерации

Енумерациите се уникатни типови со вредности кои покриваат множество


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

ен.умерациски-спецификатор:
enum иден.тификаторopt { листа-ен.умератори}
enum иден.тификатор

листа-ен.умерат ори :

ен.умерат ор

листа-енумератори , ен.умератор

ен.умератор:

иден.тификатор
иден.тификатор = кон.стан.тен- израз
256 Референтно упатство Додаток А

Идентификаторите во енумерациска листа се декларирани како константи


од тип iпt и може да се појават секаде каде што има потреба од константи . Ако
не се појават енумератори со =, тогаш вредностите на соодветните константи
започнуваат со о и се зголемуваат за 1, како што декларацијата се чита од лево
кон десно . Енумератор со= му ја задава на асоцираниот идентификато р наве­
дената вредност ; последова телните идентификатори ја продолжуваат прогре­
сијата од доделената вредност .
Имињата на енумераторите во еде н ист дел о круг мора да се разлику ваат
едно од друго и од имињата на о бични променливи, н о вредностите не мора
да бидат различни .
Улогата на идентификаторот кај енумера ц uскu сп ец uф uка тор е аналогна на
таа кај структурниот таг во еден структурен специф и катор; именува некоја по­
себна енумерација . Правилата за енумерацuскu- спецuфuкатор со и без тагови
и листи се исти како и правилата за структурни ил и униски спецификатори, со
таа разлика што не постојат некомплетн и ену мерац ис ки ти пови ; тагот на еден
енумерацuскu - спецuфuкатор без енумера циска л иста мора да референци ра
кон некој спецификатор со л и ста од внатрешен делокруг .
Енумера циите се нови у ште од nрвото изда ни е на оваа книга , но, неколку го­
дини беа дел од ја зикот .

АЅ.Ѕ Декпаратори

Деклараторите имаат синтакса :

декларатор:
покажувач opr дuректен-декларатор

директен-декларатор:
идентификатор
(декларатор)
директен-декларатор {константен-израз opr ]
дuректен -декларатор ( листа-т ип-на -параметри)
дuре ктен - декларатор ( листа-идентuфu катори0Р' )

покажувач:

* тип-квалифи кат ор-листа opt


* тип-квалификатор-листаорr покажувач

т и п -квалuф uка тор-лuста:


квалификатор- на-тuп
т uп-квалuфuкатор-лuста кваllификатор-на-т uп
А.8 Декларации 257

Структурата на деклараторите наликува на изрази за дереференцирање , функ­


ција или низа; групирањето е исто .

АВ.б Значење на декпараторите

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


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

Земајќи предв ид само делови за типот кај декларациските спецификатори


( Параграф А8 . 2) и еден посебен декларатор, една декларација има форма "Т
D", каде Т е тип а D е декларатор . Типот кој се придружува на идентификато­
рот во различните форми на деклараторот се опишува индуктивно користејќи
ја оваа нотација .
Во де кларац ија Т D каде D е безепитетски идентификатор, типот на иденти­
фикаторот е Т.
Во декларација Т D каде D има форма

( Dl )

тогаш типот на идентификаторот на Dl е ист со оној на D. Малите за град и не


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

А8.6.1 Декпаратори на покажувачи

Во декларација Т D каде D ја има формата

* тип-квалификатор-листаopt Dl
И типот на идентификаторот во декларацијата Т Dl "модификатор-на-тип
Т" , типот на идентификаторот на D е "модификатор-на -тип тип-квалифика­
тор-листа покажува ч кон т ': Квалификаторите кои следат после * се примену­
ваат врз самиот покажувач , а не кон објектот кон кој што покажува тој.
На пример , да ј а разгледаме декларацијата

int*ap[) ;
258 Референтно упатство Додаток А

Овде, ар [] ја има улогата на Dl ; декларација " int *ар [] " (nодолу) ќе и


даде на променливата ар тип " низа од int", тип квалификатор лисатата е праз­
на, а тип модификатор е "низа од" о Така, конкретната декларација и доделува
на ар , тип "низа од покажувачи кон int" о

Како други примери, декларациите

=
int i , *pi , *const cpi &i;
const int ci = 3, *pci ;
декларираат цел број i и покажувач кон цел број pi о Вредноста на констант­
ниот покажувач cpi не може да се промени ; секогаш ќе покажува кон иста ло ­
кација, иако вредноста кон која покажува може да се менува о Целиот број ci
е константа , и не може да се менува (иако може да се иницијализира, како
овде) о Тип от на pci е " покажувач кон const int" и самиот pci може да се
смени да покажува кон друго место , но вредноста кон која покажува не може
да се менува со доделување преку pci о

А8.6.2 Декларатори на низи

Во декларацијата Т D каде D има форма

Dl {константен-израз.Р,Ј

и типот на идентификаторот во декларацијата TDl е " модификатор-на-тип т:


типот на идентификаторот D е " модификатор-на- тип за низа од Т "о АКО е при­
сутен константен-израз, мора да биде од интегр ал ен тип , и вредност поголе­
ма од о о Ако н едостасува константниот израз кој ја специфи цира г раницата,
низата е од некомплете н тип о

Една низ а може да се конструира од аритметички тип, од п окажувач , од


структура или унија, од друга низа (да се генерира повеќедимензионална
низа) о Секој тип од кој се конструира низа мора да биде комплетен о Ова им­
плицира дека за повеќедимен з ио нална низа, само првата димензија може да
недостасува о Типот на објектот на низа од некомплетен тип се комплетира пре­
ку друга , комплетна декларација за објектот (Пара граф AlO о 2) , или со негово
иницијали зирање (Параграф А8 . 7) . На пример,

floatfa[17] , *afp[17] ;

декларира низа од реални броеви и низа од покажувачи кон реални броеви.


Исто така,

static int хЗd[З] [5] [7] ;


А .8 Декларации 259

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


зхѕх7 . Разгледана темелно 1 хЗd е низа од три елементи: секој елемент е низа
од пет низи ; секоја од нив е низа од седум цели броеви. Кој било од изрази­
те x3d 1 xЗd[i] 1 xЗd[i] [ј], xЗd[i] [ј] [k] може да се појават во некој из ­
раз. Првите три имаат тип "низа", а последниот има тип int. Поконкретно ,
хЗd [ i Ј [ ј] е низа од седум цели броеви а хЗd [ i] е низа од пет низ и од по се­
дум цели броеви .
Индексирањето на низата е дефинирано така што El [Е2] е идентично
* (El+E2) . Поради тоа, и покрај неговиот асиметричен изглед 1 индексирање­
то е комутативна операција. Бидејќи правилата за конверзија кои се приме ­
нуваат врз+ и врз низи (Параграф Аб . б , Параграф А7 . 7 , Параграф А7. 7) ,
ако Ele низа и Е2 е цел број 1 тога ш El [Е2] се однесува Е2- иот елемент од El .
Во примерот, xЗd[i] [ј] [k] е еквивалентно на* (хЗd[~] [ј] + k). Првиот
подизраз одхЗd[iЈ [ј] според Пара граф А7 . 1 се претвора вотип " покажувач
кон низа од цели броеви " 1 според Параграф А7 . 7, собирањето вклучува мно­
жење со големина на цел број. Тоа следува од правилата де ка низите се скла­
дираат по редови (nоследниот индекс варира најбрзо) и де ка првиот индекс
во декларацијата помага да се определи количината на меморија зафатена од
една низа 1 но не игра никаква друга улога во пресметките со индексите.

А8 .6.3 Функциски декларатори

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

Dl (листа-тип - на-параметри )

и типот на идентификаторот во декларацијата TDl е "модификатор-на-тип Т


" 1 типот на идентификаторот Dе "модификатор-на-тип функција со аргумен­
ти листа-тип - на -параметри која вра ќа Т" . Синтаксата на параметрите е

листа-тип-на - параметри:

листа- параметри

лисrпа-параметри ,

листа-параметри :
параметарска-декларација
листа-параметри 1 параметарска-декларација

параметарска-декларација:
декларациски-спецификатори декларатор
декларациски-спецификатори апстрактен-деклараторорr
260 Референтно упатство Додаток А

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


повите на параметрите . Како специјален случај, деклараторот за функција од
нов стил без параметри има параметарска листа која се состои единствено од
клучен збор void . Ако параметарската листа завршува со ", ... ", тогаш функ­
цијата може да прими повеќе аргументи отколку што е бројот на експлицитно
наведени параметри , види Параграф А7 . з . 2 •
Типовите на параметрите кои се низи или функции се менуваат во покажу­
вачи , во согласност со правилата за конверзија на параметри ; види Параграф
AlO. 1. Единствениот спецификатор на мемориска класа, кој што е дозволен во
параметарска декларација е register, и овој спецификатор се игнорира ос­
вен ако функцискиот декларатор не започнува функциска дефиниција . Слично
на ова, ако деклараторите во параметарските декларации содржат идентифи­
катори и функцискиот декларатор не продолжува со функциска дефиниција ,
идентификаторите веднаш излегуваат од делокруг . Апстрактни декларатори
кои не споменуваат идентификатори се разгледани во Параграф АЅ . в .
Во декларација на функција од стар стил Т D, каде D има форма

D 1(листа-на-идентификаториор)

и ти пот на идентификаторот во декларацијата Т D1 е " модификатор -на-тип


Т" , типот на идентификаторот D е "модификатор-на-тип функција од неспе­
цифицирани аргументи која враќа Т". Параметрите (доколку се присутни)
имаат форма

листа-на-идентuфикатори:
идентификатор
листа-на -идентификатори, идентификатор

Кај декларатор од стар стил, листата на идентификатори мора да биде отсутна


освен ако деклараторот не се користи во заглавјето на функциските дефиниции
(Параграф А10 . 1) . Декларацијата не обезбедува никаква информација во вр­
ска со типовите на параметрите.

На пример , декларацијата

int f () , *fpi () , (*pfi) () ;

декларира функција f која враќа цел број, функција fpi која враќа покажувач
кон цел број и покажувач pfi кон функција која вра ќа цел број . Во ниту еден од
овие случаи нема специфицирано тип на параметар ; тие се по стар стил.
Во декларацијата од нов стил

intstrcpy(char*dest , constchar*source) , rand(void) ;


А.8 Декларации 261

strcpy е функција која враќа int, со два аргумента, првиот е покажувач кон
знак, а вториот е покажувач кон константни знаци. Параметарските имиња се
ефективни коментари. Втората функција rand не зема аргументи и враќа int.
Функциските декларатори со параметарски прототипови се најважната проме­
на на јазикот воведена со ANSI стандардот. Имаат предност во однос на дек­
лараторите од "стар стил" од првото издание бидејќи обезбедуваа т детекција
на грешки и препознавање на аргументите при функциски повици, но по цена
на: метеж и конфузија при нивното воведување и потребата за п риспособу­
вање на двете форми. Одредена синтаксичка грдост беше потребна заради
компатибилност, имено void како експлицитен маркер на функциите од нов
стил без параметар.
Нотацијата .. , ... "за варијабилни функции , исто така , е нова и заедно со
макроата од стандардното заглавје <stdarg. h>, формализираат ме ха низам
кој беше официјално забранет, но неофициј а лно прифатен во првото издание.
Овие нотации беа преземен и ОД јазикот е++ о

А8.7 Иницијаnи3ација

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


цира почетна вредност за идентификаторот кој се декларира. Пред иницијали­
заторот стои=, и често пати е израз , или листа од иницијализатори вгнездени
во големи загради. Листата може да завршува со запирка, што придонесува на
убавина на форматирањето.

иницијализатор:
израз-за -доделување
{ листа-иницијализатори}
{ листа-иницијализатори,}

листа- иницијализатори:
иницијализатор
листа-иницијализатори, иницијализатор

Сите изрази во иницијализаторот за статички објекти или низа мора да би­


дат константни изрази како што е опишано во Параграф А7 .19. Изразите во
иницијализаторот за auto или register објект или низа, според тоа, мора да
бидат константни изрази ако иницијализаторот е листа омеѓена со големи за­
гради. Меѓутоа, ако иницијализаторот за автоматски објект е единичен израз ,
тој не мора да биде константен израз, туку само мора да го има соодветниот
тип за доделување кон објектот.
Првото издание не одобруваше иницијализација на автоматски структури,
унии или низи. ANSI стандардот го дозволува тоа, но само со константни
конструкции освен ако иницијализаторот може да се изрази преку едноставен
израз.
262 Референтно упатство Додаток А

Статички објект кој не е експлицитно иницијализиран, се иницијализи­


ра како (нему или на неговите членови) да му била доделена константа о.
Иницијалната вредност за автоматски објект неиницијализиран експлицитно е
недефинирана .
Иницијализаторот за покажувач или за објект од аритметички тип е прост
израз , ограден можеби во големи загради. Изразот се доделува на објектот .
Иницијализаторот за структура е или израз од истиот тип , или листа на ини ­
цијализато ри оградена во големи загради за нејзините членови редоследно .
Неименуваните членови од битски полиња се игнорираат и не се иницијализи­
раат. Во случај на помал број на иницијализатори во листата отколку членови
во структурата , преостанатите членови се иницијализираат со о. Не смее да
има повеќе иницијализатори отколку членови.
Иницијализаторот за една низа претставува листа од иницијализатори за
нејзините членови оградена со големи за гради. Ако листата има непозната го­
лемина , бројот на иницијализаторите ја определува неј зи ната големин а, и со
тоа се компл етира нејзиниот тип. Ако н изата има фиксна големина бројот на
иницијализатори не може да го премине бројот на членови во низата ; ако има
помалку преостанатите членови на низата се иницијализираат на о.
Како специјален случај низа од знаци може да се иницијализира со стринг
константа ; последователните знаци од стрингот ги иницијализираат соодвет­
ните членови на низата . Слично, израз од широки знаци ( Параграф А2 . б)
може да иницијализира низа од тип wchar_ t. докол ку низата има непозната го­
лемина , бр ојот на знаците во стрингот, вклучувајќи го и завршниот нулти знак,
ја определува нејзината големина ; ако нејзината голем ина е фиксна , бројот на
знаци во ст ри нгот , не вклуч увајќи го тука и завршниот нулти знак , не смее да
ја надмине големината на низата .
Иницијализаторот за униј а е или про ст изра з од истиоттип или иниц ијализа-
то р за првиот нејзин чл е н ограден со мали загради.
Првото издание не дозволу ваше иницијализација на униите. Пр авилото "за
прв член " е несмасно , но е тешко да се генерализира без нова си нтакса.
Осве н дозволувањето униите експли цитно да се иницијализираат ба рем на
примитивен на ч ин , ова ANSI правила ја пра ви дефинитивна семантиката на
стати чки у нии кои не се експлицитн о иницијализ ирани.
Агрегат претставува структура или низа. Ако еден агрегат содржи членови
од агрегатен тип, правилата за иницијализација се применуваат рекурзивно.
Заградите може да се пропуштат кај иницијализацијата во следниов случај : ако
иницијализаторот за а грегатен член, кој сам по себе е агрегат , за почнува со лева
заграда то гаш листата од иницијализатори одделени со запирка, која следи, ги
ин ицијализира членовите на подагрегатот; погреш но е да има nовеќе иниција­
лизатори отколку членови . Меѓутоа , ако иницијали заторот на пода грегатот не
започнува со л ева голема заграда, тогаш се земаат онолку елементи од листата

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

го инициј а лизираат следни от член од агре гатот од кој што е дел подагр е гатот.
А.8 Декларации 263

На пример,

int х [] = { 1 , 3, 5 } ;

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


бидејќи не е специфицирана големина и бидејќи има три иницијализатори .

floa t у [ 4] [3] ={
{1,3 , 5} ,
{2,4,6},
{3,5 , 7},
};

е комплетно оградена иницијализација: 1 , 3, и 5 го иницијализираат п р виот


ред од низата у(О], т.е. у[О] [О] , у[О] [1] , и у[О] [2] . На ист начин сл ед­
ните две линии ги иницијализираат у [1] и у [2] . Иницијализаторот зав ршу ва
предвремено и поради тоа елементите на у ( 3] се иницијализираат со о . Ист
резултат може да се постигне и со

floa t у [ 4 ] [ 3] = {
1 , 3, 5 , 2 , 4 , 6 , 3, 5 , 7
};

Иницијализато рот за у започнува со лева заграда , но не и оној за у [о Ј ; пора­


ди тоа се користат три елементи од листата . На ист начин последовател н о се
земаатследнитетри , зау[1] иу[2]. Истотака ,

floa t у [ 4] ( 3] ={
{1}, {2} , {3} , {4}
};

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


Конечно,

charmsg[] ="Syntaxerror on line %s\n";


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

А8.8 Имиња на тиnови

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


со претоnување, за да се декларира параметарски тип во функциски декл а ра­
тори, и како аргумент на sizeof) потребно е да се обезбеди име за податоч е н
264 Рефер ентно упатство Додаток А

тип . Тоа се постигнува со користење на име-на-тип, кое синтаксички е декла­


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

име-на-тип :

спецификатор- квалификатор-листа апстрактен-деклараторopt

апстрактен -декларатор:
покажува ч

покажувачорt директен-апстрактен-декларатор

директ ен-апстрактен-декларатор:
(апстрактен -декларатор)
директен-апстрактен-декларатор opt [константен-израз ор,Ј
директен-апстрактен-декларатор opt (листа-тип- на-параметри opt>

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


раторот каде идентификаторот би се појавил, доколку конструкцијата е декла­
ратор за една декларација . Именуваниот тип потоа е ист со типот на х ипотетски­
от идентификато р. На пример ,

int
int *
int*[З]
int (*) []
int * ()
int (*[]) (void)

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

А8.9 Typedef

Декларации, чии што спецификатор н а мемориска класа е typedef не декла­


рираат објекти ; нам есто тоа дефинираат идентификатори кои именуваат типо­
ви. Овие идентификатори се наречени typedef имиња.

typedef-uмe:
идент ификатор

Една typedef декларација додава како атрибут тип на секое име п омеѓу него-
А.8 Декларации 265

вите декларатори на вообичаен иот начин (види Параграф АЅ. б) . Понатаму,


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

typedef long Blockno, *Blockptr ;


typedef struct { double r , theta ; } Complex ;

конструк циите

Blocknob ;
extern Blockptr bp ;
Complex z , * zp ;
се легални декларации. Типот на b е long, на bp е п окажувач кон long, и на zе
наведената структура ; zp е покажува ч кон таква структура.

typedef не воведува нови типови, туку синоними за типови кои може да се

специфицираат на друг нач и н. На при мер, b има исти тип како кој било lon g
објект .
Имињата декларирани со typedef може повто рн о да се декларираат во
внатрешен делокруг, но мора да се наведе непр азно множество на с п ецифи­
катори кон типот . На пр имер

extern Blockno ;

не го декларира повторно Blockпo, но

extern int Blockno ;

го декл а рира .

А8 . 1 О Еквивалентност на тиnови

Две л и сти од спецификатор и на типови се еквивалентни ако го содржат


истото множество на спецификатори на тип , земајќи предвид дека н екои с пе ­
цификатори може да бидат имплицирани од други (на при мер , осамен long
имплицира long int) . Структури , унии и енумерации со различни та гов и се
разликуваат , а неозначени унии , структури или енумерации специфицираат
единствен тип .
Два типа се еднакви ако нивните апстрактни декларатори (Параграф
АЅ . 8) , после проширувањето на кои било typedef типови и бришењето на
кои било спецификатори на функциски параметри , се еднакви до н иво на ек­
вивалентност на листите на спецификатори на типови . Големините на низите и
типовите на функциските параметри се значајни.
266 Референтно у па тство Додаток А

А9 Наредби

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


Наредбите се извршуваат заради нивниот ефект и не поседуваат вредности .
Спаѓаат во не колку групи .

наредба:
означ ен а-наредба
изразна-наредба
сложена наредба
наредба-за избор
наредба -за-повторување
скок-наредба

А9. 1 03начени (анг. labeled) наредби

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

Означена-наредба :
идентификат ор : наредба
саѕе константен-израз : наредба
default: наредба

Ознака (а нг. label) која се состои од идентификатор го декларира идентифи­


каторот. Единствена употреба на идентификаторска ознака е како цел на goto .
Делокругот на идентификаторот е тековната функција . Бидејќи озна ките имаат
со п ств е н просто р на имиња тие не се мешаат со дру ги идентиф икатори и не
можат повто р но да бидат декларирани . Види Параграф All.l.
саѕе оз на ки и defaul t ознаки се користат кај наредбата swi tch (Параграф
А9. 4) . Ко н стантн иот израз кај саѕе мора да биде од интегрален тип.
Самите ознаки не го менувааттекот на управува њето (извршувањето н а про­
грамата) .

А9.2 И3ра3ни наредби

Повеќето наредби се изразни наредби, кои имаат форма

изразна-наредба:
изразорr ;

Повеќето изразни наредби се доделување или функциски повици. Сите


странични ефекти од изразот се завршуваат пред да за поч не и звршувањето на
Ао9 Наредби 267

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


наредба ; често се употребува да обезбеди празн о тело н а наредба за повтору­
вање или за сместување на ознака о

А9.3 Сложени наредби

За да може да се користат повеќе на редби на места каде што се очекува


една, обезбедена е сложена наредба (исто така, наречена "блок" ) о Телото на
функциска дефиниција претставува сложена наредба о

сложена-наредба:
{ декларациска -листа ор
,листа-наредби opt }

декларациска-листа:
декларација
декларациска-листа декларација

листа-наредби:
наредба
листа-наредби наредба

Ако идентификатор во декларациска ли ста се на оfа во делокруг надвор


од блокот, надворешната декларација се суспендира внатре во блокот (види
Параграф All ol) , по што нејзиното дејство продолжува о Еден идентифика­
тор може да се декларира само еднаш во рамките на ист блок о Тие правила се
при менуваат н а идентификатори од ист прос тор на имиња (Параграф All) ;
идентификатори од различни простор и на имиња се сметаат за различн и о
Иницијализација на автоматски објекти се изведува при секое влегување во
блокот почнувајќи од врват и продолжувајќи во редослед на деклараторите о
Ако се изврши скок внатре во блокот овие и ницијал изации не се изведуваат о
Иниц ијализација на static објекти се изведува само еднаш, пред да за почне
извршува ње н а програмата о

А9.4 Наредби 3а И3бор

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

наредба-за-избор:
if (израз) наредба
if (израз) наредба else наредба
swi tch (израз) наредба
268 Референтно упатство Додаток А

Во двете форми на if наредбата , изразот кој мора да биде од аритметички тип


или покажувач, се евалуира вклучувајќи ги сите странични ефекти и ако не е една­
ков со о, се извршува првата поднаредба . Во втората форма , втората поднаредба
се извршува ако изразот е о . Двосмисленоста на else се разрешува со поврзување
на else со последниот if кој нема else и е на исто ниво на вгнезденост во блокот.
swi tch наредбата предизвикува контролата да биде префрлена на една од не­
колкуте наредби во зависност од вредноста на изразот , кој мора да биде од инте­
грален тип . Поднаредбите контролирани со switch вообичаено се сложени. која
било наредба во рамките на поднаредбата може да биде означена со една или по­
веќе саѕе ознаки ( Параграф А9. 1) . Управувачкиот израз поминува низ интегрална
промоција (Параграф Аб .1) , и саѕе константите се претвораат во промовираниот
тип. Никои две од саѕе константите асоцирани со иста swi tch наредба , не може да
имаат иста вредност после конверзијата. Може да има најмногу една defaul t озна­
ка асоцирана со swi tch. swi tch наредбите може да се вгнездат ; саѕе или defaul t
ознака се асоцира со најмалиот swi tch кој ја содржи .
При извршување на swi tch наредба се евалуира нејзиниот израз вклучу­
вајќи ги и страничните ефекти и се споредува со секоја саѕе константа. Ако
една од саѕе константите е еднаква на вредноста од изразот, управувањето

го префрла извршувањето на наредбата од совпаѓачката сазе ознака . Доколку


ниту една саѕе константа не од говара на изразот и доколку постои defaul t оз­

нака, управувањето го префрла извршувањето врз нејзината наредба. Доколку


ниту еден сазе не се совпаѓа и нема ниту defaul t ознака, тогаш ниту една од
поднаредбите на swi tch не се извршува.
Во првото издание на оваа книга од управувачкиот израз на swi tch и неговите
саѕе константи се бараше да имаат in t тип.

А9.5 Наредби 3а повторување

Наредбите за повторување спе цифицираат извршување во циклуси.

наредба-за-повторување :
while (израз) наредба
do нapeдбa while (израз);
for (изразopt ; изразopt ; изразupt ) нар едба

Во while и do наредбите , поднаредбата се повторува се додека вредноста на


изразот е нееднаква на о ; изразот мора да биде од аритметички тип или пока­
жувач. Кај while , тестот (споредбата), вклучувајќи ги сите странични ефекти
од изразот , се одвива пред извршувањето на наредбата ; кај do тестот следи
после секоја итерација .
Кај for наредбата , првиот израз се евалуира еднаш и со тоа специфици­
ра иницијализација за цикл усот . Не п остои ограничување во неговиот тип.
Вториот израз мора да биде од аритметички тип или покажувач ; се евалуира
А. 9 Наредби 269

пред секоја итерација и доколку стане еднаков на о 1 for циклусот завршува .


Третиот циклус се евалуира после секоја итерација 1 па така специфицира пов­
торна иницијализација за циклусот . Не постои ограничување во неговиот тип.
Страничните ефекти од секој израз се извршуваат веднаш после неговата ева­
луација. Доколку поднаредбата не содржи continue 1 наредбата

for (изразl ; израз2;израз3) наредба

е еквивалентна со

изразl;
while (израз2) {
наредба
изразЗ;
}

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


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

А9.6 Наредби за скок

Наредбите за скок предизвикуваат безусловен трансфер на извршувањето.

скок-наредба:
goto идентификатор;
continue;
break;
return израз opf

Во goto наредбата , идентификаторот мора да биде ознака (Параграф


А9 .1) лоцирана во тековната функција . Извршувањето преминува на наред­
бата после ознаката .
Наредбата continue може да се појави само во рамките на итерациска на ­
редба. Предизвикува префрлање на извршувањето кон наредната итерација
на најмалиот циклус околу самата coпtinue . Поточно , во рамките на секоја од
овие наредби

while ( ... ) do { for ( ... )

contin : contin: contin :


} while ( . .. } ;

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


СО goto contin .

Наредбата break може да се појави само во наредба за повторување или


270 Референтно упатство Додаток А

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


ја оградува; извршувањето се префрла на наредбата која што следи после пре­
кинатата наредба.
Една функција враќа вредност кон својот повикувач преку наредбата
return. Кога return е проследен со некој израз неговата вредност се пре­
праќа на повикувачот на функцијата . Изразот се претвора како при доделу­
вање, во повратниот тип на функцијата во кој се појавува.
Префрлање на извршувањето на крајот на функцијата е еквивалентно со
return без израз. И во двата случаја повратната вредност е недефинирана.

А 1О. Надворешни декnарации

Единицата влез (порцијата од влезен код) доделена на С компајлерот се на­


рекува единица за превод; се состои од секвенца на надворешни декларации ,
кои се или декларации или функциски дефиниции.

единица-за превод:
надворешна-декларација
единица-за-превод надворешна-декларација

надворешна-декларација:
функциска-дефиниција
декларација

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


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

содржи кодот на функциите.

А 10.1 Функциски дефиниции

Функциските дефиниции ја имаат формата

функциска-дефиниција:
декларациски-спецификатори opr декларатор декларациска-листа opr
сложена-наредба

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


декларациски спецификатори, се exterп или static; за разликите помеѓу нив ,
види Параграф All. 2.
Една функција може да врати аритметички тип, структура, унија, покажу­
вач или void, но не функција иЛи низа. Деклараторот во функциска деклара-
А.10 Надворешни декларации 27 1

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


е од функциски тип ; т. е. , мора да содржи една од следниве форми (види
Параграф А8.6 . З) .

директен-декларатор (листа-тип-на-параметри)
директен-декларатор (листа-идентификатори opr)

каде што директе н-декларатор е идентификатор или идентификатор ограден


со мали загради. Конкретно, не смее да достигне до функциски тип преку мож­
ностите на typedef .
Во првата форма дефиницијата е функција од нов стил , и нејзините пара­
метри, заедно со нивните типови , се декларирани во нејзината листа на па ­
раметарски типови ; листата на декларации која следи после функцискиот де­
кларатор мора да отсуствува. Во случај листата на параметарски типови да се
состои само од void , во смисла дека во функцијата не зема параметри , секој
декларатор од листата на параметарски типови мора да содржи идентифика­
тор. Ако листата на параметарски типови завршува со ", .. . " тогаш функција­
та може да биде повикана со повеќе аргументи отколку што има параметри ;
va_arg макромеханизмот дефиниран во стандардното заглавје <stdarg. h> и
опишан во Додаток Б, мора да се користи за да покажува кон вишокот аргумен­
ти . Таквите функции мора да имаат најмалку еден именуван параметар .
Во втората форма, дефиницијата е од стар стил : идентификаторската листа
ги именува параметрите, додека декларациската листа врзува типови кон нив.

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


Декларациска та листа единствено мора да декларира параметри именувани во
листата, иницијализација не е дозволена, а единствен можен спецификатор на
мемориска класа е register.
Во двата стила на функциска дефиниција се подразбира дека параметрите се
декларирани веднаш после почетокот на сложената наредба која го сочинува
телото на функцијата, па така исти идентификатори не може да бидат повторно
декларирани таму (иако, како и другите идентификатори може повторно да
бидат декларирани во внатрешни блокови) . Ако еден параметар е деклари­
ран како " низа од некој mun", декларацијата се приспособува за читање како
" покажувач кон некој mun", слично, ако еден параметар е деклариран како
" функција која враќа некој mun" декларацијата се приспособува за читање како
" покажувач кон функција која враќа некој mun". За време на повикот кон функ­
ција, аргументите се претвораат по потреба и се доделуваат на параметрите ;
види Параграф А7 . з. 2.
Функциските дефиниции од нов стил се нови на воведена со ANSI стандардот.
Исто така , п остои и мал а промена во деталите на промоцијата ; прв ото издание
специфицираше дека декларациите на float параметри се приспособуваат да
читаат double. Разликата се забележува кога покажувач кон параметар се ге­
нерира внатре во функција .
Еве еден целосен пример на функциска дефиниција од нов стил
272 Референтно упатство Додаток А

intПIAx(inta , intb, inte)


{
intm;
m= (a>b) ?а: b;
return (m>e) ?m: е;

Овде int претставува спецификатор на декларацијата ; max (int а, int b , int


е) е функциски декларатор , а { .. . } е блокот којшто го дава кодот за функција­
та. Соодветна дефиниција по стар стил би била
intmax(a, b, е)

inta, b, е;

/* . . . */

каде што int max (а, b, е) е декларатор, а int а , b, е ; е декларациска листа

за параметрите .

А 10.2 Надвореwни декпарации

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


ции и други идентификатори. Терминот " надворешен " се однесува на нивна­
та локација надвор од функциите, и не е директно поврзан со клучниот збор
extern; мемориската класа за надворешно деклариран објект, може да биде
оставена празна, или да биде специфицирана како extern или statie.
Во рамките на иста единица за превод може да постојат неколку надвореш ­
ни декларации за истиот идентификатор, доколку тие се поклопуваат по тип и
поврзување , и ако има најмногу една дефиниција за идентификаторот.
Две декларации за објект или функција се поклопуваат по тип во согласност
со правилото разгледано во Параграф АВ. 10. Додатно , ако декларациите се
разликуваат бидејќи еден тип е некомплетна структура, унија или енумерација
(Параграф А8 . 3) , а другиот е соодветниот комплетиран тип со истиот таг , се
смета дека типовите се поклопуваат. Уште повеќе , ако еден тип е некомплетен
тип на низа (Параграф А8. б. 2) , а другиот е комплетиран тип на низа , докол­
ку типовите се идентични по другите основи, се зема дека се поклопуваат, исто

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

Ако првиот надворешен декларатор за една функција или објект го вклучу­


ва спецификаторот statie , идентификаторот има внатрешно поврзување ;
во спротивно има надворешно поврзување. Поврзувањето е објаснето во
Параграф 11 . 2.
A.ll Делокруг и поврзување 273

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


има иницијализатор. Декларација на надворешен објект која не поседува ини­
цијализатор и не го содржи спецификаторот extern претставува провиз орна
дефиниција. Ако во единица за превод за еден објект се појавува дефиниција 1
сите провизорни дефиниции се третираат само како непотребни декларации.
Ако во единица за превод за еден објект не се појавува ниедна дефиниција 1

сите негови провизорни дефиниции стануваат една дефиниција со иницијали­


затор о.
Секој објект мора да има точно една дефиниција. За објекти со внатрешно
врзување 1 ова правило се применува посебно за секоја единица за превод 1
бидејќи внатрешно поврзаните објекти се уникатни за една единица за превод .
За објекти со надворешно поврзување 1 правилото се применува за целата про­
грама.

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


то издание на оваа книга 1 неговиот ефект е идентичен со оној што е наведен
овде. Некои имплементации го ол абавуваат правилото 1 преку обопштување
на поимот за провизорна дефиниција. Во алтернатив ната формулација 1 која
е вообичаена за UNIX систе мите и препо з натлива како о пшта екстенз ија од
страна на стандардот 1 сите провиз о рни дефиниции за надвор е шно поврзан
објект 1 низ сите единици за превод во програмата 1 се земаат предв и д заед­
но 1 наместо посебно во секоја единица за превод. Ако една д ефиниција се
појави некаде во програмата 1 тогаш провизорните дефиниции се земат само
како декларации 1 но доколку не се п ојави дефиниција 1 тогаш сите провизор­
ни дефиниции 1 стануваа т дефиниција со и нициј ализатор о.

А 11. Делокруг и noвp3yBatbe

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


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

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


помеѓу функциите на една програма може да се изведе и преку повици и преку
манипулација на надворешни податоци.
Поради тоа, потребно е да се ра згледаат два типа на делокруг: прво 1
лексичкиот делокруг на еден идентифи катор кој е област од n рограмскиот текст
во рамките на кој се разбираат карактеристиките на идентификаторот ; и вто­
ро 1 делокругот асоциран со објекти и функции со надворешно поврзување 1
кој ги одредува врските помеѓу идентификаторите во одделно компајл и рани
единици за превод.

А 11 .1 Лексички делокруr

Идентификаторите потпаѓаат во некол ку простори на имиња кои немаат


пресек еден со друг; истиот идентификатор може да се користи за различни
274 Референтно упатство Додаток А

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

имиња. Тие класи се: објекти, функции, typedef имиња и enum константи;
ознаки; тагови на структури, унии и енумерации; и членови на секоја структу­
ра или унија посебно.
Овие правила се разликуваат на неколку начини од тие опишани во прво­
то издание на овој прирачник. Ознаките претходно не поседуваа сопствен
простор на имиња; таговите на структурите и униите имаа посебни просто­
ри на имиња, а кај некои имплементации, исто така, енумерациските тагови;
ставањето на различни типови на таrови во истиот простор е ново ограничу­

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

Лексичкиот делокруг на еден објектен или функциски идентификатор во


некоја надворешна декларација започнува на крајот од неговиот декларатор
и опстојува до крајот на единицата за превод во која се појавува . Делокругот
на еден параметар во функциска дефиниција започнува на почетокот од бло­
кот кој ја дефинира функцијата и опстојува до завршетокот на функцијата; де­
локругот на еден параметар во функциска декларација завршува на крајот од
деклараторот. Делокругот на еден идентификатор деклариран на заглавјето од
еден блок, за почнува на крајот од неговиот декларатор и опстојува до крајот
на блокот. Делокругот на една ознака е целата функција во која таа се поја­
вува. Делокругот на една структура, унија, енумерациски таг или енумера­
циска константа започнува кај нивната појава после спецификаторот на типот
и опстојува до крајот на единицата за превод (за декларации на надворешното
ниво) или до крајот на блокот (за декларациите внатре во функција) .
Ако еден идентификатор е експлицитно деклариран на заглавјето од еден
блок, вклучувајќи и блок од кој што се состои една функција, секоја деклара­
ција на идентификаторот надвор од блокот е суспендирана до крајот на блокот.

А 11.2 Повр3ување

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


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

Како што е спомнато во Параграф AlO. 2, првата надворешна декларација за


еден идентификатор му доделува на идентификаторот внатрешно поврзување
доколку се користи спецификаторот static, во спротивно идентификатор му
доделува надворешно поврзување. Ако декларацијата за еден идентификатор
во рамките на еден блок не го вклучува спецификаторот extern , тогаш иден-
А.12 Претпроцесирање 275

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


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

А 12. Претпроцесирање

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


вање на именувани датотеки. Линиите кои започнуваат со#, пред кои можеби
има празно место, комуни цираат со претпроцесорот . Синтаксата н а ти е линии
е незави сна од остатокот на јазикот ; тие може да се појават насекаде и имаат
ефект кој трае (независно од делокругот) до крајот на единицата за превод.
Границите на линиите се значајни; секоја линија се анализира посебно (nогле­
днете во Параграф А12 .2 како да п оврзете линии) . За претпроцесорот , белег
претставува кој било белег од јазикот , или секвенца од знаци која резултира со
име на датотека како кај #include директивата (Параграф А12 . 4) ; додатно ,
секој знак кој поинаку не е дефиниран се зема како белег. Сепак, ефектите на
п разните места, различни од бланко и хоризонтален таб се недефинирани за
претпроцесорските линии.
Самото претпроцесирање се одвива во неколку логички последователни
фази што можат, во една посебна имплементација, да се спојат.

1. Прво, триграф секвенците, опишани во Параграф А12 . 1, се замену­


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

2. Секоја појава на контраниз знак \ проследен со знак за нова линија се


брише, на тој начин спојувајќи ги линиите (Параграф А12. 2) .
з . Програмата се дели на белези одделени со знаци за п разни места; ко­
ментарите се заме н уваат со едно празно место. Потоа се исполнуват ди­
рективите на претпроцесорот, а макроата се експандираат (Параграф
А12.3-Параграф Al2.10).
4. Излезните секвенци во знаковните и стринговите константи се заменува ­
ат со нивните еквиваленти; потоа соседните стри нгови се надоврзуваат.

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


библиотеки, преку собирање на потребните програми и податоци и по­
врзување на надворешните функции и објектни референци кон нивните
дефиниции .
276 Референтно упатство Додаток А

А 12.1 Триграф секвенци

Знаковното множество на изворните програми во С е содржано во рамки­


те на седумбитноASCII, но е супермножество за множеството ISO 646-1983
Invariant Code Set. Со цел да се овозможи програмите да бидат претставени
со скратеното множество, сите појавувања на следните триграф се квенци се
заменуват со соодветниот единечен знак. Таа замена се извршува пред сите
други процесирања.

??= # ?? ( [ ??<
??/ \ ??) ] ??>
?? ' л
??! 1 ??-

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


Триграф секвенците се новина воведена со ANSI стандардот.

А 12.2 Спојување на линиите

Линиите кои завршуват со контраниз знак\ се спојуваат со бришење на кон­


транизот и знакот за нова линија којшто го следи. Тоа се случува пред подел­
бата во белези .

А 12.3 Дефиниција на макроа и нивна експанзија

Контролна линија со следнава форма

# dejine идентификатор секвенца-од-белези

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


соодветната секвенца на белези од страна на претпроцесорот; водечките и
п разните места кои следат по секвенцата од белези се отфрлаат. Втора #define
за истиот идентификатор се зема за грешка освен ако втората секвенца на беле­
зи не е идентична со првата, каде сите разделувања со п разни места се земаат

за еквивалентни.

Линија со следнава форма

# define uдентификатор (лuста-идентификатори) секвенца-од-белезu

каде што нема празно место помеѓу првиот идентификатор и (, претставува


макродефиниција со параметри зададени со листата на идентификатори. Како
и кај првата форма, водечки и п разни места по секвенцата од белези се отфр­
лаат, а макрото може да биде повторно дефинирано само со дефиниција во
А.12 Претпроцесирање 277

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


Контролна линија од следнава форма

ft undef идентификатор

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


на претпроцесорот. Примената на tundef врз непознат идентификатор не се
смета за грешка.

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


текстуални инстанци на макроидентификаторот проследени со опционално
празно место и потоа (, со секвенца од белези раздвоена со запирки и со ),
сочинуваат повик кон макрото. Аргументите на повикот се секвенци од белези
меѓусебно одвоени со запирка ; запирките коишто се во наводници ил и зашти­
тени со вгнездени мали загради, н е ги раздвојуваат а ргументите. За време на
собирањето, аргументите не се макроекспандираат. Бројот на аргументи во
повикот мора да одговара на бројот на параметри во дефиницијата. Откако ќе
се изолираат аргументите, водечките и п разните места кои следат се отстрану­

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


се заменуваат за секоја нивна појава без двојни наводници од соодветниот па­
раметарски идентификатор во секвенцата на замена на белезите за макрото.
Доколку на параметарот во секвенцата за замена му претходи t или следи ##,
аргументните белези се анализираат за макроповици и се експандираат по пот­
реба , веднаш пред вметнувањето.
Два специјални оператора влијаат на процесот на замена . Прво, ако на поја­
вата на еден параметар во секвенцата од белези за замена, непосредно и пре­
тходи i, околу соодветниот параметар се сместуваат нав од ници , а потоа # и
параметарскиот идентификатор се заменуваат со аргументот во на водници. Се
вметнува знак \, пред секој знак " или \ кои се појаву ваат околу или во ст ринг
или знаковна константа во аргументот.

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


содржи оператор ii, тогаш веднаш после замената на параметрите се отстра ­
нува секој ti , вкл учувајќи ги сите п разни места од двете стран и со цел да се
спојат соседни белези и да формираат нов белег. Резултатот е недефиниран
ако произлезат невалидни белези или ако истиот за виси н а редоследот од пре­
тпроцесирањето на операторите 11. Исто така ti не може да се појави на поче ­
ток или на крај на сек венца од белези за замена .
И кај двата вида на макро 1 секвенцата на белези за замена постојано пов­
торно и повторно се скенира за нови дефинирани идентификатори. Меѓутоа 1
кога еднаш еден идентификатор се замени во дадена експанзија 1 не се за ме­
нува повторно ако се јави одново за време на скенирањето; наместо тоа се
остава непроменет .

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


се смета за претпроцесорска директива.

Деталите на nроцесот на макроексnанзија nоnрецизно се оnишани во ANSI


278 Референтно упатство Додаток А

стандардот отколку во првото издание о Најзначајната промена е додавањето


н а операторите # и ##, кои овозможуваат ставање во наводници и надовр зу­
вање о Некои од новите правила, посебно тие кои вклучуваат надовр зување ,
се бизарни о (види во примерот подолу) о
На пример, оваа конструкција може да се користи за " претставување кон­
станти " , како во

#define TAВSIZE 100


int table [TAВSIZE] ;

Дефиницијата

#define AВSDIFF (а , b) ((а)> (b) ? (а)- (b) : (b)- (а))

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


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

За дадената дефиниција

#define tempfile(dir) #dir "/ % ѕ"

повик кон макрото tempfile (/usr/tmp) враќа

" /usr/tm.p" "/ % ѕ "

што последователно ќе биде поврзано во еден стринго По

#define са t (х , у) х ## у

повикот cat (var, 123) враќа var123o Меѓутоа, cat(cat(1,2) , 3) е неде­


финирано: присуството на## го оневозможува проширувањето на аргументи­
те од надворешниот повик о Така ,резултатот е
cat ( 1 , 2) 3

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


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

#definexcat(x , у) cat(x , y)

нештата функционираатглатко; xcat (xcat (1, 2) , 3) дава резултат 1 , 2 , 3, би­


дејќи самата макроекспанзија на xcat не вклучува оператор ##о
Соодветно , AВSDIFF(AВSDIFF(a , b) ,е) го враќа очекуваниот наполно
проширен резултат о
А. 12 Претпроцесирање 279

А 12.4 Вклучување на датотеки

Контролна линија во форма

1t include <име-на- датотека>

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


датотека. Знаците во името име-на-датотека не смеат да вклучуваат > или
знак за нова линија, и резултатот е недефиниран ако содржи било кој од знаци­
те", ', \, или /* . Именуваната датотека се бара во секвенца на места дефи ­
нирани со имплементацијата.
Слично , една контролна линија во форма

lt include " име-на-датотека "

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


мерно зависна од имплементацијата) , и ако тоа nребарување не
ycnee, тога ш
се пребарува како во првата форма . Резултатот од користење на , \, или/* во
името на датотеката е недефиниран, но >е дозволено .
На крај, директива во форма

f include секвенца-од-белези

која не одговара на претходните форми се интерпретира со експанзија на


секвенцата на белези како за нормален текст; мора да резултира со една од две­
те форми< .. .> или " . .. ", и потоа се третира како што е опишано претходно.

linclude датотеките може да се вгнездуваат.

А 12.5 Условна комnилација

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


следната шематска синтакса .

претпроцесорски-услов :

if-лuнuja текст elif-дeлoвu еlѕе-делорr fendif

if-линија:
ft if константен-израз
ft ifdef идентификатор
ft ifndef идентификатор
280 Референтно упа тство Додаток А

elif-дeлoвu:
elif-лuнuja текст
elrif-дeлoвu opt

elif-лuнuja:
lf elif константен-израз

еlѕе-дел:
е/ѕе-линија текст

еlѕе-линија:
lfelse

Секој а од директивите (if-line 1 elif-line 1 else-line 1 and lfendif)


во една линиј а се појаву ва сама . Константниот израз во lfif и последо в ател­
ната lfelif линија се евалуираат редоследно се додека не се дојде до израз
со ненулта вредност; текстот кој следи после линија со вредност О се отфрла.
Текстот кој следи после успешна директива се третира нормално. "Текст " овде
се однесува на каков било материјал 1 вклучувајќи и претпроцесорски линии
што не се дел од усло вната структура ; може да биде и празен. Кога еднаш ќе
се пронајде ус пешна lfif или lelif линија и нејзини от текст ќе се процесира 1
последователните lelif и lelse линии 1 заедно со нивниот текст 1 се отфрла­
ат . Докол ку сите изрази имаат вредност о 1 а постои и lelse линија 1 те кстот
што следи после lfelse се обработува нормално . Текст контролиран со неак­
тивни гранки на условот се игнорира освен за проверување на вгнезд ува ње на

ус-лови .
Константн иот израз во #if и lfelif е предмет на обично макрозаменување.
Уште повеќе 1 кои било изрази во форма

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

или

defined ( идентификатор )

се заменуваат пред да се скенираат за макроаl со lL ако идентификаторот е


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

грална константа се смета како проследена со L 1 така што целата аритметика се

зема да биде за long или з а unsigned long .


Резултантниот ко нста нте н и з раз (Пара граф А7 .19) има ограничување:
'мора да биде интегрален 1 и не може да содржи sizeof 1 израз за претопување
(cast) или енумерациска ко н с та н та.
А.12 Претпроцесирање 28 1

Контролните линии

#ifdef идентификатор
#ifndef идентификатор

соодветно се еквивалентни со

# if defined идентификатор
# if ! defined идентификатор

#elif е новина воведена од првото издание иако и претходно беше присут­


на кај некои претпроцесори. Операторот за претпроцесирањ е defined , исто
така, е нов.

А 12.6 Линиска контрола

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


во една од следниве форми

# line константа " име-на-датотека "


# line константа

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


дека линискиот број на следната изворна линија е даден со декадна цел об рој­
на константа и дека тековната влезна датотека е именувана со идентификато­
рот. Ако не е наведено името на датотеката, запаметеното име не се менува.
Макроата во линијата се прошируваат пред таа да се интерпретира.

А 12.7 Генерирање на rpewкa

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

# error секвени,а-од-белези opt


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

А 12.8 Pragma

Контролна линија во форма

# pragma секвенца-од-белези o pt
282 Референтно упатство Додаток А

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


Непрепозната прагма се игнорира .

А 12.9 Празна директива

Контролна линија во форма

#
нема никаков ефект.

А 12.1 О Предефинирани имиња

Неколку идентификатори се предефинирани и се експандираат за да произ­


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

LINE Декадна константа која го содржи бројот на тековната


изворна линија .

FILE Стрингова константа која го содржи името датотеката која се


компајлира .
DATE Стринг константа која го содржи датумот на компајлирање,
во форма "Мmm dd уууу"
ТIМЕ Стринг константа која го содржи времето на компајлирање во
форма " hh :mm: ѕѕ "
ЅТDС Константа со вредност 1 . Наменето е овој идентификатор
да биде дефиниран како 1, само во имплементации кои се
придржуваат до стандардот.
#error и ipragma се нови на воведе на со ANSI стандардот ; предефинирани-
те претпроцесорски ма к роа се нови , но некои од нив беа присутни кај некои
имплемента ци и и порано .

А 13. Граматика

Подолу е дадено резиме на граматиката која беше користена во претходни­


те делови на овој додаток. Ја има истата содржина, но во друг редослед.
Граматиката содржи недефинирани завршни симболи како цело бројна­
конст ан та, знаковн а-константа, реалнобројна -кон станта, идентиф икатор,
стринг, и енумера циска-ко нстанта ; зборовите и симболите во nечатарсхи
стил се терминали дадени буквално. Оваа граматика може да биде трансфор­
мирана механички во влез прифатлив за еден автоматски генератор на пар­
сер. Освен додавањето на какво било синтаксичко обележување кое ќе се ко­
ристи да индицира алтернативи во продукцијата, потребно е да се прошират
А.13 Граматика 283

конструкциите"еден од" и (во зависност од правилата на парсер-генераторот)


да се удвои секоја продукција со симбол opt, еднаш со симболот и еднаш без
него. Со уште една промена, конкретно бришењето на продукцијата tуреdеf­
име-идентификатор и правењето на typedef-uмe за завршен симбол, оваа гра­
матика е прифатлива за УАСС парсер-генератор. Има само еден конфликт гене­
риран со двосмисленоста на if'-else.
единица-за-превод:
надворешна -декларација
единица-за-превод надворешна-декларација

надворешна-декларација:
функциска -дефиниција
декларација

функциска-дефиниција:
декларациски-спецификатори opt декларатор декларациска-листаорt
сложена -наредба

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

декларациска-листа:
декларација
декларациска-листа декларација

декларациски- спецификатори:
спецификатор-на-мемориска-класа декларациски-спецификатори opt
тип-спецификатор декларациски-спецификатори op t
тип-квалификатор декларациски-спецификатори opt
спецификатор-на-мемориска-класа: еден од
auto register static extern typedef'
mun сп ецификатор: еден од
void char short int long float double siqned
unsigned структурен-или-униски-спецификатор енумерациски-специфи
катор typedef-uмe

тип-квалификатор: еден од
const volatile

структурен-или-униски-спецификатор:
структура-или-унија идентификатор opt { структурна-декларациска­
листа}
структура -или-унија идентификатор

'
284 Референтно упатство Додаток А

структура-или-унија: еден од
struct union

структурна-декларациска-листа:
структурна-декларација
структурна-декларациска -листа структурна-декларација

листа-иницијални-декларатори:
иницијален-декларатор
листа-иницијални-декларатори, иницијален-декларатор

иницијален-декларатор:
декларатор
декларатор = иницијализатор
структурна-декларација:
спецификатор-квалификатор-листа структурна-декларациска-листа;

спецификатор-квалификатор-листа:
тип-спецификатор спецификатор-квалификатор-листа opt
тип-квалификатор спецификатор-квалификатор-листа •Р'

структурна-декларациска-листа:
структурен-декларатор
структурна-декларациска-листа, структурен-декларатор

структурен-декларатор:
декларатор
декларатор opt: константен-израз

енумерациски -спецификатор:
enum идентификатор opt { листа-енумератори}
enum идентификатор

листа-енумератори:

енумератор

листа -енумератори, енумератор

енумератор:

идентификатор
идентификатор = константен-израз
декларатор:
покажувачopt директен-декларатор
А.13 Граматика 285

директен-декларатор:
идентификатор
(декларатор)
директен-декларатор [константен-израз opr]
директен-декларатор (листа-тип-на-параметри)
директен-декларатор (листа-идентификаториopt )

покажувач:

"тип -квалификатор-листа opt


"тип-квалификатор-листа.Р, покажувач

тип-квалификатор-листа:
тип-квалификатор
тип-квалификатор-листа тип-квалификатор

листа-тип-на-параметри:

листа-параметри

листа - параметри, ...

листа-параметри:

параметарска-декларација
листа-параметри , параметарска-декларација

параметарска-декларација:
декларациски-спецификатори декларатор
декларациски-спецификатори апстрактен-декларатор opt

листа-идентификатори:
идентификатор
листа-идентификатори, идентификатор
иницијализатор:
израз-за-доделување
{ листа-иницијализатори}
{ листа-иницијализатори, }

листа-иницијализатори:
иницијализатор
листа -иницијализатори, иницијализатор

име-на-тип:

спецификатор- квалификатор-листа апстрактен-деклараторopt


апстрактен-декларатор:
покажувач

покажувач opt директен -апстрактен-декларатор


286 Референт~о упатство Дод аток А

директен-апс_mрактен-декларатор:
(апстрактен-декларатор)
директен-апстрактен-декларатор opt { константен-израз орЈ
директен-апстрактен-декларатор opt (листа-тип-на-параметри ор)

typedef-uмe:
идентификатор

наредба:
означена-наредба
изразна-наредба
сложена-наредба
наредба-за-избор
наредба-за-повторување
скок-наредба

означена-наредба:
идентификатор: наредба
саѕе константен-израз : наредба
defaиlt : наредба

изразна-наредба:
израз.Р,;

сложена-наредба:
{ декларациска-листаopt листа-наредби ,} ор

листа-наредби:
наредба
листа-наредби наредба

наредба-за-избор:
if(израз) наредба
if(израз) наредба else наредба
switch (израз) наредба

наредба-за-повторување:
while (израз) наредба
do наредба while (израз);
for (изразор,; израз ор,; изразор) наредба
А.13 Граматика 287

скок-наредба:
goto идентификатор;
continиe;
break;
retиrn израз opf

израз:

израз-за-доделување
израз, израз-за-доделување

израз-за-доделување:
условен-израз

унарен-израз оператор-за-доделување израз-за-доделување


assignment-operator: еден од
= *=!= %= += -= <<= >>= &= л= 1=

условен-израз:

логички-ИЛИ-израз
логички-ИЛИ-израз? израз: условен-израз

константен -израз:

условен-израз

логички-ИЛИ-израз:
логички-И-израз
логички-ИЛИ-изразii логички-И-израз

логички-И-израз:
ИЛИ-израз
логички-И-израз && ИЛИ-израз

ИЛИ-израз:
исклучително-ИЛИ-израз
ИЛИ-изразi исклучително-ИЛИ-израз

исклучително-ИЛИ-израз:
И-израз
исклучително-ИЛИ-израз л И-израз

И-израз:
израз-за-еднаквост
И-израз & израз-за-еднаквост
288 Референтно упатство Додаток А

израз-за-еднаквост:
релациски-израз

израз-за-еднаквост == релациски-израз
израз-за-еднаквост != релациски-израз

рела циски - израз:

израз-за-поместување

релациски-израз < израз-за-поместување


релациски - израз > израз -з а-поместување
релациски-израз <= израз-за-поместување

релациски-израз >= израз-за-п оместување

израз-за-поместување:

израз-за-додавање
израз-за-поместување << израз-за-додавање
израз-за-поместување >> израз-за- додавање

израз-за-додавање:
мултипликативен-израз

израз-за-додавање + мултипликативен-израз
израз-за-додавање -мултипликативен-израз

мултипликативен-израз:

мултипликативен-израз * израз-за -претопување


мултипликативен-израз 1 израз-за-претопување
мултипликативен-израз % израз-за- претопување

израз-за-претопување:

унарен израз
( име-на-тип) израз-за-претопување

унарен-израз:
постфиксен израз
++унарен израз

-- инарен израз

унарен-оператор израз-за-претопување

sizeofунарен-израз
sizeof ( име-на- тип)

унарен оператор: еден од


& *+ --!
Ао13 Граматика 289

постфиксен-израз:
примарен-израз

постфиксен-израз [израз]
постфиксен-израз (аргумент-израз-листаор)
постфиксен-изразо идентификатор
постфиксен-израз ->+ идентификатор
постфиксен-израз ++
постфиксен-израз --

примарен-израз:

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

стринг

(израз)

аргумент-израз-листа:

израз-за-доделување
листа-израз-за-доделување, израз-за-доделување

константа:

интегрална-константа

знаковна-константа

реалнобројна-константа
енумерациска-константа

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


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

контролна-линија:
# define идентификатор секвенца-од-белези
# define идентификатор (идентификатор, .. о, идентификатор)
секвенца-од-белези
# иndef идентификатор
# inclиde < име-на-датотека >
# inclиde "име-на-датотека "
# line константа " име-на-датотека "
# line константа
# error секвенца -од-белези opt
# pragтa секвенца-од-белези opt
#

претпроцесорски -услов
290 Референтно упатство Додаток А

претпроцесорски-услов:

if-линија текст elif-дeлoвu еlѕе-делopr #endif

if-лuнuja:
# if константен-израз
# ifdef идентификатор
# ifndef идентификатор

elif-дeлoвu:
elif-лuнuja текст
elif-дeлoвuopr

elif-лuнuja:
# elifконстантен-израз

еlѕе-дел:
еlѕе-линија текст

еlѕе-линија:
#else
Додаток Б: Стандардна библиотека

Овој додаток претставува резим е на библиотеката дефини ран а со ANSI стан­


да рдот . етандардната библиотека не е дел од самиот јазик е, туку околина која
го поддржува ста ндардниот е и обезбедува фун кциски декларации , типови и
дефиниции на макроа . Изоставивме мал број функции кои имаат ограничена
употреба или се лесни за изведување со помош н а другите; и зоставени се и по­
веќебајтните знаци ; и ја изоставивме дискусијата за теми кои зависат од "лока­
циј ата "; т . е . карактеристики кои зависат од локалниот јазик, националност ,
или култура .

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


рани во следниве стандардни заглавја:

<assert.h> <fioat.h> <math.h> <s tdarg . h> <stdlib.h>


<ctype . h > < limi ts . h> <setjmp. h > <stddef . h > <string.h>
<errno. h> <locale. h> <signal . h > < stdio .h> <time.h>

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

ltinclude <Заглавје>

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


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

Б 1. Влез и излез: <stdio.h>

Влезно и излезните функции , типовите и макроата дефинирани во <stdio . h >


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

29 1
292 Стандардна библиотека Додаток Б

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


врзат со диск или друг периферен уред. Библиотеката поддржува текстуални
и бинарни потоци, иако кај некои системи посебно UNIX, овие се идентични .
Текстуален поток е секвенца од линии; секоја линија има нула или повеќе зна­
ци и завршува со' \n' . Една околина може да има потреба да конвертира текс­
туален поток во или од некоја друга репрезентација (како што е мапирање на
' \n' со знак за нов ред или знак за нов лист) . Бинарен поток е секвенца од неп ­
роцесирани бајти кои зачувуваат внатрешни податоци , со својство идентично
со о на што првин е за пишано, а потоа истото прочитано на ист систем.

Еден поток се поврзува со датотека или уред преку негово отворање; таа
врска с е прекинува со затворање на потокот . Отворање на датотека враќа по­
кажувач кон објект од тип FILE , кој чува разни податоци потребни за контрола
на потокот. Наизменично ќе ги користиме термините "датотечен покажувач " и
"пото к ", во случаите каде нема двосмисленост.
Кога една програма за почнува со извршување, трите потоци stdin, stdout
и ѕ tderr веќе се отворени .

Б 1.1 Операции со датотеки

Следниве функции се справуваат со операции врз датотеки. Типот size t е


неозначен интегр ал ен тип кој се враќа од страна на операторот sizeof .

FILE *fopen (const char *filename, const char *mode)

fopen ја отвора именуваната датотека , и враќа поток , или, пак, NULL вред­
ност во случај на неус пех . За полетоmode легални се следниве вредности :

"r " отворањ е на текстуа л на датотека за читање,

" w" креирање на текстуална датотека за запишување; ја брише


претходн ата содржи н а доколку ја има,
" а" дополнување; отвора или креира текстуална датотека во која
ќе се запишува почнувајќи од крајот н а нејзи ната содржина,
" r+ " отвора ње н а текстуал н а датоте ка за нејзин о ажурирање (т . е
читање и запишување),

" w+ " креирање на текстуална датотека за ажурирање, нејзината


претходна содржина , доколку ја има, се брише, и
" а+ " дополнув ање ; отворање или креирање на текстуална датоте

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


крај.

Модот за ажурирање дозволува читање и за пишување на истата датотека; по­


меѓу операции на ч итање и запишување мора да се повика fflush или функција
за позиционирање на датотека . Ако модот после п о четната буква вклучува b ,
Б.1 Влез и излез: <stdio.h> 293

како во "rb" или "w+b", тогаш станува збор за бинарна датотека. Имињата на
датотеките се ограничени на FILENAМE_МAX број на знаци. Најмногу FOPEN_
МАХ број на датотеки може да бидат отворени во еден момент.

FILE *freopen (const char *filename, const char *mode , FILE *stream)
freopenja отвора датотеката во наведениот мод и асоцира поток со
неа. Враќа поток, или NULL при појава на грешка . freopen за проме­
на на датотеките асоцирани со stdin , stdout или stderr .

int fflush (FILE *stream)


Кај излезен поток , fflush доведува до запишување на сите бафери­
рани, но незапишани податоци ; за влезен поток , ефектот е недефи­
ниран. Враќа EOF во случај на грешка при запишување, во спротивно
нула. fflush (NULL) ги ослободува с и те излезни потоци.

int fclose (FILE *stream)


fclose ги ослободува сите незапишан и податоци за еден поток, го
отфрла секој непрочитан бафериран влез , го ослободува секој авто­
матски алоциран бафер, и го затвора потокот. Враќа EOF во случај на
појава на каква било грешка а во с противн о нула .

int remove (const char *filename)


remove ја отстранува именуваната датотека , со што сите наредни
обиди за неј зи на повикување ќе бидат безуспешни . Во случај н а неус­
пех враќа ненулта вредност.

int rename (const char *oldname , const char *newname)


rename менува име на една датотека ; во случај на неуспех вра ќа
нен улта вредност.

FILE *tmpfile (void)


tmpfile креира привремена датотека со мод "wb+" која автоматски
се отстран ува после нејзина затворање или после нормално заврш у­
вање на програмата. tmpfile враќа поток , или NULL доколку датотека­
та не била креирана.

char *tmpnam(char ѕ [L_tmpnam])


tmpnam (NULL) креира стринг кој не е име на некоја веќе постој на
датотека , и враќа покажувач кон внатрешна статичка низа. tmpnam (ѕ)
го складира стрингот во ѕ и го враќа истиот како фун к ц иск а вредност ;
ѕ мора да има доволно место барем за L_ tmpnam з наци. tmpnam ге­
нерира различно име при секое неј зи на п ов ик ување; за време на из­
вршув ањето на програ мата се гаранти раат најмногу ТМР_ МАХ раз ли чни
имиња . Забележете дека tmpnam креира име , а не датотека.
294 Стандардна библиотека Додаток Б

int setvbuf (FILE *stream , char *buf , int mode, size_ t size)
setvbuf го контролира баферирањето кај потокот; мора да биде
повикана пред читање запишување или која било друга операција.
М одат _ IOFВF предизвикува целосно баферирање, _ IOLВF линиско
баферирање на текстуални датотеки , а за_IONВF нема баферирање.
Ако полето buf не е NULL, истото ќе се користи како бафер, во спро ­
тивно ќе се алоцира бафер . Полето size ја определува големината на
баферот . Во случај на грешка setvnuf враќа ненулта вредност.

void setbuf (FILE *stream , char *buf)


Ако buf е NULL , баферирањето на потокот е оневозможено. Во
спротивно , setbuf е еквивалентна на (void) setvbuf (stream , buf ,
_IOFВF , BUFSIZ) .

Б1.2 Форматиран и3ле3

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

i nt fprintf (FILE *stream , const char *format , ... )

fprintf го конвертира и запишува излезат во полето stream под контрола

на полето format. Враќа број на запишани знаци , или негативен број во слу­
чај на грешка. Стрингот за форматирање содржи два типа на објекти: оби чни
објекти, кои се копираат во излезниот поток , и спецификации за конверзија ,
од кои секоја предизвикува конверзија и печатење на следниот последователен
аргумент во fprintf. Секоја спецификација за почнува со знакот % и завршува со
знак за конверзија. Помеѓу % и знакот за конверзија, во редослед, може да има:

- З н аме нца (во кој било редослед) , кои ја модифицираат спецификацијата :

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


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

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

о: за нумерички конверзии , сnецифицира пополнување на полето


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

#, која сnецифицира алтернативна форма на излезат. За о, првата


цифра ќе стане нула. За х или х, Ох или ох ќе биде nрефиксирана во ненулти
резултат . За е , Е , f , g, и G , излезат секо гаш ќе има децимална точка ; за g или
G, н улите што следат не се отстран у ваат .
Б .1 Влез и излез: <stdio.h> 295

- Број кој специфицира минимална широчина на поле . Конвертираниот ар­


гумент ќе биде испечатен во поле со широчина барем од наведената , и поши­
рока доколку за тоа има потрба. Ако конвертираниот аргумент има помал број
на занци отколку што е широчината на полето , ќе биде nоnолнет од лево (или
десно, доколку било побара но лево порамнување) до потребната широчина.
Знакот за поnолнување обично е бланко, но, доколку има присутно знаменце
за пополнување со нула , тогаш тој е о .

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

- Прецизноста претставува број, кој сnецифицира максимален број на зна­


ци за nечатење од еден стринг, или број на цифри кои треба да се испечатат
после децималната точка за е, Е, или f конверзиите, или бројот на з начајни
цифри за q или G конверзиите, или бројот на цифри кој ќе се nечати за еден цел
број (ќе бидат додадени водечки нули за доnолнување на потребната ши рочи­
на)

- Модификатор за должина h, 1 (буквата ел), или L . " h " укажува дека со­
одветниот аргумент треба да биде печатен како short или unsiqned short ;
" 1" укажува дека аргуме нтот е од тип lonq или unsiqned lonq . " L " ука жува
дека аргументот е 1onq doub1e .

Широчина или прецизност или и двете , може да се сnецифицираат како


*. Во таков случај вредноста се nресметува со конвертирање н а следниот (те )
аргумент (и), кој(и) мора да биде(ат) int .
Знаците за конверзија и нивните значења се nрикажани во Табела Б.l .
Слу чајот во кој знакот што следи nосле % не е знак за конверзија, е недефин и­
ран.

intprintf (const char *format , ... )


prin tf ( ... ) е еквивалентно со fprin tf ( ѕ tdou t , ... ) .

int sprintf (char *ѕ , const char *format, ... )

sprint е иста како и printf со таа разлика што излезот се заnишува


во стринг ѕ, кој завршува со '\о' . ѕ мора да биде доволно голем за
да може да го чува резултатот. Враќа број на знаци во ѕ , небр оејќи го
знакот '\0 ' .
296 Ста ндардна библи отека Додаток Б

Табеnа Б . 1 printf кон верзии

Знак Тип на аргумент ; Се конвертира во


d, i int ; означ е на декадна нотација .
о int ; означена октална нота ција (без водечка нула) .
х,Х unsigned int ; неоз начена хе ксадекадна нотација (без
воде чки Ох или ОХ) , се ко ристат abcdef за Ох или AВCDEF за
ох .

u int; неозначена декадна н отација .


е int ; еден зна к, после конверзија во unsigned char
ѕ char* ; з н аците од стрингот се п ечатат додека н е се дојде
до '\0' или додека не се испечатат толку з н а ци колку што е
кажа но преку преци зноста

f double ; декадна нота ција во форма [- ]mmm.ddd, каде бројот на


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

е, Е double ; декадна нотација во форма [-]m.dddddd е± хх или


[-]m.dddddd Е± хх, каде бројот на децимали d е наведен со
прецизноста. Стандардна прецизност е б , а прецизност о ја
елиминира децималната точка

g, G double ; се користи % е или % Е доколку експонентот е помал од


-4 или поголем или еднако в на прецизноста; во спротив но се
користи% f . Нули и децимал на точка н а крајот не се п е чатат.

Р void *; п ечати ка ко п о кажувач (р е п резе нта цијата зав иси од


и м пл.емента цијата )

n int * ; бројот на з н а ци тековно за пишани со овој п о вик н а


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

% н е се конвертира ниту еден аргумент: печати%

int vprintf (const char *format , va_list arg)


int vfprintf (FILE *stream , c onst char *format, va_list arg)
int vsprintf (char *ѕ, const char *format , va_list arg)

Фу н к циите vprintf , vfprintf и vsprintf , се е квив ал е нтни н а соодветни ­


те printf функции , осве н што п роменли вата листа на аргументи е за менета со
Б.l Влез и излез: <stdio.h > 297

полето arg 1 кое е иницијализирано со макрото va_ start и можеб и со пов ици
кон va_ arg. Погледнете ја дискусијата на <stdarg. h> Дел 57.

Б 1.3 Форматиран влез

Функцијата scanf се справува со форматирана конвер изј а на вл езот .

int fscanf (FILE *stream 1 const char *format 1 • • • )


fscanf чита од полето stream под контрола на полето format 1 и ги

доделува конвертираните вредности на соодветните аргументи 1 од кои


секој мора да биде покажувач. Функцијата завршув а со retur п во моментот
кога ќе се исцрпи содржината на format . fscanf в р аќа EOF за крај на да ­
тотека или при појава на грешка пред некоја конверз иј а ; во сп ротивн о
го враќа бројот на влезни елементи кои бил е конвертиран и и доделени .

Стрингот за форматирање вообича ено содржи специфи каци и за ко н вер­


зија 1 кои се користат за директна интерпретација на влезот . Може да содржи:

- Бланко или табулатори 1 кои се игнорираат.


- Обични знаци (не %) 1 од кои се очеку ва да се согласуваат со след н иот
знак различен од празно место од влезниот поток .

- Спецификаторите за конверзија се состојат од % 1 опци о нален з н а к * за п о­


тиснување на додел ување 1 опционален број кој ја с пецифицира максимал на та
широчина на полето 1 опционални h1 11 или L кои укажу ваат на широчин ата н а
целта и знак з а конверзија .

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


поле. Нормално 1 резултатот се сместува во променлива кон која покажува соод­
ветниот аргумент. Ако индицирано потиснување на доделувања со* 1 како во %*ѕ 1
тогаш влезното полеl едноставно, се прескокнува ; не се врши н икакво доделу вање .

Едно влезно поле се дефинира како стринг од непразни знаци ; се протега до следното
празно место или колку што е широчината на полето доколку е наведен а . Тоа импли­
цира дека scanf ќе чита преку границата на линијата за да го најде својот влез 1 бидејќи
знаците за нови линии се п разни места . (Знаци за праз но место се знакот за блан ко,
таб, знакот за нова линија, знакот за нов ред, верmкален таб и знак за нов лист. )
Знакот за конверзија укажува на интерпретацијата на влезното п ол е .
Соодветниот аргумент мора да биде покажувач. Легалните знаци за ко н верзиј а
се пр и кажани во табела Б. 2.
На знаците за конверзија d 1 i 1 n 1 о 1 u и х може да и м претход и h докол ­
ку аргументот е покажувач кон short а не int 1 или со 1 (буква ел) доколку
а ргументот е покажувач кон 1ong . На знаците за конверзиј а е 1 f 1 и g може да
им претходи 1 ако во аргументната листа има покажувач кон doub1e а н е кон

floa t 1 и со L доколку има покажувач кон 1ong doub1e.


298 Стандардна библиотека Додаток Б

Табела Б.2 scanfкон верзии

Знак Влезни nодатоци ; тип на а r мент


d декаден цел број; int*.
i цел број; int*. Целиот број може да биде во октална ( со
водечка О) или хексадекадна (со водечки ох ил и О Х )

о октален цел број (со или без в одечка О) ; int* .


u неозначен декаден цел број; unsiqned int* .
х хексадекаден цел број (со или без водечки х или ОХ) ; int* .
е знаци; char* . Наредните влезни знаци се сместуваат
во наведената низа , се до бројот наведен во полето за
широчина ; стандардна вредност е 1 . Не се додава '\ о' .
Нормалното nрескокнување на п разни места е елиминирано
во овој случај; за читање на наредниот знак кој не е празно
место, користете %ls .

ѕ стринг од знаци различни од nразно место (не во


наводници) ; char*, што покажува кон низа од з наци

доволно голема за да го чува стр и нгот и завршниот знак ' \о'


кој што ќе биде доделен.

e,f , g број со nодвижна точка ; float* . Влезниот формат за float


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

содржи Е или е проследено можеби со означен цел број .


р вредност на покажувач што би се испечатила со printf
( " %р " ) ; void*.
n го запишува во аргументот бројот на тековно прочитани
з наци со овој nовик ; int* . Н е се чита ника ков влез . Бројот
на конвертирани елементи не се инкрементира.

[ ... ] се поклопува со најдолгиот непразен стринг од влезни знаци


од множеството помеѓу заградите ; char* . Се додава ' \о ' .
Со [] ... ] во множеството се вклучува ].
се поклопува со најдолгиот непразен стринг од влезни знаци
кој не npunaza на множеството помеѓу заградите; char* . Се
додава ' \о ' . Со [ ... ] ... ] во множеството се вклуч у ва ].
% знак %. Не се nрави никакво доделување .

int scanf (const char *format, ... )


scanf ( .. . ) е иденти ч но со fscanf (stdin, . . . ) .
Б.1 Влез и излез: <stdio.h> 299

int sseanf (eonst ehar *ѕ , eonst ehar *format , ... )


sseanf (ѕ, . . . ) е еквивалентна со seanf ( .. . ) со таа разлика што

влезните знаци се земаат од стрингот ѕ.

Б 1.4 Функции за влез и излез на знаци

int fgete (FILE *stream)


fgete го враќа следниот знак од полето stream како unsigned
ehar (конвертиран во int) , или EOF во случај на крај на датотека или
во случај на грешка.

ehar *fgets {ehar *ѕ, int n , FILE *stream)


fgets чита најмногу n-1 знаци во низата ѕ , стопирајќи во случај на
знак за нова линија; знакот за нова линија се вклучува во низата чиј што
крај се означува со '\О ' . fgets враќа ѕ , или NULL во случај на EOF во
случај на грешка.

int fpute (int е , FILE *stream)


fpute запишува во знак е (конвертиран во unsigend ehar) на

stream. Го враќа запишаниот знак , или EOF во случај на грешка.

int fputs {eonst ehar *ѕ , FILE *stream)


fputs запишува во стринг ѕ (кој не смее да содржи \n) од полето
stream; враќа ненегативна вредност или EOF во случај на грешка.

int gete (FILE *stream)


gete е еквивалентна со fgete , со таа разлика што во случај да е
макро , може да го евалуира stream повеќе од еднаш .

int getehar {void)


getehar е еквивалентна со gete (stdin) ,

ehar *gets (ehar *ѕ)


gets ја вчитува наредната влезна линија во низата ѕ ; го за ме нува
знакот за нова линија со '\0' . Го враќа ѕ или NULL за крај на датотека
или во случај на грешка.

int pute (int е , FILE *stream)


pute е еквивалентна со fpute, со таа разлика што во случај да е
макро, може да го евалуира stream повеќе од еднаш .

int putehar (int е)


putehar (е) е еквивалентна со pute (е , stdout) .
300 Стандардна библиотека Додаток Б

int puts (eonst ehar *ѕ)


puts ги запишува стрингот ѕ и знак за нова линија во stdout. Вра ќа
EOF во случај на грешка, во спротивно враќа ненегативна вредност .

int ungete (int е 1 FILE *stream)


ungetc го праќа е (конвертиран во unsigned ehar) назад во поле­
то stream, од каде ќе биде земен при следното читање. Се гарантира
само еден повратен знак за еден поток . EOF не може да се прати на ­

зад. ungetc го враќа знакот кој бил пратен назад или EOF во случај на
грешка.

Б 1.5 Функции 3а директен вле3 и И3Ле3

size_t fread(void *ptr , size_t size , size_t nobj 1 FILE *stream)


fread чита најмн огу nobj објекти со големина size од полето
stream во низата ptr. fread го враќа бројот на прочитани објекти ;
тој број може да биде помал отколку бараниот број. За да се о предели
статусот мора да се користат feof и ferror .

size_tfwrite(constvoid*ptr , size_tsize ,size_tnobj , FILE*stream)


fwri te запишува nobj објекти со големина size од низата ptr во
полето stream. Враќа број на запишани објекти кој во случај на грешка
е помал од nobj .

Б 1.6 Функции 3а nо3иционирање во датотека

int fseek (FILE *stream 1 long offset 1 int origin)


fseek ја поставува позицијата на датотеката за потокот stream ;
наредното читање или запишува ње ќе пристапи до податоците почну­
вајќи од новата позиција. За бинарна датотека , позицијата се поста ­
вува на offset знаци од origin , кој може д а биде ЅЕЕК_ЅЕТ (nоче­
ток) , SEEK_CUR (тековна пози ција) или SEEK_END (крај на датотека) .
За текстуален поток , offset мора да биде о , или вредност вратена
од ftell (ВО кој случај origin мора да биде ЅЕЕК_ЅЕТ) . fseek враќа
ненулта вредност во случај на грешка .

long ftell (FILE *stream)


ftell ја враќа тековната позиција на датотеката за stream или -1
за грешка.

void rewind {FILE *stream)


rewind(fp) е еквивалентно на fseek (fp,Ol, ЅЕЕК_ЅЕТ );
Б. l Влез и излез: <stdio.h> 301

clearer(fp) .

int fgetpos (FILE *stream, fpos_ t *ptr)


fgetposs ја запишува тековната позиција од stream во *ptr , за
последователно користење од fsetpos. Типот fpos _ t е соодветен за
запишување на такви вредности . fgetpos при грешка вра ќа ненулта
вредност .

int fsetpos (FILE *stream, const fpos_t *ptr)


fsetposs го позиционира потокот stream на позицијата зачувана
од fgetpos во *ptr. fsetpos во случај на грешка враќа ненулта вред­
ност.

61.7 Функции 3а rpewкa

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


случај на појава на грешка или крај на датотека . Тие индикатори можат екс­
плицитно да се постават и да се тестираат . Уште повеќе , целобројниот израз
errno (деклариран во <errno . h>) може да содржи број за грешка кој дава по­
натамошна информација за последната гре шка која се случила .

void clearerr (FILE *stream)


clearerr ги ресетира индикаторите за крај на датотека и грешка за
потокот stream.

int feof (FILE *stream)


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

int ferror (FILE *stream)


ferror враќа ненулта вредност ако е поставен индикаторот за
грешка за потокот stream.

voidperror (const char *ѕ)


perror (ѕ) го печати ѕ заедно со порака за грешка дефинирана со
имплементацијата која одговара на целиот број во errno , како во
fprintf (stderr, " %ѕ: %s\n" , ѕ , " порака за грешка" );

Види strerror во Параграф БЗ.


302 Стандардна библиотека Додаток Б

Б2. Проверка на класата на знак: <ctype.h>

Заглавјето <etype. h> декларира функции за тестирање знаци. За секоја


функција листата на аргументи е претставена со еден int чија вредност мора
да биде EOF или претставена како unsigned ehar , а вредноста која функцијата
ја враќа е int. Функциите враќаат ненулта вредност (вистина) доколку аргу­
ментот е го задоволува бараниот услов, и о ако не го задоволува.

isalnum(e) isalpha(e) или isdigit(e) евистина


isalpha(e) isupper (е) или islower (е) е вистина

isentrl(e) контролен знак

isdigit(e) декадна цифра


isgraph(e) знак кој може да се печати со исклучок на бланко
islower(e) мала буква
isprint(e) знак кој може да се печати вклучувајќи и бланко
ispunet(e) знак кој може да се печати со исклучок на бланко , цифра
или буква
isspaee (е) бланко, знак за нов лист, знак за нов ред, знак за нова
линија , таб , вертикален таб
isupper (е) голема буква
isxdigit (е) хексадекадна цифра

Во седумбитното знаковно множество ASCII , знаци кои може да се печатат


се 0х20 ( ' ') до Ох7Е ( '-') ; контролни знаци се о NUL до OxlF (US) , и Ox7F
(DEL).
Понатаму, постојат две функции кои ја менуваат големината на буквите:

int tolower (е) го претвора е во мала буква


int toupper (е) го претвора е во голема буква

Доколку е е голема буква, tolower (е) ја враќа соодветната мала буква ,


toupper (е) ја враќа соодветната голема буква; во спротивно го вра ќа е .

Б3. Функции за стрингови: <string.h>

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


<string. h>. Првата група има имиња кои започнуваат со str; втората група
има имиња кои започнуваат со mem . Сите случаи, со исклучок на mermnove , во
кои се одвива копирање помеѓу објекти кои се преклопуваат , се недефинирани.
Функциите за споредба ги третираат аргументите како низ и од unsigned ehar.
Во следната табела променливите ѕ и t се од тип ehar* ; еѕ и et се од тип
eonst ehar* ; n е од тип size_ t; а е е int претворен во ehar.
Б.З Функц и и за стрингови : < stri пg . h > 303

ehar*strcpy (ѕ, et) го копира стрингот et во стринг ѕ, вклучително


со ' \0'; враќа ѕ
ehar*strncpy (ѕ . et, n) копира најмногу n знаци од стрингот et во ѕ ;
враќа ѕ. Го пополнува '\0 ' доколку et има
помалку од n знаци

ehar*streat (ѕ , et) надоврзува стринг et на крајот на стрингот ѕ ;


враќа ѕ
ehar*strneat (ѕ , et, n) надоврзува најмногу n знаци од стрингот et на
стрингот ѕ , го означува крајот на ѕ со '\0 ';
враќа ѕ
intstrcmp(es, et) споредува стринг еѕ со стринг et , враќа <О ако
es<et, О ако es=et или >О, ако es>et
intstrncmp(es, et , n) споредува нај многу n знаци од стрингот еѕ со
стрингот et ; вра ќа <О ако es<et , о ако es==et
или >О, ако es>et
ehar*strehr(es , е) враќа покажувач кон првата појава на е во еѕ ,
или NULL доколку нема појава
ehar*strrehr(es , е) враќа покажувач кон последната појава на е во
еѕ , или NULL доколку нема појава
size_t strspn (еѕ , et) враќа должина на префикс во еѕ кој се состои од
знаци во et
size t strespn(es , враќа должина на префикс во еѕ кој се состои од
et) знаци кои не се во et
ehar*strpbrk(es, et) враќа покажувач кон првата појава на кој било
знаковен стринг et во стринг еѕ или NULL
доколку нема таква појава
ehar*strstr(es,et) враќа покажувач кон првата појава на стрингот
et во еѕ или NULL доколку нема таква појава

size_t strlen (еѕ) враќа должина на еѕ


ehar*strerror(n) враќа покажувач кон стринг дефиниран со
имплементацијата кој одговара на грешка n
ehar*strtok(s , et) го пребарува ѕ барајќи белези одделени со знаци
кои припаѓаат на et ; види подолу

Секвенца од повикувања на strtok (ѕ , et) го дели ѕ на белези , секој одде­


лен со знак од et . Првиот повик во една секвенца има нe-NULL ѕ , го пронаоѓа
првиот белег во ѕ, кој се состои од знаци кои не се во et; крајот на тоа го оз­
начува со бришење на следниот знак од ѕ со '\о ' и враќа покажувач кон беле­
гот . Секој последователен повик, индициран со NULL вредност на ѕ го враќа
следниот таков белег , пребарувајќи непосредно после крајот на претходни­
от . strtok враќа NULL кога понатаму не може да се најде друг таков белег.
Стрингот et може да биде различен за секој повик.
mem ... функциите се предвидени за манипулација на објектите како знаков-
304 Стандардна библиотека Додаток Б

ни низи; целта е да се направи интерфејс кон ефикасни рутини . Во следната


табела ѕ и tсеодтип void* ; еѕ и etce од тип eonstvoid*; ne од тип size_t;
а е е int претворен во unsigned ehar.

void* memcpy (ѕ , et, n) копира n знаци од et во ѕ и го враќа ѕ


void*menпnove(s, et, n) иста како memepy , освен што функционира
дури и ако објектите се преклопуваат
intmemcmp(es, et, n) ги споредува првите n знаци на еѕ со et ;
враќа исто како strcmp
void* memehr (еѕ, е, n) враќа покажувач кон првата појава на знак е
во еѕ или NULL доколку нема таква појава во
првите n зн аци .
void* memset (ѕ, е, n) сместува знак е во првите n знаци од ѕ,

враќа ѕ

64. Математички функции:<mаth.h>

Заглавјето <math. h> декларира математички функции и макроа .


Макроата EDOM и ERANGE (кои се наоѓаат во <errno .h>) се ненулти инте­
грални константи кои се користат да сигнализираат грешки во домен и опсег

за функциите ; HUGE_ VAL е позитивна double вредност. Грешка во домен се


случува ко га еден аргумент е надвор од доменот во кој што е дефинирана функ­
цијата . При грешка во домен , errno се поставува на вредност ЕDОМ ; врате­
ната вредност е дефинирана со имплементацијата . Грешка во опсег се случува
кога резултатот на функцијата не може да се претстави како double . Доколку
има nречекорување во резултатот , функцијата враќа НUGE_VAL со соодветни­
от знак, а errno се поставува на ERANGE . Доколку има пречекорување на до­
лната граница во резултатот, функцијата враќа О; дали errno ќе се постави на
ERANGE е дефинирано со имплементацијата.
Во следната табела х и у се од тип double, n е int , а сите функции враќаат
double . Аглите за тригонометриските функции се изразен и во радијани.

sin(x) синус одх

еоѕ(х) КОСИНУС ОД Х

t&n(x) тангенс од х

asin(x) sin-L(х) во опсег [ -pi/2 , pi/2 Ј , х припаѓа на


[-1,1].
аеоѕ (х) еоѕ- 1 (х) во опсег [О ,pi ]
, х припа ѓа на [-1, 1] .
atan (х) tan-L(х) во опсег [ -pi/2 , pi/2] .
atan2(y,x) t&n-1 (y/x) воопсег [-pi ,pi].
sinh(x) синус хи перболи к од х
Б.Ѕ Корисни функции: <stdlib.h> 305

cosh (х) косинус хиперболик од х


tanh(x) тангенс хиперболик од х
ехр(х) експоненцијална функција е-'
log(x) природен логаритам ln (х) 1 х>О.
loglO(x) декаден логаритам log10 (х) 1 х>О.
pow(x 1 y) xr. Грешка во домен се случува кога х=О и у<=О 1 или
ако х<О и у не е цел број.
sqrt (х) квадратен корен од х 1 х>=О.
ceil (х) најмалиот цел број не помал од х 1 како double.
floor (х) најмалиот цел број не помал од х 1 како double .
fabs(x) апсолутна вредност од 1х 1
ldexp(x 1 n) х*2 "

f r е х р ( х 1 го дели х во нормализиран остаток во интервалот


int*exp) [ 1/2 1 1) кој се враќа 1 и степен од 2 кој се сместува во
*ехр. Ако х е о 1 двата дела од резултатот се нули.
m о d f х 1 го дели х на целоброен дел и остаток 1 секој со истиот
double*ip) знак како х. Го сместува интегралниот дел во *ip 1 а го
враќа остатокот.
fmod(x~y) реален остаток од х!у 1 со ист знак како х . Ако у е о 1
резултатот е дефиниран со имплементацијата .

65. Корисни функции : <stdlib.h>

Заглавјето <stdlib. h> декларира функции за конверзија на броеви 1 мемо­


риска алокација и слични задачи.

double atof (const char *ѕ)


atof го претвора ѕ во double; еквивалентна е со strtod (ѕ 1

char**)NULL) .

int atoi (const char *ѕ)

го претвора ѕ воint ; еквивалентна е со (int) strtol (ѕ 1


(char**)NULL 1 10).

long atol (const char *ѕ)


го претвора ѕ во long; еквивалентна е со strtol (ѕ 1 (char**)
NULL 1 10).

double strtod (const char *ѕ 1 char **endp)


strtod го претвора префиксот на ѕ во double 1 притоаl игнори­
рајќи ги водечките празни места ; сместува покажувач кон кој било
неконвертиран суфикс во *endp освен кога endp е NULL. Во случај на
306 Стандардна библиотека Додаток Б

пречекорување на одговорот, се враќа НUGE_VAL со соодветен знак;


во случај на пречекорување на долна граница од одговорот се враќа о.
Во двата случаја errno се поставува на ERANGE.

long strtol (const char *ѕ, char **endp, int base)


strtol го претвора префиксот од ѕ во long, игнорирајќи ги водеч­
ките п разни места; сместува покажувач кон кој било неконвертиран
суфикс во *np, освен ако np е о. Ако base е помеѓу 2 и 36 1 конвер­
зијата се прави под претпоставка дека влезот е запишан со таа основа.
Ако base е о 1 основата е 8 1о
1 или 16; водечка о имплицира октална
нотација 1 а водечки о х или о х хексадекадна нотација. Буквите и во
двата случаја репрезентираат цифри од 10 до base-1; водечки О х или
о х се дозволени за основа 16. Доколку се случи пречекорување во од­
говорот 1 се враќа LONG_ МАХ или LONG_ MIN, во зависност од знакот на
резултатот 1 а errno се поставува на ERANGE.

unsigned long strtoul (const char *ѕ 1 char **endp 1 int base)


strtoul е иста како strtol со таа разлика што резултатот е

unsigned long а вредноста за грешка е ULONG_ МАХ.

int rand (void)


rand враќа псевдослучаен цел број во опсег од о до RAND_ МАХ 1 чија
вредност е барем 32767.

void srand (unsigned int seed)


srand го користи seed како основа за нова секвенца на псевдослу­
чајни броеви. Почетната основа е 1 .

void *calloc (size_t nobj 1 size_t size)


calloc враќа покажувач кон простор за низа од nobj објекти, секој
со големина size или NULL во случај барањето да не може да се задо ­
воли . Просторот се иницијализира на нула бајти.

void*malloc(size_tsize)
malloc враќа покажувач кон простор за еден објект со големина
size 1 или NULL доколку барањето не може да се задоволи. Просторот
не е иницијализиран .

void *realloc (void *р 1 size_ t size)


realloc ја менува големината на објектот кон кој покажува р во size .
Содржината ќе оаане непроменета се до минимумот од аарата и новата
големина . Ако новата големина е поголема 1 новиот проаор не е иниција­
лизиран. realloc враќа покажувач кон новиот простор или NULL доколку
барањето не може да се задоволи 1 во кој случај *р оаанува непроменето .
Б.Ѕ Корисни функции : <stdlib.h> 307

void free (void *р)


free го ослободува просторот кон кој покажува р; не прави ништо
доколку р е NULL. Р мора да биде покажувач кон простор претход но
алоциран со calloc, malloc, или realloc.

void abort (void)


abort предизвикува абнормален крај на програмата, исто како
raise(SIGAВRT).

void exi t ( int sta tus)


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

враќа на работната околина. Како се враќа status кон околината зави­


си од имплементацијата, но нула се зема за успешен крај. Вредностите
EXIT_SUCCESS И EXIT_FAILURE, ИСТО така , може да се користат.

int atexit(void (*fcn) (void))


atexit регистрира повикување на функцијата fcn при нормален
крај на програмата ; враќа ненулта вредност ако регистрацијата н е
може да се изведе.

int system (const char *ѕ)


system го предава стрингот ѕ на околината за извршување. Ако ѕ
е NULL system враќа ненулта вредност доколку посто и команден про­
цесор. Доколку ѕ не е NULL, вратената вредност за виси од имплемен­
тацијата.

char *getenv (const char *name)


getenv враќа стринг од околината асоциран со name или NULL а ко
не постои стрингот. Деталите се зависни од имплементацијата .

void*bsearch(constvoid*key,constvoid*base,size_tn , size_tsize,
int (*cmp) (constvoid *keyval, constvoid *datum))
bsearch пребарува низ base [О] ... base [n-1] за елемент кој одго­
вара на *key. Функцијата cmp мора да врати негативен резултат ако
нејзиниот прв аргумент (клучот на барањето) е помал отколку нејзи­
ниот втор аргумент (елемент на табелата) , нула ако се еднакви и по­
зитивна вредност ако е поголем. Елементите на низата base мора да
бидат во растечки редослед. bsearch враќа покажувач кон пронајде­
ниот елемент или NULL доколку истиот не постои .
308 Стандардна библиотека Додаток Б

voidqsort(void*base, size_tn, size_tsize, int (*cmp)


( const void *, const void *) )
qsort сортира во растечки редослед низа од објекти со големина
size: base [О] ... base [n-1] . Функцијата за споредба cmp е иста како и
за bsearch .

int abs (int n)


abs враќа апсолутна вредност на нејзиниот int аргумент.

long labs (long n)


labs враќа апсолутна вредност на нејзиниот long аргумент .

div_tdiv(intnum, intdenom)
div пресметува количник и остаток од num/denom. Резултатот се
сместува во int членовите quot и rem во структура од тип div_t.

ldiv _ t ldi v ( long num, long denom)


ldiv пресметува количник и остаток од num/denom. Резултатот се
сместува во l?ng членовите quot и rem во структура од тип ldiv_ t.

Бб. Дијаrностика: <assert.h>

Макрото assert се користи за додавање на дијагностика на програмите.

void assert (int израз)

Ако израз има вредност о кога се извршува

assert (израз) ,

макрото assert ќе испечати порака на stderr како, на пример ,

Assertion failed: израз, file filename, line nnn

Потоа повикува abort за прекин на извршувањето. Името на изворна­


та датотека и бројот на линијата се земаат од претпроцесорските ма кроа _
FILE И LINE
Ако е дефинирано макрото NDEBUG за време на вклучување на <assert. h> ,
макрото assert се игнорира.
Б.8 Нелокални скокови: <setjump.h> 309

Б7. Променливи листи на арrументи: <stdarg.h>

Заглавјето <stdarg. h> обезбедува можност за поминување низ листата од


аргументи на една функција чиј број и тип се непознати.
Нека lastarg е последниот именуван параметар на една функција f што
има променлив број на аргументи. Тогаш во рамките на f се декларира про­
менлива од тип va_list која кон секој аргумент ќе покажува посебно:

va_listap;

ар мора да биде иницијализирана еднаш со макрото va_start пред да се


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

va_start (va_list ар , lastarg);

Понатаму, секое извршување на макрото va_arg ќе произведе вредност


која го има типот и вредноста на наредниот неимен уван аргумент, и ќе ја мо­
дификува ар па следната употреба на va_ arg ќе го врати следниот аргумент:

typeva_arq (va_list ар, type);

Макрото

void va_end (va_list ар) ;

мора да се повика еднаш откако ќе се процесираат аргументите, но пред на­


пуштање на f.

Б8. Нелокални скокови: <setjump.h>

Декларациите во <serjump. h> обезбедуваат начин да се избегнат нормал­


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

int setjmp (jmp_buf env)


Макрото set_jump зачувува информација за состојбата во env за
употреба од страна на longmp. Вратената вредност за директен повик
кон setjmp изнесува нула , а е различна од нула за nоследователно по­
викување на longjmp. Повик кон setjmp може да се изведе само во
одредени контексти, во обичен случај тоа се тестовите на if, swi tch ,
циклусите, и во едноставните релациски изрази.
310 Стандардна библиотека Додаток Б

i f (setjmp(env) == О)
/* дојди туха nри дирехтен nових*/
else
/* дојди туха со nовихуваље на longjmp */

void longjmp ( jmp_ buf env , in t val


longjmp ја реставрира состојбата која е зачувана од најскорешниот
повик кон setjmp , со користење на информацијата зачувана во env,
а извршувањето продолжува како функцијата setjmp да била непо­
средно извршена и вратила ненулта вредност val. Функцијата која
содржи повик до setjmp не смее да биде терминира на. Пристапните
објектит имаат вредности кои ги имале во моментот на пови кување на
longjmp , со таа разлика што автоматските нe-volatile променливи

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


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

Б9. Сиrнаnи: <signal.h>

Заглавјето <signal. h> обезбедува можност за справување со исклучителни


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

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

void (*signal (int sig, void (*handler) (int))) (int)


signal го одредува справувањето со последователните сигнали. Ако
handler има вредност SIG_DFL, се користи стандардното однесување дефи­
нирано со имплементацијата , а ако истиот има вредност SIG_ IGN, сигналот се
игнорира ; во спротивно, се повикува функцијата кон која по кажува handler,
со аргумент за ти пот на сигнал. Валидни вредности за сигнали се:

SIGAВRT абнормален крај , на пример, од abort


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

пречекорување на опсег

SIGILL илегална функциска слика , на пример, илегална инструкција


SIGINT интерактивно внимание, на пр имер, interrupt
SIGSEGV и легален пристап до меморија, на пример, пристап вон
опсегот н а меморијата
SIGTERМ барање за прекин на извршување, испратен о до оваа програма

signal ја враќа претходната вредност на handler за конкретниот сигнал ,


или SIG_ERR во сл учај на nојава на грешка .

Кога потоа ќе се појави сигнал sig , сигналот се реставрира на неговото


стандардно однесување; потоа се п овикува функција за справување со сигна-
Б.10 Функци и за датум и време: <time.h> 311

ли 1 како што е (*handler) (sig) . Доколку handler врати вредност 1 извршу­


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

Иницијалната состојба на сигналите е дефинирана со имплементацијата

int raise (int sig)

Функцијата raise праќа сигнал sig на програмата ; во случај на неуспех


враќа ненулта вредност .

Б 1О. Функции за датум и време: <time.h>


Заглавјето <time . h> декларира типови и функции за манипулац ија со да ­
туми и време. Некои функции го процесираат локалното време 1 кое може да
се разликува од календарското 1 на пример, поради часовна зона. clock_ t и
time_ t се аритметички типови за претстав а на времиња 1 а структурата struct
tm ги чува компонентите на календарското време:

int tm_sec; секунди после минутата (0 1 61)


int tm_min ; минути после часот (О 1 59)
int tm_hour ; часови после полноќ (0 1 23)
int tm_mday ; ден во м есецот (1 , 31)
inttm_mon ; месеци после јануари (0 1 11)
int tm_year; години после 1900
int tm_wday ; денови после н едела (О, б)
int tm_yday ; денови п осле јануар и 1 (о 1 365)
int tm_isdst ; знаменце за сезонското поместу вање на времето

tm_isdst е поз итивен ако сме во сезона со поместено време 1 нула до колку

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

clock_tclock(void)
clock го враќа потрошеното процесорско времето од страна н а
програмата од почетокот на нејзиното извршување 1 или -1 ако инфор­
мацијата не е достапна . clock () /CLК_PER_SEK е време во секунди .

time_t time (time_t *tp)


time го враќа тековното календарско време или -1 ако неговата
вредност не е доста п на . Доколку tp е различен од NULL 1 повратната
вредност се доделува на *tp .
double difftime ( time_ t time2 1 time_ t time1)
difftime ја вра ќа разликата time2-time1 изразен а во секу нди.
312 Стандардна библиотека Додаток Б

time_tmktime (struct tm *tp)


mktime го претвора локалното време од структурата *tp во ка­

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


Компонентите ќе имаат вредности во прикажаните опсези. mktime го
враќа календарското време или -1 ако истото не може да се претстави.

Следните четири функции враќаат покажувачи кон статички објекти кои


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

char *asctime (const struct tm *tp)


asctime</tt< го претвора времето од структурата *tp во стринг со форма
Sun Jan 3 15 : 14:13 1988\n\0

char *ctime (const time_t *tp)


ctime го претвора календарското време *tp во локално време ; еквива­

лентна е со

asctime(localtime(tp))

struct tm *gmtime (const time_t *tp)


gmtime го претвора календарското време *tp во ., координирано уни­
верзално време" (анг. Coordinated Universal Тime - UТС). Враќа NULL докол­
ку uтс не е доста п но. Името gmtime има историско значење .

struct tm *localtime (const time_t *tp)


local time го претвора календарското време *tp во локално време.

size_t strftime (char *ѕ, size_t Slll4x , const char *fmt, const struct
tm *tp)
strftime ја форматира информацијата за датумот и времето содр­
жана во *tp, во стрингот ѕ во согласност со полето fmt, аналогно на

форматот на функцијата printf. Обичните знаци (вклучувајќи ја и за­


вршната ' \О') се копираат во ѕ. Секој % е се за менува како што е
опишано подолу, со користење на знаци соодветни за локалната око­

лина . Не повеќе од smax знаци се сместуваат во ѕ. strftime вра ќа


број на знаци неброејќи ја '\о' , или нула доколку биле продуцирани
повеќе од ѕ1114х знаци.

%а скратено име за ден од седмицата

%А полна име за ден од седмицата

%b скратено име на месец

%В полно име на месец


Б.ll Граници дефинирани со имплементацијата: <limits.h> и <floath> 31 3

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

%d ден од месецот (01-31)


%Н час (24-часовен формат) (00-23)
%! час (12-часовен формат) (00-11)
%ј ден од годината (001-366) .
%m месец (01-12) .
%М минута (00-59) .

%р локален еквивалент за АМ или РМ.

%Ѕ секунда (00-61).
%U број на седмица во годината (недела е 1-от ден од седмицата) (00-
53) .
%w ден од седмицата (о- б 1 недела е о) .
%W број на седмица од годината (понеделник е 1-от ден од седмицата)
(00-53).
%х локална претстава на датумот.

%Х локална претстава на времето.

%у година без столетие (00-99) .


%У година со столетие.

%Z име на временска зона 1 доколку постои.

%% %.

Б11. Граници дефинирани со импnементацијата: <limits.h> и <float.h>

Заглавјето <limi ts. h> дефинира константи за големините на сите инте ­


грални типови . Вредностите подолу се минималните прифатливи распони ;
може да се користат и поголеми вредности

СНАR BIT 8 битови во еден char


СНАR МАХ UCНAR МАХ ИЛИ ЅСНАR максимална вредност за

МАХ char
СНАR MIN О или ЅСНАR MIN минимална вредност за

char
INТ МАХ 32767 максимална вредност за

int
INТ MIN -32767 минимална вредност за
int
LONG МАХ 2147483647 максимална вредност за

long
LONG MIN -2147483647 минимална вредно ст за

long
314 Стандардна библиотека Додаток Б

ЅСНАR МАХ +127 максимална вредност за

signedchar
ЅСНАR MIN -127 минимална вредност за

signedchar
ЅНRТ МАХ +32767 максимална вредност за

short
SHRT MIN -32767 минимална вредност за

short
UCНAR МАХ 255 максимална вредност за

unsigned char
UINT МАХ 65535 максимална вредност за

unsigned int
ULONG МАХ 4294967295 максимална вредност за

unsigned 1ong
USHRT МАХ 65535 максимална вредност за

unsigned short

Имињата во табелата подолу, подмножество од <float. h>, се константи по­


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

FLT RADIX 2 основа на експонент , репрезентација


на пример, 2 , 16
FLT ROUNDS мод на заокружување на реални

броевиприсобирање
FLT DIG б декадни цифри на прецизност
FLT EPSILON 1Е-5 најмалиот број х, за кој 1. о +х ~ 1 . о
FLT МАNТ DIG број на цифри на основата FLT_ RADIX
во мантиса

FLT МАХ 1Е+37 најголемиот реален број


FLT МАХ ЕХР најмалиот n , таков за кој може да се
претстави FLT_RADIX"-1
FLT MIN 1Е-37 најмалиот нормализиран реален број
FLT MIN ЕХР најмалиот n за кој 10n е нормализиран
број
DBL DIG 10 декадни цифри на прецизност
DBL EPSILON 1Е-9 најмалиот број х, за кој 1. о +х ! =1. о
DBL МАNТ DIG број на цифри на основата FLT_RADIX
во мантиса

DBL МАХ 1Е+37 најголемиот реален број со двојна


прецизност (double)
Б.11 Граници дефинирани со иммемектацијата: <limits.h> и <floath> 315

DBL МАХ ЕХР најголемиот n, за кој може да се


претстави FLT_ RADrxn- 1
DBL MIN lE-37 најмалиот нормализиран реален број
со двојна прецизност (double)
DBL MIN ЕХР број на основата FLT RADIX во
мантиса
Додаток 8: Преrnед на промените

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


е доживеа промени. Речиси сите промени беа во смисла на продолжение на
оригиналниот јазик и внимателно беа дизајнирани за да останат компатибил­
ни со постојната практика; некои од нив претставуваат поправки на двосмис­
леностите во оригиналниот опис , а некои се модификации кои ја менуваат
постојната практика. Многу од новините беа најавени уште во документите кои
ги следеа компајлерите достапни од АТ&Т , и последователно беа усвоени од
другите производители на е компајлери . Пред извесно време, ANSI комитетот
задолжен за стандардиз ирање на јазикот ги вклучи повеќето од промените , а,
исто така, воведе други значајни модификации. Извештајот делумно беше под­
држан од некои комерцијални компајлери , уште пред објавувањето на фор­
малниот е стандард.
Овој додаток ги сумира разликите помеѓу јазикот дефиниран во првото из­
дание на книгава и она што се очекува да биде дефинирано од финалниот стан­
дард . Единствено го обработува јазикот, не неговата околина и библиотека ;
иако истите заземаат значаен дел од стандардот, има многу малку што да се

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


отека.

- За разлика од првото издание, во стандардот претпроцесирањето е де­


финирано повнимателно , и е проширено ; експлицитно се базира на белези ;
има нови оператори за надоврзување на белези (#ft), и за креирање на стрин­
гови (ft) ; постојат нови контролни линии, како ftelif и #pragma ; повторното
декларирање на макроата од иста секвенца на белези е експлицитно дозволе­
но ; параметрите внатре во стринговите повеќе не се заменуваат. Делењето на
линиите со " \ " е дозволено насекаде, не само за стринговите или дефинициите
на макроата . Види Параграф. А. 12 .

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


зголемен на Зl; најмалиот број на значајни знаци за идентификатори со надво­
решно поврзување , остана б знаци со иста големина. (Голем број имплемен­
тации поддржуваат повеќе . )

3 17
318 Преглед на промените Додато к В

- Триграф секвенците воведени од?? овозможуваат репрезентација на зна­


ците кои недостасуваат во некои знаковни множества. Дефинирани се излезни
секвенци за # \ [] { } 1 -, види Параграф А12 . 1 . Забележете дека воведување
на триграфи може да го промени значењето на стринговите кои ја содржат
секвенцата ? ? .

- Воведени се нови клучни зборови (void, const, volatile, signed ,


enum) . Клучниот збор entry е повлечен.

- Дефинирани се нови излезни секвенци, за употреба во знаковните кон­


станти и стринговите константи. Случаите од проследување на\ со знак кој не
е дел од дозволена секвенца се недефинирани. Види Параграф А2. ѕ.

-За сите омилената очигледна промена: 8 и 9 не се октални цифри.

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


разјаснување на типовите на константите: u или L за цели броеви , F или L за
реални. Исто така, ги рафинира правилата за типовите на константи кои не се
надополнети со суфикс.

- Соседни стрингови се надоврзуваат.

-Има нотација за широките стрингови и знаковни константи; види Параграф


А2.6.

- Знаците, како и останатите типови, може експлицитно да бидат декла­


рирани дека поседуваат или не поседуваат знак со користење на зборовите
signed и unsigned. Композицијата long float како синоним за double е по­
влечена, но може да се користи long double како декларатор за реален број
со екстра прецизност.

- Некое време беше достапен типот unsigned char. Стандардот воведува


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

- Типот void во повеќето имплементации беше достапен со години .


Стандардот воведува употреба на типот void* како тип за генерички покажу­
вач; претходно типот char ја имаше таа намена; во исто време, направени се
експлицитни правила против мешање на покажувачи и цели броеви , и покажу­
вачи од различен тип, без употреба на претопувања.

- СтандаRt!от воведува експлицитни минимуми за границите на аритметич­


ките типови, во заглавја како <limi ts. h>и <float. h> давајќи карактеристики
на сите посебни имплементации.
Преглед на промените Додаток В 319

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

- Стандардот од С++ ја усвојува нотацијата на квалификатор на тип, на при­


мер, const (Параграф А8. 2) .

- Стринговите сега се непроменливи, па можат да се сместуваат во мемо­


ријата резервирана само за читање.

- "Вообичаените аритметички конверзии" се сменети, во принцип, намес­


то "за целите броеви, unsigned секогаш победува; за реалните, секога ш
користи double" се преферира "унапреди до најмалиот тип кој има доволно
капацитет". Види Параграф Аб. ѕ.

- Старите оператори за доделување како =+ целосно се отстранети . Исто


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

- Повлечено е правилото кое му дозволуваше на компајлерот да ги третира


математички асоцијативните оператори како пресметковно асоцијативни.

- Унарен +е воведен заради симетрија со унарниот-.

- Покажувач кон функција може да се користи како функциски означ у вач


без експлицитна употреба на операторот *.

- Структурите може да се доделуваат, да бидат аргументи и повратни вред­


ности на функција.

- Примената на адресниот оператор врз низите е дозволена, а резултатот е


покажувач кон низата.

- Операторот sizeof, во првото издание враќаше int вредност; оттогаш ,


повеќето имплементации го направија unsigned . Стандардот го направи не­
говиот тип експлицитно зависен од имплементацијата, но бара типот size_ t ,
да биде дефиниран во стандардното заглавје <stddef. h>. Слична промена
има и во типот кој означува разлика помеѓу покажувачи (ptrdiff_t) . Види
Параграф А7. 4. 8 и Параграф А7 . 7 .

- АДресниот оператор & не може да се примени врз објект деклариран со


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

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


операнд не влијае на резултатот. Види Параграф А 7 . 8 .
320 Преглед на промените Додаток В

- Стандардот го легализира креирањето на покажувач веднаш зад посл ед­


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

Параграф.А.7.7.

- Стандардот воведува (позајмувај ќи од (++) декларација на фун кциски


прототип која ги вклучува типовите на параметрите , и вклучува експлицитно
препознавање на варирачките функции како и соодветен начин на справување
сонив. ВидиПараграф А7.3.2 , Параграф 86.3 , 57 . Сеуштеево употреба
стариот стил l но со ограничувања.

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


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

- Надворешните декларации без каков било спецификатор или квалифика­


тор (само гол декларатор) се забранети .

- Некои имплементации , ако наидат на extern декларација во некој вна­


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

- Делокругот на параметрите се вметнува во функциската наредба 1 па де­


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

- Просторите на имиња за идентификаторите се малку поинакви .


Стандардот ги става сите тагови во еден простор на имиња, а исто воведу ва
и одделен простор на имиња за ознаки ; види Параграф All. 1. Исто така ,
имињата на членовите се асоцираат со структу рата или унијата на која припаѓа ­
ат. (Вообичаена практика во последно време . )

- Униите може да се иницијализираат ; иницијализаторот се однесува на пр­


виот член.

- Автоматските структури 1 унии и низ и може да се иницијализираат , со не­


кои ограничувања .

- Знаковните низ и со експлицитна големина 1 може да се иницијализ и раат


со константен стринг со точно толку знаци (\0-та тивко се истиснува) .

- Контролниот израз и саѕе ознаките кај swi tch 1 може да бидат од инте­
грален тип.
Индекс
' апостроф 21.43-45,226 45,226
-- оператор за декрементирање ( намал ување за \f нов_лист (анг. formfeed), знак за 45,226
1) 20, 55, 124, 238 \п нова_линија, знак за 7, 17, 22. 43-45, 226, 291
- оператор за одземање 49, 241 \r знак за нов ред (анг. carriage returп) 45,226
- операторот унарен минус 238-239 \t таб, знак за 9, 12, 45,226
! логички о перато р за негација 50, 238-239 \v вертикален таб, знак за 45,226
!= оператор за.е разли чн о од• 18, 49, 243 \х хексадекадна излезна секвенца 43, 226
• наводни к 9, 22, 45, 227 л битски оператор: исклучиво ИЛИ 57, 243
# претпроцесорски оператор 105.278 _ знакот подвлечено4 1 , 226,291
## претпроцесорски оператор 105,278 _FILE_ преmроцесорско име 309
#def iпе 15,104.276 _ LINE_ претпроцесорско име 309
#def ine наспроти enum 46,175 _fillbuf фун кција 209
#def ine со аргументи 104 _IOFBF, _IOLBF, _IONBF 294
#def ine, мултилиниски 104 1битски оператор: ИЛИ 57, 243
#else, #elif 107,281 11 логички оператор ИЛИ 24, 49, 58, 246
#endif 107 - операторот комплемент на 58, 238-239
#error 282 + оператор за собирање 49, 241
#if 107, 158, 279 + оnераторот уна рен nлус 238-239
#ifdef 107,281 ++ оператор за ин крементирање (зголемување
#ifndef 107,281 за 1) 20, 55,1 24, 238
#include 38, 103, 178, 279 += оператор за доделување 59
#line 282 < оnератор за помало од 49, 242
#pragrma 282 « оператор за nоместување во лево 58, 242
#undef105,202, 278 <= оnератор за nомало или една кво на 49, 242
% оnератор за остаток при цел обројно делење <assert.h> заглавие (анг. header) 308
(модул) 49, 241 <ctype.h> заглавје 51,301
%1d претворање 20 <errno.h> заглавје 301
& адресен оп ератор 109, 238 <float.h> заглавје 42,314
&& логички оп еартор И 24, 49, 58, 243 <liraits.h> заглавје 42,314
&. битски оператор: И 57, 243 <locale.h> заглавје 291
* оператор за множење 49, 241 <math. h> заглавје 52, 304
*оператор за дереференцирање 110,238 <setjmp.h> заглавје 309
, оператор за пирка 73, 247 <signal .h> за главје 311
. оператор за членство во структура 150, 235 <stdarg.h> загла вје 182, 205, 309
.. . декларација 182, 237 <stddef .h> заглавје 121 , 158, 291
.h екстензија на име на датотека 38 <stdio.h> заглавје б, 18, 104-105, 119, 177-178,291
1оператор за делење 11,49,241 <stdio.h>, сод ржини од 207
?: тернарен оператор за условен израз 61, 246 <stdlib.h> заглавје 83, 166, 305
\\ знакот контраниз (анг. backslash) 9, 45 <string.h> заглавје 46, 124, 302
\О знакот null ( ништо, нула, празно) 35, 45, 226 <time.h> заглавје 311
\а знак за аларм 45,226 = оnератор за доделување 19, 50, 246
\b зна к за бришење на знак (анг. backspace) 9, == оnератор за еднаквост 21, 49, 243

32 1
322 Програмски јазик С Индекс

> оnератор за поголемо од 49, 242 asin библиотечна функција 305


-> оператор за членство кај nокажувач кон asm клучен збор 224
структура 154,235 atan, atan2 бибпиотечни функции 305
>= оператор за поголемо или еднакво на 49, 242 atexit библиотечна функција 308
>> оператор за nоместување во десно 58, 242 atof библиотечна функција 305
0... октална константа 43, 22б atof функција 83
atoi библиотечна функција 305
А atoi функција 51, 72, 8б
автоматска класа nри меморирање 3б, 228 atol библиотечна функција 305
автоматска nроменлива 3б, 87, 228 auto сnецификатор на класа nри меморирање
автоматска, делокруг на 93, 275 249
автоматска, иницијализација на 3б, 47, 99, 2б2
адитивни оnератори 241 Б
адреса на nроменлива 32, 110,238 бафер, влез nоставен во 200
адреса на регистер 249 бафер, nоставување во види setbuf, setvbuf BUFSIZ
адресен оnератор, & 109, 238 294
адресна аритметика види nокажувачка баферирана getchar 202
аритметика безличен (анг. null) исказ 20, 2бб
аларм, знак за \а 45, 22б безличен (анг. null) nокажувач 119, 232
Американски институт за национални стандарди безличен (анг. null) стринг 45
(анг. American National Standards lnstitute) (ANSI) безличен (анг. null), знакот за, \О 35, 45, 22б
ix, 2, 223 белег 223, 27б
апостроф, знакот,' 21,43-45, 22б белези, замена на 27б
апстрактен декларатор 2б3 белези, надоврзување на 105, 278
аргумент кој nретставува nодниза 117 бесконечен циклус, for ~;)
71, 104
аргумент, дефиниција за 28, 235 библиотечна функција 7, 79, 93
аргумент, нагорно nретопување на 54, 237 бинарен nоток 187,291-292
аргумент, nокажувач 117 бинарно дрво 1б3
аргумент, функција 28, 237 битови , изрази за маниnулација со 58, 175
аргументи со nроменлива должина, листа на битски оnератор И, &57, 243
182,205, 237,2б0,270, 309 битски оnератор ИЛИ, 1 57, 243
аргументи, void(npaзнa) листа на 38,8б,2б0,270 битски оnератор исклучиво ИЛИ, л 57, 243
аргументи, командна линија 133-1 38 битски оnератори 57, 243
аритметика, nокажувачка 110,115,117-121, 137, битско nоле, декларирање на 17б,25 1
1б2,241 битско nоле, изедначување сnоред 150, 253
аритметички оператори 49 блок види сложе н исказ
аритметички nретворања (конверзии), блок, иницијализација во 98, 2б8
вообичаени 50, 232 блок-структура б5, 98, 2б8
аритметички тиnови 229 брзо_nодредување 102,1 29
асоцијативност на о nератори б2, 234 броеви, големи на на 10, 20, 42,314
a.out б, 82 binsearch функција б9, 157, 1б 1
abort библиотечна функција 306 bitcount функција 59
abs библиотечна функција 308 break исказ 70, 7б, 2б9
асоѕ библиотечна функција 305 bsearch библиотечна функција 308
addpoint функција 152
addtree функција 1б5 в
afree функција 119 вгнезден исказ за доделување 19, 24, б 1
alloc функција 118 вгнездена структура 151
argc број на аргументи 133 вертикален таб, знак за, \v 45, 22б
argv аргумент вектор 133, 190 взаемно рекурзивни структури 164, 253
ASCII множество од знаци 21, 43, 51, 27б, 302 вистински аргумент види аргумент
asctime библиотечна функција 312 влез од тастатура 17, 177, 200
Програмски јазик С Индекс 323

влез,бафериран 200 gmtime библиотечна функција 312


влез,небафериран 200 goto исказ 77, 2б9
влез, поврат на 91
влез, форматиран види ѕсапf д
влез/излез на знаци 17, 177 датотека, вклучување на 103, 279
влез/излез, грешки при 192, 301 датотека , дескриптор на 200
влез/излез, редирекција (пренасочување) при датотека, дополнување на 187, 206, 292
178, 188, 200 датотека , креирање на 188, 199
внатрешни имиња, должина на 41, 224 датотека , мод на пристап до 187, 209, 292
внатрешни стати ч ки променливи 97 _ датотека, отворање на 187, 199, 202
внатрешно поврзување 228, 275 датотека, покажува ч на 187, 20б, 292
вовлекување 11, 21 , 2б, бб датотека, привилегии на 204
волшебни броеви 15 датотека, nристап до 187, 199, 209, 292
вообичаен (подразбирлив) функциски тип 35, 235 датотеки, п рограма за копирање (пресликување)
вообичаена (подразбирлива) големина на низа 18-19, 201,204
101,132, 156 датотеки, п рограма за надоврзување на 187
вообичаена ( подразби рлива) иницијализација датум, претворање на 130
101,262 дводимензионална ни за (матрица),
вообичаена ознака б9, 266 иницијализац ија на 131, 2б3
вообичаени аритметички претворања 50, 232 дводимензионални н из и (матрици) 129,131,263
va_list, va_start, va_arg, va_end 182,205,297,309 д восмисленост, if-else бб, 2б8, 283
void • покажувач 109, 121, 140, 233 декларатор 25б-2б0
void листа на аргументи 38, 8б, 260, 270 декла ратор на низа 257
void тип 35, 229, 233, 250 декларато р, апс трактен 2б3
volatile, квалификаторот 229, 250 декла ратор, функциски 259
vprintf, vfprintf, vspr i пtf библиотечн и фун кци и декларација 10, 47, 249-254
205, 297 декларација на typedef 171 , 249, 265
декларација на union 172, 251
г декларација на класа на мемориски простор 249
ге нерички покажувач види void • покажувач декларација на надворешна nроменлива 36, 270
глобални, делокруг на 93, 275 декларација на низа 25, 130,257
глобални, иницијализација на 47, 95, 99, 2б2 декларација на пип 25 7
големи загради 7, 11, бЅ, 98 декларација на nо кажувач 11 О, 117, 257
големи загради, позиција на 11 декларација на структура 150, 251
големина на броеви 1О, 20, 42, 314 декларација на функција 259-260
големина на структура 162,239 декларација на функција, имплицитна 31 , 85, 235
градирање кај покажувачка ар итметика 121, 232 декларација нас проти дефиниција 38, 93, 249
граничен услов 21 , 77 декларација, битска 176, 251
getbits функција 58 декларација, н адворешна 270-272
getc библиотечна функција 188, 299 дек ремент, операторот, -- 20, 55, 124, 238
getc макро 207 делење, оператор за , 1 11, 49, 241
getch функција 92 делење, целобројно 11, 49
getchar, uпbuffered 201 дел огруг 228, 273-275
getchar, бафериран 202 дел ок руг на автоматските 93, 275
getchar, библиотечна функција 17, 177, 188, 299 делокруг на глобалните 93, 275
getenv библиотечна функција 308 дело круг на ознака 78, 2бб, 275
getint функција 114 делокруг, лексички 273
getline функција 33, 37, 81, 193 делокруг, nравила за 93, 273
getop функција 91 дереференцирање ѕее индирекција на изведени
gets библиотечна функција 192, 299 типови 1, 11 , 229
gettoken функција 146 десно поместување, о пе раторот за,>> 58, 242
getword фу нкција 1б0 дефанзивно програмирање б7, 70
324 Програмски јазик С Индекс

дефиниција на аргумент 28,235 енумератор 227, 256


дефиниција на глобална променлива 38, 273 енумерациска константа 46, 107,226-227, 25б
дефиниција на макро 27б енумерациски таг 25б
дефиниција на мемориски простор 249 енумерациски тип 229
дефиниција на параметар 28, 235 ефикасн ост 61 , 97, 103, 166,219
дефиниција на функција 28, 81 , 270 Е нотација 43, 227
дефиниција, отстранување на види #uпdef EBCDIC множество знаци 51
дефиниција, провизорна 273 echo, програмата 134-135
децимален дел, отсекување na 54, 230 EDOM304
директориум (именик), програма за листање на else види if-else исказ
210 else-if 26, 67
доделување кај scanf, изземање од 184,297 end of file види EOF
доделување, вгнезден исказ за 19, 24, б 1 enum наспроти #define 46, 175
доделување, израз за 19, 24, б 1, 24б enum сnецификатор 4б,25б
доделување, конверзија преку 52, 24б EOF 18, 177, 292
доделување, оnератор за, += 59 ERANGE 304
доделување, о nератор за, = 19, 50, 246 errпo 301, 304
доделување, операто ри за, 50, 59, 246 error функција 205
доделување, повеќекратно 24 errors, влез/излез 192, 301
должина на имиња 41 ,224 exit библиотечна функција 190, 30б
должина на имињата на променливите 224 EXIT_FAILURE, EXIT_SUCCESS 306
должина на стринг 35, 45, 122 ехр библиотечна функција 305
дополнителни дејства 63, 105, 234, 237 expansion, макро 278
дрво, бинарно 163 extern спецификатор на класа на мемориски
дрво,парзирачко 144 простор 3б, 38, 93, 249
day_of_year функција 130
defined претпроцесорски оператор 107, 281 3
del nрограма 146 завршување на програма 189,192
del функција 144 заглавја, табела на стандардни 291
difftime библиотечна функција 312 загnавје, датотека за 38, 96
DIR, структурата 212 загради, израз во 235
dir.h вклучува чка датотека 215 заземач (алокатор) на мемориски п ростор
dirdcl функција 145 166,217-221
Dirent, структурата 212 запирка, оnераторот,, 73, 247
dirwalk функција 214 зборови, програма за броење на 22, 163
div библиотечна функција 308 знак влез/излез 17, 177
div_t, ldiv_t имиња на типови 308 знак кој се печати 302
dо,исказот 75 , 2б9 знак, неозначен 52,228
do-nothing функција 82 знак, означен 52, 228
double константа 43,227 знаковен додаток 52-54, 208, 226
double, тип 11, 20, 42, 229, 250 знаковна константа 21,43, 22б
double-float претворање 54, 232 знаковна константа, октална 43
знаковна кон станта, широка 226
Е знак-целоброен, конверзија 26,50, 230
евалуирање, редосnед на 24, 58, 63, 75, 90, 105, знаци (знаци за празно место), бланко 184, 194,
111,234 297,302
еднаквост, оператори за 49,243 знаци, множество од 27б
еднаквост, операторот за,== 21, 49,243 знаци, множество од, ASCII 21, 43, 51,276, 302
еквиваленција на типови 2б5 знаци, множество од, EBCDIC 51
екран, излез на 17, 178, 190, 200 знаци, множество од, 150 276
експлицитно претворање, оператор за види знаци, низа од 22,32,122
претопување знаци, стринг од види стринг, константа
Програмски јазик С Индекс 325

знаци, функции за тестирање на 194, 301 238


ѕ интеграnни типови 229
ѕвонче, знак за, види аларм, знак за интеграnно на горно претоnување 52, 230
и информации, криење на 79-80, 88, 90
идентификатор 224 исказ, означувач на крај на 11 , 65
избегнување на goto 78 искази 2бб-270
изедначување преку union (унија) 218 искази, секвенцирање на 2бб
изедначување според битски полиња 176, 253 исклучоци 234, 311
изедначување, ограничување заради 162, 1бб, искористување на константа 278
174,195,217, 233 if-else двозначност бб, 268, 283
излез на екран 17, 178, 190, 200 if-else исказ 21 ,24,65,268
излез, секвенца за 9, 21, 43-45,226,276 inode 210
излез, секвенца за, \х хексадекадна 43, 226 instali функција 170
излез, табела на секвенци за 45, 226 int type 1О, 42, 250
излез, форматиран видиprintf, редирекција isalnum библиотечна функција 160,302
(nренасочување) на излез 178 isalpha библиотечна функција 160, 194, 302
израз 234-247 iscntrl библиотечна функција 302
израз во загради 235 isdigit библиотечна функција 194,302
израз за доделување 19, 24, б 1, 246 lseek системски повик 205
израз, исказ кој е65, 67, 2бб isgraph библиотечна функција 302
израз,константен 45, 69,107, 247 islower библиотечна функција 194,302
израз, примарен 234 ISO множество на знаци 276
изрази, ред на извршување на 62, 234 isprint библиотечна функција 302
име 224 ispunct библиотеч на функција 302
име на дадотека, екстензија на .h 38 isspace библиотечна функција 160, 194, 302
име, криење на 98 isupper библиотечна функција 194,302
имиња , должина на of 41, 224 isxdigit библиотечна фувкциј 302
имиња, простор на 273 itoa функција 76
имплицитна де кларација на функција 31 , 85, 235
индекси, негативни 117
индексирање и покажувачи 114,11 6,259 jump, исказите 269
индексирање на низа 26,114,235,259
индирекција (дереференцирање), оператор за, * к
110,238 калкулатор, програма 85, 87, 89, 185
иницијализатор 273 квалификатор, тип на246, 291
иницијализатор, форма на 99, 247 класа на мемориски простор 228
иницијализација 47, 99, 260 класа на мемориски простор, auto спецификатор
иницијализација во блок 98, 268 на 249
иницијализација која се под разбира 101 ,262 класа на мемориски простор , automatic 36,228
иницијализација на автоматски 35, 47, 99, 262 класа на мемориски простор, extern
иницијализација на глобални 47, 95, 99, 262 сnецификатор на 36, 38, 93, 249
иницијализација на дводимензионална ни за класа на мемориски nростор, register
(матрица ) 131,263 спецификатор на 97, 249
иницијализација на низа 101,132,262 класа на мемориски nростор, static 36, 97, 228
иницијализација на покажувач 119,162 класа на мемориски nростор, static сnецификатор
иницијализација на статички 47, 99, 262 на 97, 249
иницијализација на структура 150,262 класа на мемориски простор, декларација на 249
иницијализација на унија 262 класа на мемориски nростор, отсуство на

иницијализација низи од структури 156 с nецификатор на 250


иницијаnизација преку константент стринг 101, класа на мемориски прос тор, сnецификатор на
251 250
инкрементирање, оnератор за, ++ 20, 55, 124, клучни зборови, листа на 224
326 Програмски јазик С Индекс

клучни зборови, програма за броење на 15б мали букви, програма за претвора ње во 179
командна линија, аргументи од 133-1 38 мемориски простор, дефиниција на 249
коментар 10,223-224, 27б мемориски простор, заземач (алокатор) 1бб,
компајлирање наС програма б, 28 217-221
компајлирање на повеќе датотеки 82 мемориски простор, резервирање на 249
ком пајли ра ње, засебно
79, 93, 273 меморискиот простор, редослед на низа во 131,
комплемент на, операторот , - 58, 238-239 259
конкатенација ( надоврзување ) на белези 105, 278 множење, оnераторот, • 49, 241
конкатенација ( надоврзување) на стрингови модул (остаток при делење), операторот, % 49,
45,105,227 241
константа, искористување на 278 модуларизација 27, 32, 39, 79,87-88, 126
константа , суфикс на 43, 22б мултипликативни оператори 241
константа, тип на 43, 22б main функција б
константен израз 45,б9, 107,247 main, return од 29, 192
константи 43, 224 makepoint функција 152
контраниз, знакот (а нг. backslash), \\ 9, 45 malloc библиотечна функција 1б8, 195, 30б
контролен знак 302 malloc функција 219
контролна линија 103, 27б-278 memchr библиотечна функчија 304
memcmp библиотечна функција 304
л memcpy библиотечна функција 304
л вредност (aнг.lvalue) 230 memmove библиотечна функција 304
лево поместување, оператор за, << 58, 242 memset библиотечна функчија 304
лексикографско подредување 138 mktime библиотечна функција 312
лексички делокруг 273 modf библиотечна функција 305
лексички конвенчии 223 month_day функција 130
линии, програма за броење на 21 month_name функција 132
линии, спојување на 27б morecore функција 220
листа на клучни зборови 224
листање на директориуми , програма за 21 О н
логичка негачија, операторот,! 50, 238-239 наводник, знакот, • 9, 22, 45, 227
логички OR, операторот, ! ! 24, 49, 58, 24б нагорно претоnување на аргумент 54, 237
логички израз, нумеричка вредност на 52 нагорно претопување, интегрално 52, 230
логичко И, операторот, 5.& 24, 49, 58, 243 надворешна (глобална) променлива 3б, 86, 228
локализација, прашања околу 291 надворешна (глобална) променлива, декларачија
labs библиотечна функција 308 оf3б,270
ldexp библиотечна функција 305 надворешна (глобална) променли ва, дефиниција
ldiv библиотечна функција 308 of38,273
localtime библиотечна функција 312 надворешна(глобална) декларација 270-271
log,log 10 библиотечни функции 305 надворешни (глобални ) имиња, должина на 41,
long double, константа 43, 227 224
long double, тип 42,227 надворешни (глобални) статички променливи 97
long, константа 43,22б надворешно (глобално) поврзување 8б, 224, 228,
long, тип 11, 20, 42, 227, 250 250, 275
LONG_MAX, LONG_MIN ЗОб најдолга линија, програма за 33, 37
longjmp библиотечна функчија 309 научна (анг.
scientific) нотација 43, 8б
lookup функција 170 небафериран getchar 201
lower функција 51 небафериран влез 200
ls наредба 210 негативни индекси 117
нееднаквост, оператор за, != 18,49,243
м некомплетен тип 251
макро претпроцесор 103,275-282 неконзистентна декларација на тип 85
макроа со аргументи 104 нелегална покажувачка аритметика 119-1 21, 1б2,
Програмски јазик С Индекс 327

241 оnератори за еднаквост 49, 243


неозначен знак 52,228 оnератори за nоместување 57, 242
низа насnроти nокажувач 114, 116-117, 122, 132 оnератори, адитивни 241
низа од з наци22, 32, 122 оnератори, аритметички 41
низа од nокажува чи 125 оnератори, битски 57, 243
низа од структури 155 оnератори,мултиnликативни 241
низа, аргумент име на 32, 117, 131 оnератори, nредимство (nредн ост) на 19, 62, 111 ,
низа, големина која се nодразбира (nочетна 154-155, 234
големина) 101, 132, 156 оnератори, релациони 18, 49, 242
низа, дводимензионална 129, 131, 263 оnератори, табела на 63
низа, декларатор на 257 оnерации врз nокажувачи, дозволени 121
низа , декларирање 25, 130, 257 оnерации врз уни и 174
низа , индексира ње кај 25, 114, 235, 259 оnишувач на датотека 200
низа , иницијализација 101 , 132, 262 отворе н системски nовик 202
низа , иницијализација на дводимензионална отсекување на децимален дел 54,230
131,263 отсекува ње со делење 11,49,241
низа , nовеќедимензионална 129, 259 отстранување на дефиниција види #undef
низа , nромена на и мето на 116,234 отсуство на сnецификатор на класа на мемориски
низа , редослед во меморискиот nростор кај nростор 250
131,259 отсуство на сnецификатор н а тиn 250
низа, референца кон 235 O_RDONLY. O_RDWR, O_WRONLY 202
нов ред, знак за (ан г. carriage return), \r 45, 226 opendir функција 215
нов_лист (анг. formfeed), з нак за, \f 45, 226 Ох ... хексадекадна константа 43, 226
нова_линија 223, 276
нова_линија, знак за , \n 7, 17, 22,43-45, 226, 291 п
нула, исnуштање на сnоредба со бб, 123 nараметар 98, 116,236
нумеричка вредност на логичко nодредување 52 nараметар, дефиниција на 28, 235
нумеричка вредност на релацио нен израз 50, 52 napcep, рекурзив но-сnуштачки 144
нумеричко nодредување 138NULL, 119 nарсирачко дрва 144
numcmp функција 141 повеќе датотеки, компајлирање на 82
nовеќедимензионал на низа 129,259
о повеќекратно доделување 24
објект 228, 230 nовеќенасочна одлука 26, 67
облик (форма) (ан г. pattern), nрограма за наоfање nовик по адреса (референца) 31
на 79,81 , 135-137 повик по вредност 31 , 111 ,236
обратна nол ска нотација 87 пов рат на влез 91
одделено конnајлирање 79, 93, 273 поврзување 228, 273-275
одеземање, оnератор за, - 49, 241 поврзување, внатрешно 228, 275
одземање на n окажувачи 121, 162, 232 поврзување, надворешно 86, 224, 228, 250, 275
ознака 77,266 повторување ( итерација), искази за 269
ознака, саѕе 69,266 nоголема или еднакво на, операторот, >= 49, 242
ознака, default 69, 266 поголема од, оnераторот, > 49, 242
ознака, делокруг на 78, 266, 275 подвлечено, знакот за, _ 41 , 224, 291
означен знак 52, 228 nодниза, аргумент кој претставува 117
означен исказ 77, 266 подредување на линии од текст 125, 139
означен тиn 42,250 подредување, лексикографско 138
означувач, функциски 235 nодредување,нумеричко 138
октална знаковна константа 43 подредување, nрограма за 126, 139
октална ко нстанта, О... 43, 226 позиција на загради 11
оnератор за собирање,+ 49,241 nокажувач кон датотека 187, 206,292
оnератори за асоцијативност of 62, 234 nокажувач кон структура 160
оnератори за доделување 50, 59, 246 покажувач кон функција 138, 172, 235
328 Програмски јазик С Индекс

покажувач наспроти liиза 114, 116-117, 122, 132 претворање, float-double 52, 232
покажувач, void * 109, 120, 140,233 претворање, експицитен оператор за види
покажувач, аргумент 117 претоn ување

покажувач, безличен (анг. null) 119, 232 претворање, знаковен-целоброен 2б,50, 230
покажувач, генерирање (созда вање) на 234 претворање, покажувач-целоброен 232-234, 241
покажувач, декларација на 110, 117,257 претворање, реален-целоброен 54, 230
покажувач, иницијализација на 119, 1б2 претворање, целоброен-знаковен 54
покажувач, претворање на 1бб, 232, 241 претворање, целоброен-покажувач 233, 241
покажувачи и индекси 114,1 1б,259 претворање, целоброен-реален 13, 230
покажува чи , дозволени операции врз 120 претопување, конверзија преку 54, 232-234, 241
покажувачи, низа од 125 претопување, оператор за 54, 1б6, 195, 232, 241,
покажувачи, одземање на 120, 1б2, 232 263
покажувачи, споредба на 119, 1б2, 219, 225 претпроцесор, макро 103, 275-282
покажувачка аритметика 110, 115, 117-120, 137, претпроцесорси оператор, defiпed 107,281
1 б2, 241 претпроцесорски оператор , # 105, 278
покажувачка аритметика, градирање во 121, 232 nретпроцесорски оператор, ## 105, 278
покажувачка аритметика, недозволена 119-121, претпроцесорско име, _FILE_ 309
1б2, 241 претпроцесорско име, _LINE_ 309
покажувач-целоброен, претворање 231-233, 241 претпроцесорско име, однапред дефинирано 282
пол е ѕее би тско пол е префисно ++и-- 55, 124
полска нотација 87 пречекорување (анг. overflow) 49,234,304, 311
помало или еднакво на, операторот, <= 49, 242 nречекорување на долна граница (анг. uпderflow)
помало од, операторот, < 49, 242 49, 304, 311
поместување, оператори за 57, 242 привилегии на датотека 204
постфиксен ++и-- 55, 123 примарен израз 234
поток, бинарен 187,291-292 пристап кон датотека, мод 187, 209, 292
поток, текстуален 17, 177, 291 провизорна дефиниција 273
празен исказ види пull исказ програма за calculator 85, 87, 89, 185
празен стринг 45 програма за cat 187, 189-192
празна функција 82 програма за del 14б
п разни места, програма за броење на 25, 70 програма за echo 134-1 35
nразни место, бланко 223 програма за fsize 213
празно место, знаци за 184, 194, 297, 302 програма за uпdcl 147
преведување, редослед на 275 програма за броење на зборови 22, 163
преведување, фази на 223, 275 програма за броење на знаци 19
превод, еди ница за 223, 270, 273 програма за броење на знаци 19
предимство (предност) на оператори 19, б2, 111 , програма за броење на клучни зборови 15б
154-155, 234 програма за броење на линии 21
преносливост 3, 43, 51 , 58, 172, 177, 179, 217 програма за броење на празни места 25, 70
престапна година, пресметка на 49, 130 програма за копирање (пресликување) на
претворања, вообичаени аритметички 50, 232 датотека 18-19, 201,204
претво рање (конверзија) 231-233 nрограма за листање на директориум 21 О
претворање (промена ) на името на низа 11б, 234 програма за надоврзување на датотеки 187
претворање на покажувач 166,232,241 програма за најдолга линија 33, 37
претворање на тип преку returп 8б, 270 програма за наоѓање на облик (форма , анг.
претворање на тип, оператор за види правила за pattern)79, 81, 135-137
претворање преку претоnување 50, 52, 232 програма за подредување 126,139
nретворање на функција 234 nрограма за претворање во мали букви 179
претворање преку поврат (анг. returп) 8б, 270 програма за претворање на температура 9-1 О,
претворање со доделување 52, 246 13-15,17
претворање со претопување 54, 232-234, 241 програма за табела за поврзување 1б8
претворање, double-float 54, 232 програма, форма на 11, 21 , 25, 47, 1б2, 223
Програмски јазик С Индекс 329

nрограма, читливост на 11, 61, 76, 101,172 register спецификатор на класа на мемориски
програмски аргументи види аргументи од nростор 97, 249
командна линија remove библиотечна функција 292
променлива 228 rename библиотечна функција 292
променлива должина, листа на аргументи со return (nоврат) од main 29, 192
182,205,236,260,270,309 return, иcкaзoт 28,35, 82, 86,270
променлива, автоматска 36,87,228 return, nретворање на тип преку 86, 270
променnива, адреса на 32, 11 О, 238 reverse функција 73
променлива, надворешна (глобална) 36, 86, 228 rewind библиотечна функција 301
променливи, должина на имиња на 224 Richards, М. 1
променливи, синтакса на имиња на 41 , 224 Ritchie, D. М. xi
nрототиn на функција 29, 35, 54, 85, 140, 236
perror библиотечна функција 301 е
рор функција 90 самореференцирачка структура 164,253
pow библиотечна функција 27, 305 секвенца на искази 266
power функција 28, 31 селекција, исказ за 268
printd функција 102 симболички ко нстанти, должина на 41
printf библиотечна функција 7, 12, 20, 179, 296 синтакса на имињата на nроменливите 41 , 224
printf претворања, табела на 180, 2% синтаксна н отација227
pгintf примери, табела на 14,180 системски nовици 199
ptlnreet функција 152 сложен исказ 65, 98, 266, 270-272
ptrdiff_t име на тип 121, 172, 242 современ стил, функција во 236
push функција 90 сnецификатор на класа н а мемориски простор
putc библиотечна функција 188,299 249
putc макро 207 сnецификатор на класа на мемориски простор,
putchar библиотечна функција 17, 178, 188, 299 auto 249
puts библиотечна функција 192, 299 сп ецификато р на класа на меморис ки nростор,
extern 36, 38, 93, 249
р сnецификатор н а кла са на мемориски п ростор,
реален-целоброен, претворање 54, 230 register 97, 249
реална константа 13, 43, 227 сnеци фикатор н а класа на мемори с ки n ростор,
реални, типови на 229 static 97, 249
реrистер, адреса на 249 спецификатор н а класа на мемори с ки n ростор,
редирекција види влез/излез, редирекција отсуство на 250
(nренасочување) nри спецификатор на тип 250
редослед на извршување 24, 58, 63, 75, 90, 105, спецификаторот enum 46, 256
111 , 234 спецификаторот struct 256
редослед на nреведување 275 спецификаторот union 256
резервирани зборови 42,224 сnојување на линии 276
резервирање на мемориски простор 249 сnоредување н а покажувачи 119, 162, 219, 243
рекузивно-спуштачки napcep 144 сnроведува

рекурзија 101,163,165,214, 237,269 ста ндарден влез 177, 188, 200


релационен израз, нумеричка вредност на 50, 52 стандарден излез 178,188,200
релациони оператори 17, 491 , 242 стандардна греш ка 188,200
RAHD_MAX 306 стандардни заглавја, табела на 291
raise библиотечна функција 311 стар-стил, функција во 28, 38, 85, 237
rand библиотечна функција 306 статички, иницијализација на 47, 99, 262
rand функција 55 степенувачки (за степенување) 27, 305
read, системски nовик 200 стринг константа 7, 22, 35, 45, 116, 122, 227
readdir функција 21 б стри нг константа , иницијализа ција со помош на
readlines функција 127 101 , 262
realloc библиотечна функција 306 стринг константа, широка 227
330 Програмски јазик С Индекс

стринг литерал види стринг константа, тип на 234 stdin 188, 292
стринг, должина на 35, 45, 122 stdout 188, 292
стрингови, надоврзување на 45,105,227 str index функција 81
структура, вгнездена 151 strcat библиотечна функција 302
структура, големина на 162, 239 strcat функција 57
стру ктура, декларација на 150,251 strchr библиотечна функција 302
структура, име на член на 150, 253 strcmp библиотечна функција 302
структура, иницијализација на 150, 262 strcmp функција 124
структура, оператор за членство во, . 150, 235 strcpy библиотечна функција 302
структура, операторот п о кажувач на,-> 154, 235 strcpy функција 123-1 24
структура, покажувач на 160 strcspn библиотечна функција 304
структура, самореференцирачка 164, 253 strerror библиотечна функција 304
структура, таг ( име, ознака) на 150, 251 strf time библиотечна функција 312
структури, взаемно рекурзивни 164, 253 strleп библиотечна функција 304
структури, иницијализација на низа од 156 strlen функција 46, 11б, 121
структури, низа од 155 strncat библиотеч на функција 302
структурна референца , семантика на 237 strncmp библиотечн а функција 302
структурна референца , синтакса 237 strпcpy библиотечна функција 302
суфикс, константен 226 strpbrk библиотечна функција 304
sbrk системски повик 219 strrchr библиотечна функција 302
scanf библиотечна функција 112, 184, 298 strspn библиотечна функција 304
scanf изземање од доделување 184, 297 strstr библиотечна функција 304
scanf претворања, табела на 185, 298 strtod библиотечна функција 305
SEEK_CUR, SEEK_END, ЅЕЕК_ЅЕТ 301 strtok библиотечна функција 304
setjmp библиотечна функција 309 strtol, strtoul библиотечни функции 306
setbuf библиотечна функција 294 struct спецификатор 251
setvbuf библиотечна функција 294 swap функција 103, 112, 129, 141
Shell, D. L. 72 switch, исказот 69, 88, 268
shellsort функција 73 syscalls.h вклучувачка датотека 201
short, тип 11, 42, 239, 250 system библиотечна функција 195, 308
SIG_DFL, SIG_ERR, SIG_IGN 311
signal библиотечна функција 31 1 т
sin библиотечна функција 305 табела за поврзување, nрограма за 168
sinh библиотечна функција 305 табела на printf претворања 180,296
ѕ izе_tименатип 121 ,1 58, 172,239,292 табела на printfпpимepи 14,180
sizeof, операторот 107, 121 , 158,238-239, 299 табела на scanf претворања 185, 298
sprintf библиотечна функција 182, 297 табела на излезни секвенци 45, 226
sqrt библиотечна функција 305 табела на оператори 63
squeeze функција 56 табела на стандардни заглавја 291
srand библ иотечна функција 306 таг (име, ознака ) на enumeration 256
srand функција 55 таг (име, ознака ) на union 256
sscanf библиотечна функција 298 таr (име, ознака) на структура 150, 251
stat системски повик 212 тастатура, влез од 17,177,200
stat структура 21 2 текстуален поток 17, 177, 291
stat.h include file 212-213 текстуални ли н ии, подредување на 125, 139
static класа на мемориски простор 36, 97, 228 температура, програма за претворање на 9-1 О,
static променливи , внатрешни 97 13-15, 17
static променливи , надворешни (глобал ни) 97 терминал, влез и излез од 17
static функциска декларација 97 тип на константа 43, 226
static, специфи катор на класа на мемориски тип на стринг 234
простор 97, 249 тип, декларација на 257
stderr 188, 190, 292 тип, квалификатор на 246, 250
Програмски јазик С Индекс 331

тиn, некомnлетен 251 ф

тиn, неконзистентна декларација 85 фази на преведување 223, 275


тиn, отсуство на сnецификатор на 250 формален параметар види параметар
тип, спецификатор на 250 форматиран влез види scanf
тиnови,аритметички 229 форматиран излез види printf
тиnови, еквиваленција на 265 фундаментални типови 1О, 42, 228
тиnови, изведени 1, 11, 229 функции за тестирање на знаци 194, 301
тиnови, имиња на 263 функција, декларација на 259-260
тиnови,реални 229 функција, имnлицитна декларација на 31, 85,235
точка заnирка 11, 17, 20, 65, 67 функција, означувач на 235
триграф секвенца 276 функција, покажувач кон 138, 172, 235
tаllрсфункција 166 функција, претворање на 234
tan библиотечна функција 305 функција, современ-стил на 236
tanh библиотечна функција 305 функција, стар-стил на 29, 38, 85, 236
Thompson, К. L. 1 функциска декларација, static 97
time библиотечна функција 312 функциска дефиниција 28, 81 , 270
time_t име на тиn 311 функциски аргумент 28, 233
ТМР_МАХ294 функциски аргумент, nретворање на види
tmpfile библиотечна функција 294 нагорно претопување на аргумент

tmpnam библиотечна функција 294 функциски декларатор 259


tolower библиотечна функција 179, 194, 302 функциски имиња, должина на 41 , 224
toupper библиотечна функција 194, 302 функциски повик, семантика на 235
treeprint функција 166 функциски повик, синтакса на 235
trim функција 77 функциски nрототип 29, 35, 54, 85, 140, 235
typedef декларација 171, 249, 265 функциски тип кој се nодразбира 35, 235
types, интегрални 229 fpos_t име на тип 301
types, фундаментални 1О, 42, 228 fprintf библиотечна функција 188, 294
types.h вклучувачка датотека 21 3, 215 fputc библиотечна функција 299
fread библиотечна функција 299
у fabs библиотечна функција 305
унарен минус, оnераторот,- 238-239 fclose библиотечна функција 189, 292
унарен nлус, оnераторот, + 238-239 fcntl.h вклучувачка датотека 202
унии, оnерации врз 174 feof библиотечна функција 192, 301
унија, иницијализација на 262 feof макро 207
унија, таг ( име, ознака) на 251 ferror библиотечна функција 192, 301
условен израз, тернарен оnератор за ?: 61, 246 ferгor макро 207
условно комnајлирање 107, 279 ff\ush библиотечна функција 292
ULONG_MAX 306 fgetc библиотечна функција 298
undcl програма 147 fgetpos библиотечна функција 301
ungetc библиотечна функција 194, 299 fgets библиотечна функција 192, 299
ungetch функција 92 fgets функција 193
union, декларација на 172, 251 FILE име на тип 187
union, изедначување сnоред 218 filecopy функција 189
uпion, спецификаторот 251 FILENAME МАХ 292
UNIX датотечен систем 199,210 float, константа 43, 227
unlink системски повик 205 float, тип 1О, 42, 229, 250
unsigned char, тиnот 42, 201 float-double, nретворање 52, 232
unsigned long константа 43,226 floor библиотечна функција 305
unsigned константа 43, 226 · fmod библиотечна функција 305
unsigned, типот 42, 59, 229, 250 fopen библиотечна функција 187,292
fopen функција 208
FOPEN_MAX 292
332 Програмски јазик С Индекс

for насnроти while 15, 71 creat системски nовик 202


for( ;;) бесконечен циклус 71 , 104 CRLF 177, 291
for, исказот 14, 20, 71 , 269 ctime библиотечна функција 312
fortran клучен збор 224
fputs библиотечна функција 192, 299 ч
fputs функција 193 членка на структура, име на 150, 253
free библиотечна функција 195, 306
free функција 220 ш
freopen библиотечна функција 189, 292 широка знаковна константа 226
frexp библиотечна функција 305 широка стринг константа 227
fscanf библиотечна функција 188, 297
fseek библиотечна функција 301 Q
fsetpos библиотечна функција 301 qsort библиотечна функција 308
fsize функција 214 qѕоrtфункција 102, 129, 140
fsize, nрограма за 213
fstat системски nовик 215 w
ftеllбиблиотечна функција 301
wchar_t име на тиn 226
fwrite библиотечна функција 299 while насnороти for 15, 71
while, исказот 11, 71 , 269
х
write системски nовик 200
хексадекадна излезна секвенца, \х 43, 226 writelines функција 127
хексадекадна константа, Ох ... 43, 226
hash табела 169
hash функција 169
Hoare, С. А. R. 102
HUGE_VAL 304

ц
цевка 178, 200
целоброен-знаковен, nретворање 54
целоброен-nокажувач, nретворање 233,241
целоброен-реален, nретворање 13,230
целобројна константаt 13,43,226
циклус види while, for, do
calloc библиотечна функција 195, 306
canonrect функција 154
саѕе ознака (aнг. label ) 69, 266
cat, nрограма 187,189-190
се команда 6, 82
ceil библиотечна функција 305
char, тиn 11 , 42, 227, 250
clearerr библиотечна функција 301
clock библиотечна функција 311
clock_t име на тиn 31 1
CLOCKS_PER_SEC 311
close системски nовик 205
closedir функција 216
const квалификатор 47, 229, 250
continue, исказот 77, 269
сору функција 33, 38
соѕ библиотечна функција 305
cosh библиотечна функција 305
Програмски јазик С
Брајан В. Керниган 1 Денис М. Ричи

Од предговорот

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


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

Како што рековме во предговорот на првото издание, "С станува покорисен како
што расте и искуството во работата со него". После десетгодишно дополнително
искуство, се уште го мислиме тоа. Се надеваме дека оваа книга ќе ви помогне да го
научите е и успешно да го користите.

ISBN 978-608-4535-48-5

9 786084 535485

You might also like