S.II 16945 db142
ПРОГРАМСКИ ЈАЗИК
Брајан В. Керниган Денис М. Ричи
'-' tamina
Ѕ . /; 1(>9'-tS-- 8d1v 42.
Ш (о. dp . G5,2.JSро11о
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 С Kerпighan ,
programming language 1 Brian W. 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 А И -- t.o~ ·.• ..' ."-.:""" - ::.>..l('l :·or w-oи .... " ':"Ј Г 1 L
v
БИБЛИОТЕК А
Сиг._Ј · .Е..
,ff;;~f:J.!:' ~r}л.
Инв. бp{p_/:.l!J 5jr. rp
42.
,2 ~ 01
/2otto
Предrовор
Содржина
xi xii
Предrовор кон првото и3дание Вовед
1
Глава
5 5 8
Глава
1 - Краток вовед во ја3ИКОТ е 1.1 По четок 1.2 Променливи и аритметички изрази 1.3 Исказот for 1.4 Симболички константи 1.5 Влез и излез на знаци 1.5.1 Копирање на датотека 1.5.2 Броење на знаци 1.5.3 Броење на линии 1.5.4 Броење на зборови 1.6 Низи 1.7 Функции 1.8 Аргументи - повикување по вредн ост 1.9 Низи од зна ц и 1.1 О Надворешни променливи и делокруг 2 - Типови, оператори и и3ра3и 2.1 Имиња на променливите 2.2 Податочни типови и нивна големина 2.3 Константи 2.4 Декларации 2.5 Аритметички оператор и 2.6 Релациони и логички о ператори 2.7 Конверзија на типови 2.8 Оператори за инкрементирање и декрементирање 2.9 Битски оператори 2.1О Оператори и изрази за доделување на вредност 2.11 Условни изрази 2.12 П риоритет и редослед на евалуирање
v
14 16 17 17 19 21
22 24 27 31
32 35 41 41
42 43 47 48 49 50 54 57
59 61 62
Vl
Програмски јазик С
Глава З
- Контрола на текот 3.1 Наредби и блокови 3.2 if- else 3.3 else- if 3.4 switch 3.5 Циклуси- while и for 3.6 Циклус do- wh ile 3.7 break и continue 3.8 goto и ознаки
Глава
4- Функции и структура на nporpaмa 4.1 Основи на функции 4.2 Функции кои враќаат нецелобројни вредности 4.3 Надв о р е шни променливи 4.4 Правила на делокругот (анг. ѕсоре) 4.5 3аглавишни датотеки 4.6 Статички променливи 4.7 Регистарски променливи 4.8 Блоковска стру кту ра 4.9 Иницијализација 4.1 О Рекурзија 4.11 е претпроцесор 4.11.1 Вклучу вање на датотеки 4.1 1.2 Макрозамена 4.11.3 Условно вклучува ње
Глава Ѕ
Глава
- Покажувачи и низи 5.1 Покажувачи и адреси 5.2 Покажувачи и функциски аргументи 5.3 Покажува чи и низи 5.4 Адресна аритметика 5.5 По кажува чи кон знаци и функции 5.6 Ни з и од пока жувачи ; Покажувачи кон покажувачи 5.7 Повеќедимензионални низи 5.8 Иницијализација на низи од покажувачи 5.9 Покажувачи наспроти повеќедимензионални низи 5.1 О Аргументи од командна линија 5.11 Покажу вачи кон фу нкции 5.12 Комплицирани де кл арац ии
6- Структури 6.1 Основни поими за структур ите 6.2 Структури и функции 6.3 Низи од структури
Содрж ина
65 65 65
67 69 71 74
76 77
79
79 83
86 93 95
97 98 98 99 101 103 106 104 107 109 109 111 114 117 121 125 129 132
133 134 139 142 149 149 152 155
Програмски јазик С
Содржина
6.4 Покажувачи кон структури 6.5 Само-референцирачки структури 6.6 Пребарување на табела 6.7 typedef 6.8 Унии 6.9 Битови полиња
vн
160 162 168 170 172 174
Гnава
7 - Вnез и изnез 7.1 Стандарден влез и излез 7.2 Форматиран излез- printf 7.3 Листи на аргументи со променлива должина 7.4 Форматиран влез- scanf 7.5 Пристап до датотеки 7.6 Справување со грешки- stderr и exit 7.7 Линиски влез и излез 7.8 Раз ни функции 7.8.1 Операции со стрингови 7.8.2 Тестирање на класата на з накот и конверзија 7.8.3 ungetc 7.8.4 Извршување на команда 7.8.5 Менаџирање со меморија 7.8.6 Математички функции 7.8.7 Генерирање на случаен број
177 177 179 182 183 187 190 192 194 194 194 195 195 195 196 197
Гnава
8- Системски интерфејс на UNIX 8.1 Дескриптори на датотеки 8.2 Примитивен 1/0- read и write 8.3 open, creat, close, unliпk 8.4 Случаен пристап- elseek 8.5 Пример- имплементација на fopen и getc 8.6 Пример - листање на содржина на директориуми 8.7 Пример - Алокатор (3аземач) на меморис ки п ростор
199 199 200 202 205 206 21 О 216
Додаток А
- Референтен
прирачник
А1 Вовед А 2 Л ексички конвенции
А2.1 Белези А2 . 2 Коментари
А2.3 Идентификатори А2.4 Клучни зборови А2 .5 Константи А2.5 . 1 Целобројни константи А2.5 .2 3наковни константи
А2.5.3 Реални константи
223 223 223 223 224 224 224 225 225 226 227
Vlll
Содржина
Програмски јазик С А2.5.4 Ен ум ерациски константи А2.6 Стрингови константи
А3 Синтаксичка нотација А4 Значење на идентификаторите А4.1 Класа на мемориски простор А4.2 Основни податочни типови А4.3 Изведени типови
А4.4 Квалификатори на типови А5 Објекти и л вредности Аб Конверзии
А6. 1 Интегрално нагорно претопување (промоција) А6.2 Интегрални конверзии Аб.З Цели и реални броеви Аб.4 Реални типови
А6.5 Аритметички конверзии Аб.б Покажувачи и цели броеви А6.7
void
А6.8 Покажувачи кон
void
А7 Изрази А7.1 Генерирање на покажувачи
А7.2 Примарни изрази А7.3 Постфиксни изрази А7.3.1 Референци кон низа А7.3.2 Функциски повици А7.3.3 Референци на структура А7.3.4 Постфиксно инкрементирање А7.4 Унарни оператори А7.4.1 Оператори за префиксно инкрементирање А7.4.2 Оператор за адресирање
А7.4.3 Оператор за индирекција (дереференцирање) А7.4.4 Операторот унарен плус А7.4.5 Операторот унарен минус
А7.4.6 Оператор за единично комплементирање А7.4.7 Оператор за логичка негација А7.4.8 Оператор А7.5 Претопувања
sizeof
(casts)
А7.6 Мултипликативни оператори А7.7 Адитивни оператори
А7.8 Оператори за поместување А7.9 Релациски оператори А7.1 О Оператори за еднаквост А7.11 Битски оператор И А7.12 Битски оператор Исклучиво ИЛИ А7.13 Битскиоператор ИЛИ
227 227 228 228 228 229 230 230 231 231 231 231 232 232 232 233 234 234 235 235 236 236 237 237 238 239 239 239 240 240 240 240 241 241 241 241 242 242 243 243 244 245 245 245
Прогр амски јазик С
Содржина
А7.14 Логички оператор И А7 . 15 Логички оператор ИЛИ А7 . 1 б Условен оператор
А7.17 Изрази за доделување А7.18 Оператор запирка А7.19 Константни изрази
А8 Декларации А8 . 1 Спецификатори за класи на мемориски простор
А8.2 Спецификатори на тип А8.3 Декларации на структура и унија А8.4 Енумерации А8.5 Декларатори
А8 .6 Значење на деклараторите А8.6.1 Декларатори на покажувачи А8.6.2 Декларатори на низи
А8 .6.3 Функци ски декларатори А8.7 Иницијализација А8 . 8 Имиња на типови
А8 .9 typedef А8 . 1 О Екви валентност на типови
А9 Наредби А9.1 Означени (анг.
labeled) наредби
А9.2 Изразни наредби А9.3 Сложени наредби А9.4 Наредби за избор А9.5 Наредби за п овторување А9.6 Наредби за скок А 1О Надвор е шни декларации
А 10.1 Функциски дефи н и ци и А 10.2 Надворешн и декла рации А 11 Делокруг и поврзување
А 11.1 Лексички дело круг А 11 .2 Поврзување А 12 Претпроцесирање
А 12.1 Триграф секвенци А 12.2 Спојување на линии
А 12.3 Дефиниција на макроа и нивно проширување А 12.4 Вклучување на датотеки А 12.5 Условна компилација А 12.6 Линиска контрола
А 12.7 Генерирање на грешка А 12.8
pragma
А 12.9 Празна директива
А 12.1 О Предефинирани имиња
1х
245 246 246 246 247 248 248 249 250 25 1 255 256 257 257 258 259 261 263 264 265 266 266 266 267 267 268 269 270 270 272 273 273 274 275 275 276 276 279 279 281 28 1 281 282 282
х
Содржина
Програмск и ја зик С А 13 Граматика
Додаток Б
- Стандардна библиотека
Б 1.1 Операции со датотеки Б 1.2 Форматиран излез Б 1.3 Форматиран влез Б 1.4 Функции за влез и излез
282
Б1 Вле з и излез:
на знаци
Б1.5 Функции за директен вле з и излез
Б1 .6 Функции за позиционирање на датотека Б 1.7 Фу нкции за грешка
Б2 Проверка на класата на знак:
Б4 Математички функции: БЅ Корисни функции: Бб Дијагностика : Б7 Променливи листи на аргументи: Б8 Нелокални скокови: Б9 Сигнали : Б 1О Функции за датум и време: Б 11 Граници дефинирани со имnлементацијата: и Б3 Функции за стрингови :
Додаток В Индекс
-
Преглед на промените
291 29 1 292 294 297 299 300 300 30 1 302 302 304 305 308 309 309 31 О 311 313 317 321
Предговор
Од издавањето на "Програмски јазик е во
1978 година, светот на компју
терите дожи веа голем напредок. Големите компјутери се многу поголем и, а персоналните компјутери имаат можности еднакви на големите компјутерски
системи од пред десети на години . За тоа време и самиот С се менуваше, иако незначително, а истиот се прошири многу пошироко од своите зачетоци како
јазик на оперативниот систем
UNIX.
Сите три фактори, растечката популарност на С, промените во јазикот со те кот на годините и креирањето на компајлери од страна на тие што не учеству
ваа во неговиот дизајн, влијаеја да се укаже на потребата за попрецизна и посо времена дефиниција на јазикот, отколку таа што беше презентирана во првото издание на оваа книга . Во
1983 година, Американскиот национ ален институт за National Standard lnstitute - ANSI) формираше комитет, чија цел беше да се направи " недвосмислена и машински - независна дефини ција на јазикот С". Резултат од сето тоа е ANSI стандардот за С.
стандарди (aнг.A merican
Стандардот ги формализира конструкциите кои беа посочени, но не и опи шани во првото издание, како што се доделувањето на структурите и енумера
циите. Истиот одредува нов начин на декларирање н а функциите кој овозмо жува проверка на дефинициите во практика. Одредува, исто така, и стандардна библиотека, со широко множество на функции за изведување на влез и излез, управување со меморија, работа со стрингови и слично . Стандардот го преци
зира однесувањето на можностите кои не беа во оригиналната дефиниција и, истовремено, јасно пока жува кои аспекти од јази кот останаа и понатаму зави сни од машината.
Второто издание на "Програмски јазик е го опишува С на начин како што
е дефиниран од
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 врз е
е остварено
индиректно преку јазикот В кој во првиот
UNIX систем
на
1970 година беше напишан од Кен Томпсон за компјутерот 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ИКОТ е Намерата ние да ги при кажеме основните
елементи на јазикот низ реални програми , но без да навлегуваме во детал и,
правила и исклучоци. Во овој момент, не се обидуваме да бидеме опширни ,
ниту прецизни (иа ко е планирано примерите да се точни)
. Сакаме на најбрз
можен начин да ве доведеме на ниво на кое ќе можете сами да пишувате корис
ни програми, а за да го направиме тоа , потребно е да се концентрираме на ос новите: променливи и константи, аритметика, контрола н а текот, функции, и основи на влез и излез. Од оваа глава намерно ги изоставуваме карактеристи
ките наС кои се потребни за пишување на поголеми програми . Тука пр ипаѓаат покажувачите , структурите,
повеќето оператори од богатото множество од
оператори во С , неколку наредби за контрола на текот и стандардната библи отека.
Овој пристап си има свои недостатоци . Највпечатлив е фактот дека овде не може да се најде комплетен приказ на која било карактеристика на јази кот , а
туторијалот, каков што е краток, може да ве наведе во погрешна насока . И би дејќи прикажаните примери не ги користат сите можности наС, тие не се так а концизни и елегантни какви што би можеле да бидат. Се обидовме да ги мини мизираме таквите последици. Друг недостаток е тоа што во подоцнежн ите гла ви со причина се повторува нешто од оваа глава . Сметаме де ка повторувањето
ќе ви помогне повеќе отколку што ќе ве нерви ра .
Во секој случај, искусните програмери би требало да бидат во можност да го извлечат најважното од оваа глава, во сооднос со нивните потреби . Почетниците треба да го надополнат читањето со пишување на мали прогр а ми , слични на приложените . И двете групи можат д а го користат материјалот
од оваа глава како основа над која ќе се наддаваат подеталните информации , кои следат од почетокот на Глава
1.1
2.
Почеток
Единствен начин да се научи нов програмски ја з ик е со пишув ање на про грами во истиот. Првата програма што ќе ја напишеме, е иста за сите јазици :
Да се испишат зборовите
5
Глава
Краток вовед во јазикот С
6
1
hello, world Ова е голема пречка: За да ја совладате, треба некаде да креирате изворен текст, успешно да го компајлирате, вчитате , извршете и да пронајдете каде
му се појавува излезот. Со совладување на овие технички детали , понатаму се друго е лесно.
Во С, програмата за испишување на
"Hello, world"
изгледа вака
#include main() {
printf("hello, world\n");
Како ќе ја покренете оваа програма за виси од системот што го користите. Како специфичен пример, на
UNIX оперативен
систем можете да креирате про
грама во датотека чие име завршува со". е ", како што е hello. е и кој а поната
му ќе се компајлира со командата се
hello.c
Ако никаде не сте направиле грешка, во смисла да сте пропуштиле знак или
сте напишале некоја буква погрешно, компајлирањето ќе биде тив ко изведено и ќе се создаде извршна датотека имен у вана а. out. Ако ја подигнете а . out со отчукување на командата
a.out ќе се испечати
hello, world Сега да ја објасниме програмата . Една С програма, без оглед на големина та, се состои од функции и променливи. Една функција содржи наредби (искази) кои ги определуваат операциите што истата треба да ги изврши , а во промен ливите се сместуваат вредности , кои се користат во текот на прес метување то.
Функциите во С изгледаат слично на подрутините и функциите во Фортран или на функциите и процедурите во Паскал . Нашиот пример е функција со име
main. Се разбира , имате слобода на функциите да им дадете имиња по ваша желба, но името "main" има посебна намена - вашата програма за почну ва со извршување од почетокот на функцијата main. Ова значи дека секоја пр о грама мора да има " main" некаде во себе. Функцијата main обично ќе повикува други функции кои ќе помогнат во неј зините пресметки; тоа ќе бидат некои функции кои вие ги имате напишано,
Почеток
101
7
а други готови од веќе понудени те би блиотеки о Првата линија на програмата
#include му кажува на компајлерот , да ги вклучи информациите од стандардната влезно
1 излезна библиотека ;
оваа линија се појаву ва на почетокот на многу е изв орни
датотеки о Стандардната библиотека е опишана во Глава 7 и Додаток Б о Еден од начините на размена на податоци помеѓу функции е повикува чката функција да обезбеди листа на вредности , наречени аргументи , кон функција
та која се повикува о Малите загради после името на функцијата, ја заградуваат листата на аргументи о Во нашиот случај,
main е дефинирана како функц ија која () о
не очекува никакви аргументи , нешто што е претставена со празна листа
#include
вклучува информација за стандардната
библиотека
main ()
дефинира функцuја наречена
main
која нема аргументи
наредбите во main се омеiени со голелш заградu
printf( "hello , world\ n " ) ;
main ја повикува библuотечната функција priпtf за да ја испеч ати оваа секвенца од знаци
\n
претставува знак за нова линија
Првата програма во С
Наредбите во функциите се затворени со големи загради
{ } о Функцијата
main содржи само една наредба printf ( " hello, world \ n " ) ; Функција се повикува со нејзиното име , придружено со листа н а нејзините аргументи во мали загради о На таков начин , ја повикуваме
printf функцијата
со аргумент " hello , world \n" о printf е библиотечна функција која печати излез, во овој случај стрингот од знаци п омеѓу наводниците о Секвенца од з наци во наводници ,
се
нарекува
стринг
или
како што е
стринг константа о
ги користиме само како аргументи на Секвенцата од стрингот
\n,
"hello , За сега,
world
\ n ",
стринговите
ќе
printf и некои други функции о
во е претставува знак за нова линија, кој при
Краток вовед во јазикот е
8
Глава
1
печатење го поместува курсорот до почетокот на следната линија . Ако го изо ставите
\n
(потенцијално корисен експеримент)
, ќе уочите дека после печа
тењето на излезат не постои ниту една линија вишок . Морате да употребите \n со цел, знакот за нов ред да се при клуч и на аргументот од
printf ;
ако напише
те нешто како
printf ( "hello , world \\) ; е компајлерот ќе поднесе извештај за грешка .
printf никогаш не обезбедува знак за нов ред по автоматизам, така што не колку повикувања може да се искористат етапно, за да се изгради соодветна
излезна линија
.
Нашата прва програма можеше да се напише и ка ко
#inelude 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
/*
nечатеае на Фарнхајт-Целзиус табела за
fahr
=О ,
20 ,
. .. , 300 */
main () {
int fahr , celsius ; int lower, upper, step; lower = О ; upper = 300 ; step = 20;
/* /* /*
долна граница на тенnературната схала
горна граница
*/
големина на чехор
*/
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) {
11
Променливи и аритметички изрази
102
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) ; предизвикува печатење на вредностите на двете целобројни променливи и
ce1sius ,
со табулатор
( \ t)
fahr
помеѓу нив.
Секоја % конструкција од првиот аргумент на printf се спарува со соодв ет
ниот втор аргумент, трет аргумент итн.
;
истите мора точно да се совпаѓаат по
број и по тип, во спротивно испечатениот резултат ќе биде погрешен . Напомена ,
printf не е дел од јазикот С ; ВО самиот С не постојат дефиниции за printf е само корисна функција од стандардната библиотека на
влез или излез .
функции , кои во нормален случај се достапн и н а С програмите. Однесува њето на
printf е дефи нирано со ANSI стандардот ,
па нејзините својства би требало да бидат
исти за кој било компајлер или библиотека, кои се во согласност со стандардот. Со цел да се концентрираме на самиот С
,
не ма да зборуваме многу за влез
ни и излезни операции се до Глава 7 . Детаљното форматирање ќе го одложиме дотогаш. Ако имате потреба за внесува ње на броеви, прочитајте ја ди скусијата за функцијата
scanf во Поглавје 7 . 4 . scanf е слична
на printf , со таа ра зли ка
што наместо печатење на излез таа врши читање од влез .
Постојат н еколку проблеми во програмата за конверзија на температури . Наједноставниот е поврзан со изгледот на излезот, бидејќи броевите не се по рамнети во десно . Тоа е лесно да се разреши ; ако на секој
%d
од
printf
му
придружиме број за шири на , отпечатените броеви ќе се порамнат во дес но како што треба. На пример, за печатење на првиот број од секоја линиј а во поле со ширин а од три цифри , а на вториот со шест , можеме да на пишеме
printf( "%3d %6d\ n", fahr , ce1sius) ; при што на излез ќе се добие :
20 40 60
-17 -6 4 15
во
26
100
37
о
Постои посериозен проблем: од причина што користевме целобројна арит метика, вредностите на Целзиусовите температури не се многу прецизн и; на
пример, 0 °F изнесува -17 . 8°С , а не -17 . За да се добијат поточ н и резултати, наместо целобројна , би требало да користиме аритметика на реалн и броеви. Еве ја втората верзија :
1.2
Променливи и аритметички изрази Иinclude
13
/* принтаље на Фарнхајт-Цепзиус табепа
for fahr main()
=О,
20, ... , 300;
реапно-бројна верзија*/
{
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. о
не
резултира со нула од причина што тоа е делење на два реални броја.
Ако еден аритметички оператор има целобројни операнди, тогаш се изве дува опера ција над цели броеви . Ако аритметичкиот оператор има еден реа
лен број како операнд и еден цел број, целиот број ќе се претвори во реален за да може операцијата да се изврши како што треба. Ако напишеме
32
(fahr-32) ,
автоматски ќе биде претворен во реален број. Сепак, пишувањето реално
бројни константи со експлицитно наведена децимална точка, дури и кога имаат целобројни вредности, ја истакнува нивната реална природа за читателите. Детаљните правила за тоа кога се врши претворање на целите броеви во реални, се дадени во Поглавје
2. 3.
Засега , уочете дека наредбата :
fahr = lower ; и условот
while (fahr <= upper) исто така, функционираат на очекуваниот начин- int-oт се претвора во пред да се изврши операцијата. Спецификаторот за конверзија во
%3. Of ,
сугерира дека реалниот број (во случајов
fahr)
fioat printf ,
треба да се испечати
Краток вовед во јазикот С
14
Глава
1
заземајќи место во ширина од најмалку з знаци, без децимални точка и оста ток. Параметарот %б . l f опишува дека вториот број
(celsius)
треба да биде
отпечатен со најмалку шест знаци, со една децимала п осле децималната точ
ка.
Излезат е од следниот облик: о
- 17 . 8
20 40
-б . 7
4.4
Ширин ата и прецизноста не мора да бидат наведени: % бf означува дека
бројот ќе биде во шири на од најмалку б знаци;
%. 2f одредува два знака после %f нагласува , единст вено,
децималната точка, но ширината не е нагласена ;
дека станува збор за број со подвижна децимална точка .
%d
печати како декаден цел број
%бd
печати како декаден цел број, во ширина барем од б знаци
%f
печати како реален број
%бf
печати како реален број, во ширина барем од б знаци
%· 2f % б . 2f
печати како реален број 1 со најмногу 2 знака после децималната точка печати како реален број 1 во шири на барем од б зна ци 1 со два знака после децималната точка
Меѓу другото,
printf, исто така, ги преnознава
и следниве спецификатори: % о за
октална претстава, % х за хексадекадна, %е за знак, %ѕ за стринг и %% за самиот %.
Вежба
1-3 .
Модифицирајте ја програмата за температурна конв ерзија да пе
чати и заглавје над табелата Вежба
1-4.
Напишете програма за печатење табела од Целзиусова во
Фаренхајтова скала .
1.3 Иска3от for Постојат мноштво различни начини да се напише програма за
о кретн а за
дача. Да се обидеме да направиме варијација на конверторот на тем ератури .
#include /* 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-5 .
1
Да се модифицира програмата за претворање на температури,
така што ќе ја печати табелата во обрате н редослед 1 т . е. од 300 степен и кон о.
1.4 Симболички
константи
За последен пат да се навратиме на програмата за температурна конверзија,
пред да ја напуштиме засекогаш. Лоша практика е да се употребуваат " вол шебни броеви " како
300
и
20
во една програма; тие нудат малку информација
за некој кој можеби подоцна ќе ја чита програмата и тешки се за систематска
промена. Еден начин за справување со волшебни броеви, е да им се зададат имиња кои ќе имаат смисла. Една
tdefine линија дефинира симболичко име или
симболичка константа да биде една конкретна низа од знаци
idefine име текст за замена Понатаму , секоја појава н а име (не во наводници и не како дел од друго име)
ќе биде заменета со соодветната, текст з а замена. Име има ист облик како и имињата на променливите: секвенца од букви или цифри што започнува со
буква. Текстот за замена може да биде каква било низа од знаци; не е огра ничен само на броеви .
tinclude idefine LOWER О 1* idefine UPPER 300 /* tdefine ЅТЕР 20 /* /* печа'l'еље ma.in ()
долна IОраница на 'l'aбena'l'a горна IОраница
*/
*/
големина на чехор
*/
на Фаренхај'l'-Целзиус 'l'абела
*/
{
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
/*
хопирај влез на излез ,
тain
1-ва верзија*/
()
{
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 е цел број дефиниран во , но конкретната број на вредност не е важна се додека не е идентична со вредноста на некоја ehar вредност . Користејќи симболичка константа, ние сме сигурни дека ништо во програмата нема да зависи од некоја специфична бројна вредност.
Програмата за копирање би била напишана пократко од некој искусен С програмер. Во С, секое доделување на вредност, како е
= getchar ()
е израз и има некоја вредност. Тоа е вредноста на левата страна после доделу
вањето. Ова значи дека доделувањето може да се појави како дел од по голем из раз. Ако доделувањето на еден знак е се стави внатре во условниот дел на еден
while циклус,
програмата за копирање може да се напише на следниов начин:
1.5
19
Влез и излез на знаци
#inelude хоnираље влез на излез,
/*
2-ра верзија
*/
main() {
int
е;
while ((е= qetehar()) != EOF) putehar(e) ;
Циклусот
while
зема еден знак, го доделува на е и потоа проверува дали зна
кот е знак за крај на датотеката. Ако не е, се извршува телото на
while циклу while се повторува. Кога конечно ќе се стигне до крајот на влезот, завршува while циклусот , а со тоа и програмата main . сот, кој го печати знакот. Потоа
Оваа верзија се концентрира на влезот- сега има само едно повикување до
qetehar -
а програмата е пократка . Резултантната програма е покомпактна
и, кога еднаш ќе навикнете на ваков облик на пишување на кодот, истиот ќе
ви биде полесен за читање. Со овој стил на пишување ќе се среќавате често. (Сепак, во пишувањето на ваков начин може да се претера , што во чести си туации доведува до замрсен код, нешто што ние ќе се обидеме да го избегнеме овде.)
Заградите околу изразот за доделување вредност , во рамките на делот за
услов, се потребни . Приоритетот на операторот ! =е поголем од оној на опе раторот =, што значи дека во отсуство на загради првин ќе се изврши рела цио ната проверка е
! =. Така , изразот
= qetehar()
!= EOF
е еквивалентен н а
е
= (qetehar()
!= EOF)
Ова доведува до непосакуван резултат кој го поставува е на вредности о или во зависност од тоа дали
qetehar (Повеќе за ова во Глава 2.)
1,
има вратено вредност за крај на датотека.
Вежба
1- б. Да се провери дали изразот qetehar ()
Вежба
1-7.
! = EOF враќа 1 или о.
Да се напише програма која ја печат и вреднос та на
EOF.
20
Краток вовед во јазикот С
1.5.2 Броење
Глава
1
на знаци
Следната програма брои знаци; слична е на програмата за копирање
#include /* броеае main()
знаци на влез ;
1-ва верзија
*/
{
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 /* броеае main()
знаци на влез ;
2-ра верзија
*/
{
double nc ; for (nc =
О;
gechar() != EOF ; ++nc)
printf( "%. 0f\ n ", nc) ;
1.5
Влез и излез на знаци
printf
користи
%f
и за
float
и за
double; %.0f
21
го отстранува печатењето на
децималната точка и децималниот дел после неа, кој е нула .
Телото на овој for циклус е празно, бидејќи целата работа се завршува во деловите за проверка и инкрементација. Но граматичките правила наС бараат
наредбата for да има тело. Изолираната точка-запирка , наречена празен ис каз, е таму за да го задоволи тоа барање. Ја ставивме во посебна линија за да ја направиме поуочлива.
Пред да ја напуштиме програмата за броење на знаци, забележете дека ако влезот не содржи знаци, проверките кај
while и for паѓаат на првиот повик
до getchar и програмата резултира со нула, што е и точниот одговор. Ова е мошне важно . Една од убавите работи кај while и for е тоа што проверките се прават на врвот на циклусот ,
пред да се премине со извршување на телото .
Ако нема што да се работи, тогаш и не се работи, дури и ако тоа подразбира да не се помине низ телото на циклусот. Програм ите треба интелигентно да ги разрешуваат случаите каде податочниот влез има должина н ула. Ци клусите
while и for обезбедуваат добра работа на програмите и при тие граничн и ус лови.
1.5.3 Броење на линии Следната програма врши броење на линии на податочен влез. Како што спомнавме погоре , стандардната библиотека обезбедува дека влез ни от текс туален поток ќе се појави како секвенца од линии, од кои секоја ќе завршува со знак за нова линија. Поради тоа, броењето линии се сведува на броење знакови за нова линија.
jinelude
/*
броеае на пинии во податочен влез
*/
main() {
int
е,
nl;
=
nl О; while ((е= getchar()) if (е = '\n') ++nl; printf( "%d\n", nl);
!= EOF)
Телото на while циклусот сега се состои од една if наредба, која го контролира инкрементот ++nl. if наредбата врши проверка на условот во заградите, и ако е вистинит, се извршува наредба што следи (или група наредби во големи загради) Повторно ги вовлековме наредбите со цел да покажеме кој кого контролира.
.
Краток вовед во јазикот С
22
Глава
1
Изразот двојно еднакво,=, е С нотација за "еднакво на" (исто со единично
=
кај Паскал и
. EQ.
кај Фортран)
.
Овој симбол се користи за да се разграничи
релацијата за еднаквост од единичното =кое кај С се користи како оператор за доделување. Едно предупредување: новите програмери во С обично пишуваат
=
а мислат на
=.
Како што ќе видиме во Глава 2, резултатот од таквата постап
ка, обично е легален израз, та ка што компајлерот не јавува никакво предупре дување .
Еден знак напишан во апострофи претставува целобројна вредност еднак ва на број ната вредност што знакот ја има во машинското м ножество на зна ци. Истиот се нарекува знаковна константа, иако во ствар ност тоа е само
алтернативен начин на запишување на мал цел број. Така, на пример, ' А '
,
е знаковна константа ; во ASCII мн ожеството, нејзината вредност е 65 и тоа
е внатрешна претстава на знакот А. Се разбира , ' А' треба да се преферира во однос на
6 5:
неговото значење е очигледно , и е независно од конкретното
множество знаци.
Излезните секве нци кај стр и нговите конс тант и, исто така, се легални и кај з наковните константи, па
'\n'
оз н ачува вред ност за знак за нова линија, кој е
10 во ASCII. Треба да се забележи дека '\n ' претставува еден знак и во изра зите се третира како целобројна вредност; од друга страна,
"\n"
претставува
стринг константа која содржи само еден знак. Прашањето за разликите помеѓу стри нговите и знаците ќе биде дискутирано понатаму во Глава 2 . Вежба
1-8 .
Да се напише програма која ќе брои знаци за празно наместо
(бланко- знаци, табови и знаци за нова линија). Вежба
1-9.
Да се напише програма која ќе го копира н ејзи ни от влез на нејзи
ниот излез , притоа, заменувајќи ги стринговите од два или повеќе бланко-зна ци со еден бланко-знак. Вежба
1-10.
Да се напише програма која ќе го копира сопствениот влез на
сопствениот излез, притоа, заменувајќи го секој таб со шење знак наназад (анг.
backspace) да
се замени со
\t ,
секој знак за бри
\b и секој контра низ со\\ .
Табовите и з наците за бришење н аназад ,тоа ги прави видливи на недвосмис ле н н ачин .
1.5.4 Броење на 3борови Оваа, четврта во нашата серија корисни програми , брои линии , зборови , и знаци, со неформална дефиниција дека, збор претставува која било секве нца од знаци која не содржи празно место, таб, или знак за нов ред. Ова е соголе ната верз ија на
UNIX програмата wc.
Влез и излез на знац и
1.5
23
#inelude
/* /*
#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
Краток вовед во јазикот С
nl
Глава
1
= (nw = (ne =0)) ;
Операторот
1 1 означува логичко ИЛИ,
па линијата
if (е='' 11 е= '\n' 11 е= '\t') се интерпретира како "ако е е бланко или е е знак за нов ред или е е таб " . (Потсетете се дека
\t
е видлива претстава на знакот таб . ) Постои и соодве
тен оператор && за л оги чко И ; неговиот приоритет е поголем од (веднаш пред) приоритетот на
11 •
Изразите поврзани со && и
11 се
пресметуваат од лево кон
десно и се гарантира дека пресметката ќе застане во моментот кога вистини тоста или неви стинитоста на изразот ќе станат познати. Ако е е бланко-знак , тогаш нема потреба да се тестира дали е знак за нова линија или таб , така што тие проверки не се прават. Овде тоа и не изгледа важно , но е значајно во по
комплексни ситуации, како што ќе видиме набргу. Примеров , исто така, го прикажува
else, кој специфицира алтернативна ак
ција доколку е невистинит условот од if . Општиот облик е (израз)
if
else
наредба 1 наредба 2
Се извршува една и само една од двете наредби асоцирани со еден if-
else . Ако израз е вистинит израз, тогаш се извршува наредба 1 ; во спротивно се, извршува наредба2• Секоја наредба може да биде единична наредба или по
веќе наредби оградени во големи загради. Кај програмата за броење зборо
ви, наредбата која следи после
else , е if кој ги контролира двете наредби во
заградите.
Вежба
1-11 .
Како вие би ја тестирале програмата за броење на зборови? Каков
податочен влез е најпогоден да открие грешки, доколку има такви во програма та?
Вежба 1-12 . Напишете програма која го печати нејзиниот влез, еден збор по ли нија о
1.6 Низи Да напишеме програма која ќе го брои појавувањето на секоја цифра , на невидливите знаци
( празно
место, таб, нов ред) и на сите останати знаци о
Ова е вештачко, но ни овозможува да илустрираме неколку аспекти на С во рамките на една програма
.
1.6
Низи
25
Постојат дванаесет категории на влезни податоци, така што во случајов е
згодно да се користи низа која ќе ги чува бројот на појавувања на секоја цифра , отколку десет посебни променливи за истата намена. Еве една верзија на про грамата:
#inelude
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' ++nwhite; else ++nother; printf ("diqits ="); for (i = О ; i < 10; ++i) printf(" %d", ndigit[i]); printf(" , white ѕраее = %d, other nwhite, nother);
11
е
'\t')
%d\n",
Излезат од програма извршена врз нејзиниот текст е следниов
diqits = 9 3
О О О О О О О
1, white
ѕраее
= 123, other = 345
Декларацијата
intndigit[10]; ја декларира
ndiqi t како низа од десет цели броеви. Индексите на н изите ndiqi t [о] , ndigit[1], ... , ndiqit[9]. Тоа се рефлектира и кај for циклусите ко и ја во С секогаш започнуваат од нула , п а конкретните елементи се иницијализираат и печатат низата .
Оваа конкретна програма се заснова врз карактеристиките на знаковната репрезентација на броевите. На пример , условот
26
Краток вовед во јазикот С
if
(е>=
&&е<=
'0'
Глава
1
'9')
определува дали знакот е е цифра. Ако е цифра, бројната вредност на таа цифра е е-
'0'
Ова функционира само ако '0', '1', ... ,
'9' имаат последователни растеч .
ки вредности. За среќа, тој услов го исполнуваат сите знаковни множества
По дефиниција, знаците се само мали цели броеви, па во аритметичките изрази,
ehar
променливите се идентични со
int
променливите. Ова е при
родно и погодно; на пример изразот е- 'О' е целоброен израз со вредност помеѓу о и на низата
9, што за виси од вредноста на е, а со тоа е и погоден како индекс ndigit.
Одлуката за тоа дали еден знак е цифра, невидлив знак, или нешто друго се изведува преку секвенцата
if
'0' && е<= '9') ++ndigit[e-'0 ' ] ; elseif (е=' ' 11 е== '\n' 11 ++nwhite; else ++nother; (е>=
е==
'\t')
етруктурата
if
(услов )
1
наредба 1
else if
(услов )
наредба 2
2
else наредба. се јавува доста често во програмите како начин да се изрази повеќенасочна
одлука. Условите се евалуираат по редослед, почнувајќи од врват се доде ка не ~е најде услов кој задоволува; притоа се извршува соодветната наредба и цемта конструкција завршува. (Секоја наредба може да биде составена од повеќе поднаредби затворени во големи загради) Ако не е исполнет ниту еден од условите, се извршува (доколку е присутна) наредбата која следи по по
следниот else. Во случај да не се наведени последниот else и наредба, како во програмата за броење на зборови, тогаш не се прави ништо. Може да има произволен број на групи
Функци и
1.7 else if
27
(услов)
наредба помеѓу почетниот
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 int power(int m, int n); /* тест main ()
за функцијата
power */
{
int i ; for (i=O; i
/* power: степенуваае на intpower(intbase, intn)
осно•ата на стеnен
n ; n>=O*/
{
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 ma.in.
од
power
на никој начин не е поврзана со истои
Обична, ќе го користиме терминот параметарl за променливите спомена ти во листата на функциите при нивната дефиниција 1 а аргумент за вредноста која се користи при повик на функцијата.
Понекогаш се користат термините формален аргумент и актуелен аргу мент за да се направи истото разграничување .
Вредноста која ја пресметува power се препраќа на main преку наредбата return. После return може да следи каков било израз:
return израз; Една функција не мора да врати вредност; наредбата
return напишана без
никаква вредност по неа, врши контрола 1 но не враќа корисна вредност кон својот повикувач. Тоа е случај и при"паѓање од работ" на функција, со доаѓање до завршната голема затворена заграда. И повикувачката функција може да ја и гнорира вредноста вратена од друга функција.
Можеби забележавте дека постои return наредба на крајот од main . Бидејќи
main е функција како и сите други функции, може да врати вредност
кон оној што ја има повикана и истата има ефект во околината во која таа била повикана и извршена. Обична, повратна вредност нула означува нормален тек на извршување;
ненулта вредност сигнализира дека нешто не е во ред.
Во интерес на едноставноста, досега пропуштавме да ја пишуваме наредбата
return
во нашите
main
функции, но отсега натаму ќе ја вклучиме и неа 1 како
потсетување дека програмите треба да враќаат статус кон нивната повикувачка околина.
Декларацијата
intpower(intbase, intn);
30
Краток во вед во јаз и кот е
пред самиот main 1 означува дека
Глава
1
int аргу npomomun на функција та 1 мора да се совпаѓа со дефиницијата и употребите на power. Погрешно е мента и која враќа
int.
power
е функција која очекува два
Оваа декларација 1 наречена
доколку дефиницијата на функцијата или некој нејзи н повик не се совпаѓаат со нејзиниот прототип
.
Имињата на параметрите не мора да се совпаѓаат. Навистина
функциски прототип имињата на параметрите се опционал ни
1
1
за еде н
па кај прототи
пот можевме да напиш еме и:
intpower (int 1 int) ; Уредно одбрани имиња ја прават програмата документира на, па ќе ги корис тиме често.
Историска забелешка: знаеме дека најголемата разлика помеѓу програма напишана во
ANSI С
и програма напиша на во постарите верзии наС 1 е во начи
нот на кој се претставени и дефинирани функциите. Во оригиналната дефини ција на е 1 функцијата
power ќе беше напишана
/* power : стеnенуваље на основата /* (верзија во стар-стил) */ power(base n) int base 1 n ;
на овој начин:
на стеnен
n ; n>=O* /
1
int i ,
р;
= 1; for (i = 1 ; i <= n ; ++i) р = р * base ; return р ;
р
Параметрите се именувани во рамка на заградите 1 а нивните типови се декла рирани пред отворањето на големите загради; недеклариран и те параме тр и се
земаат за тип
int.
(Телото на функцијата е исто како и во претходн иот случај)
Декларирањето на
power
на почетокот од програмата ќе изгледаше вака:
int power () ; Не беше дозволена употреба на листа на параметри, така што компајлерот не можеше на време да провери дали повикот кон
power е правилен. Навистина, int вредност , целата де
бидејќи во општ случај е предвидено power да враќа кларација може да биде изоставена .
Новата синтакса на функциски прототипови , им овозможува на ком пајлери те многу полесно откривање на грешките поврзани со типовите н а аргуме н тите
и нивниот број. Стариот тип на декларирање се уште е во упот реба во
ANSI
Аргументи
1.8
-
повикување по вредност
31
е, барем во транзитниот период , но наша препорака е да ја користите новата форма, доколку имате компајлер што ја поддржува. Вежба
1-15 .
Напишете ја одново програмата за конверзија на температурите
од Поглавје 1. 2, така што користи функција за конверзијата.
1.8 Аргументи -
повикување по вредност
Едно својство на функциите во е, може да и м изгледа чудно на програмери те кои користат други јазици, посебно тие што користат Фортран. Во е , сите
функциски аргументи се предаваат по "вредн ост" . Тоа значи дека повиканата функција ги предава вредностите од аргументите на привре мени променливи, а не на оригиналните. Тоа води до некои особини, различни од тие што се ка
рактеристични за " повикување преку референца ", кај јазиците како Фортран или
var параметрите во Паскал, каде повиканата ру тина пристапува н а ориги
налниот аргумент, а не на некоја локална копија. Основна разлика е во фактот дека во е
, повикан а та функција не може ди
ректно да ја измени променливата од функцијата повикувач; таа може само да ја измени својата привремена копија .
Повикување преку вредност е предност, а не недостаток . Тоа обично води кон покомпактни програми со помал број на променливи , бидејќи параметри те може да се третираат како погодно иницијализирани локал ни променливи во повиканата функција. На при мер, еве една верзија на power кој а ја користи о ваа карактеристика:
/* power: стеnенуваље на основата int power(int base, int n) int for
на стеnен
n; n>=O ;
верзија
2 */
р; (р
р
= 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
Низи од знаци
#include #define МAXLINE 1000 /*
махсимапна должина на влезот
33
*/
int getline(char line[] , int maxline) ; void copy(char to[], char from[]) ; /* печати main()
најдопrа влезна линија
*/
{
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С'МВуваме void copy(char to[], char from[]) {
int i ; i
= О;
while ( (to [i] ++i ;
from[i]) != ' \ 0')
дека uto" е Д0ВQ11И0 Г0118«:>*/
Краток вовед во јазикот е
34
Функциите
getline
Глава
и сору се декларирани на почетокот на програмата , за
која претпоставуваме дека е сместена во една датотека
main
и
getline
1
.
комуницираат преку пар аргументи и вредностите кои ги
враќаат. Во функцијата
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
Не постои начин , за оној кој ја користи функцијата знае за тоа колкава е должината на влезната линија , п а
да прави проверка за пречекорување (анr.
overflow) .
getline , однапред да getline мора внатрешно
Од друга страна, оној кој ја
користи сору веќе знае (или може да знае) која е должината на стринговите , па
затоа одлучивме во оваа функција да не пишуваме код за проверка од грешки. Вежба
1-16
Преправете ја главната рутина од програмата за печатење на нај
долга линија, така што истата правилно ќе ја печати должината на произволно долги влезни линии, и колку што е можно повеќе од текстот. Вежба
1-17 Напишете програма за печатење на сите влезни линии подолги од
во знаци.
Вежба
1-18 Напишете програма за отстранување на непотребните бланко
знаци и табулаторите од влезните линии , а која, исто така , ќе ги отстранува и целосно п разните линии.
Вежба
1-19 Напишете функција reverse (ѕ) која го превртува стрингот ѕ.
Искористете ја да напишете програма која ги преврту ва влезните линии една по една .
1.1 О Надвореwни
променливи и деnокруг
Променливите во
или лdкални за
main, како што се line, longest , итн., се при ват ни main . Бидејќи се декларирани внатре во main , ниту една друга
функција нема директен пристап до нив . Истото важи и за променливите во
другите функции; на пример, променливата
i во getline нема никаква врска
со променливата i од функцијата сору . Секоја локална променлива се креи ра само при повик на една функција и исчезнува во моментот на излегување
од таа функција. Тоа е причината поради која таквите променливи се познати како автоматски променливи , во согласност со терминологијата на дру гите јазици. Затоа, понатаму ќе го користиме терминот автоматски за референци рање на тие локални променливи (Во Глава 4 . класа
static ,
ќе зборуваме за мемориската
со која локалните променливи ги задржуваат своите вредности
помеѓу функциските повици
.)
Бидејќи автоматските променливи доаѓаат и си одат со секое пови кува ње на една функција , тие не ја задржуваат вредноста од еден до друг повик , и мора експлицитно да се постават на некоја вредност при секое влегување во функцијата
. Во случај да не се направи тоа, ќе содржат случајни вредности тн.
ѓубре. Како алтернатива за автоматските променливи , можно е да се дефинираат променливи кои се надворешни (анг.
external) за сите функции , т . е, промен
ливи до кои може да се пристап и по име од сите функции. (Овој механизам е
Краток вовед во јазикот С
36
Глава
1
сличен на фортрановите СОМК>N променливи или на паскаловите променли ви декларирани во надворешниот блок) Бидејќи до надворешните променливи може да се пристапни глобално, истите може да се користат наместо листи од
аргументи , за комуникација со податоци помеѓу функциите. Уште повеќе , би дејќи надворешните променливи се присутни постојано , т . е. не се креираат и не исчезнуваат со повикување на функциите и излегување од нив, тие ги задр жуваат своите вредност и после излегување од функциите кои нив ги поставиле. Една надворешна променлива мора да биде дефинирана, точно еднаш, надвор од доменот на сите функции ; со што за неа се резервира постој ана ме
мориска локација. Променливата мора, исто така, да биде декларирана внатре во секоја функција која сака да пристап и до неа; тоа го поставува типот на про
менливата. Декларацијата може да биде преку експлицитна extern наредба или имплицитна, разбирлива од контекстот. Со цел , конкретно да дискути раме , да ја презапишеме програмата за наоѓање најдолга линија на влез, но
овојпат line , longest и max да бидат декларирани како надворешни промен ливи. Тоа бара промена во повику вањата , декларациите , а и телата на сите три функции .
#include #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 О ;
линија
*/
Надворешни nроменливи и делокруг
1.1 о /* getline : епецијапизираиа int getline(void)
верзија
37
*/
int е , i ; extern ehar line[] ; for (i = О; i < МAXLINE - 1 (e=getehar)) != EOF line[i] = е ; if (е '\n' ) { line[i] = е; ++i;
"
"
е
!= '\n'; ++i)
line[i] = '\0'; return i;
/* еору: епецијапизирана void eopy(void)
верзија
*/
{
int i ; extern ehar line[], longest[] ; i = О; while ((longest[i] ++i ;
line [ 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 е карактеристичен за имињата на заглавјата о Функциите од стандардната библиотека, на пример, се декларирани во заглавја како
о За тоа ќе стане збор повеќе во Глава 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 помеѓу О
и
255,
додека онаа од тип
машини со двоен комплемент)
.
Дали
43
signed char помеѓу -128 и 127 (кај char вредностите се означени или не
означени зависи од машината, но знаците кои може да се печатат секогаш се позитивни.
Типот 1ong doub1e специфицира реален број со зголемена прецизност . Како и кај целобројните објекти, големините на реалните објекти се дефини рани со имплементацијата;
float, doub1e,
и
1ong doub1e можат да
претстават
една, две или три различни големини.
Стандардните заглавја <1imi ts. h> и содржат симболички кон станти за сите овие големини, заедно со другите својства на машината и ком
пајлерот. За нив се зборува во Додатокот Б. Вежба 2-1 . Напишете програма која ќе го определи опсегот на char, shorт , int и 1ong променливите, во двете варијанти, означени и неозначени, пре ку печатење на соодветните вредности од стандардните заглавја и преку директ
но пресметување. Потешко доколку нив ги пресметувате: определете го опсе
гот на различните типови за реални броеви.
2.3 Константи Целобројна константа како ва со завршно 1 (ел) или
L,
1234,
како во
е int вредност. 1ong константа се пишу
123456789L;
целобројна константа која
е преголема за да биде сместена во int, исто така, ќе биде земена за 1ong. Неозначените константи се пишуваат со завршно u или означува
u,
Реално, бројните константи содржат децимална точка
нент
( 1е-2)
(123. 4)
или експо
или обете; нивниот тип е doub1e, освен во случаите кога имаат
некој суфикс. Суфиксите ат
а суфиксот u1 ил и UL
unsigned 1ong.
f
или F означуваат float константа; 1 или
L
означува
1ong doub1e. Вредноста на еден цел број наместо во декадна, може да биде специфици
рана во октална или хексадекадна нотација. Водечка о (нула) во некоја це
лобројна константа означува октална претстава на бројот; водечки Ох или ох означува хексадекадна претстава.
но да се запише како
037,
хексадекадните константи можат да завршуваат
или со
u
вредност
31 може октал Ox1f или OxlF. Окталните и со L, и во тој случај се 1ong ,
На пример, декадниот запис
или хексадекадно како
кое ги прави 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 \f \n \r
бришење место наназад НОВ ЛИСТ
нова линија нов
ред
(анг.
carriage
\\ \? \' \"
контран и з nрашални к
аnостроф наводни к
\ооо
октален з а n и с
\xhh
хексадекаден заn и с
return)
\t \v
хоризонтален таб вертикален таб
Знаковната константа
'\0' го nретставува знакот со вред ност нул а , нултиот '\0' за да се поте н ци р а неговата природа во некој израз, но бројната вредност му е само о . Константен израз е израз кој содржи само константи . Та кв ите и зрази мо
(анг.
null)
знак. Често, наместо о се nишува
жат да бидат евалуирани за време на ком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
/* strlen: врати ја должината int strlen(char ѕ[]) int i; while (ѕ [i] ++i; return i; strlen
Глава
на ѕ
2
*/
!= '\0')
и другите функции за обработка на стрингови се декларирани во стан
дардното заглавје
.
Бидете внимателни при разграничувањето на знаковна константа со стринг константа кој содржи само еден знак: 'х' не е исто што и "х " . Првото е цел
број, ја содржи бројната вредност на буквата х во знаковното множество на компјутерот. Другото, пак, е низа од знаци која содржи еден знак (буквата х) и
'\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 int int char c har
lower ; upper ; step ; е;
line[1000] ;
Овој начин одзема повеќе простор , но е посоодветен за додавање н а коментар за секоја декларација или за понатамошни пр омени. Една променл ива може да се иницијализира во нејзината декларација. Ако името е проследено со знак за еднакво и не кој израз , изразот служи како ини цијализатор :
ehar int int float
еѕс= ' \\ ' ;
i
=О ;
limi t ерѕ
=МAXLINE + 1 ;
= 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) ; Операторот % н е може да се примени врз ку ва њето на децималите за
1и
float
или
double . Насоката н а отсе %, при негативни опе
знакот на резултатот за
ранди зависат од машината , и сто како и во акциите кои се изведу ваат во слу
чаите кога има пречекорува ње на горна или долна граница . (анг.
overflow
и
uпderflow) Бинарните + и - имаат еднаков приоритет , кој е по низок од приоритетот на *, 1, и %, кои, пак, се со понизок приоритет од унарните + и -. Аритметичките оп е ратори се асоцијативни од лево кон десно .
Табелата 2 . 1 на крајот од оваа глава, ги сумира приоритетот и асоцијатив н оста н а сите оператори во е
.
Релациони и логички оператори
2.6
49
2.6 Реnациони и nоrички оператори Релациони оператори се
>
<
>=
<=
Сите се со еднаков приоритет . Единствено операторите за проверка на еднак вост =-=-и
! =се со
понизок приоритет од нив .
Релационите оператори израз како
имаат понизок приоритет од аритметичките , па
i < 1im-1 се пресметува
како
i < (1im-1) , што е и за очекување. 1 1• Изразите поврзани со &&
Поинтересни се логичките оператори && и или
11 се евалуираат од лево
кон дес но , а евалуацијата престанува во оној мо
мент кога ќе стане позната вистинитоста на изра зот. Повеќето С програми се
потпираат на овие правила . За пример , даден е циклусот од влезната функција
get1ine for
која ја напишавме во Глава
1:
(i=O ; i < 1im-1 && (e=getehar ()) ! = '\n' s[i]
&& е
! = EOF ; ++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 ци
клус, без користење на && или
2.7 Конверзија
1 1.
на типови
Кога еден оператор има операнди од различни типови, тие се претвораат
во заеднички тип во согласност со мал број на правила. Општо земено, един ствените автоматски конверзии се случуваат кога операнд од " потесно мно
жество" се претопува во операнд од " пошироко множество", без притоа да се изгуби инфо рмација , како што е, на п ример, претворањето на цел број во
+ i. Изразите кои немаат смисла, како ко float за индексирање , не се дозволени. Изра зи те во кои може да
реален број кај изрази од облик f ристење на
дојде до загуба н а информација, како при доделување на подолг интегрален тип во пократок , или при доделување на реален тип кон
int,
може да повле
чат предупредување од компајлерот, но, не се третираат како нелегални.
вредностите се само мали цели броеви,
na
char
слободно може да се користат во
аритметичките изрази . Ова овозможува значителна флексибилност кај одре дени облици на знаковни трансформации. Пример за тоа е оваа едноставна имплементација на функцијата
atoi, која претвора ст ринг од цифри во негови
от нум ер ички еквивалент
/* atoi : хонверзија int atoi(char ѕ[])
на
ѕ во цел број
*/
int i , n; n
=
О;
for (i = О ; s[i] >= '0' n 10 * n + (ѕ [i] return n ;
=
s[i] <= '9 ' ; ++i) '0') ;
&&
Како што збо рувавме во Глава 1, изразот
Конверзија на типов и
2.7
51
s[i]- '0' ја дава бројната вредност на знакот складиран во ѕ [i] , но, само затоа што вредностите 'о'
, '1' ,
итн. формираат континуирана секвенца во растечки
редослед.
Друг пример за конверзија на
char во int е функцијата 1ower, која за зна
ковното м ножество ASCII, тран сформира голема во мала бу ква . Ако зн а кот н е
е голема буква,
1ower го враќа непроменет .
/ * lower: хонве/зија int lower(int е} if
(е>=
' А'
return
е
return
е;
на е во мапа буква ; важи само за
&& е <=
+
'а'
-
ASCII */
'Z'} 'А' ;
е1ѕе
Овој код, единствено, функциони ра за ASCII , бидејќи кај ова множество соод ветните знаци за голема и мала буква се наоѓаат на фиксно растојание од по четокот до крајот на азбуката , а помеѓу нив нема никакви други знаци осв ен
букви. Но, тоа не е вистина и за знаковното множество EBCDIC, така што оваа функција во тој случај е бесмислена . Стандардното заглавје
, опишан во Додаток Б , дефинира фа
милија на функции кои обезбедуваат тестови и кон верзии, независни од зна ковното множество. На пример , функцијата функцијата е
>=
tolower е портабилна замена за lower која претходн о ја разгледавме. Слично на тоа , проверката
'О' & & е
<= ' 9'
може да се замени со
isdigit(c} Отсега натаму ќе ги користиме функциите дефинирани во
. h>. char во int .
char , се означени (анг.
sigпed) или неозначени (анг. uпsigned) величини. Дали конверзијата на еден
char
во
int,
некогаш може да ре зул тира со негати в на вредност? Одговорот
варира од машина до машина, рефлектирајќи ги разликите во архите ктурата. Кај некои машини
char чиј најлев бит е 1 ќе б иде претворен во негативен цел . На други, пак, знакот се промовира во цел број
број ("знаковна екстензија"}
само со додавање на нулти битови од левата страна , што с екогаш резултира со позитивна вредност.
Основната дефиниција на С гарантира дека ниту еден знак од стандард но-
Типови, оператори и изрази
52
Глава
2
то множество знаци за печатење кај машината , никогаш нема да биде негати вен, така што во изразите овие знаци секогаш ќе имаат позитивна вредност . Сепак, произволни секвенци на битови складирани во знаковни променливи, може да се интерпретираат како негативни на едни машини, а позитивни на
други . Заради преносливост (портабилност) даток треба да се чува во
signed или
ehar променлива,
, во случаи кога незнаковен по
секогаш треба да се специфицира
unsignedдeклapaциja на таа променлива.
Релациски изрази како i > ј, и логички изрази поврзани со && и 11, доколку се вистинити, седефинирани да имаат вредност 1, а О во спротивен случај. Така доделувањето
d = е >=
'О' & & е
го поставува
d
за
<= '9'
1 во
случај кога е е цифра и о кога не е цифра. Сепак , функ
циите како isdigit за "вистина" можат да вратат каква било ненулта вредност. Во делот за услов на
if, while, for ,
итн . " вистината " , исто така, е означена
од која било ненулта вредност , па така ова не прави разлика Имплицитните аритметички кон верзии функционираат според очекувања та . Општо , кај оператор кој има два операнда од различен тип (бинарен опе ратор), како
+ или -,
пред почетокот на операцијата, " понискиот" тип нагорно
се претопува во "повисок" тип. Резултатот е од " повисокиот" тип. Делот б од Додаток А точно ги прецизира правилата за конверзија. Во случај кога нема
unsigned операнди,
следното множество пра вила би било задоволително:
- Ако кој било од операндите е long double , претвори го другиот во long double. - Во спротивно, ако кој било од операндите е double, претвори го другиот во double. -Во спротивно , ако кој било од операндите е fioat , претвори го другиот BOfioat. - Во спротивно, претвори ehar и short int, во int. -Потоа, ако кој било од операндите е long , претвори го и другиот во long. Забележете дека fioat променливите double по автоматизам; тоа е промена во
во еден израз не се претвораат во однос на оригиналната дефиниција.
Општо земено, математичките функции како тие во користат двој на прецизност. Главната причина за кори с тење на
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отреба на унарниот
cast) .
onepamop за претопување (анг.
Во конструкцијата
(име на т и п)
израз
израз се претвора во именуваниот тип во согласност со погорните nравила за
конверзија . Вистинското значење на оnераторот за претоnување е еквивалент но со доделувањето на израз кон nроменлива од специфичен тип , која потоа
ќе се употребува наместо целата конструкција. На nример , библиотечната рутина
sqrt
очекува аргументи од тиn
double, na
во случај на аргумент од
друг тип, нејзиниот резултат не може да се nредвиди .
.)
Така , ако
sqrt ( (double) n)
n
( sqrt е декларирана
е цел број, може да уnотребиме
во
Глава
Типови , оператори и изрази
54
со цел , да извршиме претворање на
n
во
doub1e
2
вредност, пред истиот д а се
принесе како параметар на sqrt . Забележете дека операторот за претопување продуцира вредност на
n
од соодветниот тип; врз самиот
n
не се вршат ни
какви проме ни . Операторот за претопув ање има ист приоритет како и другите унарни о ператори, како што е сумира но во табелата н а крајот од оваа глава . Ако аргументите се декларирани во функциски прототип , што е и правилно, декларацијата п редизвикува автоматско претвора ње н а сите аргументи во мо ментот кога ќе биде по викана функциј ата. Така , ако е даден прототип на sqrt
d oub1e sqrt (doub1e) повикот
root2 = sqrt (2) автоматски го претвора целиот број 2 во doub1e вредноста 2. о без потреба за екс плицитно претопување.
Стандардната библиотека вклучува преносна имплементација на генерато р на псевдослучај ни броеви и функција за иницијали зација на неговиот зачеток
(а нг.
seed); следниот код илустрира употреба на операторот за претопување:
unsigned 1ong int next
/ * rand : врах а int rand(void)
= 1;
nсевдоспучаен цел број
во оnсег од О
.. 32767 */
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 (ѕ) , која претвора стри нг од хексадекад ни цифри (вкл уч ително и опционалните Ох или ОХ) во негов целоброен екви валент . Дозволени цифри се од О до
2.8 Оператори 3а
9, од а до f, и
од А до
F.
инкрементирање и декрементирање
С обезбедува два невооби чаени оператора за инкрементирање и декре ментирање на променливите . Операторот за инкрементирање
++ додава
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и брише сите е од ѕ
void squeeze (char
ѕ
[] ,
*1
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] ;
!=е)
ѕ[ј] ј++ ;
Друг пример на слична конструкција доаѓа од функцијата напишавме во Глава
if
(е=
qetline која ја
1 . Во неа може да извршиме измена
'\n ' )
s[i] ++i ;
=е ;
со покомпактниот облик
if
(е= ѕ
'\n ' ) [i++] =е ;
Како трет пример , да ја разгледаме стандардната функција која го надоврзува стрингот
t
на крајот од стрингот ѕ.
streat (ѕ , t) , streat претпоставува
дека ѕ има резервирано доволно простор да ја чува резултантната вредност .
Како што веќе напишавме,
strcat не враќа
вредност; верзијата од стандардната
библиотека враќа покажувач кон резултантниот стринг.
/* strcat: ~ ro t на JCP&jor на void streat(ehar ѕ[] , ehar t[])
ѕ ; ѕ н:ра да е ДСЕ10Ј1Н0 Г0ЈЈ1!М
*/
{
int i ,
ј ;
i = ј = О; while (s[i] != '\0') /* најди крај на ѕ */ i++ ; while ((s[i++] = t[j++]) != '\0' ) /* коnирај t */
Како што секој елемент од менува над
i
t
се копира во ѕ , постфиксниот оператор ++се при
и ј , со цел , да обезбеди нивна соодветна позиција за наредното
извршување на циклусот.
2.9
Битски оператори
Вежба
Напишете алтернативна верзија на
2-4.
squeeze
57
(ѕ1 , ѕ2) која ги от
странува сите знаци од стрингот ѕ1 кои се содржат во ѕ2 .
Вежба 2-5
. Напишете функција any ( ѕ 1, ѕ2) , која ја вра ќа прв ата локација в о -1 ако ѕ1 не содржи знаци од ѕ2 . (Функцијата од стандардната библиотека strpbrk ja извршува и с тата работа , но враќа покажувач кон ло кацијата . ) стрингот ѕ1, каде се појавува кој било од знак од ѕ2 , или
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-битна поле од х
кое за почнува на поз и циј а р . Се претпоставува де ка пози цијата на бит о е на десниот крај и дека
getbi ts
(х , 4 ,
3)
n
и р се доволно мал и и позитивни вредн ости. Н а пример,
ги враќа трите битови кои се на местата 4 ,
3,
и
2,
десно
пора мнети
1* getbi ts :
земи n битови од по зи ција р */ uns ignedgetbits (unsignedx , intp , intn)
return
Изразот х
(х
>> (p+1- n))
>> (p+1- n)
& - ( - О<<
n);
го поместува посакуваното поле на десниот крај од збо
n п озиции со - о << n сместува нули во н ајдесн ите n битови ; понатамо шно комплементирање на тоа, со - креира маска со единици во најдесните n битови. рот . - о с одржи само едини ц и ; н е гово п оме сту вање во лево з а
Вежба
2- 6 .
Н апишете функција
setbits
(х , р,
n,
у) која го враќа х со n бито
ви кои започнуваат на позиција р наместени на n-те крајни десни битови од у, додека останатите битови од х ги остава не п ро менети .
Вежба
2- 7 . Напишете функција invert (х , р , n) која го враќа х со n битови , 1 се заменети со о и обра
кои зап оч н уваат на позиција р , н о извртени (т . е. тно)
,
додека другите битови оста нуваат непро менети .
Вежба
2 - 7.
Напишете функција
лиот број х ротиран во десно за
n
rightroot позиции.
(х,
n)
која враќа вредност на це
Оnератори и изрази за доделување на вредност
2.10
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
Типови, оператори и изрази
1* bitcount:
ги брои 1-битовите во х
Глава
2
*/
int bi tcount (unsigned х) {
intb ; for (b=O; if
(х &
х !=О ; х>>=1)
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
2.11
61
Ус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
' ');
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
Забележете дека приоритетот на битските оператори & , л и
1е
63
понизок од
приоритетот на= и ! =о Ова наведува на заклучок дека изразите за битско тес тирање од облик
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з) норедбо
else
1
норедбо 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 ; Двосмисленоста посебно е очигледна во ситуации како оваа
else- if
3.3
67
if (n>O) for (i=O ; iО) { printf(" . .. " ) ; return i; / *ПОГРЕШНО*/
else
printf ( " error --n is negative\n") ; Изведеното вовлекување недвосмислено покажува што точно сакате, но
компајлерот тоа не го разбира и го асоцира else со внатрешниот if. Вакви видови на грешки се тешки за детекција ; добра идеја е да се користат големи загради, секаде на сите места, каде што се употребуваат вгнезден и
би. Патем , забележете го присуството на точка-запирка после
if
(а
if z =а во
наред
>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] <= int binsearch(int Х 1 int v[] 1 int n)
о о о
<= v[n-1] */
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озицијата е return mid ; return -1 ; /*
nронајдена
*/
нема nојава на вредноста што се бара
*/
Фундаменталната одлука во секој циклус овде е дали х е помало, поголемо или
еднакво на средишниот елемент v [mid] ; најсоодветна и природна употреба за
else- if о
switch
3.4 Вежба
3-1.
69
Нашето бинарно пребарување и зведув а две споредби внатре в о
ц икл усот , иако и една би била доволна (по цена н а поголем број надворешни
споредби) . Напишете верзија со само една споредба во циклусот и проверете ј а разликата во времето на извршување на програмите.
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
Контрола на текот
#include main() / *брои цифри,
Глава
nра5ии места,
3
друrи* /
{
intc, i, nwhite , nother , ndigit[lO]; nwhi te = nother = О ; for (i=O ; i
саѕе саѕе
' 3 ': '8 ':
саѕе саѕе
' 4': ' 9' :
printf( "digits=" ) ; for (i=O ; i
Чисто за да биде запазена формата, вметнете break после последната опција (во нашиот случај defaul t)
, иако тоа логично не е потребно . Следниот switch, овој начин на
пат кога некоја друга опција ќе се додаде на крајот од овој дефанзивно програмирање ќе се покаже оправдан.
Вежба
3-2.
Напишете функција еѕсаре (ѕ , t) која прави претворање на зна
ци от типот на знаците за нова линија и таб во видливи излезни секвенци како
\n
и
\ t,
додека го...___ копира стрингот
t
во ѕ. Користете
swi tch.
Напишете, исто
така , функција и за другата насока, која врши претворање на излезните секвенци во вистинските знаци.
Циклуси - while и
3.5
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
е
=
nресхохнуваае на знаците за nразно место
' \t ' )
*/
нема иницијализација или реиницијализација, па употребата на while е најприродна .
for се преферира во сл учаи кога има едноставна иницијализација и некаков
Контрола на текот
72
Глава
3
инкремент , бидејќи ги чува контролните изрази видливи и блиску едни до дру ги на врвот на циклусот. Ова е најочигледно во израз како
for (i=O ; i
кој претставува С идиом за процесирање на првите
n
елементи од една низа ,
аналогно на оо циклусот од Фортран или паскаловиот
for .
Направената ана
логија, сепак, не е совршена , бидејќи индексната променлива
i
си ја здржува
вредноста при прекин на циклусот од која било причина. Бидеј ќи компонен тите на
for
се произволни изрази,
for
циклусите не се ограничени само на
аритметички прогресии . Сепак, лош начин на програмирање е форсирањето на неп оврза ни пресметки во деловите за иницијализација и инкремент кај
for ,
кои се посоодветно наменети за контролни операции на циклусот.
Како пообемен пример, ќе ја наведеме функцијата
atoi за претворање на
стринг во негов нуме рички еквивалент. Оваа е нешто поопшта од онаа што ја разгледавме во Глава
2;
се справува со опционални водечки п разни места како
и со произволен з нак+ и
-.
(Глава
4 ја
покажува
atof ,
која ја прави истата
конверзија за реалните броеви.)
Структурата на програмата ја рефлектира и формата на влезните податоци: прескокни ги празните места, ако ги има земи го знакот, ако го има
преземи го целобројниот дел и претвори го Секој чекор го извршува својот дел и остава чиста ситуација за наредниот . Целиот процес завршува при среќавање на првиот карактер кој не може да
биде дел од број .
#include /* atoi : nретвораље int atoi (char ѕ [])
на ѕ во цел број ,
верзија
2 */
{
inti , n, sign ;
1
for (i
=О ; isзp&ce(s[i]) ;
i++) /*
пресхохни rи nразните места*/
sign = (s[i] == '-' ) ? -1 : 1 ; i f (s[i] == '+' 11 s[i] == '-') /* i++ ; for (n=O; isdigit(s[i]) ; i++) n=10*n+ (s[i]- '0'); return sign *n ;
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ирката во
for
(',' ),
која мошне често се уnотребува
изразите . Два израза, раздвоени со запирка , се евалуираат од л е во
кон десно , а типот и вредноста на резултатот се типот и вредноста на десн иот
операнд. Така, во една
for наредба , можно е да се сместат повеќе изрази во
различните делови, на nример, при процесирање на два индекса паралелно.
Ова е илустрирано во функцијата
reverse (ѕ) , која го превртува стрингот ѕ.
74
Контрола на текот
Глава
3
#inelude
/* reverse: превртува етринг ѕ */ void reverse(ehar ѕ[]) {
int
е,
ј;
i,
О,
for (i =
=
е
ѕ
s[i] = ѕ[ј]
ј
= strlen(s)-1; i <
ј;
i++,
ј--)
{
[i] ; ѕ[ј];
=е;
Запирките кои ги одвојуваат функциските аргументи, променливите во декла рациите, итн.
,
не се запирка оператори и не гарантираат пресметка од лево
кон десно.
Запирка операторите треба да се користат умерено. Најсоодветна употреба е во конструкциите кои меѓусебно строго се поврзани, како во for циклусот од
функцијата reverse и во макроата каде пресметките од повеќе чекори треба да претставуваат еден единствен израз . Израз со запирка може згодно да се искористи за размена на елементите во
се третира како една операција
reverse,
for (i=O, j=strlen(s)-1; i
3-3.
ј--)
Напишете функција expand (ѕ1 , ѕ2) која ги проширува кратките
нотации од облик
xyz во ѕ2.
при што размената може да
:
a-z во стрингот ѕ1 во еквивалентни комплетни листи abe ...
Допуштени вредности се и мали и големи букви како и цифри. Треба
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
мер, i
во знаковна репрезентација со основа b, сместена во стрингот ѕ. На при
tob (n, ѕ, 16) го форматира ѕ како хексадекадна репрезентација на n.
Контрола на текот
76 Вежба
3-6 .
Наnишете верзија н а
Глава
3
itoa , која наместо два , nрифаќа три ар
гументи. Третиот аргумент е минималната шири н а н а резултантниот стрин г.
Добиениот резултат мора да биде пополнет со nразни места во случај кога ши рината на добие ниот број е nомала од минималната nредвидена ширина на
стрингот. (т . е резултатот треба да биде nорамнет во десно.
)
3.7 break и continue Понекога ш е nогодно да се има можност да се nрекине цикл усот, пред да се
nровери условот на nочетокот или крајот. Наредбата
break обезбедува nред for , while и do , како и кај swi tch. П овик на break nредиз в икува моментален пре кин на највнатре шн иот цикл ус или swi tch . Следната функција , trim, ги отстранува п разните места , табовите и знаци те за н ова линиј а од крајот на стрингот , користејќи break за nрекин на извр времен nрекин за
шува њето на циклусот, во моментот кога ќе се пронајде најдесниот небла нко, нетаб и не- " нова линија " знак .
/ * trim:
отстранува празни места ,
нова линија од храјот на ѕ
int trim(char
табови и знаци за
*/
ѕ[])
int n ; for (n= strlen(s)-1 ; n>= if (s[n] != ' ' && s[n] break ; ѕ [n+l] = '\0'; return n ;
n- -) != '\t '
О;
&&
s[n]
!= '\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 реми н ување кон следното повторување н а циклусот.
goto и
3.8
озна ки
77
Како пример ќе го земеме овој фрагмент, кој ги процесира само ненегатив ни те елементи на низата а ; негативните вредности се прескок нуваат.
for (i =О ; i
Н аредбата
continue
елемен~и
*/
често се користи , кога дел од циклусот кој следи е ком
nлициран, на тој начин што замената на условот со спротивен и вовлекување
на уште едно ниво би предизвикало предлабоко вгнездување во програмата.
3.8 goto и
ознаки
С нуди неограничена злоупотреба на гранува
.
Формално, наредбата
goto наредба и ознаки кон кои се раз goto никогаш не е потребна и во пра ктика ре
чиси секогаш е лесно да се напише код без неа . Ние во оваа книга никаде не користевме
goto.
Сепак, постојат мал број ситуа ции во кои
goto може да си најде место.
Најчесто за напуштање на процесирање во некоја длабоко вгнездена структу ра, како што е прекин на два или повеќе вгнездени циклуси одеднаш случаи не може да се користи
. Во такви break, бидејќи истата се однесува само на најв
натрешниот циклус. Поради тоа :
for ( ... ) for ( ... ) if (disaster) gotoerror ; }
error : /*
исчис~и го нередо~
*/
Ваква организираност е згодна ако кодот за справување со грешката не е три
вијален и доколку грешките можат да се појават на неколку места. Една ознака има иста форма како име на променлива и се проследува со
знак за две точки. Може да биде прикачена на која било наредба во рамките на функција во која се наоѓа и
goto . Делокругот (ан г. ѕсоре) на ознаката е целата
функција.
Како друг пример, ќе го разгледаме проблемот за определување дали две низ и а и b имаат заеднички елемент. Едно решение би било
78
Глава
Контрола на текот
for (i=O; i
еден :
Код кој вклучува
елемент
3
*/
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) која ја t , а - 1 ако
враќа п оз ицијата или индексот во ни зата ѕ каде за n очнува ни зата ѕ не ја содржи
t.
Бидејќи низите во С започнуваат со индекс нул а, сл едува
дека индексите ќе бидат или нула или позитивни , па вредноста -1 , може да
се користи како сигнал за грешка. Во случај подоцна да ни затреба некоја по софистицирана сnо редба на облици , измените ќе бидат локализира ни само на функцијата
strindex , додека д ругите делов и на програмата ќе оста нат не strstr која е слична
допрени ( ста ндардн ата библиотека обезбедува функција на
strindex ,
но враќа пока жувач нам есто индекс).
После толкаво дизајнирање, доп олн увањето на програмските детали е ед
ноставно. Овде е дадена целата програма, за да видите
како сите делови се
склопени заедно. Засега , обликот кој ќе се пребарува е константен стринг , што
не е најоnшт меха низам. ќе се навратиме за момент кон расправа околу и ни цијализација на низа од знаци , а в о Глава ѕ ќе покажеме како обликот може да се нап рави парамета р кој ќе добие вредност за време на извршување на про-
Основи на функци и
401
-рамата о Исто така , тука е малку поинаква верзијата на функцијата
81
qetline ;
чожеби б и било поучно да ја споредите со верзијата од Глава 1 о
tinclude 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
е,
=
О;
i
пинија во ѕ , ѕ[],
врати ја допжината
*/
int lim)
i ;
while (--lim >О && (c=qetchar()) s[i++] = е ; if (е == '\n ' ) s[i++] = е ; s[i] = '\0' ; return i ;
'= EOF &&
е
! = '\n ' )
*/
82
Функции и структура на програма
/* strindex: враха позиција на t int strindex(char ѕ[] , char t[]) int i ,
ј ,
Глава во ѕ,
-1
ако го нема
4
*/
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 . Најнапред , самата atof функција мора да декларира тип на вредност кој ќе
декларирана во заглавјето
го враќа , бидејќи не станува збор за цел број. Името на типот му претходи на името на функцијата
iinclude /* atof : хоиаер~ира с~инг doub1e atof(char ѕ[])
ѕ ао број од ~иn
double * /
(
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 idefine
МAXLINE
/ * едиос~авен main ()
100 халхула~ор
*/
(
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
1* atoi:
претвора стринг ѕ во цел број користејќи ја
int atoi (char
ѕ
4
atof */
[])
{
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
Надворешни променливи
#include #include
89
/* for atof() */
#define МАХОР 100 /* НАХсииа.лна rо.nемина на оnеранд или оnератор * 1 #define NUМВER '0' /* сиrнал деха е nронајден број */ int getop(char []); void push(double); double pop(void); /* инверзен main ()
nолсхи халхулатор*/
{
int type; double ор2; char ѕ[МАХОР]; while ((type = getop(s)) switch (type) { саѕе
!= EOF)
{
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() -
рор());
/*ПОГРЕШНО*/
не е дефиниран редоследот според кој се евалуираат двете повикувања кон рор . Со цел да осигураме правилен редослед на извршување, неопходно е да се земе првата вредност од купот и да се зачува во некоја помошна променли
ва, како што направивме во
#define
МAXVAL
int ѕр double
val[МAXVAL] ;
= О;
main. махсииапна дпабочина ма хупот
100 /*
/*
спедната слободна nозиција во хуnот
/* push : го става f void push(double f)
/*
val */ */
хупот со вредности*/
на врв на
xyn */
{
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 int geteh{void) ; void ungeteh{int); /* getop: земи int getop{ehar int i,
го следниот оператор или нуиерички операнд
е;
е
while { {ѕ [О]
= geteh {) ) -
s[l] = '\0' ; if {!isdigit{e) && return е ; i
*/
ѕ[])
е
'
'
11
е
'\t l )
!= '. ' ) /* не е број */
= О;
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озиција int getch(void) /* return (bufp >
void ungetch(int
е)
земи О)
/*
(можеби вратен)
? buf[--bufp]
врати
ro
во
buf */
знак*/
: getehar() ;
знакот назад на влез
*/
if (bufp >= BUFSIZE) printf( " ungeteh : too many characters\n" ); else buf[bufp++] = е ;
Стандардната библиотека содржи функција ungetc која обезбедува враќање на еден знак; ќе ја разгледуваме во Глава 7. За вратените знаци користевме низа, а не еден знак, за да илустрираме еден поопшт пристап.
Вежба
4-3.
Со помош на дадената рамка, лесно е калкулаторот да се проши
ри. Додајте модул (%) оператор и можност за работа со негативни броеви . Вежба
4-4.
Додајте команди за печатење на елементот од врват на купот, без
тој да е отстранет од врват , за негово удвојување и за смена на двата елемента кои се наоѓаат на врват .
Вежба
pow.
4-5 .
Додајте пристап до функциите од библиотеката како sin , ехр , и
Погледнете ја
Вежба
4-6.
понудат
во Додаток
Б , Дел
4.
Додајте команди за справување со променливи. (Лесно е да се
26 променливи со имиња од еден знак.) Додајте променлива за вред
носта која била печатена последна . Вежба 4-7 . Напишете рутина ungets (ѕ) која назад на влез ќе вра ќа цел стринг. Треба ли ungets да има информација за buf и bufp , или, единствено, треба да ја користи
ungetch?
Правила на делокругот (анг. ѕсоре)
4.4 Вежба
4-8.
Претпоставете дека никога ш н ема да има повеќе од еден знак за
враќа ње на влез. Модифицирајте ги Вежба
4-9.
Нашите
getch
и
getch
ungetc h
и
ungetch
соодветн о.
не се с праву ваат правилно со враќање
на EOF на влез. Одлучете кои треба да се нивн и те однесувања ако на влезот и потоа имплементирајте го вашиот дизајн Вежба
93
4-10.
EOF се
враќа
.
Една алтернативна организација користи
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:
#include #include #include "calc.h" #defineМAXТOP
100
#include #include #include " calc.h" getop() {
main() {
stack . c: #include #include " calc . h " #define МAXVAL 100 intsp=O; double val [МAXVAL] ; voidpush(double) {
getch . c: #include #define BUFSIZE 100 char buf [BUFSIZE] ; intbufp=O ; int getch (void) {
double рор (void) {
{
void ungetch (int) {
Има уште едно нешто за кое треба да се грижиме- дефинициите и деклараци ите кои се споделуваат помеѓу датотеките. Сакаме да го централизираме тоа колку што е можно повеќе, така што ќе имаме само една копија за средување и одржување,
како што напредува програмата. Во согласност со ова , ќе го
сместиме тој заеднички материјал во заглавишна датотека (заглавје) calc. h ,
која ќе биде вклучена во зависност од потребата . (Ставката #include е опиша на во Глава 4 . 11) . Резултантната програма во овој случај изгледа вака: Постои рамнотежа помеѓу желбата секоја датотека да има пристап само до
информацијата која и е потребна и практичната реалност, каде што е потешко одржувањето на повеќе заглавја. Се до некоја средна големина на п рограма
та , веројатно, најдобро е да се има едно заглавје, кое што содржи се што треба
да биде споделено помеѓу било кои два дела од програмата; тоа е одлуката која треба да се направи тука. За многу поголема програма , би била потребна поголема организација и повеќе заглавја.
Статички променливи
4.6
4.6 Статички
97
променливи
Променливите ѕр и
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.7 Регистарски Декларацијата
Глава
4
променливи
register
му советува на компајлерот дека променливата ќе
биде интензивно користена. Идејата е регистарските променливи да се сме стат во машинските регистри , што би резултирало со помал и и побрзи програ ми . Сепак , компајлерите може да го занемарат советот. Регистарс ката де кларацијата изгледа вака :
register int х ; register char е; Регистарската декларацијата може да биде аплицирана и врз автоматските про менливи како и врз формалните параметри на функцијата. Последниов случај би изгледал вака :
f (register unsignedm , register long n) {
register int i;
Во практика постојат ограничувања на регистарските променливи , кои за
висат од реалноста на употребуваниот хардвер . Само неколку променливи од секоја функциј а може да се чуваат во регистрите , и само на неколку одредени типови тоа им е дозволено. Вишокот регистарски декларации се безопасни, од причина што зборот
register се игнорира за одвишни и недозволени де
кларации. Исто , не е возможно да се земе адреса на регистарска променлива (ќе биде аргументирано во Глава 5) , без оглед на тоа дали променливата, во општо, е сместена во регистар. Кон кретните ограничувања за бројот и типот на регистарските променливи варира од машина до машина .
4.8 Бnоковска
структура
С не е блоковско
-
структуриран јазик како Паскал и нему сличните, би
дејќи функциите не може да бидат дефинирани во рамките на другите функ ции. Од друга страна, променливите може да се дефинираат на блоковско
-
структуриран начин во рамките на функцијата. Декларацијата на променливи те (вклучително и иницијализацијата) може да следи после голема отворена
заграда која означува почеток на кој било сложен израз, а не само почеток на функција . Променливите декларирани на овој начин ги покриваат идентично именуваните променливи од надворешните блокови и живеат се до соодветна та голема затворена заграда. На пример во
Блоковска структура
4.8 if ( n
>
О
99
) {
int i;
/*декларација на ново
for (i
= О;
i* /
i < n; i++)
делокругот за променливата
i е делот од if во кој се влегува кога е исполнет
условот; ова i нема никаква врска со кое било i надвор од блокот. Една авто матска променлива декларирана и иницијализирана во блок, се иницијализи
ра одново со секое ново влегување во блокот.
static променлива се иницијали
зира само при првото влегување во блокот . Автоматските променливи, вклучително и формалните параметри, исто
така, ги кријат истоименуваните надворешни променливи и функции. На при мер, во декларациите
intx; inty; f(doublex) {
doubley;
во рамките на функцијата
f, појавувањата на параметарот х се однесува на double, додека надвор од f, се однесува на
параметарот деклариран како
надворешниот int. Истото важи и за променливата у.
Без разлика на стилот, најдобро е да се одбегнуваат имиња на променливи
кои кријат имиња од надворешен делокруг; можноста за конфузија и грешка е премногу голема
.
4.9 Иницијапизација Иницијализацијата досега беше спомнува на многу пати, но секогаш површ но поврзано со некој друг проблем. Оваа лекција сумира некои од правилата , сега откако ги дискутиравме различните класи на мемориски простор.
Во отсуство на експлицитна иницијализација, надворешните и статичките
променливи се гарантира дека се иницијализирани на нула; автоматските и ре
гистарските променливи имаат недефинирани (т. е. ѓубре) иницијални вред ности .
Скаларните променливи може да се иницијализираат при дефинирање , со додавање еднакво и некој израз после името на променливата:
Функции и структура на програма
100
intx=1; char squote = '\' ' ; long day = lOOOL * бОL *
бОL
*24L ;
Глава
4
/*милисекунди /ден*/
Кај надворешните и статичките променливи, изразот мора да биде констан та; иницијализацијата се врши еднаш, концептуално пред да за почне и з вршу вањето на програмата. Кај автоматските и регистарските променливи, тоа се
прави повторно со секој повик на функција или влез во некој блок . За автоматските и регистарс ките променливи , иницијализаторот не е огра ничен само на константни вредности; тоа може да биде израз кој вклучува пре тходно дефинирани вредности , дури и функциски повици . На пример, ини
цијализациите на програмата за бинарно пребарување од Поглавјето
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
/* printd : nечатеае void printd(int n)
на
n
дехадно
{
i f (n
< 0) {
putchar ( ' -'); n = -n ;
1 10) printd(n 1 10) ; putchar(n % 10 + ' 0 ' ) ; if (n
*/
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рефрли го 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артиционираље во
nартиционирачхиот елемент
*/
qsort(v , left , last-1) ; qsort(v , last+1 , right) ;
Операцијата за замена ја преместивме во посебна функција swap , бидејќи се појавува трипати во
qsort .
е nр етп р оцесор
4.11
103
/* swap: размена на v[i] и v[j] */ void swap(int v[] , int i, int ј) {
int temp ;
temp v[i] v[j]
v [i] ; v[j] ; temp ;
Стандардната библиотека вклучува везрија на qsort, која што може да сортира објекти од каков било тиn. Рекурзијата не штеди на меморија, бидејќи купот од вредности кои се про цесираат мора да се зачува некаде . Н и ту, nак, е побрза . Но, рекурзивниот код е nокомпактен и честопати полесен да се напише и разбе ре откол ку н еговиот
нерекурзивен еквивалент. Рекурзијата е посеб но згодна за рекурзивно дефи нираните податочни структури, како што се дрвата, за кои ќе разгледаме убав пример во Поглавје б . б.
Вежба
4-12.
Адаптирајте ја идејата имплементирана во printd , за да напи
шете рекурзивна верзија на i toa ; односно претворете цел број во стринг со повикување на рекурзивна рутина.
Вежба
4-13 . Напишете рекурзивна верзија на reverse (ѕ) којашто огледал
но го превртув а стрингот ѕ.
4.11
е претпроцесор
Програмскиот ј азик С овозможува одредени олеснувања, ако се користи
претпроцесор како прв чекор во компајлирањето. Двете најчесто употребу вани структури се
#include ,
за да се вклучи сод ржината на една датотека за
време на компајлирање и #define за да се заме ни некој симбол со а лтернативна се квенца од з наци . Другите конструкции о пишани во ова п оглавје вклучуваат услов но компајлирање и макроа со аргументи.
4.11.1
Вклучување на датотеки
Вклучува њето на датотеките овозможува пол ес но с пр авување со (помеѓу другото)
во форма
колекции од
#define директиви
и декларации. Секоја изворна линија
Функции и структура на nрограма
104
Глава
4
#include "име - на- датотека" или
#include <име- на- датотека> се за менува со содржината на датотеката име- на - датотека. Ако име - на - датотека се наведе во наводници, пребарувањето за датотеката обична заnочнува каде што се на оѓа изворната nрограма; ако не се nронајде таму, или ако нејзиното име е омеѓено со
< и >,
пребарувањето продолжува на nредефинираните места со имплементацијата .
Вклучената датотека и самата може да содржи други #include директиви. Честопати има повеќе
#include линии на nочетокот на изворната датоте #define искази и extern декларации, или да
ка за да се вклучат заедничките
се овозможи nристаn до декларации на функциски nрототиnови за функции
кои се наоѓаат во стандардната библиотека, во заглавја како што е, на nример,
. (Прецизно кажано, не мора да станува збор за датотеки ; детали те за тоа како се 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
дефинира нов збор, forever,
105
за бесконечен циклус.
Можно е да се дефинираат макроа со аргументи, па текстот за замена може
да биде различен при различни повикувања на макрото. Како пример , ќе де финираме макро наречено 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) .
Нема сомнение, макроата се корисни. Еден практичен пример доаѓа од заглавјето , во кое getchar и putchar често се дефинирани како
макроа за да се избегне преоптоварување за време на извршување на функ циски повик за секој процесиран знак. Функциите во , исто така, често се имплементирани како макроа.
На имињата може да им биде откажана дефиницијата преку #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
Гл ава
Функции и структура на програма
Директивите ftifdef и
iifndef се специјализирани форми кои проверуваат ftif по го ре можеше да се
дали едно име е дефинирано . Првиот пример од напише и како
ftifndefНDR
ldefineНDR
/*
содржината
iendif
4
hdr . h
е сместена туха
*/
Глава
5:
Покажувачи и низи
Покажувач е променлива која содржи адреса на друга променлива о Во С покажу вачите се многу користени, делумно поради тоа што тие некогаш се единствениот
начин да се претстави некое пресметување, а делумно и поради тоа што честопати
нивното користење резултира со поефикасен и покомпактен код во споредба со дру гите можни начини о Покажувачите и низите се тесно поврзани ; ова поглавје, исто
така, ја проучува таа поврзаност и објаснува како истата да се искористи о Покажувачите некогаш во комбинација со
goto наредбата создаваа програ
ми кои беа готово невозможни за разбирање о Ова е сигурно вистина кога тие се користат невнимателно, а мошне е лесно да се креираат покажувачи кои по
кажуваат на некое неочекувано место о Со дисциплина, сепак, покажувачите
може да се искористат да се постигне јасност и едноставност о Овој аспект ќе се обидеме да го илустрираме овде о Главната промена во
ANSI
е стандардот е разјаснувањето на правилата за
тоа како се манипулира со покажувачите, нешто што во практика веќе го при
менуваат добрите програмери и што го наметнуваат добрите компајлери о Исто така, типотvоid* (покажувач кон
void)
е замена за
char*
како соодветен тип
за генерички покажувач о
5.1
Покажувачи и адреси
Да започнеме со една поедноставе н а слика за мемориската организација о
Денешните компјутерски платформи им аат ни за од последователно нумерира ни или адресирани мемориски клетки , кои може да се манипулираат индиви
дуално или во поврзани групи о Една вообичаена ситуација е дека, секој бајт · може да биде
char , пар од еднобајтни клетки може да се третира како short long цел број о Покажувач е група
цел број, а четири соседни бајти формираат
на клетки (обично две или чети ри) кои во себе може да чуваат адреса о Така, ако е е
char и р е покажувач кој покажува кон него , ситуацијата б и можеле да ја
презентираме на следниов начин :
109
Глава
Покажувачи и низи
110
5
Унарн иот оператор & ја дава адресата на еден објект, па така наредбата р= &е ;
ја доделува адресата на е на променл ивата р и за р се вел и дека " п окажува ко н "с . Операторот & се однесува единствено на објекти во меморијата: про менливи и елементи на ни з и . Н е може да се при ме ни на изрази , ко н станти, или
register
проме нливи.
Унарниот оператор
*
се нарекува onepamop за индирекција или onepamop
за дереференцирање; кога ќе се при мени над некој покажувач , го пристапува објектот кон кој покажува покажувачот . Да претпоставиме дека х и у се цели броеви, а ip е покажувач кон int. Наредната вештачка секвенца п окажува како се де кларира по кажувач и како се користат & и
=
int х 1, int *ip ;
у
=2,
= &х; = *ip;
*ip ip
nокажувач кон
/* ip сега nокажува / * у е сега 1 */ /* х е сега О */ /* ip сега nокажува
ip у
z[10] ; /* ip е
= О;
= &z[O] ;
*:
int * /
кон х
кон
*/
z[O] */
Декл арациите н а х, у, и z се исти како што беа и досега. Декларацијата на по кажу вачот
ip ,
int* р ; е осмислен а како мнемоничка; таа вели дека изразот
*ip
има
int
вредност.
Синтаксата на деклара цијата на една проме нлива , ја имитира синтаксата на изрази те во кои проме нливата може д а се п ојави. Ваквото раз мислување се однесува и на функциските де к лар ации. На пр име р ,
doub1e*dp , atof(char*) ; оз нач ува дека во некој из раз аргументот на
atof е
*dp
и
atof (ѕ) char.
имаат
double
вредности , и дека
по кажува ч ко н
Исто така , треба да забележите дека еден п окажувач е обврзан да покажува кон некој одреден вид на објект: секој покажувач п окажува ко н специфичен податочен тип. (Има еден исклучок на ова: " пока жу вач кон
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
void swap(int
х,
int
Глава у)
/*
ПОГРЕШНО
5
*/
{
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 int getch (void) ; void ungeteh (int); /* getint: земи ro int getint (int *pn)
наредниот број од влез •о
*pn
*/
{
int е, sign ; while (isspace (е= getch ())) / * nреехохни if (!isdigit(e) &&е '=EOF&&c !='+ ' ungetch (е) ; 1* не е број */ return О ;
nрuни места
&&е!='-')
{
*/
Глава
Покажувачи и низи
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-тиотелемент пред него о Па, акора покажува кон а [О], се однесува на а
a[i]
[1],
ра
+i
на адресата на а
[i],
а*
(pa+i)
*
(ра+1)
на вредноста на
о
а [О]
Овие забелешки се вистинити , без разлика од кој тип или големина се про менливите во низата а о Значењето на "додавање
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 : враха допжина int strlen(char *ѕ)
на стринr ѕ
*/
int n ; for (n = О ; n++ ; return n ;
*ѕ
!= ' \0 ',
ѕ++)
Бидејќи ѕ е п о кажувач, неговото инкрементирање е соврше но л егалн о; ѕ++ нема ефект кај стрингот во функцијата која ја повикала ја инкрементир а при ватната копија на покажува чот во
strlen, таа само strlen . Тоа зн ачи дека
сите п ов ици како
strlen( " hello, world" ) ; strlen(array) ; strlen (ptr) ;
/* string хонстанта */ /* char array[lOO] ; */ /* char *ptr ; */
Покажувачи и низи
5.3
11 7
се функционални. Како формални параметри во една функција
char
ѕ
char
*ѕ;
1
[] ;
и
се еквивалентни; ние ја претпочитаме втората варијанта од причина ш то та а поексплицитно декларира дека променлив ата е покажувач. Кога име на низа
се предава како аргумент на една функција 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
ноставните форми на покажувачка или адресна аритметика. С е конзистентен и регуларен во пристапот на адресната аритметика; интеграцијата на покажу вачите, низите и адресната аритметика е една од предностите на овој јазик. Да
ја илустрираме со пишување на едноставна програма за доделување на мемо риски простор . Програмата ќе има две рутини. Првата , кажувач кон
n
alloc (n) ,
враќа по
последователни знаковни позиции, кои може да се користат од
повикувачот на alloc за чување на знаци. Втората , afree (р) , го ослободува претходно доделениот мемориски простор, со намера истиот да може да се ко
ристи подоцна. Рутините се " рудиментирани " бидејќи повикувањата до afree мора да се направат во обратен редослед од повиците до alloc. Всушност, ме морискиот простор менаџиран од
alloc
и
претставува куп (ан г.
afree ,
stack)
или листа во која последниот запишан елемент прв излегува. Стандардната
библиотека обезбедува аналогни функции кои ги немаат споменатите ограни чувања ; во Поглавје
8 . 7 ќе покажеме начин како тие да се имплементираат.
Најл есна имплементација би била alloc да предава делови од голема низа од знаци која ќе ја повикуваме со allocbuf . Оваа низа ќе биде приватна за alloc и afree . Бидејќи работат со покажувачи, а не со индекси на низи, ниту
една од рутините нема потреба да го знае името на таа низа, која може да биде декларирана како
static во изв орната датотека која
ги содржи
alloc и afree ,
и со тоа да биде невидлива надвор од неа. Во практични имплементации, оваа низа не ни мора да има име ; таа би можела да се добие со повик до malloc или со барање до оперативниот систем за добивање покажувач кон некој неимену ван мемориски блок. пред повикот ко н
allocbuf
alloc
allocp : "'-...
:L.. -1--L---LI---'-1----'-1--~--_____. -+----+--
се користи
posle повикот кон alloc
allocbuf
:1
слободн о ------'~
-.
allocp :
11
"'-...
1
~~==~==~с~е~к~о~р~ис~т~и~::::::~~;.:::сс~л~об~о~д~н~о-=~~
Понатаму, потреб на ни е информација за тоа колкава е тековната зафате ност на
allocbuf .
Ние користиме покажувач наречен
allocp ,
кој покажува
кон следниот слободен елемент. Кога alloc ќе побара n знаци , проверува дали има останато доволно простор во
тековната вредност на allocp (Т. е. инкрементира за
n
allocbuf.
Ако е така,
alloc ја
враќа
, почетокот на слободниот блок) , па го
места, за да покажува кон следната слободна област. Во
случај да нема доволно простор, alloc враќа нула. Един ствено што прави
afree
(р) е да го постави
allocp
на пока жувачот р ако р е во
allocbuf .
119
Адресна аритметика
5.4 #define ALLOCSIZE 10000 / *
голеии:на на расnоложив nростор
static char allocbuf[ALLOCSIZE ] ; / * аростор static char *allocp = allocbuf ; / * спедиат а char *alloc(int n) /*
резервираи за
*/
alloc */ */
слободна nозиција
враха nо~ажувач ~ои
n
знаци* /
{
if (allocbuf + ALID:SIZE - allocp >е= n) { / * allocp += n; return allocp - n ; /* старото р */ else /* иеиа доволно nростор */ return О;
void afree (char
има дово.пно nростор
*р) /*ослободи го nро с торот на ~ој nо~ажува р
*/
*/
{
if
(р
>= allocbuf allocp=p;
&& р
< allocbuf + ALLOCSIZE)
Општо земено , покажувач може да се иницијализира исто како и која било друга променлива , иако во н о рмален случај еди н ствен и вредности со смисла
се н ула или израз кој ја вклучува адресата на некој претходно соодветно дефи ни ран податок. Декларацијата
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 е дефини ран во . Отсега па н ата му ќе користиме NULL .
Проверки те од облик
if (allocbuf + ALLOCSIZE - allocp >= n) { /*
има доаолно nростор*/
и
if
(р
>= allocbuf
&& р
< allocbuf + ALLOCSIZE)
покаж у ваат неколку важни својства на адресната аритметика. Прво , покажу
вачите може да се споредуваат меѓусебно во одредени ситуации. Ако р и q по кажуваат кон елементи на иста низа, релациите како=,
! =, <, >=,
итн.
,
се
валидни. На пример,
p
q.
Секој покажувач може валидно да се споредув а
за еднаквост и нееднаквост со нула . Но однесувањето е недефинирано за а рит метика или сп оредби со покажувачи кои не покажуваат кон елементи од една иста низа.
(Постои ед ен и склучок: адресата н а првиот елеме н т после к рај от
на ни зата може да се ко ристи во адресна аритметика)
Втор о, веќе забележавме де ка покажувач и цел број може да се соб ираат и одземаат . Конструкцијата
p+n ја означува адресата на n-тиот елемент после елементот кон кој по кажу ва р.
Ова е вистинито без разлика н а типот на објектот кон кој покажува р ;
n
се ска
лира соодветно со големината на објектот кон кој п окажува р , определена од декларацијата на р . Ако еден
int е со должина од четири бај ти ,
int-oт ќе биде
скалира н за чет ири .
Одземањето на покажува чите, исто така, е валидна операција: ако р и
кажуваат ко н елеме_нт од иста низа и р
< q,
тогаш
q по q-p+l е бројот на елементи
од р до q . Овој факт може да се искористи за да се напише уште една верзија на
strlen :
1
Адресна аритметика
5.4
/* strlen: враха должина int strlen (char *ѕ)
на стринг ѕ
121
*/
{
char *р= ѕ; while (*р != '\0') р++;
returnp-
ѕ;
Со почетната декларација , р се поставува на вредноста на ѕ , т. е , покажува кон првиот знак од стрингот. Во while циклусот , секој знак се и спитува одделно се до дека не се дојде до
'\0'
на крајот . Бидејќи р покажува кон знаци , секој п ов ик на
р++ го поместува р кон следниот знак во стрингот, а р
- ѕ го дава бројот на знаци
кои биле изминати , т. е. , самата должина на стрингот . (Бројот на знаци во еден стринг може да б иде преголем за чување во int. Заглавјето дефи ни ра тип ptrdiff_ t кој е доволно голем да чува разлика помеѓу вр едностите на два пока жувача. Ако бевме претпазливи ќе користевме
size_ t тип како повратна вредност strlen, за да ја направиме соодветна на стандардната библиотека . size_ t е од неозначен целоброен тип кој го враќа операторот sizeof . ) за функцијата
Адресната аритметика е конзистентна: во случај да работевме со променливи од тип
float,
кои зафаќаат повеќе меморија од променливи од тип
покажувач кон
float,
char
и ако р беше
р++ ќе го помести р за да по кажува кон следниот
мент. Поради тоа би можеле да напишеме друга верзија на
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
amessage:
lnow is the time\0
is the t ime\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 во ѕ , void strcpy(char *ѕ, char *t)
верзија со покажу8ачи
2 */
{
while ( (*ѕ++ = *t++) != '\0')
Ова ја преместува инкрементацијата на ѕ и t во условниот дел на циклусот. Вредноста на
*t++
е знакот кон кој покажувал
ран ; постфиксниот оператор
++
t,
пред
t
да биде и нкременти
не ја менува вредноста на t се доде ка не се
предаде знакот кон кој t покажува . На ист начин, знакот се запишува во стара
та позиција пред да биде зголемен ѕ . Тоа е и знакот кој се споредува со ' \о' во контролниот дел на циклусот. Крајниот ефект е копирање на знаците од t во ѕ , вклучителносотерминалниотзнак
' \0' .
124
Покажувачи и низи
Глава
5
Конечно, забележете дека споредбата со '\о' е одвишна , бидејќи пра шањето што се обработува е дали изразот внатре во заградите е нула или не. Така што , функцијата, најверојатно, би била напиша на како
/* strcpy: хопирај го t во ѕ; void strcpy(char *ѕ , char *t)
верзија со nохажувачи З
*/
{
while
(*ѕ++
= *t++)
Иако ова на прв поглед може да ви изгледа необично, на оваа програмер ска финеса ќе треба да се навикнете , од причина што ќе ја среќавате мошне често во понатамошните С програми. во стандардната библиотека
Функцијата
()
strcpy
целниот стринг. Втората рутина која ќе ја проучиме е споредува стринговите ѕ и
t
која е дефинирана
како функциска вредност го враќа
strcmp
(ѕ,
t) ,
која ги
и враќа негативен резултат , нула или позитивен
резултат во случаите кога ѕ е лексикографски помало , еднакво или поголема
од
t .
Резултатот се добива со одземање на првите знаци кои се разликуваат .
/* strcmp: врати <О ахо s
О
ахо
s==t, >0
ахо
s>t */
s==t, >0
ахо
s>t */
int i ; for (i = О ; s[i] == t[i]; i++) i f (ѕ [i] = '\0') return О ; return s[i] - t[i] ;
Верзијата со покажувачи:
/* strcmp: врати <О ахо s
О ахо
== *t ; ѕ++ , t++) = '\0 ' ) return О; return *ѕ - *t ; *ѕ
(*ѕ
Бидејќи++ и-- може да се сретнат во префиксна и постфиксна форма , мож на е појава и на други комбинации помеѓу*, од една страна, и++ и--, од друга страна , иако тие се поретки. На пример,
Низи од покажувачи; Покажу вачи кон пок ажувачи
5.6
125
*--р
го намалува р пред да биде преземен знакот кон кој покажува р. Всушност
,
парот изрази
*р++
val
= val ; = *--р ;
/* /*
додај
val
на хуnот
отстрани го врвот
*/ на хуnот во
претставуваат стандардни идиоми за додај (анг. рации кај купови; погледни го Поглавје
Заглавјето
val * /
push) и отстрани
(анг. рор) опе
4 . 3.
содржи декларации за функциите спомнати во ова
поглавје и голем број на други функции за работа со стрингови од стандардната библиотека.
Вежба 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 .
Преуредете ги програмите од претходните глави така што намес
то низи ќе користите покажувачи. Добри можности нудат функциите (Глава (Глава
4), atoi , itoa и 3) и strindex и getop 1
и
5.6 Низи
нивните варијанти (Глави (Глава
2, 3
и
getline 4), reverse
4) .
од покажувачи; Покажувачи кон покажувачи
Бидејќи пока жувачите во суштина се променливи , тие може да се чуваат во низ и исто како и другите типови променливи
.
Да го илустрираме к ажаното со
пишување на програма која ќе сортира мн ожество од текстуални лин и и по ал
фабетски редослед 1 слично на функцијата
sort од оперативниот систем UNIX. Shell sort) функцијата кој сор
Во Глава з 1 го претставивме Шел-сорт (анг.
тираше низа од цели броеви 1 а во Глава 4 се подобривме со воведување на
quicksort . Истите алгоритми ќе функционираат и тука , со таа разлика што
126
Покажувачи и низи
Глава
5
сега ќе треба да се справиме со текстуални линии кои се со различни должи ни , и кои, за разлика од целите броеви, не можат да се споредуваат и преместу
ваат во рамките на една операција. Ни треба податочна репрезентација која ќе се справи ефикасно и соодветно со променливата должина на текстуалните линии .
Токму овде ќе го воведеме поимот за низи од по кажувачи . Ако линиите,
кои треба да се сортираат, се складираат од-крај-до-крај во една д олга низа од знаци, тогаш кон секоја линија може да се пристап и преку покажувач кон н ејзиниот прв знак. Дв е линии може да се споредуваат со пр едав ање на нивни
те покажувачи како аргументи на функцијата
strcmp . Во случај кога треба да се
заменат две неподредени л и нии , се врши замена н а покажувачите од ни зата , а не на самите текстуални линии .
На тој начин ги елиминираме проблемите на комплицирано менаџирање со меморијата и големото преоптоварување кое би настанала со преместување на сам ите линии. Сортирањето се одвива во три ч еко ри:
вчитај ги сите линии од податочен влез сортирајги
испечати ги во подреден редослед Како и вообичаено, најдобро е да се подели програмата во функции кои соодветствуваат на природната подел ба, со main рутина која ќе ги контролира другите функции. За почеток ќе го занемариме чекорот со сортирањето и ќе се концентрираме на податочната структура и на влезот и излезат.
Влезната рутина треба да ги собира и складира з наците од секој а линија и да изгради низа од покажувач и кон ли ниите. Исто така, ќе треба да го б рои бројот на влезни линии , затоа што таа информација е потребна во деловите за сортирање и печатење . Бидејќи влезната функција може да се справи само со конечен број на влезн и линии, ќе треба да врати нек аков сигн ал во случај да се појават преrолем број линии на влез. Единственото нешто што треба да прави излезната ру ти на , е да ги п е чати линиите во редослед во кој тие се под редени во низата од по кажувачи .
Низи од покажувачи ; Покажувачи кон покажувачи
5.6
127
#include #include #define char
МAXLINES
5000 /*
*1ineptr[МAXLINES] ;
махсимален број на
/*
линии за сортираи.е* 1
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) return - 1 ; else { line[len-1] = ' \0 ' ; /* избриши го знакот strcpy(p , line) ; lineptr[nlines++] = р ; return nlines ;
NULL)
за нов ред
*/
Глава
Покажувачи и низи
128
5
/* writelines: nечатеае на текстуапките линии */ void writelines(char *lineptr[], int nlines) {
int i; for (i = О; i < nlines ; i++) printf( " %s\n" , lineptr[i]) ; getline е истата од lineptr : char *lineptr [МAXLINES] Функцијата
Поглавје
1 . 9.
Главната промена е деклара
цијата за
означува дека
lineptr
е низа со МAXLINES бр ој на елементи , од кои секој
елемент е покажувач кон
*lineptr [i]
char .
Односно ,
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]) <О) swap(v 1 ++last 1 i) ; swap(v left last); qsort(v 1 left 1 last-1); qsort(v 1 last+1 1 right); 1
1
Соодветно 1 и
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 (Т. е. соодветно и
temp
lineptr)
е покажувач кон
char
1
треба да биде од истиот тип 1 со што ~е може да се изведе
нивно меѓусебно копирање. Вежба
5-7.
Повторно напишете ја функцијата
readlines да меморира линии alloc за одржување на
во низа која е добиена од main наместо да се повикува меморискиот простор. Колку е побрза програмата?
5.7 Повеќед~мен3ионални
ни3и
С обезбедува правоаголни пове~едимензионални низ и 1 иако во практика тие се користат многу помалку отколку низите од покажувачи. Во овој дел
1
~е
објасниме некои од нивните карактеристики .
Ќе го разгледаме проблемот на конверзија на датум, од ден во месецот 1 во ден од годината и обратно. На пример 1 1-ви март е 60-rиот ден од непрестап на година, а 61-иот од престап на. За да ги изведеме претворањата
нираме две функции: во ден од годината,
day_of_year која додека month_ day
1
1
да дефи
~е врши претворање од месец и ден , пак, ~е претвора ден од годината во
соодветните ден и месец. Бидеј~и последната функција како краен резултат
има две вредности 1 аргументите ден и месец ~е бидат покажувачи :
month_day (1988 60, 1
&m 1
&d)
Покажувачи и низи
130
ги поставува
m на 2 и d на 29
Глава
5
(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 != for (i = 1 ; i < month; i++) day += daytab[leap] [i]; return day;
О
1 1 year%400
О;
/* 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; Спомнете си дека аритметичката вредност на логичките изрази, како што е слу чајот со
, па истата daytab о Низата daytab треба да биде надворешна и за day_of_year и за month_ day , со што и обете ќе бидат во мож ност да ја користат о Ја деклариравме за char за да ја илустрираме легитимната употреба на char за чување на мали незнаковни цели броеви о leap ,
изнесува или нула (невистина) или еден (вистина)
може да се користи како индекс за низата
Повеќедимензионални низи
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-тиот месец од годи ната. Ова е идеална ситуација за примена на внатрешна
name
static низа. month_
содржи приватна низа од стрингови и при повик враќа покажувач кон
соодветниот стринг. Овој дел ја обработува иницијализацијата на таа низа од стрингови.
Синтаксата е слична на претходните иницијализации:
/* month_name: враќа ние char *month_name(int n)
на n-тиот месец од годината
*/
{
static char *name[] = { "Illegal month", "January" , "February", "April ",
"Мау " ,
"М&rch" ,
"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
Покажувачи наспроти повеќедимензионални низи
5.9 Покажувачи
наспроти повеќедимен3ионални ни3и
133
Почетниците во С понекогаш се збунети од разликата помеѓу дводимензио нална низа и низа од покажувачи, како што е случајот со
name од
претходниот
пример. Ако се дадени дефинициите
int а [10] [20] ; int *b [10] ; тогаш и а[З]
[4]
и b[З]
[4]
се синтаксички легални референци кон една
вредност. Но а е вистинска дводимензионална низа:
200 локации
int
со int-го
лемина се резервираат, а конвенционалната правоаголна индексна пресметка
20*row+col се користи за пронаоѓање на елементот а [row] [со!] . Меѓутоа за b , дефиницијата резервира 10 покажувачи кои не се иницијализираат воопш то; иницијализирањето мора да се изведе експлицитно, било статички, било преку код. Претпоставувајќи дека секој елемент од
елементи, тогаш ќе се резервираат места за двесте
b покажува кон низа од 20 int променливи, плус 10
клетки за покажувачите. Предност на низите од покажувачи , е тоа што реди
ците од низата може да бидат со различни должини. Односно, секој елемент од
b
не мора да покажува кон низа од
кон низ и од
2 елемента,
20 елементи;
некои може да покажуваат
некои кон низи од ѕо, додека други да не покажуваат
кон ништо.
name: illegal month\0
Jan\0
1
Feb\0
1
Каr\0
1
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
о
Вежба
Маr\0
Feb\0
15
45
30
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]
е името со кое се повикува п рограмата , што значи
дека мини малната вредн ост што може да ја има но ст
1,
argc е 1 .
Ако
argc
има вред
тоа зна ч и дека после името н а програмата нема никакви други ар гуме н
argc има вредност з, а arqv [ о ] , ce" echo", " hello", и"wоrld" соодветно. Првиот опци онале н аргумент е argv[l] , додека последни от е argv[argc-1] ; додатно на ов а , ста нда рдот дефинира дека argv[argc] е NULL покажувач.
ти н а команд на линија. Во примерот п огоре ,
argv[l],
иargv[2]
5.10
Аргументи од ко ма ндна ли нија
Првата верзија на
echo ја третира argv како
#include /* echo арrуненти од хонандна main(int argc , char *argv[])
135
ни за од знако вни покажу ва чи:
линија ;
1 - ва верзија
*/
{
int i ; for (i = 1 ; i < argc ; i++) printf ( "% ѕ % ѕ", argv[i ], printf( " \n " ) ; return О ;
(i
< argc-1 )
?
"") ;
Бидејќи argv е покажувач кон низа од покажувачи , наместо со индексите на низата , можеме да манипулираме со самиот покажувач ко н неа . Следн ата ва ријанта е базирана н а зголемување на argv, кој е покажувач кон char , додека
argc одбројува наназад: #include /* echo арrуненти од хонандна main(int argc , char *argv[])
пинија ;
2-ра верзи ја
*/
{
while (--argc > 0) printf( "%s %s ", *++argv, printf( " \n") ; return О ;
(argc > 1) ?
"" ) ;
Бидејќи argv е покажувач кон почетокот на низата од аргуме нтните стрингови , неговото инкрементирање
argv[1]
наместо
argv[O].
тува на следниот аргумент;
(++argv)
го по ставу ва да п окажува ко н прв ич ниот
Секое п оследователно инкрементирање го поме с
*argv
е пока жува ч до тој ар гу ме нт . Во исто вре
ме , argc се декреме нтира ; кога ќе дојде до нул а, н ема повеќе а ргументи за
печатење. Алтернативно, можеме да напишеме printf наредба на следниов начин
printf ( (argc > 1) ?
"% ѕ "
:
"% ѕ ",
*++argv) ;
Ова покажува дека форматирачкиот аргумент на printf , исто така, може да биде израз. Како втор пример , ќе н а правиме некои подобрувања на програ
мата за пронаоѓање на облици, од Поглавје 4 . 1 . Ако се сеќавате, таму обл и кот кој се бара го поставивме длабоко во внатрешноста на п рограмата, што е едн о очигледно незадоволител н о ре ш е ни е. Правејќи па р ал ела со UNIX -o вa тa
програма grep, можеме да ја подобриме програмата па критериумот, кој треба да се пронајде , ќе го наведеме како прв аргумент од командна л инија.
Покажувачи и низи
136
Глава
5
linclude linclude 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 ако нема таква појава . Функцијата е декларирана во
.
Моделот може да се објас
ни сега, за да може да се илустрираат понатамошните конструкции со покажу
вачите. Претпоставете дека сакаме да воведеме два опционални аргумента. Едниот вели " печати ги сите линии освен оние кои се поклопуваат со наведе ниот критериум "; другата вели " пред секоја испечатена линија да се вметне
бројот на линијата ". Општоприфатена конвенција за С програмите на
UNIX системите , е дека ар flag)
гумент кој почнува со знакот минус, означува опционално знаменце (анг.
или параметар. Ако одбереме -х (за " исклучок") за да сигнализираме инвер
зија и - n ("број" ) за да обезбедиме нумерирано печатење , тогаш командата
find
-х
-n pattern
ќе ја печати секоја линија која не одговара на зададениот критериум, при што на
почетокот е ставен и бројот на линијата. Опционалните аргументи треба да се дозволени во кој било редослед , а остатокот од програмата треба да биде не зависен од бројот на аргументи што ќе ги предадеме . Уште повеќе , за корисни ците би било згодно да може опциски те аргументи да се комбинираат, како во
find -nx pattern Во продолжение следи нашето решение:
А р гум енти од ком андн а линија
5.10
137
#inelude #inelude #define МAXLINE 1000 int getline(ehar *line 1 int max) ;
/* find: tеоатеве на Ј1ИН5И1'8 xat одn:8С1Р8АТ main(int arge 1 ehar *argv[])
на~ зададеи со 1-испо аргумент
*/
{ eharline[МAXLINE];
long lineno = О; int е 1 exeept =О 1 number =О 1 found =О ; while (--arge >О && (*++argv) while (е= *++argv [О]) switeh (е) {
[О]=
'-
1
)
еаѕе ' х ':
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 е поставен , да речеме, на вредност 10, но
нејзиниот влез. Во оnшт случај,
може да биде изменет со опционален аргумент за да
tail -n ги печати последните n линии. Програмата треба да се однесува рационална без разлика колку се неразумн и влезните вредности за
n.
Напишете ја програмата
така што истата оптимално ќе го користи меморискиот простор ; линиите треба да се складираат на начин како што тоа беше при кажано во програмата за сорти
рање оД-поглавје 5 . 6, не во дводимензионална низа со фиксна големина/
Покажувачи кон функции
5.11
5.11
139
Покажувачи кон функции
Во С, функциите сами за себе не претставуваат променливи , но можно е да се дефинираат покажувачи кон нив, на кои може да и м се доделува вредноста, може да се постават во низа, да се предадат како аргументи на други функ ции, да бидат вратени од други функции , итн. Ние ќе го илустрираме тоа со моди фицирање на процедурата за сортирање, напишана пора н о во рамките на оваа глава, така што за зададен опционалниот аргумент
-n,
сортирањето на влез
ните линии ќе се прави по нумерички , а не по лексикографски пат. Сортирањето често се состои од три чекори
- споредба која го определува
редоследот на секој пар објекти, разм е на која го превртува нивниот ред ослед и сортирачки алгоритам кој изведува споредби и ра зм ени се додека елемент и
те не се распоредат како што треба. Сортирачкиот алгоритам е независен од
операциите на споредба и размена , па така со предавање на различни фун к ции за споредба и размена како негови аргументи , можеме да обезбедиме со р тирање по различни критериуми. Ова е пристапот кој го користиме во наш иот нов алгоритам за сортирање .
Лексикографското споредување на две линии, како и порано , се прави со
strcmp; исто така, ќе ни биде потребна и рутина nшncmp , која споредува две ли нии врз основа на нивната нумеричка вредност и ќе враќа исти резултати , како и
strcmp. Овие функции се декларирани пред main , а покажувач кон соодветната функција ќе се предава како аргумент на qsort. Во случајов го занемаривме валиди рањето на аргументите , за да можеме да се концентрираме на главните прашања . #include #include #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 *) ; сортира влезни линии */ main ( int argc, char * argv [] )
1* {
intnlines ;/ *бpoj на nрочитани линии од влез
int numeric =О; 1* 1 за нумеричко сортираље */ if (argc>l&&strcmp(argv[l] , " -n" ) numeric= 1 ;
==О)
*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 *
. Кој било тип на покажувач може да биде
и потоа да биде претопен наназад без да има загуба
на информација , па така ќе ја повикаме во
void*
тип.
qsort се
претопување на аргументите
Детаљното претопување на функциските аргументи ги прето
пува аргументите на функцијата за споредба. Во општ случај тоа нема никакво влијание на актуелната репрезентација , но ја уверува компајлерот дека се е во
најдобар ред .
/* 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 /* nwncшp: нумеричка споредба int nwncшp (char *sl, char *ѕ2)
на
sl
и ѕ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
Покажувачи и низи
Глава
void swap (void *v [] , int i , int
5
ј ;)
{
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 параметар за инде ксната категорија и
5.12
- n за страните)
Комп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
кон
се бази ра на граматика која наведува декларатор, компл етн о нав еден
во Додаток А, Поглавј е
dcl: direct-dcl:
8 .5;
ова е неговата п оеднос таве на форма
опционал *ѕ direct-dcl
name (dc/) direct-dcl() direct-dcl{onцucкa големина]
Кажано со збо рови, деклараторот некол ку * .
dcl е direct- dcl
на кој може да му п ретходат
dcl во мали за град и, direct-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 #include #include #define
МАХТОКЕN
enum
NАМЕ,
100
PARENS ,
void dcl (void} ; void dirdcl(void) ;
int gettoken(void) ;
ВRАСКЕТЅ
};
Глава
Покажувачи и низи
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
Комплициранидекларации
for
(*р++ =е ;
147
isalnum (е= geteh ()) ; )
*р++ =е;
*р= '\0'; ungeteh(e) ; return tokentype = NАМЕ ; } else return tokentype =е;
geteh и ungeteh ги
раз гледавме во Глава 4 .
Полесно е да се оди во обратна насока, посебно ако не се грижиме за гене рирање на одвишни мали загради. Програмата
undel
претвора псевдокод од
облик "х е функција која враќа покажувач кон низа од покажувачи кон функција која враќа
ehar" ,
што ќе го изразиме како
x()*[]*()ehar во
ehar ( * ( *х () ) []) () Скратената влезна синтакса ни дава можност повторно да ја користиме функцијата gettoken . undel ги користи истите надворешни променливи како и /* 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
О;
del .
148
Глава
Покажувачи и низи
Вежба
5-18 .
Направетеја
Вежба
5-19.
Изменетеја
dcl да биде отпорна
5
на грешки во влезот.
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 } ;
Основни nоими за структурите
6.1
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 структури.
struct rect screen ;
Ако screen го декларираме како
Структури
152
тогаш конструкцијата менливата
ptl 1
Глава б
screen. ptl . х се однесува screen.
на координатата х 1 од nро
која е членка на
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 (О О) ; screen .pt2 =makepoint(XМAX 1 УМАХ); middle = makepoint ( (screen. ptl. х + screen . pt2. х) / 2 1 ( screen. ptl . у + screen. pt2 . у) / 2) ; 1
Структури и функции
6.2
153
Следниот чекор е креирање на функции за аритметички операции со точки . На пример ,
/* addpoints: собираае на точки */ struct addpoint (struct point pl, struct point р2) {
pl.x +=р2 .х ; pl . у += р2 . у ; returnpl ;
Во овој случај и двата аргумента, како и вратената вредност, претставува ат структури. Ги зголемивме компонентите во
pl
не користејќи експлицитна
помошна променлива, со цел да потенцираме дека структурните параметри се
предаваат по вредност како и кај секоја друга променлива
Како друг пример, функцијата
ptinrect,
.
тестира дали една точка се наоѓа
во внатрешноста на еден правоаголник, со усвоена конвенција дека правоа голникот ги вклучува неговата лева и долна страна но не и горната и десната.
1* ptinrect:
врати еден &Jto int ptinrect(struct point
return &&
р.х р.у
r , во struct rect r)
р nриnаѓа на р,
>= r.ptl.x && >= r.ptl.y &&
р.х р.у
CIIpO'l'ИJIHO О
*1
< r.pt2.x < r.pt2 . y ;
Оваа зема предвид дека правоаголникот е претставен во стандардна форма, при што координатите на точката
ptl
се помали од координатите на
pt2 .
Следната функција враќа правоаголник за кој гарантира дека е во договорената (каноничната) форма :
ldefine min(a, b) ldefine max(a, b) /* canonrect: прсuаоаrопних
((а)
< (b) ?
(а)
((а)
> (b) ?
(а)
(b)) (b))
ханонизирај rи хоординатите на еден
*1
struct rect canonrect(struct rect r) {
struct rect temp ; temp.ptl.x = min(r.ptl . x , teшp.ptl.y = min(r.ptl . y, teшp.pt2.x = max(r.ptl.x, teшp.pt2.y = max(r.ptl.y , return temp ;
r . pt2.x) ; r.pt2.y) ; r.pt2 . x); r.pt2.y);
Структури
154
Глава б
Кога голема структура се предава како аргумент на функција , во општ слу чај поефикасно е да се предаде покажувач кој покажува кон неа, отколку да се копира целата структура . Покажувачите кон структурите се исти како и пока
жувачите кон обичните променливи. Декларацијата
structpoint *рр ; означува дека рр е покажувач кон структура од тип кажува кон
point структура,
struct 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 #inc1ude #inc1ude 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 е дефиниран во заглавјето о) Објект може да биде
променлива, низа или структура о Име на тип може да биде името на некој ос новен тип како што се
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
Глава б
читан о еден знак повеќе. Повикот кон што ќе го очекува
за прескокнување и
isalnum за
идентификација на бројки и букви; сите тие се функции од стан
дардното заглавје
Вежба
6-1 .
unqetch тој знак го враќа на влез каде следниот повик. Getword, исто така, ги користи isspace на п разните места, isalpha за идентификација на буквите
Нашата верзија на
qetword
не се справува правилно со знакот за
подвлечено, со константни стрингови , коментари или претпроцесорски кон
тролни линии . Напишете подобра верзија .
6.4 Покажувачи
кон структури
За да илустрираме некои поставки врзани со низите и покажувачите кон
структури, да ја напишеме повторно програмата за пребројување на клуч ни зборови од јазикот С , но овој пат преку покажувачи наместо преку инде кси на низи . Надворешната декларација на
binsearch имаат потреба
keytab
не се менува, но
main
и
од модификации .
Jinclude linclude linclude Jdefine МAXWORD 100 int qetword(char *, int); struct key *binsearch(char *, struct key *, int) ;
/* преброј~а.е main()
на ЈСЛУЧНИ зборови од јuижот С ; аерзија со по~чи
{
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
/"men of/
for
all/
/""-
good """
"
pa.rty
""" time their / """
to
a.id come За да откриеме дали еден нов збор веќе се наоѓа во дрвото, за почнуваме со коренот и го сnоредуваме новиот збор со зборот кој се чува во тој ја зол. Ако се совпаѓаат, прашањето се одговара nотврдно . Доколку новиот збор е помал , го продолжуваме барањето кај левото дете на тековниот јазол , во спротивно се префрламе на десното. Ако нема деца во бараната насока, новиот збор не е во дрвото, а најденото празно место, всушност, е и најсоодветното место за смес
тување на новиот збор . Овој процес е рекурзивен , бидеј ќи пребарувањето од
Структури
164
Глава б
кој било јазол го користи пребарувањето од некое од неговите деца. Поради тоа , за вметнување и печатење најсоодветна би била употребата на рекурзив ни рутини .
Навраќајќи се на поранешниот опис за јазол, најсоодветно е истиот да се претстави како структура со четири компоненти:
struct tnode { char *word; int count ; struct tnode *left; struct tnode *right ;
/* /* /* /* /*
јазол на дрвото
*/
nохажувач хон техст број на појавувааа лево дете десно дете
*/ */
*/ */
}; Рекурзивната декларација на јазолот може да ви изгледа необична, но е ко ректна . Не е дозволено структурата да содржи инстанца од самата себе, но
struct node *left; ја декларира променливата структура
left како
покажувач кон
tnode,
а не самата како
tnode .
Понекогаш е потребна варијација кај себереференцирачките (себеповику вачките ) структури : две структури кои покажуваат една кон друга. Еден начин тоа да се изведе б и бил:
struct t { struct
ѕ
*р ;
/*
р похажува хон ѕ
*/
};
struct
ѕ
{
struct t *q; /* q
похажув хон
t */
}; Кодот за целата програма е изненадувачки мал, земајќи ги предвид и ве ќе готовите функции како
getword, кои ги getword
вчитува зборови со помош на
напишавме претходно. Рутината
main
и ги сместува во дрвото со помош на
addtree . linclude iinclude iinclude idefine МAXWORD 100 struct tnode *addtree (struct tnode * , char *) ; void treeprint (struct tnode *); intgetword(char *, int);
Самореференцирачки структури
6.5 /* броеае main ()
на појавуаааа на апезни зборови
165
*/
{
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; 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;
if
(р = р
=
Структури
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 и сродните функции н а неа се декларирани во стан дардното заглавје
. Така, talloc може да се напише како:
#include / * 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 != NULL) strcpy(p , ѕ) ; return р ; if
'\0 ' */
(р
malloc враќа NULL доколку нема расположив простор ; strdup ја пренесува таа вредност понатаму, препуштајќи го справувањето со греш ките на повикувачот . Меморијата резервирана со повик кон
тр еба со повик кон функцијата Вежба
6-2 .
malloc се ослободува за п о вто рна free ; поглед н ете ги Глава 7 и Глава 8 .
упо
Напи шете програма која чита е програма и ги печати во алфабет
ски редосл ед сите групи на имиња н а променливи, што се иде нтични во првите
б знаци , н о различни некаде понатаму. Не бројте г и зборовите кои се во рамки на стрингови и коментари. На правете
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 nохажувачи
*hashtab[НASHSIZE] ;
/*
табела на
*/
Хеш функцијата , која се користи и од
lookup
и од
install ,
ја додава вред
носта на секој знак во стрингот на измешаната комбинација на претходните и го враќа остатокот по модул големина на низата . Ова не е најдобрата можна хеш
функција, но е кратка и ефективна.
/* hash : форкирај хеш вредност unsigned hash(char *ѕ)
~а
стрииrот ѕ
*/
{
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] intstrcmp(Strinq , Strinq) ; р (Strinq) malloc (100) ;
,
alloc (int) ;
=
Забележете дека типот што се декларира со 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 вклучувајќи ги
1 1 и о Нема да ја при кажеме целата библиотека овде би дејќи повеќе сме заинтересирани за пишување на е програмите кои ја корис
тат о Библиотеката е опишана детаљно во Додаток Б о
7.1
Стандарден вле3 и и3ле3
Како што веќе кажа вме во Глава 1
1
библиотеката имплементира едноставен
модел на влез и излез на текст о Текстуален поток се состои од секвенца од ли нии ; секоја линија завршува со знак за нов ред о Ако системот не работи на тој начин 1 библиотеката прави што било, што е потребно за да направи да изгледа дека е така тоа о На пример 1 библиотеката може да ги претвора знаците за нов ред (анго carriage return) и
linefeed во знак за
нова линија во влезот и повторно
назад во излезат о Наједноставниот механизам за влез е да се чита знак по знак
од стандардниот влез
1
во нормален случај тастатурата
int getchar (void)
177
1
со
getchar :
Влез и излез
178
при секој повик,
Глава
getehar
7
го враќа наредниот знак кој доа ѓа на вл ез, или EOF
кога доаѓа до крај на датотека. Симболичката константа EOF е дефинирана во
.
Вредноста обична и е
-1 , но проверките треба да бидат пишува
ни изразен и преку EOF, за истите да бидат независни од нејзината вредност. Во многу околини, датотеката може да биде заменета со тастатура со корис тење на конвенцијата за редирекција на влез<; ако програмата
getehar ,
prog користи
тогаш командната линија
prog
не е вклучена во аргументите од командна линија во
argv .
"
Промената на вле
зот, исто така, е невидлива ако влезот доа ѓа од друга програма преку механизам на цевка; кај некои системи, командната линија
otherprog 1 prog ги извршува и двете програми излезат на
otherprog на
otherprog
и
prog и го сnроведува prog . Функцијата
(а нг.
pipes)
стандардниот влез за
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
Форматиран излез
7.2
nред nрвата референца. Кога името е ставена во загради
- printf
179
< и >се прави преба UNIX
рување за заглавјето во стандардно множество од места (на пример, на
системи, обична е директориумот /usr/include)
.
Многу програми читаат само една влезна низа и запишуваат само една излезна низа ; за такви програми, влезот и излезат со getchar , putchar, и printf може да бидат целосно адекватни и тоа сигурно е доволно за почеток.
Ова особено е точно во случаите каде пренасочувањето се користи за повр зува ње на излезат од една програма со влезот на следната
.
На пример , да ја
разгледаме програмата подолу, која го преобразува нејзиниот влез во мали букви:
#include #include main() /* lower:
хокверзија на впезот во мапи бухви
*/
{
intc while ((е= getchar ()) ! = EOF) putchar(tolower(c)) ; return О ;
Функцијата
tolower
е дефинирана во
;
ги претвора големите во
мали букви и ги вра ќа другите знаци непроменети . Како што спомнавме прет
ходно, "функциите" како getchar и putchar во често пати се јаву ваат како макроа, со тоа избегнувајќи го непотребното трошење на време при
функциски повик за секој знак . Ќе покажеме како се прави ова во Поглавје в . ѕ . Без разлика како
функциите се
имплементирани на дадена машина ,
програмите кои ги користат немаат информација за множеството знаци.
Вежба
7- 1 . Напишете програма која ги претвора големите во мали букви или
малите во големи, во зависност од името со кое е повикана програмата, кое се
наоѓа во argv [О] .
7.2 Форматиран
излез
- printf
Излезната функција printf преведува внатрешни вредности во знаци. Во пре тходните глави ја користевме
printf
неформално . Описот овде ги покрива најо
пштите примени, но не е комплетен; за целосната при казна, видете го Додаток В .
intprintf (char *format, argl, arg2 , ... ) ;
180
Влез и излез
printf
ги конвертира 1 форматира и ги печати нејзините аргументи на стан
Глава
дарден излез под контрола на
fortnAt.
7
Го враќа бројот на испечатени знаци.
Форматираната низа содржи два типа на објекти: обични знаци 1 кои се ко пираат на излезната низа 1 и спецификации за конверзија
1
од кои секоја пре
дизвикува конверзија и печатење на следниот последова телен аргумент на
printf .
Секоја спецификација за конверзија за почнува со % и завршува со знак
за конверзија. Меѓу% и знакот за конверзија може да има
-
1
редоследно:
Знак минус , кој специфицира лево порамнување на конвертираниот аргумент. Број кој ја специфицира минималната ширин а на полето. Конвертираниот
аргумент ќе биде испечатен во поле широко најмалку колку оваа вредност. Ако е потребно ќе биде дополнет од лево (или десно, ако е побарано лево порамнување) за да се дополни ширината на полето.
-Точка , која ја одделува ширината на полето од прецизноста.
-
Број , прецизноста, која го специфицира максималниот број на знаци кои
ќе се печатат од стринг, или бројот на цифри по децималната запирка на деци мална вредност , или минималниот број на цифри за цел број .
- h
ако целиот број треба да се печати како
печати како
Знаците за конверзија се прикажани на табела 7
%,
short,
или
1
(буква ел) ако се
long . .1 .
Ако зна кот, кој следи после
не е според спецификација за конверзија, однесувањето е недефинирано.
Табела 7
. 1 Основни конверзии со printf
3нак
Тип на аргументот 1 се испишува како
d, i
int; декаден број int ; неозначен октален број (без водечка нула) int; неозначен хексадецимален број (без водечки Ох или ОХ), со користење на abcdef или AВCDEF з а 10 1 • • • , 15 int ; неоз начен декаден број int; еден з нак char* ; печати знаци од низата додека не се појави '\О ' или точно
о
х, Х
u е ѕ
колку бројот на знаци дадени со прецизноста.
f
double ;
[-] m. dddddd,
со
бројот
на
d-овци
е
за дадена
преци зноста (предефинирана вредност е
е , Е
6) . double ; [-]m . dddddde+- xxili [-]m .ddddddE+- xx ,
собројот на
d-овци е зададена преци з носта (nредефиниран а вредност е
g, G
double ;
користи % е или % Е ако експоне нтот е помал од
поголем или
еднаков
на
п ре цизноста;
инаку се
6) .
-4
користи
или
%f .
Завршни нули или завршна децималн а точка не се печ атат . Р
void* ;
%
не се конвертира ниеден аргумент; печати %
пока жувач (репрезентација зави с на од имплеме нтац ија)
Форматиран излез-
7.2
printf
Ширина или прецизност може да се специфицираат со помош на
*,
181 во кој
случај вредноста се пресметува со конверзија на следниот аргумент (кој мора
да биде цел број)
. На пример, за да се испечатат најмногу max знаци од низа ѕ ,
printf (" %. *ѕ " ,
1114Х , ѕ) ;
Повеќето од конверзиите кои ги обезбедува форматирачката низа , беа илу стрирани во претходните глави. Еден исклучок е прецизноста бидејќи се одне сува на низи. Следната табела го покажува ефектот на различните специфика ции во печатењето на
" hello , world" (12
знаци)
.
Ставивме две точки пред и
после секое поле за да се види како тоа се проширува .
:%ѕ: :%10ѕ:
:%.10ѕ:
:% -lOs : :%.15ѕ: : %- 15ѕ:
: %15.10ѕ: :%ѕ-15.10:
Предупредување:
: hello, world : :hello, world: : hello, wor: : hello, world : :hello , world: :hello, world hello, wor: : hello , wor printf
го користи својот прв аргумент за да одлучи
колку аргументи следат и кој е нивниот тип. Ако не постои доволен број на
аргументи или ако се од погрешен тип, printf ќе се збуни и вие ќе добиете погрешен резултат . Треба, исто така, да бидете свесни за разли ката меѓу овие два повика :
printf(s); printf( " %s " ,s) ; Функцијата
sprintf ги
1*
ОТ КАЖУВА ако ѕ со држи
/*
БЕЗБЕДН О
% *1
*/
прави истите операции како и
printf ,
но го зачуву
ва излезот во низа :
int sprintf (char *strinq, char *forlll4t, arql , arq2, ... ) ; sprintf ги форматира аргументите во arql, arq2 , итн . , според полето forlll4t како претходно, но го сместува резултатот во стринг , н аместо на стан дардниот излез; стрингот мора да биде доволно голем за да го прими резулта тот.
Вежба
чин
.
7-2 .
Напишете програма која ќе печати произволен влез на разумен на
Како минимум, треба да печати неграфички знаци во октален или хекса
децимален броен систем според локалниот начин, и да ги подели текстуални линии .
долгите
182
Влез и излез
7.3 Листи
Глава
7
на арrументи со променnива доnжина
Овој дел содржи имплементација на минималната верзија на printf, за да при каже како да се напише функција која процесира аргументна листа со про менлива големина во преносна (портабилна) форма
. Бидејќи, главно, сме за minprintf ќе ја процесира повикува вистинскиот printf
интересирани за процесирањето на аргументите ,
низата за форматирање и аргументите, но ќе го
во случаите каде е потребна конверзија на форматот . Соодветната декларација за
printfe intprintf(char*fmt , ... )
каде што декларацијата
. . .
значи дека бројот и типовите на нејзините аргу
менти може да варира. Декларацијата
...
може да се појави само на крајот од
листата со аргументи . Haшиoтminprintf е деклариран со
voidminprintf (char *fmt, ... ) бидејќи не го враќа бројот на знаци како што прави printf. Незгодата сега е како mi.nprintf да се движи низ листата со аргументи, кога таа листата нема ниту име. Стандардното заглавје
содржи
множе
ство од макродефиниции кои дефинираат како се одвива движењето низ лис тата од аргументи . Имплементацијата на ова макро ќе варира од машина на машина , но интерфејсот кој го нуди е универзален. Типот va_list се користи за декларација на променлива која ќе се однесува на секој аргумент ; кај
minprintf , оваа променлива е наречена ар , за " по va_surt го иницијализира ар да покажува
кажувач кон аргумент " . Макрото
на првиот неименуван аргумент. Потребно е да се повика пред да се користи ар. Мора да има најмалку еден именуван аргумент ; последниот именуван ар гумент се користи од страна на
Секој повик на
va_arg користи
va_start за да започне со работа. va_arg враќа еден аргумент и го поместува ар на следниот ;
име на тип з а да одреди кој тип да го врати и колкав чекор тре
ба да преземе. Конечно , va_end прави какво било чистење што е потребно. Мора да биде повикан пред да заврши програмата . Овие својства ја формираат основата на нашиот поедноставен
printf :
Форматиран влез -
7.4
iinclude /* minprintf : минииалиа фуикциј а printf листа на аргументи */ void minprintf(char *fmt , . .. )
scanf
183
со промеилива
{
va_list ар ; / * nокажува char *р, *sval ; int ival ; double dval ; va_start(ap, fmt) ; /*
кои секој од беѕимеките
*/
нека пронеиливата ар nокажува
на првиот аргумент без име
for(p=fmt ; *p ; p++){ if(*p!='% ' ){ putchar (*р) continue ;
arg
*/
;
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
Глава
int scanf (char *format 1
•
•
7
• )
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
%1
опци
опционален број за спецификација
опционални
h 1 1 или L кои ја покажуваат
и знак за конверзија .
Спецификацијата за конверзија ја насочува конверзијата на следното влез но поле . Нормално резултатот се сместува во променливата кон која покажува соодветниот аргумент. Ако е индицирана забрана за доделување илустрирана со знакот
*
1
тогаш се прескокнува влезното поле; не се прави никакво доде
лување. Влез но поле се дефинира како низа од непразни знаци ; се протега до следното празно место или доколку е специфицирана неговата широчина , до неговиот крај . Ова не наведува на тоа дека
scanf
ќе чита и надвор од гра
ниците на редот или линијата 1 за да го најде својот влез 1 бидејќи знаците за
Форматиран влез-
7.4 нова линија се
return,
nразни
места. (Пусти знаци се бланко, нова линија,
вертикален таб и
Знакот
за
scanf
185
carriage
formfeed .)
конверзија
индицира
интерnретација
на
влезното
поле.
Соодветниот аргумент мора да биде покажувач , што и се бара од семантиката на повикување по вредност во е. Знаците за конверзија се прикажани во табе ла
7.2. Табела
7.2 : Основни scanf конверзии
Знак
Податок на вле3; Тип на аргументот
d i
Цел број во декаден запис; Цел број;
int*
int
о
Цел број во октален запис (со и без водечка нула)
u
Неозначен цел број;
х
Цел број во хексадекаден запис (со или без почетна Ох или ОХ)
е
Знаци;
char
unsiqned int*
*.Следните влезни знаци (иницијално
1)
се сместени
на означеното место. Нормалното прескокнување преку п разните места е исклучено;
за да се пр очита следниот непразен знак се
користи % 1ѕ
ѕ
Низа од знаци
(без наводниц и );
char *,
покажува кон низа од
знаци доволно долга за стрингот и за завршна '\0' која ќе биде додадена.
Реален број со опционален предзнак
e , f,g
точка и опционален експонент ;
, опционална децимална
float*
Знак% ; не се прави никакво доделување.
%
Знаците за конверзија
d, i,
о,
u,
и х може да му претходат на
покаже дека во листата на аргументи се појавува покажу вач кон
кон
int,
или со
1
h, short
за да се наместо
(буква ел) за да се покаже дека се јавува покажувач кон
1ong
во листата на аргументи.
Како прв при мер, ќе го земеме рудиментираниот калкулатор од Глава 4 , кој сега може да се напише со
scanf ,
која ќе ја изведува конверзијата на подато
ците кои доаѓаат на влез:
#include 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
запишува во неа и дали се случиле грешки или крај на датотека. Корисниците не мора да ги знаат деталите , затоа што дефинициите добиени од вклучуваат структурна декларација наречена
FILE.
Единствената де кларација
потребна за покажувач кон датотека е дадена со следниот пример
FILE *fp ; FILE * fopen ( char *n&llle, char *mode) ; Ова кажува дека fp е покажувач кон FILE и fopen враќа покажувач на FILE.
Забележете дека FILE е име на тип , како int , а не таг (ознака) на структура та; се дефинира со typedef. (Детали за тоа како fopen може да се имплемен тира на
UNIX систем
Повикот за
fopen
дадени се во Поглавје 8. ѕ) во програма е
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 наместо како функции, може да се дефинираат како макроа. Кога се стартува е програма, околината на оперативниот систем е одговор
на за отворање на три датотеки и обезбедување на покажувачи кон нив . Овие датотеки се наречени
:
стандарден влез,
стандарден
излез,
грешка; соодветните покажувачи кон датотеки се наречени
stderr,
а се декларирани во
татурата, а
stdout и stderr
.
Нормално
се поврза ни со екранот,
и стандардна
stdin, stdout
и
stdin е поврзан со тас но 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
Глава
#include /* cat: кадоврзува датотеки , main(int argc, char *argv[])
верзија
7
1 */
{
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 во void filecopy(FILE *ifp, FILE *ofp)
датотеха
ofp */
{
int
е;
while ((е= getc(ifp)) putc(c, ofp);
!= EOF)
Покажувачите кон датотеки 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
stderr на ист начин
како што се
stdin и stdout .
нормално се појавува на екранот дури и кога стан
дардниот излез е пренасочен .
Да ја преправиме
cat за
да ги пишува нејзините пораки за грешка на стан
дардната грешка .
#include /* cat: надоврзува датотеки, main(int argc, char *argv[])
верзија
2 */
{
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 За излез , функцијата
fputs
193
запишува стринг (кој не мора да содржи знак за
нова линија) во датотека:
int fputs (ehar *line, FILE *fp) враќа
EOF ако се случи грешка , а во спротивно ненегативна вредност.
Библиотечните функции gets и puts се слични на fqets и fputs , но оперираат на
stdinиstdout. Забунаправифактотдека, gеtзгобрише
'\n',
арutзгододава .
За да покажеме дека нема ништо посебно во врска со функциите fqets и
fputs ,
ќе ги при кажеме истите , копирани од стандардната библиотека на нашиот систем :
/* fqets : преземи најкногу до n знаци од ehar *fqets(ehar *ѕ, int n , FILE *iop)
датоте~а
iop */
{
reqister int е ; reqister ehar *еѕ; еѕ
=
ѕ ;
while (--n> О && (е= qete(iop)) != EOF) if ((*еѕ++ =е) == '\n ' ) break ; *еѕ = '\0 '; return (е == EOF && еѕ ѕ) ? NULL ѕ; /* fputs : запиши етрииг ѕ во int fputs (ehar *ѕ , FILE *iop)
датоте~а
iop */
{
int е; while (е= *ѕ++) pute (е , iop) ; return ferror ( iop) ? EOF :
Стандардот специфицира дека
EOF за
О
;
ferror враќа ненула за грешка ; fputs
грешка и ненегативна вредност во спротивно.
Лесно е да се имплементира нашата
qetline од fqets:
/* qetline: вчитува лииија , враха int qetline (ehar *line, intmax)
допжина
{
if (fqets (line, max, stdin) return О ; else return strlen (line) ;
=NULL)
*/
враќа
Влез и излез
194 Вежба
7-6 .
Глава
7
Напишете програма за споредба на две датотеки 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 кои се наоѓаат во . Кај следните 1 ѕ и t се ehar * 1 а е и n се int.
strcat (ѕ t) strneat(s 1 t 1 n) strcmp (ѕ 1 t) 1
strncmp(s 1 t 1 n) strcpy (ѕ 1 t) strncpy(s 1 t 1 n) strlen(s) strchr(s 1 c) strrehr(s 1 e)
додај го t на крајот од ѕ додај
n
знаци од t на крајот од ѕ
врати н е гати вна
исто
1
нула
1
или позитивна вредност за
s t како strcmp 1 но само за
првите
n
знаци
ко пирај го t во ѕ
копирај најмногу n зн аци од t во ѕ врати ја долж ин ата на ѕ врати покажувач кон првиот е во ѕ
1
или
врати п окажувач кон последниот е во ѕ
1
NULL ако
го н ема
или NULL а ко го нема
7 .8.2 Тестирање на класата на знакот и конверзија Неколку функции од
и зведуваа т тестови на знаци и нивни кон
верзии. Во следниот дел 1 е е int кој може да се претста в и како unsigned char или EOF. Функциите враќаат
int .
Разни фун кци и
7.8 isalpha (е) isupper (е) islower {е) isdigit (е) isalnum(e) isspaee (е) toupper (е) tolower (е)
195
ненула ако е е алфабетски , о ако не е ненула ако е е голема буква , О ако н е е ненула ако е е мала бу ква , о ако не е ненула ако е е цифра, о ако не е н е н ула ако
isalpha (е)
или
isdigit(e) , О ако н е е return , fonnfeed, вертикалентаб
ненулаакосебланко, таб , новред,
го враќа е претворено во голема буква го враќа е претворен о во мала буква
7.8.3 ungetc Стандардната библиотека овозможува прили чно о граничена верзија на функцијата
ungeteh која ја
н апи ш авме во Глава 4 ; се н ар е кува
ungete .
int ungete (int е, FILE *fp) го става знакот е назад на датотеката
EOF за грешка. ungete може да се користи со која било од влезните функции како seanf , gete , или g etehar . fp
и вра ќа или е, или
Се гарантира само еден повратен знак по датотека.
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
враќа покажувач до доволно слободен простор за низа од
n
7
објекти од специ
фиц.ираната големина , или NULL ако барањето не може да биде задоволено. Меморијата е иницијализирана на н ула.
Покажувачот вратен од malloc или calloc го има соодветната подреду вање за објектот што е во прашање, но мора да биде претопен во соодветниот тип, како
int *ip; ip = (int *) calloc (n, sizeof (int)) ; free (р) го ослободува просторот покажуван од р , каде што р бил оригинално добиен со повик кон malloc или calloc . Не постојат ограничувања на редо следот по кој се ослободува простор, н о е огромна грешка да се ослободи неш то што не е добиено со повик на malloc или calloc .
Исто така, грешка е да се користи нешто откако било ослободено. Вообичаен, но неточен дел од код, е овој циклус кој ослободува објекти од листа:
for (p=head; free(p) ;
р
!=NULL ; p=p->next)
/*
ПОГ РЕШНО
*/
Правилниот начин да се изведе тоа е следниот:
for (p=head; р !=NULL ; p=q) q=p->next; free(p) ;
Поглавје 8 . 7 ја прикажува имплементацијата на заземач на меморија како malloc , во кој зафатените блокови може да бидат ослободени во кој било ре дослед.
7 .8 .6
Математички функции
Постојат повеќе од 20 математички функции декларирани во ; овде се некои од тие кои се почесто користени. Секоја зема еден или два
double аргумента и враќа double.
Разни функции
7.8 sin(x)
синус
соѕ (х)
к ос ину с
atan2(y , x)
арку с
ехр(х)
експоненцијал на
log(x)
приро ден
loglO(x)
стандарден
pow(x,y)
хУ
sqrt (х)
квадр атен
корен
(х)
апс олу тна
вредн ос т
fabs
од х , од
х х,
т анге нс
е
во
х е од
197
р адиј ани в о радијани
у/х ,
во ради ја н и
функција
( со ос н ова е ) ( со осн ова
10)
од х на
ех
л о гаритам о д х логаритам од х
(х>О) (х>О )
(х>О) х
7.8.7 Генерирање на случаен број Функцијата rand () пресметува секвенца од псевдо случајни цели броеви во опсегот од о до RAND_ МАХ кој е дефиниран во . Еден начин за креи рање на случајни децимални броеви поголеми или еднакви на нула, но помал и од
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 outfile Во таков случај, командниот интерпретер ги заменува однапред дефинира
ните доделувања за датотечните дескриптори о и нормален случај, деск рипторот
2 останува
1 со наведените датотеки. Во
поврзан за мониторот, со цел пора
ките за грешка да се печатат таму . Слич н о разм ислување важи и за влезот и из
лезат врзани преку цевка (а нг.
pipe) .
Програмата не знае од каде доаѓа влезот
ниту каде оди излезат, се додека ги користи датотеката о за влез и датотеките и
2 за
1
излез .
8.2 Примитивен 1/0- read и write Влезот и и злезат ги користат системските повици
read и write, до кои од read и
С про грамата се пристапува преку две функции, наречени соодветно
wri te.
Кај двете, првиот аргуме нт е дескриптор од датотека. Вториот аргумент
е знаковна низа која означува од каде доаѓаат и каде одат податоците во вашата програма. Третиот аргумент е бројот на бајти кои треба да се префрлат.
int n_ read
= read(int
int n written
fd , char *buf, int n) ;
= 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 беа компајл ирани со вклуче на , ќе #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 int fd ; int open(char *name , int fiags , int perms) ; fd = open(name, fiags , perms) ; Како и кај
fopen, аргуме нтот name е з наковен стринг кој содржи име н а дато int кој специфицира на кој начин да се отвори
тека . Вториот аргимент fiags е
датотеката; главните вредности се :
О О
О
RDONLY WRONLY RDWR
отвори само за читање
отвори само за запишување
Кај системите
отвори з а читање и за запишување
system v UNIX, овие константи се дефинирани во , (BSD) во заглавишната датотека .
а кај верзиите на Berkley
За да се отвори една датотека за читање
fd
= open(name ,
За примените на
O_RDONLY,O);
open
кои ќе ги раз гледаме, аргументот
perms секогаш
има
вредн ост нула.
Обид за отворање на датотека која не постои резултира со грешка. За кре ирање на нова датоте ка и повторно за пиш ување во н екоја постојна , се користи системскиот повик
crea t.
int creat(char *name, int perms) ; fd
= creat(name ,
perms) ;
враќа дескриптор кога е успешно креирана датотека и -1 во спротивен случај. Ако датотеката веќе постои,
creat
ќе ја скрати нејзи ната должина на нула , со
тоа ослободувај ќи ја претходната содржина на датотеката; повик на
creat врз
веќе постојна датотека, не се смета за грешка . Ако датотеката не постои , аргументот
perms.
creat ќе ја
креира со привилегиите наведени со
Кај датотечниот систем на
UNIX, резервирани се девет би
тови на информација врзана за датотека, кои контролираат привилегии, како читање, запишување, и извршување , за еден сопстве ник, група на сопственици
и за сите останати. Следи дека трицифрените октални броеви се соодветни за специфицирање на привилегиите . На пример, 0755 означува привилегии н а читање, запишување и извршување за сопственикот, а привилегии на читање и изврш ување за групата и за сите останати.
За илустрација, покажа на е поедноставена верзија на UNIX-oвaтa програ ма ср, која копира една во друга датотека. Н а шата верзија ко п ира само една
204
Системски интерфејс на
Глава
UNIX
8
датотека, не дозволува вториот аргумент да биде директориум и наместо да ги копира
, самата
ги креира привилегиите,
#include #include #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 . Функцијата од стандардната библиотека слична на
printf,
vprintf е
со исклучок дека променливата листа на аргументи е заме
нета со единствен аргумент, кој се иницијализира со повик кон ма крото va_ start . Слично, vprintf и vsprintf соодветствуваат на fprintf и sprint . Постои ограничување (вообичаено околу 20) за бројот на датотеки кои може
да бидат отворени во рамките на една програма. Односно, секоја програма што
има намера да обработува повеќе датотеки мора да биде подготвена на преиско ристување на датотечните дескриптори. Функцијата close (int fd) ја раскину ва врската помеѓу дескрипторот и отворената датотека и го ослободу ва дескрип торот за употреба со некоја друга датотека ; соодветствува на функцијата fclose
од стандардната библиотека, со таа разлика што не поседува бафер кој треба да се испразни . Прекинувањето на програмата со повик на
exit
или преку наред
бата return од главната програма ги затвора сите отворени датотеки.
Случаен пристап
8.4
- lseek
205
#include #include /* error: исnечати nopaxa за void error(char *fшt, ... )
rрешха и умри
*/
{
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)
ја отстранува датотеката
от систем. Соодветствува на функцијата
remove
name од датотечни
од стандардната библиотека.
Вежба 8-1. Напишетеја повторно програмата саtод Глава 7 , користејќи read, wri te, open и close на местото на нивните еквиваленти од стандардната биб лиотека. Направете тестови за да ја определите релативната брзина на двете верзии.
8.4 Сnучаен пристап - lseek Излезат и влезот нормално се секвенцијални : секој повик на
wri te
read
или
за почнува на позиција во датотеката која следи веднаш после претход
ниот таков повик. Сепак, кога е тоа потребно, од датотеката може да чита или во неа да се запишува во произволен редослед . Системскиот повик
lseek,
обезбедува можност за движење низ една датотека, без читање или запишу вање на никакви податоци.
lonq lseek(int fd , lonq offset , int oriqin) ; ја поставува тековната позиција на вредноста на дескриптор е
fd, земено
offset,
во датотека чиј
релативно во однос на локацијата наведена во
Следното читање или запишување ќе за почне од таа позиција.
биде о, 1, или
origin . origin може да
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 ;
Вратената вредност од тотеката, или
-1 ако
lseek
е од тип
long
роѕ
*/
и ј а дава н овата п озиција во да
настане н екоја гре шка. Функцијата
fseek од стандардната
библиотека е лична со lseek, со таа разлика што п рвиот аргументе е FILE* и во случај да настане грешка, вратената вредн ост е различна од нула .
8.5
Пример
-
импnементација на
fopen
и
getc
ќе илустрираме како некои од овие делови формираат цел ина, преку приказ на една имплементациј а н а рут ините од стандардната библиотека, fopen и getc . Спомнете си дека датотеки те од стандардната библиотека се опи шани пре ку по кажувачи кон д атотеки, а н е пр еку дескриптори . Покажувач ко н датотека
претставува покажувач кон структура која содржи некол ку делови информација
за датотеката: покажувач кон бафер, со што датотеката може да биде чита на во големи бло кови; бројач за бројот на преостан ати знаци во баферот; покажувач кон наредната позиција на знак во баферот; датотечен дескриптор; и з наменца кои ги опишуваат пр ивилегиите на читање /за пишување, статус н а грешки, итн.
Податоч ната с труктура која о пишува едн а датотека, е сместена во за гл авј ето
, кое мора да биде вклучено ( со #incl ude) во секоја изворна да тотека што користи рутини од стандардната влезно/излезна библиотека. Таа, и сто така , е вкл учена во функциите од таа библи отека. Во следниот извадок
Пример
8.5 од стандардното заглавје
,
- имnлементација на fopen и getc
207
имињата кои се предвидени за посебна
употреба само за функции од библиотеката, започнуваат со знак за подвлече но, со што е помалку веројатно дека ќе дојдат во конфликт (ќе се поклопат) со имиња од корисничката програма. Оваа конвенција се користи од сите рутини
на стандардната библиотека.
#define #define #define #define
NULL О EOF {-1) BUFSIZ 1024 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, UNВUF = 04, EOF = 010 , ERR = 020
/ * датотека отворена за читање * / /* датотека отаорена за заnишуваље */ /* датотеката е небаферирана */ /* EOF се nојави во оваа датотека */ /* грешка се појави во оваа датотека */
};
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, што обезбедува нивна позитивност . Иако нема да зборуваме околу детали те, ја вклучи вме дефинициј ата на за да покажеме дека функционира на многу сличен начин ка ко и вајќи функција
_flushbuf
getc,
putc
повику
кога ќе се наполни нејзиниот бафер . Исто така, вклу
чивме макроа за пристап до на станата грешка, до статусот за крај н а датотека та како и до нејзиниот дескриптор.
Сега може да се напише функцијата
fopen .
Поголемиот дел од неа опфаќа опера
ции за отворање на датотеката и позиционирање на вистинското место, како и поста
вување на битовите-знаменца да ја покажуваат соодветната состојба.
fopen не ало
цира простор за баферот; тоа се прави од_fillbuf при првото читање на датотеката . Иinclude
" syscalls . h " fdefine РЕRМЅ 0666 / * RW Иinclude
за со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
fopen и getc
209
не се справува с о сите модови за пристапни привилегии
кои ги нуди стандардот, иако додавање на некои од нив не б и зафатило премно гу код . Конкретно, нашата
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 мора да биде дефин и рана и иницијализирана за
FILE
-
{ { { } ;
iob[OPEN_МAX] о, о, о,
(char * ) (char *) (char *)
о, о, о,
=
{ /* (char (char (char
stdin, stdout и stderr:
stdin , stdout, stderr */ *) о, _READ , о } , *) о, _WRITE , 1 } , *) о , _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) fp е по кажувач ко н датотека , а н е int ста тус, а н е позиција. Н апи шете ј а fseek. Осигурете се дека вашата fseek соодветно коо рдинира со бафери е идентична н а
fseek
со таа разлика што
датотечен дескр иптор , к ако и што враќа
рањето напра вено за другите функции од библиотеката .
8.6
Пример- Листање содржина на директориуми
Понекогаш се употребува друг приод на интеракција со датотечниот систем
-
утврдување н а информација околу некоја датотека, а не што таа содржи. Пример за тоа е командата
ls за листање на директориумите во UNIX- ги печ а ти
имињата
на датотеките во еден директориум, и во случај на потреба, други информации како големини, привилегии, итн . Аналогна е командата Бидејќи директориум во
dir во мѕ-ооѕ . UNIX се третира к ако датотека, един стве но што тре
ба ls да направи е истиот да го прочита, како би дошла до имињата на датоте ките во н его. Но, за да се п р иста п и до дру ги информации околу една датотека, н а приме р, нејз ината гол емина, потребно е да се кор исти системски повик. На
други системи , системски п ови к може да биде потребен, дури и за приста п до имињата на датотек ите; н а пример, таков е случајот со мѕ-ооѕ . Он а што сака ме е да се обезбеди пристап до саканата информација, на н ачин релативно не зави сен од системот, иако имплементацијата може да биде в исоко зави сна од системот.
ќе илустрираме дел од ова со пишува ње програма наречена fsize. Функцијата fsize е специј ал на форма на ls која ги печати големи ните на сите датотеки наведени во нејзината листа од аргументи кои доа ѓаат од командна
линија. Ако еде н од датотеките е директориум, fsize се п рименува себеси ре курзивно н а тој директориум. Во слу чај вооп што да нема а ргуме нти , го проце сира те ковн иот директо р и ум.
Да започнеме со кра ток п реглед врз структурата н а UNIХ-овиот датотече н с и стем. Директориум е датотека која содржи л и ста н а ими њ а од датотеки и
не каква информација за тоа каде ти е се лоцирани. "Локација та" е ин декс во
8.6
Пример- Листање содржина на директориуми
друга табела наречена
" inode list". " inode"
211
за една датотека е место каде се
чува целата информација врзана за некоја датотека, со исклучок на нејзиното име. Еден директориумски запис обично се состои од два елемента, име на датотеката и број на inode-oт . За жал, форматот и точната содржина на еден директориум не се исти на сите верзии на системот. Така, ќе ја поделиме таа задача на два дела со цел да ги изолираме непреносливите делови. Надворешното ниво дефинира струк тура наречена
Dirent
и три рутини
opendir, readdir
closedir
и
за да се
обезбеди пристап до името и бројот на inode-oт во еден директориумски за
пис кој е независен од системот. Ке ја напишеме
fsize со тој интерфејс . Потоа
ќе покажеме како истите да се имплементираат на систем и кои користат иста директори умска структура како
version 7 и v UNIX;
варијантите се оста вени за
вежбање . Структурата Dirent ги содржи
inode бројот и името.
Максималната должи
opendir вра ќа по DIR, која е аналогна на FILE структурата што се користи од readdir и closedir. Оваа информација се зачувува во д атотека наречена dirent. h. на на името е NАМЕ_МАХ, чија вредност зависи од системот. кажувач кон структура наречена
#define
NАМЕ МАХ
14 /*
најголема должина на име на датотека*/
/*
заииси од системот
typedef struct { /* nренослив директориуиски long ino ; /* број char name[NAМE_МAX+1] ; /*име+ терминален Dirent; typedef struct int fd ; Dirent d ; DIR ;
/* /* /*
минииален
DIR :
*/
запис*/
на
inode*/ '\0' */
знак
без баферираае ,
итн.*/
датотечен дескриптор за директориум*/ директориуискиот запис*/
DIR *opendir(char *dirname) ; Dirent *readdir(DIR *dfd); void closedir(DIR *dfd); Системскиот повик stat зема име на датотека и ја враќа целата информација што ја содржи inode - oт за таа датотека, или
char *name; struct stat stbuf; int stat(char *, struct stat *) ; stat(name, &stbuf) ;
-1 во случај на грешка.
Односно,
Системски интерфејс на
212
ја исполнува структурата
stbuf
Глава
UNIX со
inode
8
информацијата за името на датотека
та. Структурата што ја опишува вредноста вратена од stat се наоѓа во <ѕуѕ/
stat. h>, и
во општ случај изгледа вака:
struct stat
/* inode
dev t ino t short short short short dev t off t
st_dev ; st_ino ; st_mode ; st_nlink ; st_uid; /* st_ gid ; / * st_rdev ; st_size ; t~e_t st_atime ; time_t st_mtime; time t st_ctime;
информацијата вратена од
/* inode уред* / /* inode број */ /* битови за модот*/ /* број на врсхи ~он
stat*/
датоте~а* /
~орисничхи идентифи~атор на соnствени~от* / корисничхи идентифи~атор на груnата* /
/* /* /* /*
/*
за сnеција.пни датоте~и*/ големина на датоте~ата во знаци*/ време на nоследен nристаn
*/
време на nоследна модифи~ација
*/
време на nоследна промена на inode-oт
*/
}; Повеќето од овие вредности се објаснети со коментарите. Податочните типови како
dev_t и ino_t се дефи нирани во , која, исто така, мора да . За писот ts_ mode содржи м ножество знаме нца за опис на датотека . Н ивните дефиниции, исто така, се вклучени во ; потребен ние само делот биде вклуч ена
кој се справува со датотечниот тип : Ѕ
#define #define #define #define #define /*
.. .
Ѕ Ѕ Ѕ Ѕ
IFМТ 0160000 IFDIR 0040000 IFCHR 0020000 IFВLK 0060000 IFREG 0010000
/* /* /* /* /*
тиn на датотека*/
*1 */ nосебен бло~ */ реrуларно */ дире~ториум nосебен
*/
Сега сме подготвени да ја напишеме програмата од
fstat
зна~
fsize . Ако модот доби ен
наведува дека датотеката не е дир е кториум, тогаш големината ние
доста п н а и може веднаш да се п е чати. Ако името претста вува директориум, то
гаш истиот мора да го обработиме датотека по датотека; истиот може да содржи и п од-директориуми , па процесот е рекурз ивен .
Главната рутина
(main) се справува со аргументите од командна линија ; го fsize .
предава секој аргуме н т н а функцијата
Пример - Листање содржина на директориуми
8.6 #include #include #include #include #include #include #include
" syscalls . h " " dirent.h"
/* /* /*
213
знаменца за читање и заnиwуваље дефиниции ка nодат очки тиnови струхтура вратена од
*/
*/
stat */
void fsize(char *) /* nечатеае на име ка датотеха * / main(int argc , char **argv) {
/* nрвичко : if (argc == 1) fsize (". " ) ; else while (--argc > О) fsize(*++argv) ; return О ;
теховек директориум
*/
Функцијата
fsize ја печати големината на датотеката. Ако датотеката е ди fsize прво ја п ов икува dirwalk за да се справи со сите да тотеки во него . Забележете како знаме нцата имен ува н и ѕ_IFМТ и ѕ_ IFDR од се користат за определување дали една датотека е директориум . ректори ум, тогаш,
Заградите се п отребн и, бидејќи приоритетот на & е понизок од он ој на= .
int stat(char *, struct stat *) ; void dirwalk(char * , void (*fcn) (char *) );
/* fsize: nечати име на void fsize(char *name)
датотехата
" name " */
{
struct stat stbuf ; if (stat(name, &stbuf) == -1) { fprintf(stderr , " fsize : can ' t return ;
ас се ѕѕ
%s \ n ", name) ;
if ((stbuf. st_mode & Ѕ IFМТ) = S_IFDIR) dirwalk(name , fsize) ; printf( "%81d %s \ n " , stbuf.st_size , name); Функцијата
dirwalk е општа
рутина која применува функциј а врз секоја да
тотека во еден директори ум. Го отвора директо риу мот, поминува н из датоте
ките во него, повикувајќи ја функцијата за секоја од нив, потоа го затвора ди рек ториумот и се вра ќа. Бидејќи
fsize ја п овикува dirwalk за
двете фу нкции рекурзивно се повик уваат една со д руга.
секој директор иум,
Системски интерфејс на
214
#define
МАХ_РАТН
Глава
UNIX
8
1024
/* dirwalk: nримени ја fcn врз сите датотехи void dirwalk(char *dir 1 void (*fcn) (char *))
во
dir */
{
char name[МAX_PATH] Dirent *dp ; DIR *dfd ;
;
==
if ((dfd = opendir(dir)) NULL) { fprintf(stderr 1 " dirwalk: can 1 t open %s\n" return ;
1
dir) ;
whi1e ((dp = readdir(dfd)) != NULL) { if (strcmp(dp->name 1 " . " ) ==О 11 strcmp (dp->name continue ; /* ski p self and parent */ if (strlen(dir)+strlen(dp->name)+2 > sizeof(name)) fprintf(stderr 1 " dirwalk : name % ѕ % ѕ too long\n" dir 1 dp->name) ; else { sprintf(name 1 "% ѕ/ % ѕ " 1 dir 1 dp->name); ( *fcn) (name) ; 1
"
•• "
))
1
closedir(dfd);
Секој повик до readdir вра ќа покажувач кон инфо рмација за наредната датоте ка , или
NULL кога нема преоста нато повеќе датотеки . Секој директори
ум секогаш содржи записи за себе, наречени ".",и за неговиот родител
" . . ";
овие мора да бидат прескокнати, или програмата ќе врти до бесконечност . Се до ова ни во, кодот е независен од то а како се форма тирани ди ре ктор и уми те. Сл едниот чекор е да се претстават минималните верзии на
opendir, readdi r и closedir з а еден посебе н систем . Рутините што следат се за систе мите v ersion 7 и System v UNIX; тие ја користат директориумската информација од заглавјето , која и згледа н а следниов на ч ин : #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 .
Целосното множество на " системски" типови се наоѓа во
opendir го отвора директориумот, потврдува де ка датотеката е дире ктори fstat, кој наликува на stat освен во тоа што се применува врз датотечен дескриптор ) , алоцира директориумска стру к
ум (овој пат преку системскиот повик тура и ја запишува информацијата:
int fstat(int fd , struct stat *); /* opendir: отвора дирехториум DIR *opendir(char *dirname)
за
readdir
nовихувааа* /
{
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)
dp->fd = fd ; return dp ; closedir
return NULL ;
ја затвора директориумската датотека и ја ослободув а меморијата:
/* closedir: затвора дирехториум void closedir(DIR *dp )
отворен со
opendir */
{
if (dp) { close{dp->fd) ; free(dp);
На крај,
readdir ја употребува read за
читање на секој директориумски за
пис . Ако местото за директориуми не е тековно во употреба (бидејќ и н екоја
датотека била отстра нета) , Во спротивно,
inode бројот е нула, а таа локација се прескокнува. inode бројот и името се сместуваат во static структура, а еден
покажувач кон неа се враќа на корисникот. Секое пови кување ја пребриш ува информацијата од претход ни от.
Системски интерфејс на
216
Глава
UNIX
8
#include /* покалиа директориумска структура * / /* readdir: р едо спе дно чита директориумски записи */ Dirent * readdir (DIR *dp) {
struct dir ect di rbuf; /* струкоrура
покаnна директориумска
*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, да се порамни соодветно за објектот кој ќе биде зачу ван
во неа о Иако машините варираат, за секоја машина постои еден најограничен
тип: ако најограничениот тип може да се смести на одредена адреса, ќе можат и сите други типови о На некои машини, најограничен тип е
int или,
пак,
double; на д руги
long о
Еден слободен блок содржи покажувач кон наредниот блок во с инџ ирот, запис за големината на блокот, и самиот слоб оден простор; контролната ин
формација што е на почетокот се нарекува "заглавје" о За да се поедностави порамнувањето, сите блокови се цел број пати од голем ината на загла вјето и
Системски интерфејс на
218
Глава
UNIX
8
заглавјето се порамнува правилно . Тоа се п остигн ува со унија која ја содржи посаку ваната структура на заглавјето и инстанца од најограничениот тип за по рамн ува ње, за кој произволно го прогласивме типот
typedef long Al.ign ; / * на long* /
long .
за nорамиуаав.е хон оrраничуаав.ата
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 Променливата
base е
потребна за започнување. Ако
е случај при првиот повик на
m.alloc,
freep
е
NULL,
219
како што
тогаш се креира дегенерирана листа сло
бодни; истата содржи еден блок со големина нула, и покажува кон себеси. Во секој случај, листата слободни потоа се пребарува . Барањето за слободно место со посакуваната големина започнува од точката
(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 *) if
(р
if
Функцијата
=
(р+1)
;
freep) /* измината е целата листа на = 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
/* free: смес~ува блох void free(void *ар)
ар во листата слободни
22 1
*/
{
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рограмски јазик С, Хз . 1591989. " Ова у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 break саѕе
char const continue default do
double else enum extern float for goto if
int long register return u short signed sizeof static
struct switch typedef nion unsigned void volatile 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 претставување на вредности од
10 до 15
или од А до F за
о
Една целобројна константа може да биде проследена со буквите u или
за да се означи дека е неозначена (анrо проследена и со буквата
1
или
L за да
се
u, unsigned) о Исто така, може да биде означи дека станува збор за 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 int о Ако целобројната константа има наставка UL , тогаш таа е од тип unsigned 1ongo 1
Објаснувањето за типовите н а целоброј ните константи оди подалеку од првото издание, кое единст ве но ги сметаше големите целобројни константи како да се од ти п
1ong о
Наставк ите
U се нови о
226
Референтно упатство
Додаток А
А2.5 .2 Знаковни константи Знаковната константа претставува секвенца од еден или повеќе знаци
омеѓена со апострофи како на пример 'х'
. Вредноста на знаковната константа
која има само еден знак , е бројната вредност на знакот во знаковното множе ство на компјутерот во време на извршување на програмата . Вредно ста на по
веќезнаковна константа за виси од имплементацијата . Знаковните константи не содржа т зна к
' или знак за нова линија; за да може
да ги претставиме нив , како и некои дру ги слични знаци , може да се ко р и стат следниве излезни секвенци
:
нова линија
N L (LF)
\n
контра н из
хоризонтален таб
нт
\t
пра шалник
вертикален таб
VT
апостроф
бришење наназад
вѕ
\v \b
\\ \? \'
наводник
\"
CR
\r
октален бр ој
FF BEL
\f
враќање на почеток на редот НОВ ЛИСТ
звуЧен аларм
\а
?
хексадекаден број
Секвенцата \ооо содржи контраниз знак проследен со
1, 2
000
\ ооо
hh
\ xhh
или з октални ци
фри , кои ја определуваат вредноста на посакуваниот зна к. Чест пример за ваква конструкција е \О (не проследена со цифра) знакот NUL . Се квенцата
\xhh
, којашто го определува
содржи контраниз проследен со х, п а проследен
со хексадекадни цифри , кои ја определуваат вредноста на посакуваниот знак. Не постои ограничување за бројот на цифрите , но однесувањето во случаите каде вредноста на резултантниот знак ја преминува вредноста на најголемиот
знак, е недефинирано. И за окталните и за хексадекадните излезни знаци , ако имплеметацијата го третира типот
char
како означен (анг.
slgned) , вредноста е char. Во случаите
значајно-проширена и се смета ка ко да е претопена во тип
во кои знакот кој што следи после\ не припаѓа на претходно наведените знаци,
однесувањето е недефинирано. Кај некои имплементации, постои проширено множество на з наци кои не
можат да бидат претставени со типот char . Константа која припаѓа на ова про ширено множество се запишува со водечко
L, на пример, L ' х', и се нарекува wide) знаковна константа . Таквата константа е од тип wchar_ t , сложен тип дефиниран во стандарното заглавје . Како и кај обич широка (анг.
ните з наковни константи, може да се користат октални и хексадекадни излезни
секвенци ; ефектот не е дефиниран ако наведената вредност ја преминува мак сималната дефин и рана со
wchar_ t.
Некои од овие излезн и секвенци се нови, посебно хе ксаде кадната знаковна репрезентација
.
Широките зна ци, исто така, се нови
.
Знаковните множества
кои вообичаено се користат во Аме рика и За падна Европа можат да се енко дираат, да се вклопат со типот
wchar_ t
char ;
главната причина за додавање на типот
беше да се сместат азиските јази ци .
Лексич к и конвенции
А.2
227
А2.Ѕ.З Реални константи Една реалноброј на константа се состои од целоброен дел , деци малн а точка , децимале н дел , е или Е , опционално означен (со+ или
-) целоброен
експонент и опц ионална наставка за тип која може да биде f ,
F,
1 или L .
Целобројниот и децималн иот дел се состојат од секв ен ца на цифри . Може да се случи кој било од нив (но н е и двата одедн а ш) да недостасува; може да не до стасува дец ималната точка, може да недостасува е зад но со експо нен тот (но
не и двете одеднаш) за
float , L
или
1
за
. Типот се о пр едел у ва според на ставката; F 1ong doub1e , во спроти в но за doub1e .
или f се смета
Суфиксите кај реал н ите констан ти се н о ви .
А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 Основни податочни типови Постојат неколку фундаментални типо ви. Ста ндардн ото за главје
h> опишано во Додаток Б ги дефинира најголеми те и најмал ите можни вр еднос ти за секој тип во рамките на една л окал н а имплементација
.
Броев ите да дени
во Додаток Б ги прикажуваат најмалите прифатл ив и го лем и ни .
Објектите декларирани како знаци (char) се доволно големи да го склади раат кој било член од знаковното множество на компјутерот . Ако не1<0ј знак од тоа множество се складира во
char објект, негова та вредност е една ква
на целобројниот код за тој знак и тој код е ненегативен . Други величин и може да се чуваат во
char променливи , но можниот опсег на вредн остите, а п осеб
но дали тие вредности се оз начени , за виси од имплементацијата на локалната платформа . Неознач ените знаци декларирани како
uns igned char зафаќаат
исто количество на меморија како и обичните знаци, н о тие секогаш се нене гативни ; експлицитно означените знаци , дефинирани како
signed c har, исто
така, зафаќаат исто количество на мемориј а како и обичните знаци. Типот
unsigned char
не беше обработен во пр вото издан ие на оваа к н ига,
но се употребуваше во н еа . Ти пот
signed char е нов. char типовите , достапни се најмногу три големи ни на цели броеви , декларирани како short int, int и long int . Обичните int објекти имаат Покрај
природна големина која за виси од локалната архитектура ; ос танатите гол еми
ни се воведени за да задоволат одредени специј ални потреб и . Подол гите .цели броеви обезбедуваат простор со голе,_, барем колку и просторот за пократки те цели броеви, но во зависност од локалната имплементација об ич ните цели броеви може да бидат еквивалентни и на кратките и на долгите цели броеви. Сите
int типови претставуваат исклучително означе ни вредности , освен ако
тоа не е наведено поинаку .
Неозначените цели броеви, декларирани со кл уч ниот збор приклонуваат на аритметика со модул 2" каде зентацијата,
na
n
u nsigned , се
е бројот на битски во репре
поради тоа аритметиката на неозначени величини ни ко г;з ш н е
230
Референтно упатство
предизвикува пречекорување (анг.
Додаток А
overflow) .
Множеството на ненегативни
вредности кои може да се чуваат во еден означен објект е подмножество од вредностите кои може да се чуваат во соодветен неозначен објект , и репрезен тацијата за вредностите кои се поклопуваат е идентична.
Кој било реален број со единична прецизност (float) , реален број со двој на прецизност
(double) и реален број со екстра прецизност (long double)
можат да бидат синонимни, но оние со повисока прецизност се барем прециз ни колку тие пред нив.
Типот
long double
е нов . Првото издание дефинираше тип
беше еквивалентен со
double;
long float
кој
таа декларација е повлечена.
Енумерациите се уникатни податочни типови кои имаат целоброј ни вред ности; со секоја енумерација се асоцира и множество од именувани констан
ти (Параграф АЅ . 4) . Енумерациите се однесуваат како целите броеви, но во општ случај компајлерот издава предупредување кога на еден објект од кон кретна енумерација му се доделува нешто што е различно од нејзините кон станти.
Бидејќи објектите од овие типови може да се интерпретираат како броеви, за нив ќе велиме дека се аритметички типови. Типовите
char и int од сите
големини, со или без знак , како и енумерациските типови ќе бидат нареку вани под заедничко име интегрални типови . Типовите
double ќе бидат нарекувани реални типови. Типот void означува празно множество на
float, double
и
long
вредности. Се користи како по
вратна вредност кај функциите кои не генерираат резултат .
А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 .
-И наку , и врз двата о п еранда се изведуваат инте грални пр омоции; по -
Конверзии
А .б
тоа ако едниот операнд е од тип
unsigned long int ,
233
другиот се претвора во
unsigned long int. -Инаку, ако едниот операнд е од тип
int,
long int ,
а другиот е од тип
резултатот зависи од тоа дали вредностите на
unsigned
може да се
unsigned int опера ндот се претвора во long int . -Инаку, ако едниот операнд е од тип long int , другиот се претвора во long int . -Инаку , ако кој било операнд е од тип unsigned int , другиот се претвора во unsigned int. -Инаку, и двата операнда имаат тип int. Овде има 2 промени. Прво, аритмети ката врз floa t оnеранди nретn очита претстават со
long int ;
long int ;
unsigned int
ако е така
во спротивно и двата се претвораат во
единична прецизн ост, а не двојна ; 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* играа улога на генерички покажувач. посебно ја нагласува интеракцијата помеѓу
void*
ANSI стандардот
покажувачите и објектните
покажувачи во доделувањата и релациите, додека кај интеракциите помеѓу п окажувачите од другите ти п ови претопувањето експлицитно се наведува .
А.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 . Доколку функциската декларација во делокругот на еден повик е во стар стил, тогаш се применува основна промоција на аргументите за секој аргумент што следи: интегрална промоција (Параграф аргумент од интегрален тип , а секој
float
Аб . 1) се извршува врз секој
аргумент се претвора во
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
ако вредноста на неговиот операнд е еднаква на о, а во
спротивно о . Типот на резултатот е
А7.4.8 Оператор Операторот
int .
sizeof
sizeof враќа број на бајти потребни да се складира во мемо
рија објект од типот на неговиот операнд . Операндот е или израз кој не се ева луира, или име на податочен тип оградено со мали загради. При примена на
sizeof
врз
char
резултатот е
1;
при негова примена врз низа , резултатот е
вкупниот број на бајти во низата . Применет врз структура или унија резулта
тот е бројот на бајти во објектот , вклучувајќи го и потребното порамнување за објектот да се смести во низата : големината на низата од n елементи е n пати од големината на еден елемент. Операторот не може да се примени врз операнд
од функциски тип или врз некомплетен тип или врз битско поле . Резултатот е неозначена интегрална константа; конкретниот тип е дефиниран со имплемен
тацијата. Стандардното заглавје ра овој тип како
(погледни Додаток Б) го дефини
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 во стандардното за главје . Вредноста е недефинирана освен ако покажувачите не п о кажува ат кон објекти од истата низа; меѓутоа ако р покажува кон последниот елемент на една н и за, тогаш (р+ 1) -р има вредност
1.
А7.8 Оператори 3а поместуван.е
Операторите за поместување << и >> групираат од лево кон десно . И кај двата оператора секој операнд мора да биде интегрален и е подложен на ин тегрални промоции. Типот на резултатот е типот на промовираниот лев опе
ранд. Резултатот е недефиниран ако десниот операнд е негативен, или пого лем од или еднаков на бројот на битски на типот на левиот израз . uзраз-за-поместување:
адuтивен-израз израз-за-поместување израз-за-поместување
<< адитивен-израз >> адитивен-uзраз
Вредноста на Е1<<Е2 е Е1 (интерпретиран како битска низа) лево поместен за
Е2 битски; во отсуство на пречекорување тоа е еквивалентно на множење со 2Е 2 • Вредноста на Е1>>Е2 е Е1 десно поместен Е2 битски позиции. Поместувањето во десно е еквивалентно со делење со 2Е2 , ако Е1 е неозначен или ако има ненегатив на вредност; во спротивно резултатот е дефиниран со имплементацијата.
А7 .9 Релациски оператори
Релациските оператори групираат од лево кон десно, но тој факт е бескори сен;
a
се парсира како
(a
<е, а
(a
се евалуира во о или
1.
244
Референтно упатство
Додаток А
релациски-израз: израз-за-поместување
релаци ски- израз
< израз -за - п оместувањ е
релациски-израз
> и зраз-за-поместување
<= изра з -за - п оместува НЈ е релаци ски-израз >= израз -за - п оместување
релациски-израз
Операторите < (помал о)
, > (поголема) , <= (помал о или еднакво)
и
>= (по
големо или еднакво) враќаат о доколку наведената релација е невистин ита и
1 доколку е вистинита о Типот на резултатот е int о Вообичаените аритмети чки кон верзии се изведуваат врз аритметички операнди о Покажувачите од објекти кон ист тип (игнорирајќи какви било квалификатори) може да се споредуваат; резултатот за виси од релативните локации во адресниот простор на пока жува
ните објекти о Споредувањето на покажувач и е деф и нирано само за делови од ист објект; ако два покажувача покажуваат кон ист прост објект , тие се споре дуваат како еднакв и; ако покажува ч и те се членови од иста структура , покажу
вачите кон објекти декларирани подоцна во структурата се споредуваат како поголеми ; ако покажувачите се одн есуваат н а членови од н иза резултатот на
споредувањето е еквивалентен со резултатот од споредувањето на
соодвет
ните и ндекси о Ако р покажува кон последниот член од една н и за тогаш
споредува како поголема од р, иа ко
p+l се p+l покажу ва на дво р од низата о Во спро
тивно, споредувањето покажу вачи е недефинираноо Овие п равила мал ку ги либерализи раат о граничувањата наведени во прво то издание дозволувајќи споредба на п окажувачи кон разл ичн и членови н а
структ ура ил и ун ија о Исто така , го легализираат споредувањето со покажувач кој е за еде н елемент надвор од крајот н а н екоја ни за о
А7.10 Оператори 3а еднаквост израз -за-екви валенција: релациски-израз
израз -за - е квиваленција
== р ел ациски - израз
израз -за-еквиваленција!= р елациски - израз Операторите
=
(еднакво) и
!=
(н еедн акво) се а налогни н а релациските
оператори освен што и маат п о низок п р иоритет о (Така a
e=l секогаш
кога a
константен интегрален израз со вредност о , или со покажувач кон 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ички оператор ИЛИ логички - ИЛИ-и зраз: лог ички - И-израз
логички-ИЛИ- израз
Операторот
11 групира
1 1 логички -И-израз од лево кон десно. Враќа 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]
може да се појават во некој из
раз. Првите три имаат тип "низа", а последниот има тип хЗd [ i Ј
[ ј]
int.
Поконкретно ,
е низа од седум цели броеви а хЗd [ i] е низа од пет низ и од по се
дум цели броеви . Индексирањето на низата е дефинирано така што
* (El+E2) .
El [Е2]
е идентично
Поради тоа, и покрај неговиот асиметричен изглед 1 индексирање
то е комутативна операција. Бидејќи правилата за конверзија кои се приме
нуваат врз+ и врз низи (Параграф Аб . б , Параграф А7 . 7 , Параграф А7. 7) , ако Ele низа и Е2 е цел број 1 тога ш El [Е2] се однесува Е2- иот елемент од El Во примерот, xЗd[i] [ј]
[k]
е еквивалентно на* (хЗd[~] [ј]
+ k).
.
Првиот
подизраз одхЗd[iЈ [ј] според Пара граф А7 . 1 се претвора вотип " покажувач кон низа од цели броеви "
според Параграф А7 . 7, собирањето вклучува мно
1
жење со големина на цел број. Тоа следува од правилата де ка низите се скла дираат по редови (nоследниот индекс варира најбрзо) и де ка првиот индекс во декларацијата помага да се определи количината на меморија зафатена од една низа
1
но не игра никаква друга улога во пресметките со индексите.
А8 .6.3 Функциски декларатори
Во декларација на функција од нов стил Т D, каде
Dl
1
има форма
(листа-тип - на-параметри )
и типот на идентификаторот во декларацијата "
D
типот на идентификаторот
Dе
TDl
е "модификатор-на-тип Т
"модификатор-на-тип функција со аргумен
ти листа-тип - на -параметри која вра ќа Т"
.
Синтаксата на параметрите е
листа-тип-на - параметри:
листа- параметри лисrпа-параметри
листа-параметри
,
:
параметарска-декларација листа-параметри
1
параметарска-декларација
параметарска-декларација: декларациски-спецификатори декларатор декларациски-спецификатори апстрактен-деклараторорr
260
Референтно упатство
Додаток А
Во декларацијата од нов стил, параметарската листа ги специфицира ти повите на параметрите . Како специјален случај, деклараторот за функција од
нов стил без параметри има параметарска листа која се состои единствено од клучен збор void . Ако параметарската листа завршува со ",
... ",
тогаш функ
цијата може да прими повеќе аргументи отколку што е бројот на експлицитно наведени параметри , види Параграф А7 . з . 2 • Типовите на параметрите кои се низи или функции се менуваат во покажу
вачи , во согласност со правилата за конверзија на параметри ; види Параграф
AlO. 1. Единствениот спецификатор на мемориска класа, кој што е дозволен во параметарска декларација е register, и овој спецификатор се игнорира ос вен ако функцискиот декларатор не започнува функциска дефиниција . Слично на ова, ако деклараторите во параметарските декларации содржат идентифи
катори и функцискиот декларатор не продолжува со функциска дефиниција , идентификаторите веднаш излегуваат од делокруг . Апстрактни декларатори кои не споменуваат идентификатори се разгледани во Параграф АЅ . в
.
Во декларација на функција од стар стил Т D, каде D има форма
D 1(листа-на-идентификаториор) и ти пот на идентификаторот во декларацијата Т" , типот на идентификаторот
D
Т D1 е
"
модификатор -на-тип
е "модификатор-на-тип функција од неспе
цифицирани аргументи која враќа Т". Параметрите (доколку се присутни) имаат форма
листа-на-идент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
како експлицитен маркер на функциите од нов
стил без параметар. Нотацијата ..
,
... "за
варијабилни функции , исто така , е нова и заедно со
макроата од стандардното заглавје
,
формализираат ме ха низам
кој беше официјално забранет, но неофициј а лно прифатен во првото издание.
Овие нотации беа преземен и ОД јазикот е++ о
А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 ; се легални декларации. Типот на наведената структура ;
typedef
zp е
b
е
long,
на
bp е
п окажувач кон
long,
и на
zе
покажува ч кон таква структура.
не воведува нови типови, туку синоними за типови кои може да се
специфицираат на друг нач и н. На при мер, 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 не се извршува. Во првото издание на оваа книга од управувачкиот израз на саѕе константи се бараше да имаат
in t
swi tch и неговите
тип.
А9.5 Наредби 3а повторување Наредбите за повторување спе цифицираат извршување во циклуси. наредба-за-повторување :
while (израз) наредба do нapeдбa while (израз); for (изразopt ; изразopt ; изразupt ) нар едба Во while и do наредбите , поднаредбата се повторува се додека вредноста на изразот е нееднаква на о ; изразот мора да биде од аритметички тип или пока
жувач. Кај while , тестот (споредбата), вклучувајќи ги сите странични ефекти од изразот , се одвива пред извршувањето на наредбата ; кај do тестот следи после секоја итерација
.
Кај for наредбата , првиот израз се евалуира еднаш и со тоа специфици ра иницијализација за цикл усот . Не п остои ограничување во неговиот тип.
Вториот израз мора да биде од аритметички тип или покажувач ; се евалуира
Наредби
А. 9 пред секоја итерација и доколку стане еднаков на о
1
for
269
циклусот завршува .
Третиот циклус се евалуира после секоја итерација 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 макромеханизмот дефиниран во стандардното заглавје и опишан во Додаток Б, мора да се користи за да покажува кон вишокот аргумен
ти . Таквите функции мора да имаат најмалку еден именуван параметар . Во втората форма, дефиницијата е од стар стил
:
идентификаторската листа
ги именува параметрите, додека декларациската листа врзува типови кон нив.
Ако не е дадена декларација за параметарот , неговиот тип се зема за
int .
Декларациска та листа единствено мора да декларира параметри именувани во листата, иницијализација не е дозволена, а единствен можен спецификатор на мемориска класа е
register.
Во двата стила на функциска дефиниција се подразбира дека параметрите се декларирани веднаш после почетокот на сложената наредба која го сочинува
телото на функцијата, па така исти идентификатори не може да бидат повторно декларирани таму (иако, како и другите идентификатори може повторно да
бидат декларирани во внатрешни блокови)
. Ако еден параметар е деклари
ран како " низа од некој mun", декларацијата се приспособува за читање како " покажувач кон некој
mun",
слично, ако еден параметар е деклариран како
" функција која враќа некој mun" декларацијата се приспособува за читање како " покажувач кон функција која враќа некој mun". За време на повикот кон функ ција, аргументите се претвораат по потреба и се доделуваат на параметрите ; види Параграф А7 . з.
2.
Функциските дефиниции од нов стил се нови на воведена со ANSI стандардот. Исто така , п остои и мал а промена во деталите на промоцијата ; прв ото издание
специфицираше дека декларациите на читаат
double.
float
параметри се приспособуваат да
Разликата се забележува кога покажувач кон параметар се ге
нерира внатре во функција .
Еве еден целосен пример на функциска дефиниција од нов стил
Додаток А
Референтно упатство
272
intb, inte)
intПIAx(inta ,
{
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-Параграф
4.
Al2.10).
Излезните секвенци во знаковните и стринговите константи се заменува ат со нивните еквиваленти; потоа соседните стри нгови се надоврзуваат.
ѕ. Резултатот се преведува , потоа се поврзува со останатите програми и
библиотеки, преку собирање на потребните програми и податоци и по врзување на надворешните функции и објектни референци кон нивните дефиниции .
Референтно упатство
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,
околу соодветниот параметар се сместуваат нав од ници , а потоа
#
и
параметарскиот идентификатор се заменуваат со аргументот во на водници. Се вметнува знак \, пред секој знак " или
\
кои се појаву ваат околу или во ст ринг
или знаковна константа во аргументот.
Второ, ако дефиницијата на секвенцата белези и за дв ата вида на макроа содржи оператор
нува секој
ti ,
ii,
тогаш веднаш после замената на параметрите се отстра
вкл учувајќи ги сите п разни места од двете стран и со цел да се
спојат соседни белези и да формираат нов белег. Резултатот е недефиниран ако произлезат невалидни белези или ако истиот за виси н а редоследот од пре тпроцесирањето на операторите 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 "
име-на-датотека
"
пребарува првин во асоцијација со оригиналната изворна датотека мерно зависна од имплементацијата)
(фраза на
ycnee, тога ш се пребарува како во првата форма . Резултатот од користење на , \, или/* во ,
и ако тоа nребарување не
името на датотеката е недефиниран, но >е дозволено . На крај, директива во форма
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 ако не е. Сите идентификатори кои пре остануваат п осле макроекспанзијата се заменуваат со грална константа се смета како проследена со
L
1
OL. На крај секој а инте 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 О Предефинирани имиња Неколку идентификатори се предефинирани и се експандираат за да произ ведат специјална информација. Тие, заедно со операторот за претпроцесорска експанзија
LINE
defined, не можат да се дефинираат повторно ниту да се поништат. Декадна
константа
која
го содржи
бројот на тековната
изворна линија .
FILE
Стрингова константа која го содржи името датотеката која се компајлира .
DATE
Стринг константа која го содржи датумот на компајлирање,
ТIМЕ
Стринг константа која го содржи времето на компајлирање во
во форма "Мmm dd уууу" форма " 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
Граматика
директен-декларатор: идентификатор (декларатор)
директен-декларатор [константен-израз opr] директен-декларатор (листа-тип-на-параметри) директен-декларатор (листа-идентификаториopt ) покажувач:
"тип -квалификатор-листа opt
"тип-квалификатор-листа.Р, покажувач тип-квалификатор-листа: тип-квалификатор тип-квалификатор-листа тип-квалификатор листа-тип-на-параметри: листа-параметри
листа - параметри,
...
листа-параметри:
параметарска-декларација листа-параметри , параметарска-декларација параметарска-декларација:
декларациски-спецификатори декларатор декларациски-спецификатори апстрактен-декларатор opt листа-идентификатори: идентификатор листа-идентификатори, идентификатор иницијализатор:
израз-за-доделување
{ листа-иницијализатори} { листа-иницијализатори, } листа-иницијализатори:
иницијализатор листа -иницијализатори, иницијализатор
име-на-тип:
спецификатор- квалификатор-листа апстрактен-деклараторopt апстрактен-декларатор: покажувач
покажувач opt директен -апстрактен-декларатор
285
286
Референт~о упатство
Дод аток А
директен-апс_mрактен-декларатор: (апстрактен-декларатор)
директен-апстрактен-декларатор opt { константен-израз орЈ директен-апстрактен-декларатор opt (листа-тип-на-параметри ор) typedef-uмe: идентификатор наредба: означена-наредба
изразна-наредба сложена-наредба
наредба-за-избор наредба-за-повторување скок-наредба означена-наредба:
идентификатор: наредба саѕе константен-израз : наредба defaиlt : наредба
изразна-наредба: израз.Р,;
сложена-наредба:
{ декларациска-листаopt листа-наредби ,} ор
листа-наредби: наредба листа-наредби наредба наредба-за-избор:
if(израз) наредба if(израз) наредба else наредба switch (израз) наредба наредба-за-повторување:
while (израз) наредба do наредба while (израз); for (изразор,; израз ор,; изразор) наредба
А.13
Граматика
скок-наредба:
goto идентификатор; continиe;
break; retиrn израз opf израз:
израз-за-доделување израз, израз-за-доделување израз-за-доделување: условен-израз
унарен-израз оператор-за-доделување израз-за-доделување
assignment-operator: еден од = *=!= %= += -= <<= >>= &= л= 1= условен-израз:
логички-ИЛИ-израз логички-ИЛИ-израз? израз: условен-израз
константен -израз:
условен-израз
логички-ИЛИ-израз:
логички-И-израз
логички-ИЛИ-изразii логички-И-израз логички-И-израз: ИЛИ-израз логички-И-израз && ИЛИ-израз
ИЛИ-израз: исклучително-ИЛИ-израз
ИЛИ-изразi исклучително-ИЛИ-израз исклучително-ИЛИ-израз:
И-израз исклучително-ИЛИ-израз л И-израз И-израз:
израз-за-еднаквост
И-израз & израз-за-еднаквост
287
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 стан да рдот . етандардната библиотека не е дел од самиот јазик е, туку околина која
го поддржува ста ндардниот е и обезбедува фун кциски декларации , типови и дефиниции на макроа . Изоставивме мал број функции кои имаат ограничена употреба или се лесни за изведување со помош н а другите; и зоставени се и по
веќебајтните знаци ; и ја изоставивме дискусијата за теми кои зависат од "лока циј ата "; т . е . карактеристики кои зависат од локалниот јазик, националност , или култура .
Функциите, ти повите и макроата од ста ндардна та библиотека се деклар и рани во следниве стандардни заглавја:
< limi ts . h>
< stdio .h>
До едно заглавје може да се пристап и пр еку
ltinclude <Заглавје> За главјата може да бидат вклучени во кој б ило редослед и пр оизволен број
пати . Тие мора да бидат вклучени надвор од сите надворешни декларации или дефиниции и п ред каква било употреба на нештата што самите тие ги деклари раат . Едно заглавје не мора да биде изворна датотека. Надворешни идентиф икатори кои започнуваат со зн ак за подвлечено ре зервирани се за уп отреба од страна на библиотеката, како што се и другите идентификатори кои започнуваат со знак за подвлечено и голема буква , или со у ште еден знак за подвлечено.
Б 1. Влез и излез:
Влезно и излезните функции , типовите и макроата дефинирани во претставуваат речиси третина од целата библиотека.
29 1
292
Стандардна библиотека
Додаток Б
Поток претставува и з вор или дестинација за податоци кои можат да се по
врзат со диск или друг периферен уред. Библиотеката поддржува текстуални
и бинарни потоци, иако кај некои системи посебно
UNIX, овие се идентични .
Текстуален поток е секвенца од линии; секоја линија има нула или повеќе зна ци и завршува со'
\n' .
Една околина може да има потреба да конвертира текс
туален поток во или од некоја друга репрезентација (како што е мапирање на
' \n'
со знак за нов ред или знак за нов лист) . Бинарен поток е секвенца од неп
роцесирани бајти кои зачувуваат внатрешни податоци , со својство идентично со о на што првин е за пишано, а потоа истото прочитано на ист систем.
Еден поток се поврзува со датотека или уред преку негово отворање; таа врска с е прекинува со затворање на потокот . Отворање на датотека враќа по
кажувач кон објект од тип FILE , кој чува разни податоци потребни за контрола на потокот. Наизменично ќе ги користиме термините "датотечен покажувач " и "пото к ", во случаите каде нема двосмисленост.
Кога една програма за почнува со извршување, трите потоци и ѕ tderr веќе се отворени
stdin, stdout
.
Б 1.1 Операции со датотеки Следниве функции се справуваат со операции врз датотеки. Типот size t е неозначен интегр ал ен тип кој се враќа од страна на операторот sizeof .
FILE *fopen (const char *filename, const char *mode) fopen ја отвора именуваната датотека , и враќа поток , или, пак, NULL вред mode легални се следниве вредности :
ност во случај на неус пех . За полето
"r " " w"
отворањ е на текстуа л на датотека за читање,
креирање на текстуална датотека за запишување; ја брише претходн ата содржи н а доколку ја има,
" а"
дополнување; отвора или креира текстуална датотека во која ќе се запишува почнувајќи од крајот н а нејзи ната содржина,
" r+ "
отвора ње н а текстуал н а датоте ка за нејзин о ажурирање (т . е
" w+ "
креирање на текстуална датотека за ажурирање, нејзината
" а+ "
дополнув ање ; отворање или креирање на текстуална датоте
читање и запишување),
претходна содржина , доколку ја има, се брише, и ка за ажурирање, за пишувањето се изведува на нејзиниот крај.
Модот за ажурирање дозволува читање и за пишување на истата датотека; по
меѓу операции на ч итање и запишување мора да се повика fflush или функција за позиционирање на датотека . Ако модот после п о четната буква вклучува b ,
Влез и излез:
Б.1
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рефиксирана во ненулти резултат . За е , Е ,
G,
f , g,
и
G , излезат секо гаш ќе има децимална точка ; за g или
н улите што следат не се отстран у ваат .
Б .1
Влез и излез:
-
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 или поголем или еднако в на прецизноста; во спротив но се користи%
Р
void *;
f . Нули и децимал на точка н а крајот не се п е чатат.
п ечати ка ко п о кажувач (р е п резе нта цијата зав иси од
и м пл.емента цијата )
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
Влез и излез:
полето
arg 1
кое е иницијализирано со макрото
кон va_ arg. Погледнете ја дискусијата на
va_ start и можеб и Дел 57.
297
со пов ици
Б 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
Стандардна библиотека
Додаток Б
scanfкон верзии
Табела Б.2 Знак
Влезни nодатоци ; тип на а
d
декаден цел број;
i
цел број;
r
мент
int*.
int*. Целиот број може да биде во октална ( со
водечка О) или хексадекадна (со водечки ох ил и О Х )
о
октален цел број (со или без в одечка О) ;
u
неозначен декаден цел број;
х
хексадекаден цел број (со или без водечки х или ОХ)
е
знаци;
unsiqned int* .
Наредните
char* .
int* .
влезни
знаци
се
; int* .
сместуваат
во наведената низа , се до бројот наведен во полето за широчина ; стандардна вредност е 1 . Не се додава '\ о'
.
Нормалното nрескокнување на п разни места е елиминирано во овој случај; за читање на наредниот знак кој не е празно место, користете
ѕ
стринг
од
%ls .
знаци
различни
char*,
наводници) ;
што
од
nразно
покажува
кон
место низа
(не од
во
з наци
доволно голема за да го чува стр и нгот и завршниот знак ' \о'
кој што ќе биде доделен.
e,f , g
број со nодвижна точка ;
е оnционален децимална
+-,
точка
float* . Влезниот формат за float
стринг од броеви кој може да содржи и
опционално
поле
за
експонент
содржи Е или е проследено можеби со означен цел број
кое
.
р
вредност на покажувач што би се испечатила со printf
n
го запишува во аргументот бројот на тековно прочитани
( " %р " ) ;
void*.
з наци со овој nовик ;
int* .
Н е се чита ника ков влез . Бројот
на конвертирани елементи не се инкрементира.
[ ... ]
се поклопува со најдолгиот непразен стринг од влезни знаци
од множеството помеѓу заградите ; Со
[] ... ]
char* . Се додава ' \о ' .
во множеството се вклучува
].
се поклопува со најдолгиот непразен стринг од влезни знаци
кој не npunaza на множеството помеѓу заградите; додава ' \о '
%
знак
.
Со
[ ... ] ... ]
char* . Се
во множеството се вклуч у ва
%. Не се nрави никакво доделување .
int scanf (const char *format, ... ) scanf ( .. . ) е иденти ч но со fscanf (stdin, . . . ) .
].
Влез и излез:
Б.1
int sseanf (eonst ehar *ѕ , eonst ehar *format , ... ) sseanf (ѕ, . . . ) е еквивалентна со seanf ( .. . ) со таа
299
разлика што
влезните знаци се земаат од стрингот ѕ.
Б 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 е еквивалентна
со
ehar *gets (ehar *ѕ) gets ја вчитува
наредната влезна линија во низата ѕ ; го за ме нува
gete (stdin) ,
знакот за нова линија со
'\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
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 (деклариран во ) може да содржи број за грешка кој дава по натамошна информација за последната гре шка која се случила .
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. Проверка на класата на знак:
Заглавјето
декларира функции за тестирање знаци. За секоја int чија вредност мора да биде EOF или претставена како unsigned ehar , а вредноста која функцијата ја враќа е int. Функциите враќаат ненулта вредност (вистина) доколку аргу функција листата на аргументи е претставена со еден
ментот е го задоволува бараниот услов, и о ако не го задоволува.
isalnum(e) isalpha(e)
isalpha(e) isupper (е)
isentrl(e)
или или
isdigit(e) islower (е)
евистина е вистина
контролен знак
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. Функции за стрингови:
Постојата две групи на функции за стрингови дефинирани во заглавјето
.
Првата група има имиња кои започнуваат со
има имиња кои започнуваат со
mem .
str;
втората група
Сите случаи, со исклучок на
mermnove ,
во
кои се одвива копирање помеѓу објекти кои се преклопуваат , се недефинирани. Функциите за споредба ги третираат аргументите како низ и од unsigned ehar. Во следната табела променливите ѕ и
eonst ehar* ; n
е од тип
size_ t;
а е е
t се од тип ehar* ; еѕ и et int претворен во ehar.
се од тип
Б.З
Функц и и за стрингови : < stri пg . h >
ehar*strcpy (ѕ, et)
го копира стрингот
со
ehar*strncpy (ѕ . et, n)
' \0';
n
знаци од стрингот
Го пополнува
помалку од
ehar*streat (ѕ , et)
во стринг ѕ, вклучително
et
враќа ѕ
копира најмногу
враќа ѕ.
303
n
et во ѕ ; et има
доколку
'\0 '
знаци
надоврзува стринг
на крајот на стрингот ѕ ;
et
враќа ѕ
ehar*strneat (ѕ , et, n)
надоврзува најмногу стрингот ѕ ,
n
знаци од стрингот
го означува крајот на ѕ со
et на '\0 ';
враќа ѕ споредува стринг еѕ со стринг
intstrcmp(es, et)
es
О ако
es=et
et , враќа es>et
<О ако
или >О, ако
споредува нај многу n знаци од стрингот еѕ со
стрингот
et ; вра ќа <О ако eset
или >О, ако
ehar*strehr(es ,
е)
враќа покажувач кон првата појава на е во еѕ , или NULL доколку нема појава
ehar*strrehr(es ,
е)
враќа покажувач кон последната појава на е во еѕ , или
size_t strspn (еѕ , et)
NULL доколку
знаци во
size t et)
strespn(es ,
et
враќа должина на префикс во еѕ кој се состои од знаци кои не се во
ehar*strpbrk(es, et)
нема појава
враќа должина на префикс во еѕ кој се состои од
et
враќа покажувач кон првата појава на кој било знаковен
et
стринг
во
стринг
еѕ
или
NULL
доколку нема таква појава
ehar*strstr(es,et)
враќа покажувач кон првата појава на стрингот
et во size_t strlen (еѕ) ehar*strerror(n)
еѕ или
NULL доколку нема
таква појава
враќа должина на еѕ враќа
покажувач
кон
стринг
дефиниран
имплементацијата кој одговара на грешка
ehar*strtok(s , et)
со
n
го пребарува ѕ барајќи белези одделени со знаци кои припаѓаат на
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) void*menпnove(s, et, n)
n знаци од et во ѕ memepy , освен
иста како
и го враќа ѕ што функционира
дури и ако објектите се преклопуваат
intmemcmp(es, et, n)
n strcmp
ги споредува првите
враќа исто како
void* memehr (еѕ,
е,
знаци на еѕ со
et ;
враќа покажувач кон првата појава на знак е
n)
во еѕ или NULL доколку нема таква појава во првите
void* memset (ѕ,
е,
n)
n зн аци .
сместува знак е
во првите
n
знаци
од ѕ,
враќа ѕ
64. Математички функции: Заглавјето декларира математички функции и макроа . Макроата
EDOM и ERANGE
(кои се наоѓаат во
се ненулти инте
)
грални константи кои се користат да сигнализираат грешки во домен и опсег
за функциите ;
HUGE_ VAL
е позитивна
double
вредност. Грешка во домен се
случува ко га еден аргумент е надвор од доменот во кој што е дефинирана функ цијата . При грешка во домен ,
errno
се поставува на вредност ЕDОМ ; врате
ната вредност е дефинирана со имплементацијата
.
Грешка во опсег се случува
кога резултатот на функцијата не може да се претстави како
double .
Доколку
има nречекорување во резултатот , функцијата враќа НUGE_VAL со соодветни от знак, а
errno се
поставува на
ERANGE .
Доколку има пречекорување на до
лната граница во резултатот, функцијата враќа О; дали
ERANGE е дефинирано со
Во следната табела х и у се од тип
double .
errno ќе
се постави на
имплементацијата.
double, n е int ,
а сите функции враќаат
Аглите за тригонометриските функции се изразен и во радијани.
sin(x) еоѕ(х)
t&n(x) asin(x)
синус одх КОСИНУС ОД Х
тангенс од х
sin-L(х) [-1,1].
во опсег
[ -pi/2 , pi/2 Ј ,
аеоѕ (х)
еоѕ- 1 (х) во опсег [О ,pi ]
atan (х) atan2(y,x) sinh(x)
синус хи перболи к од х
, х припа ѓа tan-L(х) во опсег [ -pi/2 , pi/2] . t&n-1 (y/x) воопсег [-pi ,pi].
х припаѓа
на
[-1, 1] .
на
Корисни функции:
Б.Ѕ
cosh (х)
косинус хиперболик од х
tanh(x)
тангенс хиперболик од х
ехр(х)
експоненцијална функција е-'
log(x)
природен логаритам
loglO(x)
декаден логаритам
pow(x 1 y)
xr.
ln (х)
305
х>О.
1
log10 (х)
1
х>О.
Грешка во домен се случува кога х=О и у<=О 1 или
ако х<О и у не е цел број.
sqrt (х)
квадратен корен од х 1 х>=О.
ceil (х)
најмалиот цел број не помал од х 1 како double.
floor (х)
најмалиот цел број не помал од х 1 како double .
fabs(x)
апсолутна вредност од
ldexp(x 1 n)
х*2 "
f r е х int*exp)
р
(
х
1
го дели
х
во
1х 1
нормализиран
остаток
во
интервалот
[ 1/2 1 1) кој се враќа 1 и степен од 2 кој се сместува во *ехр. Ако х е о 1 двата дела од резултатот се нули.
m о d f double*ip)
х
1
го дели х на целоброен дел и остаток 1 секој со истиот знак како х. Го сместува интегралниот дел во *ip 1 а го враќа остатокот.
fmod(x~y)
реален остаток од х!у 1 со ист знак како х . Ако у е о 1 резултатот е дефиниран со имплементацијата .
65. Корисни функции : Заглавјето декларира функции за конверзија на броеви
1
мемо
риска алокација и слични задачи.
double atof (const char *ѕ) atof го претвора ѕ во double; char**)NULL) . int atoi (const char го претвора ѕ во
еквивалентна е со
strtod
(ѕ
1
*ѕ)
int ; (char**)NULL 1 10).
еквивалентна е со
long atol (const char *ѕ) го претвора ѕ во long; NULL 1 10).
(int) strtol (ѕ 1
еквивалентна е со
strtol (ѕ 1 (char**)
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 а водечки о х или о х хексадекадна нотација. Буквите и во
двата случаја репрезентираат цифри од о х се дозволени за основа
16.
10 до base-1; водечки О х или
Доколку се случи пречекорување во од
говорот 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 во кој случај *р оаанува непроменето .
Б.Ѕ
Корисни функции
:
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 враќа апсолутна
вредност на нејзиниот
long labs (long n) labs враќа апсолутна
int аргумент.
вредност на нејзиниот
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 се
користи за додавање на дијагностика на програмите.
void assert (int израз) Ако израз има вредност о кога се извршува
assert (израз) , макрото
assert ќе
испечати порака на
stderr
како, на пример ,
Assertion failed: израз, file filename, line nnn Потоа повикува abort за прекин на извршувањето. Името на изворна та датотека и бројот на линијата се земаат од претпроцесорските ма кроа
FILE
И
_
LINE
Ако е дефинирано макрото NDEBUG за време на вклучување на , макрото
assert се
игнорира.
Б.8
Нелокални скокови:
Б7. Променливи листи на арrументи:
309
Заглавјето обезбедува можност за поминување низ листата од аргументи на една функција чиј број и тип се непознати
. 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. Нелокални скокови: Декларациите во
обезбедуваат начин да се избегнат нормал
ните функциски повикувања и повратни секвенци, во општ случај да се овоз можи непосредно излегување од длабоко вгнезден функциски 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и:
Заглавјето обезбедува можност за справување со исклучителни услови кои настануваат за време на извршувањето,
како што е сигнал за пре
кин од некој надворешен извор или не која грешка во извршување.
void (*signal (int sig, void (*handler) (int))) (int) signal го одредува справувањето со последователните сигнали. Ако handler има вредност SIG_DFL, се користи стандардното однесување дефи нирано со имплементацијата , а ако истиот има вредност SIG_ IGN, сигналот се игнорира ; во спротивно, се повикува функцијата кон која по кажува handler, со аргумент за ти пот на сигнал. Валидни вредности за сигнали се:
SIGAВRT абнормален крај , на пример, од
SIGFPE аритметичка
грешка , на
abort
пример, делење со нула или
пречекорување на опсег
SIGILL илегална функциска слика , на пример, илегална инструкција SIGINT интерактивно внимание, на пр имер, interrupt SIGSEGV и легален пристап до меморија, на пример, пристап вон опсегот н а меморијата SIGTERМ барање за прекин на извршување, испратен о до оваа програма
signal ја враќа претходната вредност на handler за конкретниот сигнал , SIG_ERR во сл учај на nојава на грешка .
или
Кога потоа ќе се појави сигнал
sig ,
сигналот се реставрира на неговото
стандардно однесување; потоа се п овикува функција за справување со сигна-
Б.10
Функци и за датум и време:
ли 1 како што е
(*handler) (sig) .
Доколку
handler
311
врати вредност 1 извршу
вањето на програмата продолжува од местото каде што била за време на поја вата на сигналот .
Иницијалната состојба на сигналите е дефинирана со имплементацијата
int raise (int sig) Функцијата raise праќа сигнал sig на програмата ; во случај на неуспех враќа ненулта вредност
.
Б 1О. Функции за датум и време: Заглавјето
декларира типови и функции за манипулац ија со да
туми и време. Некои функции го процесираат локалното време 1 кое може да се разликува од календарското 1 на пример, поради часовна зона.
time_ t се аритметички типови за претстав а на времиња 1 tm ги чува компонентите на календарското време: int tm_sec; int tm_min ; int tm_hour ; int tm_mday ; inttm_mon ; int tm_year; int tm_wday ; int tm_yday ; int tm_isdst ;
секунди после минутата минути после часот (О
1
clock_ t и struct
а структурата
(0 1 61)
59)
часови после полноќ (0 1 23) ден во м есецот
(1 , 31)
месеци после јануари години после
(0 1 11)
1900
денови после н едела (О, б)
денови п осле јануар и
1 (о 1 365)
знаменце за сезонското поместу вање на времето
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
време
*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
Граници дефинирани со имплементацијата: и
%е
локална претстава на датум и време
%d
ден од месецот
%Н
час (24-часовен формат)
%!
час (12-часовен формат)
%ј
ден од годината
%m
месец
(01-31) (00-23) (00-11) (001-366) .
%М
(01-12) . минута (00-59) .
%р
локален еквивалент за АМ или РМ.
%Ѕ
секунда
%U
31 3
(00-61).
број на седмица во годината (недела е 1-от ден од седмицата)
(00-
53) . %w
ден од седмицата (о- б
%W
број на седмица од годината (понеделник е 1-от ден од седмицата)
%х
локална претстава на датумот.
%Х
локална претстава на времето.
%у
година без столетие
%У
година со столетие.
%Z
име на временска зона
%%
%.
1
недела е о)
.
(00-53).
(00-99) .
1
доколку постои.
Б11. Граници дефинирани со импnементацијата: и Заглавјето
дефинира константи за големините на сите инте
грални типови . Вредностите подолу се минималните прифатливи распони ; може да се користат и поголеми вредности
СНАR
BIT
СНАR МАХ
8
битови во еден
UCНAR МАХ ИЛИ ЅСНАR
максимална
char
МАХ СНАR
MIN
О или ЅСНАR
char
вредност за
MIN
минимална
вредност
за
char INТ МАХ
32767
максимална
вредност за
int INТ
MIN
-32767
минимална
вредност за
int LONG
МАХ
2147483647
максимална вредност за
-2147483647
минимална
long LONG MIN
long
вредно ст
за
Стандардна библиотека
314
Додаток Б
+127
ЅСНАR МАХ
максимална вредност за
signedchar ЅСНАR
-127
MIN
минимална
вредност за
signedchar
+32767
ЅНRТ МАХ
максимална вредност за
short
SHRT MIN
-32767
минимална
вредност за
short
255
UCНAR МАХ
максимална вредност за
unsigned char
UINT
65535
МАХ
максимална вредност за
unsigned int
ULONG
4294967295
МАХ
максимална вредност за
unsigned 1ong
USHRT
МАХ
65535
максимална
вредност за
unsigned short Имињата во табелата подолу, подмножество од , се константи по врзани со аритметиката на реалните броеви. Секоја од дадените вредности, претставува минимална големина за соодветната величина
.
Секоја имплемен
тација дефинира соодветни вредности.
FLT RADIX
2
основа на експонент , репрезентација на
FLT ROUNDS
пример,
мод
на
2 , 16
заокружување
на
реални
броевиприсобирање
FLT DIG
б
FLT EPSILON
1Е-5
FLT
МАNТ
FLT FLT
МАХ
DIG
декадни цифри на прецизност
најмалиот број х, за кој 1. о број на цифри на основата
+х ~ 1 . о FLT_ RADIX
во мантиса
1Е+37
МАХ ЕХР
најголемиот реален број
најмалиот претстави
FLT MIN FLT MIN
1Е-37 ЕХР
n , таков за кој може да се FLT_RADIX"-1
најмалиот нормализиран реален број најмалиот n за кој
10n е нормализиран
број
DBL DIG
10
DBL EPSILON
1Е-9
DBL
МАNТ
DBL
МАХ
DIG
декадни цифри на прецизност најмалиот број х, за кој 1. о број на цифри на основата
+х
!
=1. о
FLT_RADIX
во мантиса
1Е+37
најголемиот реален број со двојна прецизност
(double)
Б.11
DBL
Граници дефинирани со иммемектацијата: и
МАХ
ЕХР
најголемиот претстави
DBL MIN
lE-37
n,
за
кој
може да
315 се
FLT_ RADrxn- 1
најмалиот нормализиран реален број со двојна прецизност (double)
DBL MIN
ЕХР
број
на
мантиса
основата
FLT RADIX
во
Додаток
8: Преrnед на промените
Уште од објавувањето на првото издание на оваа книга , дефиницијата на е доживеа промени. Речиси сите промени беа во смисла на продолжение на оригиналниот јазик и внимателно беа дизајнирани за да останат компатибил ни со постојната практика; некои од нив претставуваат поправки на двосмис
леностите во оригиналниот опис , а некои се модификации кои ја менуваат постојната практика. Многу од новините беа најавени уште во документите кои ги следеа компајлерите достапни од АТ&Т , и последователно беа усвоени од другите производители на е компајлери . Пред извесно време,
ANSI
комитетот
задолжен за стандардиз ирање на јазикот ги вклучи повеќето од промените , а,
исто така, воведе други значајни модификации. Извештајот делумно беше под држан од некои комерцијални компајлери , уште пред објавувањето на фор малниот е стандард. Овој додаток ги сумира разликите помеѓу јазикот дефиниран во првото из
дание на книгава и она што се очекува да биде дефинирано од финалниот стан дард . Единствено го обработува јазикот, не неговата околина и библиотека ; иако истите заземаат значаен дел од стандардот, има многу малку што да се
споредува , бидејќи првото издание не се обиде да опише околина или библи отека.
-
За разлика од првото издание, во стандардот претпроцесирањето е де
финирано повнимателно , и е проширено ; експлицитно се базира на белези ; има нови оператори за надоврзување на белези гови
(ft) ;
(#ft), и за креирање на стрин #pragma ; повторното
постојат нови контролни линии, како ftelif и
декларирање на макроата од иста секвенца на белези е експлицитно дозволе но ; параметрите внатре во стринговите повеќе не се заменуваат. Делењето на
линиите со " \ " е дозволено насекаде, не само за стринговите или дефинициите на макроата . Види Параграф. А. 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
во повеќето имплементации беше достапен со години .
Стандардот воведува употреба на типот вач; претходно типот
char ја
void*
како тип за генерички покажу
имаше таа намена; во исто време, направени се
експлицитни правила против мешање на покажувачи и цели броеви , и покажу вачи од различен тип, без употреба на претопувања.
-
СтандаRt!от воведува експлицитни минимуми за границите на аритметич
ките типови, во заглавја како
и давајќи
на сите посебни имплементации.
карактеристики
Преглед на промените
мер,
-
Додаток В
319
Енумерациите се нови уште од првото издание на книгата.
Стандардот од С++ ја усвојува нотацијата на квалификатор на тип, на при
const
(Параграф А8.
2) .
Стринговите сега се непроменливи, па можат да се сместуваат во мемо
ријата резервирана само за читање.
то
"Вообичаените аритметички конверзии" се сменети, во принцип, намес
"за целите броеви, unsigned секогаш победува; за реалните, секога ш
користи double" се преферира "унапреди до најмалиот тип кој има доволно капацитет". Види Параграф Аб. ѕ.
-
Старите оператори за доделување како
=+
целосно се отстранети . Исто
така , операторите за доделување сега претставуваат еден белег ; во прв ото издание, се сметаа за парови, и можеа да се одделат со празно место помеѓу.
-
Повлечено е правилото кое му дозволуваше на компајлерот да ги третира
математички асоцијативните оператори како пресметковно асоцијативни.
-
Унарен +е воведен заради симетрија со унарниот-.
Покажувач кон функција може да се користи како функциски означ у вач
без експлицитна употреба на операторот
-
*.
Структурите може да се доделуваат, да бидат аргументи и повратни вред
ности на функција.
-
Примената на адресниот оператор врз низите е дозволена, а резултатот е
покажувач кон низата.
-
Операторот sizeof, во првото издание враќаше int вредност; оттогаш ,
повеќето имплементации го направија unsigned . Стандардот го направи не
говиот тип експлицитно зависен од имплементацијата, но бара типот size_ t , да биде дефиниран во стандардното заглавје . Слична промена има и во типот кој означува разлика помеѓу покажувачи (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 -- оператор за декрементирање ( намал ување за 1) 20, 55, 124, 238 - оператор за одземање 49, 241 - операторот унарен минус 238-239 ! логички о перато р за негација 50, 238-239 != оператор за.е разли чн о од• 18, 49, 243 • наводни к 9, 22, 45, 227 # претпроцесорски оператор 105.278 ## претпроцесорски оператор 105,278 #def iпе 15,104.276 #def ine наспроти enum 46,175 #def ine со аргументи 104 #def ine, мултилиниски 104 #else, #elif 107,281 #endif 107 #error 282 #if 107, 158, 279 #ifdef 107,281 #ifndef 107,281 #include 38, 103, 178, 279 #line 282 #pragrma 282 #undef105,202, 278 % оnератор за остаток при цел обројно делење (модул) 49, 241 %1d претворање 20 & адресен оп ератор 109, 238 && логички оп еартор И 24, 49, 58, 243 &. битски оператор: И 57, 243 * оператор за множење 49, 241 *оператор за дереференцирање 110,238 , оператор за пирка 73, 247 . оператор за членство во структура 150, 235 .. . декларација 182, 237 .h екстензија на име на датотека 38 1оператор за делење 11,49,241 ?: тернарен оператор за условен израз 61, 246 \\ знакот контраниз (анг. backslash) 9, 45 \О знакот null ( ништо, нула, празно) 35, 45, 226 \а знак за аларм 45,226 \b зна к за бришење на знак (анг. backspace) 9,
45,226 \f нов_лист (анг. formfeed), знак за 45,226 \п нова_линија, знак за 7, 17, 22. 43-45, 226, 291 \r знак за нов ред (анг. carriage returп) 45,226 \t таб, знак за 9, 12, 45,226 \v вертикален таб, знак за 45,226 \х хексадекадна излезна секвенца 43, 226 л битски оператор: исклучиво ИЛИ 57, 243 _ знакот подвлечено4 1 , 226,291 _FILE_ преmроцесорско име 309 _ LINE_ претпроцесорско име 309 _fillbuf фун кција 209 _IOFBF, _IOLBF, _IONBF 294 1битски оператор: ИЛИ 57, 243 11 логички оператор ИЛИ 24, 49, 58, 246 - операторот комплемент на 58, 238-239 + оператор за собирање 49, 241 + оnераторот уна рен nлус 238-239 ++ оператор за ин крементирање (зголемување за 1) 20, 55,1 24, 238 += оператор за доделување 59 < оnератор за помало од 49, 242 « оператор за nоместување во лево 58, 242 <= оnератор за nомало или една кво на 49, 242 заглавие (анг. header) 308 заглавје 51,301 заглавје 301 заглавје 42,314 заглавје 42,314 заглавје 291 заглавје 52, 304 заглавје 309 за главје 311 загла вје 182, 205, 309 заглавје 121 , 158, 291 заглавје б, 18, 104-105, 119, 177-178,291 , сод ржини од 207 заглавје 83, 166, 305 заглавје 46, 124, 302 заглавје 311 = оnератор за доделување 19, 50, 246 == оnератор за еднаквост 21, 49, 243
32 1
322
Програмски јазик С
Индекс
> оnератор за поголемо од 49, 242 -> оператор за членство кај nокажувач кон структура
154,235
>= оператор за поголемо или еднакво на 49, 242 >> оператор за nоместување во десно 58, 242 0... октална константа 43, 22б А автоматска класа nри меморирање 3б, автоматска nроменлива 3б, 87, автоматска, делокруг на 93,
228
228
275
asin библиотечна функција 305 asm клучен збор 224 atan, atan2 бибпиотечни функции 305 atexit библиотечна функција 308 atof библиотечна функција 305 atof функција 83 atoi библиотечна функција 305 atoi функција 51, 72, 8б atol библиотечна функција 305 auto сnецификатор на класа nри меморирање 249
автоматска, иницијализација на 3б, 47, 99, 2б2 адитивни оnератори
Б
адреса на nроменлива
бафер, влез nоставен во
241 32, 110,238 адреса на регистер 249 адресен оnератор, & 109, 238
294
адресна аритметика види nокажувачка
баферирана
аритметика
безличен (анг. null) исказ
аларм, знак за \а
200
бафер, nоставување во види setbuf, setvbuf BUFSIZ
getchar 202
20, 2бб 119, 232 безличен (анг. null) стринг 45 безличен (анг. null), знакот за, \О 35, 45, 22б белег 223, 27б безличен (анг. null) nокажувач
45, 22б
Американски институт за национални стандарди
(анг. American National Standards lnstitute) (ANSI) ix, 2, 223 апостроф, знакот,' 21,43-45, 22б
белези, замена на 27б белези, надоврзување на
аргумент, nокажувач
105, 278 71, 104 библиотечна функција 7, 79, 93 бинарен nоток 187,291-292 бинарно дрво 1б3
аргумент, функција
битови , изрази за маниnулација со
апстрактен декларатор 2б3 аргумент кој nретставува nодниза
аргумент, дефиниција за
117
28, 235
аргумент, нагорно nретопување на
54, 237
117 28, 237
аргументи со nроменлива должина, листа на
182,205, 237,2б0,270, 309
бесконечен циклус, for ~;)
58, 175
битски оnератор И, &
57, 243 битски оnератор ИЛИ, 1 57, 243
аргументи, void(npaзнa) листа на 38,8б,2б0,270
битски оnератор исклучиво ИЛИ, л
аргументи, командна линија
битски оnератори
аритметика, nокажувачка
битско nоле, декларирање на 17б,25 1
133-1 38 110,115,117-121, 137,
битско nоле, изедначување сnоред
1б2,241 аритметички оператори
блок, иницијализација во
вообичаени
блок-структура б5, 98, 2б8
50, 232
98, 2б8
брзо_nодредување
229
асоцијативност на о nератори б2,
150, 253
блок види сложе н исказ
49
аритметички nретворања (конверзии), аритметички тиnови
57, 243
57, 243
234
a.out б, 82 abort библиотечна функција 306 abs библиотечна функција 308 асоѕ библиотечна функција 305 addpoint функција 152 addtree функција 1б5 afree функција 119 alloc функција 118 argc број на аргументи 133 argv аргумент вектор 133, 190 ASCII множество од знаци 21, 43, 51, 27б, 302 asctime библиотечна функција 312
102,1 29 10, 20, 42,314 binsearch функција б9, 157, 1б 1 bitcount функција 59 break исказ 70, 7б, 2б9 bsearch библиотечна функција 308
броеви, големи на на
в
19, 24, б 1 151 вертикален таб, знак за, \v 45, 22б взаемно рекурзивни структури 164, 253 вгнезден исказ за доделување вгнездена структура
вистински аргумент види аргумент влез од тастатура
17, 177, 200
Индекс
Програмски јазик С
влез,небафериран
200 200 влез, поврат на 91
gmtime библиотечна функција 312 goto исказ 77, 2б9
влез, форматиран види ѕсапf
д
влез/излез на знаци
датотека, вклучување на
влез,бафериран
17, 177 влез/излез, грешки при 192, 301 178, 188, 200 внатрешни имиња, должина на
41, 224 97 _ 228, 275
внатрешни стати ч ки променливи
внатрешно поврзување
11, 21 , 2б, бб 15
волшебни броеви
вообичаен (подразбирлив) функциски тип
103, 279 200 датотека, дополнување на 187, 206, 292 датотека , креирање на 188, 199 датотека , мод на пристап до 187, 209, 292 датотека, отворање на 187, 199, 202 датотека, покажува ч на 187, 20б, 292 датотека, привилегии на 204 датотека, nристап до 187, 199, 209, 292 датотека , дескриптор на
влез/излез, редирекција (пренасочување) при
вовлекување
35, 235
датотеки, п рограма за копирање (пресликување)
вообичаена (подразбирлива) големина на низа
18-19, 201,204
101,132, 156
датотеки, п рограма за надоврзување на
вообичаена ( подразби рлива) иницијализација
датум, претворање на
101,262
дводимензионална ни за (матрица),
вообичаена ознака б9,
323
266
иницијализац ија на
187
130
131, 2б3
50, 232 va_list, va_start, va_arg, va_end 182,205,297,309 void • покажувач 109, 121, 140, 233 void листа на аргументи 38, 8б, 260, 270 void тип 35, 229, 233, 250 volatile, квалификаторот 229, 250 vprintf, vfprintf, vspr i пtf библиотечн и фун кци и 205, 297
дводимензионални н из и (матрици)
г
декларација на класа на мемориски простор
ге нерички покажувач види
декларација на надворешна nроменлива
вообичаени аритметички претворања
void • покажувач глобални, делокруг на 93, 275 глобални, иницијализација на 47, 95, 99, 2б2 големи загради 7, 11, бЅ, 98 големи загради, позиција на 11 големина на броеви 1О, 20, 42, 314 големина на структура 162,239 градирање кај покажувачка ар итметика 121, 232 граничен услов 21 , 77 getbits функција 58 getc библиотечна функција 188, 299 getc макро 207 getch функција 92 getchar, uпbuffered 201 getchar, бафериран 202 getchar, библиотечна функција 17, 177, 188, 299 getenv библиотечна функција 308 getint функција 114 getline функција 33, 37, 81, 193 getop функција 91 gets библиотечна функција 192, 299 gettoken функција 146 getword фу нкција 1б0
д восмисленост,
if-else
бб, 2б8,
129,131,263
283
декларатор 25б-2б0 декла ратор на низа
257
декларато р, апс трактен 2б3
декла ратор, функциски
259 10, 47, 249-254 декларација на typedef 171 , 249, 265 декларација на union 172, 251 декларација
249 36, 270
декларација на низа
25, 130,257 25 7 декларација на nо кажувач 11 О, 117, 257 декларација на структура 150, 251 декларација на функција 259-260 декларација на функција, имплицитна 31 , 85, 235 декларација нас проти дефиниција 38, 93, 249 декларација, битска 176, 251 декларација, н адворешна 270-272 дек ремент, операторот, -- 20, 55, 124, 238 делење, оператор за , 1 11, 49, 241 делење, целобројно 11, 49 дел огруг 228, 273-275 дел ок руг на автоматските 93, 275 делокруг на глобалните 93, 275 дело круг на ознака 78, 2бб, 275 делокруг, лексички 273 делокруг, nравила за 93, 273 декларација на пип
дереференцирање ѕее индирекција на изведени типови
1, 11 , 229
десно поместување, о пе раторот за,>>
дефанзивно програмирање б7,
70
58, 242
324
Програмски јазик С
Индекс
дефиниција на аргумент 28,235
енумератор
дефиниција на глобална променлива
38, 273
дефиниција на макро 27б дефиниција на мемориски простор
249
28, 235 28, 81 , 270
дефиниција, отстранување на види #uпdef дефиниција, провизорна
273 na 54, 230
децимален дел, отсекување
директориум (именик), програма за листање на
210 доделување кај
scanf, изземање од 184,297 19, 24, б 1 доделување, израз за 19, 24, б 1, 24б доделување, конверзија преку 52, 24б доделување, оnератор за, += 59 доделување, о nератор за, = 19, 50, 246 доделување, операто ри за, 50, 59, 246 доделување, повеќекратно 24 должина на имиња 41 ,224 должина на имињата на променливите 224 должина на стринг 35, 45, 122 дополнителни дејства 63, 105, 234, 237 дрво, бинарно 163 дрво,парзирачко 144 day_of_year функција 130 defined претпроцесорски оператор 107, 281 del nрограма 146 del функција 144 difftime библиотечна функција 312 DIR, структурата 212 dir.h вклучува чка датотека 215 dirdcl функција 145 Dirent, структурата 212 dirwalk функција 214 div библиотечна функција 308 div_t, ldiv_t имиња на типови 308 доделување, вгнезден исказ за
dо,исказот 75 , 2б9
do-nothing функција 82 double константа 43,227 double, тип 11, 20, 42, 229, 250 double-float претворање 54, 232 Е евалуирање, редосnед на
24, 58, 63, 75, 90, 105,
111,234 еднаквост, оператори за
46, 107,226-227, 25б
енумерациски таг 25б
дефиниција на параметар
дефиниција на функција
227, 256
енумерациска константа
49,243 21, 49,243
енумерациски тип
229 61 , 97, 103, 166,219 Е нотација 43, 227 EBCDIC множество знаци 51 echo, програмата 134-135 EDOM304 else види if-else исказ else-if 26, 67 end of file види EOF enum наспроти #define 46, 175 enum сnецификатор 4б,25б EOF 18, 177, 292 ERANGE 304 errпo 301, 304 error функција 205 errors, влез/излез 192, 301 exit библиотечна функција 190, 30б EXIT_FAILURE, EXIT_SUCCESS 306 ехр библиотечна функција 305 expansion, макро 278 extern спецификатор на класа на мемориски простор 3б, 38, 93, 249 ефикасн ост
3 завршување на програма
189,192 291
заглавја, табела на стандардни загnавје, датотека за загради, израз во
38, 96 235
заземач (алокатор) на мемориски п ростор
166,217-221 запирка, оnераторот,,
73, 247
зборови, програма за броење на
22, 163 17, 177 знак кој се печати 302 знак, неозначен 52,228 знак, означен 52, 228 знаковен додаток 52-54, 208, 226 знаковна константа 21,43, 22б знаковна константа, октална 43 знаковна кон станта, широка 226 знак-целоброен, конверзија 26,50, 230 знаци (знаци за празно место), бланко 184, 194, 297,302 знак влез/излез
знаци, множество од 27б
еднаквост, операторот за,==
знаци, множество од,
еквиваленција на типови 2б5
знаци, множество од, EBCDIC
експлицитно претворање, оператор за види
ASCII 21, 43, 51,276, 302 51 знаци, множество од, 150 276 знаци, низа од 22,32,122
претопување
знаци, стринг од види стринг, константа
екран, излез на
17, 178, 190, 200
Програмски јазик С
Индекс
знаци, функции за тестирање на
194, 301
238
ѕ
интеграnни типови
ѕвонче, знак за, види аларм, знак за
интеграnно на горно претоnување 52, 230
и
информации, криење на
224 избегнување на goto 78 изедначување преку union (унија) 218
исказ, означувач на крај на
идентификатор
изедначување според битски полиња изедначување, ограничување заради
178
234-247
израз во загради
235
19, 24, б 1, 246 65, 67, 2бб израз,константен 45, 69,107, 247 израз, примарен 234 изрази, ред на извршување на 62, 234 име 224 име на дадотека, екстензија на .h 38 име, криење на 98 имиња , должина на of 41, 224 имиња, простор на 273 имплицитна де кларација на функција 31 , 85, 235 индекси, негативни 117 индексирање и покажувачи 114,11 6,259 индексирање на низа 26,114,235,259 индирекција (дереференцирање), оператор за, * 110,238 иницијализатор 273 иницијализатор, форма на 99, 247 иницијализација 47, 99, 260 иницијализација во блок 98, 268 иницијализација која се под разбира 101 ,262 иницијализација на автоматски 35, 47, 99, 262 иницијализација на глобални 47, 95, 99, 262 израз за доделување израз, исказ кој е
иницијализација на дводимензионална ни за (матрица )
131,263
101,132,262 119,162 иницијализација на статички 47, 99, 262 иницијализација на структура 150,262 иницијализација на унија 262 иницијализација низи од структури 156 иницијализација на низа
иницијализација на покажувач
иницијаnизација преку константент стринг
исклучоци
234, 311
278 if-else двозначност бб, 268, 283 if-else исказ 21 ,24,65,268 inode 210 instali функција 170 int type 1О, 42, 250 isalnum библиотечна функција 160,302 isalpha библиотечна функција 160, 194, 302 iscntrl библиотечна функција 302 isdigit библиотечна функција 194,302 lseek системски повик 205 isgraph библиотечна функција 302 islower библиотечна функција 194,302 ISO множество на знаци 276 isprint библиотечна функција 302 ispunct библиотеч на функција 302 isspace библиотечна функција 160, 194, 302 isupper библиотечна функција 194,302 isxdigit библиотечна фувкциј 302 itoa функција 76 искористување на константа
jump, исказите 269 к
калкулатор, програма
85, 87, 89, 185 246, 291 класа на мемориски простор 228 класа на мемориски простор, auto спецификатор квалификатор, тип на
на 249 класа на мемориски простор , automatic
36,228 extern сnецификатор на 36, 38, 93, 249 класа на мемориски nростор, register спецификатор на 97, 249 класа на мемориски nростор, static 36, 97, 228 класа на мемориски nростор, static сnецификатор на 97, 249 класа на мемориски простор, декларација на 249 класа на мемориски простор,
класа на мемориски nростор, отсуство на
с nецификатор на
101,
251 инкрементирање, оnератор за, ++
79-80, 88, 90 11 , 65
искази, секвенцирање на 2бб
176, 253 162, 1бб,
излез, форматиран видиprintf, редирекција израз
229
искази 2бб-270
174,195,217, 233 излез на екран 17, 178, 190, 200 излез, секвенца за 9, 21, 43-45,226,276 излез, секвенца за, \х хексадекадна 43, 226 излез, табела на секвенци за 45, 226 (nренасочување) на излез
325
250
класа на мемориски прос тор, сnецификатор на
250 20, 55, 124,
клучни зборови, листа на
224
326
Програмски јазик С
Индекс
клучни зборови, програма за броење на 15б
мали букви, програма за претвора ње во
командна линија, аргументи од
133-1 38
мемориски простор, дефиниција на
28
217-221
коментар
179
249
мемориски простор, заземач (алокатор) 1бб,
10,223-224, 27б
компајлирање наС програма б,
компајлирање на повеќе датотеки
82 79, 93, 273 комплемент на, операторот , - 58, 238-239 конкатенација ( надоврзување ) на белези 105, 278
мемориски простор, резервирање на
ком пајли ра ње, засебно
меморискиот простор, редослед на низа во
конкатенација ( надоврзување) на стрингови
модул (остаток при делење), операторот, %
45,105,227
241
константа, искористување на
константа , суфикс на константа, тип на
43, 22б
43, 224
контраниз, знакот (а нг. backslash), \\
9, 45
контролен знак
302 контролна линија 103, 27б-278 л л вредност (aнг.lvalue)
230
лево поместување, оператор за, <<
лексикографско подредување лексички делокруг
58, 242
138
273 223
лексички конвенчии
линии, програма за броење на
21
линии, спојување на 27б листа на клучни зборови
множење, оnераторот, •
main функција б main, return од 29, 192 makepoint функција 152 malloc библиотечна функција 1б8, 195, 30б malloc функција 219 memchr библиотечна функчија 304 memcmp библиотечна функција 304 memcpy библиотечна функција 304 memmove библиотечна функција 304 memset библиотечна функчија 304 mktime библиотечна функција 312 modf библиотечна функција 305 month_day функција 130 month_name функција 132 morecore функција 220
224 н
логичка негачија, операторот,!
наводник, знакот, •
21 О 50, 238-239 логички OR, операторот, ! ! 24, 49, 58, 24б логички израз, нумеричка вредност на 52 логичко И, операторот, 5.& 24, 49, 58, 243 локализација, прашања околу 291 labs библиотечна функција 308 ldexp библиотечна функција 305 ldiv библиотечна функција 308 localtime библиотечна функција 312 log,log 10 библиотечни функции 305 long double, константа 43, 227 long double, тип 42,227 long, константа 43,22б long, тип 11, 20, 42, 227, 250 LONG_MAX, LONG_MIN ЗОб longjmp библиотечна функчија 309 lookup функција 170 lower функција 51 ls наредба 210 м макроа со аргументи
49,
27, 32, 39, 79,87-88, 126 241
листање на директориуми , програма за
макро претпроцесор
49, 241
мултипликативни оператори
константен израз 45,б9, 107,247 константи
131,
259
модуларизација
278
43, 22б
249
103,275-282 104
9, 22, 45, 227
нагорно претоnување на аргумент
54, 237
нагорно претопување, интегрално 52, 230 надворешна (глобална) променлива 3б, 86, 228 надворешна (глобална) променлива, декларачија
оf3б,270 надворешна (глобална) променли ва, дефиниција
of38,273 надворешна(глобална) декларација
270-271 41,
надворешни (глобални ) имиња, должина на
224 надворешни (глобални) статички променливи
надворешно (глобално) поврзување 8б,
97 224, 228,
250, 275 најдолга линија, програма за
33, 37 scientific) нотација 43, 8б небафериран getchar 201 небафериран влез 200 негативни индекси 117 нееднаквост, оператор за, != 18,49,243 некомплетен тип 251 неконзистентна декларација на тип 85 нелегална покажувачка аритметика 119-1 21, 1б2, научна (анг.
Програмски јазик С
Индекс
241
327
оnератори за еднаквост 49, 243
неозначен знак
оnератори за nоместување 57, 242
52,228
низа насnроти nокажувач
114, 116-117, 122, 132
низа од з наци
22, 32, 122 низа од nокажува чи 125 низа од структури 155 низа, аргумент име на 32, 117, 131
оnератори, адитивни
241
41 57, 243
оnератори, аритметички
оnератори, битски
оnератори,мултиnликативни
низа, големина која се nодразбира (nочетна
154-155, 234
големина)
оnератори, релациони
101, 132, 156
низа, дводимензионална
241
оnератори, nредимство (nредн ост) на
19, 62, 111 ,
18, 49, 242
оnератори, табела на 63
129, 131, 263
низа, декларатор на
257 25, 130, 257 низа , индексира ње кај 25, 114, 235, 259 низа , иницијализација 101 , 132, 262
оnерации врз nокажувачи, дозволени
низа , декларирање
оnерации врз уни и
низа , иницијализација на дводимензионална
отсекување на децимален дел
131,263
отсекува ње со делење
121
174 оnишувач на датотека 200 отворе н системски nовик 202
низа , nовеќедимензионална
54,230 11,49,241 отстранување на дефиниција види #undef
низа , nромена на и мето на
отсуство на сnецификатор на класа на мемориски
129, 259 116,234
низа , редослед во меморискиот nростор кај
nростор
131,259
отсуство на сnецификатор н а тиn
низа, референца кон
235 нов ред, знак за (ан г. carriage return), \r 45, 226 нов_лист (анг. formfeed), з нак за, \f 45, 226 нова_линија 223, 276 нова_линија, знак за , \n 7, 17, 22,43-45, 226, 291 нула, исnуштање на сnоредба со бб, 123 нумеричка вредност на логичко nодредување нумеричка вредност на релацио нен израз
нумеричко nодредување
52 50, 52
138NULL, 119
numcmp функција 141 о објект
228, 230
облик (форма) (ан г. pattern), nрограма за наоfање на
79,81 , 135-137
обратна nол ска нотација
87
одделено конnајлирање 79, 93, 273 одеземање, оnератор за,
- 49, 241 121, 162, 232
одземање на n окажувачи ознака
77,266 69,266 ознака, default 69, 266 ознака, делокруг на 78, 266, 275 означен знак 52, 228 означен исказ 77, 266 означен тиn 42,250 означувач, функциски 235 октална знаковна константа 43 октална ко нстанта, О... 43, 226 оnератор за собирање,+ 49,241 оnератори за асоцијативност of 62, 234 оnератори за доделување 50, 59, 246 ознака, саѕе
250
250 O_RDONLY. O_RDWR, O_WRONLY 202 opendir функција 215 Ох ... хексадекадна константа 43, 226 п nараметар
98, 116,236
nараметар, дефиниција на
28, 235 napcep, рекурзив но-сnуштачки 144 nарсирачко дрва 144 повеќе датотеки, компајлирање на 82 nовеќедимензионал на низа 129,259 повеќекратно доделување 24 nовеќенасочна одлука 26, 67 nовик по адреса (референца) 31 повик по вредност 31 , 111 ,236 пов рат на влез 91 поврзување 228, 273-275 поврзување, внатрешно 228, 275 поврзување, надворешно 86, 224, 228, 250, 275 повторување ( итерација), искази за 269 nоголема или еднакво на, операторот, >= 49, 242 поголема од, оnераторот, > 49, 242 подвлечено, знакот за, _ 41 , 224, 291 nодниза, аргумент кој претставува 117 подредување на линии од текст 125, 139 подредување, лексикографско 138 nодредување,нумеричко 138 подредување, nрограма за 126, 139 позиција на загради 11 nокажувач кон датотека 187, 206,292 nокажувач кон структура 160 покажувач кон функција 138, 172, 235
328
Програмски јазик С
покажувач наспроти liиза
покажувач, void
Индекс
114, 116-117, 122, 132
* 109, 120, 140,233
покажувач, аргумент
претоn ување
претворање, знаковен-целоброен 2б,50, 230
null) 119, 232
покажувач, генерирање (созда вање) на
234
покажувач, декларација на
110, 117,257 покажувач, иницијализација на 119, 1б2 покажувач, претворање на 1бб, 232, 241 покажувачи и индекси 114,1 1б,259 покажува чи , дозволени операции врз 120 покажувачи, низа од 125 покажувачи, одземање на 120, 1б2, 232 покажувачи, споредба на 119, 1б2, 219, 225 покажувачка аритметика 110, 115, 117-120, 137, 1 б2, 241 покажувачка аритметика, градирање во покажувачка аритметика, недозволена
1б2,
121, 232 119-121,
241
покажувач-целоброен, претворање
231-233, 241
пол е ѕее би тско пол е полска нотација
87
помало или еднакво на, операторот,
помало од, операторот,
<= 49, 242
< 49, 242
претворање, покажувач-целоброен
232-234, 241 54, 230 претворање, целоброен-знаковен 54 претворање, целоброен-покажувач 233, 241 претворање, целоброен-реален 13, 230 претопување, конверзија преку 54, 232-234, 241 претопување, оператор за 54, 1б6, 195, 232, 241, 263 претпроцесор, макро 103, 275-282 претпроцесорси оператор, defiпed 107,281 претпроцесорски оператор , # 105, 278 nретпроцесорски оператор, ## 105, 278 претпроцесорско име, _FILE_ 309 претпроцесорско име, _LINE_ 309 претпроцесорско име, однапред дефинирано 282 префисно ++и-- 55, 124 пречекорување (анг. overflow) 49,234,304, 311 претворање, реален-целоброен
nречекорување на долна граница (анг. uпderflow)
49, 304, 311
поместување, оператори за
привилегии на датотека
постфиксен ++и--
примарен израз
57, 242 55, 123 поток, бинарен 187,291-292 поток, текстуален 17, 177, 291
187, 209, 292 273 програма за calculator 85, 87, 89, 185 програма за cat 187, 189-192 програма за del 14б програма за echo 134-1 35 програма за fsize 213 програма за uпdcl 147 програма за броење на зборови 22, 163 програма за броење на знаци 19 програма за броење на знаци 19 провизорна дефиниција
45
празна функција
204
234
пристап кон датотека, мод
празен исказ види пull исказ празен стринг
52, 232
претворање, експицитен оператор за види
117
покажувач, безличен (анг.
претворање, float-double
82
п разни места, програма за броење на
25, 70 nразни место, бланко 223 празно место, знаци за 184, 194, 297, 302 преведување, редослед на 275 преведување, фази на 223, 275 превод, еди ница за 223, 270, 273 предимство (предност) на оператори 19, б2, 111 , 154-155, 234 преносливост 3, 43, 51 , 58, 172, 177, 179, 217 престапна година, пресметка на 49, 130 претворања, вообичаени аритметички 50, 232 претво рање (конверзија) 231-233 претворање (промена ) на името на низа 11б, 234 претворање на покажувач 166,232,241 претворање на тип преку returп 8б, 270
програма за броење на клучни зборови 15б програма за броење на линии
датотека
18-19, 201,204
nрограма за листање на директориум програма за најдолга линија
33, 37
програма за наоѓање на облик (форма , анг.
pattern)79, 81, 135-137 програма за подредување
126,139
nрограма за претворање во мали букви
234
претворање преку поврат (анг. returп) 8б,
21 О 187
програма за надоврзување на датотеки
претворање преку претоnување
nретворање на функција
25, 70
програма за копирање (пресликување) на
претворање на тип, оператор за види правила за
50, 52, 232
21
програма за броење на празни места
270
претворање со доделување
13-15,17
претворање со претопување
програма за табела за поврзување
52, 246 54, 232-234, 241 претворање, double-float 54, 232
179
програма за претворање на температура 9-1 О,
програма, форма на
1б8 11, 21 , 25, 47, 1б2, 223
Програмски јазик С nрограма, читливост на
Индекс
11, 61, 76, 101,172
програмски аргументи види аргументи од командна линија променлива
228
променлива должина, листа на аргументи со
182,205,236,260,270,309 36,87,228 променnива, адреса на 32, 11 О, 238 променлива, автоматска
променлива, надворешна (глобална) 36, 86, 228 променливи, должина на имиња на
224 41 , 224 nрототиn на функција 29, 35, 54, 85, 140, 236 perror библиотечна функција 301 рор функција 90 pow библиотечна функција 27, 305 power функција 28, 31 printd функција 102 printf библиотечна функција 7, 12, 20, 179, 296 printf претворања, табела на 180, 2% pгintf примери, табела на 14,180 ptlnreet функција 152 ptrdiff_t име на тип 121, 172, 242 push функција 90 putc библиотечна функција 188,299 putc макро 207 putchar библиотечна функција 17, 178, 188, 299 puts библиотечна функција 192, 299 променливи, синтакса на имиња на
329
register спецификатор на класа на мемориски nростор 97, 249 remove библиотечна функција 292 rename библиотечна функција 292 return (nоврат) од main 29, 192 return, иcкaзoт 28,35, 82, 86,270 return, nретворање на тип преку 86, 270 reverse функција 73 rewind библиотечна функција 301 Richards, М. 1 Ritchie, D. М. xi е
самореференцирачка структура секвенца на искази селекција, исказ за
164,253
266 268
симболички ко нстанти, должина на
41
синтакса на имињата на nроменливите
41 , 224
синтаксна н отација
227 системски nовици 199 сложен исказ 65, 98, 266, 270-272 современ стил, функција во 236 сnецификатор на класа н а мемориски простор
249 сnецификатор на класа на мемориски простор,
auto 249 сп ецификато р на класа на меморис ки nростор,
extern 36, 38, 93, 249 сnецификатор н а кла са на мемориски п ростор,
р
реален-целоброен, претворање
54, 230
register 97, 249 сnеци фикатор н а класа на мемори с ки n ростор,
реална константа
13, 43, 227 реални, типови на 229 реrистер, адреса на 249
static 97, 249 спецификатор н а класа на мемори с ки n ростор,
редирекција види влез/излез, редирекција
отсуство на
(nренасочување) nри
спецификатор на тип
редослед на извршување
24, 58, 63, 75, 90, 105,
250
спецификаторот enum 46, 256
спецификаторот struct
111 , 234 резервирани зборови
275 42,224
резервирање на мемориски простор
сnојување на линии
napcep 144 101,163,165,214, 237,269
релационен израз, нумеричка вредност на
276
сnоредување н а покажувачи
249
рекузивно-спуштачки
релациони оператори 17, 491 , 242 RAHD_MAX 306 raise библиотечна функција 311 rand библиотечна функција 306 rand функција 55 read, системски nовик 200 readdir функција 21 б readlines функција 127 realloc библиотечна функција 306
256
спецификаторот union 256
редослед на nреведување
рекурзија
250
119, 162, 219, 243
сnроведува
ста ндарден влез
50, 52
177, 188, 200 178,188,200 стандардна греш ка 188,200 стандарден излез
стандардни заглавја, табела на
291 28, 38, 85, 237 статички, иницијализација на 47, 99, 262 степенувачки (за степенување) 27, 305 стринг константа 7, 22, 35, 45, 116, 122, 227 стар-стил, функција во
стри нг константа , иницијализа ција со помош на
101 , 262 стринг константа, широка
227
330
Индекс
Програмски јазик С
стринг литерал види стринг константа, тип на 234 стринг, должина на
35, 45, 122 стрингови, надоврзување на 45,105,227 структура, вгнездена 151 структура, големина на 162, 239 стру ктура, декларација на 150,251 структура, име на член на 150, 253 структура, иницијализација на 150, 262 структура, оператор за членство во, . 150, 235 структура, операторот п о кажувач на,-> 154, 235 структура, покажувач на 160 структура, самореференцирачка 164, 253 структура, таг ( име, ознака) на 150, 251 структури, взаемно рекурзивни 164, 253 структури, иницијализација на низа од 156 структури, низа од 155 структурна референца , семантика на 237 структурна референца , синтакса 237 суфикс, константен 226 sbrk системски повик 219 scanf библиотечна функција 112, 184, 298 scanf изземање од доделување 184, 297 scanf претворања, табела на 185, 298 SEEK_CUR, SEEK_END, ЅЕЕК_ЅЕТ 301 setjmp библиотечна функција 309 setbuf библиотечна функција 294 setvbuf библиотечна функција 294 Shell, D. L. 72 shellsort функција 73 short, тип 11, 42, 239, 250 SIG_DFL, SIG_ERR, SIG_IGN 311 signal библиотечна функција 31 1 sin библиотечна функција 305 sinh библиотечна функција 305 ѕ izе_tименатип 121 ,1 58, 172,239,292 sizeof, операторот 107, 121 , 158,238-239, 299 sprintf библиотечна функција 182, 297 sqrt библиотечна функција 305 squeeze функција 56 srand библ иотечна функција 306 srand функција 55 sscanf библиотечна функција 298 stat системски повик 212 stat структура 21 2 stat.h include file 212-213 static класа на мемориски простор 36, 97, 228 static променливи , внатрешни 97 static променливи , надворешни (глобал ни) 97 static функциска декларација 97 static, специфи катор на класа на мемориски простор 97, 249 stderr 188, 190, 292
stdin 188, 292 stdout 188, 292 str index функција 81 strcat библиотечна функција 302 strcat функција 57 strchr библиотечна функција 302 strcmp библиотечна функција 302 strcmp функција 124 strcpy библиотечна функција 302 strcpy функција 123-1 24 strcspn библиотечна функција 304 strerror библиотечна функција 304 strf time библиотечна функција 312 strleп библиотечна функција 304 strlen функција 46, 11б, 121 strncat библиотеч на функција 302 strncmp библиотечн а функција 302 strпcpy библиотечна функција 302 strpbrk библиотечна функција 304 strrchr библиотечна функција 302 strspn библиотечна функција 304 strstr библиотечна функција 304 strtod библиотечна функција 305 strtok библиотечна функција 304 strtol, strtoul библиотечни функции 306 struct спецификатор 251 swap функција 103, 112, 129, 141 switch, исказот 69, 88, 268 syscalls.h вклучувачка датотека 201 system библиотечна функција 195, 308 т
табела за поврзување, nрограма за
168 printf претворања 180,296 табела на printfпpимepи 14,180 табела на scanf претворања 185, 298 табела на излезни секвенци 45, 226 табела на оператори 63 табела на стандардни заглавја 291 таг (име, ознака ) на enumeration 256 таг (име, ознака ) на union 256 таr (име, ознака) на структура 150, 251 тастатура, влез од 17,177,200 текстуален поток 17, 177, 291 текстуални ли н ии, подредување на 125, 139 температура, програма за претворање на 9-1 О, 13-15, 17 терминал, влез и излез од 17 тип на константа 43, 226 тип на стринг 234 тип, декларација на 257 тип, квалификатор на 246, 250 табела на
Програмски јазик С тиn, некомnлетен
Индекс ф
251
85 тиn, отсуство на сnецификатор на 250 тип, спецификатор на 250 тиnови,аритметички 229 тиnови, еквиваленција на 265 тиnови, изведени 1, 11, 229 тиnови, имиња на 263 тиnови,реални 229 точка заnирка 11, 17, 20, 65, 67 триграф секвенца 276 tаllрсфункција 166 tan библиотечна функција 305 tanh библиотечна функција 305 Thompson, К. L. 1 time библиотечна функција 312 time_t име на тиn 311
фази на преведување
ТМР_МАХ294
функциски аргумент, nретворање на види
тиn, неконзистентна декларација
форматиран влез види scanf форматиран излез види
printf 1О, 42, 228 функции за тестирање на знаци 194, 301 функција, декларација на 259-260 функција, имnлицитна декларација на 31, 85,235 функција, означувач на 235 функција, покажувач кон 138, 172, 235 функција, претворање на 234 функција, современ-стил на 236 функција, стар-стил на 29, 38, 85, 236 функциска декларација, static 97 функциска дефиниција 28, 81 , 270 функциски аргумент 28, 233 фундаментални типови
у
унарен минус, оnераторот,унарен nлус, оnераторот,
238-239
+ 238-239
174
унија, иницијализација на унија, таг ( име, ознака) на
262 251
условен израз, тернарен оnератор за условно комnајлирање
223, 275
формален параметар види параметар
tmpfile библиотечна функција 294 tmpnam библиотечна функција 294 tolower библиотечна функција 179, 194, 302 toupper библиотечна функција 194, 302 treeprint функција 166 trim функција 77 typedef декларација 171, 249, 265 types, интегрални 229 types, фундаментални 1О, 42, 228 types.h вклучувачка датотека 21 3, 215
унии, оnерации врз
331
?: 61, 246
107, 279
ULONG_MAX 306 undcl програма 147 ungetc библиотечна функција 194, 299 ungetch функција 92 union, декларација на 172, 251 union, изедначување сnоред 218 uпion, спецификаторот 251 UNIX датотечен систем 199,210 unlink системски повик 205 unsigned char, тиnот 42, 201 unsigned long константа 43,226 unsigned константа 43, 226 · unsigned, типот 42, 59, 229, 250
нагорно претопување на аргумент
функциски декларатор
259
функциски имиња, должина на
41 , 224 235 функциски повик, синтакса на 235 функциски nрототип 29, 35, 54, 85, 140, 235 функциски тип кој се nодразбира 35, 235 fpos_t име на тип 301 fprintf библиотечна функција 188, 294 fputc библиотечна функција 299 fread библиотечна функција 299 fabs библиотечна функција 305 fclose библиотечна функција 189, 292 fcntl.h вклучувачка датотека 202 feof библиотечна функција 192, 301 feof макро 207 ferror библиотечна функција 192, 301 ferгor макро 207 ff\ush библиотечна функција 292 fgetc библиотечна функција 298 fgetpos библиотечна функција 301 fgets библиотечна функција 192, 299 fgets функција 193 FILE име на тип 187 filecopy функција 189 FILENAME МАХ 292 float, константа 43, 227 float, тип 1О, 42, 229, 250 float-double, nретворање 52, 232 floor библиотечна функција 305 fmod библиотечна функција 305 fopen библиотечна функција 187,292 fopen функција 208 FOPEN_MAX 292 функциски повик, семантика на
332
Програмски јазик С
Индекс
for насnроти while 15, 71 for( ;;) бесконечен циклус 71 , 104 for, исказот 14, 20, 71 , 269 fortran клучен збор 224 fputs библиотечна функција 192, 299 fputs функција 193 free библиотечна функција 195, 306 free функција 220 freopen библиотечна функција 189, 292 frexp библиотечна функција 305 fscanf библиотечна функција 188, 297 fseek библиотечна функција 301 fsetpos библиотечна функција 301 fsize функција 214 fsize, nрограма за 213 fstat системски nовик 215 ftеllбиблиотечна функција 301 fwrite библиотечна функција 299 х хексадекадна излезна секвенца, \х 43, хексадекадна константа, Ох ...
226
43, 226
hash табела 169 hash функција 169 Hoare, С. А. R. 102 HUGE_VAL 304 ц цевка
178, 200
целоброен-знаковен, nретворање 54 целоброен-nокажувач, nретворање
целоброен-реален, nретворање целобројна константаt
233,241 13,230
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
creat системски nовик 202 CRLF 177, 291 ctime библиотечна функција 312 ч членка на структура, име на
150, 253
ш широка знаковна константа
широка стринг константа
226 227
Q
qsort библиотечна функција 308 qѕоrtфункција 102, 129, 140
w wchar_t име на тиn 226 while насnороти for 15, 71 while, исказот 11, 71 , 269 write системски nовик 200 writelines функција 127
Програмски јазик С Брајан В. Керниган
1 Денис М.
Ричи
Од предговорот
Се обидовме да ја задржиме концизноста од првото издание. С
не е голем јазик,
па не е добро кога е објаснет со голема книга. Го доработивме претставувањето на критичните можности како што се покажувачите, кои ја претставуваат сржта на
програмирањето во С
.
Ги прочистивме оригиналните примери и додадовме нови
во неколку поглавја. На пример, делот кој ги опишува комплицираните декларации,
е проширен со програми кои ги претвораат декларациите во зборови и обратно. Како и претходно и овде сите примери беа тестирани директно од текстот, т.е. во форма препознатлива за компјутерите. Како што рековме во предговорот на првото издание,
"С
станува покорисен како
што расте и искуството во работата со него". После десетгодишно дополнително искуство, се уште го мислиме тоа. Се надеваме дека оваа книга ќе ви помогне да го научите е и успешно да го користите.
ISBN 978-608-4535-48-5
9 786084 535485