/
Text
Osborne 19
Хърб Шилдт казва на програмистите
това, което те искат и трябва да знаят
— просто, ясно, систематизирано и
авторитетно.
ACM Computing Renews
Практически
самоучител
Най-успешният
и доказан метод
за научаване на С
Работы с всички
C/C++ компилатори
Трето издание
Вижте►► Научете>► Използвайте
• Овладейте основите на С
• Получете знания чрез стотици
примеры и упражнения
• Възползвайте се от
професионални трикове и
техники
------Разглежда ANSI С---------
СОФТПРЕС
практически
самоучител
Хърбърт Шилдт
Osbome/McGraw-Hill
ОфТПРЕС
ИЗДАТЕЛСТВО
Teach Yourself C, Third Edition
Authors: Herbert Schildt
Bulgarian edition copyright © 2001 by SoftPress Ltd.
Original edition copyright © 1997 by The McGraw-Hill Companies.
All rights reserved.
С - Практически самоучител
Автор,- Хърбърт Шйлдт
Езданието на български езих е пуб.гйкувано от издателство СофтПрес ООД,
в сътрудничесгво с “Интерфейс-Болгария” ООД, 2001
ISBN 954-685-168-х
Издателски колектие:
Редактор: Красимир Цолов
Предпечатна подготовка; Милена Славкоза
Художествен редактор: Владимир Владимиров
© Красимир Караиванов, Христо Кошев, превод, 2001
Всички права запазени. Нито едва част от тази книга не може да бъде размножавапа
или предавана под никаква форма или начин, електронен или механичен, вклточи-
телно фотокопиране, записване или чрез каквито и да е системи за съхранение на
информация, без предварително писмено разрешение на СофтПрес ООД.
За контакты:
Адрес: София 1407, П.К. 114
теп.: (02) 958 25 80, 958 25 67; факс: (02) 58 62 04
e-mail: clients@soft-press.com; Web site: www.soft-press.com
За Дистрибуция:
СофтПрес София - ул. “Искърско шосе’ 19;
тел,: 02/ 973 15 06; e-mail: iliev@soft-press.com
СофтПрес Пловдив - бул. “Руски” 139, стая 104;
тел.: 032/ 62 75 62; тел./факс: 032/ 62 27 47; e-mail: p!o\,div@scft-press.com
СофтПрес Бургас - йд. Тройката” 4;
тел.: 056/ 80 02 31; факс: 056/ 80 31 39; e-mail: burgas@soft-press.com
СофтПрес Стара Загара - ул. “Пар Симеон Велики” 117;
тел.: 042/ 60 27 75; e-mail: stzagora@soft-press.com
СофтПрес Варна - бул. “Васин Левеки” бл. 9, вх. А, ет. 2, ап. 11;
тел.: 052/ 30 42 69; e-mail: varna@soft-press com
За автора
Хърбърт Шилдт е един от водешитс автори на
книги за програмиране в света. Той е специалист
по езиците С и C++, старши Windows програ-
ммист и експерт по Java. Неговите книги за прог-
рамиране са продадени в почти два милиона ек-
земпляра по света и са преведени на всички ос-
новни езици. Той е автор на редица бестселъри,
вкпючително: C:The Complete Reference, C++:
The Complete Reference, C++ from the Ground Up,
MFC Programming from the Ground Up, Windows
95 Programming in C and C++, Windows NT4
Programming from the Ground Up и много друга.
Също така той е член на комисните по стандар-
тизация на ANSI С и C++.
СЪДЪРЖАНИЕ
Предговор 13
За понататыино обучение 19
1 Основи на С ... 21
1.1 Компонентите на една С програма 22
1.2 Създаване и компилиране на програма 27
1.3 Деклариране на променливи и присвояване на
стойкости 30
1.4 Въвеждане на числа от клавиатурата 35
1.5 Извършване на изчисления посредством аритметични
изрази 35
1.6 Добавяне на коментари в програмата 39
1.7 Писа.че на ваши-собствени функции 42
1.8 Използване на функции за връщане на стойности 46
19 Използване на аргументи на функции 50
1.10 Ключовите думи в С 53
2 Представяне на управляващите конструкции в С ... 57
2.1 Запознаване с if 59
2.2 Добавяне на else 61
2.3 Сьздаване на блокоье с код 63
2.4 Използване на цикъл for 66
2.5 Замяна на операторите за инкрементиране и
декрементиране в С 70
2.6 Разширяване на вьзможностите на printf() 74
2.7 Програмиране с логически сператори и оператори за
сравнение 77
3 Още управляващи конструкции в С ... 83
3.1 Четене на знакове от клавиатурата 85
3.2 Влагане на конструкции if 89
3 3 Вариаииите на цикъла for 92
3.4 Цикълът while на С 96
6 С - Практически самоучител
3.5 Използване на цикъла do 98
3.6 Създаване на вложени цикли 100
3.7 Използване на break за излизане от цикъл 102
3.8 Използване на конструкцията continue 105
3;9 Избор между алтернативи чрез конструкцията switch 107
3.10 Конструкцията goto 112
4 Подробно описание на типовете даннй, променливите и
изразите ... 115
4.1 Използване на модификаторите за типове данни в С 117
4.2 Къде се декларират променливи 122
4 3 Кснстантите отблизо 129
4.4 Инициализираке на прсменливи 132
4.5 Конвертиранё на типовете при пресмятане на изрази 135
4.5 Конвертиране на типове при присваивания 138
4.7 Преобразуване на типове 141
5 Масиви и низове . . . 145
5.1 Деклариране на едномерни масиви 147
5.2 Използване на низове 152
5.3 Създаване на многомерни масиви 158
5.4 Инициализиране на масиви 161
5.5 Построяване на масиви от низове 165
6 Използване на указатели . . .171
6.1 Основните понятия за укэзателите 173
6.2 Ограничения за изразите с указатели 177
6.3 Използване на указатели с масиви 181
6.4 Използване на указатели към низови константи 188
6.5 Създаване на масиви от указатели 190
6.6 Множествената индиректноёт 192
6.7 Използване на указатели като параметри 194
7 Подробно разглеждане на функциите ... 199
7.1 Прототипи на функции 200
7.2 Рекурсия 210
7.3 Подробно разглеждане на параметрите 214
Съдьржание 7
7.4 Предаваче на аргументи на main() 218
7.5 Сравннване на по-старито декларации на параметри със
съврсменните 222
8 Конзолен вход/изход . . . 227
8.1 Сще една предпроцесорна директива 229
8.2 Въвеждане и извеждане на знакове и низове 233
8.3 Никои нестандартам конзолни функции 235
8.4 По-подробно раэглеждане на gets(} и puts() 238
8.5 Овлздяване на printff) 240
8 6 Овладяваие на scanf() 246
9 Файлов вход/изход . . . 255
9.1 Какво представляват потоците 257
9.2 Основите на файловата система 258
9.3 feoff) и ferror() 266
9 4 Няксй текстови функции от високо ниво 270
9 5 Четене и записване на двоични данни 274
9 6 Произволен достъп 280
9.7 Различии функции на файловата система 284
9.8 Стандартнее потоци 287
10 Структури и обединения . . . 293
10.1 Основи на структурите 294
10.2 Деклариране на указатели към структури 306
10.3 Работа с вложени структури 310
10.4 Какво представляват побитовите полета 315
10.5 Създаване на обединения 319
11 Разширени типове данни и оператори . . . 327
11.1 Използваче на спецификаторите от кгаса за
съхранение 329
11.2 Изголзване на модификаторите за достъп 338
11.3 Дефиниране на иэброявания 342
11.4 Какво представлява typedef 346
11.5 Използване на побитовите оператори на С 348
11.6 Овлздяване на операторите за изместване 352
8 С - Практически самоучител
11.7 Операторът ? 354
11.8 Още за оператора за присвояване 357
11.9 Операторът запетая 359
11.10 Приоритетите на операциите 361
12 Предпроцесорът на С и някои професионални теми . . . 365
12.1 Още за #define и ^include 367
12.2 Условно компилиране 371
12.3 #errort #undef, #lrne и #pragma 377
12.4 Вградените в С м^кроси 380
12.5 Използване на операторите # и ## 382
12,6 Указатели към функции 383
12.7 Овладяване на динамичного заделяне 369
А Место използвани библиотечки функции на С . . . 397
А 1 Низови и знаке ви функции 398
А.2 Математически функции 409
А.З Функции за час и дата 417
А.4 Функции за динамично заделяне 423
А.5 Други функции 426
В Преглед на ключовите думи в С , . . 439
С План на Windows програма . . . 451
Кея версия на Windows? 452
Перспекгивите на Windows програмирането 452
Как взаимодействат Windows и вашата програма 455
Windows е многозадачен s456
WIN32 API 456
Компонентите на един прозорец 457
Никои основи на Windows приложенията 458
Функцията на прозореца 470
Няколко думи за дефинициэнните файлове 471
Конвенциите за имената 471
За да научите ловече 471
Съдържание 9
D Отговори . . . 473
1.3 1.4 1.5 1.6 1.7 18 1.9 Упражнения 474 Упражнения 4 74 Упражнения 474 Упражнения 475 Упражнения 475 Упражнения 476 Упражнения 476 Проверка на уменията, представени в главата Глава 1 477 Преглед на знанията Глава 2 478
2.1 2.2 2.3 2.4 2.5 2.6 2.7 Упражнения 479 Упражнения 479 Упражнения 480 Упражнения 480 Упражнения 481 Упражнения 482 Упражнения 482 Проверка на уменията, представени в главата Глава 2 482 Преглед на знанията Глава 3 484
3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9 Упражнения 485 Упражнения 486 Упражнения 487 Упражнения 488 Упражнения 489 Упражнения 489 Упражнения 490 Упражнения 491 Упражнения 491
3.10 Упражнения 492
4.1 Проверка на уменията, представени в главата Глава 3 492 Проверка на натрупаните знания Глава 3 494 Преглед на знанията Глава 4 494 Упражнения 496
10 С - Практически самоучител
4.2 Упражнения 496
4.3 Упражнения 498
4.4 Упражнения 498
4.5 Упражнения 498
4.6 Упражнения 499
4.7 Упражнение 499
Проверка на умения-a, представени в
глават а Глава 4 499
Проверка на натрупаните умения Глава 4 500
Преглед на знанията Глава 5 501
5.1 Упражнения 502
5.2 Упражнения 503
5.3 Упражнения 504
5.4 Упражнения 504
5.5 Упражнения 505
Проверка на уменията, представени в
главата Глава 5 506
Проверка на натрупаните умения Глава 5 507
Преглед на знанията Глава 6 510
6.1 Упражнения 511
6.2 Упражнения 512
6.3 Упражнения 512
6.4 Упражнения 512
6.5 Упражнения 513
6.6 Упражнения 513
6.7 Упражнения 514
Проверка на уменията, представени в
главата Глава 6 515
Проверка на натрупаните умения Глава 6 515
Преглед на знанията Глава 7 516
7.1 Упоажнения 517
7.2 Упражнения 518
7.3 Упражнения 519
74 Упражнения 519
7.5 Упражнения 521
Съдържание 11
Провёрка на уменията, представени р
главата Глава 7 521
Проверка на натру па ните умения Глава 7 Преглед на знанията Глава 8 524 523
8.1 Упражнения 525
8.2 Упражнения 526
8.3 Упражнения 526
8.4 Упражнения 527
8.5 Упражнения 527
8.6 Упражнения 527
Проверка на уменията, представени в главата Глава 8 528
Проверка на натрупаните умения Глава 8 Преглед на знанията Глава 9 533 529
9.2 Упражнения 534
9.3 Упражнения 536
9.4 Упражнения 538
95 Упражнения 542
9.6 Упражнения 543
9.7 Упражнения 545
9.8 Упражнения 545
Проверка на уменията, представени в главата Глава 9 546
Проверка на натрупаните умения Глава 9 Преглед на знанията Глава 10 552 548
10.1 Упражнения 555
10.2 Упражнения 557
10.3 Упражнения 558
10.4 Упражнения 561
10.5 Упражнения 561
Проверка на уменията, представени в главата Глава 10 563
Проверка на натрупаните умения Глаза 10 565
Преглед на знанията Глава 11 567
11.1 Упражнения 568
11.2 Упражнения 568
12 С - Практически самоучител
11.3 Упражнения 569
11.4 Упражнения 569
11.5 Упражнения 570
116 Упражнения 571
11.7 Упражнения 573
11.8 Упражнения 573
11.9 Упражнения 573
Проверка на уменията, представени в
главата Глава 11 574
Проверка на натрупаните умения Глава 11 575
Преглед на знанията Глава 12 579
12.1 Упражнения 580
12.2 Упражнения 580
12.5 Упражнения 581
12.6 Упражнения 581
12.7 Упражнения 583
Проверка на уменията, представени в
главата Глава 12 584
Проверка на натрупаните умения Глава 12 585
Предговор
Тази книга ви учи как да програмиратс на езика, считан обикно-
вено за най-важният в света: С.
Една от причините за постигането и задържането на успеха
на С, е че програмистите го харесват. С комбинира финност и
елегантност с истинска мощ и гьвкавост. Тий е структуриран
език, неналагащ ограничения и неизползващ принуда. Освеи то-
ва С е език, предаващ изцяло управлението на вас - програмис-
тите. С е създадсн от программиста за програмисти. Той не е про-
дукт, замислен от комисия, а по-скоро резултат от търсенето на
по-добър език за пршрамиране.
С е важен и поради друга причина. Той е преддверието към
другите два професионални световни езика: C++ и Java. C++ е
построен върху С, a Java е построен върху C++. Поради това С е
в основата на цялото модерно програмирапе и знанието на С ле-
жи в основата на създаването на високоефективни и качествен!!
софтуерни продукта. Накратко казано, за да бъдете професиона-
лен програмист в днешно време, трябва да сте компетентни по С
Кратка история на С
С беше изобретен и имплементиран първоначално от Денис Ри-
чи в DEC PDP-11 при използваяето на операционната система
UNIX. С е резултагьт от процес па разработка, който започна с
един по-стар език, наречен RCPL и разработен от Мартин Ри-
чардс BCPL новлия на език, наречен В, изобретен от Кен Томп-
сън, и това доведе до създаването на С крез седемдесегте години.
В продължение на много години практическият стандарт за С
беше този, описан в книгата The С Programming Language от
Брайън Кърниган и Денис Ричи (Prentice-Hall, 1978 г.). С иарасг-
ването на популярностга на С обаче, проз 1983 г. беше сформи-
рана комисия, която създаде ANSI (American National Standards
Institute) стандарт за С. Процесът на стандартизация отне шест
години (много повсче, отколкото някой беше очаквал). Стандар-
тьт ANSI С беше окончателно ратифипиран в края на 1989 г., а
първите копия бяха досгьпни за масите през 1990 г. Този стан-
дарт беше леко прсменен през 1996 г. В наши дни на практика
всички С компилатори спазват ANSI стандарта и това е версияга
на С, която ще изучавате в тази книга. (Това означава, че книгата
разглежда ANSI С.)
С често се оприличава на език от средно пиво. Преди появата
на С в света съществуваха общо взето два вида езици за програ-
14 С - Практически самоучител
мираке. Единият от тях се нарича асемблерен език, който предс-
тавляла символната репрезентация на същинските машинни инс-
трукции, изпълнявани от компютьра. Асемблерният език е от
ниеко ниво, запюто програмистите трябва да работят (макар и в
символен вид) с истинските инструкции, конто компютърът те
изпълнява. Той може да се използва за създаването на високос-
фективни програми, но не предоставя вградени конструкции за
контрол или I/O функции. Всички тези елементи трябва да се йз-
граждат от програмиста. Точно обратного, езиците от высоко
ниво буферират програмиста от компютьра. Те обикновено под-
държат различии структури за контрол, кбманди за вход и изход
и други подобии, конто правят програмирането по-бързо и по-
лесио. Все пак елементите от езйцитё от високо ниво може да не
са директно свьрзани с начина, по който Компютърът всыцпост
Изпълнява Програмата. Тези разлики често правят програмите,
написани на език от високо ниво, по-малко ёфективни в сравне-
ние с тези, написани на асемблерен език; Тъй като много хора
намират програмирането на асемблерен език за досадна и трудна
задача, се появи нуждата от език, който да балансира леснота за
използване и ефективност. Много програмиста смятат, че С пре
доставя този баланс. Той успешно комбивйра структурата на
език от високо ниво с мощностга и ефективността на асемблерен
език. Тъй като С запълва прззнината между асемблерните езици
и тези от високо ниво, той се нарича език от средно ниво.
Първоначално С се използвапте предимно за създаването на
системен софту ер. Систем ният софту ер се състои от нрограми,
конто спомагат за използването на компютьра. Тук са включени
програми от рода на операциояни системи, компилатори и ре-
дактори. Все пак с нарастването на популярността на С, той за-
почиа да се използва за създаване на програми с общо предназ-
начение. В днептяо времс на практика С се използва от програ-
мистите за всякакви програмни задачи. Той е език, който издър-
жа проверката на времето и се доказа като гъвкаво и мощно
средство за програмиране.
Си Qi*
Начинаещите понякога се объркват за разликите между С и C++
и за това, как тези езици са евързани помежду си. Накраткс каза-
но, C++ е разширсна версия на С, която е проектирана да под-
държа обектно-ориентирано програмиране (ООП). C++ съдържа
и педдържа цслия език С заедмо с набор от обсктно- ориентирани
допълиения (Това означала, че C++ е надмножество на С.) Тъй
като C++ е построен върху основата на С, няма как да се научи
С+ч без да се знаят основните пеща от С. Затова ако миелите, че
един ден ще се ориентирате към C++, вашите знания по С ще са
не само полезни, но и задължителни.
Предгопор 15
За книгата
Тази книга е укикална, защото ви учи на езика С чрез прилагане-
то на сисгемагизирано обучение, Това сгава чрез последователи©
представяне на идеи, всяка от конто е последвапа от редана при-
меря и упражнения, за да можете напълно Да разберете всяка те-
ма. Този подход осигурява пълното овладяване на всяка тема
преди да преминаването напред.
Материалите се представят послёдователпо. Това означава, че
всяка глава трябза впимателно да се отработи, защото всяка
следваша глава иредполага, че знаете материала, предстаиена в
предишните.
Тази книга разглежда стандартно ANSI С. Така се осигурява
приложностга на вашите знания върху най-широк спекгьр от
среди за С. Освен това тази книга използва современен синтак-
сис и структура, коего означава, че от самого начало ще науча-
вате прапинния начин за нисане на С программ.
Как е организирана книгата
Книгата се състои от 12 глави и четири приложения, Всяка глава
(освен първата) започва с Проверка на знанията, конто се със-
тои от вьпроси и упражнения, покриващи материала от предиш-
ната глава Главите са разделени на секции. Всяка секция разг-
лежда пе една тема. В края на всяка секция има примери, след-
вани от упражнения, конто изпитват вашите знания по темата. В
края па всяка от главите щс видите Проверка на уменнята,
представени в главата, която нроверява ваптите знания за мате-
риала, представен в главата. Накрая се представя Проверка на
натрупаните умения, коетс нроверява колко добре можете да
интегрирате новите знания с тези от предишните глави.
Какво е новото в третото издание
В по-голямата си част формата и структурата на тази книга не са
промейяни спрямо предишните две издания. След като С е ста-
билен и стандартизиран език, нямаше нуж,ца от големи промени.
Една от двете значими промени е включването на пьяните про-
тотипи на функциите във всички программ, започвайки от първа
глава. Тъй като вече всички компилатори подцържат - всъщност
почти изискват - прототипиране на функциите, първоначалното
разглеждане на този въпрос беше преместено в началото на кни-
гата. Другият набор от промени беше предизвикан от нараства-
щото доминиране на 32-битовите среди. Това наложи пренапис-
вэнето на много от примерите, така че те да могат да работяг
както за 16-, така и за 32-би гови програми. На никои места мо-
жете да откриете допълнителни примери, разширени обяснепия
16 С - Практически самоучител
и повече упражнения. На последно място някои промени бяха
направени просто, за да отразят модерните стилове в писането на
програмен код.
Използеаните конвенции
Когато в текста се споменава част от програма (например ине на
променлива), тя ще се показва с удебелеп шрифт. Ако в текста
има термин, той ще с отпечатан с наклонен шрифт.
Ще работи ли C++ компилатор със С прогреми?
В днешно време повечето компиляторы могат да компилират од-
новременно С и C++ программ. Бсыцност често срещано явле-
ние е даден компилатор да се рекламира като “C/C++” компила-
тор, или понякога просто каго ’’C++”. Все пак всички компила-
тори, конто могат да компилират C++ програми, могат също така
да компилират и такива на С. Затова ако ваишят компилатор се
нарича “C++ компилатор”, не се притеснявайте - той съшо така е
пълен компилатор на стандартно ANSI С.
Ако използвате Windows
Ако използвате Windows и цента ви е да пишете програми, бази-
рани на Windows, тогава сте избрали да учите правилния език. С
е езикът, за конто беше проектирано Windows програмирапето, и
той е езикът, на конто иа написани много от Windows програми-
те. Въпреки това, няма да можете веднага да пишете Windows
програми. Ето защо.
Windows прогремите са доста по-сложни за стздаване от ос-
таналите. Дори и най-малката, нищо не правеща 'Windows прог-
рама изтюлзва няколко специализираны С техники, като структу-
ры, указатели и разширени типове на параметры на функции. По-
ради тази причина не е възможно да се преподана програмирапе
на С чрез писането на Windows програми, защото Windows про-
грамата предполага, че вие сте опытен С програмист!
Ако на вашия кимпютър използвате Windows (вместо DOS,
UNIX и т.н.), пак можете да учите С. Просто ше трябва да стар-
тирате вашите програми от командния ред на DOS, защото те
няма да са програми, базирани на Windows. Всички съвременни
C/C++ комиилатори автоматично ще създадат правилната среда
за излълнение на програмите, показаны в тази книга, така че това
не е нещо, за ксето обикновено трябва да се тревожите.
Ако ще пишете Windows програми, ще трябва да прочетете
Приложение С след каго свършите с книгата. То съдържа кратко
описание на основите на Windows програмиранетс и включва
план на Windows програма, която можете да се опитате да създа-
Предговор 17
дете. Проучването на гози план ше ви даде сыцо гака нъзмож-
ност да разберете, че е необходимо истинско познаване на С
предн да е възможно създаването на Windows програми.
Не забравяйте: Код в Web
Заномнете, че соре кодовете за всички програми от таэй книга са
доитьпни безнлатно в Интернет на адрес http://www.osborne.com.
Свалянето на този код ще ви снести необходим оспа от въьеж-
дансто на примерите
ЗА ПОНАТАТЪШНО ОБУЧЕНИЕ
С - Практически само учится, трето издание е вашего вьве-
декие в серията от книги за програмиране на Хърб Шилдт. Его
една част от списъка с остапалите книги за програмиране на
Шилдт; публикуванп от Osborne/McGraw-HilL
Ако искате да научите повече за С, тези книги те са ви осо-
бено полезни:
С: The complete Reference
The Annotated ANSI C Standard
Ако ще преминавате към C++ (обектно-ориептираното раз-
ширение на С), значи кнт/гите на Шилдт ще ви предложат отлич-
но разглеждапе на този важен език. Ние препоръчваме:
C++ - Практически самоучитеп (издадена на бълаарски език от
издателство СофтПрес)
C++: The Complete Reference
C++ from the Ground Up
Ако ще разрабо гвате програми за Интернет, трябва да проче-
тете
Java: The Complete Reference
конто e написана в съавторство с Патрик Ноутьн.
На последно място, акс искате да програмирате под Windows,
препоръчваме
Schildt's Windows 95 Programming in C and C++
Schildt’s Advanced Windows 95 Programming in C and C++
Windows NT 4 from the Ground Up
MFC Programming from the Ground Up
Ако ви трябват солидни отговори, бързо
потърсете Хърбърт Шилдт - признатият
специалист по програмиране.
Основи НА С
Разглеждани теми в главата
1.1 Компонентите на една С
програма
1.2 Създаване и компилиране на
програма
1,3 Деклариране на променливи и
присвояване на стойкости
1.4 Вьвеждане на числа от
клавиатурата
1.5 Извършване на начисления
посредством аритмстични изрази
1.6 Добавяне на коментари в
програмата
1.7 Писане на заши-собствени
функции
1.8 Използване на функции за
воъщане на стойности
1.9 Използване на аргументи на
функции
1.10 Ключоь ите думи в С
22 С - Практически самоучител
Отделимте елементи.от компютьрните езици като С не
работяг сами, а по-скорс в сьединения с други елемен-
ти. Това налага нуждата от разбирането на еяколко
ключовй аспекта от С преди подробного проучване на
всеки елемент на езика. За да направл това, тази глава представя
бърз преглед на езика С. Неговата цел е да ви даде задоволител-
ни работки знания по С, така че да можете да разберете приме-
рите в следвашите глави.
Докато преми наваге през тази глава, не се притеспявайте, ако
някои от негцата не са сьвсем ясни. Основного нещо, което тряб-
ва да разберете, е как и защо примерните програми се изпълня-
ват по показания начин. Не забравяйте, че повечетс от темите,
представени в тази глава, ще се обсъждат доста по-подробно по-
късно в книгата. В тази глава ще научите за основната структура
на С прогремите; какво е конструкция, променлива, константа и
функция в езика С. Ше се научите как да показвате текст на ек-
рана и как да получавате информация от клавиатурата.
За най-пъйноценно използване на тази книга, трябва да имате
комшотьр, С компилятор и текстов редактор. (Със същия успех
бихте могли да използвате и C++ компилятор. Той сыцо може да
компилира С програми.) Ако вашият компилятор притежава соб-
ствен текстов редактор, няма да ви трябва отделен такъв. За най-
добри резултати трябва да работше заедио с примерите и да
правше всички упражнения.
ы
КОМПОНЕНТИТЕ НА ЕДНА С
ПРОГРАМА
Всички програми на С имат едни и същи определеии основпи
компонента и особсноста. Всички тс се състоят от одна или по-
вече функции, всяка от конто съдържа една пли повече конст-
рукции. В С, терминът функция се използва за подпрограма, коя-
то може да се извйква от други части на програмата. Функциитс
са "тухлите” на С. Конструкцията задача действие, което трябва
да се извърши от програмата. С други думи, конструкции! е са
частите от програмата, конто всъщност извършват операциите.
Всички конструкции на С завършват с точка и запетая. С не
разпознава край на рсд като герминатор. Това означала, че няма
ограничения за местоположението на конструкцията върху реда.
Сыцо така, бихте могли да поставите две или повече конструк-
ции на един и същи ред.
Обикповеиата форма на С функция е :
Основи на С 23
тип-на-резу) imam име-на-функцията(списък-с-пэраметри)
{
поредииа от конструкции
}
Тук тип-на-резултат определя типа на данните, връщдни от
функцията. Както ще видите ио-нататьк, функцията има възмож-
ноет да врыла стойност. Име-на-функцията е името на функци-
ята. На функцията може да се предава информация чрез нейните
параметра конто се задават в описъка с параметри на функцията
- списък-с-параметри. Иоредицата от конструкции може да се
съетои от една или повече конструкции. (Чисто технически пог-
леднато, една функция може да не съдържа никакви конструк-
ции, но тъй като това означава, че тя не изпълнява никакво дейс-
твие, това е частен случай.) Ако “врыцани стойности” и “пара-
метри” са нови концепции за вас, не се притеснявайте - тс ще
бъдат обяснени по-късно в тази глава.
С някояко изключения, можете да наречете дадена функция с
каквето пожелаете име. То трябва да ее състои само от главни и
малки букви от латинската азбука, числата от 0 до 9 и подчерта-
вагцото тире. Името на функцията обаче не може да започва с
цифра. С е чувствителен към регистрите на буквите, което оз-
начава, че различава малки от главни буКви. Затова, според С,
Myfunc и myfunc са две напълно различий имена.
Въпреки че една програма на С може да съдьржа някс-лко
функции, единстаеката функция, която тя трябва да притежава,
е maln( ). Функцията main() е мястото, където започва изпълне-
нието на програмата. Това означава, че когато се стартира вата-
га програма, тя започва да изпълнява консгрукциите в main(),
като започва с първата конструкция след отварящата фигурна
скоба. Вашата програма завършва, когато достигав до затваря-
щата фигурна скоба на main(). Разбйра се, фигурните скоби
всъщност не сътцествуват в компилираната версия на програма-
та, но е полезно да миелите, че те наистина са там.
Когато в тази книга в текста се сном сняв а дздена функция, тя
ще е отпечатана с удебелея шрифт и ще е следвана от скоби. По
този начин веднага ще можете да забележите, че името се отиася
за функция, а не за някагва друга част от програмата.
Друг важен компонент във всички програми на С са т.нар.
библиотечни функции. ANSI С стандарты1 определя набор от
библиотечки функции, конто трябва да се доставят с всички С
компилатори. Тези функции могат свободно да се използват от
вашата програма. В С тази колекция от функции обикновено се
нарича стандартна библиотека. Стандартната библиотека съ-
държа функции за извършване на дисков I/O (input/output-
24 С - Практически самоучител
вход/изход), манипуляции с низовс, математически начисления и
много други. Когато вашата програма се компилира, кодът на
всяка библиотечна функция се вмъква автоматично. Това е една
от разликитс в работзта на някои други езици. В BASIC или Pas-
cal например, операции като записване във файл или изчислява-
не на косинус се извършват чрез използвансто на ключови думи,
вградени в самия език. Предимството на С да притежава тези
операции като библиотечки функции е в подобряването на гьв-
кавостта. Библиотечпите функции могат да се подобряват и раз-
ширяват, за да се приспособят към променливите условия, но
самият език С не трябва да се променя. Както ще видите по-къс-
но, на практика всички программ на С, конто ще създадете, ще
използваг функции от стандартната С библиотека.
Една от най-често срещаните библиотечки функции се нари-
ча printf(). Това е С функция с обшо предназначение. Функция-
та printff ) е доста многостранна, позволяваща много вариации.
Нейпаза най-проста форма е показана тук:
pri л tl (“ни з-за-от п ечатване");
Функцията printf( ) отпечатва на екрана знаковете, конто се съ-
държат между кавичките. (Кавичките не се показват на екрана.)
В С, един или повече знакове, оградени с кавички, се наричат
низ. Низът в кавички, поставен между скобите на printf(), се на-
рича аргумент на printf(). По принцип, предаваната на дадена
функция информация се нарича аргумент. В С, извикването на
библиотечна функция е конструкция, поради коего тя трябва да
завърши с точка и запетая.
За да извикате функция, вие зада вате нейното име, следвано
от ограден в скоби списък с аргументите й. Ако функцията не
изисква никакви аргумента, значи не се задава нищо - списъкът
между скобите е празен. Ако нма повече от един аргумент, те
трябва да се разделят със залетай.
Друг компонент, обичаен за повечето програми на С, е хедър-
ният файл. В С, информациям за функциите от стандартната
библиотека се намира в различии файлове, предоставени с вашия
компилятор. Всички тези файлове са с разширение .Н. С компи-
латорът използва информацията от тези файлове, за да обрабоги
правилне библиотечните функции. Тези файлове се добавят към
програмата чрез нредпроцесорната директива #inciude. Всички
С компилатори използват нреднроцесор за първата фаза на ком -
пилираието. Този кредцроцесор извършва различии манипула-
ции върху соре кода преди той да се компилира.
Предпроцесорните директиви всъщност не са част от езика С, а
по-скоро инструкции от вас за компилятора. Директивата
# in dude казва на предпроцесора да прочете друг файл и да го
Основи на С 25
вмъкне във вашага програма. По-късно в тази книга ще научите
повече за преднроцесора.
Най-често изискваният хедърен файл се нарича STDIO.H. Ето
директивата, конто вмъква този файл:
^include <stdio.h>
Името на файла можете да зададете както с малки, така и с глав-
ны букви, но обикновено се използват малки. Хсдърният файл
STDIO.H съдържа, нзмежду многого други неща, информация,
сиързана с библиотечната функция printf(). Обърнсте внимание,
че директивата #include не завьршва с точка и запетая. Причина-
та за тсва е, че ^include не е ключова дума от С, която може да
дефинира конструкция. Вместо това тя с инструкция към самия
компнлатор.
Едко последпо уточнение: с малки изключения, С игнорира
всички интервали. Това означава, че той изобщо не се интересу-
ва на кое място на реда се появява конструкция, фигурна скоба
или функция. Ако искате, можете да поставите два или повече
тези елементи на един и същ ред. Примерите в тази книга показ-
ват начина, по който обикновено изглежда коды на С; това е
форма, към която би трябвало да се придържзте. Конкретного
позициониране на конструкции, функции и скоби е въпрос от
стилистично, а не програмно естество.
ПРИМЕРИ
1. Тъй като всички програми на С имат сходни черти, осмис-
лянето на една програма ще ви помогне да разберете мно-
го други. Тук е показана една от най-простите програми:
#include <stdio.h>
int main(void)
{
printf("This is a short C program.");
return 0;
}
Когато тази програма се компилира и изпълни, тя ще по-
каже на екрана на вашия компютър съобщението This is а
short С program.
Въпреки че тази програма е дълга само шест реда, тя
илюстрира аспектите, стандартны за всички програми на
С. Нека я разгледаме ред по ред.
Първият ред от програмата е
26 С - Практически самоучител
ttinclude <stdio.h>
Това предизвиква прочитаието на файла STDIO.H от стра-
на на компилатора и нсговото включване в програмата.
Този файл съдържа информация, касаеща printf().
Вторият ред
int main(void)
поставя началото на функцията main(). Както беше спо-
менато по-рано, всички програми на С трябва ла имат
функция main(). Това е мястото, откъдето започва изпъл-
пението на програмата. int определя, че main( ) връща це-
лочислена стойност. void показва на компилатора, че
main() не приема никакви параметри.
След main() се намира отзаряща фигурна скоба. Това
поставя началото на конструкциите, конто изграждат фун-
кцията.
Следващият ред от програмата е
printf("This is a short С program.");
Това е С конструкция. Тя извиква стандартната библио-
течна функция printff ), която предизвиква изобразяването
на низа на екрана.
Следващият ред кара main() да върне стойността пула.
В този случай тази стойност се връща на извиквашия про-
цес, конто обикновено е операционната система.
return 0;
Според приетата конвенция, стойност пула, връщана от
main( ), показва, че програмата е завършняа своего изпъл-
нение нормално. Всяка друга стойност е сигнал за грешка.
Операционната система може да провери тази стойност, за
да определи дали програмата е изпълнена правилно, или
се е получила грешка, return е една от ключовите думи в
С и тя се разглежда по-подробно по-късно в тази глава.
Накрая програмата е официално зазършила, когато се
доспи не до затварящата фигурна скоба на main().
2. Ето още един пример за програма на С:
^include <stdjo.h>
int main(void)
Основи на С 27
{
printf("This is ");
printf("another C ");
printf("program.");
return 0;
}
Тази програма отпечатва This is another C program, на
екрана на компютъра. Основният момент в нея е, че конс-
трукциите се изпьлняват послсдователно, като се започва
от отварящата фигурна скоба и се завърши със затварящата.
1.2
СЪЗДАВАНЕ И КОМПИЛИРАНЕ НА
ПРОГРАМА
Начинът на създаване и компилиране на програма се определи
до голяма степей от вашия компилятор и операционната систе-
ма, под която работи той. Ако използвате PC или съвместим с
него, значи имате избор от богата гама чудесим компилатори,
като например тези на Borland и Microsoft. Тези компилатори
сьдържат ингегрирани среди за разработка на программ. Когато
използвате такава среда, можете да редактирате, компилирате и
изпълнявате вашите программ направо там - в средата. I ова е
чудесен вариант за начинавши - просто следвайте инструкциите.
предозтавени с компилятора.
Ако използвате традиционен компилятор от командния ред,
тогава трябва да следвате следните стъпки, за да създадете и
компилирате програма
I. Създайте програма с помощта на редактор.
2. Компилирайте програмата.
3. Изпълнете програмата.
Точният метод за осыцествяване на тези стъпки е обяснен в ръ-
ководството на вашия компилятор.
Почти всички съвременни С компилатори са също така и С н
компилатори. Както вече знаете, С-н- е обектно-ориентираното
разширение на С. Най-вероятно за компилиране на вашия С код
вие използвате C++компилятор Не се притеснявайте Товаена-
пълно приемливо, защото всички C++ компилатори имат въз-
можността да компилпрат С программ. Ако например използвате
Borland C++ или Microsoft Visual C++, тогава всичко ше работи
28 С - Практически самоучител
прекрасно. Вънреки всичко има нещо, за което трябва да внима-
вате: разширението на вашите файлове.
Когато именувате вашия програмен файл, трябва да му дадете
разширение .С, а нс .СРР. Това е важно. Ако използвате C++
компилятор, тогава той автоматично ще предположи, че файл с
разширение .С съдържа програма на С, и ще я компилира като
такава, а ако е с разширение .СРР, ще предположи, че програма-
та е написана на C++, и ще се опита да я компилира по съответ-
ния начин. Проблемът е, че вънреки че С е основа на C++, не
всички програми на С са валидни C++- програми. В някои случаи
опитите С програма да се компилира като C++ такава ще доведе
до генерирането на грешки. Тъй като програмите в тази книга са
на С, тс трябва да се комнилират като такива. Използването на
разширението .С осигурява това.
Вашата програма трябва да се компилира като С програма, а
не като такава на C++. За целта се уверете, че прогремите
ви използватразширението С, а не .СРР.
Файлът със създадената от вас С програма се нарича соре
файл. Файлът с компилкраната форма на вашата програма, из-
пълнявана от компютьра, се нарича обектен файл, или понякога
изпълним файл.
Ако з програмата въведете нещо, което не е както трябва,
компилаторът ще докладка за синтактични грешки при опит да я
компилира. Повечето С компилятор!! се опитват да открият ня-
какъв смисьл в соре кода, без значение какво стс написали. По-
ряди тази причина, докладваната грешка нс винаги показва ис-
тинската причина. Например ако забраните да поставите отва-
рящата фигурна скоба на функцията main(), това ще предизвика
някои компи латори да обявят, че конструкцията printf( ) е гре-
шен идентификатор. Това показва, че когато получите съобще-
ние за синтактична грешка, трябва да прегледате редовете преди
този с грешката. Така вероятно по-бързо ще откриете истинската
причина за грешката.
Много от компилаторитс докладват не само действителните
грешки, но и предупредит ел ни такива. Езикът С с проектиран да
бъде почти “всеопрошаващ” и позволява да се компилира почти
всичко, което е синтактично правилно. Все пак някои неща, въп-
реки че са синтактично правилни, са много подозрителни. Ако
компилаторът попадне на такава ситуация, той отпечатва пре-
дупреждение Вие, като програмист, трябва да решите дали не-
говите подозрения са основателни. Честно казано, някои компи-
латори са прекалено отзивчиви и отправят предупреждения за
напълно правилни конструкции. По-важното е, че някои компи-
Осноси на С 29
латори ви дават възможност да включвате различии опции, прос-
то докладващи за информация за вашата програма, която може
би бихте искали да знаете. Поиякога тази информация се доклад-
ва под формата на предупреди гелно съобщепие, въпреки че
всъщност компилаторът няма за какво да ви “предупреди”.
Програмите в тази книга са в съответствие с ANSI стандарта за С
и те няма да генерират никакви предупредителни сообщения, за
коитс трябва да се тревожите.
ПРИМЕРЫ
1. Ако използвате Borland C++, можете да създадете и ком-
пилирате вашата програма посредством интсгрираната
среда. Предлагат се интеракгивни инструкции. Ако изпол-
звате версията за командния ред, ще използвате команден
ред, подобен на този (при положение, че името на програ-
мата ви се карича TEST.C). Така можете да компилирате
дадена програма след като за създаването й сте използвали
тскстови редактор.
ВСС TEST.C
2. Ако използвате Microsoft Visual C++, можете да създадете
и компилирате програмата си в интсгрираната среда.
11редлагат се интеракгивни инструкции Ако използвате
компилаторът за командния ред, този команден ред ще
компилира програмата ви след като сте използвали текс-
тови файл за нейното сьздаване. (Отново предполагамс, че
програмата ви се нарича TEST.C.)
CL TEST.C
3. Ако използвате компилатор от друг производится, обър-
нете се към ръководството на потребителя, за да научите
подробностите за компилирането на програмите ви.
1. Въведете в комнютьра примерните програми от секция
1.1. Компилирайте и ги стартирайте.
30 С - Практически самоучител
Тип
Деклариране на променливи и
ПРИСВОЯВАНЕ НА СТОЙНОСТИ
Променлива е именувана част о г памегга, която може да съдържа
различии стойности. Само най-тривиалните програми не използ-
ват променливи. В С, за разлика от никои компютърни езици,
всички променлизи трябва да се декларират преди да могат да се
пзползват. Декларацията на променлива изпълнява една главна
роля: тя показва на компилатсра от какие тин е използваната
променлива. С поддържа пет различии основни типа данни. Те
са показани в таблица 1-1 заедно с ключовите думи, конто ги
представляват. Нека void не ви обърква Това е специален тип
данни, който ще изследвамс по-късно.
Променлива от тип char е дълга 8 бита и най-често се използ-
ва за съхранение на един знак. Тьй като С е много гьвкав, про-
менлива от тип char може да ее използва при желание и като
“малко цяло число”.
Целочислените променливи (int) могат да съдържат цели
числа (без десетична запетая) със знак. За 1б-битови среди, каго
DOS или Windows 3.11, целите числа са дълги обикновено 16
бита и могат да съдържат стойности в интервала от -32 768 до
32 767. В 32-битовите среди, като Windows 95 или NT, целите
числа обикновено са с дължина 32 бита. В този случай те могат
да съхраняват стойности в интервала от -2 147 483 648 до
2 147 483 647.
Клиочова дума
знэкони данни
цепи числа със знак
числа с плаваща запетая
числа с плгваща запетая с двойна точност
без стойност
char
int
float
doub’e
void
Таблица 1-1
Петте ссновни типа данни в С
Променливите от тип float или double съдържат стойности с
плаваща запетая и със знак, конто могат да имат дробни компо •
ненти. Едпнствената разлика между двата типа е, че double има
около два пъти по-висока точност (брой значещи цифри) откол-
кото float. Освен това при повечето случаи в С, една променлива
от тип double е способна да съдържа стойности, по-големи по
абсолютна стойност, спрямо тези от тип float. Във всички слу-
Оснсви на С 31
чаи, разбира се, променливите от тип float и double могат да
съхраняват много големи стойкости.
За да декларирате прсменлива, използвайте тази обша ферма:
тип име-на-променливата,
където тип е тип за данни в С, а име-на-променливата е името
на променливата. Например следващият ред декларира counter
да бьде от тип int:
int counter;
В С, декларацията на променлива е конструкция и трябва да за-
вършва с точка и запетая.
Същсствуват две места, където се декларират иромепливи: в
някоя функция или извън всички функции. Променяйвите, дек-
ларирапи извън всички функции, се паричат глобалки променли-
ви и могат да се досгигаг от всяка функция в програмата. Гло-
б.шните промеиливи съществуват през цялото време на изпълне-
ние на програмата.
Променливите, декларирани в тялото на дадена функция, се
паричат покални промеиливи. Локалните промеиливи са известии
- и moi ат да се достигат - само вьв функция га, в която са декла-
рирани. Стандартна практика е декларирането на всички локал-
ни промеиливи да става в началото па функцията, веднага след
отварящата фигурна скоба. Засега има два основни момента, ко-
нто трябва да знаете за локалните промеиливи. Първо, локалнит е
промеиливи на една функция нямат нищо общо с тези па друга
функция. Това озиачава, че ако в.една функция е декларирана
прсменлива count, друга променлива count може да е деклари-
рана във втора функция - двете промеиливи са напълно отделни
и не са евързани по никакъв начин. Второго нещо, което трябва
да знаете за локалните промеиливи, е, че те се създават при из-
внкването на функцията и се унищожават при излизането от ней.
Примерите в тази и слсдвашите няколко глави ще използват са-
мо локални промеиливи. Глава 4 обсъжда по-подробно въпроси-
те и усложнепията на глобалните и локалните промеиливи.
Имате възможност да декларирате повече от едка променлива
от един тип, като използвате списък от имена, разделени със за-
петая. Следвашият ред например декларира три промеиливи -
х, у и z - от тип float:
float х, у, z;
Подобно на имената на функциите, тези на променливите в С
се състояг от букви от латинската азбука, цифрите от 0 до 9 и
32 С - Практически самоучигел
подчертаващо таре. (Името обаче не може да започва с цифра.)
Запомнете, че С е чувствителен към регистрите на буквите;
count и COUNT се две съвсем различии имена на променливи.
За да присвоите стойност на променлива, псставете нейното
име от лявата страна на знак за равенство. Стойностга, конто ис-
кате да и дадете, поставьте отдясно. В С, операцията по присвоя-
ване е конструкция, така че трябва да завършва с точка и запетая:
име-на-променлива = стойност;
Например, за да присвоите на целочислената променлива, наре-
чена num, стойността 100, можете да използвате следната конст-
рукция:
num = 100;
В това присвояване 100 е константа. Така както има различии
типове променливи, по същия начин има и различии видове кон-
станти. Константата е постоянна стойност, използвана в прог-
рамата. Константите често се използват в началото на програми-
те за ииициализнране на променливи.
Знакова константа се задана чрез поставянето на знак между
апострофи. Например, за да зададете буквата “А”, трябва да из-
ползвате ‘А’. Числата от тип int ее задават като цели числа.
Стойностите с плаваща запетая трябва да включват десетична
запетая'. Например, за да зададете 100,1, трябва да използвате
100.1. Ако числото, което искатс да зададете, няма дробна част,
тогава трябва да използвате 0. За да кажете на компилатора, че
100 е число с плаваща запетая, трябва да изнолзватс 100.0.
Можете да използвате printf() за изобразяването на стойкос-
ти на знакове, цели числа и на числа с плаваща запетая. За да
направите това все пак, трябва да знаете повече за функцията
printf(). Нека да разгледаме следния пример. Тази конструкция:
printf("This prints the number %d", 99);
изобразила на екрана This prints the number 99. Както можете
да забележите, това извикване на printf( ) съдържа не един, а два
аргумента. Нървият е низът в кавичките, а вторият е константата
99. Обърнете внимание, че аргументите са отделени един от друг
със запетая. Изобщо, когато една функция има повече от един
ар1умент, те се разделят със запетаи. Действието па функцията
printf() е следното. Нървият аргумент е низ, ограден в кавички,
който може да съдържа нормални символи или форматни спе-
1 Б. пр. В С, за разделится на цялата от дробна га част сс използва точка, а
не запетая.
Основи на С 33
цификатори. Последните започват със знак за процент. Нормал-
ните знакове се изобразяват директив на екрана, какао са подре-
дени в низа (четенето започва от ляво на дясно). Форматният
спецификатор, наричан още код за форматиране, информира
рrintf( ), че ще се отпечатва слемент от различен тип. В този
случай %d означава, че щс се изобразява цяло число в десетичен
формат. Стойността, която трябва да се покаже, се намира във
втория аргумент. След това тази стойност се изобразява на мяс-
тото в низа, на което се намира форматният спецификатор. За да
разберете отношенията между нормалните знакове и кодовете за
форматиране, разгледайте тази конструкция:
printf("This displays %d, too", 99);
Сега извикването на printf( ) изобразява This displays 99, too.
Главното e, че стойността, асоциирана с кода за форматиране, се
изобразява на мястото, където този код за форматиране се среща
в низа.
Ако искате да зададете знакова стойност, форматният специ-
фикатор е %с. За задаваке на стойност с плаваща запетая, изпол-
звайте %f, Този код за форматиране работи одновременно за
float и double. Както ще видите по-нататък, printf() има още
много вьзможносги.
Имайте предвид, че стойноститс, напасвапи с форматните
спецификатори, не е необходимо да са константи; те могат да
бъдат и променливи.
ПРИМЕРИ
1. Показаната тук програма илюстрира трите нови концеп-
ции, представени в тази секция. Първо се декларира про-
менлива, наречена num. Второ, на тази променлива се
присвоява стойност 100. Накрая се използва printf( ), за да
се изобрази на скрапа This value is 100. Изучете тази прог-
рама добре:
#include <stclio.h>
int main (void)
I
int num;
num = 10G;
printf("The value is %d", num);
return 0;
}
34 С - Практически самоучитеп
Конструкцията
int num;
декларира num като целочислена променлива.
За да изобрази стойностга на num, програмата използва
тази конструкция:
printf("The value is £d", num);
2. Tази програма създава променливи от типовете char, float
и double; на всяка от тях присвоява стойност и отиечатва
на екрана тези стойности.
^include <stdio.h>
int main(void)
char ch;
float f;
double d;
ch = 'X’;
f = 100.123;
d = 123.009;
printf("ch is %c, ", ch);
printf ("f is %f, ", f);
printf("d is %f", d) ;
return 0;
1. Въведете, компилирайте и изпълнете примерните програ-
ми от тази секция.
2. Напишете програма, която декларира целочислена про-
менлива, наречена num. Дайте на тази променлива стой-
ност 1000 и след това използвайте конструкция printf(), за
да покажете на екрана стойностга така:
1000 is the value of num
ОсновинаС 35
1.4
ВЪВЕЖДАНЕ НА ЧИСЛА ОТ
КЛАВИАТУРАТА
Въпреки че съществуват няколко начина за дъвеждане на число-
вн стойкости от клавиатурата, един от най-лесните е чрез изпол-
зване на една друга стандартна библиотечна функция, наречена
scanf( ). Въпреки че тя притежава значителни възможности, в та-
зи глава ще я използваме само, за да четом цели и дробни числа
то клавиатурата.
За да използвате scanf() за чете на целочислена стойност от
клавиатурата, извикайте я като използвате стандартната й форма:
scanf(“%ci”, &име-на-1п(-променлива);
където име-на-int-npoMeivtuea е името на целочислената промен-
лива, «ияго стойност искаге да получите. Първият аргумент на
scanf() е низ, който определи как да се обработва вторият аргу-
мент. В този случай %d указва, че вторият аргумент ще получи
целочислена стойност в десетичен формат. Този фрагмент нап-
ример чете пяло число, въведено от клавиатурата:
int. num;
scant Snum) ;
Зиакът &, предшестващ името на променливата, е жизпено
важен за рабстата на scanf( ). Въпреки че подробного обяснение
ще дойде по-късно, в обши линии знакът & дава възможност на
функцията да поставя стойност в един о г аргументите си.
Важно е да разберете един важен момент: когато вьвеждате
число от клавиатурата, вне просто пишете низ от цифри. Функ-
цията scanf( ) изчаква да натиснете клавиша ENTER, и едва то-
гава конвертира този низ във вътрешния двоичен формат, изпол-
зва н от компютьра.
За да ирочетете ст клавиатурата дробно число, извикайте
scanf() в нейната стандартна форма:
scanf(u%f, 8djMe-Ha-float-npoMeHjiuea)-,
където UMe-Ha-float-променлнва е името на променливата, декла-
рирана от тип float. Ако искате да въведете double променлива,
използвайте спецификатора %И.
Обърнете внимание, че форматните спецификатори за съог-
ветните типове данни при scanff) са подобии на използваните
при pi intf(). Разликата е, че за double се използва %lf. Това съв-
сем не е съвпадепие - printff) и scanf() са взаимно допълващи
се функции.
36 С - Практически самоучител
ПРИМЕР
1. Тази програма изисква от вас да выведете одно цяло и едно
дробно число. След това изобразява въведените стойности.
^include <stdio.h>
int main(void)
{
int num;
float f;
printf("Enter an integer: ");
scanf("%d", &num);
printf("Enter a floating point number: ");
scanf("%f”, &f);
printf("%d ", num);
printf("%f", f);
return 0;
)
1. Въведете, компилирайте и изпълнете примерната програма.
2. Напишете програма, конто изисква въвеждането на две
дробни числа (използвайте типа float), и след това изо бра
зява тяхната сума.
.5
ИЗВЪРШВАНЕ НА ИЗЧИСЛЕНИЯ
ПОСРЕДСТВОМ АРИТМЕТИЧНИ ИЗРАЗИ
В С, изразите играят доста по-значителна роля, отколкото в ло-
вечето други програмни езици. Това отчасти се дьлжи на факта,
че за разлика от много други езици, С дефинира доста повече
оператори. Израз е комбинация от оператори и операнди. Изра-
зите в С следват правилата на алгебрата, така че в по-голямата си
част те ще са ви познати. В тази секция ще разглеждаме само
аритметични изрази.
Оснопи на С 37
С дефинира следниге пет аритметични оператора:
Оператор Значение
+ събиране
- изваждане
* умножение
/ деление
% остатък при целочислено делене
Операторите “+”, “/” и могат да се използват с вески
един от основните типове данни, но “%” може да се използва
само за целочислени типове. Операторът за остатък при цело-
числено дслене извлича остатъка от деленето на две цели числа
Тази операция няма смисъл, ако се приложи за типове с плаваща
запетая.
Операторът има две значения. Първо, това е операторът за
изваждане и второ, той може да се използва като единичен ми-
нус за промяна на знака на число. Единичен оператор е такъв,
конто използва само един операнд.
Всеки израз може да се постави в дясната страна на конст-
рукция за присвояване. Следващият програмен фрагмент напри-
мер присвоява на целочислената променлива answer стойностга
на израза 100*31.
int answer;
answer = 100 * 31;
Операгорите и ‘’%” са с по-висок приоритет от “+” и
Въпреки това, можете да използвате скоби, за да задавате
реда на пресмятане. Този израз например генерира сгойност нула.
10-2’5
но този - стойност 40
(10-2) *5
Всеки С израз може да съдържа променливи, константа или и
двете. Например ако приемом, че answer и count са променливи,
следващият израз е напълно валиден:
answer = count - 100;
На последно място, в изразите можете напълно свободно да из-
ползвате интервали.
38 С - Практически самоучител
ПРИМЕНИ
1. Както беше изтъкнато по-рано, операторы за остатък при
делене връща остатька от делочислено делсне. Например
остатъкы от 10 % 3 е равен на 1. Следващата програма
показва резултатитс от няколко целочислени деления и
техните остатъци:
((include <stdio.h>
int main(void)
{
printf("%d", 5/2);
printf (" %d", 5%2);
printf(" %d”, 4/2);
printf(" %d", 4%2);
return 0;
)
Програмата ще изобрази 212 0 па екрана на компютьра.
2. При дълги изрази употрсбата на скоби и интервали може
да добави яснота, въпреки че те може да не са задължи-
телни. Разгледайте този примерен израз:
count *num+83/val-19%ccunt
Следващият израз генерира същия резултат, но е доста по-
лесен за разчитане:
(count * num) + (98 / val) - (19 % count)
3. Следващата програма изчислява лицето на правоъгълник
при зададени размери. Първо, тя запитва потребителя за
дължината и широчината на правоъгьлника и след това
изобразява неговата площ:
((include <stdio.h>
int main(void)
{
int len, width;
printf("Enter length: ");
scanf("%d", &len);
printf("Enter width: ");
scanf("%d", fiwidth);
printf("Area is %d", len * width);
Основи на С 39
return 0;
}
4. Както беше споменато по-горе, операторът може да се
използва и като единичен оператор за промяпа на знака на
неговият операнд. За да видите как става това, опитайте
следващата програма:
#include <stdio.h>
int main(void)
{
int i;
i = 10;
i = - i;
printf("This is i: %d", i);
return 0;
}
1. Напишете програма, която да изчислява обема на куб. Не-
ка програма да изисква от потребителя да вьведе всеки
един от размерите.
2. Напишете програма, която да изчислява броя па секупдите
в една година
1.6
ДОБАВЯНЕ НА КОМЕНТАРИ В
ПРОГРАМАТА
Коментар е бележка за вас (или други програмисти), която
поставите във вашия соре код. Всички коментари се игнорират
от компилатора. Тс съществуват единствено за ваше удобство.
Коментарите се използват предимно за документарные на смисъ-
ла или целта на вашия соре код, така че по-късно да можете да се
подсетите как работа той и как да го използвате.
В С, началото на коментар се обозначава със знаковата двой-
ка “/*”, а края - с Това например е синтактично коректен
коментар:
40 С - Практически самоучител
/ж Това е коментар. */
Коментарите могат да се разпростират на няколко реда. Напри-
мер това е напълно валидно в С;
/’
Това е по-дълъг коментар,
който е разширен
на повече от
пет реда.
*/
В С, коментар може да се постави навсякъде, освен по средата на
някоя ключова дума, име на функция или променлива.
Можете да използвате коментар, за да прем ахнете временно
никой ред от кода. Просто обградсте този ред със символите за
коментар.
Въпреки че още не е дефинирано в ANSI С стандарта, можете
да използвате еще един стил на коментиране, наречен коменти-
ране на един ред. Този коментар започва с “//” и завършва в края
на реда. Коментарът на един ред беше създаден от C++. Неговата
у потреба в С е технически невалидна, но повечето компилатори
ще го приемат. Поради това много от програмистите запечнаха
да го използват в своите програми на С. След като коментарът на
един ред не е дефиниран от настоящий ANSI С стандарт, той ня-
ма да се използва в тази книга. Въпреки това не се учудвайте ако
го срещнете в комерсиални програми на С.
Едно носледно уточнение: в С не може да съществува комен-
тар в коментар. Това означава, че коментарите не могат да се
влагат. Например това няма да бъде възприето от С:
/* това е коментар /* това е друг коментар,
вложен в първия - което ще предизвика
синтактична грешка */ с вложен коментар
*/
ПРИМЕРИ
1. Една година на Юпитер (времето, необходимо на Юпитер
да направи пълна обиколка около Слънцето) има продъл-
жителност около 12 земни години. Следващата програма
дава възможност за конвертирапе на земни дни в юпитер-
ски години. Просто задайте броя на земните дни и тя ще
изчисли еквивалентния брой юпитерски години. Обьрнете
внимание на употребата на коментари в програмата.
Основы на С 41
/* Тази програма конвертира земни дни в юпитерски
години.*/
frinclude <stdio.h>
int main(void)
{
float e_days; /* брой земни дни */
float j years; /* еквивалентен брой юпитерски
години*/
/* получаване на брон на земните дни */
printf("Enter number of earth days: ");
scanf("%f", se days);
/* сега, изчисляване на юпитерските години */
j_years = е days / (365.0 * 12.0);
/* изобразяване на отговора */
printf("Equivalent Jovian years: %f", j_years);
return 0;
)
Обьрнеге внимание, че коментарите могат да са на един и
същ ред заедно с други програмни С конструкции.
Коментарите често се използват като помощно средст-
во при описанието на това, какво върши програмата. Въп-
реки че тази програма е лесна за разбиранс дори и без ко-
ментари, много от програмите са доста трудни за проумя-
ване дори и със свободната употреби на коментари. Под-
ходът при по-сложните програми е същия като използва-
ният тук: просто описвайте действията на програмата
Обърнетс също така внимание на коментара в началото на
програмата. Но принцип е добре да облените цента на
програмата пре ди започването на соре кода
2. Не можете да поставите коментари вътре в имената на
функции или променливи. Това например е грешна конст-
рукция:
pri/* грешно */ntf("this won't work"); .
1. Върнете се обратно и поставете коментари в програмите,
разрабогени в предишните секции.
42 С - Практически самоучител
2. Правилен ли е този коментар?
/**/
3. Правилен ли е този коментар?
/* printf("this is a test"); */
ПИСАНЕ НА ВАШИ-СОБСТВЕНИ
ФУНКЦИИ
Функциите са “градивните елементи” в езика С. До тук, създаде-
ните от вас програми включваха само една функция - main().
Повечето програми от реалния свят обаче, ще съдържат много
повече функции. В тази секция ще започнете да се учите как да
пишете програми, съдържащи множество функции.
Общата форма на С програма, състояща се от множество
функции, е показана тук:
/* включване на хедърните файлове 7
/* прототипи на функциите 7
int main(void)
{
/‘ ... 7
}
тип-на-резултата И (списък-с-параметри)
{
/* ... 7
}
тип-на-резултата f2(cnucbK-c-napaMempu)
{
/* ... 7
}
тип-на-резултата fN(cnucbK-c-napaMempu)
Г... 7
Основи на С 43
Разбира се, можете да именувате вашите функции по различен
начин. Тук тип-на-резултата определи типа на данните, връща-
ни от функцията. Ако дадена функция не врыца стойност, тогава
типът на нсйния резултат трябва да е void. Ако една функция не
използва парамет ри, нейният списък-с-параметрй трябва да се
състои от ключовата дума void.
Обърнете внимание на коментара за прототилите. Прототи-
път на функцията декларира функцията прсди тя да се използва
и прсди нсйната дефиниция. Прстотипът се състои от името на
функцията, типът на резултата и списъкът с параметрите й. Той
завършва с точка и запетая. Компилаторът трябва да знас тази
информация, за да може правилко да изпълни обръщенията към
функцията Нека например да е дадена следната проста функция:
void myfunc(void)
printf("This is a test.");
Нейният прототип e
void myfunc(void) ;
Единствената функция, която няма нужда оз прототип, е main( ),
тъй като тя е предварително дефинирана от езика С.
Прототипите са важна част от прогэамирането на С, но ще
трябва да научите повече за езика, преди да можете изцяло да
разберете тяхната цел и стойност. В следващите няколко глави
ще използваме прототипи без да даваме повече обяснепия. При
необходимост прототипите ще се дават във всички примории
програми от тази книга. Вие също трябва да ги включвате в
програмите, конто пишете. Пълното обяснение на прототипите е
дадено з глава 7.
Когато една функция се извика, изпъпнението на програмата
се прехвърля в нея Когато се достигне края на тази функция, из-
пълнението се връща на мястото, веднага след конструкцията за
извикване на функцията. Всяка функция от програмата може да
извика която и да е друга такава. Обикновено main() не се из-
виква от никоя друга функция, но няма техническо ограничение
това да се прави.
В следващите примери ще се научите да създавате най-прос-
тия вид функции в С: без връщане на стойност и без параметри.
Тук е показана схемата на една такава функция:
void FuncName(void) {
/к тялото на функцията е тук */
)
44 С - Практически самоучител
Разбира се, името на функцията може да е различно. Тъй като
функцията не връща стойност, типът на нейния резултат е void.
По аналогична причини, списъкът с параметры е сыцо void.
ПРИМЕРЫ
1. Следващата програма съдържа две функции: main() и
funcl(). Опитайте се да определите какво ще се отпечата
на екрана, преди да прочстете обясненияга по-долу.
/* Програма с две функции */
tfinclude <stdio.h>
void fund (void) ; /* прототип на fund ( ) х/
int main(void)
{
printf("I ");
fund ( ) ;
printf("C.");
return 0;
)
void fund (void)
{
printf("like ") ;
}
Тази програма изобразила на екрана I like С. Ето как рабо-
та тя. В main(), първото извикване на printf() отпечатва
I. След това се извиква funcl(). Това кара обрыцението
към printf() във funcl() да се изпълни и да се отпечата
like. Тъй като това е единствената конструкция във
funcl(), функцията завършва. Това предизвиква нродъл-
жаването на изпълнението в main( ) и на екрана се изобра-
зява С. Обърнете внимание, че конструкцията, извикваща
funcl(), завършва с точка и запетая. (Запомпете, че извик-
ването на функция е конструкция.)
Основой момент в осмислянето на това, как трябва да
пишете ваши-собствени функции, е че Когато се достигне
до затварящата фигурна скоба, функцията завършва и из-
пълнението продължава след точката, от която е извикана
функцията.
Обърнете внимание на прототипа на funcl(). Както
можете да забележите той се съсгои от нейното име, типа
на резултата и списъка с параметрите, ио няма тяло. Той
се терминира от точка и запетая.
Основи на С 45
X Тази програма отпечатва на екрана 12 3:
/* Тази програма има три функции. */
#include <stdio.h>
void fund (void) ; /* прототипи */
void func2(void);
int main(void)
{
func2( );
printf("3") ;
return 0;
void func2(void)
{
fund ( ) t
printf ("2 ');
void fund (void)
{
printf("1 ");
В тази програма, main( ) първо извиква func2( ), кояю от
своя страна, извиква funcl(). funcl() изобразява 1 и връ-
ща изпълнениего във func2(). Тя, от своя страна, отпечат-
ва 2 и завършва. Изпълнениего се врыца в main() и се от-
печатва 3.
1. Въведсте, компилирайте и изпълнете двете примерим
програми от тази секция.
2. Напишете програма, която съдържа попе две функции и
отпечатва съобщението The summer soldier, the sunshine
patriot.
3. Махнете прототипът от първага примерна програма и я
компилирайте. Какво се случва?
46 С- Практически самоучител
1.8
ИЗПОЛЗВАНЕ НА ФУНКЦИИ ЗА
ВРЪЩАНЕ НА СТОЙНОСТИ
В С, функииите могат да връщат стойност на извиквашите конс-
трукции. Например друга стандартна библиотечна функция в С е
sqrt( ), която връща квадратен корен от аргумента За да може
вашата програма да получи врыцаната стойност,, трябва да пос-
тавите функцията в дясната страна конструкция за присвояване.
Следващата програма например отпечатва квадратен корен от 10;
#include <sldio.h>
include <marh.h> /* необходим на sqrt ( ) */
int main(void)
double answer;
answer = sqrt(lO.O);
pri ntf (11 % f ", answer);
return 0;
}
Тази програма извиква sqrt() и присвоява врыцаната от нея
стойност на answer. Обърнете внимание, че sqrt() използва хе-
дьрния файл МАТН.Н.
ВсЫцност конструкцията за присвояване в предипшата прог-
рама не е технически необходима, защото sqrt() можете просто
да се използва като аргумент в printf(), както е показано гук:
#include <stdio.h>
^include <math.h> /* необходим на sqrt( ) +/
int main(void)
printfsort(10.0));
return 0;
)
Причииата тсва да работа е, че С автоматично ще извика sqrt()
и ще получи връщаната от нея стойност преди да извика printf().
След това връщаната стойност става втори артумент на printf().
Ако това ви изглежда странно, не се притеснявайте. С увелича-
ването на вашите знания по С ще разбирате тази ситуация все
по-добре
За свой аргумент функцията sqrt() изисква стойност с пла-
ваща запетая, а връщаната стойност е от тип double Типът на
стойността, връщава от функцията, трябва да съвпада с типа па
Осноеи на С 47
променливата, на която тя се присвоява. По-нататък ще видите
защо това е важно. Сьщо така е важно да предавате аргумента на
функцията от тип, който тя изисква.
Когато съставяте ваши-собствепи функции, връщането на
стойност в извикващата функция става чрез конструкцията
return. Тази конструкция има следната обща форма:
return стойност,
къдего стойност е стойностга, която ще се връща. Тази програ-
ма например отпечатва на екрана 10:
#include <stdio.h>
int func(vcid); /* прототип *7
int ma in(void)
{
int num;
num = func( ) ;
printf("%d", num);
return 0;
}
int func(void)
(
return 10;
J
В този пример func() връща целочислена стойност и типът на
връщапата променлива е зададен като int. Въпреки че можете да
създавате функции, връщащи всякакъв гип данни, тези, конто
връщат стойкости от тип int, са най-често срещаните. По-ната-
тьк в тази книга ще видите много примери на функции, връща-
щи данни от други типове. Функциите, декларирани като void,
не връщат стойкости.
Ако дадена функция не задзва изрично тип на връщапата
стойност, по подразбиране тя се приема за int Например func()
можете да изглежда така:
func(void)
{
return 10;
В този случай се подразбира int. Употребата на правилото за “int
по подразбиране” се среща дос га често в по-старите С кодовс. В
48 С - Практически самоучител
последно времс обаче ее забслязва отдръпване от използването
на подразбиращ се int. Не се знае дали това ще продьлжи, но за
да се избегнат недоразумения, тази книга винаги изричне задава
int.
Един важен момент: когато срещне конструкция return, фун-
кцията нсзабавно приключва. Никакви конструкции след това
няма да се изпълнят. Поради това конструкцията return дредиз-
виква приключването на функцията преди да се достигне затва-
рящата фигурна скоба.
Стойността, асоциирана с конструкцията return, не е задъл-
жително да бъде константа. Това може да всякакъв валиден из-
раз на С.
Освен това конструкцията return може да се използва и са-
мостоятелно - без стойност за ьръщане. Тази форма на return
изглежда така:
return ;
Най-често тази форма се използва от void функциите (те., от
функциите, типът на чиято връщана стойност е void), за да се
предизвика незабавиото прекратяване на функцията преди да е
дост Игната затварящата фигурна скоба. Въпреки че не е препс-
ръчително, тази форма на return може да се използва и във фун-
кции, конто трябва да врыцат стойности. Връшаната по този на-
чин стойност обаче не е дефинирана.
В една функция може да сыцествуват повече от една конст-
рукция return. По-късно в тази книга ще срещнете примеря за
това.
Въпреки че функциите връщат стойности, не е задължително
тези стойности да се присвояват на нещо. Ако връщаната стой-
ност не се използва, тя се губи, но без това да причиняьа някаква
вреда.
ПРИМЕРИ
1. 'Гази програма изписва квадрата на число, въведено от
клавиатурата. Квадратът се изчислява чрез функцията
get_sqr(). Нейният начин на работа би трябвало да е ясен.
ttinclude <stdio.h>
int get_sqr (void) ;
int main(void)
I
int sqr;
Основи на С 49
sqr = getsqr( );
printf("Square: %d", sqr);
return 0;
)
int get_sqr(void)
{
int num;
printf("Enter a number: ");
scanf("%d", &num) ;
return num*num; /* повдигане на числото на
квадрат ★/
}
2. Както беше споменато по-рано, можете да използвате
return без да задавате стойност. Това дава възможност на
функцията да приключи прсди да достигне до затварящата
фигурна скоба. В следващата програма например редът
This is never printed, никога няма да се отпечата.
finclude <stdio.b>
void fund (void) ;
int main(void)
{
fund ( ) ;
return 0;
}
void fund (void)
{
printf("This is printed.");
return; /* завършване без стойност */
printf("This is never printed.");
}
1. Въведете, комиилирайте и изпьлнете програмите от тази
секция.
2. Напишете програма, използваща функция на име
convert(), изискваща от потребителя да въведе количество
50 С - Практически самоучител
долари, и след това връща стойностга им ь английски ли-
ри. (Обменният курс да с $2,00 за лира.) Отпечатайте кон-
вертираната стойност.
3. Какво не е наред в тази програма?
^include <stdio.h>
int fl(void);
int main(void)
{
double answer;
answer = f 1 ( );
printf("%f", answer);
return 0;
}
int fl(void)
1
return 100;
)
4. Какво не e наред в тази функция?
void func(void)
{
int i;
printf("Enter a number: ");
scanf("%d", & i) ;
return i;
1.9
ИЗПОЛЗВАНЕ НА АРГУМЕНТИ HA
ФУНКЦИИ
Както бете споменато по-рано, аргументът на една функция е
стойностга, която й се предава при нейното извикване. Всяка
функция в С може да има от нула до няколко аргумента. (Гбрна-
та граница се определя от използвания компилатор, но ANSI С
стандаргът задава, че функциите трябва да могат да приемат ио-
не 31 аргумента.) За да може една функция да приема аргумента,
трябва да се дскларират специални променливи, конто да прие-
Осноеи на С 51
мат стойностите на аргументите. Те се наричат формалин пара-
метра на функцията. Параметрите се декларират между скобите,
следващи името й. Показаната по-долу функция например отпе-
чатва сумата на два целочислени аргумента, използвани за из-
викването й.
void sum(int х, int у)
{
printf("%d ", x + у);
}
При всяко извикване на sum() тя ще сумира стойността, преда-
дена на х, с тази, предадена на у, Трябва да запомните обаче, че х
и у са просто операционните промеиливи на функцията, конто
получават стойностите, подавани при извиквапето й. Разгледай-
те следната програма, илюстрпраща как се извиква sum().
/* Проста програма, демонстрираща sum ( ). */
Ainclude <stdio.h>
void sum(int x, int y) ;
int main(void)
{
sum(1, 20);
sum(9, 6) ;
sum(81, 9);
return 0;
void sum(int x, int y)
{
printf("%d ", x + y) ;
Тази програма ще отпечата на екрана 21, 15 и 90. Когато се из-
виква sum(), стойностга на всеки аргумент се копира в съответ-
ния параметьр. Тона означава, че при първото извикване на
sum(), в х се копира 1, а в у - 20. При следващото извикване в х
и у се копират съответно 9 и 6, а при гретого - 81 и 9.
Лко никога не сте работали с езици, поззоляващи параметри-
зирани функции, гореописаният процес може да ви изглежда
странен. Не се притеснявайгс - след като разгледате достатъчно
примери за програми на С, концепцията за параметрите и функ-
циите ще ви се изясни.
Важного е да осмислигс два термина. Първо, аргумент озна-
чава стойността, която се предава на функцията. Променливата
във функцията, получаваща тази стойност на аргумента, се на-
52 С - Практически самоучител
рича формален параметьр на функцията. Функции, приемащи
параметри, се наричат параметризирани функции. Занимаете, че
ако за аргумент на функция се използва променлива, последнага
няма нищо общо с формалния параметьр, получаващ нейната
стойност.
При функции!е в С, аргументите винаги се разделят със зале-
тай. В тази книга терминът списък с аргументы ще се използва за
аргумента, разделени със запетаи.
Всички параметри на функции се декларират по начин, подо-
бен на излолзвания в $иш( ). Трябва да зададете типа и името на
всеки параметьр и ако има повече от един параметьр, трябва да
използвате запетая, за да ги разделите. Функциите без параметри
трябва да използват ключовата дума void в своя списък с аргу-
мента.
ПРИМЕРИ 1 2
1. Даден аргумент на функция може да се състои от израз.
Например напълно валидно е да извикате sum( ), както е
показано тук:
sun (10-2, 9*7) ;
2. Тази програма използва функцията outchar( ), за да отпе-
чатва знакове на екрана. Програмата отпечатва АВС.
#inciude <stdio.h>
void outchar(char ch);
int main(void)
outchar('A');
outchar(’B’);
outchar(1C);
return 0;
void outchar(char ch)
printf ch);
Основи на С 53
1. Напишете програма, използваща функция на име
outnum( ). Нека тази функция да приема един целочислен
аргумент и да го изписва на екрана.
2. Какво не е наред в тази програма?
#include <stdio.h>
void sqr_it(int num);
int main(void)
(
sqr it (10.0);
return 0;
void sqr it(int num)
{
printf("%d", num * num);
1.10
КЛЮЧОВИТЕ ДУМИ в C
Преди да приключим тази глава, трябва да станете “доста близ-
ки” с ключовите думи, изграждащи езика С. ANSI С стандартът
дефинира 32 ключови думи, конто не могат да се използват за
имена на функции или променливи. Тези думи, в комбинация с
официалния сингаксис на С, формират езика за програмиране С.
Те са изброени в таблица 1-2.
Много компилатори на С са добавили различии други ключо-
ви думи, използвани за по-добра имплементация в с редат а. Ос-
вен тоза те така постигат поддръжка на многоезичпо програми-
рапе, црекъевания и организирапе на паметта. Някои често сре-
щани допълнителни ключови думи са дадени в таблица 1-3.
Важно е за ключовите думи да използвате малки букви. С
изисква всички ключови думи да са с малки букви. Например
RETURN няма да бъде разпозната като ключовата дума return.
Освен това никоя ключова дума не може да се използва като име
на променлива или функция.
54 С - Практически самоучител
autu double int struct
break else long switch
case enum register typedef
char extern return union
const float short unsigned
continue for signed void
default goto sizeof volatile
do if static while
Таблица 1-2
32-те ключови дума, дефинирани от ANSI С стандарта
asm _cs _ds _es
_ss cdecl far huge
interrupt near pascal
Таблица 1*3
Някои часто срещани разширени ключови дами на С
Проверка на уменията, представени в главата
I. Гравитацията на луната е около 17% от тази на Зсмята.
Напишете програма, която да дава възможност на потре-
бителя да въвежда своето тегло и след това да извежда
действителното му тегло на Луната.
2. Какво не е наред в този програмен фрагмент?
/* въьеждане на число
scanf("%d", Snum);
3. Обикновената чаша има обем от 8 унции. Напишете прог-
рама, която да конвертера унции в чаши. Използвайте
функция, наречена o_to_c( ), за да извършите конвертира-
нето. Извиквайте я с броя на унциите и нека тя да ви връ-
ща сьответния брой чаши.
4. Кои са петте основи типа данни в С?
Основи на С 55
5. Какво не е наред с всяко от тези имена на променливи?
a) short-fall
b) Sbalance
с) last + name
d) 9 times
2
Представяне на
УПРАВЛЯВАЩИТЕ
КОНСТРУКЦИИ в С
Разглеждани теми в главата
2.1 Запознаване с if
2.2 Добавяне на else
2.3 Създаване на блокове с код
2.4 Използване на цикъл for
2.S Замяна на операторите за
инкрементиране и
декрементиране в С
2.6 Разширяване на
чъзможностите на printf()
2.7 Програмиране с логически
оператори и оператори за
сравнение
58 С - Практически самоучител
В тази глава ще се запознаете с две от най-важните уп-
равляващи конструкции в С: if и for. Най-общо казано,
управляващите конструкции определят начина на из-
пълнение на вашата програма. По този начин те офор-
мят гръбнака на програмите. Ще научите също така и за блоко-
вете с програмен код, за операторите за сравнение и за логичес-
кие оператори, както и повече за функцията printf().
Проверка на знанията
Преди да продължите нататък, трябва да можете да отговори-
те вярно на следващите въпроси, както и да изпълните упражне-
нията:
1. Всички програми в С се състоят от една или повече функ-
ции. Какво е името на функцията, която трябва да присъс-
тва във всяка програма? Какви специални функции изпъл-
нява тя?
2. Функцията printf () се използва за изписване на информа-
ция на екрана. Напишете npot-рама, която изписва This is
the number 100. (Отпечатайте 100 като число, а не като
низ.)
3. Хедърните файлове съдържат информация, използвана от
стандартните библиотечни функции. По какъв начин на-
реждате на компилатора да включи даден хедърен файл в
програмата? Дайте пример.
4. С поддържа пет основни типа данни. Назовете ги.
5. Кои от изброените имена на промеиливи са невалидни в С?
a. count
b. 123count
с. $test
d. This_is_a_long_name
е. new-word
6. За какво се използва scanf()?
7. Напишете програма, която чете цяло число от клавиатура-
та и отпечатва на екрана неговия квадрат.
Представяне на управляващите конструкции ь С 59
8. Как се поставят коментари в програма на С? Дайте пример.
9. Как функциите връщат стойност на извикващите конст-
рукции?
10. Функция, наречена Myfunc (), притежава три параметъра:
един int, наречен count, един float, наречен balance, и
един char, наречен ch. Функцията не връша стойност. На-
пишете нейния прототип.
2.1
Запознаване с if
Конструкцията if е една от конструкциите за избор (наричани
понякога условны конструкции). Нейното действие се управляла
от резултата от да депо условие, който може да е true (вярно) или
false (грешно). Казано по-просго, конструкциите за избор вземат
решения на базата на някакво условие.
В своята най-проста форма, конструкцията if дава възмож-
ност на програмата да изпълни, при някакво условие, дадена
конструкция. Тази ферма на if е показпа тук:
(израз) (конструкция);
Изразът може да бъде който и да е валиден С израз. Ако той е
верен, тогава конструкция ще се изпълни, но а ко не е, тя се
прескача и се изпълнява иьрвата конструкция след if. В С, всеки
израз е верен, ако неговата стойност е различна от пула. Ако е
равен на пула, изразът е грешен. Конструкцията след if обикно-
вено се нарича цел на конструкцията if.
Най-често изразът в if сравнява една стойност с друга като
използва оператор за сравнение. Въпреки че ще се запознаете
по-подробно с всички оператори за сравнение по-късно в тази
глава, тук са дадени три от тях, за да имаме възможност да съз-
дадем някои примерни программ. Операторът за сравнение про-
верява какво е отношението на една стойност спрямо друга. За
да провери например дали една стойност е по-голяма от друга, С
използва оператора за сравнение “>” Резултатът от това сравне-
ние е или true, или false. Например 10 > 9 е true (вярно), но 9 > 10
е false (грешно). Следователно следвашата конструкцията if ще
изпише на екрана съобщението true
if(10 > 9) printf("true");
Тъй като обаче изразът в следващата конструкция е грешен
(false), if няма да изпълни своята целева конструкция.
if(5 > 9) printf("this will not print");
60 С - Пр эктически самоучител
С използва “<” като оператор за по-малко. Например 10 < 11
е вярно (true). За да провери за равенство, С предоставя операто-
ра = =. (Не може да има интервал между двата знака за равенст-
во.) По такъв начин 10 = 10 е вярно, но 10 == 11 не е.
Газбира се, изразът в И може да съдържа променливи. След
ната програма например може да определи дали едно цяло чис-
ло, въведено от клавиатурата, е отрицателно или не.
#include <stdio.h>
int raain(void)
{
int num;
printf("Enter an integer: ");
scant("?d", &num);
if(num < 0) printf("Number is negative.");
if(num > -1) printf("Number is non-negative.");
return 0;
}
Запомнете, че в С всяка стойност, различна от нула, се прие-
ма за true, а нулата - за false. Ето защо е напълно корсктно да
имате конструкция if като показаната тук:
if(ccunt+l) printf("Not Zero");
ПРИМЕРИ
1. Следвашата програма поставя основите на упражнение по
събиране. Тя показва на екрана две числа и изисква от
потребителя да въведе отговора. След това програмата
казва дали отговорът е верен, или не.
#include <stdio.h>
int main(void)
{
int answer;
printf("What is 10 + 14? ");
scanf("%d'’, &answer);
if (answer == 10 *-14) printf(“Right!");
return 0;
)
Представяне на управпяващите конструкции в С 61
2. Следващата програма преобразува футове в метри и об-
ратно, в зависимост от желанието на потребителя:
#include <stdio.h>
int main(void)
float num;
int choice;
printf("Enter value: ");
scanf &пшг.) ;
printf("1: Feet to Meters, 2:
printf("Enter choice: ");
scanf("%d", &choice);
Meters to Feet. ");
if(choice -= 1) printf("%f", num I 3.28);
if(choice == 2) printf("%f", num * 3.28);
return 0;
}
1. Кои от тези изрази са верни (true)?
а. 0
Ь. 1
с. 10*9 <90
d. 1 = = 1
е. -1
2. Напишете програма, която изисква от потребителя да въ-
веде цяло число, и след това определя дали то е четно, или
нечетно. (Съвет: използвайте оператора за остатък от де-
ление - %)
2.2
ДОБАВЯНЕ НА ELSE
Към всяка конструкция if можете да добавите else. В този случай
конструкцията if изглежда така:
62 С - Практически самоучител
if (израз) конструкция7;
else конструкция2;
Ако изразът е верен (true), ще се изпълни целта на if, а частта
след else ще се пропуске. Ако обаче изразът е грешен (false), то-
гава целта на if се пропуска, а тази на else се изпълнява. При ни-
какви обстоятелства не могат да бъдат изпълнени и двете конст-
рукции. По този начин прибавянето на else осигурява начин за
разклонение на програмата.
ПРИМЕРИ
1. В някои случаи можете да използвате else, за да създавате
по-ефективен код. Тук например конструкцията else е из-
ползвана вместо втория if в програмата от предходната сек-
ция, определяща дали дадено число е отрицателно, или не.
#include <stdio.h>
int main(void)
(
int num;
printf("Enter an integer: ");
scanf("%d", &num);
if(num < 0) printf("Number is negative.");
else printf("Number is non-negative.");
return 0;
Спомнете си, че оригиналната версия на програмата из-
рично проверяваше числата дали са неотрицателни като
сравняваше num с -1 чрез втората конструкция if. Тъй ка-
то обаче съществуват само две възможности - num е или
отрицателно, или не - няма нужда от втора проверка. По-
ради начина на генериране на кода от страна на С компи-
латора, конструкцията else изисква много по-малко ма-
шинни инструкции, отколкото допълнителен if. Това оп-
ределя по-високата й ефективност.
2. Тази програма изисква от потребителя да въведе две чис-
ла, след това разделя първото на второго и показва резул-
тата. Тъй като делението на пула не е дефинирано, прог-
рамата използва конструкции if и else, за да предотврати
такъв случай.
Представяне на управляващите конструкции в С 63
^include <stdio.h>
int main(void)
int numl, num2;
printf("Enter first number: ");
scant("id", snuml);
printf("Enter second number: ");
scanf("%d", &nu.r.2) ;
if(num2 == 0) printf("Cannot divide by zero.");
else printf("Answer is: %d.", numl / num2);
return 0;
}
1. Напишете програма, която изисква от потребителя да въ-
ведс две числа, и след гона изписва на екрана сумата или
произведението им, в зависимост от избора на потребителя.
2. Направете отново упражнение 2 от секция 2.1, като изпол-
звате конструкция else.
2.3
СЪЗДАВАНЕ НА БЛОКОВЕС КОД
В С можете да обедините две или лонече конструкции в едно.
Това се парича блок с код, или кодов блок. За да създадете блок с
код, конструкциите в него трябва да са заградени с отваряща и
затваряша фигурна скоба. След като това е направено веднъж,
конструкциите формират едно логическо цяло, което може да се
ползва навсякъде, вместо единична конструкция.
Например общага форма на конструкция if, при ползване на
блокове с код, е:
(((израз) {
конструкция*!;
конструкция2;
64 С - Практически самоучител
конструкция N;
}
else {
конструкция 1,
конструкция?-,
конструкция N-,
}
Ако изразът е верен, всички конструкции от блока, свързан с if,
ще се изпълнят. Ако изразът е грешен, тогава ще се изиълнят
всички конструкции от блока на else. (Пмайте предвид, че else не
е задължителна конструкция и се ползва по избор.). Следващият
фрагмент например оТисчатва съобщението This is an example
of code block, ако потребителя! въведе положително число
scant ("Ы", £пшп) ;
if(num >0) {
printf("This is ”) ;
printf("an example of ");
printf("a code block.");
He забравяйте, че блокът с код представлява едно неделимо ло-
гическо дяло. Това означава, че при никакви обстоятелства нито
едка от printf( ) конструкциите от този фрагмент не може да се
изкълни без това да стане и с останалите.
В показания пример, конструкциите в блока с код са отместе-
ни навътре. Въпреки че С не обръща внимание на това, къде на
реда се появява дадена конструкция, прието е тя да се нише мал-
ко по-навътре от началото на блока. Това оформление нрави
структурата на програмата по-лесна за разбиране. Местоположе-
нието на фигурните скоби сыцо е производно. Въпреки това на-
чинът, по който те са ноказани з примера, е общоприет и ще се
използва в представените в тази книга примери.
Както виждате, в С можете да използвате блокове с код на
мястото на всяка единична конструкция.
ПРИМЕРИ
1. Следващата програма е подобрена версия на програмата за
превръщане на футове в метри и обратно. Обърнете вни-
мание как използването на блокове с код дава възможност
на програмата специално да изисква всяка мерна единица.
Представяне на управляващите конструкции в С 65
^include <stdio.h>
int main(void)
{
float num;
int choice;
printf ("1: Feet to Meters, 2: Meters to Feet. ") ;
printf("Enter choice: ") ;
scanf("%d", ^choice);
if(choice == 1) {
printf ("Enter number of feet: ");
scanf("%f", Snum);
printf ("Meters: %f", num / 3.28);
)
else {
printf("Enter number of meters: ");
scanf("%f", &num) ;
printf("Feet: %f", num * 3.28);
)
return 0;
)
2. Чрез използването на блокове с код можем да подобрим
програмата за упражнение по събиране, така че тя да от-
печатва и верния отговор, когаго потребителят направи
грешка.
#include <stdio.h>
int main(void)
{
int answer;
printf("What is 10 + 14? ");
scanf("%d", &answer),
if(answer == 10+14) printf ("Right!");
else (
printf("Sorry, you're wrong. ");
printf ("The answer is 24.");
}
return 0;
Този пример илюстрира нещо много важно: не е задължи-
телно целите на конструкциите if и else да са одновремен-
но блокове с код. В дадения пример, целта на if е единична
конструкция, докато тази на else е блок. Запомнете, че мо-
жете да използвате единична конструкция или блок на
всяко от двете места.
66 С - Практически самоучител
1. Напишете програма, която събира или изважда две цели
числа. Първо, изискайте от потребителя да избере опера-
цията, а след това - да въведе числата. Накрая, изпишете
резултата на екрана.
2. Верен ли е следният фрагмент?
if (count < 100)
printf("Number is less than 100.");
printf("Its square is %d.", count * count);
2.4
Използване на цикъл for
Цикълът for e една от трите конструкции за цикъл в С. Той пре-
доставя възможност една или повече конструкции да се изпъл-
няват многократно. Ако сте програмирали на някой друг комгао-
търен език, като BASIC или Pascal, с удоволствие ще научите, че
for работи подобно на своите еквиваленти в другите езици.
Много програмисти на С считат цикъла for за най-гъвкав.
Въпреки че са възможни много варианта на цикъла for, в тази
секция ще разгледаме само негоВата най-обща форма.
Цикълът for се използва за повторение на дадена конструк-
ция или блок от конструкции определен брой пъти. Тук е пока-
зана основната форма за многократно изпълнение на единична
конструкция.
{ог(инициализация; проверка на условие; инкрементиране) кон-
струкция;
Секцията инициализация се използва за задаване на начална
стойност на променливата, използвана за управление на цикъла.
Тази променлива обикновено се нарича променлива за управле-
ние на цикъла. Секцията за инициализация се изпълнява само
веднъж - преди започването на цикъла. Частта от цикъла про-
верка на условие сравнява променливата за управление на цикъ-
ла със зададена стойност. Ако проверката на условието покаже
резултат true, цикълът се повтаря. Ако е false, цикълът се прекра-
тява и изпълнениего на програмата се прехвърля на реда след
цикъла. Проверката на условието се извършва в началото, или
преди, всяко повторение на цикъла. Частта за инкрементиране се
изпълнява на след цикъла. Това означава, че инкрементирането
Предстазяне на упрагляващите конструкции в С 67
се изпълнява след като конструкцията или блокът от конструк-
ции, формиращи тялото на цикъла, са били изпълнени. Пред-
назначение™ на инкрементирането е да увеличава (или намаля-
ва) променливата за управление на цикъла с определена стойност.
Като лесен първи пример, тази прсграма използва цикъл for,
за да изпише на екрана числата от 1 до 10
^include <stdio.h>
int main(void)
{
int num;
for (n’jm=l; num<ll; nuir=num-l) printf ("%d ", num);
printf("terminating");
return 0;
}
Програмата дава следния резултат:
1 2345678910 terminating
Програмата работа по следния начин’ първо, променливата за
управление на цикъл num се инициализира, така че да бъде рав-
на на 1. След това се проверява изразът num < 11. Тъй като той е
true, се стартира цикъльт for. След като сё изпише съответното
число, num се инкрементира и условието за проверка се прове-
рява отново. Този процес продължава, докато num стане равно
на 11. Тогава цикъльт for спира и на екрана се изписва terminating
Запомнете, че частта за инициализация на цикъл for се изпълня-
ва само веднъж - при първоначалното стартиране на цикъла.
Както беше казано по-ранс, нроверката на условието се из-
върптва в началото на всяка итерация. Това означава, че ако със
започването на цикъла проверката даде резултат false, цикълът
няма да се изпълни нито веднъж. Следващата програма напри-
мер изписва на екрана само terminating, защотс променливата
num е инициализирана със стойност 11, ксето прави условието
за проверка ipeiimo.
#include <stdio.h>
int main(void)
(
int num;
/* този цикъл няма ла се изпълни */
for(num=ll; num<ll; num=num+l) printf ("%d ", num);
68 С - Практически самоучител
printf("terminating") ;
return 0;
}
За да повторите няколко конструкции, използвайте блок с код
като цел на цикъла for. Например тази програма изчислява сума-
та и произведението на числата от 1 до 5:
((include <stdio.h>
int main(void)
int num, sum, prod;
sum = 0;
prod = 1;
for(num=l; num<6; num=num+l) {
sum = sum + num;
prod = prod * num;
)
printf("product and sum: %d %d", prod, sum);
return 0;
}
Цикълът for може да се изпълнява и низходящо. Например
този фрагмент декрементира променливата за управление на
цикъла.
for(num=20; num>0; num=num-l) . . .
Освен това променливата за управление на цикъла може да бъде
декрементира или инкрементира с повече от единица. Следваща-
та програма например брои до 100 през пет:
((include <stdio.h>
int main(void)
{
int i;
for(i=0; i<101; i=i+5) printf("%d ", i);
return 0;
Представяне на управляващите конструкции в С 69
ПРИМЕРИ
1. Програмата за упражнение по смятане може да бъде по-
добрела посредством цикъл for. Версията, показана тук,
пита за сумата на числата от 1 до 10. Става въпрос за су-
мите на 1+1, 2+2 и т.н. Тази програма може да бъде полез-
на за първокласник, конто се учи да събира.
((include <stdio.h>
int main(void)
int answer, count;
for(count=l; count<ll; count=count+l) {
printf("What is %d + %d? ", count, count);
scanf("%d", Sanswer);
if(answer == count+count) printf("Right 1 ");
else {
. printf("Sorry, you’re wrong. ");
printf("The answer is %d. ", count+count);
}
return 0;
)
Забележете, че в тази програма, конструкцията if е част от
блок на for. Освен това целта на else е блок с код. Това е
напълно коректно. В С, блок с код може да съдържа конс-
трукции, конто създават други блокове. Забележете също
така как отместването прави структурата на програмата
по-ясна.
2. Можем да използваме цикъл for за създаването на програ-
ма, която определя дали дадено число е просто. Следваща-
та програма изисква от потребителя да въведе число и
след това проверява дали то има делители.
/* Проверка за просто число. */
((.include <stdio.h>
int main(void)
int num, i, is_prime;
printf("Enter the number to test: ");
scanf("%d", &num) ;
/* сега проверката за делители */
is_prime = 1;
70 С - Практически самоучител
for(i=2; i<=num/2; i=i+l)
if((num%i)==0) is_prime = 0;
if(is_prime==l) printf("The number is prime.");
else printf("The number is not prime.");
return 0;
1. Създайте програма, която изписва на екрана числата от 1
до 100.
2. Напишете програма, която изписва на екрана онези числа
между 17 и 100, конто могат да се делят точно на 17.
3. Напишете програма, подобна на тази за проверката за
просто число, но така че да изписва и всички делители на
числото, което потребителят е въвел. Например ако потре-
бителят е въвел 8, програмата да покаже 2 и 4.
2.5
Замяна на операторите за
ИНКРЕМЕНТИРАНЕИ
ДЕКРЕМЕНТИРАНЕВ С
Когато в предишната секция научихте за цикъла for, частта за
инкрементиране приличаше, повече или по-малко, на показаната
тук:
for(num=0; num<some_value; num=num+l) . . .
Въпреки че не е грешно, в професионално написаните програми
на С почти никога няма да срещнете конструкция като
num = num + 1. Това е така, защото С осигурява специален опе-
ратор, който увеличава променливата с единица. Операторът за
инкрементиране е ++ (два плюса, без интервали помежду им).
Чрез използването оператора за инкрементиране, можете да
промените реда:
i = i + 1;
Представяне на управляващите конструкции в С 71
по следния начин’
i++;
Ето зато, даденият по рано цикъл for, обикновено ще бъде на-
писан по следния начин:
for(num=0; rum<some value; num++) . . .
Аналогично, за намаляването на дадснй променлива с едини-
ца можете да използвате оператора за декрементиране\ —. (Не
трябва да има интервали между двата минуса.) От това следва,
че изразът
count = count - 1;
може да бъде написан като:
count--;
Освен че това ви снестява нисане, още една причина за из-
ползването на операторите за инкрементиране и декрементиране
е, че при повечето С компилатори, те ще се изпълнят по-бързо,
отколкото еквивалентните им изрази с равенство. Причината за
тази разлика е, че компилатсрът на С често избягза отделни Ma-
in инни инструкции за зареждане и съхранение, и в изпълнимата
версия на програмата ги заменя с единична инструкция за инк-
рементиране или декрементиране.
Нс е задължително операторите за инкрементиране и декре-
мектиране да бъдат след променливата; те могат също така и да
я предхождат. Въпреки че ефектът върху променливата е един и
същ, позицияга на оператора оказва влияние върху извършване-
то на операцията. За да разберете как става това, разгледайте
следната програма:
#include <stdio.h>
int main(void)
int i, j ;
i - 10;
j = i++;
/* това ще отпечата li 10 */
printf("i and j: %d %d", i, j);
return 0;
72 С - Практически самоучител
Не се притеснявайте от конструкцията j = i+ +. Операторът за
инкрементиране може да е част от всеки валиден С израз. Конст-
рукцията работи но следния начин. Първо, на j се нрисвоява те-
кущата стойност на 1. След това i се инкрементира. Его защо
стойностга на j е 10, а не 11. Когато операторът за инкременти-
ране или декрементиране, е поставен след променливата. тогава
съответната операция се извършва след като стойностга на про-
менливата вече е извлечена, зада се използва в израза. Поради
това ако приемем, че max има стойност 1, следният израз:
count = 10 * max++;
присвсява на count стойност 10 и увеличава max с единица,
Ако променливата е предшествана от оператор за инкремен-
тиране или декрементиране, тогава нърво се изпълнява съответ-
ната операция и след това се извлича стойностга на променлива-
та за използване в израза. Например ако променим предишната
програма по следния начин, j ще има стойност 11.
#include <stdio.h>
int main(void)
int i, j;
i = 10;
j = ++i;
/* тона ще отпечата 11 11 */
printf ("i and j: %d %dM, i, j);
return 0;
Ако просто използвате операторите за инкрементиране или
декрементиране, за да замените еквивалеятните конструкции за
присвояване, няма никакво значение дали той е преди или след
променливата. Това е втпрос на ваш личен стил.
ПРИМЕРИ
1. Тук е дадена програмата за упражнение по сьбиране, раз-
работена в секция 2. Тя е пренаписана чрез оператора за
инкрементиране.
#include <stdio.h>
int main(void)
{
Предстаеяна на управляв ащите конструкции в С 73
int answer, count;
for(ccunt=l; count<ll; count++) {
printf("What is %d + %d? ", count, count);
scanf("%d", ianswer);
if (answer =- count+count) printf("Right! ");
else {
printf("Sorry, you're wrong. ");
printf ("The answer is %d. ", count (-count) ;
return 0;
2. Следвашата програма илюстрира употребата на операто-
рите за инкрементиране и декрементиране:
^include <scdio.h>
int main(void)
int i;
i = 0;
i++;
printf("%d ", i); /* отпечатза 1 */
i—;
printf("%d ", i); /* отпечатва 0 */
return 0;
1. Напишете отново решенията на зададите за цикьл for от
предкшната секция, така че да използват оператори за ин-
крементиране и декрементиране.
2. В дадената програма заменете съответните конструкции за
присвояване с оператори за инкрементиране или декре-
ментиране.
#include <stdio.h>
int main(void)
74 С - Практически самоучител
int а, Ь;
а = 1 ;
а = а + 1;
Ь = а;
b = b - 1;
printf("%d %d", a, b);
return 0;
2.6
Разширяване на възможностите
НА PRINTF( )
Доссга използвахме printf(), за да отпечатваме на екрана числа
и низове. Може би обаче сте се питали как бихте могли да ука-
жете на printf(), че искатс изобразяването да продължи на след-
ващия ред. Начияът да направите това, а и много други нетца, е
като използвате С константата обратно наклонена черта. Езикът
С дефинира няколко специални знакови кода, показани в табли-
ца 2.1 и представляваши специални символы, конто не могат да
бъдат въведени ст клавиатурата, не могат да се отпечатат, може
да не са част от всички кодови таблици или просто служат за
други нужди Можете да ползвате кодовете с обратно наклонена
черта навсякъде, където използвате стандартните знакове. Зна-
ке вите константа с обратно наклонена черта се наричат също та-
ка escape последователности.
Код Значение
\ь Backspace
\f нова страница
\п нов ред
\г връщане на каретката
\t хоризонтална табулация
V кэвички
V апостроф
Предстзвлне на управля защите конструкции в С 75
Код Значение
\0 Null
\\ обратно наклонена черта
\v вертикална табулация
\а звуков сигнал
\? въпросителек знак
\N осмична константа (където N е осмична стойност)
\xN шестнайсетична константа (където N е шестнайсетичнэ стойност)
Таблица 2-1
Кодовете с сбоатна наклонена черта е С
Вероятно най-важният код с обрано наклонена черта е “\п”.
Този код най-често се нарича знак за нов ред. Когато компилато-
рът на С срещне “\п” той го преобразува в комбинация “врыцаяе
на каретката/преминаване на следващ ред” Например следната
програма :
ttinclude <stdio.h>
int. ma in (void)
{
printf("This
printf("This
printf("This
is line one.'xn");
is line two.\n");
is line three.");
return 0;
}
изписва на екрана:
This is line one.
This is line two.
This is line three.
Запомнете, че кодовете с обратно наклонена черта са знакови
константа. Поради това, за да присвоите такъв код на знакова
променлива, трябва да го оградите с апострофи, както е показано
в следния фрагмент:
char ch;
ch = '\t'; /*присвоява на ch знака за табулация */
76 С - Практически самоучител
ПРИМЕРИ
1. Тази програма произвежда звук от говорителя:
ttinclude <stdio.h>
int main(void)
{
printf("\a");
return 0;
2. Всеки специален знак може да се въведе чрез осмична или
шестнайсетична стойност, предшествана от обратно нак-
лонена черта. Осмичната система е с основа 8 и използва
цифрите от 0 до 7. В тази система числото 10 отговаря на
числото 8 в десетичната бройна система. Шестнайсетич-
ната бройна система използва за основа 16 и цифрите от 0
до 9 плюс буквите от ‘А’ до ‘F*, конто отговарят на числа-
та 10,11,12,13,14 и 15. Например числото 10 в шестнайсе-
тична бройна система отговаря на 16 в десетична. Когато
задавате код в шестнайсетична бройна система, трябва да
поставите ‘х’ след обратно наклонената черта и след това
числото. Кодовата таблица ASCII е дефинирана от 0 до
127. Много компютри обаче, включително и повечето PC,
използват стойностите от 128 до 255 за специални и гра-
фични знакове. Ако вашият компютьр поддържа тези до-
пълнителни знакове, следващата програма ще изпише ня-
кои от тях на екрана.
ttinclude <stdio.h>
int main(void)
printf("\xA0 \xAl \xA2 \xA3");
return 0;
}
3. Нее необходимо знакът за нов ред, “\п”, да се намира в
края на отпечатвания с printf( ) низ; той може да се нами-
ра навсякъде в низа. Освен това в един низ можете да пос-
тавите толкова знакове за нов ред, колкою желаете. Важ-
ною е, че няма връзка между знак за нов ред и край на ни-
за. Например тази програма:
#include <stdio.h>
int main(void)
{
Представяне на управляващите конструкции в С 77
printf("or.e\ntwo\nrhree\nfour");
return 0;
отпечатва на екрана:
ипе
two
three
four
1. Напишете програма, която отпечатва таблица с числа.
Всеки ред от таблицата трябва да съдържа три части: чис-
ло, неговият квадрат и неговата трста степей. Започнете с
1 и завършете с 10. Използвайте цикъл for, за да генерира-
те числата.
2. Напишете програма, която изисква от потребителя да въ-
веде цяло число, а след това използва цикъл for, за да изб-
рои и отпечата на отделен ред числата от въведепата стой-
ност до 0. Когато достигне нуда га, века програмата да из-
дава звук от юворителя.
3. Направете няколко самостоятелни опита с кодовете с об-
ратно наклонена черта.
2.7
Програмиране с логически
ОПЕРАТОРИ И ОПЕРАТОРИ ЗА
СРАВНЕНИЕ
Езикът С притежава богато множество от оператори. В тази сек-
ция ще научите повече за логическите оператори и тези за срав-
нение в С. Както впдяхте по-рано, операторите за сравнение
сравняват две стойности и връщат резултат true или false, в зави-
симост от ноказаното при проверката. Логическите оператори
свързват true и false резултати. Всички тези оператори са дадени
в таблтши 2-2 и 2-3.
78 С - Практически самоучител
Оператор Действие
> по-голямо от
>= по-голямо от или равно на по-малко от
<= по-малко от или равно на
= = равно на
1= различно от
Таблица 2-2
Оператори за сравнение
Логическите оператори се използват за поддръжка на основ-
ните логически операции AND, OR и NOT, базирайки се на
следната таблица на истинност. Таблицата използва 1 за true и О
за false:
Р q p&&q pllq !р
0 0 0 0 1
0 1 0 i 1
1 1 1 i 0
1 0 0 i 0
Както логическите оператори, така и тези за сравнение имат
по-нисък приоритет от аритметичните оператори. Това означава,
че израз от вида:
10 + count > а + 12
се изпълнява все едно, че е написан по следния начин
(10 + count) > (а + 12)
Оператор Действие
&& AND (И)
11 I OR (ИЛИ) NOT (инвертиране)
Таблица 2-3
Логически оператори
Представяне на управляващите конструкции н С 79
Чрез логически оператори можете да свързвате произволен б рой
оператори за сравнение в едно. Следващият пример обединява
три оператора за сравнение.
var > max I | ! (ггах==1С0) && 0 <= item
Таблипата по-долу показва относителните приоритета на логи-
ческите оператори и тези за сравнение.
Най висок приоритет
Най-нисък приоритет
= = j=
II
Има един важен факт, касаещ резултата на логически опера-
тори и операторите за сравнение, който трябва да запомните’ ре-
зултатьт винаги е или 0, или 1. Въпреки че С дефинира като true
всяка стойност, различна от нула, тези оператори винаги дават 1
като стойност за true. Вашите програми могат да се възползват
ст това.
Можете да използвате логически оператори и оператори за
сравнение, както в if, така и във for конструкции. Например
следната конструкция дава информация кита а и b са одновре-
менно положителни:
if(a>0 && b>0) printf("Both are positive.");
ПРИМЕРИ
1. В професионално написаните програми на С няма да
срещнете конструкция от вида:
if (count 1= 0) . . .
Причина та за това е, че в С true е всяка стойност, различна
от нуля, a false е пула. Ето затцо преходната конструкция
обикновено се пише по следния начин:
if(count) . . .
Освен това конструкции като тази :
if(count == 0) . .
80 С - Практически самоучител
обикновено се пишат така:
if(!count) . . .
Изразът icount връща стойност true само ако count е нула.
2. Важно е да запомните, че резултатът о г всяка операция за
сравнение или логическа операция е 0 при false и 1 при
true. Следващата програма например изисква въвеждането
на две цели числа и след това изписва рсзултата от всички
приложени върху тлх операции за сравнение и логически
операции. Във всички случаи резултатът е или 0, или 1.
#include <stdio.h>
int main(void)
int i, j;
printf("Enter first number:
scanf("%d", &i);
printf ("Enter second numoer: ");
scanf ("%d", &j);
/* операции за сравнение */
printf("i < j %d\n", i < j);
printf( "i <= j %d\n", i <=
printf( "i == j %d\n", i == j);
printf( "i > з %d\n", i > j) t
printf( "i >= j %d\n", i >= j);
/* логически операции */
printf ("i && j %d\n", i a& j);
printf ("i || j %d\n", i || j);
printf ("!i !j fcd %d\n", !i, ! j);
return 0;
}
3. С не дефинира логическата оператор “изключващо ИЛИ”
(exclusive OR - XOR). Въпреки това, не е трудно да се
създаде функция, която да изпълнява тази операция. Опе-
рацията XOR използва следната таблица на истинност:
P q XOR
0 0 0
0 1 1
1 0 1
1 1 0
Представяне на управляващите конструкции в С 81
Това означава, че операция! a XOR връща резултат true,
тогава и само тогава, когато само едипият от опсрандите е
true. Следващата функция използва операторше &&. и ||,
за да конструира операция XOR. Тя сравняла сюйностите
на двата си аргумента и воъща резултата на операция
XOR.
int xor(int a, int ь)
return (а || Ь) && !(а && Ь);
Следващата преграма използва тази функция. Тя изписва
резултата от операции! е AND ,OR, XOR, приложени вър-
ху въведените от вас стойкости:
/* Тази програма демонстрира функпията хэг ( ). */
((include <stdio.h>
int xor(int a, int b) ;
int main(void)
{
int p, q;
printf("enter P (0 or 1): ");
scant (' %a", &p) ;
printf("enter Q (0 or 1): ");
scant('%d", &q);
printf ("P AND Q: %d\n", p q) ;
printf("P OR Q: %d\n", p I I q);
printf("P XOR Q: %d\n”, xor(p, q));
return 0;
int xor(int a, int b)
return (a I I b) && ! (a && b);
1. Какво прави следиият цикъл?
for(x=0; xClCO; x++) printf("%d ", x);
2. Връща ли този израз стойност true?
82 С - Практически самсучитеп
! (10=9)
3. Дават ли един и сын резултат следните два израза?
а. О && I || 1
Ь. 0&&(1||1)
4. Изпробвайте самостоятелно операторите за сравнение и
логическите оператори.
Проверка науменията, представени в главата
1. Напишете програма, с която в компштъризиран вид да се
играе играта “познай магическотс число'’. Правилата са
следните; играчът има 10 опита, за да отгатнс магическотс
число. Ако въведеното число е избраното магическо чис-
ло, иска програмата изписва съобщението “RIGHT!” и
след това да се затваря. В противен случай, програмата да
дава информация дали въведената стойност е по-голяма
или по-малка от магического число и след това да дава
възможност на играча да направи нов опит. Този процес
трябва да продължи, докато играчът отгатне числото или
изчерпи всичките десет вьзможни опита. За разнообразие,
можете да направите програмата така, че да сьобщава
колко опита са били нужни за отгатване на числото.
2. Напишете програма, коя го изчислява квадрату раза на къ-
ща, при дадени размери на всички стаи. Създайте програ-
мата т ака, че да пита потребителя за броя на стайте в къ-
щата и след това да изисква въвеждането на размерите на
всяка една от тях. Накрая да изписва на екрана общэта
квадратура.
3. Кои са операторите за инкрементиране и декрементиране
и какво правят те?
4. Създайте подобрена версия на програмата за упражнение
по събиране, която да помни броят на верните и грешните
отговори и да ги изписва, когато приключва.
5. Напишете програма, кояго да отпечагва числата от 1 до
100, като използва 5 колони. Нека всяко число да е разде-
лено от предишното с табуляция.
3
ОЩЕ УПРАВЛЯВАЩИ
КОНСТРУКЦИИ В С
Разглеждани теми в главата
3.1 Чечене на знакове от
клавиатуоата
3.2 Влагане на конструкции if
3.3 Вариациите на цикъла for
3.4 Цикъльт while на С
3.5 Използване на цикъла do
3.6 Създаване на вложени цикли
3.7 Използване на break за
излизане от цикъл
3.8 Използване на конструкцията
continue
3.9 Избор между алтернативи
чрез конструкцията switch
3.10 Конструкцията goto
84 С - Практически самоучител
Този раздел продьлжава дискусията за управлявашите
конструкции в С. Преди това обаче ше започнем с обяс-
нение на това, как се четат знакове от клавиатурата.
Въпреки че вече знаете как да получавате числа от кла-
виатурата, време е да разберете и как да четете отдел ни знакове,
защото няколко примера в тази глава ще се възползват от това. В
този раздел, също така, завършваме и с обсъждането на конст-
рукциите if и for. Представени са и две други конструкции за
цикли в С - while и do. По нататьк тази глава ще представи вло-
жените цикли, както две други управляващи конструкции в С -
break и continue. Накрая, главата разглежда конструкцията за
селекция switch и завършва с конструкцията за безусловен пре-
ход goto.
Проверка на знанията
Преди да продължим нататък, трябва да можете да отговори-
те на слединге въпроси, както и да изпълните упражнениям:
1. Кои са операторите за сравнение и логическите оператори
в С?
2. Какво е блок с код и как можете.да съставите такъв?
3. Как можете да изпишете нов ред посредством printf()?
4. Напишете програма, която да отпечатва числата от -100 до
100
5. Напишете програма, която да отпечатва 5 различии посло-
вици. Нека програмата изисква от потребителя номера на
послозицата за отпечатване и след това да я изписва на
екрана. (Можете да използвате каквито искате пословици.)
6. Как може да бъде пренаписана следната конструкция?
count = count + 1;
7 Кои стойности в С се считат за true и кои - за false?
Още управляващи конструкции в С 85
3.1
Четене на знакове от
КЛАВИАТУРАТА
Вьиреки че числата са важни, программе ви трябва да могат да
четат и знакове от клавиатурата. В С можете да правите това по
различии начини. За съжаление, тази принципно проста задача
се усложнява от някои остатьци от “корениге” на С. Нека все пак
да започнем с градиционния начин на четене на знакове от кла-
ьиатурата, а по-късно ще научите и алтернативните.
С дефинира функция, наречена getchar(), която връща еди-
ничен знак, избран от клавиатурата. Когато бъде извикана, фун-
кцията чака да бъде натиснат някакъв клавиш, ("лед това
getchar() отразява избрания клавиш на екрана и връща стой-
ността му на извикващата конструкция. Функцията getchar() е
дефинирана от стандарта ANSI С и изисква хсдърния файл
STDIO.H. Следващата програма'илюстрира нейната упстреба ка-
то прочита знак от клавиатурата и след това показва какво е по-
лучила. (Помнете, че за да отпечатаю на екрана даден знак,
трябва да използвате форматния спецификатор %с на printf( ).)
#include <stdio.h>
int main(void)
{
char ch;
ch = getchar(); /* четене на знак */
printf(" you typed: %c", ch);
return 0;
Ако изпробвате тази програма, нейното поведение може да е
по-различно от това, което сте очаквали. Проблемы се състои в
това, че в много С компилатори getchar( ) се имплементира по
такъв начин, че буферира линейно входа. Това означава, че тя не
даъа отговор веднага след натискането на някои клавиш, а чака
да се въведе цял ред, който може да включва още знакове. Каза-
но с други думи, въпреки че getcbar() ще прочете и върне само
един знак, тя ще чака да въведете знак за връщане на каретката
(например да натиснете ENTER), преди ла го направи. Когато
getchar() дава отговор, тя връща първия знак, който сте въвели.
Въпреки това всички останали знакове, конто сте набрали,
включително и този за връщане на каретката, ще останат във
входния буфер Те ще бъдат използвани от следващите входни
заявки, като например извиквания на scanf( ). При определени
условия това може да предизвика неприятности. Тази ситуация
86 С - Практически самоучител
се изучава по-подробно в глава 8. За сега с достатъчно да знаете,
че getchar() може да се държи по-начин, различен от този, който
ви подсказва ингуицията. Разбира се, програмите, демонстрира-
ни в тази книга, работяг правидно.
Причинага getchar() да работи по гореописания начин е, че
версията на UNIX, за която е разработен С, буферира линейно
входа. Когато бяха разработени С компилатори и за други инте-
рактивны среди, разработчиците трябваше да решат какво да бъ-
де поведението на getchar(). Много от тях решила да запазят
линейного й буфериране в интерес на съ вместимости, въпреки
че за това няма технически обосновали причини. (Всъщност
според стандарта ANSI С, getchar() не трябва да е линейно бу-
ферирана.) Когато getchar() се имплементира с линейно буфе-
риране в едка модерна интерактивна среда, нейнага упо среба се
ограничава зиачително
Тъй като много компилатори използват линейно буферирапи
версии на getcharf), повсчсто от тях предоставят друга функция
за изпълнение на интерактивен конзолен вход. Въпреки че тя не
е определена от стандарта ANSI С, повечето компилатори нары-
чат тази функция getche(). Можете да използвате getche() по
същил начин като getchar(), но трябва да имате прсдвид, че тя
щс връша стойността веднага след като сте натиснали нхкакъв
клавиш. Освен това тя не буферира линейно входа. При повечето
компилатори тази функция изисква хедърен файл, наречен
CON1O.H, по при вас той може да е различен. От това следва, че
ако искаге да постынете интерактивно вьвеждане на знак, ше
трябва да използвате по-скоро функцията getche(), отколкото
getchar().
Тъй като всички читатели ше имат достъп до функцията
getchar( ), тя ше се използвана в повечето от примерите в тази
книга, изискващи въвеждакето па знакове. Пякои примери обаче
ще използват getcbe(). Ако вашият компилятор не съдържа тази
функция, заменете я с getchar() Имате пълната свобода сами да
експерименгираге с getche().
Ако използвате компипатора Visual С +г на Microsoft, трябва да
знаете, че по времо на създаването на тази книга, функцията
getchef) не беше съвместима със стандартните С функции за
вход, като напоимер scanff). Вместо това трябва да използ-
вате специални конзолни версии на тези функции, като напри-
мер cscanf(). Тези и други нестандартно I/O функции са описа-
ли в глава 8. Примерите в тази книга, използващи getche(),
работят коректно с Visual C++, защото при тях е избягната
употребата на стандартно входни функции.
Още управляващи конструкции в С 81
На практика всички компютри използват ASCII кодове за
представяне на знаковете. Следовате.тно знаковстс, върнати от
getchar( ) и getche(), ще бъдат представени от своите ASCII ко-
довс. Това е много полезно, защото ASCII кодовете представля-
ват подредени послсдователности, в конто кодът на всяка буква
е с единица по-голям от тсзи на нредходнага; същото вяжи и за
кодовете на цифрите. Това означава, че ‘а’ е по-малко от ‘в’, ‘2’
е по-малко от ‘3* и т.н. Можете да сравнявате знакове така, какгс
сравнявате числа. Например:
ch = getcharO;
if(ch < ' f') printf("character is less than f") ;
e напълно валиден фрагмент, който ще изпище на екрана съот-
ветното съобшение, ако потребителят въведе който и да е знак,
на мира щ се преди f.
ПРИМЕРИ
1. Следващага програма прочита знак и изписва на екрана
неговия ASCII код. Тук е илюстрирана една много важна
възможност на С: можете да използвате char като short
int. Програмата демонстрира и употребата на функцията
getche().
♦include <conio.h>
♦include <stdio.h>
int main(void)
{
char ch;
printf("Enter a character: ");
ch = qetche();
printf("\nlts ASCII code is %d", ch);
return 0;
Тъй като тази програма използва getche(), тя се задейства
веднага, щом натиснете някакъв клавиш. Преди да про
дължите нататьк, заменете getche( ) с getchar( ) и вижте
какъв ще е резултатът. Както сами ще установите,
getchar() не връща знак на програма, докато не натиснете
ENTER
2. Една от най-често срсщаните употреби на четенето на зна
кове от клавиатурата е за избор от меню. Например след-
88 С - Практически самоучител
ващата програма дава ьъзможност иа потребителя да съ-
бира, вади, умножава и дели две числа.
([include <stdic.h>
int main(void)
{
int a, b;
char ch;
printf("Do you want to:\n");
printf("Add, Subtract, Multiply, or Divide?\n") ;
printf("Enter first letter: ");
ch = getchar();
printf ("\n");
printf("Enter first number: ");
scanf("%d", &a);
printf("Enter second number: ");
scanf ( "%ct", &b) ;
if(ch=='A') printf("%d”, a+b) ;
if(ch==’S') printf("%d", a-b);
if (ch=-='M.’) printf ('4d", a*b) ;
if(ch=='D’ && b!=0) printf("%d", a/b);
return 0;
}
Това, което трябва да имате предвид, е, че С прави разлика
между главните и малките букви. Затона ако но гребителят
въведе s, програмата няма да го разпознае като заявка за
изваждане. (По-късно ще се научите и как да променяте
регистъра на буквите.)
3. Друга причина, поради която програмите ви ще трябва да
четат знакове от клавиатурата, е за получаване на потвър-
ждение или отказ от истребителя. Например този фраг-
мент показва дали потребитслят желае да продължи ната-
тьк, или нс:
printf("Do you wish to continue? (Y/N : ");
ch = getche();
if(ch==’Y') {
/* продължаванс на нещо */
Още управляващи конструкции в С 89
1. Напишете програма, която прочита десет букви. След като
буквите се прочетат, на екрана да се отпечатва първата по
азбучен ред. (Съвет: буквата с май-малка стойност е първа.)
2. Напишете програма, която изобразява на екрана ASCII ко-
довете на знаковете от ’А’ до 'Z' и от 'а' до 'z' Как се разли-
чават кодовете на главните и .малките букви?
3.2
ВЛАГАНЕ НА КОНСТРУКЦИИ IF
Ако една конструкцията if е цел на друг if или else, тогава тя се
нарича вложена във въпшния if. Его един пример за вложен if:
if(count > max) /* выдоен if */
if(error) printf("Error, try again."); /* вложен if */
Тук конструкцията printf() ще cc изпълни само ако count е по-
голямо от шах и error не е равно на пула. Обърнете внимание на
начина, по който if е отместен по-навътрс. Това е общоприета
практика. Тя дава възможност на всеки, който чете програмата,
бързо да разбере, чс този if е вложен, както и кои са останалите
вложени действия. Вложен if може да се камира и в блок от кон-
струкции, който с цел на въпшния if.
Всеки компилатор по ANSI стандарта ще ви дадс възможност
да влагате конструкции if па дълбочина най-малко 15 нива.
(Доста рядке обаче ще намерите толкова дълбоко влагане.)
Един доста объркващ аспект, съществуващ при влагането на
if, с демонстриран чрез следния фрагмент:
if (Р)
if(q) princf("a and b are true");
else printf ("To which stateiaent does this else apply?");
Въпросът, зададсн от втората конструкция printf() е: кой от два-
та if е свързан с конструкцията else? За щастие, отговорьт е съв-
сем прост: дадена конструкция else вкнаги се асоциира с най-
близката до нея конструкция if от същия блок, която все още ня-
ма асоцииран else. В дадсния пример, конструкцията else е асо-
циирана с втория if.
90 С - Практически самоучител
ПРИМЕРИ
1. Възможно е навързването на няколко конструкции if и else
в нещо, което понякога се нарича if-else-if стълба или if-
else-if стълбица поради визуалното му представяне. При
тази ситуация всеки вграден if има за своя цел друг if Ос-
новнияг начин, по конто изглежда if-else-if стълбицата, е
показан тук:
if (израз) конструкция;
else
if(израз) конструкция,
else
И(израз) конструкция;
else конструкция;
Изразите се пресмятат отгоре надолу. Веднага щом бъде
намерено вярно условие, конструкцията, асоциирана с не-
го, се изпълнява, а останалата част от стълбицата се прес-
кача. Ако нито един от изразите не е верен, се изпълнява
последният else. Това означава, че ако всички останали
проверки на условия не са удовлетворен и, се изпълнява
конструкцията на последния else. А_ко няма последен else,
а всички условия са грешни, няма да се извърши никакво
действие.
Въпреки че насечеьото кредставяне на основната фор-
ма па if- else -if стълбицата, което беше демонстрирано по-
тере, е технически коректно, то понякога става доста дъл-
боко, и затова стълбицата обикновено се нише така:
if(израз) конструкция;
else if(u3pa3) конструкция;
else И(израз) конструкция;
else конструкция,
Бихме могли да подобрим аритметичната програма,
разработена в секция 3.1, чрез if-else-if стълбица, както е
показано туте:
flinclude <stdio.h>
int main(void)
f
Още управпяващи конструкции е С 91
int а, Ь;
char ch;
printf("Do you want to:\n");
printf(“Add, Subtract, Multiply, or Divide7\n");
printf("Enter first letter: ") ;
ch = getcharO;
printf("\n");
printf("Enter first number: ") ;
scanf ("%d", *.a) ;
printf("Enter second number: ");
scanf("%d", &b);
if(ch=='A') printf("id", a+b) ;
else if(ch==’S') printf("%d”, a-b);
else if(ch==’M') printf("id", a*b);
else if(ch==’D’ «£ b!=0) printf("id", a/b);
return 0;
}
Гова e подобрена версия, спрямо оригинала, защото сега
при намиране на съвпадение всички останали конструкции
if се прескачат. Това, от своя страна, означава, че програ-
мата не губи време за изпълняване на излишни операции.
Въпреки че в дадения пример това не е от особена важ-
ност, ще се сблъскате със ситуации, в конто няма да е така.
2. Вложените конструкции if са много чссто употребявани в
програмирането. Его например едно пс-добрение към про-
грамата за упражнение по събиране, разработена в пре-
дишката глава. То дава на потребителя втори опит за лос-
тигане до верния отговор.
^include <stdio.h>
int main(void)
{
int answer, count;
int again;
for(count=l; count<ll; ccunt++) {
printf (''What is %d + id7 ", count, count);
scanf("id", «answer);
if(answer == count+count) printf("Right!\n");
else {
printf("Sorry, you’re wrong\n");
printf("Try again.\n ") ;
printf("\nWhat is id + id? ", count, count);
scanf("id", «answer);
92 С-Практически самоучител
/* вграден if */
if(answer == count+count) printf("Right!\n");
else
printf("Wrong, the answer is %d\n",
count+count);
)
}
return 0;
)
Тук вторият if e вложен в блока else на външния if.
1. С кой if е свързана конструкцията else в този пример?
if(ch==*‘S') { /* първи if */
printf ("Enter a number: ");
scanf("%d", &y) ;
/* втори if */
if(у) printf("Its square is %d.", y*y);
)
else printf("Make next selection.");
2 . Напишете програма, която изчислява площта на кръг, пра-
воъгълник или триъгьлник. Използвайге if-else-if стьлбица.
3.3
Вариациите на цикъла for
В С, цикълът for е със зпачително по-големи возможности, от-
колкою в повечето други комшотърни езици. Когато ви го пред-
ставихме в раздел 2, вие видяхте само формата, подобна на из-
ползваната в остановите езици. Сега ще се убедите, че цикълът
for с много но-гъвкав.
Причината за по-голямата гъвкавост на for е в това, че изра-
зите, наречени инициализация, проверка на условие и инкремен-
тиране, не се ограничават до тези тесни роли. Цикълът for не
поставя ограничения на типовете изрази, конто сс появяват в не-
го. Например не е задьджително да използвате частта за инициа-
лизиране, за да инициализирате променливата за управление на
лик-ьпя Освен това лопи няма нужда от използването на такава
Още управляващи конструкции в С 93
променлива, защото условният израз може да използва други
средства за спиране на цикъла. На носледно място, частта за ин-
крементиране, технически погледнато, нредставлява просто из-
раз, който се изчислява при всяка итерация. Не е задължително
той да инкрементира или декрементира стойността на някоя
променлива.
Друга важна причина за гъвкавостта на гози цикъл е, че един
или повече от изразите, конто присьсгват в него, могат да бъдат
празни. Например ако променливата за управление на цикъла
вече е инициализирана извън for, няма нужда от израз за иници-
ализация.
ПРИМЕРИ
1. Тази програма поодължава изпълнението на цикъла, дока-
тэ от клавиатурата се въвсде q Вместо да проверява про-
менливата за управление на цикъла, проверката на усло-
вного в този цикъл for проверява стойността на знака, вь-
ведсн от потребителя
Ifinclude <stdio.h>
#include <conio.h>
int main(void)
{
int i;
char ch;
ch = ’a'; /* задаване на начална стойност на ch */
for(i=0; ch != 'q'; i++) {
printf("pass: %d\n", i);
ch = getche ();
I
return 0;
}
Тук условието, което контролира цикъла, няма нищо общо
с променливата за управление на цикъла Причината на ch
да е зададена начална стойност е, за да се предотврати слу-
чайного съдържапе на q при започването на програмата.
2. Както бете казано по-рано, възможно е някой израз в ци-
къла да остане празеп. Например тази програма изисква от
потребителя да въведе число и след това брои от него до
нула. Тук променливата за управление на цикъла се ини-
94 С - Практически самоучител
циализира от потребителя извън цикъла, което означава,
че частта за инициализация е празна-
fl include <stdio.h>
int main(void)
int i;
printf("Enter an integer: ");
scanf("%d", &i);
fcr(; i; i--) printf("%d, i) ;
i
return C; '
3. Друга вариация на for e, че негэвата цел може да бъде
празна. Например тази програма продължава да чете зна-
кове от клавиатурата, докато потребит елят въведе q.
^include <stdio.h>
flinclude <conio.h>
int main(void)
{
char ch;
for(ch=getche(); ch!=’q'; ch=getche());
printf("Found the q.");
return 0;
}
Обърнете внимание, че конструкциите, присвояващи стой-
ност на ch, са преместени в цикъла. Това означава, че при
започване на цикъла, се извиква getche(). След това стой-
ността на ch ее проверява за стойност q. Следва принцип-
ното изпълнекие на несъществуващата цел на for, както и
извикването на getche() от частта за инкрементиране. То-
зи процес се повтаря, докато потребителят въведе q
Причината, поради която целта на for да може да оста-
на празна, е в това, че С позволяла използването на null
конструкции.
4. Използвайки for е възможпо да се създаде цикъл, който
никога не спира Този вид цикли обикновено се наричат
безкрайни цикли. Въпреки че случайного създаване па без-
краен цикъл е бъг (логическа грешка), лонякога ще искате
умишлено да създадете такъв. (По-нататьк в тази глава ше
разберете, че има начин да се излезе дори и от безкраен
Още упраЕля ващи конструкции в С 95
цикъл^) За да създадете безкраен цикъл, създайте цикъл
for, подобен на този:
for (
Както можете да забележите, във for няма иикакви изрази.
Когато в условнэта част няма никакви изрази компилато-
рът приема, че тя е вярна и следователно продължава да
изпълнява тялото на цикъла.
5. В С, за разлика от повечето други компютърпи езици, про-
менливата за управление на цикъла може да се променя
извън частта за инкрементиране. Например слсдващата
програма “ръчно” увеличава i в края на всяка итерация на
цикъла.
ttinclude <stdic.h>
int main(void)
(
int i ;
for(i=0; i<10; ) (
printf("%d ", i) ;
i++;
return 0;
1. Напишете програма, изчисляваща времето за път, при да-
дени разстояние и средня скорост. Нека потребителят оп-
ределя броя на изчисленията на времето, конто той или тя
иска да се извършат.
2. За създаване на цикли за времезакъснение често се изпол-
зват цикли for с празни пели. Създайте програма, която
пита потребителя за някакво число, и след това започва да
итерира, докато стигне до нуля. След като приключи
преброяването, трябва да се чуе звуков сигнал, но не и да
се изобрази нещо на екрана.
96 С - Практически самоучител
3. Дори даден цикъл for да използва променлива за контро-
лиране на цикъла, не е задължително тя да ее инкремснти-
ра или декрементира с точно определена стойност. Приба-
вяната или изважданага стойност може да се променя. На-
пишете програма, която започва от 1 и продължава до
1000. Нека в израза за инкрементиране на тази програма,
променливата за управление на цикъла да се прибавя сама
към себе си. Това е един лесен начин за получаване на
аритметичната прогресия 1 2 4 8 16 и т.н.
3.4
ЦИКЪЛЪТ WHILE НА С
Друг цикъл в С е while. Той има следната обща форма:
whilefuapaa) конструкция1,
Разбира се, целта на while може да бъде също блок с код. Цикъ-
лът while работа чрез повтаряне на своята цел, докато изразът е
верен. Когато той стане грешен, цикълът спира Стойностга на
израза се проверява в началото на всяка итерация на цикъла. То-
ва означава, че ако изразът, с който се започва, е грешен, цикъ-
лът няма да бъде изпълнен нито веднъж.
ПРИМЕРИ
1. Въпреки че for е достатъчно 1ъвкав, за да си позволи да
бъде контролиран от фактори, конто не са свързани с тра-
диционната му употреба, все пак трябва да изберете
цикъл, съответстващ най-добре на нуждите на ситуацията
Например по-долу е дадена демонстрация как чрез while
може по-добре да се изчака въвеждането на q. Ако срав-
ните това решение с пример 3 от секция 3.3, ще видите
колко пе лена е тази версия. (Скоро ще научите обаче за
съществуването на по-добър цикъл за изпълнениего на та-
кива задачи.)
#ir.clude. <stdio.h>
#inclode <conio.h>
int main(void)
{
char ch;
ch - getcheO;
while (ch! = 'q') ch = getcheO;
Още управляващи конструкции в С 97
printf("Found the q.");
return 0;
2. Следващата програма представлява проста машина за ко-
диране. Тя трансформира въведените от вас знакове в ко-
дирана форма чрез добавяне на единица към всяка буква.
Например *А’ става ‘В‘ и т н. Програмата спира да работи,
когато натиснете ENTER. (При натискане на ENTER, фун-
кцията getche() врыца \г.)
((include <stdio.h>
((include <conio.h>
int main(void)
{
char ch;
printf("Enter your message.\n");
ch = getche();
wiii le (ch ! = ' \ r ' ) {
printf ("%c", chH);
ch - getche();
}
return 0;
1. В упражнение 1 от секция 3.3 създадохте програма, която
изчислява времето за път при дадени разстояние и средна
скорост. Тогава използвахте цикъл for, за да дадете въз-
можност на потребителя да начисли ияколко времена.
Преработете програмата така, че да използва цикъл while.
2. Напишете програма, която да дскодира съобщения, коди-
рани чрез програмата за кодиране от втория пример на та-
зи секция
9В С - Практически самоучител
3.5
Използване на цикъла do
Последният цикъл в С е do. Той има следната обща форма:
do {
конструкции
} while(uapa3);
Ако се повтаря само една конструкция, фигурните скоби не са
ксобходими. Повечето програмиста все пак ги включват, за да
могат по-лесно да разпознават, че думата while, с която завърш-
ва цикълът do, е част от цикъл do, а не началото на цикъл while.
Цикълът do повтаря конструкцията или конструкциите, дока-
то изразът е верен. Той спира да се изпълнява, когато изразът
стане грешен. Цикълът do е уникален, тъй като той изпълнява
кода от своего тяло поне веднъж. Това е така, защото изразът,
контролиращ цикъла, се проверява в края на всяка итерация на
цикъла.
ПРИМЕРИ
1. Фактът, че do випаги изпълнява тялото на своя цикъл поне
веднъж, го прави перфектен за проверка на вход от меню.
Например тази версия на аритметичната програма изисква
от потребителя да въвежда своя? избор, докато гой или тя
въведе правилен отговор.
ftinclude <stdio.h>
int main(void)
(
int a, b;
char ch;
printf("Do you want to:\n");
printf("Add, Subtract, Multiply, or Divide?\n");
/* принуждаване на потребителя да въведе
валидна стойност */
do {
printf("Enter first letter: ");
ch = getcharO;
} while(ch 1 =’A’ && ch! = ’S’ S& ch! = ’M' ch! = 'D');
printf("\n");
printf("Enter first number: ");
scanf("%d", &a);
printf("Enter second number: ");
scanf("%d", &b);
Още управляващи конструкции в С 99
if(ch=='A’) printf (’'%d", а+Ь) ;
else if(ch==’S') printf ("%d'', a-b) ;
else if(ch=='M') printf("%d", a*b);
else if(ch==’O' && b!=C) printf(”%d", a/b);
return 0;
}
2. Цикълът doe особено полезен, когато вашата програма
чака да се случи някакво събитие. Например тази програ-
ма чака потребителя да въведе q. Забележете, че тя съдър-
жа едно извикване на getche() по-малко от еквивалентната
програма, описана в секцията за цикъла while.
^include <stdic.h>
#include <conio.h>
int main(void)
(
char ch;
do {
ch = getche ();
} while(ch!=’q’);
printf("Found the q.");
return 0;
}
Тъй като условието на цикъла се проверява в края на всяка
итерация, не е необходима предварителна инициализация
на ch преди започването на цикъла.
1. Напишете програма, която да превръща галони в литри.
Използвайте цикъл do, за да дадете възможност на потре-
бителя да повтаря преврыцането. (Един галон е равен
приблизително на 3.7854 литра.)
2. Напишете програма, която изписва на екрана посоченото
по-долу меню и използва цикъл do, за да проверява за ко-
ректни отговори. (Не е нужно програмата да изпълнява
действителните функции, посочени в менюто )
100 С - Практически самоучител
Mailing list menu:
1 Enter addresses
2. Delete addresses
3. Search the list
4. Print the list
5. Quit
Enter the number of your choice (1-5).
3.6
Създаване на вложени цикли
Когато тялото на един цикъл съдържа друг, вторият цикъл се
нарича вложен в първия. Всеки един от циклите в С може да бъ-
де вложен в който и да било друг Стандартът на ANSI за С оп-
редели, че циклите могат да се влагаг на дълбочина най-малко 15
нива. Въпреки това повечето компилатори позволяват влагапе до
практически всякакво ниво. Като прост пример за вложени цик-
ли for, този фрагмент отпечатва на екрана на екрана десет пъти
числата от 1 до 10.
for(i=0; i<10; i++) {
for(j=-l; jell; j++) printf("%d ", j); /* вложен
цикъл * I
printf("\n");
}
ПРИМЕРИ
1. Можете да използвате вложен for, за да направите още ед-
нс подобрение на упражнението по аритметика. Във вер-
сияга, показана по-долу, програмата дава на потребителя
три възможности да даде правилния отговор. Обърнете
внимание на употребата на променливата right, която има
за задача да спре изпълнението на цикъла по-рано, ако бъ-
де даден правилен отговср.
#include <stdio.h>
int main(void)
int answer, count, chances, right;
□ще управляващи конструкции в С 101
for(соant=l; count<ll; counr++) {
printf("What is %d + %d?"# count, count);
scanf("%d", ianswer),
if(answer == ccunt+count) printf("Right!\n");
else {
printf("Sorry, you1re wrong.\n");
printf("Try again.\n");
right = 0;
/* вложен for */
for(chances=0; chances<3 && I right;
chances++) (
printf("What is %d + %d? ", count, count);
scanf("%d", ianswer);
if(answer == count+count) {
printf("Right!\n");
right = 1;
}
/* ако отговорът отново e грешен, кажете на
потребителя */
if(1 right)
printf("The answer is %d.\n",
count+count);
return 0;
2. Тази програма използва три цикъла for, за да отпечата азбу-
ката три пъги, като всяка буква се изобразява по два пъти:
#include <stdio.h>
int main(void)
int i, j, k;
for(i=0; i<3; i++)
for(j=0; j<26; g++)
fcr(k=0; k<2; k++) printf("%c", 'A’+j);
return 0;
Конструкцията
printf("%c", ’A'+j);
102 С - Практически самоучител
работа, защото ASCII кодовете на буквите от азбуката са
строго нарастващи - всеки следващ е по-голям от този на
предходната буква.
I. Напишете програма, кояго намира всички прости числа
между 2 и 1000.
2. Напишете програма, която прочита десет знака от клавиа-
турата. При всяко прочитане на знак използвайте сгой-
ността на ASCII кода му, за да отпечатате низ от точки,
равни на брой на този код. Например за буквата 'А', чийто
код е 65, програмата би трябвало да изпише 65 точки.
3.7
Използване на break за излизане
от цикъл
Конструкцията break дава възможност за излизане от пикъл в
която и да е точка на неговото тяло, като по този начин се заоби-
каля израза за нормално прекъсване. Ако в един цикъл се срещне
конструкцията break, той незабавно се прекратява и изпълнени-
ето на програмата продължава с конструкцията след цикъла.
Например този цикъл отпечатва само числата от 1 до 10:
#include <stdio.h>
int main(void)
{
int i;
for(i=l; i<100; i++) {
printf("%d ”, i);
if(i==10) break; /* излизане от цикъла ★/
)
return 0;
}
Конструкцията break може да бъде използвана във всеки от три-
те цикъла на С.
Още управлясащи конструкции в С 103
В един цикъл можете да поставите толкова конструкции
break, колкого желаете. Тъй като прекалено много изходни точ-
ки от един цикъл могат да деструктурират кеда, гго-добре е да
използвате break само за специални цели, а не като нормален
изход от цикъла.
ПРИМЕРИ
1. Конструкцията break обикновено се използва в цикли, при
конто някакво специално условие трябва да предизвика
незабавно прекратяване. Тук е даден пример за такава си-
туация. В този случай натискането на клавиш може да
сире изпълнението на програмата.
ttinciude <stdio.h>
#include <conio.h>
int main(void)
int i;
char ch;
/* изписване на всички числа, конто са кратки
на 6 */
for(i=l; i<10000; i++) {
if(!(i%6)) {
printf (,rld, more? (Y/N)", i) ;
ch = getche();
if(ch==’N') break; /* спиране на цикъла */
printf("\n"); •
}
}
return 0;
)
2. Дадена конструкция break ще предизвика прекратяване
само на най-вътрешиия цикъл. Следващата програма нап-
ример отпечатва пет пъти числата от 0 до 5.
#include <stdio.h>
int main(void)
{
int i, j;
tor(i=0; i<5; i++) {
for(j=0; jclOO; j++) {
printf("%d", j);
if(j==5) break;
104 С - Практически самоучител
printf("\п") ;
return 0;
}
3. Причината, поради която С включва конструкцията break
е, за да даде възможност на вашите програми за пс-голяма
ефективност. Разгледайге например следния фрагмент:
do {
printf("Load, Save, Edit, Quit?\n");
do {
printf("Enter your selection:
ch = getcharf);
} while(ch!=’L‘ && ch!='S’ && ch!='E* && ch!='Q’);
if(ch ! = ’Q') {
/* извършване на нещо */
if(ch ! = 'Q') {
/* иззършЕане на нещо друго */
/* и т.н. */
} while(ch != ’ Q')
В тази ситуация се извършват няколко проверки дали ch
съдържа ‘Q’ По този начин се избягва изпълнението на
определени части, когато бъде избрана опцията Quit. Пиве-
чето програмисти на С биха написали горния цикъл така:
for( ; ; ) { /* безкраен цикъл for */
printf("Load, Save, Edit, Quit?\n");
do {
printf ("Enter your selection: ");
ch = getcharf);
} while (ch! = ’L‘ &£. ch! = ’S’ && ch!-'E' && ch! = 'Q');
if(ch == 'Q') break;
/* извършване на нещо */
/* извършване на нещо друго */
/* и т.н. */
В тази версия е достатъчно ch да се проверяла само вед-
нъж дали съдържа ‘Q’. Както можете да забележите, тази
имплементация е но-ефективна, защото се изисква само
една конструкция if
Още управляс а щи конструкции в С 105
I. Напишете самостоятелно няколко програми, конто изпол-
зват break, за да излизат от цикли. Уьерете се, че сте опи-
тали и с трите вида цикли.
2. Напишете програма, която отпечатва таблица с правилния
размер па бакшиша. Заиочиете таблицата от I лв. и завьр-
шете със ЮС л в., като използвате нарастване от I лв. Из-
числявайте бакшишите за три процентови пива: 10%, 15%
и 20%. След всеки ред питайте потребителя дали той или
тя иска да продължи. Ако отговорът е отрицателен, изгюл-
звайте break, за да спрете цикъла и да излезете от прог-
рамата.
3.8
Използване на конструкцията
continue
Конструкцията continue е един вид противоположна па break.
Тя предизвиква изпълнениего па следваш.ата итерация, като про-
пуска кода, намиращ се между нея и условието на цикъла. Нап-
ример тази програма никога не отпечатва нищо:
#include <stdio.h>
int main(void)
{
int x;
for(x=0; x<103; x++) {
continue;
printf("%d ", x) ; /* това никога не се изпълнява */
1
ret am 0;
При всяко достигане на конструкцията continue тя предизвиква
повторението на цикъла, като пропуска конструкцията printff)
При циклите while и do-while, конструкцията continue ще
предизвика премпнаването на изпълнението направо към про-
верката на условието и след това продължаването на цикличния
процес. В случая с for се изпълнява частта за инкрементиране,
извьршва се проверката на условието и цикълът продължава.
Честно казано, continue се използва рядке. Това не е защото
неговото използване е лоша практика, а просто защото не се
срещат често случаи за уместно прилагане.
106 С - Практически самоучител
ПРИМЕРИ
I. Добра причина за използването на continue е, за ла се за-
почпе отново лоредица от конструкции при възникването
на грешка. Следващата програма например изчислява те-
кущата сума на числата, вьведени от потребителя. Преди
добавянето на число към сумата програмата проверява да-
йн то е въведено правилно, като изисква от потребителя да
го въведе отново. Ако двете числа не съвпадат, програмата
използва continue, за да започне цикьла отначало.
#include <stdio.h>
int main(void)
int total, i, j;
total = 0;
do (
printf("Enter next number (0 to stop): ");
scanf("%d", &i);
printf("Enter number again: ");
scanf ("%d’‘, & j ) ;
if(i '= j) {
printf("Mismatch\n") ;
continue;
total = total + i;
} while (i) ;
printf("Total is %d\n", total);
return 0;
l. Напишете програма, която отпечатва само нечетните чис-
ла между 1 и 100. Използвайте цикъл for, подобен на този’
fcr(i=l; i<101; i++) . . .
Използвайте конструкция continue, за да избегнете отпе-
чатването на четните числа.
Сще управляващи конструкции в С 107
3.9
ИЗБОР МЕЖДУ АЛТЕРНАТИВИ ЧРЕЗ
КОНСТРУКЦИЯТА SWITCH
Конструкцията if е подходяща за избор между две алтернативи,
но тя лесно се затруднява при появата на повече такива. Решени-
ето на езика С на този проблем е конструкцията switch. Това е
конструкцията на С за избор от множество възможности. Тя се
използва за избор на един от няколко възможяи, алтернативни
начина за изпълнение на програмата, и работа както е описано
по-долу. Дадена стойност се сравнява последователно със спи-
сък от целочислени или знакови константа. Когато се намери
съвпадане, се изпълнява поредицата от конструкции, асоциирана
с това съвпадение. Ето как изглежда общата форма на конструк-
цията switch:
switch(cmo£/wocm) {
case константа 1
поредица от конструкции
break,
case константа2:
поредица от конструкции
break;
case константа 3:
поредица от конструкции
break;
default
поредица от конструкции
break;
Поредицата от конструкции на default се изпълнява, ако не се
намери съвпадение. default не е задължителна част от switch
Лко всички сравнения пропадиат и няма default, тогава не се из-
вършва никакво действие. Ако се намери съвпадение, конструк-
циите, асоциираш! с този case, се изпълняват до срсщапе на
break, или, в случая с default или последним case, до стигане до
края на switch.
Като прост пример, тази програма разпознава цифрите 1, 2, 3
и 4 и отпечатва името на въведената. Това означава, че ако въве-
дсте 2, програмата ще покаже two.
#inclvcle <stdio.h>
int irain(void)
(
int i;
108 С - Практически самоучител
printf("Enter a number between 1 and 4: ");
scanf("%d", &i);
switch(i) {
case 1:
printf (‘'one") ;
break;
case 2:
pri nt f("two");
break;
case 3:
printf("three");
break;
case 4:
printf("four");
break;
default-
printf("Unrecognized Number");
return 0;
Конструкцията switch се различава от if по това, че може да
проверява само за равенство, докато изразът за проверка на if
може да е от всякакъв тип Също така, switch работи само с ти-
повете int и char Нс можете например да използвате числа с
плаваща запетая.
Поредиците от конструкции, асоциирани с вески case, не са
блокове; те не се ограждат с фигурни скоби.
ANSI С стандартьт повелява, че се разрешават поне 257 конс-
трукции case. Поради причини, сеързани с ефективностга, тряб-
ва да ограничавате броя на case конструкциите до много по-ма-
лък брой. Също така, в един и сьщ switch не могат да съществу-
ват две case константа с идентични стойности.
Възможно е един switch да бъде част от поредица конструк-
ции на по-външен switch. Това се нарича вложен switch. Ако
case константите на вътрешния и на външния switch съдържат
общи стойности, това няма да предизвика конфликт. Следващи-
ят програмен фрагмент е напълно приемлив:
switch(a) {
case 1:
switch(b) {
case 0: printf("b is false");
break;
case 1: printf("b is true");
break;
Още управляващи конструкции в С 109
case 2:
Всеки компилатор по ANSI стандарта ще позволи влагането
на switch конструкции на ионе 15 нива.
ПРИМЕРИ
I. Конструкцията switch често се използва за обработката на
команди от меню. Аритметичната програма например мо-
же да се запише, както е показано по-долу. Тази версия
отразява професионалния начин за нисане на С код.
#include <stdio.h>
int main(void)
{
int a, b;
char ch;
printf("Do you want to:\n");
printf("Add, Subtract, Multiply, or Divide?\n") ;
/* принуждаване на потребителя да въведе
валиден отговор */
do {
printf("Enter first letter: ");
ch = getchar();
} while(ch!='A' && ch!='S' && ch!='M' && ch!='D');
printf("\n");
printf("Enter first number: ");
scanf("%d", &a);
printf("Enter second number: ");
scanf("%d", &b);
switch(ch) {
case 'A': printf("%d", a+b) ;
break;
case 'S': printf("%d", a-b) ;
break;
case 'M': printf("%d", a*b);
break;
case 'D': if(b!=O) printf("%d", a/b);
}
return 0;
2. Технически погледнато, конструкцията break не e задъл-
жителна. Когато тя се срещне в switch, това предизвиква
110 С - Практически самоучител
изпълнението на програмата да излезс от цялата конст-
рукция switch и да продължи с първата конструкция след
switch. Това доста прилила на излизансто от цикъл Лко
обаче конструкцията break се пропусне, изпълнението
продължава със следващия case или default (при положе-
ние, че такъв съществува). От тук слсдва, чс ако липсва
конструкция break, изпълнениего па програмата “попада”
на следващия case и спира едза когато срещне break или
стигне края на switch. Проучете внимателно тази програма:
ft include <stdio.h>
^include <conio.h>
int main(void)
char ch;
do {
printf("\nEnter a character, q to quit: ");
ch = getcheO;
printf ("\n");
switch(ch) {
case ’a’:
printf("Now is ");
case 'b':
printf ("the time ");
case ’c':
printf("for all good men");
break;
case *d':
printf("The summer ");
case ’ e' :
printf("soldier ”);
}
} while(ch != ’ q ’) ;
return 0;
)
Ако потребителят въведе а, на екрана се отпечатва цялата
фраза Now is the time for all good men. Въвеждането на b
изписва the time for all good men. Както виждаге, след ка-
то веднъж изпълнението започне от един case, то продъл-
жава, докато срещне break или стигне до края на switch.
3. Поредицата о г конструкции, асоциирана с даден case, мо-
же да е празна. По този начин се дава възможност две пли
повече конструкции case да поделят една и съща поредица
от конструкции, без да се налага дублиране на кода. Нап-
ример тук е дадена програма, която раздели буквите на
гласни и съгласни:
Още управляващи конструкции в С 111
#include <stdio.h>
#include <conio.h>
int main(void)
{
char ch;
printf("Enter the letter: ");
ch = getche ();
switch(ch) {
case 'a':
case 'e':
case ’i ’ :
case ’o':
case ' u':
case 'у':
printf(" is a vowel\n");
break;
default:
printf (" is a consonant");
}
return 0;
}
1. Какво не e наред в този фрагмент?
float f;
scanf("%f", &f);
switch(f) {
case 10.05:
2. Напишете програма, която брои буквите, цифрите и пунк-
гуационните зпаци, въвеждани от потребителя. Спрете въ-
веждането, когато бъде катиснат ENTER. Използвайте
конструкция switch, за да категоризирате знаковете на:
пунктуационни, цифри и букви. Когато програмата завър-
шва, докладвайте броя на знаковете от всяка категория.
(Ако желаете, предположете, че ако далей знак не е цифра
или пунктуация, значи е буква. Също така използвайте са-
мо най-често срещаните знакове за пунктуация.)
112 С - Практически самоучител
3.1
Конструкцията goto
С поддържа конструкция за безусловен преход, наречена goto.
Тъй като С е заместник на асембперния код, включването на goto
е необходимо, защото тази конструкция може да се използва за
създаването на много бърз изпълним код. Въпреки това, повече-
то програмисти не използват goto, защото тя деструктурира
програмата и ако се използва често, може да палрави програмата
практически нсразбираема. Освен това не съшествува конструк-
ция, която да. изисква goto Поради тези причини, goto не се из-
ползва извън тази секция на книгата.
Конструкцията goto може да извършва преход само в текуща-
та функция. Тя не може да “прескача” между различии функции,
goto работа с номощта на етикет. В С, етикетът представлява
валидно име на идентификатор, следван от двоеточие. Например
следната конструкция goto “прескача” конструкцията priiitf():
goto mylabel;
printf("This will not print.");
raylabel: printf("This will print.");
Може би единствената полезна употреба на goto е за изход от
лълбоко вложен код при някаква извънредна грешка.
ПРИМЕР
I. Тази програма използва goto за да създаде еквивалент на
цикьл for, изпълняван отД до 10 (Това е просто пример за
goto. В реалната практика трябва да използвате истински
цикъл for.)
((include <stdio.h>
int inain(void)
{
int i;
i = 1;
again;
printf("%d", i);
i++;
if (i<tlO) goto again;
return 0;
Още управлязащи конструкции г С 113
I Напишете програма, която използва goto, за да емулира
цикъл while, избрсяваш числата от 1 до 10.
Проверка на уменията, представени в глаьата
На този етап би трябвало да сте в състояние да отговорите на
тези вънроси и да изпьлните упражненията.
1. Както е показано в упражнение 2 на секция 3.1, ASCII ко-
доветс на малките букви се различават от тези па главните
с 32. Поради това, за да преобразувате малка буква в глав-
на, просто извадете 32 от нея. Напишете програма, която
чете знакове от клавиатурата и изписва на екрана малките
букви като главни. Спрсте, когато бъде натиснат ENTER.
2. Използвайте вложена конструкция if, за да напишете про-
грама, която изисква от потребителя да въведе число и
след това докладва дали то е положително, нула или отри-
цателно.
3. Верен ли е този цикъл for?
char ch;
ch = 'x';
for ( ; ch ! =
ch - getche();
4. Покажете традиционния начин за създаване на безкраен
цикъл в С.
5. Използвайте трите конструкции за цикъл, за да покажете
три различии начина за броене от 1 до 19.
6. Каква е ролята на конструкция га break, когато се използва
в цикъл?
7. Вярна ли е тази конструкция switch?
switch(i) {
case 1: printf("nickel");
break;
case 2: printf("dime");
break;
114 С - Практически самоучител
case 3: printf("quarter");
)
8. Верен ли е този фрагмент с goto?
goto alldone;
allclone
Проверка на натрупаните умения
Тази част проверява колко добре сте интегрирали материала
от тази глава с този от предишните.
!. Използвайте конструкция switch, за да напишете програ-
ма, която чете знакове от клавиатурата и наблюдава за та-
булации, нови рсдове и backspace. Когато се въведе някой
от тези знакове, го изобразете с думи. Например ако пот-
ребителят натисне клавиша TAB, отпечатайте tab. Нека
потребителят да трябва да въведе q, за да сире програмата.
2. Тъй като тази програма не е коректна, покажете как би из-
глеждала тя, ако беше написана от опитен С програмист.
^include <=tdio.h>
int main(void)
(
int i, j, k;
for(k-0; k<10; k=k+l) {
printf("Enter first number: ");
scanf("%d", &i);
printf("Enter second number: ");
scanf{"%d", & j ) ;
if(j != 0) printf("%d\n", i/j);
if(j == 0) printf("cannot divide by zero\n");
return 0;
Подробно описание на
ТИПОВЕТЕ ДАННИ,
ПРОМЕНЛИВИТЕ И
ИЗРАЗИТЕ
Разглеждани теми в главата
4.1 Използване на
модификаторите за типове данни
вС
4.2 Къде се декларират
променливи
4.3 Константите отблизо
4.4 Инициализиране на
променливи
4.5 Конвертиране на типовете
при пресмятане на изрази
4.6 Конвертиране на типове при
присвоявания
4.7 Преобразуване на типове
116 С - Практически самоучител
ази глава изследва по-пълно някои от концепциите, пред-
ставени в глава I. Тук се разглеждат модификаторите на
данни в С, глобалните и локалните променливи и конс-
тантите. Освен това се обсъждат различните конвертира-
ния на типове.
Проверка на знанията
Преди да продължим, трябва да сте в състояние да отговорите
на следващите въпроси и да изпълните следните упражнения.
I. Използвайте трите конструкции за цикъл на С, за да покаже-
те три начина за написване на цикъл, който брой от 1 до 10.
2. Конвертирайте следната поредица от конструкции if в ек-
вивалентен switch.
if(ch=='L') load ( );
else
else
else
else
if(ch==
if(ch==
if(ch==
if(ch==
Е’)
save( );
enter( );
display( );
quit( );
3. Напишете програма, приемаща знакове от клавиатурата,
докато потребителят натисне клавиша ENTER.
4. Каква е ролята на break?
5. Каква е ролята на continue?
6. Напишете програма, изписваща даденото по-долу меню и
извършваща избраната операция. Нека това се повтаря,
докато потребителят избере Quit.
Convert
1. feet to meters
2. meters to feet
3. ounces to pounds
4. pounds to ounces
5. Quit
Enter the number of your choice:
Подробно описание на типовете данни, променливите и изразите 117
4.1
Използване на модификаторите
ЗА ТИПОВЕ ДАННИ В С
В глава 1 научихте, че С притежава пет основни типа данни.
void, char, int, float и double. Тези основни гипове, с изключе-
ние на void, могат да се модифицират чрез използването на мо-
дификаторите на типове в С. По този начин, типовете могат
по-добре да се впишат във вашите нужди. Модификаторите на
типове са:
long
short
signed
unsigned
Модификаторы на тип предшества името на типа. Например та-
зи конструкция декларира long цяло число:
long int i;
Сега ще обясним вьздействието па вески един от модификаторите.
Модификаторите long и short могат да се прилагат върху int.
Като общо правило, числата от тип short int са по-малки от тези
от тип int. Точно обратного, long int е по-голям от int. Например
при повечето 16-битови среди, числата от тип int са дълги 16 би-
та, докато тези от long int - 32 бита. Точного значение на long и
short обаче записи от имплементацията. При създаването на
стандарта на ANS] за С, са зададени лшнимални размера на int,
short int и long int. Стандарты не определи точни размери на те-
зи слсменти. (Вижтс таблица 4-1.) Ако използваме например ми-
нималните размери, зададени навремето от ANSI С стандарта,
най-малкият приемлив размер за int е 16 бита, което важи и за
short int. По този начин се разрещава int числата и тези от тип
short int да имат един и сыц размер! Всъщност в повечето
16-битови среди няма разлика между двата типа. Ако погледнете
обаче 32-битовите среди, ще забележите, че int и long int са с
еднакъв размер. Тъй като точного въздействие на модификато-
рите long и short се определи от работната среда и компютьра,
на който работите, трябва да проверите документацията на ва-
шия компилатор, за да разберете техните въздействия точно.
Модификаторы long може да се прилага и при double. В този
случай, модификаторы подобрява приблизително два иъти точ-
ное гга на променливата с плаваща запетая
Модификаторы signed се използва за определяне на целочис-
лена стойност със знак. (Число със знак означава, че то може да
бъде положително или отрицателно.) Прилагай его на signed
118 С - Практически самоучител
върху променливи от тип int е излишно, тъй като декларацията
на целочислена променлива автоматично създава стойност със
знак. Основного използване на signed е с char. Дали самият char
е със или без знак, зависи изцяло от имплементацията. При ни-
кои имплементации по подразбиране char е без знак, докато при
други е със. За да осигурите променлива от тип char със знак във
всички среди, трябва да я дефинирате като signed char. Тъй като
повечето компилатори имплемснтират char със знак, тази книга
просто приема, че всички такива променливи са със знак и няма
да използва модификатора signed.
Модификаторы unsigned може да се прилага върху char и
int Той може да се използва и в комбинация с long и short. Чрез
този модификатор се създава цяло число без знак, Разликата
между целите числа със и без знак е в начина на интерпретация
на най-старшия бит. Ако е зададено цяло число със знак, тогава
С компилаторът ще геперира код, който предполага, че най-стар-
шият бит се използва като флаг за знак. Ако този флаг за знак е О
- числото е положително; а ако е I - отрицателно. Отрипателни-
те числа обикновено се представят чрез допълнителен код. При
този метод, всички битове на числото (с изключение на флага за
знак) се инвертират и към новополучсното число се добавя 1.
Накрая, флагът за знак се в ди га в 1. (Причината за този метод на
представяне е, че но тази начин се улесняват аритметичните
операции с числа със знак в процесора.)
Целите числа със знак са много важни за много алгоритми, но
те притежават само половината от големината па своите беззна-
кови събратя. Например така изглежда числото 32 767 в двоичен
код:
01111111 11111111
Ако това е стойност със знак и най-старшият бит е настроен в I,
числото би се интерпретирало като -1 (разглежда се формата с
допълнителен код). Ако това обаче е стойност без знак и най-
старшият бит е 1, тогава числото става 65 535.
Таблица 4-1 показва позволените комбинации на основните
типовс с модификаторите за тях. Таблицата показва и най-често
срещания размер и минималния интервал от стойности за всеки
тип, както са зададени от ANSI С стандарта.
Важно е да разберете, че интервалите, показани в таблица 4-1,
са просто минимумите, конто всеки компилятор трябва да пре-
доставя. Компилаторите имат пълната свобода да ги превишават,
а повечето от тях го правят, поне за някои типове данни. Както
беше споменато но-рано, типът int в 32-битова среда обикновено
ще има по-голям интервал от стойности от задължителния ми-
нимум.
Подробно описание на типовете данни, променливите и изразите 119
Тип Типичен размер в битове Минимален интервал от стойности
char 8 -127 до 127
unsigned char 8 0 до 255
signed char 8 -127 до 127
int 16 или 32 -32 767 до 32 767
unsigned int 16 или 32 0 до 65 535
signed int 16 или 32 както int
short int 16 както int
unsigned short int 16 0 до 65 535
signed short int 16 както short int
long int 32 -2 147 483 647 до -2 147 483 647
unsigned (eng int 32 както long int
signed long int 32 0 до 4 294 967 295
float 32 точнсст да шестия знак
double 64 точност до десетия знак
long double 80 точност до десетия знак
1 Таблица 4-1 | Всички типосе данни, дефинирани от ANSI С стандарта.
Съшо така в средите, използващи аритметика с допълнителен
код (коего е случая с повечето компютри). долната граница на
int и char със знак, е по-голяма с единица от показаният мини-
мум. Например в повечето среди, signed char е с интервал от -
128 до 127, a short int е обикновено от -32 768 до 21 767. Трябва
да проверите документацията на вашия компилатор, за да
узнаете точните интервали от стойкости на типовете данпи,
прилагани във вашия компютьр.
С дава възможност за кратко озиачаване на декларациите
unsigned, short или long int. Можеге просто да използвате думи-
те unsigned, short или long без да споменавате int. Последната се
подразбира. Например:
unsigned count;
unsigned int num;
декларират unsigned int променливи.
Важно e да знаете, че променливите от тип char могат да се
използват за съхраление на стойности, различии от тези в ASCII
таблицата. С правд малка разлика между char и int - изключе-
120 С - Практически самоучител
ние е големината, която всеки от тях може да съдържа. Поради
това, както беше споменато по-рано, char променлива със знак
може да се използва вместо short int, ако ситуацията не изисква
по-големи стойности.
Лко изобразявате цели числа, модифицирани с short, long
или unsigned, чрез printf(), не можете просто да използвате спе-
цификатора %d. Причината за това е, че printf() трябва да знае
точно типа на приеманите данни. За да използвате printf( ) при
отпечатване на short, слагайте %bd При изобразяване на long,
използвайте %ld. Когато показвате unsigned стойност, подавай-
те %и. За да отпечатате unsigned long int, използвайте %Iu.
Също така при long double, слагайте %Lf.
Функцията scanf() работи по подобен на print f(), начин. При
четенетс съозветно на short int, използнайтс %hd, на long int -
%id, при unsigned long int - %lu, за double - %lf, а за
long double - %Lf.
ПРИМЕРИ
1. Следвашага програма демонстрира как да въвеждате и из-
веждате short, Long и unsigned стойности.
((include <stdio.h>
int main(void)
{
unsigned u;
1ong 1;
short s;
printf ("Enter an unsigned: ");
scanf("%uH, &u);
printf("Enter a long: ");
scanf("%ld", &1);
printf("Enter a short: ");
scanf("%hd", &s) ;
printf ("%u %ld %hd\n", u, 1, s);
return 0;
2. За да разберете разликата в интерпретиранета на целите
числа със и без знак отстрана на С, стартирайте следната
малка програма. (Тази програма предполага, че short int е
с дължииа 16 бита.)
Подробно описание на типовете данни, променливите и изразите 121
#include <stdio.h>
int main(void)
{
short int i; /* short int със знак */
unsigned short int u; /* short int без знак */
u = 33000;
i = u;
printf("%hd %hu", i, u);
return 0;
Когато тази програма се изпълни, резултатът е
-32536 33000. Причината за това е, че битовият модел,
който представлява 33000 при unsigned short int, се ин-
терпретира като -32536 при signed short int.
3. В С можете да използвате char промеиливи на всяко мяс-
то, на което бихте използвали int такива (вземането пред-
вид на разликите в интервалите от стойности не е основа-
телна причина). Следващата програма например използва
char променлива, за да контролира цикъл, сумиращ числа-
та от I до 100. В някои случаи на компютьра му е необхо-
димо по-малко време, за да достигне до един байт (char),
отколкото до два байта. Поради тази причина много про-
фесионални програмисти използват промеиливи от тип
char, вместо целочислени, когато обхватьт позволява това.
#include <stdio.h>
int main(void)
(
int i;
char j;
i = 0; .
for(j=l; j<101; j++) i = j + i;
printf ("Total is: %d", i);
return 0;
)
122 С-Практически самоучител
I. Покажете как се декларира променлива от тип unsigned
short int, наречена locconnter.
2. Напишете програма, която изисква от потребителя да въ-
веде някакво разстояние, и след това изчислява времсто,
необходимо на светлината, за да го измине. За съхранение
на разстояниетс. използвайте unsigned long int. (Светли-
ната се движи с около 300 000 км в секунда)
3. Напишете пс- друг начин следната конструкция:
short int i;
4.2
КЪДЕ СЕ ДЕКЛАРИРАТ ПРОМЕНЛИВИ
Както вече разбрахте в главаI, има две основни места, на конто
могат да се декларират променливи: в тялото на функция и извън
всички функции. Тези променливи се наричат съответно локални
и глобални. Сега е моменты да разгледаме подробно тези два
вила променливи и правилами за областите на видимост, конто
ги управляват.
Локалните променливи (декларирани вътре във функция) мо-
гат да се референцнрзт само от конструкции, намиращи се в тази
функция. Те не са известки извън функцията. Едно от най-важ-
ните неща при локалните променливи е, че те сътцествуват само
докато се изпълнява функцията, в която са декларирани. Това
означава, че дадена локална променлива се създава при влизане-
ю във функцията и се унищожава при напускането й.
Тъй като локалните променливи не са известии извън техпите
собствени функции, напълно приемливо е локални променливи
от различии функции да имат едки и същи имена. Вземете прсд-
вид следната програма:
^include <stdio.h>
void fl(void), f2(void);
int main(void)
fl( );
return 0;
Подробно описание на типовете данни. променливите и изразите 123
void fl(void)
(
int count;
for(count=0; countclO; counLH) f2 ( );
}
void f2(void)
{
int count;
for(counted; count<10; coant++) printf(”%d ", count);
Програмата отпечатва па екрана десст пъти числата от 0 до 9.
Фактът, че и двете функции използват променлива, наречена
count, няма никакъв ефект върху работата на кода. Поради това,
какво става с count от Г2(), няма никакво значение за count от
П().
Езикът С съдържа ключовата дума auto, използвана за декла-
риране на локални промеиливи. Тъй като обаче всички промен-
ливи по подразбиране са auto, на практика тази ключова дума
пикога не се използва. Следователно няма да я видите в приме-
рите на тази книга.
Локалните промеиливи трябва да се декларират в началото на
всеки блок на функцията. Не е задължително да се декларират
само в началото на блока, дефиниращ функцията. Следващата
програма например е напълно вярка:
#include <stdic.h>
int main(void)
int i;
for(i=0; i<10; i++) (
if(i==5) (
int j; /* декларация на j в блока if */
j = i * 10;
printf ("%d", j);
)
)
return 0;
}
Всяка променлива, декларирана в ладен блок, е известна само за
кода от този блок. Поради тази причина j не може да се използва
извън нейния блок. Честно казано, повечето С програмисти дск-
ларнрат всички промеиливи, използвани от функцията, в начало-
124 С - Практически самоучител
то на нейния блок, защото така е по-удобно. Това е подходы,
който ще се използва в тази книга.
Запомнете едно нещо: трябва да декларирате всички локални
променливи в началото на блока, в който те са дефинирани, пре-
ди каквито и да е други конструкции. Следващият случай нап-
ример е грешен:
#include <stdio.h>
int main(void)
{
printf("This program won't compile.");
int i; /* това трябва да e първо */
i = 10;
printf("%d", i);
return 0;
}
Когато се извика дадена функция, нейниге локални промен-
ливи се създават, а при нейното приключване се унищожават.
Това означава, че локалните променливи не могат да задържат
своите стойности между различните извиквания.
Формалните параметри на функциите също са локални про-
менливи. Въпреки че тези променливи изпълняват специалната
задача да присмат стойностите на аргументитс, предадени на
функцията, те могат да се използват на равно с всяка друга ло-
кална променлива от функцията.
За разлика от локалните променливи, глобалните са известки
в цялата програма и могат да се използват от всяка част на кода.
Освен това те задържат своите стойности по време на цялото из-
пълнение на програмата. Глобалнигс променливи се създават ка-
то се декларирзт извън всички функции. Разгледайте следната
примерна програма:
#include <stdio.h>
void fl(void);
int max; /* това e глобална променлива */
int main(void)
I
max = 10;
fl( ) ;
return 0;
}
void fl(void)
Подробно описание на типовете данни, променливите и изразите 125
fcr(i=0; i<max; i++) printf("%d ", i) ;
}
В този случай, както main(), така и П(), използват глобалиата
променлива max. Функцията main() задава на max стойност I О,
а П() използва тази стойност, за да контролира своя цикъл for.
ПРИМЕРИ
I. ВС, локалните и глобалните променливи могат да имат
едни и евши имена. Това например е валидна програма:
^include <stdio.h>
void fl(void);
int count; /* глобална count */
int main(void)
{
count = 10;
fl 0 ;
print f ("count in r.iain( ): %d\n", count);
return 0;
}
void fl(void)
{
int count; /* локална count */
count = ICO;
printf("count in f]() : %d\n", count);
}
Програмата показва следния резултат:
count in f1() . 100
count in main(): 10
В main(). референцията към count e към глобалиата про-
менлива. Във fl() е дефинирана локална променлива, съ-
що наречена count. Когато се достигне до конструкцията
за присвояване във fl( ), компилаторът първо проверява
дали съществува локална променлива, наречена count.
След като това е така, се използва тя, а не глобалиата със
същото име. Това означава, че ако има локална и глобална
126 С - Практически самоучител
промеиливи с едно и сыцо име, компилаторът винаги ще
използва локалната.
2. Глобалните промеиливи са много полезни, когато един и
същи данни се използват от много функции в програмата.
Въпреки това обаче винаги трябва да използвате локални
промеиливи, където това е възможно, защото прекомерна-
та употреба на глобалки промеиливи води до появата на
някои негативни последствия. Първо, глобалните промен
ливи използват памст през цялото време на изпълнение на
програмата, а не само когато са необходима В случаи, ко-
гато количеството памет не е достатьчно, това може да се
окаже проблем. Второ, използванетс на глобална промен-
лива на мястото на локална, прави дадена функция по-тяс-
но специализирана, защото тя разчита на нещо, което е
дефинирано извън нея. Например тук е даден пример с
безпричинно използване на глобални промеиливи:
Ainclude <stdio.h>
int power(void);
int m, e;
int main(void)
{
m = 2;
e = 3;
printf("%d raised to the %d power is %d", m, e,
power( ));
return 0;
}
/* Специализирана версия на power. */
int power(void)
{
int temp, temp2;
temp = 1;
temp2 = e;
tor( ; temp2> 0; temp2--) temp - remp * m;
return temp;
}
В този случай функцията power( ) e създадена, за да из-
числява стоЙЕОстга на т, повдигната на степей е. Тъй каго
ш и е са глобални, функцията не може да се използва за
Подробно описание на типовете данни, променливите и изразите 127
изчисляването на степента на други стойности. Тя може да
оперира само със стойностите, съдържани в ш и с. Ако
програмата обаче се пренапише в следния вариант,
power() може да се използва с всякакви стойности.
linclude <stdio.h>
int power(int m, int e);
int main(void)
int m, e;
m = 2;
e = 3;
printf("%d to the %d is %d\n", m, e, power(m, e) );
printf( "4 to the 5th is %d\n", power(4, 5) ) ;
printf( ”3 to the 3rd is %d\n", power(3, 3) ) ;
return 0;
/* Параметриэирана версия на power. */
int power(int m, int e)
int temp;
temp = 1;
for( ; e> 0; e—) temp = temp * m;
return temp;
)
Чрез нараметризирането на power( ), можете да я използ-
вате за връщане на резултата от повдигането на която и да
е стойност на някаква степей, както е видно от новата
програма.
Важного е, че при специализирапата версия всяка про-
грама, използваща power(), трябва винаги да дскларира m
и е като глобални променливи. Едва след това трябва да ги
зареди с желаните стойности. за да може да използва
power(). В параметризирана форма функцията е напълно
завьршена - няма нужда от допълнителни уточнения пре-
ди да се използва.
На последно място, използването на голям брой гло-
бални променливи може да доведе до появата на грешки в
upoi рамата, поради неизвестпи и нсжелани странични
ефекти. Голям проблем при разработването на мащабни
програми е случайната промяна па стойността на дадена
променлива, защото тя е била използвана някьде другаде в
128 С - Практически самоучител
програмата. Това може да се случи в С, ако използвате
прекалено много глобалии променливи в програмите си.
3. Запомнете, че локалните променливи не запазват своите
стойности между отделяйте извиквания на функциите.
Следващата програма например няма да работа вярно:
((include <stdio.h>
int series(void);
int main(void)
int i;
for(i=G; i<10; i++) printf (' %d ", series ( ));
return 0;
}
/* Това e грешно. */
int series (void)
int total;
total = (total + 1423) % 1422;
return total;
}
Тази програма се опитва да използва функцията
series(), за да генерира редица от числа, всяко от конто се
базира на цредишното. Стойностга на value обаче няма да
се поддържа между отделяйте извиквания на функцията и
няма да може да изпълни предвидената задача.
1. Какви са основните разлики между локалните и глобални-
те променливи?
2. Напишете програма, която съдържа функция на име
soundspeed(), служеща за изчисляване броя на секундите,
необходими на звука да измине дадено разстсяние. Напи-
шете програмата по два начина: първо, soundspeed() да
бъде специализирана функция, и второ - да е параметри-
зирана. (За скорост на звука, използвайте 3760 м/сек )
Подробно описание на типовете данни, променливите и изразите 129
4.3
КОНСТАНТИТЕ ОТБЛИЗО
Константите се отнасят до твърди стойности, конто не могат да се
променят от програмата. Например числото l(J0 е константа. В
досегашните примерни програми използвахме константи без мно-
го обяспения, защото в повечето слу чаи тяхпата употреба е инту-
итивна. Дойде време обаче за тяхното официално представяне.
Целочислените константи се задават като числа без дробни
части. Например 10 и -100 са целочислени константи. Констан-
тите с плаваща запетая изискват употребата на десетична точка2,
следвана от дробната част на числото. Например 11.123 е конс-
танта с плаваща запетая. Сыцо така С дава възможност да изпол-
звате и научен запис на дробнитс числа. Константите, използва-
ши научен запис. трябва да бъдат в следната обща форма:
число Е знак експон&нта
Знакът не е задължителен. Въпреки че за яснота общата форма е
показана с интервали между отделните части, истинско число
може да се запише и без тях. Следващият пример дефинира
стойността 1234.56 каю използва научен запис
123.456Е1
Знаковите константи се ограждат с апострофи. Например 'а и
‘%’ са знакови константи. Както беше показано в някои от при-
мерите, това означава, че когато искате да присвоите знак на
променлива от тип char, трябва да използвате конструкция, по-
добна на-
ch
1 Z 1 ;
В С не сыцествува забрана, ограничаваща ви да присвоявате
стойност, зададена чрез числова константа, на знакова промен-
лива. Например ASCII кодът на ‘А’ е 65. Поради това тези две
конструкции за присвояване са еквивалентни.
char ch;
ch = 1А';
ch = 65;
Когато във вашата програма въведете числови константи,
компилаторът трябва да реши от какъв тип са те. Например дали
1000 е int, unsigned или short? Досега не се тревожихме за това.
2 Б.пр. Точкста се използва вместо десетична запетая.
130 С - Практически самоучител
защото С автоматично конвертора типа на дясната страна на
конструкция за присвояване в типа на променливата отляво. (По-
късно в тази глава ще изследваме по-подробно този процес ) То-
ва означава, че за много от случайте няма значение какво компи-
латорът смята че е I 000. Това обаче може да се окаже доста важ
но, ако използвате константа като аргумент на функция, напри-
мер при извикване на printf().
По подразбиране, С компилаторът приспособява дадена чис-
лова константа към най-малкият съвместим тип данни, който
може да я съдържа. Ако разглеждаме 16-битови цели числа, по
подразбиране 10 е int, а 100003 е long. Въпреки че 10 може да се
вмести и в char, компилаторът няма да го направи, защото това
означава преминаване отвъд границите на типа. Единствените
изключения от правилото за най-малкият тип са константите с
плаваща запетая. Те се приемат за double. На практика, вьв
всички програми, създадени от вас като начинает, разбиранията
на компилатора ще са напълно адекватни. Както обаче ще види-
те по-късно в тази книга, ще настъпи момент, когато ще трябва
точно да определите типа на исканите константи.
В случайте, когато предположения!а, направени от С за чис-
ловитс константи не съвпадат с вашите желания, ви се дава въз-
можност точно да определите техпия тип като използвате су-
фикс. При числата с плаваща запетая, ако поставите Т’ след
числото, то тс се третира като float. Ако поставите ‘L’, числото
става от тип long double. За целочислените типове, суфиксът ‘U’
се отнася за unsigned, a ‘L’ - за long.
Вероятно вече знаете, че поиякога в програмиранетс е по-лес-
но да се използва бройна система с основа S или 16, вместо с 10.
Както разбрахте в глава 2, бройната система с основа 8 се нарича
осмична и тя използва числата от 0 до 7. Системата с основа 16
се нарича шестнайсетична и използва числата от 0 до 9 плюс
буквите от ‘А’ до ‘F’, конто прсдставляват числата от 10 до 15. С
предоставя възможност целочислените константи да се задават в
шестнайсетична или осмична форма, ако така ви е по-лесно.
Шестиайсетичните константи трябва да започват с ‘Ох* (пула,
следвана от х), следвано от константата в шестнайсетична фор-
ма. Осмичните константи започват с нула. Например ОкАВ е
шестнайсетична константа, а 024 е осмична. При въвеждането на
шестнайсетични константи можете да използвате, както главни,
така и малки букви.
В допълнение към предварително дефинирапите типове дан-
ни, С поддържа още един тип константи: низове. Низът е набор
от знакове, затворен в кавички. Оше от глава 1 работите с низо-
ве, защото както printf(), така и scanf() ги използват Запомнете
един важен факт: въпреки че С позволява дефинирането на низо-
ви константи официал но той няма низов тип за данни. Вместо
Подробно описание на типовете данни, променливите и изразите 131
това, както ще видите малко по-късно в книгата, низовете се
поддържат от С като знакови масиви. (Масивите се разглеждат в
глава 5.)
За да изобразите низ чрез print f(), трябва или да го направите
част от кснтролния низ, или да го предадете като отделен аргу-
мент. Вьв втория случай се използва кодът за форматиране %s.
Следващата примерна програма отпечатва на екрана Once upon
a time:
^include <stdio.h>
int main(void)
{
printf ("Is %s %s", "Once", "upon", "a time”);
return 0;
}
В този случай всеки низ се предава на printf( ) като аргумент
и се изобразява чрез спецификатора %s.
ПРИМЕРИ
I. За ла разберете защо е важно да използвате правилен фор-
матен спецификатор в printf(), опитайте тази програма
(Тя предполага, че short int е 16-битов.) Вместо да отпеча-
та числото 42340, тя показва -23196, защото смята, че по-
лучава short int със знак. Прсблемът се състои в това, че
числото 42 340 е извън рамките на short int. За да работи
всичко правилно, трябва да използвате спецификатора
%hu.
#include <stdio.h>
int main(void)
I
printf("%hd", 42340); /* това не работи правилно */
return 0;
}
2. За да разберете защо е задължително да задавате на ком-
пилатора типа на използваните константи, опитайте след-
ващата проррама. При повечето компилатори тя няма да
произведе желания резултат. (Ако проработи вярно, това е
чиста случайност.)
132 С - Практически самоучител
#include <stdio.h>
int main(void)
printf2309);
return 0;
Тази програма казва на printГ() да очаква стойност с
плаваша запетая, но компилаторът предполага, че 2309 е
просто int. От това следва, че няма да се отпечата правил-
ната стойност. За да поправите това, трябва да зададете
2309 като 2309.0. Добавянето на дееетичната точка при-
нуждаьа компилатора да третира стойността като от тип
double
I. Как показвате на компилатора, че дадена константа с пла-
ваща запетая трябва да се предсгавя като float, а не като
double?
2. Напишете програма, която прочита и изписва long int
стойност.
3. Напишете програма, отпечатваща I like С, като използва
три отдел ни низа
ИНИЦИАЛИЗИРАНЕ НА ПРОМЕИЛИВИ
Променливите могат да получават начални стойности още при
своего деклариране. Това се нарича инициашзиране на нромен-
ливи. Общата форма на инициал изацията на променлива е пока-
зана тук:
тип име-на-променпива = константа;
Следващата конструкция например, декларира count от тип int и
й задава начэлна стойност 100.
int count = 100;
Подробно описание на типовете данни, променпивите и изразите 133
Основною предимство при използването на ннициализацията
вместо на стделна конструкция за присвояване е, че компилато-
ръг може да има възможност да промзведе по-бърз код. Освен
това така си спестявате малко допълннтелно нисане.
I -доОя пнит& прамкнвдуй могат да се инициализират само чрез
константна Локалните променливи могат да се инициализират
чрез константи, променливи или извиквания на функции, стига
всяка от тях да е валидна по време на ннициализацията. Въпреки
това, в повечето случаи както глобалните, така и локалните про-
менливи, се инициализират чрез константи.
Глобалните променливи се инициализират само всднъж - при
започване на изиълнението на програмата. Локалните променли-
ви се инициализират при всяко извикване на функцията.
Глобалните променливи, конто не са изрично инициализира-
ни, автоматично се настройват на нула. Подобните локални про-
менливи трябва да се приемат, че съдържат неизвестни стойнос-
ти. Въпреки че някои компилатори автоматично инициализират
неинициализираните локални променливи със стойност нула, не
трябва да разчитате на това.
ПРИМЕРИ
1. Следващата програма дава на променливата i начална
стойност -1 и след това я изписва.
#include <stdio.h^
int main(void)
{
int i = -1;
printf("i is initialized to %d", i);
return 0;
}
2. Когато декларирате списък с променливи, можете да ини-
циализирате една или повече от тях. Следващият фраг-
мент от код например инициализира min на 0, a max - на
100. Той не инициализира count.
int min=0, count, max=100;
3. Какго беше споменато по-рано, локалните променливи се
инициализират при всяко извикване на функция! а. Поради
тази причина следващата програма отпечатва 10 гри пъти
134 С - Практически самоучител
((include <stdio.h>
void f(void);
int main(void)
f ( ) ;
f ( ) ;
f ( ) ;
return 0.'
void f(vcid)
{
int i = 10;
printf(”%d ", i);
4. Всяка локална променлива може да се инициализира чрез
израз, който е валиден по време на декларацията на про-
менливата. Разгледайте следната примерна програма:
#include <stdio.h>
int х = 10; /* инициализиране на глобална
променлива */
int myfunc(int i);
int main(void)
/* инициализация на локална променлива чрез
глэбална */
int у = х;
/* инициализация на локална променлива чрез
друга такава и извикване на функция */
int z = myfunc(у);
printf("%d %d", у, z);
return 0;
)
int myfunc(int i)
return i/2;
)
Локалната променлива у се инициализира чрез стойностга
на глобалиата променлива х. Тьй като х се инициализира
преди извикваяето на main(), напълно валидно е нейната
Подробно описание на типовете данни, променливите и изразите 135
стойност да се използва за инициализиране на локални
промеиливи. Стойността на z се инициализира чрез извик-
ване на myfunc(), използваща за аргумент у. Тъй като у
вече е инициализирана, нейното използване като аргумент
на myfunc() е напълно коректно.
I. Напишете програма, която задана на целочислената про-
менлива i начална стойност 100 и след това използва I, за
да управлява цикъл for, изписваш числата от 100 до 1.
2. Приемеге, че този ред с код декларира глобални промен-
ливи. Верен ли е той?
int а=1, Ь=2, с=а;
3. Ако горната декларация е за локални промеиливи, щеше
ли да е вярна?
4.5
КОНВЕРТИРАНЕ НА ТИПОВЕТЕ ПРИ
ПРЕСМЯТАНЕ НА ИЗРАЗИ
За разлика от много други компютърни езици, С предоставя въз-
можност за смесваие на различии типове данни в един израз. То-
ва например е напълно зерен код:
char ch;
int i;
float f;
double outcome;
ch = 'O’;
i = 10;
f = 10.2;
outcome = ch * i / f;
С позволява смесването на различии типове данни в един израз,
защото притежава строг набор от правила за коивергиране, дик-
туващи как трябва да се преодсляъаг различията в типовете. В
тази секция ще разгледаме подробно тези правила.
136 С - Праю ически самоучител
Едната част от правилата за конвертиране на С се нарича
вътрешно повишаване. Когато в израз на С се използва char или
short int променлива, нейната стойност автоматично се “издига”
в int по време на изчисляването на израза. Поради тази причина
можете да използвате char променливи като “къси цели числа”
навсякъде, където можете да използвате int променлива. Не заб-
равяйте, че вьтрешното повишаване се прилага само по време на
изчислението на израза. Променливите не се уголсмяват физи-
чески. (Пакрагко казано, компилаторът просто използва времен-
ни копия на стойностите.)
След като е приложено автоматичного вътрешно преобразу-
ване, С компилаторът ще конвсртира всички операиди “до” типа
на най-големия операнд. Това се нарича повишаване на типове-
те и се извършва операция по операция, както е показано в
следващия алгоритъм за конвертиране на типове.
IF даден операнд е long double
THEN вторият се конвертира в long double
ELSE IF даден операнд е double
THEN вторият се конвертира в double
ELSE IF даден операнд е float
THEN вторият се конвертира в float
ELSE IF даден операнд е unsigned long
THEN вторият се конвертира в unsigned long
ELSE IF даден операнд е long
THEN вторият се конвертира в long
EL SE IF даден операнд е unsigned
THEN вторият се конвертира в unsigned
Съществува един допълнителен случай: ако един операнд е long,
а другият е unsigned int и ако стойностга на unsigned int не мо-
же да се представлява от long, и двата операнда ее преобразуват
в unsigned long.
След като веднъж тези правила за цреобразуване се прило-
жат, всяка двойка операнди ще е от един и същ тип, и резултата
от всяка операция ще е от типа на своите операнди
ПРИМЕРИ
1. В слсдващата програма i се издига до float по време на из-
числението на израза i*f. Поради това програмата отпе-
чатва 232.5.
#include <stdio.h>
int maj_n(void)
int i;
Подробно описание на типовете данни, променпивите и изразите 137
float f;
i = 10;
f = 23.25;
printf("if", i*f);
return 0;
2. Тази програма илюстрира как short int променпивите ав-
томатично се повишават в int. Конструкцията printf( ) ра-
боти коректно, въпреки че се използва модификатора %d,
защото i автоматично се издига до int, когато се извиква
prin tf()
((include <stdio.h>
int main(void)
{
short int i;
i = -10;
printf("%d", i) ;
return 0;
3. Въпреки че резултатът от даден израз ще е от най-големия
тил, правилата за преобразуване на типове се прилагат
операция по операция. Например в този израз
100.0/(10/3)
деленето на 10 на 3 води до целочислен резултат, тъй като
и двата операнда са цели числа. След това тази стойност
се издига до 3.0, за да раздели 100.0,
char ch;
short i;
unsigned long ul ;
float f;
138 С - Практически самоучител
какъв е крайният тип на следния израз:
f/ch - (i*ul)
2. Какъв е типът на горняя под-израз i*ul?
4.6
КОНВЕРТИРАНЕ НА ТИПОВЕ ПРИ
ПРИСВОЯВАНИЯ
В една конструкция за присвояване, в която тинът на дясната
страна е различен от този на лявата, типът на дясната страна се
преобразува в този на лявата. Ако типът на лявата страна е но
голям от този на дясната, този пронес не предизвиква проблеми.
Ако обаче типът на лявата страна е ло-малък от този на дясната,
меже да сс стигне до загуба на данни. Тази програма например
изобразява на екрана -24:
^include <stdio.h>
int main(void)
{
char ch;
int i;
i = 1000;
ch = i;
printf("%d", ch);
return 0;
}
Причината за това e, че само младшите осем бита на i се ко-
пира! в ch. Тъй като този вид конвертиране на тип при присвоя-
ване не е некоректен в С, няма да получите съобщение за греш-
ка. Спомнете си, че една от причините за създаване на С беше, за
да замести асемблерния език - това означава, че той трябва да
позволява всякакъв вид преобразувания на типове. В някои слу-
чай например може да искате само младшите осем бита на i, и
тогава този вид присвояване е един лесен начин за тяхното по-
лу ч а ване.
Когато в дадено присвояване се срещне преобразуване на ця-
ло число в char, или на “по-дълго цяло число” в “по-късо”, ос-
новного правило е, че ще се премахнат съответният брой старей
битове. Например в много от срсдите това означава, че ще се из-
Подробно описание на типовете данни, променливите и изразите 139
губят 8 бита при преминаване от int в char, а при преобразуване
па long в int ще се изгубят 16.
Когато се конвертира or long double в double, или of double
във float, се губи точност. Когато се преобразува дробна стой-
ност в целочислена се губи дробната част, а ако числото е прека-
лено голямо, за да се помести в набелязания тип, резултагът е
неизползнаем.
Запомнете две важни неща: първо, преобразувапето на int във
float или на float в double и т.н., няма да подобри прецизността
или точността. Този вид преобразования само ще променят
формата на представяне на стойностга. Второ, някои С компила-
тори винаги ще трети pa r char стойностите като unsigned. Други
пък - като signed. Поради това, какво ще се случи, когато про-
менлива от тип char съдържа стойност, по-голяма от 127, зависи
от имплемептацията. Ако това е важно за създавапата от вас
програма, трябва изрично да декларирате променливата или като
signed, или като unsigned.
ПРИМЕРЫ
I. Както беше споменато по-рано, при конвертиранего на
стойност с плаваща запетая в целочислена, се губи дроб-
ната част. Следващата програма демонстрира този факт.
Тя отпечатва 1234.0098 1234.
^include <stdio.h>
int main(void)
{
int i;
float f;
f = 1234.0098;
i = f; /* конвертиране в int */
printf(”%f %d", f, i);
return 0;
2. Когато се конвертира от по-голям целочислен тип п по-ма-
лък, е възможно да се генсрира неизползваема стойност.
Това се демонстрира от следващата програма. (Тази прог-
рама предполага, че short int числата са с дължина 16
бита, a long int - 32.)
#include <stdio.h>
int main(void)
140 С - Практически самоучител
short int si;
long int li;
li = 100000;
si = li; /* конвертиране в short int */
printf("%hd", si);
return 0;
Тъй като най-голямата стойност, която може да се съдър-
жа в short int е 32 767, там не може да се постави 100 000.
Компилаторъг обаче копира младшите 16 бита на 11 в si.
Така се генерира безсмислената стойност -31072.
1. Какво ще изпише на екрана тази програма?
frinclude <stdio.h>
int main(void)
<
int i;
long double Id;
Id = 10.0;
i = Id;
printf ("%d”, i) ;
2 Какво изписва на екрана тази програма?
^include <stdio.h>
int rrain(void)
float f;
f = 10 / 3;
printff);
return 0;
Подробно описание на типовете данни, променливите и изразите *141
Преобразуване на типове
Понякога може да искате да преобразувате типа на дадена про-
менлива само временно. Например бихте могли да искате да из-
ползвате стойност с плаваща запетая за едно начисление, но ня-
кьде другаде да искате да приложите оператора за остазък при
деление Тъй като онераторът за остатьк при деление може да се
използва само с цели числа, значи имате проблем. Едно от въз-
можните решения е да създадеге целочислена променлива, която
да използвате в операцията за деление, и да й присвоите своев-
ременно дробната стойност. Това обаче е малко грубо решение.
Друг начин да се справите с този проблем е да използвате преоб-
разуване на типове, предизвикващо временна промяла на типа.
Иреобразуването на тип има следната обща форма:
(тип) стойност
където тип е името на валиден тип данни в С. Например
flcat f;
f = 100.2;
/* отпечатЕане на f като int */
printf("id", (int) f);
В този случай иреобразуването на тип предизвиква конвертира-
незо на f в int.
ПРИМЕРИ
1. Както вече разбрахте в глава 1, една от библиотечните
функции на С - sqrt(), връща корен квадратен от аргу-
мента. За хедърен файл се използва МАТН.Н. Едипичният
аргумент на тази функция трябва да е от тип double. Ти-
път на резултата е също double. Следващата програма от-
печатва корен квадратен на числата от 1 до 100, като из-
ползва цикъл Гог. Освен това изписва поотделно цялата и
дробната част на всеки резултат. При лостигането на това,
програмата използва преобразуване на тип, за да конвер-
тера стойността, връща)ia от sqrt(), в int
#include <scdio h>
#include <math.h>
int main(void)
142 С - Практически самоучител
double i;
for(i=1.0; iclOl.O; i++) {
printf("The square root of %lf is %]f\n", i,
sqrt(i));
printf("Whole number part: %d ", (int)sqrt(i));
printf("Fractional part: %lf\n", sqrt(i)-
(int)sqrt(i));
printf("\n");
return 0;
)
2. He можете да преобразу вате променлива, която се намира
в лявата страна на конструкция за присвояване. Например
в С това е невалидна конструкция:
int num;
(float) num = 123.23; /* това e некоректно */
1. Напишете програма, използваща for за отпечатване на
числата от 1 до 10 през една десета. За управление на ци-
къла използвайте променлива с плаваща запетая. Прило-
жете също така преобразувапе на тип по такъв начин, че
изразът за проверка да се изчислява като пелочислен, за да
се подобри скоростта.
2. След като с оператора “%” не може да се използва стой-
ност с плаваща запетая, как бихте поправили тази конст-
рукция?
х = 123.23 % 3; /* поправите тази конструкция */
Подробно описание на типовете данни, променливите и изразите 143
Проверка на уменията, представени в главата
На този етап би грябвало да сте в състояпие да отговорите на
следните въпроси и да изпълните тези упражнения:
1. Какво представляват модификаторите на типове данни в С
и каква е тяхната функция?
2. Как дефинирате константа изрично от тип unsigned, long и
long double?
3. Покажете как се дава начална стойност С.0 на float про-
менлива, наречена balance
4. Кои са автоматичните вьтрешни повишения в С?
5. Каква е разликата между signed и unsigned цяло число?
6. Дайте една причина, поради която бихте използвали гло-
бална променлива във ваша програма.
7. Напишете програма, съдържаща функция, наречена
seriesf). Нека тази функция да генерира серия числа, ба-
зирани на следната формула:
следващо-число = (предишно-число * 1468J % 467
Задайте начална стойност 21. Използвайте глобална про-
менлива за съхранение па последната стойност между от-
дел ните извиквания. В main() демонстрирайте работоспо-
собнэстта на функцията, като я извикате десет пъти и от-
печатате резултата.
8. Какво е преобразуване на тип? Дайте пример.
Проверка на натрупаните умения
Тази част проверява доколко добре сте интегрирали материа-
ла о г тази глава с този от предишните глави.
1 Както знаете от глава 3, в един switch не може да има две
конструкции case с една и съща стойност. Поради това то-
144 С-Практически самоучител
зи switch валиден ли с, или не? Защо? (Съвет: ASCII коды
на ‘А’ с 65.)
switch(x) {
case ’А* : printf ("is an A");
break;
case 65 : printf("is the number 65");
break;
}
2. Технически погледнато, поради причини, свързани с тра-
дициите, функциите getchar() и getche() са декларирани
да връщат цели числа, а не char. Прочетеният от клавиа-
турата знак обаче се съдържа в младшим байт. Можете ли
да облените защо тази стойнсст може да се присволва на
char променливи?
3. Ще завърши ли някога цикълът в дадения фрагмент? За-
що? (Предположете, че int е с дьлжина 16 бита.)
int i
for(i=0; i<33000; i++);
Масивиинизове
Разглеждани теми в главата
5.1 Масиви и низове
5.2 Деклариране на едномерни
масиви
5.3 Използване на низове
5.4 Създаване на многомерни
масиви
5.5 Инициализиране на масиви
5.6 Построяване на масиви от
низове
146 С - Практически самоучител
В тази глава се представят масивите. В основата си, маси-
вът е списък от свързани променливи и може да бъде
доста полезен в много ситуации. Тъй като з С низовете
са просто масиви от знакове, от тази глава ще научите за
тях и за някои от иизовите функции на С.
Проверка на знанията
Преди да нродължим, трябва да сте в състояние да отговорите
на следващите въпроси и да изпълните следните упражнения,
1. Каква е разликата между локална и глобална променлива?
2. Какъв тип за данпи ще присвой на тези числа компилато-
рът па С? (Предположете 16-битови цели числа.)
а. 10
б.10000
в. 123.45
г. 123564
д.-45099
3. Напишете програма, която приема стойности от тип long,
short и double, и след това ги отпечатва на екрана.
4. Каква е ролята на преобразуването на типове?
5. Към кой if е асоциирана конструкцията else от дадения
фрагмент? Какво е общото правило?
if (i)
if (j) printf("i and j are true");
else printf("i is false");
6. Като използвате следващия фрагмент, каква е стойностга
на а, когато i е 1? А при 1 = 4?
switch(i) {
case 1: а = 1;
case 2: а = 2;
break;
case 3: а = 3;
break;
Масивиинизове 147
case 4:
case 5r a = 5;
5.1
Деклариране на едномерни
масиви
В С, едномсрният масив е списък от промеиливи, конто са от
един и същ тип и се достигат чрез общо име. Всяка отделна про-
менлива от масива се нарича елемент на масива. Масивите пре-
доставят удобен начин за работа със свързани данни
За да декларирате едномерен масив, използвайте следи ата
обща форма:
тип име-на-променлива[размер]
където тип е валиден тип за данни в С, нме-на-променлива е
името на масива, а размер задава броя на елементите на масива.
За да декларирате например целочислен масив с 20 елсмента,
наречен myarray, използвайте тази конструкция:
int myarray[20] ;
Определен елемент на масива се достига, като масивът се ин-
дексира чрез номер на елсмента. В С, всички масиви започват от
нула. Това означава, че ако искате да достигнете до първия еле-
мент на масива, за индекс трябва да използвате нула. За да ин-
дексирате един масив, задайте индекса на желания елемент в
квадратни скоби. Например този ред референцира втория еле-
мент на myarray:
myarray[1]
Запомнете, че масивите винаги започват от нула, така че индекс
1 сочи втория елемент.
За да присвоите стойност на елемент от масива, поставете ма-
сива от лявата страна на конструкция за присвояване. Следващи-
ят пример задава на първия елемент от myarray стойност 100:
myarraylO] = 1О0;
С съхранява едномернше масиви в съседни части от ламетта,
като първият елемент е на наЙ-малкия адрес. Например след из-
пълнението на този фрагмент
148 С “ Практически самоучител
int i [ 5 ] ;
int j ;
for(j=0; j<5; j++) i[j] = j;
масивът i ще изглежда така:
i[0] i[J] i[2] i[3] i[4]
0 1 2 3 4
Всеки елемент на масива може да се използва навсякъде, къ-
дето може да ее използва нормалпа променлива или константа.
Следващата програма например зарежда масива sqrs с квадрата-
те на числата от 1 до 10 и след това ги изписва на екрана.
Я include <stdio.h>
int main(void)
{
int sqrs[10];
int i;
for(i=l; i<ll; i++) sqrs(i-l) = i*i;
for(i=0; i<10; i++) printf("%d ", sqrs[ij);
return 0;
Когато искате да използвате scanf(), за да вьведсте числоьа
стойност в елемент от масив, просто поставете пред името
на масива. Например това извикване на scanf() пролита цяло
число в count[9].
scanf&count[9j);
С не пзвършва никакви проверки за границите на индексите
на маспвите. Това означава, че е възможно да преминете отвъд
края на масива. Например ако един масив, наречен а, е деклари-
ран да притежава пет елемента, компилаторът ще ви разреши да
достигнете до (несъществуващия) дссетия елемент чрез конст-
рукция, подобна на a[9J. Разбира се, опитът за достигане на несъ-
ществуващи елементи обикновено води до катастрофални после-
дний, конто често предизвикват срив на програмата. От вас, като
програмист, зависи нике га да не се преминава отвъд границите
на масива.
В С не можете да присвоите един цял масив на друг. Следва-
щият фрагмент например е грешен.
Масиви и низове 149
char al [10], а2[10];
а2 = al; /* това е грешно *7
Ако искате да копирате стойностите на всички елементи от един
масив в друг, трябва да сгорите това поотделно за всеки елемент.
ПРИМЕРЫ
1. Масивите са много полезни, когато трябва да се управлява
списък с информация. Например следващата програма че-
те дневната температура за всеки ден от месеца и след то-
ва докладва средно-мссечната температура, най-горещия и
най-студения ден.
#i.nclude <stdio.h>
int main(void)
{
int temp[31], i, rain, max, avg;
int days;
printf("How many days in the month? ");
scanf("%d", idays);
for(i=0; i<days; i++) {
printf("Enter nocnday temperature for day
%d: ", iH);
scanf("%d", &temp[i]);
)
/* намиране на средната температура*/
avg = 0;
for(i=0; i<days; i++) avg = avg + temp[i];
printf("Average temperature: ld\n", avg/days);
/* намиране на min и max */
min = 200; /* инициализиране на min and max */
max = 0;
for(i=0; Kdays; i++) {
if(min>temp[i] ) min = tempfi];
if(max<temp[i]) max = temp(i];
}
printf("Minimum temperature: %d\n", min) ;
printf("Maximum temperature; %d\n", max);
return 0;
)
150 С - Практически самоучител
2. Както беше споменато ио-рано, за да копирате съдържа-
ниет о на един масив в друг, трябва изрично да Копирате
всеки елемент поотделно. Например следващата програма
зарежда al с числата от I до 10 и след това ги копира в а2.
♦include <stdio.h>
int main(void)
{
int al[10], a2 [ 10];
int i;
for(i=l; i<ll; i++) al [1-1] - i;
for(i=0; i<10; i++) a2[i] = al[i] ;
for(i=0; К10; i++) printf( ”%d ", a2 Li]) ;
return 0;
}
3. Следващата програма e подобрена версия на машината за
кэдиране, разработена в глава 3. В тази версия, потребите-
лят въвежда съобшеяието и то се съхранява в масив от
знакове. Когато потребителят ватисне ENTER, 11ялото съ-
общение се кодира като към всяка буква се дсбавя I.
♦include <stdio.h>
♦include <conio.h>
int main(void)
{
char mess[80] ;
int i;
printf("Enter message (less than 80
characters)\n");
for(i=0; i<80; i++) {
mess[i] = getcheO;
if(mess[i]=='\r*) break;
}
printf("\n");
for(i=C; mess [i]! = '\r’; i++) printf("ic",
mess [i]+1);
return 0;
]
4 Масивите са особсно полезки, когато искате да сортирате
информация. Следващата програма например дава вьз-
Масиви и низов© 151
можност на потребителя да въведе най-много 100 числа и
след това ги сортира. Използва се така наречепият алгори-
тъм на “мехурчегс”. Този алгоритьм за сортиране не е
много ефективен, но е лесен за разбиране и съсзавяне. Ос-
новната концепция зад него, олкъдето всьщност е получил
и имело си, е повтарящи се сравнения и при необходимост
размяна на съседни елементи. Това доста прилича па ме-
хурчета зъв вола, всяко от конто си тьрси подходящо пиво.
linclude cstdio.h>
([include <stdlib.h>
int irain(void)
{
int itemflOO];
int a, b, t;
int count;
/* прочитано на числата */
printf("How many numbers? ") ;
scanf("%d", &count);
for(a=0; account; a++) scanf("%d", &item[a]);
/* сега, сортиране чрез метода на мехурчето */
for(a=l; account; +ta)
for(h=count-l; b>=a; —b) (
/* сравнение на съседни елементи */
if(item[b-l] > item[bl) {
/* размяна на елементи */
t - item[b-l];
item[b-lj = itemfb];
item[b] = t;
/* изписване на сортиранич списък */
for(t=D; tccount; tt+) printf("%d ", item[t]);
return 0;
1. Какво не e народ с този фрагмент?
([include Cstdio.h>
int main(void)
*152 С - Практически самоучител
int i, count[10];
for(i=0; i<100; i++) I
printf("Enter a number:
scanf("%d", &count[i]);
2. Напишете програма, която прочита десет числа, въведени
от потребителя, и докладва ако някои от тях съвпадат.
3. Променете показаната сортираща програма, така че да
сортира данни от тип float.
5.2
Използване на низове
Най-честата у потреба ка едномерни масиви в С са низов ете. За
разлика от много други езици за програмирапе, С не притежава
вграден тип за низ. Вместо това низът се дефинира като null-
терминиран масие от знакове. В С, null означава нула. Фактът,
че низът трябва да се герминира от нула, означава, че масивът,
деклариран да съдържа низа, трябва да е с един байт по-голям от
най-голсмия възможен низ, който ще съхранява. По този начин
се оставя място за иулата. Низовите константи се терминират ав-
томатично от компилатора.
Съществуват някслко начина за четене на низ от клавиатура-
та. Методы, използван в тази глава, разчита на друга стандартна
функция на С - gets(). Както и останалите стандартен I/O функ-
ции, gets() също изисква хедърния файл STD1O.H. За да използ-
вате gets(), трябва да я извикате посредством името на знаков
масив без никакъв индекс. Функцията gets() продължава да чете
знакове, докато бъде натиснат ENTER. Клавишът ENTER (т.е.,
нов ред) не се съхранява, а се замества с терминираща низа нула.
Следващата програма например първо прочита низ, въведен от
клавиатурата, а след това извежда неговото съдържание знак по
знак.
#include <stdio.h>
int main(void)
{
char str[BO];
int i;
Масиви и низове 153
printf("Enter a string (less than 80 chars): ”);
gets (str);
for(i=0; str[i]; i++) printf(”%c", str[i]);
return 0;
Обърнете внимание как програмата използва факта, че нулата
значи false, за да управлява цикъла за отпечатване на низа.
Съшествува един потенциален проблем с gets(), за който
трябва да внимавате. Функцията gets() не извършва проверка на
граничите, така чс е възможно потребителят да въведе повече
знакове, отколкото получавашият масив може да съдържа. Нап-
ример ако извикате getsf ) с масив с дължина 20 знака, няма ме-
ханизьм, който да ви спре от въвеждане на повече от 20 знака.
Лко въведете повече от 20 знака, масивът ще се препълни. Това
очевидно може да доведе до неприятности, включително блоки-
ране на програмата. По-късно в тали глава ще научите за алтср-
нативни начини за четене на низове, въпреки че нито един от тях
не е толкова удобен, както gets(). Засега, винаги извиквайте
gets() с масив, по-голям от очакваното въвеждане.
В предишната програма, низът, въведен от истребителя, беше
отпечатан на екрана знак по знак. Разбира се, съществува по-ле-
сен начин за изписване на низове. Това става чрез printf(), както
с показано в тази версия на програмата:
fl include <stdio.h>
int main(void)
{
char str [80];
printf("Enter a string (less than 8u chars): ");
gets (str);
printf(str); /* отпечатване на низа */
return j;
I
Спомнете си, че първият аргумент на printf() е низ. Тъй каго str
съдържа низ, тя може да се използва като първи аргумент на
printf(). Така ще се изобрази съдържанието на str
Ако искахте да отпечатате и други елсменти освен str, мо-
жехте да изобразите str чрез форматния спецификатор %s. Нап-
ример ако искате да отпечатате символ за нов ред след str, мо-
жете да използвате следното извикване на printf( ):
printf("%s\n", str);
154 С - Практически самоучител
Този метод използва форматния спецификатор %s, следван от
символ за нов ред. str се предана на printf( ) като втори
аргумент, който се напасва със спецификатора %s.
Стандартната библиотека на С предэставя много функции за
обработка на низове. Четирите най-важни са: strcpy(), strcat(),
strcmp() и strlen(). Тези функции изискват хедърния файл
STRING.Н. Нека разглеламе всяка една от тях
Функцията strcpy( ) има следната обща форма;
strcpy(e, от);
Тя копира сълържанието на от във в. Сълържанието на от не се
променя. Например този фрагмент копира низа “hello” в str и го
изобразява на екрана:
char str 180];
strcpylstr, '‘hello");
printf("%s", str);
Функцията strcpy() не извършва проверка на границите, така че
трябва да сте сигурни, че приемащият масив е достатъчно голям,
за да съдържа копирапото съдържание заедно с нулевия терми-
натор.
Функцията strcat() добавя съдържанието на един низ към
друг. Това се нарича конкатенация. Общата форма на тази фун-
кция е:
strcat(e, от);
Тя добавя съдържанието на от към съдържанието на в. Не из-
вьршва проверка на границите, така че трябва да сте сигурни, че
в е достатъчно голям, за да побере своего теку то съдържание
плюс полученото от от. Този фрагмент изобразява hello there.
char str[8Э];
strcpy(str, "hello");
strcat(str, " there");
print f(str);
Функцияга strcmp() сравняьа два низа и приема следната
обща форма:
skcmp(s7, s2);
Тази функция връща нула ако двата низа са еднакви. Ако si е по-
малък от s?, връща число, по-малко от нула, а ако si е по-голям
Масиви и низове 155
от s2, връщаното число е положително. Низовете се сравняват
лексикографски: това означава по азбучен ред. От тук следва, че
един низ е по-малък от друг, ако първият би се появил в речника
преди вторил. Един низ е пс-голлм от друг, ако би се поязил след
пего Сравнението не се базира на дължината на низовете и е
чувствително към регистъра на буквите - малкитс букви са по-
големи от главните. Следващият фрагмент отпечатва 0, защото
низовете са еднакви:
printf("%d", strcmp ("one", "one"));
Функцията strlen() воъща дължината на даден низ в брой
знакове. Нейната обща форма е:
strlen(stz);
Функцията strlen() не включва нулевия терминатор в бройката
на знаковете. Това означава, че ако strlen( ) се извика с низа
“test”, тя ще върне 4.
ПРИМЕРЫ
I. Следващата програма изисква въвежданего на два низа.
След това върху тях се демонстрира действието на чети-
рите основни пизови функции.
#include <string.h>
#include <stdio.h>
int main(void)
char strl[80], str2[80];
int i;
printf("Enter the first string: ");
gets(strl) ;
printf("Enter the second string: ");
gets(str2);
/* проверка на дълягиката на низовете */
printf ("%s is %d chars longXn", strl,
strlen(strl));
printf("%s is %d chars longXn’', str2,
strlen(str2));
/* сравняване на низовете */
i = strcmp(strl, str2);
if (!i) printf("The strings are equal.Xn");
else if(i<0) printf("%s is less than %s\n"f
strl, str2);
15В С - Практически самоучител
else printf("%s is greater than %s\n", strl,
str2);
/* конкатиниране на str2 към края на strl ако
има достатъчно място ’/
if(strlen(strl) + strlen(str2) < 80) {
strcat(strl, str2);
printf("%s\n", strl);
}
/* копиране на str2 в strl */
strcpy(strl, str2);
printf("%s %s\n", strl, str2);
return 0;
}
2. Една от честите употреби на низовете е за подцръжка на
ксшандно-базиран интерфейс. За разлика от менюто, кое-
то дава възможност на потребителя да прави избор, ко-
мандно-базирания интерфейс изобразява подканвашо сь-
общение, изчаква потребителя да въведе команда и след
това изпълнява заявката на командата. Много операцион-
ки сиетеми, като Windows или DOS например, поддържат
интерфейси с команден ред. Следващата програма е по-
добна на програмата, разработена в секция 3.1. Тя отново
дава възможност на потребителя да събира, изважда, ум-
цожава или дели, но не използва меню, а вместо това
програмата използва командно-базиран интерфейс.
#include <stdlib.h>
ttinclude <stdxo.h>
([include <string.h>
int main(void)
{
cha r command[80], temp[80];
int i, j ;
for( ; ; ) {
printf("Operation? ");
gets(command);
/* проверка дали потреСителят иска да сире */
if (Istrciup (command, "quit")) break;
printf("Enter first number: ");
gers(temp);
i = atoi(temp);
printf("Enter second number: ");
gets(temp) ;
j = atoi(temp);
Масиви и низове 157
/* сега се извършва операцията */
if(’strcmp(command, "add"))
printf("%d\n", i+j);
else if(!strcmp(command, "subtract"))
printf("%d\n", i-j);
else if(!stremp(command, "divide")) {
if<j-) printf ("%d\n", i/j);
)
else if(!stremp(command, "muitiply"))
printf("%d\n", i*j);
else printf("Unknown command. \n");
}
return 0;
)
Забележете, че този пример въвежда още една от стандар-
тните библиотечни функции на С - atoi(). Тази функция
връща целочисления еквивалент на числото, представля-
вано от нейния низов аргумент. Например atoi(“100”)
връща стойност 100. Причината scanf() да не се използва
за четене на числа е, че в този контекст тя е несъвместима
с gets(). (Ще трябва да научите повече за С преди да мо-
жете да разберете причината за тази несьвместимост.)
Функцията atoi( ) използва хедьрния файл STDLTB.H.
3. Можете да създадете низ с нулева дължина като използва-
те конструкция strcpy() по подобен начин:
strcpy(str, "");
Такъв низ се нарича ну лев низ. Той съдържа само един
елемент: нулевият терминатор.
1. Напишете програма, която чете низ и след тона го изписва
на екрана отзад напред.
2. Какво не е наред с тази програма?
#include <string.h>
((include <stdio.h>
int main(void)
char str[5];
strcpy(str, "this is a test");
printf(str);
158 С - Практически самоучител
return 0;
3. Напишете програма, която прочита множество низове и
при всяко прочитана ги конкатенира към втори низ, наре-
чен bigstr. Към края на вески низ добавяйте нов ред. Ако
потребителят напише quit, спрете четенето и изпишете
bigstr (който ще съдържа запис на всички въведени низо-
ве). Също така трябва да спрете, ако bigstr ще се препълни
при следващата конкатенация.
СЪЗДАВАНЕ НА МНОГОМЕРНИ
МАСИВИ
В допълнение към едномерните масиви, можете да създавате та-
кива с два или повече размера. Например, за да създадете един
двумерен масив I Ox 12 с цели числа, наречен count, ше използва-
те тази конструкция:
int count[10][12];
Както можете да забележите, за да добавите размер, трябва прос-
то да зададете неговата големина в квадратни скоби.
В основата си, двумерният масив е масив о г едномерни маси-
ви и е най-лесно да се представи във вид на редове и колони.
Например ако е даден целочислен масив 4x5, наречен two_d,
можете да си го представите като показания на фигура 5-1. Ако
приемем този концептуален изглед, пэследсвателносгга на дос-
тигене до елементите на двумерния масив върви по редове, отля-
во надясно. Това означава, че най-десният индекс ще се променя
най-бързо при последе вателно обхождане на масива от най-нис-
кия адрес в паметта към най-високня.
0 12 3 4
0
1
2
3
Фигура 5-1
Концептуален изглед на двумерен масив 4x5
Масиви и низо-е 159
Двумерните масиви се използват както едномерните. Напри-
мер следващата програма зарежда един масив 4x5 с произведе-
нията на индексите, а след това изобразява масива вьв формата
ред-колона.
#include <stdio.h>
int main(void)
(
int twod[4]{5];
int i, j;
for(i=0; i<4; i++)
fcr(j=0; j<5; j++)
twod[i] [j] = i*j ;
for(i=0; i<4; i++) {
for(j=0; j<5; j++)
printf("%d ", twodfi][j]) ;
printf("\n");
return 0;
}
Резултатът oi програмата изглежда така:
0 0 0 0 0
0 12 3 4
0 2 4 6 8
0 3 6 9 12
За да създадете масиви с три или повече размера, просто до-
бавете техните големини Следващата конструкция например
създава тримерен масив 10x12x8.
float values[101[12] [8] ;
Тримерният масив е всыцност масив от двумерни масиви.
Можете да създавате масиви с повече от три размера, но това
се прави рядко, защото заеманата от тях памет нараства експо-
ненциално с добавянето на всеки нов размер. Например един
100-знаков едномерен масив изисква ЮС байта памет. 100x100-
знаков масив изисква 10 000 байта, а 100x100x100 - 1 000 000
байта. Един четиримерен масив 160x100x100x100 би изисквал
100 000 000 байта памет - което е доста дори и за днешните
стандарти.
160 С - Практически самоучител
ПРИМЕР
I. Добро приложение на двумерните масиви е управленисто
на списъци от числа. Например бихте могли да използвате
този двумерен масив, за да съхранявате дневните темпера-
тури за всеки ден от годината, групирани по месеци.
float yeartemp[12][31];
По същата логика, следващата програма може да се изпол-
зва за сьхранение на броя на точкитс, отбелязани от вески
играч на баскегболен отбор, за всяко от полувремсната.
#include <stdio.h>
int main(void)
{
int bball[4][5] ;
int i, j;
for(i=0; i<4; i++)
for(j=0; j<5; j + f) {
printf("Quarter %d, player %d, ", i+1, j+1);
printf("Enter number cf points: ");
scanf("%d”, &bball[i][j]);
/* изобразяване на резултатите */
for(i=0; i<4; i++)
for(j*=0; j<5; j++) {
printf("Quarter %d, player %d, ", i+1, j+1);
printf("%d\n", bball[i] [ j ]);
}
return 0;
}
1. Напишете програма, която дефикира един примерен масив
3x3x3 и го зарежда с числата от 1 до 27.
2. Направете програмата от първото упражнение така, че да .
показва сумата от елементите.
Масиви и низове 161
5.4
ИНИЦИАЛИЗИРАНЕ НА МАСИВИ
Както всички останали видове променливи, елементите на маси-
ва също могат да получават начални стойности. Това се постига
чрез задаването на списък от стойности, присвоявани на елемен-
тите на масива. Общата форма на инициализацията на едномерен
масив е:
тип име-на-масив[размер] = {списък-със-стойности},
Списъкът-със-стсйности представлява списък от константи,
разделени със запетаи и съвместими по тип с базовия тип на ма-
сива. Псставянето на констаитите става отляво надясно - първа-
та константа те бъде поставена на първата позиция на масива,
втората константа на в гора позиция и т н. Обърнете внимание, че
след има точка и запетая. В следващия пример един пет-ел е-
ментен масив се инициализира с квадратите на числата от 1 до 5
int i[5] = {1, 4, 9, 16, 25};
Това означава, че i|(J| ще притежава стойност 1, a i[4] - 25
Знаковите масиви могат да се инициализират но два начина.
Първо, ако масивът няма да съдържа null-тсрминиран низ, прос-
то задавате всеки знак посредством списък от константи, разде-
лени със запетаи. Например така се инициализира масив а с бук-
вите ‘А’, ‘В’ и ‘С’
char а}3] = {'А', 'В', 'С };
Ако знаковият масив ще съдържа низ, можете да го инициализи-
рате като използвате низ, ограден в кавички:
cnar пате[5] = "Herb";
Обърнете внимание, че около низа няма фигурни скоби. При та-
зи форма на инициализация, те не се използват. Тъй като низове-
те в С трябва да завьршват с нула, трябва да се уверите, че дек-
ларирания от вас масив е достатьчно дьлъг, за да включва и ну-
лата. Това е причината, поради която name е с дължина 5 знака,
въпреки че “Вего” е само 4 букви. Когато използвате низова кон-
станта, компилаторът автоматично предоставя нулевия терминатор.
Многомерните масиви се инициализират по сыция начин,
както и едномерните. Например тук масивът sqr се инициализи-
ра със стойностите от 1 до 9:
int sqr[3]13j
1, 2, 3,
162 С - Практически самоучител
4, 5, 6,
7, 8, 9
Тази инициализация предизвиква sqr[0][0] да приеме стойност I,
sqrjOHl] да съдържа 2, sqr[0][2] - 3 и т.н.
Ако инициализирате едномерен масив, не е нужно да задавате
размера му - просто не поставяйте ншцо в квадратнпте скоби.
Ако не зададеге размера, компилаторът ще преброи инициализа-
торите и ше използва тази стойност каго размер на масива. Нап-
ример
int pwr [ ] = {1, 2, 4, 8, 16, 32, 64, 128};
принуждава компилатора да създаде инициализиран масив с
осем елемента. Масивите без изрично зададена дължина се на-
ричатмасиви без фиксиранразмер. Те са полезни, защото разме
рите им се настройват автоматично в зависимост от броя на ини-
циализаторите. Освен това тази инициализация помага за избяг-
ване па грешки при броенето на дълги списъци, което е особено
важно при инициализирането на низове. Например тук един ма-
сив без фиксирана дължина е използван за съхранение на под-
тикващо съобшение.
char prompt[] = "Enter your name:
Ако в някой следващ момент искате да примените съобщението
на “Enter your last name”, няма да се налога първо да преброите
знаковете и след това да промелите размера на масива. Размеры
на prompt автоматично ще се промени.
Инициализацията на масивите без фиксирана дължина не се
ограничава само до едномерните масиви. При многомерните ма-
сиви обаче трябва да зададеге всичко без най-лявото измерение.
Това се прави, за да дадете възможност на С правилно да индек-
сира масива. По този начин можете да построявате таблици с
променлива дължина като компилаторът автоматично ще задегд
достагьчно памет. Например декларацията на sqr като масив без
фиксирана дължина е:
int sqr[ ] Г 3 ]
1, 2, 3,
4, 5, 6,
7, 8, 9
};
Предимството на тази декларация пред версията с точен размер
е, че таблиците могат да се удължават или скъсяват без да се
применят размерите на масива
Масиви и низове 163
ПРИМЕРИ
1. Често срсщана употреба на инициализираи масив е при
създавакето на таблица за справки. В следващата програма
например един двумерен масив 5x2, се инициализира така,
че първият елемент от всеки ред е номерът на файловия
сървър от мрежа, а вторият елемент съдържа броя на пот'
ребителите, свързани с този сървър. Програмата дава вьз-
можност на потребителя да въвежда номера на сървъра,
след това прави справка с таблицата и докладва броя на
нотребителите.
#include <stdio.h>
int main(void)
{
int ServerUsers[5][2] = {
1, 14,
2, 28,
3, 19,
4, 8,
5, 15
};
int server;
int i;
printf("Enter the server number: ");
scanf("%d", &server) ;
/* справка в таблицата */
for(i=0; i<5; i++)
if(server == ServerUsers[i] [0] ) (
printf("There are %d users on server %d.\n",
ServerUsers[i][1], server);
break;
}
/* докпадване за грешка, ако сървърът не бьде
открит */
if(i=«=5) printf("Server not listed.\n");
return 0;
}
2. Въпреки че даден масив може да е със зададени начални
стойности, неговото съдържание може да се променя.
Следващата програма например отпечатва на екрана hello.
#include <stdio.h>
ttinclude <string.h>
164 С - Практически самоучител
int main(void)
{
char str[80] = "I like C";
strcpy(str, "hello");
printf(str);
return 0;
}
Както илюстрира тази програма, инициализацията не фик-
сира по никакъз начин съдържанието на масива.
1. Верен ли е този фрагмент?
int balance[] = 10.0, 122.23, 1.00,0;
2. Верен ли е този фрагмент?
ttinclude <stdio.h>
#include <string.h>
int main(void)
char name[1 = "Tom",
strcpy(name, "Tom Brazwell");
3. Напишете програма, инициализираща един масив 10x3,
така че първият елемент на всеки ред да съдържа число,
вторият елемент да съдържа неговия квадрат, а третият -
неговага трета степей. Заночнете с 1 и завършете с 10.
Например първите няколко реда ще изглеждаг така:
1,1,1
2,4, 8
3, 9, 27
4, 16, 64
След това питайте потребителя за някоя трета степей, нап-
равете справка с таблицата и докладвайте кореиа на ку-
бичната стойност и съответния квадрат. Използвайте ма-
Масиви и низове 165
сив без фиксирана дължина, така че размерът на таблицата
да е лесно применим.
5.5
ПОСТРОЯВАНЕ НА МАСИВИ ОТ
НИЗОВЕ
Масивите от низове, често наричани назови таблица, са много
популярни в програмирането на С. Една такава таблица се сьз-
дава както всеки друг двумерен масив. Все пак начиньт, по кой-
то си я представяте, ще е малко по различен от реалността. Нап-
ример тук е дадена една малка низова таблица. Какво миелите,
че дефинира тя?
char names [10] [40];
Тази конструкция задава таблица, съдържаща 10 низа, всеки от
конто с максимална дължина 40 знака (включително пулевия
терминатор). За да достигнете да низ от тази таблица, задайте
само най-левия индекс. Например, за да прсчетете низ от клави-
атурата в третия низ от names, използвайте тази конструкция:
gets(names[2 ] ) ;
По сыцата логика, за да отпечатате първия низ, използвайте тази
printf() конструкция:
printf(names[0] ) ;
Дадената по-долу декларация създава тримерна таблица с три
списъка от низове. Всеки списък е с дължина пег низа, а всеки
низ може да съдържа 80 знака.
char animals[3][5][60];
За да достигнете до определен низ от тази таблица, трябва да за-
дадете двата най-леви индекса. Например, за да достигнете до
втория низ от третия списък, задайте animals[2][l].
ПРИМЕРИ
1. Следващата програма дава възможпост за въвеждане на
десет низа и след това предоставя възможността за показ-
ването им един по един в произволен ред. За да спрете
програмата, въведете отрицателно число.
166 С - Практически самоучител
linclude <stdio,b>
int main(void)
{
char text[10] [80];
int i;
for(i=0; i<10; i++) {
printf("%d; ", i+1);
gets (text[i]);
do {
printf("Enter number of string (1-10) : ");
scanf (,r%d", &i) ;
i—; /* промяла на стойността, за да съвпада
с индекса на масива */
if(i>=0 Л& i<10) printf("%s\n", text[i]);
} while(i>=0);
return 0;
2. Можете да инициализирате низова таблица както всеки
друг вид масив. Следващата програма например използва
инициализирана таблица с низове, за да превежда от немс-
ки на английски. Забележете, че около списъка са необхо-
дими фигурни скоби. Те не са слагат единствепо когато се
инициализира едйн-единствен низ.
/* А.нглийско-Немски преводач. */
[[include <stdio.h>
[[include <string.h>
char words[][2][40] = {
"dog", "Hund",
"no", "nein",
"year", "Jahr",
"child", "Kind",
"I", "Ich",
"drive", "fahren",
"house", "Haus",
"to", "zu",
If I» If »l
int main(void)
{
char english[80];
int i;
Масиви и низове 167
printf("Enter English word:
gets(english);
/* търсене на думата */
i = 0;
/* търсене до срещане на празен низ */
while (strcmp(words [i] [0] , ) {
if(!strcmp(english, words[iJ[0])) {
printf("German translation: %s",
words[ij [1]);
break;
}
i++;
}
if(!strcmp(words Li] [0], )
printf ("Not in dictior.aryXn") ;
return 0;
3. Можете да достигате отделимте знакове, изграждаши да-
ден низ от низэва таблица, като използвате най-десния ин-
декс. Следващата програма например отпечатва низовете
от таблииата знак по знак.
#inciudo <stdio.h>
int main(void)
{
char text [][801 = {
"When", "in”, "the",
"course", "of", "human",
"events", ""
) ;
int i, j;
/* сега се отпечатват */
for(i=C; text[i][01; i++) {
for(j=0; text[i][j]; j++)
printf("%c", text(iHj));
printf(" ");
return 0;
168 С - Практически самоучител
1. Напишете програма, създаваща низова таблица. Тази таб-
лица трябва да съдържа английските думи за числата от
нула до девет. Използвайте двумерния масив, за да дадете
възможност на потребителя да въведе цифра (като знак), а
след това вашата програма да изпише словесния й еквива-
лент. (Съвет: за да получите индекс от таблицата, извадете
‘О’ от въведения знак.)
Проверка на уменията, представени в главата
В този момент трябва да сте в състояние да отговорите на
следващите въпроси и да изпълните следните упражнения:
1. Какво е масив?
2. Ако е даден масивът
int count[10];
дали следващата конструкция ще предизвика грешка?
for(i=0; i<20; i++) count[i] = i;
3. Напишете програма, която изисква от потребителя да въ-
веде 20 числа и след това намира и показва най-често сре-
щаното сред тях.
4. Покажете как се инициализира масив от цели числа, наре-
чен items, със стойности от 1 до 10.
5. Напишете програма, която чете низове от клавиатурата,
докато потребителя напише quit.
6. Напишете програма, изпълняваща ролята на електронен
речник. Ако потребителях въведе дума от речника, прог-
рамата показва нейното значение. Използвайте тримерен
char масив за съхранение на думите и техните значения.
Масиви и низове 169
Проверка на натрупаните умения
Тази част проверява доколко добре сте интегрирали материа-
ла от тази глава с този от предишните.
I. Напишете програма, която чете низове от клавиатурата.
Ако никой от низовете е no-къс от 80 знака, допълнете го с
точки. Отпечатайте низа, за да се уверите, че сте оразме-
рили масива правилно.
2. Напишете програма, която прочита низ и след това го ко-
дира, като започвайки отляво и редува взимане но един
знак от всеки край. Програмата трябва да спре, когато дос-
тигне средата на низа. Например низът “Hi there” ще изг-
лежда като “Heir eth”.
3. Напишете програма, изброяваща броя на интервалите, за-
летайте и точките от даден низ. Използвайте конструкция
switch, за да категоризиратс знаковете.
4. Какво не е наред с този фрагмент?
char str[80];
str = getchar();
5. Напишете програма за компютърен вариант на играта “Бе-
сеница”. В тази игра на играна се показва дължината на
определена дума (използват се подчертаващи тирета) и .
той се опитва да отгатне думата, като въвежда букви. При
всяко въвеждане на нова буква се проверява дали думата
съдържа тази буква. Ако тсва е така, съотвстната буква се
показва. Вройте буквите, въведени до завършването на
думата. За да е по-просто, нека играчът да печели, ако по-
знае думата с въвеждане на 15 или по-малко букви. За цел-
та на това упражнение използвайте думата “concatenation”.
6
Използване на
указатели
Раэглеждани теми в главата
6.1 Основните понятия за
указателите
6.2 Ограничения за изразите с
указатели
6.3 Използване на указатели с
масиви
6.4 Използване на указатели към
низози константи
6.5 Създаване на масиви от
указатели
6.6 Множествената индирекгност
6.7 Използване на указатели като
параметри
172 С - Практически самоучител
Тази глава разглежда една от най-важните, а понякога и
една от най-трудните, възможности на С: указателям.
Указателям най общо казано, е адресът на даден обект.
Едка от причините указателите да са толкова важни е, че
голяма част от силата на езика С произлиза от уникалння начин,
по който те се импле.ментират. В тази глава ще научите за специ-
алните указателни оператори, указателната аритмегика и каква е
връзката между масивиге и указателите. Освен това ще се залоз-
наете с начините за използване на указатели като параметри на
функции.
Проверка на знанията
Преди да продължим, трябва да сте в състояние да отговорите
на следващите въпрсси и да изпълните следните упражнения.
I. Напишете програма, въвеждаща десет цели числа в масив.
Нека след това програмата изписва ноотделно сумата на
четннте и нечетните числа.
2. Напишете програма, симулираща включването към отда-
лечена система. Системата може да се достига само ако
потребителят знае паролата, която в този случай е
“Tristan”. Дайте на потребителя правого на три опита за
въвеждане на правилната парола. Ако той успес, просто
напишете Log-on Successful и излезге. Ако потребителят
не успес да вьвсде правилната парола в продвижение на
три пъти, отпечатайте Access Denied и излезте.
3. Какво не е наред в този фрагмент?
char name[10] = "Thomas Jefferson";
4. Какво e нулев низ?
5. Какво прави strcpy()? Какво прави strcmp()?
6. Напишеле програма, създаваща низсва таблица, състояша
се от имена и телефонии номера. Инициализирайте табли-
цата с имена на ваши познати и техните телефонии номе-
ра. Нека след това програмата да изисква въвеждането на
име и да отпечатва съответния телефонен номер. С други
думи, създайте компютърен вариант на телефонен указател.
Използване на указатели 173
6.1
Основните ПОНЯТИЯ ЗА
УКАЗАТЕЛИТЕ
Указателя г е променлива, съдържаща адреса в паметта на друг
обект. Например ако променливата р съдържа адреса на друга про-
менлива, наречена q, се казва, че р сочи q. Поради това ако в иамст-
та q е разположена на адрес 100, тогава р ще има стойност 100.
За да декларирате променлива като указагел, използвайте та-
зи обща форма:
тип *име-на-променлиеа;
В този случай, тин е базовият тип на указателя. Базовият тип
определя типа на обекта, сочен от указателя. Обърнете внима-
ние, че името на променливата се предшества от звездичка. По
този начин компютьрьт знае, че декларирате указагел. Следва-
щата конструкция например създава указагел към цяло число:
int *р;
С съдържа два специални указателни оператора: * и &. Опе-
раторът & връща адреса на променливата, стояща сдед него.
Онерагорът * връща стойността, съхранявана на адреса, който
следва. (Указателният оператор * няма нищо общо с оператора
за умножение, използващ същият символ.) Например, изслед-
вайте тази кратка програма:
#include <stdio.h>
int main(void)
{
int *p, q;
q = 199; /* присвояване 199 на q */
p - &q; /* на p се присвоява адреса на q */
printf("id", *p); /* изсбразяване на стойността на q
посредством указател */
return 0;
Тази програма отнечатва на екрана 199. Нека да видим защо.
Първо, редът
int *р, q;
174 С - Практически самоучител
дефинира две промеиливи: р, декларирана като указател към ця-
ло число, и q, която е цяло число. След това на q се присвоява
стойност 199. На следващия ред на р се присвоява адреса на q.
Операторьт & може да се чете като “адреса на”. Его защо този
ред може да се прочете като “на р се присвоява адресът на q”
Накрая, стойността се отпечатва посредством оператора *, при-
ложен върху р. Този оператор може да се чете като “на адрес”.
Поради това конструкцията printff ) може да се прочете като
“изписване на стойността на адрес q”, която е 199.
Когато стойностга на една промен лиг а се сочи чрез указател,
този процес се нарича индиректност. .
Възмсжно е операторьт * да се използва в лявата част на кон-
струкция за присвояване. В гакьв случай указателят се използва
за присвояване на нова стойност на дадена променлива. Напри-
мер следващата програма индиректно присвоява стойност на q
като използва указателя р:
flinclude <stdic.h>
int main(void)
{
int *p, q;
p = &q; /* получаване па адреса на q */
*р = 399; /* присвояване на стойност на q чрез
указател *7
printf("q's value is %d", q) ;
return 0;
)
В показаните току-що прости программ няма истинска причина
да се използва указател. С напредването па изучаването на С
обаче ще разберете защо указателите са важни. Например указа-
телите се използват за поддържане на свързани списъци и дво-
ични дървета.
Базовият тип на указателите е много важен. Въпреки че С
позволява всеки тип указател да сочи където и да е в паметта, ба
зовият тип определи как ще се обработва соченият обект. За да
разберете Еажността на този факт, разгледайте следния фрагмент:
int q;
double *fp;
fp = &q;
/* какво извършва този ред? */
♦fp = ЮС.23;
Използване на указатели 175
Въпреки че е синтактически верен, този фрагмент е грешен. На
указаз елят fp се присвоява адресът на цяло число. След това този
адрес се използва ст лявата страна на конструкция за присвоява-
не, за да му се присвой дробна стойност. Типът int обаче обик-
новено е но къс от double и тази конструкция за присвояване ще
предизвика препокриване на паметта, намираща се в съседство с
q. Например в среда с 2-байтови цели числа и 8 байтов double,
конструкцията за присвояване ще използва задслените за q 2
байта, както и 6 съседни байта. Това немин) емо ще предизвика
грешка.
По принцип С компилаторът използва базсвия тип, за да оп-
редели колко байта принадлежат на обекта, сочен от указателя.
По такъв начин С знае колко байга трябва да копира, когато се
прави индирект но присвояване, или колко байта трябва да срав-
ни при индиректно сравнение. Поради тази причина е много
важно винаги да използвате правилен базов тип за указателите.
С изключение на специалните случаи, никога не използвайте
указател от един тип, за да сочите обект от друг тип.
Ако се опитате да използвате указател преди да му е присво-
ен адрес на променлива, програмата ви вероятно ще блокира. За-
помпете, че декларирането на указателна променлива просто
създава променлива, имаща възможност да съдържа адрес от па-
метта. На нея не й се лава никаква смислена начална стойност.
Его защо следващият фрагмент е грешен.
int main(void)
int *p;
*p = 10; /* грешно - p не сочи към нищо */
Както отбелязва коментарът, указа 'едят р не сочи към никой
познат обект. От това следва, че опитът за индиректно присвоя-
ване на стойност на р е безсмислен и опасен.
Според декларацията на указателите в С, указател със стой-
ност null (нула), се приема за неизползваем и несочещ към нищо
По конвенция в С, null се приема за невалиден адрес от паметга
Все пак обаче компилаторът ще зи ладе възможност да използ-
вате нулез указател, обикновено с катастрофални последний.
ПРИМЕРИ
1. За да се демонстрира графично как работи инидиректност-
та, приемете следните декларации:
int + р. q;
176 С - Практически самоучител
След това приемете. че за q е заделена памет на адрес 102,
аре точно преди нея, на адрес 100. След тази конструкция:
р = &q;
указателя; р съдържа стойността 102. Псради това след
това нрисвояване, паметта изглежда така
Адрес Съдържание
100 102
102 неизвестно ◄-
р сочи към q
След изпълнението на конструкцията
*р = 1000;
паметта изглежда така:
Адрес Съдържание
100 102
102 1000
р сочи към q
Запомнете, че стойността на р няма нищо общо с гази на q.
Тя просто прсдставлява адреса на q, чрез който може да се
прилага индиректен оператор.
2. За да илюстрираме защо е задължително базоьият тип на
даден указагел да съвлада с този на сочения обект, изп-
робвайте тази грешна, но безопасна програма. (Някои
компилатори може би ще генерират прсдупредително съ-
общение, но пито един няма да спре компилирането със
съобшение за грешка.)
/* Тази програма е грешна, но безобидна. */
#include <stdio.h>
int ma in(void)
{
int *p;
double q, temp.-
temp = 1234.34;
Използване на указатели 177
р = &terap; /* опит за присвояване на стойност
на а, като се */
q = *р; /* използва индиректност през
целочислен указател */
printf (q) ; /* това няма да отпечата
1234.34 */
return 0;
}
Въпреки че р сочи temp, съдържаща иаистина стойността
1234.34, конструкцията:
q = *р;
не успява да копира числото, защото ще бъдаг прехвърле-
ни само 2 байта (предполага се, че целите числа са 2 бай-
та). Тъй като р е целочислен указател, той не може да се
използва за прехвърляне на 8-байтова стойност (приема се
8-байтов double).
1 Какво е указател?
2 Кои са указателните оператори и какво правят те?
3 . Защо е важен базовият тип на указателя?
4 Напишете програма с цикъл for, която брои от (J до 9 и из-
писва числата на екрана. Отпечатайте числата като изпол-
звате указател.
6.2
Ограничения за изразите с
указатели
По принцип указателите могат да се използват както всички ос-
танали промеиливи. Трябва обаче да спазвате някои правила и
ограничения.
В допълнение към операторите * и & съищствуват още само
чстири оператора, конто могат да се прилагат върху указателни
промеиливи: аритметичните оператори +, -Н-, - и - -. Оевен това
178 С - Практически самоучител
можете да събирате и изваждате само цслочислени стойности:
Не можете например да прибавите дробно число към указател.
Указателната аритметика се различава от “нормалната” по
едно много важно нещо: тя се извършва в зависимост от базовия
тип на указателя. При всяко инкрементиране на указател, той ще
сочи към следващия елемент, дефиниран от неговия базов тип,
намиращ ее след сочения в момента. Например предположете, че
целочисленият указател р съдържа адрес 200. След изпълнение-
то на конструкцията
Р++;
р ще съдържа стойността 202, при положение че int е дълъг
2 байта. По същата логика, ако р е float указател, (предполага се
4-байтов float), тогава резултатната стойност, съдържана в р,
щеше да бъде 204.
Едияствената указателна аритметика, която се държи като
“нормална”, е при използването на char указатели. Тъй като зна-
ковете са дълги само един байт, ипкрементирането увеличила
стойността на указателя с единица, а декремента рането я нама-
лява с единица.
Можете да добавите или изваждате всяка целочислена стой-
ност от указател. Например следващият фрагмент е валиден:
int *р
р = р + 200;
Тази конструкция кара р да сочи двестното цяло число след со-
ченото от р досега.
Освен добавяне и изваждане па цяло число не можете да из-
вършвате никакъв друг вид аритметични операции - не можете
да умпожавате, делите или получавате остатък от делене на ука-
зател. Можете обаче да изваждате един указател от друг, за да
разберете броя на елементите между двата.
Имате възможност да прилагате операторите за инкременти-
ране и декрементиране както върху самия указател, така и върху
сочения от неге обект. Трябва обаче да сте много внимателни, ко-
гато се опитвате да променяте обскта, сочен от указател. Напри-
мер приемете, че р сочи към цяло число, съдържашо стойност I.
Какво миелите ще направи следната конструкция?
*р++;
Използване на указатели 179
Точно обратного на това, което си миелите - тази конструкция
първо инкрементира р и след това получава стойността от новия
адрес. За да ипкрементирате соченото от указателя число, трябва
да използвате такава форма:
(*Р)++;
Скобите предизвикват инкрементирането на стойността, сочена
от р.
Можете да сравнявате два указателя посредством оператори
за сравнение. Сравнснието на указатели обаче има смисъл само
ако двата указателя са евързани помежду си - ако например и
двата сочат към един и същ обект. (Скоро ще видите пример за
сравнение на указатели.) Можете сыцо така да сравнявате указа-
тел с нулата, за да видите дали е нулев.
В този момент може би се чудите какъв е смисълът от указа-
телната аритметика, но скоро ще видите, че тя е един от най-
важните компонента на езика С.
ПРИМЕРИ
1. Можете да използвате printf( ), за да изписвате адреса от
паметта, съдържан от даден указател, като използвате
форматния спецификатор %р. Тази възможност на
printf() ни позволява да илюстрираме няколко аспекта от
указателната аритметика Следващата програма например
показва как цялата указателна аритметика е евързана с ба-
зовия тип на указа геля.
ttinclude <stdio.h>
inc main(void)
{
char *cp, ch;
int *ip, i;
float *fp, f;
double *dp, d;
cp = &ch;
ip = Si;
fp = &f;
dp = &d;
/* отпечатване на текушите стойности */
printf("%р %р %р %p\n", ср, ip, fp, dp);
/* сега се увеличават с единица */
ср++;
ip++;
180 С - Практически самоучител
fp++;
dp++;
/* отпечатване на новите стойности */
printf("^р %р %р\п", ср, ip, fp, dp);
return 0;
}
Въпреки че стойностите, съдьржани в указателните про-
менливи от тази програма ще са много различии при раз-
личните компилатори и дори при различните версии па
един и същ компилатор, ще видите, че адресът, сочен от
ch ще се инкрементира с един байт. Останалите ще се ин-
крементират с броя байтове на техния базов тип. Напри-
мер в една 16-битова среда, това обикновено ще е 2 байта
за int, 4 за float и 8 за double
2. Следващата програма илюстрира нуждата от скоби, когато
искате да инкрементирате обекта, сочен от указателя, а не
самия указател.
^include <stdio.h>
int main(void)
{
int *p, q?
P = &q;
q = 1;
printf("%p ", p);
*рт+; /* това няма да инкрементира q */
printf ("%d %p", q, p);
return 0;
След изпълнението на тази програма q все още ще прите-
жава стойност 1, но р ще е ипкрементиран. Ако обаче,
програмата се напише така:
#include <stdio.h>
int main(void)
(
int *p, q;
p = aq;
q = 1;
Изпслзване на указатели 181
printf("%р ", р);
(*р)++; /* сега q е инкрементиран, аре
непременен */
printf("%d %р", q, р);
return 0;
}
q се инкрементира на 2, а р остава непременен.
1. Какво не е народ в този фрагмент?
int *р, i;
р = &i;
р = р * 8;
2. Можете ли да добавите число с плаваща запетая към ука-
зател?
3. Приемете, че р е float указател, който в момента сочи към
адрес 100, и че типът float е с дължина 4 байта, Каква ще е
стойността на р след изпълнението на този фрагмент?
р = р + 2;
6.3
Използване на указатели с
масиви
В С, указателите и масивите са тясно евързани Всъщност често
те са взаимозаменяеми. Тази връзка прави тяхната имплемента-
ция уникална и много мощна,
Ако използвате само името на масив, без индекс след пего,
вие генерирате указател към началото на масива. Това е причи-
ната да не се използват индекси, когато се чете низ чрез gets()
например. Онова, което се предава на gets( ), не е масив, а указа-
тел. Всъщност в С не можете да предавате масив на функция;
можете единствено да предавате указател към този масив Tmw
182 С - Практически самоучител
същестлен момент не беше спомеяат в нредишната глава за ма-
сивите, защото все още не знаехте нищо за указателите, но той е
жизненоважен за разбирането на езика С. Функцията gets() из-
ползва указател, за да зареди сочения масив със знакове, въведе-
ни от клавиатурата. По-късно ще видите как се прави това.
След като име на масив без индекс е указател към началото
на масива, вероятно сте стигнали до заключението, че можете да
присвоите тази стойност на друг указател и да използвате указа-
телна аритметика, за да обхождате масива. Всъщност точно това
се прави. Разгледайте следната програма:
^include <stdio.h>
int main(void)
(
int a[J0] = (10, 20, 30, 40, 50, 60, 70, 80, 90, 100);
int *p;
p = a; /* на p се присвоява адреса на началото на а */
/* така се отпечатьат първият, вторият и третият
елемент на а */
printf("%d %d %d\n’-', *p, *(p+l), *(p+2));
/* това извъргава сыцото, но използва а */
printf("%d %d", а[0], а(1], а[2]);
return 0;
)
В този случай и двсте конструкции printf() изписват едно и съ-
що нещо. Скобите в изразите, като *(р + 2), са необходима за-
щото операторьт * е с по-висок приоритет от +.
Сега би трябвало напълно да разбере защо указателната
аритметика се извършва относно базовия тип - тя дава възмож-
ност на масивите и указателите да се свързват помежду си.
За да използвате указател за обхождане на многомерен масив,
трябва рьчно да правите това, което компилаторът извършва ав-
томатично. Например в този масив
float balance[10][5];
всеки ред съдържа пет елемента. Поради това, за да достигнете
до balance[3][1] посредством указател, трябва да използвате
фрагмент, подобен на този:
float *р;
р = (float *) balance;
*(р + (3*5) + 1)
Използване на указатели 183
За да достигнете до желания елемент, трябва да умножите номе-
ра на реда по броя на елемент ите па ред и след това да прибавите
номера на елемента от реда. По принцип при многомерните ма-
сиви е по-лесно да се използва индексиране, вместо указателна
аритметика.
В предишния пример преобразуването на balance във float *
беше наистина необходимо. Тъй като масивът се индексира ръч-
но, указателната аритметика трябва да се отнася за float указа-
тел. Типът на указателя, генериран от balance обаче, е за двуме-
рен float масив, Поради това се налага нуждата от преобразуване
Указателите и масивите са евързани не само с факта, че чрез
използването на указателна аритметика можете да достигате
елсменти от масива. Може би щс се изнепадате да научите за
възможностга да индексирате указател, все едно че е масив.
Следващата програма например е напълно валидна:
#include <stdio.h>
int main(void)
I
char str[] = "Pointers are fun";
char *p;
int i;
p = str;
/* обхождане до срещане на null */
for(i=0; p[i]; i++)
printf("%c", p[i]);
return 0;
Трябва добре да запомните едно: указател се индексира само ако
сочи към масив Следващият фрагмент е синтактически верен,
но иначе е грешен; ако се опитате да го изпълните, вероятно ще
блокирате вашия компютър.
char *р, ch;
int i;
р = Sch;
for(i=0; i<10; i++) p[i] = 'A'+i; /* грешно */
Т ъй като променливата ch не е масив, тя не може смислено да се
индексира.
Въпреки че можете да индексирате указател, все едно че е
масив, рядко ше правите това, защото обикновено указателната
аритметика е по-удобпа. Освен това в някои случаи С компила-
184 С - Практически самоучител
торът може да генерира по-бърз, изпълним код за израз с указа-
тели, отколкото за еквпвалснтен израз, използващ масиви.
Тъй като име на масив без индекс е указател към началото на
масива, ако искате, можете да използвате указателна аритметика,
за да дсстигате до елементите на масива. Например следващата
програма е напълно валидна и отпечатва на екрана с:
((include <scdio.h>
int main(void)
{
char str[80];
*(str+3) = 'c';
printf("%c", *(str+3));
return 0;
}
He можете обаче да променяге стойността на указателя, гене-
рИрана от използванети на име на масив. Например ако разглеж-
даме предишната програма, това е невалидна конструкция:
str++;
Указателят, генериран от str, трябва да се смята за константа,
която винаги сочи към начало го на масива. Поради това е нева-
лидно той да се прсменя и компилаторът ще докладва за грешка.
ПРИМЕРИ
I. Две от библиотечните функции на С - toupper() и
tolower( ) се извикват с char аргумента. В случая на toup-
рег(), ако знакът е малка буква, се връща съответната
главна; в останалите случаи знакът се връща непременен.
При tolower(), ако знакът е главна буква, се връща съот-
ветната малка; в останалите случаи знакът се връща неп-
ременен. Тези функции използват хедърния файл
CTY РЕ.П. Следващата програма изисква възеждането на
низ от потребителя и след това го отпечатва - първо, само
с главни букви, а след това само с малки Тази версия из-
ползва индексиране на масива, за да достигне до знаковете
от низа, така че те да могат да се конвертират в съотвегния
регистър.
#include <ctype.h>
((include <stdio.h>
int main(void)
Използване на указатели 185
char str[80];
int i;
printf("Enter a string: ");
gets (str);
for(i=0; str[i]j i++)
str[i] = toupper(str[i]);
printf("%s\n", str); /* низ с главни букви */
for(i=0; str[i]; i++)
str[i] - tolower(str[i]);
printf("%s\n", str); /* низ с малки букви */
return 0;
}
По-долу e показана сьщата програма, само че този път за
достъп до низа се използва указател, Това е начинът, по
който ще видите тази програма да се пище от професио-
пални С програмисти, защото инкрементирането на указа-
тел често е по-бързо от индексирането па масив.
#include <ctype.h>
#include <stdio.h>
int main(void)
(
char str[80], *p;
printf("Enter a string: ");
gets (str);
p = з t r;
while (*p) {
*p = toupper(*p);
P++;
}
printf ("%s\n", str); /* низ с главни букви */
p = str; /* инициализиране на p */
while(*p) {
*p = tolower(*p);
p++ ;
)
printf("%s\n", str); /* низ с малки букви */
186 С - Практически самоучител
return 0;
}
Прели да оставим този пример, едно малко отклонение.
Коды
while(*р) {
*р = toupper(*p);
Р++;
)
обикновено ще се напиие от опитните програмнсти така:
whi1е(*р)
*р++ = toupperPp);
Тъй като е след р, тгьрво се променя стойността, сочена
от р, и след това р се инкрементира да сочи следващия
елемент. Понеже това е начины, по който често се пише С
коды, от време на време в книгата ще използваме компак-
тната версия, когато сметнем за уместно.
2. Заиомнете, че въпреки че повечето от примерите инкре-
ментираха указатели, винаги можете и да декремептирате
указател. Следващата програма например използва указа-
тел, за да копира съдържанието на един низ в друг, но в
обратен ред.
#include <stdio.h>
ffinclude <string.h>
int main(void)
char strl[] = "Pointers are fun to use";
char str2-80], *pl, *p2;
/* p сочи края на strl */
Pl = strl + strlen(strl) - 1;
P2 = Str2;
while(pl >= strl)
*p2++ = *pl—;
/* null терминира str2 */
*p2 = ’\0’;
printf("%s %s", strl, str2);
return C;
}
Използване на указатели 187
Тази програма работа като настроила pl да сочи към края
на strl, а р2 към началото на str2. След това копира съ-
държанието на strl в str2 в обратен ред. Обърнете внима-
ние на сравнението на указатели в иикъла while. То се из-
ползва за спиране на копирането, когато се достиг не до
началото на strl.
Също така забележете употребата на компактните
форми *р2++ и *р!~. Цикълът е еквивалентен на този:
whilefpl >= strl) (
*р2 = *pl;
Р1 —;
р2++;
Трябва отново да споменем, че е много важно да свикнете
с компактните форми на указателните операции от този
тип.
1. Верен ли е този фрагмент?
int count[10];
count - count + 2;
2. Какво стойност ще покаже този фрагмент?
int temp[5] = (10, 19, 23, 8, 9};
int *р,-
р = temp;
printf("%d", *(p+3));
3. Напишете програма, която чете низ и търси първия интер-
вал. Ако намери такъв, отпечатва остатъка от низа.
188 С - Практически самоучител
6.4
Използване на указатели към
низови константи
Както вече знаете, С дава възможност в програмите да се изпол-
зват низови константи, оградени с кавички. Когато компилато-
рът срещне такън низ, той го съхранява в низовата таблица на
програмата и генерира указател към него. Поради тази причина
следващата програма е вярна и отпечатва на екрана one two
three.
ttinclude <stdio.h>
int main(void)
char *p;
p = "one two three";
printf(p);
return 0;
Нека да видим как работи тази програма. Първо, р се декла-
рира като указател към char. Това означава, че може да сочи към
масив ст знакове. Когато компилаторът компилира реда
р = "one two three";
той съхранява низа в низовата таблица на програмата и присвоя-
ва на р адреса на низа в таблицата. Поради това, когато в конст-
рукцията printf( ) използваме р, на екрана се отпечатва one two
three.
Тази програма може да се напише по-ефективно, както е по-
казано тук:
#include <stdio.h>
int main(void)
char *p = "one two three";
printf(p);
return 0;
В този случай p се инициализира да сочи към низ.
Използване на указатели 189
ПРИМЕРИ
I. Тази програма чете низове, докато въвсдсте stop:
#include <stdio.h>
tfinclude <string h>
int main(void)
char *p = "stop";
char str[80 J;
do {
printf("Enuer a string: ");
gets (str);
} while(strcmp(p, str));
return 0;
}
2. Използването на указатели към иизови константи може да
е много полезно, когато тези константи се доста дьлги.
Например, представете си, че имате npoi рама, кояТО ria
различии места подтиква потребителя да постази дискета
р устройство А. За да си снести те малко нисане, можете да
изберете да инициализиратс указател към низа и след това
просто да използвате указателя, когато трябва да се изпи-
шете съобщението; например:
char *TnsDisk = "Insert disk inco drive A, then
press ENTER";
printf(InsDisk);
printf(InsDrsk);
Друго предимство на този подход е, че ако се налага да
смените съобщението. ще е достатъчно -да го направите
само на одно място.
190 С - Практически самоучител
1. Напишете програма, която създава три указателя към char
и така ги инициализира, че пърЕИят сочи към низа “one”,
вторият - към “two”, а тргтият - към “three”. Нека след то-
ва програмата да отпечатва шестте пермутации на тези три
низа. (Например една лермутация е “one two three”, а дру-
га е “two three one”.)
6.5
Създаване на масиви от
указатели
Указателите могат да се поставят в масиви, както всеки друг вид
данни. Например следващата конструкция декларира масив от
целочислени указатели, съдържащ 20 елемента:
int *ра[20];
Адресът на целочислената променлива, наречена myvar, се
присвоява па деветия елемент така:
ра[8] = &myvar;
Тъй като ра е масив от указатели, единствените стойности, кон-
то могат да се съдържат от елементите на масива, са адресите на
целочислени стойности. За да присвоите стойност 100 на про-
менливата, сочена от третия елемент, използвайте конструкция-
та:
*ра [2] = 100;
ПРИМЕРИ
1. Вероятно единствена га най-честа употреба на масивите от
указатели е при създаването на низови таблици, което
много наподобява използването на масиви без фиксирана
дължина от предишната глава, Например тази функция
изобразява съобщение за грешка в зависимост ог стой-
ността на параметьра й err_num.
char *р[1 = {
"Input exceeds field width",
"Out of range",
"Printer not turned on",
Използване на указатели 191
"Paper out",
"Disk full",
"Disk write error"
void error(int err num)
{
printf(p[err_num]);
}
2. Следващата програма използва двумерен масив от указа-
тели, за да създаде низова таблица, свързваща различимте
сортове ябълки с техните Цветове. За да използвате прог-
рамата, въведете името на никоя ябълка и тя ще ви каже
нейния цвят.
tti.nclude <stdio.h>
^include <string.h>
char *p(l [2] = {
"Red Delicious", "red",
"Golden Delicious", "yellow",
"Winesap", "red",
"Gala", "reddish cranqe",
"Lodi", "green",
"Mutsu", "yellow",
"Cortland", "red",
"Jonathan", "red",
"" /* термин-сраче на таблигдата c null низоге */
};
int main(void)
int i;
char apple[80];
printf("Enter name of apple: ");
gets(apple);
for(i=0; *p[i][0]; i++) (
if(!strcmp(apple, p[i][0]))
printf("%s is %s\n", apple, p[i][l]);
}
return 0;
}
Разгледайте внимателно условието, коптролиращо цикъла
for. Изразът I][0] извлича стойностга на първия байт на
Г” низ. Тъй като списъкът се терминира от нулеви низове,
тази стойност ще е нула (false), когаго се достигнс до края
па таблицата. Във всички останали случаи тя ще е различ-
на от нула и цикълът ще се повтаря.
192 С - Практически самоучител
I. В това упражнение ще създадете “инструмент за взимане
на решения”. Това е програма, която отговаря с “да”, “не”
или “може би” на въпрос, въведен от клавиатурата. За да
създадете тази програма, използвайте масив от char указа-
тели и ги инициализирайте да сочат към тези три низа-
“Yes”, “No” и “Maybe Rephrase the question ”. След това
прочетете въпроса на потребителя и намерете дължината
на низа. Използвайте формулата
индекс = дължина % 3
за да начислите индекс в масива от указатели.
6.6
МНОЖЕСТВЕНАТА ИНДИРЕКТНОСТ
В С е възможно да съшествува указател към указател. Това се
нарича множесшвена индиректност (вижте. фигура 6-1). Когато
един указател сочи към друг, първият указател съдържа адреса
на втория, който, от своя страна, сочи местоположението на
обекта.
За да декларирате указател към указател, трябва да поставите
още една звездичка пред името на указателя. Например следва-
щата декларация указва на компилатора, че шр е указагел към
char указател:
char **mp;
Важно е да разберете, че шр не е указател към char, а всъщност
е указател към указагел към char.
Фигура 6-1
Указател към
указател
Указател
Применлива
Множесшвена индиректност
Използване на указатели 193
Достигаясто на стойността, ипдиректно сочена от указател
към указател, изисква операторы звездичка да се приложи дна
пъти. Например
char * **шр,
*р, ch;
р = &ch; /* получаваие на адреса на ch */
mp = &р; /* получаваие на адреса на р */
**шр “ 'А'; /* присвояване на стойност А на ch,
посредством множествена индиректност */
Както иредлагат коментарите, на ch се присвоява стойност като
се използват два указателя.
Множествената индиректност не се ограничава само до “ука-
зател към указател”. Можете да прилагате * толкова често, кол-
кото се налага. Множествената индиректност отвъд указател към
указател обаче е доста трудна за проследяване и не се прспоръчва
Сега може да не ви е ясна нуждата ог множествена индирект-
ност, но с увеличаването на знанията ви по С ще срещне ге някои
примери, при конто тя е много ценна.
ПРИМЕРИ
I. Следващата програма присвоява стойност на val посредс-
твом множествена индиректност. Първо се изписва стой-
ността директно, а след това чрез множествена индирект-
ност.
ttinclude <stdic.h>
int main(void)
{
float *fp, **mfp, val;
fp = &val;
mfp ч» &fp;
**mfp = 123.903;
printf ("%f %f", val, **mfp);
return 0;
2. Следващата програма ви показва как бихте могли да чете-
те низ посредством gets() и указател към указател кьм низа.
#include <stdio.h>
int main(void)
194 С - Практически самоучител
char *р, **mp, str[80J;
Р = str;
тр = &р;
printf("Enter your name: ");
gets(*mp);
printf ("Hi %s", *ir.pl;
return 0;
Забележете, че когато mp се използва като аргумент в
gets() и printf(), се използва само една звездичка Това е
така, защото за своите операции и дзете функции изискват
указател към низ.
Запомнете, че **шр е указател към р. р обаче е указател
към низа str. Поради това *тр е указател към str. Ако сте
малко объркани, не се притеснявайте. С времето ще разви-
ете по-ясна представа за указателите към указатели
Ий®
I. За по-добро разбиране на множествената ипдиректност,
напишете програма, присвояваща целочислена стойност
посредством указател кьм указател. Преди програмата да
приключи, отпечатайте адресите на цялото число, на ука-
зателя и на указателя към указателя. (Спомнете си, че
трябва да използвате %р, за да изпшпете стойност на ука-
зател.)
Използване на указатели като
параметри
Указателите могат да се предават на функции. Например, когато
извиквате функция, като strlen(), с имс на низ, всъщност вие й
предавате указател. Когато предавате на някоя функция указа-
тел, тя трябва да е декларирана да приема указател от съответния
тип. В случая със strlen(), това е char указател. В следващата
глава с прсдставено подробно разглеждане на използването на
Използване на указатели 195
указатели като параметри. Все пак тук са представени някои ос-
новни концепции.
Когато предавате указател па функция, кодът от тази функция
има достъп до променливата, сочена от папаметъра. Това озна-
чава, че функцията може да променя променливата, използвака
за нейното извикване. Точно поради това функции, като strcpy( )
например, могат да работят. Тъй като на функцията се предана
указагел, тя има възможност да променя масива, получаващ низа.
Сега можете да разберете защо при scanf( ) името на промен-
ливата трябва да се предшества от &. За да може scanf() да про-
мени стойността на един от своите аргумента, трябва да й се
предаде указател кьм този аргумент.
ПРИМЕРИ
I. Друга стандартна библиотечна функция в С се нарича
puts(); тя изписва на екрана своя аргумент, следвап от нов
ред. Следващата програма създава своя версия на puts( ),
наречена myputs().
#include <stdio.h>
void myputs(char *p) ;
int main(void)
myputs("this is a test");
return 0;
}
void myputs(char *p)
(
while (*p) { /* обхождаяе, докато p започне да
сочи нупата, която тёрминира низа */
printf("%с", *р);
р+ + ; /* премина.ване към следвашия знак */
printf("\п");
Тази програма илюстрира един много важен момент, кой-
то беше споменат по-рано в тази глава. Когато компилато-
ръ г срещпе низова константа, той я поста вя в низовата
таблица на програмата и генерира указател към нея. Зато-
ва всъщност функцията myputs( ) се извиква с указател, а
р трябва да се декларира като указател към char.
2. Следващата програма показва един от начините за импле-
ментиране на функцията strcpy( ), наречен mystrcpy().
196 С - Практически самоучител
}finclude <stdio.h>
void mystrcpy(char *to, char *from);
int main(void)
{
char str[80];
mystrcpy(str, "this is a test");
printf(str);
return 0;
void mystrcpy(char *to, char *from)
while(жfrom) *to++ = *from++;
*to == '\C'; /* null терминира низа */
}
I. Напишете ваша-собствена версия на strcat( ), наречена
mystrcat( ), и напишете кратка програма за нейната де-
монстрация.
2. Напишете програма, предаваща на функция указател към
целочислена променлива. В тази функция, присвоете на
променливата стойност -1. След завършването на функци-
ята покажете, че променливата наистина съдържа -1 като
я отпечатате.
Проверка на уменията, представени в главата
В този момент трябва да сте в състояние да отговорите на
следващите въпроси и да изпълните следните упражнения:
1. Покажете как се декларира указател към double.
Използване на указатели 1ST
2. Напишете програма, присвояваща индиректно стойност на
променлива посредством указател към нся.
3. Верен ли е този фрагмент? Ако не, защо?
int main(void)
char *p;
printf("Enter a string: ") ;
gets (p) ;
return 0;
)
4, Как са свързани помежду си указателите и масивите?
5. Даден е следният фрагмент:
char *р, str(80] = "this is a test";
р = s t г;
Покажете два начина за достигане до ‘i’ от думата “this”.
6. Да предположим, че р е декларирана като указател към
double, и съдържа адрес 100. След това да приемом, че
double е с дължина 8 байта. След каго инкременетираме р,
каква ще е нейната стойност?
Проверка на натрупаните умения
Тази част проверява доколко добре сте интегрирали материа-
ла от тази глава с този от предпишите главк.
I. Какво е предимството на изнолзването на указатели пред
индексирането на масиви?
2. I Io-долу е дадена програма, която брой интервалите в низ,
въведен от потребителя. Пренапишете програмата, така че
да използва указателна аритметика вместо индексиране на
масив.
#include <stdio.h>
int main(void)
{
char str [80];
198 С - Практически самоучител
int i, spaces;
printf("Enter a string; ");
gets (str);
spaces = 0;
for(i=0; str[i]; i++)
if(str[i|==' ') spaces++;
printf("Number of spaces; %d1', spaces);
return 0;
3. Пренапишеге следната референция към масив посредст-
вом указателна аритметика.
int count [ЮС] [10];
count[44] [6 J = 99;
Подробно разглеждане
на ФУНКЦИИТЕ
Разглеждани теми в главата
7.1 Прототипи на функции
7.2 Рекурсия
7.3 Подробно разглеждане на
параметрите
7.4 Предавало на аргументи на
main()
7.5 Сравкяване на по-с^арите
декларации на параметри със
съвременните
200 С-Практическисамсучител
В основата на С са функциите. Всички конструкции тряб-
ва да са включени във функции и разбирането на меха-
низма им на действие е определят за успешного прог-
рамиране на С. Тази глава прави прсглед на няколко
важни геми, отнасящи се до функциите.
Проверка на знанията
Преди да продължим трйбва да можете да отговорите на тези
въпроси и да изпълните упражнспията:
1. Какво прави този фрагмент?
int i, *р;
р = si;
*р = 19;
2. Какво се генерира, когато използвате име на масив без
индекс?
3. Верен ли е този фрагмент? Ако да, защо работа?
char *р = "this is a string";
4. Напишете кратка програма. която индиректно нрисвоява
стойност с плаваща запетая на променлива посредством
указател кьм нея.
5. Напишете ваша-собствена версия на strlen(), наречена
niystrleu(), и я демонстрирайте в програма.
6. Верен ли е този фрагмент? Ако да, какво извежда програмата?
char str[8];
strcpy(stx, "ABCDEFG") ;
printf("%c", *(str+2));
7.1
Прототипи на функции
В глава l бяха представени накратко ирототипите на функциите.
Сега е време да разберете каква точно е ролята на протогипите и
защо те са важни за програмиранего на С. Проготипите на функ-
Подробно разглеждане на функциите 201
ции не се поддържаха от оригиналната версия на С, но бяха до-
бавени при стандартизирането му през 1989 г. Много хира счи-
тат прототипите за най-важното допълнение към С от създаване-
то му насам. Тс не са технически необходими, но въпреки това
по причини, конто ще се изясняг от само себе си, трябва да ги
използвате във всички ваши програми.
Основната форма на прототип на функция е показан тук:
тип име-на-фушщия(тип име-на-параметър1,
тип име-на-параметър2,
тип име-на-параметър1\1);
Всеки прототип декларира три атрибута, асоциирани с функцията:
1. Типът на резултата от изпълнението на функция га.
2. Броят на параметрите й
3. Типът на параметрите й.
Прототипите предоставят няколко предимства: те дават ин-
формация на компилятора какъв е типът на резултата; позволя-
ват му да намира и издава сьобщения за некоректни преобразу-
вания на типа на аргументите, използвани за извикване функци-
ята и дефишщията на параметрите й. Освен това прототипите
дават възможност на компилятора да докладва за различия в
броя на аргументите, предавали на функцията и тези от нейният
прототип. Нека да разгледаме всяко от тези предимства.
Когато извиквате дадена функция, компиляторы1 трябва да
знае типа на връшаните от нея данни, така че да може да генери-
ра подходящих код за тяхната обработка. Причината е лесно раз-
бираема - различите типове данни имат различии размери. Ко-
дът, обработващ целочислен резултат, е различен от този за ре-
зултат с плаваща запетая. Ако използвате функция без прототип,
компилаторът приема по иодразбиране, че тя връща цяло число.
В случай че нейният резултат е от друг тип, това ще предизвика
геяериране на {решка. Когато функцията е в един и същ файл за-
одно с останалата част от програмата, компилаторът ще засече
тази грешка, но ако е в друг файл или библиотека, тогава греш-
ката ще се появи при опит за изпълнение на програмата.
При липсата на прототип на дадена функция, не е синтакти-
чески грешно да я извикате с несъвместими по тип или брой ар-
гумента спрямо декларираните. Разбира се, това очевидно е
грешно, въпреки че компилаторът ще приеме програмата без
възражения. Упот ребата на прототипи предотвратява появата на
подобии грешки, като предоставя възможност на компилятора да
202 С - Практик ески самоучител
ги открива. Важно е да разберете обаче, че не всички преобразу-
вания на типове са неприемливи при извикването на функции.
На практика компилаторът на С автоматично конвертира пове-
чето аргумента в типа на параметрите на функцията. Някои ти-
пове кснвертирания обаче са напълно невъзможни. Например не
можете да конвертирате цяло число в указател. С въвеждането
на прототипите се дава възможност на компилатора да хваща и
връща подобии грешки.
Както беше споменато по-рано, колкото и важни да са прото-
типитс на функциите, те не са задължителни. Поради необходи-
мостта от поддръжка на съвмсстимост с по старите кодове, всич-
ки компклатори на С все още пс-ддържат програми без прототи-
пи. Разбира се, в бъдещс тази ситуация би могла да се промени.
В ранните версии на С, преди въвеждането на прототипите,
все още е било необходимо ла се указва на компилатора какъв е
типът на резултата (освен ако е int) поради посочените по-горе
причини. Това се правеШе от предшественика на прототипа, на-
речен предварителна декларация или предварителна референ-
ция. Предварителната декларация е опростена форма на прото-
тип, указваща единствено типа Fia резултата, но не и броя и типа
на параметрите й. Въпреки че това е остаряла форма, предвари-
телната декларация Все още е позволена поради необходимостта
от съвместимост с по-старите кодове.
Следващата програма демонстрира предварителната деклара-
ция. Тя се използва, за да се укаже типа на резултата на функци-
яга voluroe().
^include <stdio.h>
double volume(); /* предварителна декларация на
volume () */
int main(void)
{
double vol;
vol = volume(12.2, 5.67, 9.03);
printf ("Volume: %f", vol);
return 0;
}
/* Изчисляване на обема на куб. */
double volume(double si, double s2, double s3)
{
return si * s2 * s3;
)
Подробно разглеждане на функциите 203
Тъй като старкят тип декларации не дават на компилатора пи-
каква информация за параметрите на volume( ), това не е прото-
тип па функция. Вместо това тя просто указва типа па резултата
на volumc(). Проблемът е, че липсата па пълен прототип ще
позволи volume( ) да бъде извиквана с грешен тип или брой на
аргуменгнте. Например ако вземем предвид предходната прог-
рама, следващата конструкция няма да генерира съобщение за
грешка:
volume (120.2, 99.3); /* липсгащ пэследен аргумент */
Тъй като на компилатора не се дава информация за параметрите
на volume(), той няма да успее да определи това извикване като
грешно.
Въпреки че предварителпата декларация вече не се използва,
тя е доста често срсщана в по-сгарите програми. Ако трябва да
обнови вате стари програми, първата задача е да добавите про
тотипи.
След въвеждането на прототипи в С трябваше да се разрешат
два незначителни проблема със съвместимостта между старата
версия на С и A MSI варианта на С. Първо, как да се обработва
предварителната декларация, която не използва списък с пара-
метри. За да се справи с това, стандаэтът ANSI С определя, че
ако се срещне декларация на функция без параметри, нищо не се
знае за параметрите на функцията. Тя може да има параметри, но
може и да няма. По този начин се предоставя възможност за
съвместно съществуване на предварителната декларация и про-
тотипите. Оттук обаче следва въпросът: как се задава прототип
на функция без аргумента? Например следваша функция просто
отпечатва ред от точки:
veld line()
(
int i;
for(i=0; i<80; i+ + ) printf
}
Ако се опитате да използвате следващата декларация като про-
тотип, няма да успеете, просто защото компилатора ще реши, че
използвате стария метод на декларация.
void line ();
Решениею на този проблем е чрез ттрипагането на ключовага
дума void. Когато една функция няма параметри, нейният прото-
204 С - Практичаски самоучител
тип използва void между скобите. Тук например е даден правил-
ният прототип на line():
void line(void);
По този начин на компилатора изрично се задава, че функцията
няма параметри и всяко нейно извикване с параметри е грешка.
Задьлжително трябва да използвате void и при дефинирането на
функцията. Например line() трябва да изглежда така:
void line(void)
{
int i;
for(i=0; i<80; i++) printf
}
Тъй като използваме void за задаване на празен списък с пара-
метри още от глава I, то този механизъм ви е познат.
Вторият проблем, отнасящ се до прототипите, е тяхното въз-
действие върху автоматичните повишения на типовете. Поради
някои особенисти на с редата, в която е разработван С, при из-
викването на функция без прототип, се извършват всички вът-
решни повишения (например char се конвертира в int), а всички
float стойности се конвертират в double Тези повишения обаче
като че ли са в противоречие с целите на прототипите. Решение-
то на проблема е, че при съществуване на прототип се поддър-
жат типовете, зададени в прототипа, и няма никакви повишения
на типове
Има още един специален случай, свързан с прототипите: спи-
съци от аргумента с променяща се дължина. В тази книга няма
да създаваме подобии функции, защото те изискват прилагането
на някои специални техники. Това обаче е възможно и понякога
е доста полезно. Например двете функции printf() и scanf()
приемат променлив брой аргумента. За да зададеге променящ се
брой аргумента, в прототипа използвайте ... (многоточие). Нап-
ример
int myfunefint а, ...);
задава функция с един целочислен парамстър и променлив брой
други параметри.
В програмирането на С дълго време съществуваше неяснота
относно понятпята: декларация и дефиниция. Декларацията оп-
редели типа на обекта, а дефиницията създава място за съхране-
ние на обекта. Самата функция, съдържаща тялото й, е дефиниция.
Подробно разглеждане на функциите 205
В С е напълно допустимо една функция да бъде изцяло дефи-
нирана приди първата й употреба. По този начин се избягва не-
обходимостга от отделен прототип. Това обаче действа само за
малки програми. На практика трябва да присъстват прототипите
на всички функции, използвани от програмата.
Запомнете, че ако дадена функция нс връща стойност, тогава
типът на нейния резултат трябва да се зададе като void - както в
дефиницията й, така и в прототипа.
Прототипите па функции ви дават възможност да пишете по-
дсори и по-надеждни програми, защото ви гарантират, че функ-
циите в програмата ще се извикват с правилен брой и тип аргу-
мента. Напълно прототипираните програми са образец, предс-
гавляващ съврсменното състояние на програмирането на С. Чес-
тно казано, в днешно време никой професионален С програмист
не създава програми без прототипи. Освен това бъдещите версии
на стандарта ANSI С вероятно ще включват прототипи на функ-
ции, което важи за C++. Въпреки че прототипите все още не са
задължителни, тяхното приложение е почти универсално. Добре
е да ги използвате във всички ваши прсграми
ПРИМЕРИ
1. За да видите как прототипът на една функция може да
предотврати грешки, опитайте се да компилирате тази
версия на програмата за обем, която включва пълният
прототип на volunie():
tfinclude <stdio.h>
/* това е пълната декларация на volume( ) */
double volume(double si, double s2, double s3) ;
int main(void)
{
double vol;
vol - volume (12.2, 5.67, 9.03, 10.2); /* грешка*/
printf("Volume: %f", vol);
return 0;
)
/* Изчислява оЗема на куО. */
double volume(double si, double
s2, double s3)
return si * s2 * s3;
206 С - Практически самоучител
Както ще се уверите, тази програма не може да се компи-
лира, защото компилаторът знае, че volume( ) е деклзри-
рана да има само три аргумента, а програмата се опитва да
я извика с четири.
2. Както беше обленено, ако една функцията е дефинирана
преди своего извикване, не е необходим отделен прото-
тип. Например следващата програма е напълно валидна:
((include <stdio.h>
/* декларира getnum( ) преди първата й уготреба*/
float qetnum(void)
{
float x;
printf("Enter a number: ");
scanf("%f", &x);
return x;
int main(void)
float i;
i = getnvm( );
printf ("%f", i)
return 0;
Тъй като getnum() e дефинирана преди да бъде използва-
на, компилаторът знае типа на нейния резултат и това, че
тя няма параметри. Не е необходим отделен прототип.
Причината, поради кояго този метод се използва е, че го-
лемите програми обиквовено се разпростират в няколко
файла. Тъй една функция не може да се дефинира повече
от веднъж, прототипите са едипствспият начин да инфор-
мирате всички файлове за функцията (Многофайловите
програми се обяснени в глава 11).
3. Както вече знаете, стандартната библиотечна функция
sqrt() зръща стойност от тип double. Може би се чудите
как компилаторът знае това. Отговорът е, че sqrt() има
прототип в хедърния файл MATH Н. За да се уверите във
важността на използване го на хедърния файл, опитайте се
да компилирате тази програма:
#include <stdio.h>
/* rnath.h не е включен умишлено */
int main(void)
Подробно разглеждане на функциите 207
{
double answer;
answer = sqrt(9.0);
printf (answer);
return 0;
}
Когато стартирате тази програма, тя извежда нещо раз-
лично от 3, защото компилаторът генерира код, копиращ
само два байта (предполага се двубайтов int) в answer, а
не осем байта, което обикновено е размерь? на double.
Ако включите МАТЧ.Н програмата, ще работи вярно.
Най-общо казано, всички стандартни библиотечки
функции в С имат прототип и, зададени в пякой хедърен
файл. Например прототипите на printf() и scanf() са в
STDIO.H. Това е една от причините, поради която е важно
да включвате правилни^е хедърни файлове за всяка изпэл-
звана библиотечка функция.
4. Има одна често срещана ситуация, кояго може да ви се
стори объркваща. Някои функции за “обработка на знако-
ве'’ врьщаг резултаг от тип int, вместо char. Например ти-
път на резултата на функцията getcharf ) е int, а не char.
Причината за това се крие във факта, че С лесно извьршва
конвертиране на char в int и обратно. Няма загуба на ин-
формация. Например следващата програма е напълно ва-
лидна:
tfinclude <stdio.h>
int get a char(void);
int main(void)
char ch;
ch = get a char( );
printf(”%c", ch);
return 0;
)
int get_a_char(void)
return 'a';
Когато get a char() приключи, тя издига знака ‘а’ до int,
като добавя старши байт (или байтовете), съдържащ нули.
208 С - Практически самоучител
Когато в main() тази стойност се присвоява на променли-
вата ch, този байт (или байтове) се премахва. Една от при
чините функции като gtt_a_char() да се декларират с тип
на резултата int, вместо char, е да се даде възможност за
връщането на различии кодове за грешки, който предна-
мерено са извън областта на char.
5. Когато една функция връща указател, той трябва да бъде
от един и същ тип във функцията и в прототипа. Напри-
мер, разгледайте тази кратка програма:
#include <stdio.h>
int *init(int x);
inn count;
int main(void)
{
int *p;
p = init(llO); /* ыр'ыца указател */
printf(“count (through p) is %d", *p) ;
return 0;
)
int *init(int x)
count = x;
return &count; /* сръща указател */
}
Както можете да видите, функцията init() връща указател
към глобалната променлива count Забележете начина, по
който е зададен типът на резултата на init(). Същата об-
щата форма се използва и за всички други видове указате-
ли, връщани от функции. Въпреки че този пример е еле-
ментарен, функциите, връщащи указатели, са доста пенни
в много случаи. Оше нещо: ако дадена функция връща
указател, трябва да сте сигурни, че обектьт, сочен от ука-
зателя, не е извън областта на видимост след приключва-
него на функцията Това означава, че не трябва да връщате
указатели към локални променливи.
6. Функцията main() няма (пито изисква) прототип. Това ви
позволява да дефинирате main() по всякакъв начин, под-
държан от компилятора. Тази книга използва
int main(void) { ...
Подробно разглеждане на функциите 209
защото това с една от най-използваните форми. Друга,
често употребявана, форма е показана тук:
void main(void) { ...
Тази форма се използва, когато main() не връща никаква
стойност. Ео късно в тази глава ще видите още една фор-
ма на main(), която има параметри.
Причината, поради която main() няма проготип, е да
предостави възможност С да се използва във възможно
най-разнообразии среди. Тъй като пренизните изисквания
при стартирането на програмата и дсйствията, конто тряб-
ва да се извършат при нейното терминирале, обикновено
се различават при различните операционни системи, С
позволяза приемливите форми на main() да се определят
от компилатора. Независимо ог това, почти всички компи-
латори приемат int main(void) и void main(void).
1. Напишете програма, създаваща функция на име avg(), ко-
ято чете 10 числа с плаваща запетая, въведени ог потреби-
теля, и връща средната им стойност. Използвайте предва-
рителна декларация, а не прототип.
2. Пренапишете програмата от упражнение 1 така, че да из-
ползва прототипи.
3. Варна ли е следващата програма? Ако не, защо? Ако е,
може ли да бъде подобрена?
#include <stdio.h>
double myfunc( ) ;
int main(void)
{
printf("%f", myfunc(10.2));
return 0;
double myfunc(double num)
I
return num / 2.0;
)
210 С - Практически самоучител
4. Покажете прототипа на функция, наречена Purge( ), която
няма параметри и връща указател към променлива от тип
double.
5. Експериментирайте самсстоятслно с концепциитс, разгле
дани в тази глава.
7-2
Рекурсия
Рекурсията е процесът, при който нещо се дефинира посредст-
вом себе си. Когато това се отпася до компютърнитс езици, ре-
курсията означава, че дадена функция може да извиква себе си.
Не всички компюрърни езици поддържат рекурсивни функции,
но С ги поддъжа. Един много прост пример за рекурсия е пока-
зан тук.
#include <stdio.h>
void recurse(int i);
int main(void)
{
recurse (0);
return 0;
)
void recurse(int i)
{
if(i<10) {
recurse(i+1); /* рекурсивно извикване */
printf("%d ", i);
)
)
Тази програма извежда на екрана
9876543210
Нека видим защо.
Функцията rccurse() първо се извиква с аргумент 0. Това е
нейното първо активиране. Тъй като 0 е по-малко от 10,
recurse() извиква сама себе си със стойността на 1 (в конкретния
случай 0) плюс 1. Това е втората активация на recurse() и 1 е
равно на 1. Това предизвиква ново извикване на recurse() пос-
редством стойност 2. Този процес се повтаря, докато recurse()
бъде извикана със стойнсст 10. Това води до приключването от
Подробно разглеждане на функциите 211
recurse( ). Тъй като това става в точката на извикване, ще се из-
пълни конструкцията printf( ) от предишната активация (отпе-
чатване на 9) и ще последва ново приключване. Отново има
връщане в точката на извикване ст предишната активация, което
изписва 8. Процесът продължава, докато приключат всички из-
виквания и програмата завърши.
Важно е да разберете, че не съществуват множество копия на
рекурсквната функция. Вместо това има само едно копие. Когато
една функция бива извикана, се заделя място за параметрите и
локалните й променлива в стека. По този начин, когато дадена
функция се извиква рекурсивно, всеки пъг тя започва с нова тру-
па от параметри и локални променливи, но кодът, съставляващ
функцията, остава един и същ.
Ако разгледате предходната програма, ще видите че рекурси-
ята всъщност е нов вид механизъм за управление на програмата.
Ето защо всяка рекурсивна функция трябва да има конструкция с
условие, която да определя дали функцията да се извика отново
или да върне резултат от изпълнението си. Без подобно условие,
рекурсивната функция ще се изпълнява, докато запиши заделе-
ната за стек памет, и след това ще блокира програмата.
Обикновено рекурсията се използва впимателно. Тя обаче
може да бъде доста полезна в опростявапсто на определен тип
алгоритми. Така например сортирането по метода Quicksort е
доста труден за реализиране без рекурсия. Ако по принцип сте
“новобранец” в програмнрапето, може би ще ви се сгори доста
трудно да се справяте с рекурсията. Не се тревожете - с нараст-
ване на опита ви, използванего на рекурспвни функции ще ви
изглежда все по-естествено.
ПРИМЕРИ
1. Описаната по-горе рекурсивна програма може да се про-
мени, така че да отпечатва па екрана цифрите от 0 до 9. За
да постигнем това трябва просто да сменим мястото на
printf(), както с посолено по-долу:
#include <stdio.h>
void recurse(int i) ;
int main(void)
{
recurse(0);
return 0;
}
212 С - Практически самоучител
void recurse(int i)
{
if(i<10) {
printf("%d ", i);
recurse(i41);
Тъй като сега извикването на printff ) предхожда рекур-
сивного извикване на recurse(), цифрите се извеждат в
нараотващ ред.
2. Следвашата програмата демонстрира как рекурсилта може
да се използва за копиране на един низ в друг.
#include <stdio.h>
void rccpy(char *sl, char *s2) ;
int main(void)
{
char str[83];
rcopy(str, "this is a test");
printf(str);
return 0;
}
/* Копира s2 в si посредством рекурсия. */
void rcopy(char *sl, char *s2)
{
if(*s2) { /* ако не e в края на s2 */
*sl++ = *s2++;
rcopyfsl, s2);
}
else *sl = ’\0’; /* null терминира низа */
}
Програмата работа като нрисвоява знака, сочен от s2, на
този, сочен от si, и след това инкрементира двата указате-
ля. Тези указатели се използват за рекурсивно извикване
на гсору(), докато s2 започне да сочи нулата, терминира-
ща низа.
Въпреки че това е един интересен пример за използва-
нето на рекурсия, никой професионален програмист на С
не би написал функция като тази, поради една просга при-
чина: ефсктивност. Времето за извикване на функция е
значително повече от времето за изпълнение на цикъл.
Поради това задачи като тази почти винаги се решават
чрез пиклични подходи
Подробно разглеждане на функциите 213
3. Съществуват програми, б конто две или повече функции
са взаимно рекурсивни. Взаимната рекурсия се ноявява,
когато една функция извиква друга, която на свой ред из-
виква първата. Като пример, изучете тази кратка програма:
ftinclude <stdio.h>
void f2(int b);
void fl(inc a);
int main(void)
{
fl(30) ;
return 0;
)
void fl(int a)
(
if (a) ±2(a-l);
printf("%d ", a);
)
void f2(int b)
{
printf(".");
if(b) fl(b-l);
)
'Гази програма отпечатва на екрана
........0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30
Тези данни са породеки от начина, по който двете функ-
ции П( ) и 12( ), се извикват една друга. Всеки път, когато
бъде извикана П () тя нроверява дали а е нула. Ако не е,
извиква f2( ) със стойност а-1. Функцията Г2() първо отпе-
чатва точка, след което проверяет дали Ъ е нула. Ако ус-
ловного не е true, тя извиква П( ) с аргумент Ь-1 и проце-
сът се повтаря. Накрая b се нулира и извикванията на
функциите започват да се разплитат, което води до отпе-
чатването на числата от 0 до 30 през одно.
214 С - Практически самоучител
1. Един от Haii-добре известните примери за рекурсия е ре-
курсивната версия на функция за начисление на факторие-
ла на число. Факгориелът па дадсно число се получава от
умнсжаването на числото с всички цели числа по-малки от
него и по-големи от единица. Това означава, че факториел
от 4 е 4x3x2. или 24. Напишете функция, наречена fact(),
използваща рекурсия за изчисляваис па факториела на це-
лочисления си аргумент. Нека тя го връща като резултат.
Демонстрирайте употребата й в програма.
2. Какво не е наред в тази рекурсивна функция?
void f(voiо)
{
int i ;
printf(«in f( ) \n»);
/* извиква f( ) 10 пъти */
for(i=C; i<10; i++) f( );
}
3. Напишете програма, изписваща знак по знак на екрана
низ посредством рекурсивна функция.
7.3
Подробно разглеждане на
ПАРАМЕТРИТЕ
Най-общо казано за компютьрните езици, на подпрограмите мо-
гат да се предавал аргумепти по два начина. Първият с наречен
извикване но стойност. Този метод копира стойностите на ар-
гументите вьв формалните параметрите на подпрограмата. Или
иначе казано, промените, извъртпвани с параметрите, не засягат
аргумента, използван при извикването па подпрограмата. Други-
ят начин за предаване на аргумент към подпрограма е чрез из
сиксане по адрес. При него адресът на аргумента се копира в
съответния параметър. По този начин промените на параметъра
ще засегнат аргумента.
По подразбиране С използва предаване по стойност. Това оз-
начава, че не можете да промекяте аргументите, използвани за
извикването на функцията. Как вито и операции да извършвате с
параметъра на функцията, те няма да се отразят на аргумента из-
вън тази функция. Както видяхте в глава 6 обаче, имате възмож-
Подробно разглеждане на функциите 215
ноет да извършвате извикване по адрес посредством предаване
на указател към аргумента. Тъй като по този начин предавате
адреса па аргумента, то стойностга му може да бъде променяна,
както във функцията, така и извън нея
Класически пример за функция, извикваща по адрес, е
swap(), показана по-долу. Тя размена стойностите на своите два
целочислени аргумента.
Я include <stdio.h>
void swap(int *1, int *j);
int main(void)
(
int numl, mm2;
numl = IOC;
num2 = 800;
printf("ntrnl: %d num2: %d\n", numl, num2);
swap(&numl, &num2);
printf ("numl : %d mm2: %d\n", numl, mm2);
return 0;
)
/* Размена стойностите, указвани от два указателя към
цели числа. */
void swap(int *i, int *j)
{
int temp;
temp = *i;
*i = *j;
* j = t emp;
}
Тъй като указателите към двете цели числа се предават на функ-
цията, се разменят деиствителните стойности на аргументите.
Както знаете, при използването на масив като аргумент на
функция се предава само нсговия адрес, а не копие на целия ма-
сив. Това всъщност е извикване по адрес. Това означава, че дек-
ларацията па параметьра трябва да е указател от съвместнм тип.
Има три начина да декларирате параметьр, предназначен за по-
лучаване на указател към масив. Първият е парамстьрът да се
декларира каго масив от същия тип и размер, какъвто е масивът,
използван за извикването на функцията. При втория, пара меть-
рьт може да се задаце като масив без фиксиран размер. Послед-
нкяг, и най-често употребяваннят, е парамегърьт да се зададе ка-
то указател към базовия тип на масива. Следващата програма
демонстрира тези три метода:
216 С - Практически самоучител
«include <stdio.h>
void fl (int num[5))> f2(int num[]), f3(int *num) ;
int main(void)
{
int ccunt[5] = {1, 2, 3, 4, 5);
fl (count);
f2 (count);
f3(count);
return 0;
}
/* параметър, зададен като масиь */
void fl(int num[5])
int i;
for(i=-0; i<5; i++) printf("%d ", num[i]);
)
/* параметър, зададен като масив без фиксирана дължина */
void £2 (int num (J)
{
int i;
for(i=0; i<5; i++) printf("%d ", num[i]);
)
/* параметър, зададен като указател */
void f3(int *num)
int i;
for(i=0; i<5; i++) printf("%d ", numfi]);
Въпреки че трите метода за декларация изглеждат различии., те
всички водят до създаване на параметър от тип указател
ПРИМЕРИ
1. Някои компютърни езици, като BASIC, предоставят функ-
ция за вход, позволяваща в и да зададеге подсказващо съ-
общение. С няма аналог на подобна функция, но лесно
можете да създадете такава. Следващата програмата из-
ползва функцията prompt( ), за да изобрази съответното
сьобщение, след което прочита въведеното от потребителя
число.
Подробно разглеждане на функциите 217
ttinclude <stdio.h>
void prompt(char *msg, int *num);
int main(void)
{
int i;
prompt("Enter a num: ", &i);
printf("Your number is: %d", i);
return 0;
}
void prompt(char *msg, int *num)
{
printf(msg);
scanf("%d", num);
Тъй като параметьрът num вече с указател, ляма нужда да
поставите пред него & при извикването на scanf().
(Всыцност това би било грешка')
да
1. Вярна ли е тази програма? Ако не, защо?
flinclude <stdio.h>
myfunc(int num, int rain, int max);
int main(void)
int i;
printf("Enter a number between 1 and 10: ");
myfunc(&i, 1, 10);
return 0;
}
void myfunc(int num, int min, int max)
{
do (
scanf("%d", num);
} while(*num<min || *nurc>max);
218 С - Практически самоучител
2. Напишете програма, създаваща функция за въвеждане,
подобна на prompt(). Нека тя да пропита низ вместо цяло
число.
3. Обяспете разликите между извикване по стойност и из-
викване по адрес.
Предаване на аргументы на
MAIN( )
Много програми давят възможност при тяхпото стартирапе да се
задават аргументите от командния ред. Аргумент от командная
ред е информация™, която следва името на програмата в коман-
дной ред на операционната система. Аргументите от командния
ред се използват за предаване на информация към програмата.
Например, когато използвате текстов редактор, вероятно след
името на редактора указвате името на файла за редактиране. Ако
приемем, че използваният от вас редактор се нарича EDTEXT,
тогава следващата команда извиква файла TEST за редакция.
EDTEXT TEST
В този случай TEST е аргумент от командния ред.
ВаШите програми на С също могат да използват аргумента от
командния ред Те се предават на програмата на С чрез два ар-
гумента на функцията main( ). Тези параметри се наричат argon
argv. Както може би се досещате, тези параметри са незадължи-
телни и не присъстват, когато не се използват аргумента от ко-
мандния ред. Нека да разгледаме по-отблизо argc и argv.
Параметьрът argc съдържа броя на аргументите от команд-
ния ред и е цяло число. Той не може да бъде по-малък от 1, за-
щото името на програмата се определи като първи аргумент.
Параметьрът argv е масив от низови указатели. Най-често
срешаният метод за деклариране на argv е:
char *argv[1;
Празните скоби обозначават, че това с масив с неопределена
дължина. Всички аргумента от командния ред се предават на
main() като низове За да достигнете до определен низ, индек-
сирайте масива argv. Например argv[0] сочи името па програма
та, a argv| 1 ] - първия аргумент. Следващата програма отпечатва
всички командни аргументи, присъстващи при нейното изпъл-
нение.
Подробно р азглеждане на функциите 219
#include <stdio.h>
int main(int argc, char *argv[J)
{
int i;
for(i=l; i<argc; i++) printf("%s ", argv[i]);
return 0;
С няма изисквания какво трябва да съдържа един аргумент от
командния ред, тъй като в тази си част операционните системи
се различават съществено. Въпреки това най-честата конвенция
е: всеки аргумент от командния ред трябва да бъде отделен с ин-
тервал или табуляция. Залетайките, точките и залетайте и т.н. не
се считат за разделители. Например
This is a test
съдържа 4 низа, но
this,that,and,another
e само един низ.
Ако искате да предадете от командния ред аргумент, който
съдържа интервали, трябва да го поставите в кавички, както е
показано в примера:
"this is a test"
Имената на параметрите argc и argv са пропзволни - можете
да използвате каквито искате имена. По традиция обаче argc и
argv се употребяват от създаването па С. Добре е да ги използва-
те, така че всеки, който чете програмите ви, да може бързо да гм
разпознае като параметри от командния ред.
Още нетцо: стандарты ANSI С само дефинира параметрите
argc и argv. Вашият компилатор обаче би могьл да позволява
допълнителни параметри на main(). Например някои DOS- или
Windows-съвместими компилатори позволяват достъи до ин-
формация за средата чрез аргумента от командния ред, Провере-
те в наръчника на вашия компилатор за подобии възможности.
ПРИМЕРИ
1. Когато предавате числови данни на програмата, те ще бъ-
дат полутени във формата на низове. Вашата програма ще
трябва да ги конвертира в правилния вырешен формат
220 С - Практически самоучител
посредством една или друга стандартна библиотечна фун-
кция на С. Най-използваните функции за конвертиране са
показани тук посредством техните прототипи:
int atoi(char *str);
double atoffchar *str);
long atol(char *str);
Тези функции използват хсдърния файл STDL1B.H. Функ-
цияга atoi( ) връша int еквивалента на своя низов аргу-
мент. Функцията atof() връща double еквивалента на своя
низов аргумент, a atol() връща long. Лко извикате някоя
от тези функции с низ, който не е валидно число, ще полу-
чите стойност нула. Следващата програма демонстрира
използването на тези функции. За да я използвате, въведе-
те едно цяло число, едно дълго цяло число и едно число с
плаваща запетая в команд ш-я ред. След това те ще бъдат
изписани на екрана.
((include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int i ;
double d;
long 1;
i = atoi (argv[l]);
1 = atol(argv[2]);
d = ato£(argv[3]);
printf("%d %ld %f", i, 1, d);
return 0;
}
2. Тази програма преобразува унции в паунди. За да я изпол-
звате, въведете унциите в командния ред.
((include <stdio.h>
^include <stdlib-h>
int main(int argc, char *argv[J)
{
double pounds;
pounds = atof(argvfl]) / 16.0;
printf("%f pounds", pounds);
return 0;
}
Подробно разглеждане на функциите 221
3. Въпреки че примерите досега не го правят, в реалните
програми трябва да проверявате дали потребителят е въ-
вел на командния ред точния брой аргумента. Начинът да
направите това е да проверите стойностга на argc Прог-
рамата за преобразуване на унции в паунди например би
могла ла бъде подобрена по следния начин:
ttinclude <stdio.h>
ttinclude <stdlib.h>
int main(int argc, char *argv[])
{
double pounds;
if(argc!=2) {
printf ("Usage: CC14VERT <ounces>\n");
printf("Try Again");
else {
pounds = atof(argv[1]) / 16.0;
printf("%f pounds", pounds);
return 0;
По този начин програмата ще извършва преобразуване са-
мо ако на командния ред е въведен аргумент. (Разбира се,
бихте могли да изисквате от потребителя да въведе всяка
липсьаща информация.)
Предходната програми би била написана от професио-
нален програмист на С по следния начин.
ttinclude <stdro.h>
ttinclude <stdlib h>
int main(int arac,
{
aoubie pounds;
char *argv[])
if(argc!=2) {
printf("Usage: CONVERT <ounces>\n");
printf("Try Again");
exit(l); /* спира програмата */
pounds = atof (argv[1]) / 16.0;
printf("%f pounds", pounds);
return 0;
222 С - Практически самоучител
Когато никое условие, необходимо за работата на програ-
мата, не е изпълнено, повечето програмиста на С извикват
сгандартната библпотсчна функция cxit(), за да термини-
рат програмата. Функцията exit() има следния прототип:
vofd exit(int retum-codey
и използва хедърния файл STDLIB.H. Когато exit() тер-
минира програмата, тя връща стойността return-code {код
за резултата) на операционната система. По принцип по-
вечето операционни системи използват код за резултат,
нула ако програмата е завършила нормално. Ненулевите
стойности са индикация на ненормално прекъсване на
програмата
1 I {апишете програма, приема ща два аргумента от команд-
ния ред. Нека програмата да ги сравнява и да докладва кой
от тях е лексикографски по-голям.
2. Напишете програма, която приема два числови аргумента
и изчислява тяхпата сума.
3. Разширете програмата от упражнение 2 така, че да приема
три ар1умента. Първият аргумент трябва да е една от
следните думи: add, subtract, multiply или divide. Бази-
райки се на операцията, указана от първия аргумент, из-
вършетс съответната операция с двата числови аргумента.
/
-__________________________________________________________________
7.5
Сравняване на ло-старите
ДЕКЛАРАЦИИ НА ПАРАМЕТРИ СЪС
СЪВРЕМЕННИТЕ
Ранните версии на С използват различен метод за деклариране
на параметри от показания в тази книга. Този по стар метод е
наречен старо деклариране или класическо деклариране Форма-
та, използвана в тази книга, е така наречената модерна форма.
Тя бе въведена със създаването на стандарта ANSI С. Въпреки че
новата форма би трябвало да бъде използвана за всички нови
Подробно разглеждане на функциите 223
програми, все още можете да откриете класическо деклариране в
никои по-стари програми и трябва да сте запознати с него.
Общата форма на класическото деклариране е показана тук:
тип име-на-функция(параметър1, параметър2, ... napSMemupN)
тип параметър1,
тип парамепгьр2;
тип параметъра
{
код на функцията
}
Забележете, че декларацията е разделена на две части. Вътре в
скобите са зададени само имената на параметрите, а извън тях са
имената и типовете им. Например ако е дадена следната модерна
декларация
float i(char ch, long size, double max)
{
еквивалентната в класически сгил е:
float f(ch, size, max)
char ch;
long size;
double rax;
{
)
/Друг аспект на класическото деклариране е, че можете да зада-
вате повече от един параметьр след името на типа. Следващият
пример е напълно валиден:
myfuncfi, j, k)
int i, j, k;
{
224 С - Практически са моучител
Стандарты' ANSI С определя и двата начина на декларирапе
като валидни и ссновната причина за това е съв мести мостта със
старите програми. (Има буквално мипиони редове с код на С,
конто все още използват класическото деклариране.) Това озна-
чава, че ако се натькнеге на програми, използващи класическата
форма, не се притеснявайге - вашият компилатор ще ги компи-
лира без проблеми. Въпреки това за всички нови програми би
трябвало да използвате модерното деклариране.
ПРИМЕРИ
1. Следващата програма използва класичсска декларация:
#include <stdio.h>
int area(int 1, int w);
int maxn(void)
{
printf("area is %d", area(10, 13));
return 0;
}
int aread, w)
int 1, w;
{
return 1 * w;
)
Забележете, че въпреки че е използвано класическо декла-
риране на параметрите, е възмсжно използването на про
тотип.
1. Преобразувайте програмата така, че f_to m( ) да използва
класическо деклариране на параметрите.
#include <stdio.h>
double f to_m(double f);
int main(void)
double feet;
printf ("Enter feet: ");
Подробно разглеждане на функциите 225
scanf("%If", &feet);
printf("Meters: %f", f_tc m(feet));
return 0;
}
double f_to_m(double f)
(
return f / 3.28;
}
Проверка на уменията, представени в главата
В този момент трябва да сте в състояние да отговорите на
следващите въпроси и да изпълните следните упражнения:
1. Как се декларира прототип на функция, която няма пара-
метри?
2. Какво е прототип на функция и каква е ползата от него?
3. Как се предават аргументы от командный ред на програма
на С?
4 Напишете програма, използваща рекурсивна функция за
отпечатване на буквите от азбуката.
5 Напишете програма, която приема като аргумент от ко-
мандния ред низ. Нека го извежда в кодирана форма, като
кодирането се извършва чрез прибавянсто на 1 към всеки
знак.
6. Какъв е прототипът на тази функция?
double myfunc(int х, int у, char ch;
7. Покажете как функцията ст упражнение 6 би изглеждала в
класически стал на деклариране.
8. Какво правы функцията exit( )?
9 Какьо правы функцията atoi()?
226 С - Практически самоучител
Проверка на натрупаните умения
Тази част проверява доколко добре сте интегрирали материа-
ла от тази глава с този от предишните глави.
1. Напишете програма, позволяваща достъп само ако потре-
бителят въведе правилната пароли като аргумент от ко-
мандния ред. При въвеждане на правилната парола отпе-
чатайте на екрана Access Permitted, а в противен случай -
Access Denied.
2. Създайте функция, наречена string_up(), преобразуваща
низа, с който е извикана, в низ от знакове в горен регис-
тър. Демонстрирайте използването й в програма. (Съвет:
използвайте функцията toupper(), за да преобразувате
знаковете от долей в горен регистьр.)
3. Напишете функция на име avg(), изчисляваща средната
стойност на списък от параметри с плаваща запетая. Фун-
кцията ще има два аргумента. Първият е указател към ма-
сива, съдържащ стойностите, а вторият - цяло число,
указващо размера на масива. Демонстрирайте използване-
то й в програма.
4. Обяснете как указателите позволяват на С да създава па-
раметри от тип извикване но адрес.
8
Конзолен вход/изход
Разглеждани теми в главата
8.1 Още една предпроцесорна
директива
8.2 Въвеждане и извеждане на
знакове и низове
8.3 Някои нестандартни конзолни
функции
8.4 По-подробно разглеждане на
gets() и puts()
8.5 Овладяване на printf()
8.6 Овладяване на scanf()
228 С - Практически самоучител
В тази глава ще научите за конзолните входно/изходни
(I/O - input/output) функции на С. Това са функциите,
конто четат и извеждат информация към и от конзолата.
Вече използвахте някои от тези функции, но тук ще ги
разгледаме в детайли. Тази глава започва с отклонение, предста-
вящо друга предпроцесорна директива - //define.
Проверка на знанията
Преди да продължим, трябва да можете да отговорите на тези
въпроси и да направите упражненията:
1. Какво е необходимо, за да може компилаторът да направи
проверка за правилното извикване на дадена функция?
2. Какви са принципните предимства от използването на
прототипи на функции?
3. Напишете програма, използваща функция hypot( ), връ-
щаща дължината на хипотенузата на правоъгълен триъ-
гълник. Аргументите й са дължините на двете противопо-
ложни катета. Нека типът на функцията да бъде double.
Типът на параметрите също да бъде double. Демонстри-
райте функцията в програма. (Питагоровата теорема гласи,
че квадратьт от дължината на хипотенузата е равен на
сбора от квадратите на прилежащите катета.)
4. Какъв тип на резултата трябва да използвате за функция,
която не връща стойност?
5. Напишете рекурсивна функция rstrlen( ), изчисляваща
дължината на низ. Демонстрирайте работата й в програма.
6. Съставете програма, съобщаваща колко аргумента от ко-
мандния ред са извикани с нея. Нека тя да извежда и съ-
държанието на последния от тях.
7. Как ще кодирате този прототип, използвайки стария тип за
предварителна декларация?
void func(int a, char ch, double d)
{
Конзолен вход/изход 229
8.1
Още една предпроцесорна
ДИРЕКТИВА
Както вероятно си спомнятс, предпроцесорът на С изпълнява ня-
кои преобразования на соре кода на вашата програма, преди тя
да се компилира. Предпроцесорната директива е инструкция към
предпроцесора. Досега научихте и използвахте една предпроце-
сорна директива - //include. Преди да продолжим, ще трябва да
се за познаете с друга такава: //deline.
Директивата //define посочва па предпроцесора да извърши
текстово замесване из цялата програма. Това води до замяна на
една последователност от знакове с друга. Процесът е известен
като заместване на макроса. Общата форма на //define е показа-
на тук:
//define име-на-макрос знакова-последователност
Забележете, че този ред не завършва с точка и запетая. Всеки
път, когато бъде срещнато име-na- макрос, то се замена с асоци-
ираната последователност от знакове. Например вижте тази
програма:
Иinclude <stdio.h>
^define MAX 100
int main(void)
int i;
for(i=0; i<MAZ; i++) printf("%d ", i);
return 0;
Когато срещне идентификатора MAX, предпроцесорът автома-
тично го замена със 10о. Така за компилатора цикълът for ще из-
глежда по следния начин:
for(i=0; i<100; i + + ) printf("%d ", i);
Трябва обаче да запомните: по време на заместването 100 е
просто низ от знакове, състоящ се от 1 и две 0. Предпроцесорът
не преобразува числовите низове в техния вътрешен двоичен
формат, това е оставено на компилатора.
Име-на-макрос може да бъде всеки валиден за С идентифика-
тор I ой трябва да следва съшите правила, както и имената на
230 С - Практически самоучител
променливите. Въпреки че в име-на-макрос можете да използва-
те както малки, гака и главки букви, сред повечето програмисти
се е утвърдило правилото, да се използват само главни Това
прави значително по-лесно четенето на програмите.
Трябва да има един или няколко интервала между име-на-
макрос и знаковата последователност. Тя може да съдържа вся-
какви символи, включително и иптервали. Знаковата последова-
телност се терминира от края на реда.
Предпроцесорните директиви, в това число и #define, не се
влияят от блоковете с код на С. Това означава, че независимо
дали ще дефинирате име-на-макрос извън всички функции, или
вътре в някоя функция, всднъж дефинирано, коды след него има
досгьн до макроса. Например тази програма извежда на екрана
186000.
#include <stdio.h>
void f(void);
int main(void)
{
#define LIGHTSPEED 136000
f ();
return 0;
}
void f(void)
{
orintf("%ld", LIGHTSPEED);
}
Трябва да запомните: всяка предпроцесорпа директива трябва
да бъде па отделен ред.
Заместването на макроси е полезло по две причини. Първо,
много библиотечки функции па С използват определени предва
рително дефинирани стойности, за да посочват специални състэ-
яния или резултати. Вашите програми ще имат нужда от досты!
до тези стойности, когато използват тези функции. Много често
обаче истинските стойности ще се различават, в зависимост от
програмната среда. Поради тази причина, тези стойности се за-
дават с имена на макроси. Имената на макроси са дефинирани в
хедърния файл, съдържащ всяка отделна функция. Ще видите
примери за това в следващата част.
Втората причина за важността на заместването на макроси е(
че то може да улесни подържането на програмата. Например ако
знаете, че дадена стойност (като размерът на даден масив) ще се
Конзолен вход/изход 231
използва на ияколко места в програмата, по-добре е да създадете
макрос за тази стойност. По-късно, ако ви се наложи да ироме-
няте тази стойност, просто промените дефиницията на макроса.
Всички обръщения към нея ще бъдат автоматично променени
при следващото компилиране на програмата.
ПРИМЕРИ
1. Тъй като заместванията на макроси са просто замествания
на текст, можете да използвате име-на-макрос вместо низ,
ограден с кавички. Например следващата програма пзвеж-
да: Macro Substitutions are Fun.
#include <stdio.h>
#define FUN "Macro Substitutions are Fun"
int main(void)
{
printf(FUN);
return 0;
}
За компилатора printf() изглежда така:
printf("Macro Substitutions are Fun");
2. Веднъж дефинирано, името па макрос може да се използва
за дефинирането на друго такова. Например, разгледайте
следващата програма:
# include <stdio.h>
ftdefine SMALL 1
# define MEDIUM SMALL+1
# define LARGE MEDIUM+1
int main(void)
{
printf("%d %d %d", SMALL, MEDIUM, LARGE);
return 0;
}
Тя извежда на екрана 12 3.
3. Ако в дадеп низ, ограден в кавички, се появи име на мак-
рос, няма да се извърши заместване. Например, вижте
следната дефиниция:
232 С - Практически самоучител
^define ERROR "catastrophic error occurred"
в следващата конструкция няма да бьдс извършепа замяна
printf("ERROR: Try again");
1. Създайте програма, която дефинира два макроса, МАХ и
COLNTBY. Нека вашата програма брой от нула до
МАХ-1 със стъпка COU NT BY. (Дайте стойност 3 на
COU1NTBY за демонстрация на програмата.)
2. Верен ли е следният фрагмент?
#define MAX MIN+100
#define MIN 10
3. Верен ли е следният фрагмент?
#define STR this is a test
printf(STR);
4. Вярна ли e програмата?
#def.ine STDIO <stdio.h>
#include STDIO
int main(void)
{
printf("This is a test.");
return 0;
}
Конзолен вхсд/изход 233
8.2
ВЪВЕЖДАНЕ И ИЗВЕЖДАНЕ НА
ЗНАКОВЕИ НИЗОВЕ
Въпреки че вече разбракте как да въвеждате и извеждате знакове
и низове, тази секция разглежда този процес по-подробно.
Стандарты ANSI С дефннира следните две функции, конто
изпълняват въвеждането и извеждането на знакове:
int gelchar(void);
mt putchar(int ch),
И двете използват хедърния файл STD1O.H .Както бе споменато
по-рано, много комггилатори изпълняват getchar() чрез линейно
буфериране, което ограничава употребата й в интерактивна сре-
да. Повечето комнилатори притежават нестандартната функция,
наречена getchc(), работеща като gctchar(), но интерактивно.
Ще разгледаме getche() и други нестандартни функции в никоя
от следващите секции.
Функцията getchar() връща следващия символ, вьведеп от
клавиатурата. Този символ се чете като unsigned char и се пре-
образува в int. Най-често вашите програми ще нрисвояваг тази
стойност на char променлива, въпреки че getchar() е декларира-
на да връща int. Присвояването се извършва р отрязването на
старшия(те) байт(ове) на пелочислената променлива.
Причината getchar() да връща цяло число е, че при ноявата
на грешка при въвеждането, функцията връща макроса EOF. Той
е отрицателно цяло число (обикновено -1). Макросы EOF е де-
финиран в хедърния файл STUDIO.H и означава край на файла.
Тъй като EOF е цяло число, за да с възможно връшането му,
getchar( ) трябва да връща цяло число. В повечето случаи, ако се
появи грешка при въвеждане от клавиатурата, това означава, че
компютьрът е блокирал. Поради тази причина много от програ-
мистите не проверяват за ноявата на EOF, когато използват
getchar( ). Те приемах, че е въведсн коректен знак. Разбира се,
има и обстоятелства, при конто тсва не е лрието - например ако
входът/изходът е нренасочен, както е обяснено в глава 9. В нове-
чето случаи обаче не трябва да се тревожите за грешки при из-
пълнение на getchar()
Функцията putchar( ) извежда на екрана единствен знак.
Въпреки че параметьрът й е деклариран от тин int, неговата
стойност се конвертира от функцията в unsigned char. Така се
извежда само младшият байт на ch. Ако извеждащата операция е
успешна, putchar() връща изведения символ. Ако се появи
грешка при извеждането, се връша EOF. По причини, подобии
на тези при getchar(), ако извеждането не бъде извършено, най-
234 С - Практически самоучител
вероятно компютърът е блокирал, така че повечето програмисти
не проверяват върната от putcliar() стойност за грешка.
Причината, пора ди която бихте иредпочели да използвате
putchar() вместо printf() със спецификатор %с за извсждане на
знак е, че putcliar() е но-бърза и далеч по-ефективиа. Тъй като
printf() с доста по-мощна и гъвкава, едно пейно извикване гене-
рира много повече натоварване, отколкото putcharf).
ПРИМЕРИ
1. Както бе посочено по-рано, getchar() използва линейно
буфсриране. Когато въвеждането е линейно буферирано,
не се предават символи, докато не бъде натиснат ENTER.
Следващата програмата демонстрира това:
frinclude <stdiq.h>
int main(void)
{
char ch;
do {
ch = qetchar();
putchar(’.');
} while(ch != *\n’);
return 0;
}
Вместо да извежда точка, между всеки символ, ще видите
всички букви, въведени преди да натиснете ENTER, след-
вани от низ от точки.
И още нещо: при въвеждането на символи с getchar()
натискането на ENTER води до връщане на символ за край
на ред (\п) При използването па алтернативните нестан-
дартни функции, натискането на ENTER, води до връщане
на символа за нов ред (\г). Запомнете тази разлика.
2. Следващата програма показва, че в С можете да използва-
те константите с обратно наклонена черта с функцията
putchar().
#jnclude <stdio.h>
int main(void)
{
putchar(’A’);
putchar(’\n');
putchar(’В');
return 0;
}
Конзолбн вход/изход 235
Програмата извежда на екрана.
А
В
«ШИ
1. Пренапишете програмата от пример 1, така че да проверя-
ва за грешки при операции за въвеждане и извеждапе.
2. Какво е грешно в следния фрагмент?
char str[30] = "this is a test";
putchar(str);
8.3
НЯКОИ НЕСТАНДАРТЕН КОНЗОЛНИ
ФУНКЦИИ
Тъй като въвеждането на символи с getchar( ) е линейно буфе-
рирапо, много от компилаторите са снабдени с допълнителни
начини за интерактивно въвеждане на символи. Вече се запоз-
нахте с един от тях: функцията getche( ). Тук е даден нейният
прототип, както и на близката до нея getch():
int getche(void);
int getch(void);
И двете функции използват хедърния файл CONIO.H. Функцията
getche( ) изчаква натискапето на следващия клавиш. Когато бъде
патиснат клавиш, getche() го извежда на екрана, след което не-
забавно връща знака. Този знак се чете като unsigned char и се
повишава до int. Вашите програми обаче могат просто да прис-
воят тази стойност на char променлива. Функцията gctch() е
същата, както getche( ), само че въведеният символ нс се извеж-
да на екрана.
236 С - Практически самоучител
Друга, много полезна, невключена в стандарта ANSI
функция, с която са снабдени повечето компилатори на С, е
kbhit(). Нейният прототип е:
int kbhit(void);
Тази функция също изисква хедърния файл CON1O.H. Тя се из-
ползва за олределяне дали е натиснат клавиш или не. При натис-
кансто на такъв, функцията връща истина (непулева стойност),
но не чете символа. Ако клавишът все още е натиснат, можете да
го прочетете с getche( ) или getch(). Ако не бъде натиснат кла-
виш kbhit(), връща неистина (нула).
За някои компилатори нестандартните входно/изходни функ-
ции, като getche( ), са несъвместими със стандартните такива,
като printf() и scauf() Когато това се случи, съвместната им
унотреба би могла да причини необичайно поведение на програ-
мата. Повечето проблеми, причинени от подобна несъвмести-
мост, се появяват при въвеждане на информация (въпреки че би-
ха могли да се появят и при извеждането й). Ако стандартните и
нестандартните входно/изходни функции са несъвместими във
вашия компилятор, може би ще ви се наложи да използвате нес-
тандартни версии на scanf() и/или printf(). Това са cprintR) и
cscanf().
Функцията cprintf() работа като printf( ), само че не конвер-
тера знака за нов ред (\п) в двойката CR/I F, както printf(). По-
ради това е нужно да извеждате знак за CR (\г), където трябва.
Функцията cscanf() действа като scanf(). Функциите cprintf() и
cscanf( ) използват хедърния файл CONIO.il. Те са специално
проектираки да бъдат съвместими с geteh() и getche(), както и с
останалите нестандартни входно/изходни функции.
Microsoft C++ подържа току-що описан ите функции. Освен то-
ва поёържа и алтернативни имена на функциите. започеащи с
долна черта. Например във Visual C++ можете да извикате
getche() и като _getche().
На последно място: дори за компилатори, имащи несъьмес-
тимост между стандартните и нестандартните входно/изходни
функции, тези несъвместимссти могат да предизвикат проблеми
в някои случаи и да не предизвикат такива в други. Ако откриезе
подобии проблеми, опитайте с друга функция.
Конзолен вход/изход 237
ПРИМЕРИ
1. Функцията getch() дава възможност за по-голям контрол
върху екрана, защото можете да определите какво да се
извежда при всяко иатискане на клавиш В следвашия
пример, програмата чете знакове, докато бъде въведено
‘q*. Всички знакове се извеждат с главни букви посредст-
вом функцията cprintf().
((include
(li nclude
#include
<stdio• h>
<conio.h>
<ctype.h>
int main(void)
char ch;
do {
ch = getch();
cprintf("%c", toupper(ch));
} while(ch != ' q');
return 0;
}
2. Функцията kbhit() e много полезна, когато желаете да да-
дете възможност на потребителя да прекъсне дадено из-
пьлнение, без да го карате непрекъснато да отговаря на
въпрос, като “Продължавате ли?”. Например следващата
програма извсада таблица с 5-процентови данъци за про-
дажби с нарастване от 20 цента. Програмата продължава
да извежда таблицата докато бъде натиснат клавиш или
докато се достигне най-високата стойност.
((include <stdio.h>
((include <conio.h>
int main(void)
(
double amount;
amount = 0.20;
cprintf("Printing 5-percent tax table\n\r");
cprintf("Press a key tc stop.\n\n\r");
do {
cprintf("amount: %f, tax: %f\n\r", amount,
airount *0.05) ;
if(kbhit()) break;
amount = amount + 0.20;
} while(amount < 100 0);
return 0;
238 С - Практически самоучител
При извикванияга на cprlntf() забележете, че и двата зна-
ка - \г и \п - трябва да се отпечатат. Както вече бе облене-
но, функцията не конвертира автоматично знак за нов ред
(\п ) в двойката CR/LF.
1. Напишете програма, извеждаща ASCII кодозете на всеки
въведен символ. Не трябва обаче да извеждате самия сим-
вол.
2. Напишете програма, извеждаща точки на екрана, докато
бъде натиснат клавиш.
8.4
ПО-ПОДРОБНО РАЗГЛЕЖДАНЕ НА
GETS() ИPUTS()
Въпреки че и двете функции, gets() и pnts(), бяха представени
по-рано, нека сега ги разгледаме по-подробно. Техните прототи-
пи са:
char *gets(char *str);
int puts(char *str);
Тези функции използват хедърния файл STD1O.H. Функцията
gets( ) чете знакове, въведени от клавиатурата, докато стигне до
знак за край на ред (те., докато бъде натиснат ENTER). Тя съхра-
нява въведените символи в масив, сочен о г str. Символът за край
на ред не се включва в низа. Той се конвертира в нулев термина-
тор. Ако изпълнението й е успешно, gets() връща указател. со-
чещ към началото на str. Ако се появи грешка, връща нулев ука-
зател.
Функцията pnts( ) извежда на екрана низ, сочен от str. Тя ав-
томатично добавя двойката CR/LF. При успешно изпълнение,
puts( ) връща неотрицателна стойност. Ако се появи грешка,
врътца F.OF.
Главната причина, поради която се използва puts() вместо
printf() за извеждането на низ с, че puts() е много по-малка и
по-бърза. Въпреки че това не е важно за примерните програми,
разглеждани в тази книга, то може да е съществено в никои за-
дачи.
Конзслен вход/изход 239
ПРИМЕРИ
1. Тази програма показва как бихте могли да използвате
връщаната от gets() стойност, за да достигнете до низа,
съдържаш информацията за въвеждането. Забележетс, че
програмата проверява дали е имало грешка преди да из-
ползва низа.
#include <stdio.h>
int main(void)
{
char *p, str [80];
printf("Enter a string: ");
p = gets(str);
if(p) /* ако не e нула */
printf T'%s %s", p, str);
return 0;
2. Ако прели да продължите искате да сто сигурни, че gets()
не е открила грешка, можете да я разположите директив в
конструкция за проверка if, както е посочено в следващата
програма:
ttinclude <stdio.h>
int main(void)
{
char str[80];
printf("Enter a string: ");
if(gets(str)) /* ако не e нула */
printf("Here is your string: is”, str);
return 0;
Тъй като пулевият указател е false, в този случай няма
нужда от междинпата променлива р и конструкцията
gets() може да се разположи директив в конструкцията if.
3. Важно е да разберете, че макар gets( ) да връща указател
към началото на масив, тя все пак трябва да бъде извикана
с указател към действителен масив. Например долното е
грешно:
char *р;
р = gets(р); /* грешно!!! */
240 С - Практ ически самоучител
В този случай не е дефиниран масив, в който gets( ) може
да постави низа. Това ще предизвика 1решка в програмата.
4. Следващата програма извежда думите one, two и three на
три отделки реда посредством puts().
♦include <stdlc.h>
int main(void)
{
puts("one");
puts("two");
puts("three");
return 0;
1. Компилирайте програмата от пример 2. Забележете разме-
ра на компилирания код. След това я пренапишете посред-
ством printf( ) вместо puts( ). Ще видите, че версията, из-
ползваша printf(), е с пяколко байта по-голяма.
2. Варна ли е програмата? Ако не, защо?
♦include <stdio.h>
int main(void)
{
char *pf *q;
printf("Enter a string: ”);
p = gets(q);
printf(p);
return 0;
Овладяване ha printf( )
Въпреки че вече знаете много нсща за priirtf(), ще бъдете изне-
надани колко още възможности има в нея. В тази секция ще нау-
чите за иякои от тях. За да започнем пека прегледаме това, което
знаете досега.
Функцията printf() има следния прототип:
Конзолен вход/изход 241
int printf(char * контролен-низ,...); Многоточиетс показва наличието на списък с променлив брой аргумента. Функцията printf() връща броя на изведените сим воли. Лко има грешка, тя връща отрицателна стойност. Честно казано, много малко програмиста проверяват връщаната от printf() стойнсст, защото както вече бс споменато, ако конзола- та не работа,, най-вероятно компютърът е блокирал. Контролният низ може да съдържа два типа данни: извежда- ни знакове и форматни спецификатор!!. Всички форматни спе- цификатори започват с %. Форматният спецификатор, наричан също и като форматиращ код, определя как ще бъде изведен ар- гументът, асоцииран с него. Форматните спецификатори и тех- нике аргумента се асоциират отляво надясно и трябва да има толкова аргумента, колкого са спецификаторите. Приеманите от printf() форматни спецификатор!! са показани в таблица 8-1. Вече нознавате спецификаторите %с, %d, %s, %u, %р и %f. Останалите ще разгледаме сега.
Код / Формат
%с Знак
%d Десетично числи със знак
%i Десетично число със знак
%е Научно представяне (с малко 'е’)
%Е Научно представяне (с главно ‘Е’)
%f Десетично число с плаваща запетая
%g %G Използва %е или %f - което е по-късо Използва %Е или %f- което е по-късо
%0 Осмично число без знак
%s Низ от знакове
%u Десетично число без знак
%x Шесгнадесетично число без знак (малки букви)
%X Шестнадесетично число без знак (главни букви)
%p %n Изеежда указател Асоциираният аргумент е указател към цяло число в което се предава броя на стпечатаните до момента знакове
%% Извежда знака %
Форматните спецификаторы за printff)
242 С - Практически самоучител
Спецификаторы %i е сьщият като %d и затова е излишен.
Можете да извеждате числа от тип float и double посредством
научно представяне чрез спецификаторите %е или %Е. Ьдипст-
вепата разлика между тях е, че %е използва малко ‘е’, а %Е -
главно ‘Е’. Тези спсцификатори могат да имат приложен моди-
фикатор L, позволяващ им да извеждат числа от тип long double.
Спецификаторите %g и %G извеждат аргумента в нормален
или научен формат, в зависимост от това, кой е по-къс. Разлика-
та между %g и %G е, че ‘е’ ше бъде сьответно в долей или го-
рен регистьр при по-късата научна форма. Спецификаторите мо-
гат да имат модификатор L, позволяващ им да извеждат числа от
тип long double.
Можете да изведете цяло число в осмичеп формат посредст-
вом %о или в шестнадесе гичен формат чрез %х или %Х. Из-
ползването на %х води до извеждапс па буквите от ‘а’ до Т в
долей регистьр. %Х ги извежда в горен региегьр. Тези специфи-
катэри могат да имат модификатори h и 1, позволяващи им да
извеждат съответно short и long числа.
Ар1ументы, асоцииран със спецификатора %п, трябва да е
указател към цяло число. Когато срещне %n, printf() присвоява
на цяло го число, към което сочи указателят, броя на изведените
до момента знакове.
Тъй като всички форматни спецификатори започват със знак
за процент, за да изведете самия знак, използвайте %%.
Всички спецификатори, с изключение на %%, %р и %с, мо-
гат да имат асоциирани спецификатор за минимална ширина на
полето и/или спецификатор за т очност. И двата спецификатора
са цели числа. Ако извежданото число е по-кьео от споменатото
в спецификатора за минимална ширина, то се допълва с интерва-
ли, така че да отговаря на минималната дължина. Ако числото е
по дълго от минималната широчина на полето, то не се отрязва.
Спецификаторы за минимална широчина се разполага след про-
центния знак и преди форматния спецификатор.
Спецификаторы- за точност следва спецификатора за мини-
мална дължина. Разделени са един от друг с точка. Специфика-
торы за точност влияе различно върху различните спецификато-
ри. Ако е приложен към %d, %i, %о, %u и %х, той олределя
колко цифри да се отпечатат. При необходимост се прибавят во-
дещи нули Когато е приложен към %f, %е и %Е, опрсделя кол-
ко цифри ще бъдат изведени след десетичната запетая За %g и
%G, определи брея на значещитс цифри. Приложен към %s, оп-
редели максималната дължина на полето. Ако низы е по-дълъг
от указаното, той ще бъде отрязан.
Конзолен вход/изход 243
По подразбиране всички извеждани числови стойности сс
дясно подравнени. За да бъдат лябо подравнени, сложете знак
минус веднага след знака %.
Основната форма на всеки форматен спецификатор е показа-
на по-долу. Незадължителните елемспти са заградени в квадрат-
ни скоби.
%[-][минимална-широчина-на-полегпо][.][точност]форматен-
опрвделител
Например следващият форматен спецификатор указва на
printf( ) да изведе double като използва поле с ширина 15, с два
знака след десетичната запетая.
%15.2f
ПРИМЕРИ
1. Ако не желаете да определятс минималната ширина на ио-
лето, все пак можете да укажете него вата точност. Просто
поставете точка пред стойпостта на определитепя за точ-
ност, както е направено в следващата програма:
#include <stdio.h>
int main(void)
(
printf("%.5d\n", 10);
printf("$%.2f\n", 99.95);
printf("%.10s", "Not all of this will be
printedXn");
return 0;
Изведеното от тази програма изглежда така:
00010
$99.95
Not all of
Забележете ефекта от спецификатора за тсчност и негово-
то отражение върху извеждането на отделяйте типове
данни.
2. Спецификаторът за минимална ширина на полето е особе-
но полезен за създаването на таблици. съдържа щи колони
с цифри, конто трябва да са подравнени. Например тази
програма извежда 1000 случайпи числа в три колони. Тя
244 С - Практически самоучител
използва стандартната библиотечна функция на С rand( ),
за да генерира случайни числа. Функцията връща случай-
но генерирано число всеки път, когато бъде извикана, и
използва хедърния файл STDLIB.H.
♦ include <stdio.h;>
♦include <stdlib.h>
int main(void)
{
int i;
for(i=0; i<1000; i++)
printf(”%10d %10d %10d\n", rand(), rand{),
rand ());
return 0;
}
Част от изведеното от тази програма е показано тук. Забе-
лежете как са подравнени колоните. (Запомнете: Ако из-
пълните програмата, най-вероятно ще видите различии
числа.)
13254 24685 31124
24543 25464 26541
30654 3412 2240
5450 28798 3465
12344 5540 25413
24301 16760 15768
910 761 24066
28489 12106 21345
30687 18540 64
17413 26332 24650
24504 64 65 15430
25532 12046 15132
26231 19465 5106
3465 22343 2045
1310 10540 24543
11320 23412 25820
12351 23210 5530
23340 12506 20635
4304 26635 3200
3. Следващата програма отпечатва стойностга 90 по четири
различии начина: десетичен, осмичен, шестнадесетичен с
малки букви и шестнадесетичен с главки букви. Тя извежда
и число с плаваща запетая, използвайки малко и главно ‘е’.
Конзолен вход/изход 245
ilinclude <stdio.h>
int main(void)
{
printf("%d %o %x %X\n", 90, 90, 90, 90);
printf("%e %E\n”, 99.231, 99.231);
return 0;
}
Резултатът от програмата e :
90 132 5a 5A
9.92310e+01 9 92310E+01
4. Следващата програма демонстрира използването па спе-
цификатора %п:
#include <stdio.h>
int main(void)
{
int i;
printf("%d %f\n%n", 100, 123.23, &i) ;
printf (,,!td characters output so far", i) ;
return 0;
)
Резултатът изглежда така:
100 123 23000G
15 character output so far
Петпадесетият знак e за край на ред.
1. Напишете програма, извеждаща таблица от числа, като
всеки ред съдържа число, квадрата му и неговия куб. Нека
таблицата да започва от 2 и да завършва на 100. Колоните
й да са подравнени, а цифрите във тях да са ляво под-
равнени.
2. Изведеге следният ред посредством printf( )?
246 С - Практически самоучител
Clearance price 40% off as marked
3. Покажете как се извежда 1023.03, така че да са отпечатал
само две десетични цифри след запетаята
8.6
ОВЛАДЯВАНЕ НА SCANFf )
Както printf( ), така и scanf() има много възможнссти, неизпол-
звани до момента от нас. В тази секция ще се запознаем с никои
от тях. Нека да започнем с преглед на това, което сте научили до
момента.
Прототипът на scanf() е показан тук:
int scanf(char "контролен-низ, ...);
Контролният низ се състои предимно от формагни спецификато-
ри. Макар че може да съдържа и други знакове. (Скоро ще нау-
чите за ефекта, оказван от другите знакове в контролния низ.)
Форматните спецификатори указват как scanf() да присвоява че-
тената информапияга на променливите, сочени ст аргументите
след контролния низ. Спецификаторите се асоциират с ар j у мен-
тиле по ред, отляво надясно. Трябва да има толкова аргумента,
колкого са и спецификаторите. Спецификаторите са посочени га
в таблица 8-2. Както можете да се уверите, спецификаторите на
scanf() са почти същите, като тези на printf().
Функцията scanf() връща броя на полетата с присвоени стой-
ности. Ако се появи грешка преди да е направено някакво прис-
вояване, функцията връща EOF.
Спецификаторите %х и %о се използват за четене на цели
числа без знак, базирани съответно на шестпадесетична и ос-
мична система.
Спецификаторите %d, %i, %u, %х и %о могат да се моди-
фицират с модификатор h, когато въвеждат в short променлива,
и с модификатор 1, когато въвеждат в long променлива.
Спецификаторите %е, %f и %g са еквивалентни. Всички те
четат числа с плаваща запетая, представени в десетичен формат
или в научен запис. Немодифицирани, те предавал стойности на
променливи от тип float. Но можете да ги модифицирате с 1, ко-
гато въвеждате променливи от тип double. За да четете long
double, модифицирайте ги с L.
Конзолен вход/изход 247
Код Значение
%с Чете знак
%d Чете десетично цяло число
%i Чете десетично цяло число
%е Чете число с плаваща запетая
%f Чете число с плаваща запетая
%д %о Чете число с плаваща запетая Чете осмично число
%s Чете низ
%х Чете шестнадесетичнс число
%р %п Чете указател Получава стойност на броя въведени до момента знакове
%и Чете цяло число без знак
%о Търси множество ст знакове
Таблица 8-2
Форматните спецификатора на scanff)
Можете да използвате scanf(), за да четете низ чрез специфика-
тора %s, но вероятно не бихте го направили, тъй като scanf()
спира при срещане на празен символ (това може да бъде интер-
вал, табуляция или символ за нов ред). Това означава, че трудно
бихте използвали scanf() за прочитане на низ, подобен на следния:
this is one string
Тьй като чма интервал след “this”, scanf( ) ще спре въвеждането
на тази позиция. Затова за въвеждане на низове обикновено се
използва gets().
Спецификаторът %р въвежда адрес в паметга посредством
формата, определен от средата. Спецификаторът %п присвоява
броя на знаковете, въведени до момента на целочислена промен-
лива, сочена ст асоциирания с него аргумент. Той може да бъде
модифициран с 1 или h, така че да присвоява стойност на цело-
числена променлива от типа long или short.
Има една много интересна възможност на scanf( ), наречена
scanset (множество затьреене). Този спецификатор се създава
като поставите списък със знакове в квадратни скоби. Например
тук имате спецификатор scansei, съдържащ бу квите ’АВС’.
%[АБС]
248 С - Практически самоучител
Когато scanf( ) срещне множество за търсене, тя започва да
чете въвежданото в знаковия масив, сочен от асоциирания със
множество™ за търсене аргумент. Тя ще продължи да чете сим-
волите, докато бъде въведен знак, различен от тези в специфика-
тора. Когато бъде прочетен различен знак, scanf() спира четене-
то за този спецификатор и се прехвьрля на следващия елемент от
контролния списък.
Можете да определите облает в множество™ за търсене пос-
редством тире (-). Например това множество за търсене задаза
областта от буквите от ‘А’ до ‘Z’.
%[A-Z]
Погледнато технически, използването на области не е определе-
но от стандарт a ANSI С, но е широко прието.
Когато областта на множеството за търсене е много голяма, е
по-лесно да ее определи кои символи да не влизат в нея. За да
направите това, ноставете ‘л’ преди множеството. Например
% Г0123456789]
Когато scanf( ) срещне това множество за търсене, тя ще чете
всички символи без цифрите от 0 до 9.
Можете да подтиснете присвсявансто на дадено поле, като
поставите звездичка (*) веднага след знака за процент (%). Това
е особено полезно, когато се въвежда информация, съдържаща
ненужни символи. Например, вижте това извикване на scanf(),
int first, second;
scanf("fcdi’cld", &first, ^second);
което при въвеждането на
555-2345
ще предизвика присвояване на 555 на first, пренебрегваие на ти-
рето и присвояване на 2345 на second Тъй като тирсто не е съ-
шествена информация, няма нужда да се присвоява на аргумент
и да се отдели място за тази информация.
Можете да посочите и поле с максимална ширина за всички •
специфлкатори с изключение на %с, за кой го полете винаги с
един знак, и на %п, който не се включва в спецификатсрите за
въвеждане на данни. Максималната ширина на полете е цяло
число, което стой след знака ‘%’. Например следното определя
максималната ширина на низа, присвоен на str, да бъде 20 знаке:
Конзолен вход/изход 249
scanf("%20s", str);
Ако се появи правей знак в контролния низ, scanf() ще за-
почне четенето на низа, пренебрегвайки празните знакове, дока-
то срещне първия непразен. Ако се появи някакъв друг знак з
контролния низ, scanf( ) огхвърля всички съвпадащи знакове,
докато прочете първия различен.
Оше нещо: кот ато scanf() е замисляна, гя е била линейно бу-
ферирана по сыция начин, като getchar(). Макар че това нс от
значение при въвеждането на числа, то прави scanf() малко по-
лезна за друг тип въвеждания.
ПРИМЕРИ
1. За да видите какъв е ефектът от спецификатора %s, опи-
тайте следващата програма. При изискването за въвеждане
напишете this is a test и натиснете ENTER. На екрана ще се
изведе само this. Това с така, защото при четснето на пн-
зове scanf() спира, когато срещне първия празен символ.
^include <stdio.h>
int main(void)
{
char str[80];
/* Въведете "this is a test" */
printf("Enter a string: ");
scanf("%s", str);
printf(str);
return 0;
2. Следва пример за употребата на множество за тьрссне, ко-
его приема малки и главни букви. Опитайте се да въведеге
някакви букви, а след това някакъв друг символ, след коего
други букви. След натискане на enter в str ше се пазят само
буквите, конто сте въвели пред» нсбуквения символ.
#include <stdio.h>
int main(void)
I
char str[80];
printf("Enter letters, anything else to stop\n");
scanf("%[a-zA~Z]", str);
printf(str);
250 С - Практически самоучител
return Э;
}
3. Ако желаете да въведете низ, съдържащ интервали, пос-
редством scanf(), можете да направите малка промина на
гориата програма.
(♦include <stdio h>
int main(void)
{
char str i.80] ;
printf("Enter letters and spacds\n");
scanf ("%[a-zA-Z str);
printf(str);
return 0;
)
Можете също така да включите различии разделители и
други символи, така че индиректно да прочетете всяка-
къв низ. Това обаче паистина е сложен начин за да то
постигнете.
4 Тази програма позвслява на потребители да въведе число,
следвано от операнд, и второ число, като например 12+4.
След това извършва операцията с числата и извежда ре-
зултата.
#include <stdio.h>
int main(void)
{
int i, j;
char op;
printf("Enter operation: ");
scanf("%d%c%d", &i, sop, &j);
switch(op) {
case printf("%d", i+j);
break;
case printf("%d", i-j);
break;
case if (j) printf("%d", i/j);
break;
case printf("%d", i*j);
return 0;
Конзолен вход^изход 251
Забележете, че форматът за въвеждане не позволява ин-
тервали между първото число и операнда. Може да пре-
махнете това неудобство. Както знаете, scanf() автома-
тично премахва дразните символи, като изключение прави
само спецификаторът %с. Тъй като сме сигурни, че опе-
рандът не би бил такъв символ, можем да модифицираме
извикването на scanf() да изглежда така:
scanf("%d %c%d", &i, &op, &j);
Независимо дали има интервал в контролыия низ или не,
scanf() ше прескочи всички празни символи, докато стиг-
не до първия непразен, това включва и незначещите нули,
С преобразуваната по този начин програма можете да въ-
веждате и интервали между първото число и оператора
5. Следващата програма показва употребата на специфика-
тора за максимална ширина на попето:
ttinclude <stdio.h>
int main(void)
int i, j;
printf("Enter an integer:
scanf("%3d%d", &i, & j ) ;
printf("%d %d", i, j);
return 0;
}
Ако стартирате програмата и въведете числото 12345, на i
ще се присвой стойност 123. a j ще има стойност 45 При-
чината за това е, че на scanf() е указано, че i ще съдържа
само 3 знака Остатъкът от въведеното се дава за стойност
на j
6 Следващата програма показва ефекта от присъствието на
непразни знакове в контролния низ. Тя ви позволява да
въведете десетична стойност, но присвоява лявата част на
числото (т.е., цифрите преди десетичната запетая) на една
цяла променлива, а дробната част - на друга. Десетичната
точка между двата спецификатора води до пренебрегване-
то на въведената десетична точка.
ttinclude <stdio.h>
int main(void)
{
int i, j;
2S2 С - Практически самоучител
printf("Enter a decimal number: ");
scanf{"%d-%d", &i, &j);
printf("left part: %d, right part: %d", i, j);
return 0;
}
1. Напишете програма, която ви пита за вашего пълно име,
след което извежда вашего първо и второ име и фамилия-
та ви. Нека програмата да чете не повече от 20 символа за
всяко от имената. Накрая програмата да извежда отново
вашего пълно име.
2. Напишете програма, четяща число с плаваща запетая като
низ, посредством множество за гърсене.
3. Верен ли е фрагменты? Ако не, защо?
char ch;
scanf("%2с", ich);
4. Напишете програма, която въвежда низ, едно double число
и едно цяло число. След като прочете стойност пте, пека да
изведе колко знака са били въведени. (Съвег: използвайте
спецификатора %п.)
5. Напишете програма, п реобразу ваща шесгнадесстично
число, въведепо от потребителя, в съответният му десети-
чеп и осмичен еквивалент.
Проверка на уменията, представени в главата
Преди да продължим, трябва да можете да отговорите на тези
въпроси и да изпълните упражненията:
1. Каква е разликата между getchar( ), getche( ) и getch()?
Конзолен вход/изход 253
2. Каква е разликата между форматнитс спецификаторы %е
и %Е на printf()?
3. Какво е мно?кество за гьрсене?
4 Напишете програма, използваща scanf(), която въвежда
първото ви име, рождената ви дата (ползвайте формата
мм/дд/гг) и телефония ви номер. Изведете информацията
на екрана за да се уверите, че е въведена вярно
5. Какво е предимствого от използването на puts() пред
printf(), когато се извежда низ? Какъв е недсстатъкьт на
puts()?
6 Напишете програма, дефинираща макрос COUNT като
стойност 100, и го използвайте в цикъл for за извежданс
ца числата от 0 до 99.
7. Какво е EOF и къде е дефиниран?
Проверка на натрупаните умения
Тази част проверява колко добре сте интегрирали материала
от пастоящата глава с този от предходнитс.
1. Напишете програма, позволяваща ви да въвеждате срод-
ните резултати на 9 играчи. Нека погребителят въведе
първите имена на играчите и техните резултати. Използ-
вайте двумерен масив от знакове за имената на играчите и
масив от double за резултатите. След като всички данни са
въведени, нека програмата извежда информация за игра-
чите с най добьр и най-лош резултат, като и средния ре-
зултат на отбора.
2. Напишете програма, която е прост електронен библиоте-
чен каталог. Нека тя да извежда следното меню:
Card Catalog:
1. Enter
2. Search by Author
3. Search by Title
4 Quit
Choose your selection:
254 С - Практически самоучител
Ако бъде избрано Enter, нека програмата да въвежда име,
автор и издател на книгите. Този процес да продължава,
докато потребителят въъеде празен ред за име на книга.
При търсенето, нека потребителят да укаже автор или
заглавие и ако то бъде намерено в каталога, да се изведе
останалата информация След като завършите тази прог-
рама, запазете файла си, защото в следващата глава ще на-
учите как да записвате файла па диск.
9
Файлов вход/изход
Раэглеждани теми в главата
9.1 Какво представляват
потоците
9.2 Основите на файловата
система
9.3 feof() и ferror()
9.4 Някои текстови функции от
високо ниво
9.5 Четене и записване на
двоични данни
9.G Произволен достъп
9.7 Различии функции на
файловата система
9.8 Стандартните потоци
256 С - Практически самоучител
Въпреки че С няма дефинирани ключови думи за извърщ-
ване на файлов вход/изход, стандартната библиотека аа
С съдържа богата колекция от входно/изходни функции.
Както ще се уверите в тази глава, възможностите, конто
дава С, са ефикасни и гъвкави.
Повечето компилатори на С са снабдени с два цялостни комп-
лекта от функции за файлов вход/изход. Единият е т. нар.
файловата система ANSI (наричана още и буферираната фай-
лова система). Тя е дефинирана от стандарта ANSI С. Вто
рата файлова система е базирана на операционната система
UNIX и се нарича файловата система на UNIX (позната и като
небуферираната файлова система). Тази система не влиза в
стандарта ANSI С, тъй като две файлоеи системе биха били
много. Не всички олерационни системи биха могли да прилагаю
файловата система UNIX и поради тази причина настоящата
книга разглежда само файловата система ANSI. За по-подроб-
на информация отпоено файловата система UNIX, вижте кни-
гата ми С: The Complete Reference (Berkley, CA, Osbcme/McGiaw-
Hill).
Проверка на знанията
Преди да продължим, би грябвало да можете да направите те-
зи упражнения и да отговорите на въпросите;
1. Каква е разликата между getchar( ) и getche()?
2. Кажете една причина, поради която не бихте използвали
9canf() със спецификатора %s, за да четсте низове?
3. Напишете програма, извеждаща четириколонна таблица
на простите числа между 2 и 1000. Подравнете колоните.
4. Напишете прот рама, въвеждаща double стойност, знак и
низ до 20 знака. Изведете ги отново на екрана, за да пот-
върдите правилното им въвеждане.
5. Напишете програма, кояго чете и отхвьрля водещитс циф-
ри и след това прочита низ. (Съвет: използвайте scanset, за
да прочетете без да присвоявате водещите цифри.)
Файлов вход/изход 257
9.1
Какво представляват потоците
Преди да започнем с изясняването на файловия вход/изход, тряб-
ва да разберете две много важни концепции: поток и файл.
Входно/изходната система на С предоставя постоянен интерфейс
на програмиста, който не зависи от използваното устройство. За
да се постигне това, С изгражда едно абстрактно ниво между
програмиста и харду ера. Тази абстракция се нарича поток. Дейс-
твителното устройство, което извършва вход/изход, се нарича
файл. Така потокът се явява логически интерфейс към файла.
Както е дефиниран в С, терминът файл може да се отнася за дис-
ков, екранен, клавиатурен файл, за файл в паметта, за файл на
лентов носител и други различии устройства за вход/изход. Раз-
бира се, най-разпространената форма на файл е дисковият файл.
Въпреки че файловете са различии по форма и възможности,
всички потоци са еднакви. Предимството на този подход е, че за
програмиста едно хардуерно устройство прилича на всички ос-
танали. Потокът автоматично преодолява различията.
Потокът се свърза с файл посредством операция за отваряне,
а освобождаването става чрез операция за затваряне.
Има два типа потоци: текстов и двоичен. Текстовият поток
съдържа ASCII знакове. Когато използвате текстов поток, може
да извършвате някои знакови преобразувания. Например, когато
извеждате знак за нов ред, той обикновено се преобразува в знак
за връщане на каретката. Поради тази причина може да няма съ-
ответствие едно към едно между изпратеното към потока и запи-
саното във файла. Двоичният поток може да се използва с вся-
какъв тип данни. Не се извършат никакви знакови преобразува-
ния и предаването на информация е едно към едно между, изп-
ратеното към потока и това, което се съдържа във файла.
Последна концепция, която трябва да разберете, е текущото
местоположение. Текущото местоположение, срещано също и
като текуща позиция, е позицията във файла, в която ще се осъ-
ществи следващият достъп до него. Например ако файлът е дъ-
лъг 100 байта и половината от него веч^ е прочетена, следващият
оператор за четене ще започне от 50-тия байт - текущото место-
положение.
Да обобщим: в С, дисковият вход/изход (както и други видо-
ве вход/изход) се изпълнява през логически интерфейс, наречен
поток. Всички потоци имат подобии свойства и всички те се уп-
равляват с един и същи входно/изходни функции, независимо
какъв тип файл е асоцииран с потока. Файлът е същинската фи-
зически цел, която получава или доставя данни. Въпреки че фай-
ловете се различават едни от други, потоците са еднакви. (Раз-
бира се, някои устройства може да не подържат операции с про-
258 С - Практически самоучител
• изволен достъп и затова асоциираните с тях потоци също не биха могли да поддържат тези операции.) След като вече се запознахте с теорията, стояща зад файлова- та система на С, е време да се заемем с нея на практика. | Основите на файловата система В тази секция ще научите как да отваряте и затваряте файл. Ще се запознаете и с четенето и записването на знакове във файл. За да отворите файл и да го асоциирате с поток, използвайте fopen(). Нейният прототип е показан тук: FILE *fopen(char *име_на_файл, char * режим)-, Функцията fopen(), както и всички функции на файловата система, използва хедърния файл STDIO.H. Името на файла, който трябва да се отвори, е указано в име-на-файл. То трябва да е валидно файлово име, както е определено от операционната система. Низът, указан в режим, определя какви операции ще се извършват с файла. Определените от ANSI С стандарта режими са показани в таблица 9-1. Вашият компилатор може да позволя- ва и допълнителни режими.
Режим Значение
“г” Отваря текстов файл за четене
“w" Създава текстов файл за запис
“а" Добавя към текстов файл
“rb” Отваря двоичен файл за четене
"wb” Създава двоичен файл за запис
“ab” Добавя към двоичен файл
"г+” Отваря текстов файл за четене/запис
“w+” Създава текстов файл за четен/запис
“а+” Добавя към или създава текстов файл за четене/запис
“r+b” Отваря двоичен файл за четене/запис. Можете да използвате и “rb+”
“w+b” Създава двоичен файл за четене/запис. Можете да използвате и ”wb+’
“a+b” Добавя към или създава двоичен файл за четене/запис. Можете да използвате и“ab+”
Таблица 9*1
Стандартните стойности на режим
Файлов вход/изход 259
Ако операцията за отваряне е успешна, fopen() връща вали-
ден файлов указател. Типът FILE е дефиниран в STDIO.H. Това
е структура, съдържаша различна информация за файла, като
размера му, текущото му местонахождение и режима на достъп.
Тя на практика идентифицира файла (Структурата е трупа от
променливи достьпни чрез едно общо име. В следващата глава
ще научите за структуриге, но не е необходимо да знаете за тях,
за да разберете как работи файловата система на С.) Функцията
fopen() връша указател към структурата, асоциирана с файла от
процеса на отварянето му. Ще използвате този указател с всички
останали функции, работещи с файла. Пикога не бива да проме-
няте този указател или обекта, сочен от него
Ако функция га fopen() не успсе да отвори файла, гя връща
ну лев указател, Хедърният файл STDIO.H дефинира макрос
NULL, определен за нулев указател. Важно е да се уверите, че е
върнат валиден файлов указател. За да направите това, проверете
стойността, върната от fopen(), за да стс сигурни, че не е NULL.
Например верният начин да отворите файла myfile за въвеждане
на текст е показан във фрагмента
FILE *fp;
if((fp = fopen("myfile" , "r")) == NULL) {
printf("Error opening flle.\n");
exit(l); /* ил/ заместете с ваш кед за грешки */
J
Въпреки че повечето от режимиге за работа с файлове са раз-
бираеми от само себе си, ще дадем някои коментари. Ако при
отваряне на файл само за четене той не съществува, fopen( ) ще
пропадне. При опит за отваряне на файл в режим за добавяне,
когато той не съществува, файлът ще бъде създаден. При всяко
следващо отваряне за добавяне, информацията ще бъде записва-
на в края му. Оригиналното съдържание ще остане непроменено.
Ако отваряте файл за запис и той не съществува, тогава се съз-
дава нов файл. Ако такъв вече съществува, съдържанието му ше
бъде унищожено и ще бъде създаден нов. Разликата между ре-
жимите г+ и w+ е, че г+ няма да създаде файл, ако той не същес-
твува; w+ обаче ще го създаде. Ако файльт съществува, отваря-
нето му с w+ унищожава съдържанието му, докато отварянето с
г+ - не.
За да затворите файл, използвайте fclose(), чийто прототип с
int fcloseiFILE *fp);
Функцията fclose( ) затваря файла, асоцииран с fp, който трябва
да е валиден файлов указател, добит предварително посредством
260 С - Практически самоучител
fopen(). След това функцията премахва асоциацията между по*
токаи файла. За да подобрят ефективността си, повечето файло-
ни системки приложения записват данните на диска сектор по
сектор. Поради това данните се складират в буфер, докато ин-
формацията достигни размера на сектор, за да бъде физически
записана на диска. Когато извикате fdose(), тя автоматично за-
писва информацията от частично пълния буфер на диска. Това
често се нарича почистване на буфера.
Никога не трябва да извиквате fclose( ) с невалиден аргумент.
Това ще предизвика разрушаване на файловата система и неиоп-
равими загуби на данни.
Функцията fclose( ) връща нула, ако операцията е успешна.
Ако се появи грешка, връща EOF.
След като файлът е отворен, можете да записвате и/или да че-
тетс байтове (т.е., знакове) от него в зависимост от неговия ре-
жим посредством тези две функции:
int fgetc(FILE 7р);
int fputc(int ch, FILE */р);
Функцията fgetc() чете слсдващия байт от файла, сочен от fp
като unsigned char, и го връща като цяло число. (Знакът се връ-
ща в младшия байт). Ако се появи грешка, fgetc( ) врыца EOF.
Както навярно си спомняте от глава 8, EOF е отрицателно число
(обикновено -1) Функцията fgetc() връща EOF и когато дос-
тигло края на файла. Въпреки че fgetc() връща цяло число, ва-
шата програма може да го присвой на char променлива, тъй като
младшият байт съдържа знака, прочетен от файла.
Функцията fputc( ) записва байта, съдържан в младшия байт
на ch, като unsigned char във файл, асоцииран с fp. Въпреки че
ch е дефинирана ка го int, можете да я извиквате с char, което е
най-честата процедура. Ако всичко е наред, функцията fpulc()
връща записания знак и врыца EOF, ако се е появила грешка.
Историческа забележка: традиционните имена на fgctc( ) и
fputc() са getc() и putc(). Стандарт ANSIС все още дефинира
тези имена и те са взаимозамсняеми с fgctc() и fputc( ). Една от
причините за прибавянего на новите имена е за однородное!.
Всички други функции на ANSI файловата система започват с
‘Г, така че ‘f беше прибавено към geic() и putc(). Стандартът
ANSI С все още подържа традиционните имена, тъй като има
много програми, конто ги използват. Ако срещнете програми,
използващи getc( ) и putc( ), не се притеспявайте. Това са просто
други имена за fgetc() и fputc().
Файлов вход/изход 261
ПРИМЕРИ
1. Тази програма демонсгрира четирите функции на файло-
вата система, конто научнхте в тази секция. Тя отваря
файла MYFILE за изход. След това записва в него низа
“This is a file system test.”. Затваря файла и го отваря за че-
тене. Ыакрая, извежда на екрана информацията от файла и
го затваря.
^include <stdio.h>
tfinclude <stdlib.h>
int main(void)
{
char str[80] = "This is a file system test.Xn";
FILE *fp;
char *p;
int i;
/* отваря myfile за изход */
if((fp = fopen ("myfile", "w" ) ) ==N'JLL) {
printf("Cannot open file.Xn");
exit (1);
/* записаа str на диска */
p = str;
while(*p) {
if(fputc(*p, fp)*==EQF) {
printf("Error writing file.Xn");
exit (1);
}
P++;
}
fclose(fp);
/* отваря myfile за вход */
if((fp = fopen("myfile", "r"))==NULL) {
printf("Cannot open file.Xn");
exit (1) ;
]
/* чете отноьо файла */
for(;;) {
i = fgetc(fp);
if(i == EOF) break;
putchar(i);
}
fclose(fp);
return 0;
)
262 С - Практически самоучител
В тази версия, при четене от файла връщаната от fgetc()
стойност се присвоява на целочислената променлива 1.
Стойността на тази променлива се проверява, за да се види
дали е достигнат краят на файла. При повечето компила-
тори обаче можете просто да присвоите връщаната от
fgetc( ) стойност на char променлива и пак да я проверява
те за EOF. Този подход е демонстриран в следващата версия:
frinclude <stdio.h>
#include <stdlib.h>
int main(void)
char str|80] = "This is a file system test.Xn";
FILE *fp;
char ch, *p;
/★ отваря myfile за извеждане */
if((fp = fopen("myfile", "w"))==NULL) {
printf("Cannot open file.Xn");
exit (1);
/* записва str на диска */
p = str;
while(*p) {
if(fputc(*p, fp)==EOF) {
printf("Error writing file.Xn”);
exit(1);
)
Р++ ;
fclose(fp);
/* отваря myfile за въвеждане +/
if((fp = fopen("myfile", "r"))==NULL) {
printf ("Cannot open file.Xn’’);
exit(1);
/* чете отново файла */
for(;;) (
ch = fgetc(fp);
if(ch == EOF) break;
putchar(ch);
}
fclose(fp);
return 0;
Файлов вход/изход 263
Причината, поради която този вариант работа, е, че при
сравняване на char и int стойностга на char автоматично
бива "повишена” в еквивалентна int стойност.
Сыдествува обаче и по-добър начин да бъде написана
горната програмата. Например няма нужда от отделна стъп-
ка за сравнение, защото сравненного може да се извърши
своевременно в конструкцията if, както е показано тук:
ttinclude <stdio.h>
ttinclude <stdlib.h>
int main(void)
{
char str[8C] = "This is a file system test.\n";
FILE *fp;
char ch, *p;
/* отваря my file за извеждане */'
if ((fp = fopen("myf-.le", "w"))==NULL) {
printf("Cannot open file.\n");
exit(1) ;
}
/* записва str на диска */
p = s t r ;
while(*p) {
if(fputc(*p, fp)==EOF) {
printf(“Error writing file.In");
exit(1);
}
P++ ;
}
fclose(fp);
/* отваря myfile за въвеждане */
if((fp = fopen("myfile", "r"))==NULL) {
printf("Cannot open file.\n");
exi t(1);
}
/* чете qthobo файла */
for(;;) {
if((ch = fgetc(fp)) == EOF) break;
putchar(ch);
)
fclose(fp);
return 0;
}
He позвелявайте на конструкцията
if ((ch = fgetc(fp))==ECF) break;
264 С - Практически самоучител
да зи подвежда. Ето какво се случва. Първо, вътре в if,
връщаната от fgetc() стойност се присвоив а на ch. Кактс
може би си спомня те, в С сперацията по присвояване е из-
раз. Цялата стойност на ((ch = fgetc(fp)) е равна на връща-
ната от fgetc( ) стойност - това е целочислената стойност,
която се проверява за EOF.
Като продължснис на този подход, обикновено ще ви-
дите тази програма написана от професионален програ-
мист на С така:
^include <stdio.h>
^include <stdlih.h>
int main(void)
{
char str[80] - "This is a file system test.Xn";
FILE *fp;
char ch, *p;
/* этваря myfile за извеждане */
if((fp = fopen("myfile", "w"))==NULL) (
printf("Cannot open file.Xn");
exit (1);
/* записва str на диска */
p = str;
while(J p)
if(fputc{*p++, fp)==EOF) (
printf("Error writing file.Xn");
exit (1) ;
)
fclose(fp);
/* отваря myfile за въвеждёне ★/
if((fp = fopen("myfile", "r"))==NULL) (
printf{"Cannot open file.Xn");
exit(1) ;
/♦ чете отново файла */
while((ch = fgetc(fp)) != EOF) putchar(ch);
fclose(fp);
return 0;
Забележете, че в този случай всеки знак се чете, нрисвоява
се на ch и се проверява за EOF Всичко това е един израз
на цикъл while, контролиращ процеса на въвеждането.
Ако сравните тази версия с оригиналната, ще се уверите
Фай лег вход/изход 265
колко по-ефективна е тя Всъщност възможността за ин-
тегриране на подобии операции е една от причините С да е
толкова мощен. Важно е да сникнете с подобии подходи.
По-натагьк в тази книга ще се занимаем по-подрибно с
подобии операции за присвояване.
2. Следващата програма приема два аргумента от командния
ред Първият е име на файл, а вторият - знак. Програмата
претърсва посочения файл за знака. Ако файлът съдържа
поне един такъв знак, програмата докладва за това. Забе-
лежете как гя използва argv, за да получи името на файла
и търсения знак.
/* Претърсва определен файл за даден знак. */
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[j)
{
FILE *fp;
char chi
/* проверка за правилен брой аргументи в
командния ред */
if(argc!=3) {
printf ("Usage: find <filenaiue> <ch>\n”);
exit(1);
/* отваряне на файл за въвеждане */
if((fp = fopen(argv[1J, "r"))==NULL) {
printf("Cannot open file.Xn");
exit (1);
/* търсене на знак */
while ((ch = fgetc(fp)) ! = EOF)
if(ch==*argv[2)) {
printf("%c found", ch);
break;
}
fclose(fp);
return 0;
266 С - Практически самоучител
1. Напишете програма, извеждаща съдържанието на текстов
файл, зададен като аргумент в ксмандния ред.
2. Създайте програма, която чете текстов файл и брои колко
пъти се съдържа всяка от буквите от ‘А’ до ‘Z‘. Нека да
извежда резултата (Не правете разлика между малки и
главни букви.)
3. Напишете програма, копираша съдържанието на един тек-
стов файл в друг. Нека програмата да приема три аргумен-
та от командния ред. Първият да е името на файла-източ-
ник, вторият да е името на файла-приемник и трстият да е
незадължителен. Ако той присъства и е равен на ’’watch”,
иска програмата да извежда всеки знак, докато копира
файла. В противен случай да не извършва никакие извеж-
дане. Ако файлът-приемвик не съществува, създайте го
FEOF( ) И FERRORf )
Както знаете, когато fgetc() връща EOF, има грешка или е дос-
тигнат краят на файла, но как да разберете кое от двете всъшност
се е случило? Ако работите с двоичен файл, всички стойности са
валидни Това означава, че е възможно даден байт да има стой-
ността на (когато се преобразува в int) EOF, така че няма как да
знаете дали е върната стойност или е достигнат краят на файла.
Решението на тези прсблеми са функциите feof() и ferror(), чи-
ито прототипи са показани тук:
int feof(FILE Vp);
int ferror(FILE *fp);
Функцията feof() връща ненулева стойност, ако е достигнат кра-
ят на асопиирания с fp файл. В противен случай тя връща нула.
Тази функция работи и с двата типа файлове: двопчни и тексто
ви. Функцията ferror() връша пенулева стойност, ако файлът,
асоцииран cfp, е дал грешка; в останалите случаи връща пула
Посредством функцията eof() следващият фрагмент показва
как да четете до края на файл:
FILE *fp;
while (!feof(fp)) ch = fgetc(fp);
Файлов вход/изход 267
Този код работа за всички видовс файлове и по-принцил е пс-до-
бър от проверката за EOF. Въпреки това все оше няма никаква
проверка за грешки. Това е направено тук:
FILE *fp;
while(Ifecf(fp)) {
ch = fgetc(fp);
if(ferror(fp)) {
printf("File ErrorXn");
break;
Помнете, че ferror() само докладва за статуса на файловата сис-
тема във връзка с последиия файлов достъп. Лко искате да про-
дължите с проверките за грешки, трябва да я ьикате след всяка
файлова операция.
Най-разрушителните файлови грешки се появяват в мивото на
операционната система. Често самата операционка система
прихваща подобии грешки и издава свои съобщения за тях. Нап-
ример ако е открит лош сектор върху диска, повечето операци-
онки системи ще прекъснат изпълнението на програмата и ше
изведал съобщение за грешката. Често единствените грешки, ко-
нто се предавал на вашата програма, са тези, причинени ст греш-
ки във вашата част, като например опит за достъп до файл по на-
чин, несъдържащ се в режима, с който е отворен, или когато
предизвикате извънредно състояние. Обикновено такива грешки
се откриват чрез проверка на резултата на другите файлови фун-
кции, вместо чрез ferror( ). По тези причини в С кодовете има
малко (ако изобшо има) извиквания на ferror(). Едно последно
нещо: нс всички примери в тази книга имат пълна проверка за
грешки, главно за да се залазят програмите къси и лесни за раз-
биране. Ако обаче пишете программ за дсйствитслна употреба,
ще трябва да обръщате специално внимание на проверката за
грешки.
ПРИМЕРИ
1. Тази програма копира всякакъв вид файлове - двоични и
текстови. Тя приема два аргумента от командния ред.
Първият е името па файла-източник. вторият - името на
файла-приемник. Ако файлът-приемник не същсствува, тя
го създава. Програмата включва пълна проверка за греш-
ки. (Сравнете тази версия с програмата за копиранс на
текстови файлове, която писахте в предишката част.)
268 С - Практически самоучител
/* Копиране на файл. */
tinclude <stdio,h>
((include <stdlib.h>
int main(int argc, char *argv[])
{
FILE ‘from, *to;
char ch;
/* проверка за верен брой аргументи от
командния ред */
if(argc!=3) {
printf("Usage: copy <source> <destination>\n");
exit (1) ;
}
/* отваряне на файла-източник */
if ((from = fopen (argv [1], "rb"))“-NULL) {
printf("Cannot open source file.Xn");
exit (1);
)
/* огваряке файла-приемник */
if ((to = fopen (argv [2] , "wb"))==NULL) {
printf("Cannot open destination file.Xn");
exit (1);
}
/* копиране на файла */
while(!feof(from)) {
ch = fgetc(from);
if(ferror(from)) {
printf("Error reading source file.Xn");
exit (1);
}
if(!feof(from)) fputc(ch, to);
if(ferror(to)) {
printf("Error writing destination file.Xn");
exit(1);
}
if(fclose(from)==EOF) {
printf("Error closing source file.Xn");
exit (1) ;
if(fclose(to)--EOF) {
pri nr.f ( "Error closing destination file.Xn");
exit (1);
return 0;
Файлов вход/изход 269
2. Следващата програма сравнява два файла, чиито имена са
зададени като аргумента в командния ред. Тя или извежда
Files are the same, или байта на първата разлика. Тази
програма също използва пълна проверка за грешки
/* Сравняване на файлове. */
Hinclude <stdio.h>
If include <stdlib.h>
int main(int argc, char *argv(J)
FILE *fpl, *fp2;
char chi, ch2, same;
unsigned long 1;
/* проверка за верен орой аргумента в командния
ред */
if(argc!=3) {
printf ("Usage: compare <file 1> <file 2>\n");
exit (1);
}
/* отваряне на първия файл */
if((fpl = fopen (argv[1 ] , "rb"))—NULL) (
printf("Cannot open first file.Xn");
exit (1) ;
/* отваряне на втория файл */
if((fp2 = fopen(argv [2], "rb"))==NULL) {
printf("Cannot open second file.Xn");
exit(1);
J
1 = 0;
same = 1;
/* сравняване на файловете */
while(!feof(fpl)) {
chi = fgetc (fpl);
if(ferror(fpl)) {
printf("Error reading first file.Xn");
exit(1);
}
ch2 = fgetc(fp2);
if(ferror(fp2)) {
printf("Error reading second file.Xn");
exit(1);
)
if(chl!=ch2) {
printf("Files differ at byte number %lu", 1);
same = 0;
break;
1++;
}
270 С - Практически самоучител
if(same) printf("Files are the same.\n");
if(fclose(fpl)==EOF) (
printf("Error closing first file.\n");
exit (1);
if (fclose(fp2)==EOF) {
printf("Error closing second file.\n");
exit (1);
return 0;
1. Напишете програма, конто изчислява броя на байтовете
във файл (текстов или двоичен) и извежда резултат а. Нека
потребителят да указва файла като аргумент в командния
ред.
2. Напишете програма, която разменя съдържанието на два
файла, чиито имена се задават като аргумента в команд-
ния ред. Това означава че при да дени файлове FILE! и
FILE2, след изпълнениего на програмата, FTLE1 ще съ-
държа информапията на FILE2, a FILE2 ще има оригинал-
ното съдържание на F1LE1. (Съвет: Използвайте временен
файл, който да помогне процеса на размяна.)
Ня КОИ ТЕКСТОВИ ФУНКЦИИ от
високо ниво
При работа с текстови файлове С е снабден с четири функции,
конто правят операциите по-лесни. Първите две са fputs( ) и
fgets(), конто съответно четат и записват низ от и във файл.
Техните прототипи са:
int fputs(char *str, FILE *fp);
char *fgets(char *str, int num, FILE */p);
Файлов вход/изход 271
Функцията fputs() записва низа, сочен от str, във файла, асоции-
ран с fp. Тя връща EOF, ако се появи грешка, и неотрицателна
стойност, ако е завършила успешно. Нулата, термияираща низа,
не се записва. Като своята сходна функция puts(), тя не добавя
автоматично символ за ное ред.
Функцията fgets() чете знакове от файла, асоцииран cfp, в
низа, сочен от str, докато достигне до знака с номер пит-\, дока-
то прочете знак за нов ред или достигне да края на файла. Във
всички случаи низът се терминира с нула. За разлика от сходната
си функция gets(), знакът за нов ред се запазва. Функцията връ-
ща низа str, ако е завършила успешно, и нулев указател, ако се с
поя вила грешка.
Файловата система на С има две много мощни функции, по-
добии на други две, конто вече нознавате. Тс са fprintf( ) и
fscanf(). Те действат също както printf( ) и scanf(), с изклгоче-
ние на това, че работят с файлове. Прототипите им са:
int fprintf(FILE Vp, char *контролен-низ,...');
int fscanf(FILE "fp, char *контролен-низ,...у
Вместо да насочват входа/изхода към конзолата, тези функции
го насочват към файл, сочен urfp. Иначе техните операции са
абсолютно еднакви с тези на техните конзолно-базирани срод-
ници. Предимството на fprintf() и fscanf() е, че те могат лесно
да запишат разнообразии данни във файл посредством текстов
формат.
ПРИМЕРИ
I. Следващата програма използва fputs() и fgets(). Тя чете
редсвез е, въведени от потребителя, и ги записва във файл,
зададен в командния ред. Когато бъде въведен празен ред,
въвеждането се ирекратява и файлът се затваря. След това
той бива отворен отново и програмата използва fgets( ), за
да изведе съдьржанието му.
//include <stdio.h>
//include <stdlib.h>
ttinclude "string.h"
int mainfint argc, char *argv(])
{
FILE *fp;
char str[80];
/* проверка за аргумент от командния ред */
if(argc!=2) {
272 С - Практически самоучител
printf ("Specify file name.Xn");
exit (1);
/* отваряне на файл за изход */
if((fр = fopen(argv[1], "w"))==NULL) {
printf("Cannot open file.Xn");
exit (1) ;
}
printf("Enter a blank line to stop.Xn");
do {
printf(": ");
gets(str);
strcat(str, "\n"); /* добавяне на нов ред */
if(*str != '\n') tputs(str, fp) ;
) while(*str 1= '\n');
fclose(fp);
/* отваряне на файл за вход */
if((fp = fopen(argv[1], "г"))==NULL) {
printf("Cannot open file.Xn");
exit (1) ;
/* четене на файла отново */
do {
fgets(str, '/9, fp) ;
if(!feof(fp)) printf(str);.
} while{!feof(fp));
fclose(fp);
return 0;
)
2. Тази програма демонстрира fprintf() и fscanf(). Тя първо
записва double стойност, int стойност и низ във файл, за-
даден в командния ред След това ги чете и извежда стой-
ностите им за потвърждение. Ако прегледате файла, съз-
даден от програмата, ще видите, че той съдържа напълно
четим текст. Това е така, защото fprintf() записва на диска
това, което printf() би извела на екрана. Не използват ки-
какви вырешни формата.
({include <stdio.h>
#inc.lude <stdlib.h>
({include <string.h>
int main(int argc, char ’argvfj)
{
FILE *fp;
double Id;
int d;
Файлов вход/изход 273
char str[80];
/* проверка за аргумент от командния ред */
if(argc!=2) {
printf("Specifу file name.\n");
exit(1);
}
/* отваряне на файл за изход */
if((fp = fopen(argv[1], "w"))==NULL) {
printf("Cannot open file.\n");
exit (1);
}
fprintf(fp, "%f fclose(fp); %d %s", 12345.342, 1908, "hello"
/* отваряне на файл за вход */
if((fp = fopen(argv[1], "r"))==NULL) {
printf("Cannot open file.\n");
exit(1) ;
}
fscanf(fp, "%lf%d%s", &ld, &d, str) ;
printf("%f %d %s", Id, d, str);
fclose(fp);
return 0;
1. В глава 6 написахте програма, подържаща прост телефо-
нен указател. Напишете програма, която да разшири тази
концепция, като предоставите възможност за записване на
указателя във файл на диска. Нека програмата да предста-
вя следното меню:
1. Enter the names and numbers
2. Find numbers
3. Save directory to disk
4. Load directory from disk
5. Quit
Програмата трябва да e способна да записва 100 имена и
номера. (Използвайте само първите имена, ако желаете.)
Използвайте fprintf(), за да запишете указателя на диска,
и fscanf(), за да го прочетете обратно в паметта.
274 С - Практически самоучител
2. Напишете програма, излолзваща fgets() за извеждаяе па
съдържапието на текстов файл, екрак по екран. След из-
веждапето на всеки екран нека потребителят да потвърж-
дава извеждането на следващия
3. Напишете програмата, копираща текстов файл. Укажете
файла-източник и файла-прием ник като аргумента в ко-
мандния ред. Използвайте fputs() и fgets(), за да копирате
файла. Включетс пълна проверка за грешки.
9.5
Четене и записване на двоични
данни
Колкото и да са полезни fprintf() и fscanf(), те не са най-ефек-
тивният начин за четене и запис на цпфрови данни. Причината за
това е, че и двете функции извършват конвертирания на типова
данни. Например когато извеждате цифра с fprIntf(), тя бива
конвертирана от-двоичен формат в ASCII текст. Съответно при
четене на цифра посредством fscanf( ), тя трябва да бъде преоб-
разувана обратно в двоичного си представяне. За много задачи
времето за копвертиранс не е от особено значение, но за други то
е сериозно ограничение. Освен това за някои типове данни файл,
създаден чрез fprint f(), ще бъде по-голям от огледалния му ва-
риант в двоичен формат. Поради тези причини файловата систе-
ма на С притсжава две важни функции: fread( ) и fwrite(). Тези
функции могат да четат всякакви типове данни, като използват
теХните двоични представяния Прототипите им са:
sizej fread(void "buffer, sizej size, sizej num, FILE *fp);
sizej fwrite(viod "buffer, sizej size, size_t num, FILE "fp)-,
Както виждате, в тези прототипи присъстват някои непознати
елементи. Но преди да ги обсъдим е необходимо да направим
кратко описание на всяка от функциите.
Функцията fread() чете от файл, асоцииран с fp, пит броя
обекти, всеки от тях с размер size байта, в буфер, сочен от buffer.
Тя връща броя на прочетените обекти. Ако тази стойност е по-
малка от нит, или е достигнат края! на файла, или се е появила
грешка. Можете да използвате eof() или ferror(), за да откриеге
кое от двете се е случило.
Функцията fwrite() е противоположна па fread(). Тя записва
във файл, асоцииран с fp, пит броя обекти, всеки с размер size
байта, от буфер, сочен от buffer. Тя също връща броя на записи-
Файлов вход/изход 275
ните обекти. Този брой ще бъде по-малко от пит само ако има
грешка при записването.
Преди да погдеднем примерите, нека се запознаем с новите
концепции, представени в прототипите на функциите.
Първата концепция е void указател. Указателят от тип void е
такъв указател, който може да сочи всякакъв тип данни, без да
използва преобразуване на типа. Той обикновено се нарича общ
указател. В С, void указателите се използват по две основни
причини. Иървс, като бе показано при fread() и fwrite( ), за да
могат функциите да получават указатели към всякакви типове
данни, без това да причинява грешки в типовете. Както беше
спомснато по-рано, fread() и fwrite() могат да се използват за
четене и запис на всякакви видове данни. Това означава, че фун-
кцията трябва да може да получава всякакви данни, сочени от
буфер, void указателите правят това възможно. Втората причина
е да се даде възможност на функциите да връщат общ указател.
Ще видите примери за това пс-късно в тази книга.
Втората новост е типът size_t. Този тип е дефиниран в хедър-
ния файл STDIO.H. (Ще научите как да дефинирате типове по-
късно в тази книга.) Променлива от този тип е дефинирана от
стандарта ANSI С като съхраняваща стойност с размер на най-
големият обект, поддържан от компилатора. За нашия случай
можете да смятате, че size_t има размер unsigned или unsigned
long. Причината, поради която се използва size_t, вместо вграде-
ния тип е да се позволи на компилатора на С при изпълнение в
различии среди да се придържа към нуждите и изискванията на
средата.
Когато използвате fread() или fwrite() за въвеждане или из-
веждане на двоични данни, файлът трябва да бъде отворен за
двоични операции. Забравянето на това може да ви коства труд-
ни за откриване проблеми.
За да разберете действието на fread() и fwrite(), нека заноч-
нем с прост пример Следващата програма записва цяло число
във файла MYFILE, като използва неговото вътрешно двоично
представяне, и след това го пропита обратно. (Програмата прие-
ма, че целите числа са дълги два байта.)
ttinclude <stdio.h>
ttinclude <stdlib.h>
int main(void)
{
FILE *fp;
int i;
/* отваряне на файл за изход */
if((fp = fopen("myfile", "wb"))==NULL) {
276 С - Практически самоучител
printf("Cannot open file.\n");
exit (1);
i = 100;
if(fwrite (5i, 2, 1, fp) 1= 1) {
printf("Write error occurred.\n");
exit (1);
}
fclose(fp);
/* отваряне на файл за вход */
if((fp = fopen ("myfile", "rb"))==NULL) {
printf("Cannot open file.Xn");
exit (1) ;
if(fread(&i, 2, 1, fp) ! = 1) {
printf("Read error occurred.\n");
exit (1);
printf("i is %d", i);
fclcse (fp);
return 0;
Забележете колко лесна e проверката за грешки, използвана в та-
зи програма - престо чрез сравняване на броя на прочетените
или заиисаните обекти сьс зададения брой. В никои ситуации
обаче все още ще имате нужда от cof() или ferror(), за да опре-
делите дали е бил достигнет краят на файла, или е възникнала
грешка.
Едно ог грешните неща в предходния пример е допускапето
относно размера на целочислената променлива и това, че този
размер е твърдо включен в кода на програмата. Иначе казано,
програмата няма да рабоги правилно с компилатори, използващв
4-байтови цели числа. По-общо казано, размерите на типовете
данни са различии при различните системи и е трудно да бъдат
олределени ръчно. Поради тази причина С включва кодовата
дума sizeof, която е оператор по време на компилиране, връщащ
размера, в байтове, на определен тип данни или променлива.
Общата й форма е:
sizeof (гл ил),
или
sizeof име_на_променлива,
Файлов вход/изход 277
Ако например float числата са дълги четири байта и f е промен-
лива от тип float, двата следващи израза ще имат стойност 4:
sizeof f
sizeof (float)
Когато използвате sizeof с тип, той трябва да е затворен в скоби.
Не са необходими скоби, когато използвате име на променлива,
въпреки че използването им няма да е грешно.
С използването на sizeof не само че си спестлвате черната ра-
бота по изчисляването на размера на даден обект, но също така
осигурявате лесния преход на кода си към нова среда. Подобре-
ната версия на предходната програма е показана тук. Гя използва
sizeof.
ftinciude <stdic.h>
#include <stdlib.h>
int main(void)
{
FILE *fpr-
int i;
/* ствзряне на файл за изход */
if((fp = fopen("myfile", "wo"))==NULL) {
printf("Cannot open file.Xn"),
exit(1);
i = IGO;
if(fwrite(&i, sizeof(int), 1, fp) 1= 1) {
printf("Write error occurred.\n");
exit(1);
fclose(fp);
/* отваряне на файл за вход */
if((fp = fopen ("myfile", "rb") ) =-=NULL) {
printf("Cannot open file.Xn");
exit (1) ;
if(fread(&i, sizeof i, 1, fp) 1= 1) {
printf ("Read error occurred. \n’’) ;
exit(1);
printf("i is %d",i);
fclose(fp);
return 0;
278 С - Практически самоучител
ПРИМЕРИ
1. Тази програма запълва един 10-елементен масив с числа с
плаваща запетая, записва ги във файл, след което ги чете
обратно. Програмата записва вески елемент от масива от-
делно. Тъй като двоичните числа се записват с тсхния вы-
решен двоичен формат, файлы трябва да бъде отворен за
двоични зходно/изходни операции.
tinclude <stdio.h>
#include <stdlib.h>
double d[lC] = {
10.23, 19.87, 1002.23, 12.9, 0.897,
11.45, 75.34, 0.0, 1.01, 875.875
int main(void)
(
int i;
FILE *fp;
if((fp = fopen("myfile", "wb"))==NULL) {
printf("Cannot open file.Xn");
exit (1);
for(i-0; i<10; ii+)
if(fwrite(&d[i], sizeof(double), 1, fp) != 1) {
printf("Write error.\n");
exit(1);
}
fclose(fp);
if((fp = fopen("myfile", "rb"))==NULL) {
printf("Cannot open file.Xn");
exit (1);
)
/* изчистване на масива */
for(i=0; i<10; i++) d [i] = -1.0;
fcr(i=0; i<10; i++)
if(fread(&d[i], sizeof(double), 1, fp) != 1) |
printf("Read error.\n");
exit (1) ;
fclose(fp);
/* извеждаче на масива */
for(i=0; i<10; i++) printf("%f ", d[i]);
return 0;
Файлов зход/изход 279
Масивът се изчиства между операциите по записване и че-
тене само за гарантиране, че стойностите му са получены
от конструкцията fread()
2. Следващата програма върши същите неща, както първата,
но в този случай използва само едно извикване на fread()
и fwrite(), защото целият масив се записва в една стъпка,
което е по-ефективно. Този пример показва колко мощни
са двете функции.
#include <stdio h>
((include <stdlib.h>
double d[10] = {
10.23, IS.87, 1002.23, 12.9, 0.897,
11.45, 75.34, 0.0, 1.G1, 875.875
int main(void)
{
int i;
FILE *fp;
if((fp = fopen("myfile", "wb"))==NULL) {
printf("Cannot open file.Xn"),
exit(1) ;
/* записване на целия масив в една стъпка */
if(fwrite(d, sizeof dr 1, fp) != 1) {
printf("Write error.\n");
exit (1);
}
fclose(fp);
if((ip = fopen("myfile", "rb"))==NULL) {
printf("Cannot open file.Xn");
exit (1) ;
/* изчистване на масива */
for(i=0; i<10; i++) d[i] = —1.0;
/* четене на целия масив в една стъпка */
if(fread(d, sizeof d, 1, fp) != 1) {
printf("Read error.\n");
exit(1);
fclose(fp);
/* извеждане на масива */
for(i=0; i<10; 1++) printf("%f ", d[i]);
280 С - Практически самоучител
return 0;
}
УПРАЖНЕНИЯ
1. Напишете програма, която позволява на потребителя да
въведе толкова double стойности, колкото иска (не повече
от 32 767), и ги записва в дисков файл така, както са въве-
дени. Наречете този файл VALUES. Вройте колко стой-
ности са въведени и запишете броя им във файл, наречен
COUNT.
2. Използвайте файла, създаден в упражнение 1, и напишете
програма, която първо прочита от COUNT броя на еле-
ментите във VALUES. След това програмата чете стой-
ностите от VALUES и ги отпечатва.
9.6
Произволен достъп
Примерите до момента записваха или четяха файл последова-
телно от началото до края му. Използвайки други функции на
файловата система на С, винаги можете да имате достъп до всяка
позиция в файл. Функцията, позволяваща ви това, е fseek() и
нейния прототип е:
int fseek(FILE *fp, long отместеане, int начало};
В този случай fp е асоцииран с файла за достъп. Стойността на
отместеане определи броя на байтовете след начало, конто ще
зададат новата текуща позиция. Начало трябва да бъде един от
макросите, показани тук заедно с техните значения:
Начало
SEEK_SET
SEEK_CUR
SEEK_END
Значение
Търси от началото на файл
Търси оттекущата позиция
Търси от края на файл
Файлов вход/изход 281
Тези макроси са дефинирани в хедърния файл STDIO.H. Напри-
мер ако искате да зададете текущото местоположение на 100
байта след началото на даден файл, тогава начало ще бъде
SEEKJSET, а отместване - 100.
Функцията fseek() връща нула при успешно завършване и
ненулева стойност при грешка. В повечето имплементации мо-
жете да позиционирате след края на файла, но не можете да пра-
вите това преди неговото начало.
Можете да определите текущата позиция на даден файл, като
използвате ftell() - друга от файловите функции на С, чиито
прототип е:
long ftell(FILE Vp);
Тя връща като резултат коя е текущата позиция на файла, асоци-
иран с fp. Ако се появи грешка, връща -1.
Общо казано, ще използвате произволния достъп предимно с
двоични файлове. Причината за това е проста. В текстовите фай-
лове се извършват преобразувания на знакове и може да няма
директно съответствие между това, което е във файла, и номера
на байта, в който то би се появило при търсене. Можете да из-
ползвате fseek(), за да търсите в текстов файл единствен© ако
търсите предварително определена позиция с ftell(), като изпол-
звате SEEK_SET за начало.
Запомнете-. дори и файл., съдьржащ.текст, жж да ее етьери
като двоичен, ако така предпочитате. Няма изрична забрана за
използване на произволен достъп до файлове с текст. Забраната
се отнася само за файлове, отворени като текстови.
ПРИМЕРИ
1. Следващата програма използва fseek(), за да изведе стой-
ностга на който и да е байт от файл, зададен в командния
ред.
♦include <stdio.h>
♦include <stdlib.h>
int main(int argc, char *argv[])
{
long loc;
FILE *fp;
/* проверка дали e указано име на файл */
if(argc!=2) {
printf("File name missing.\n");
exit(1);
282 С - Практически самоучител
}
if((fp = fopen (.argv 11] , "rb”) ) =«NULL) {
printf("Cannot open file.Xn");
exit (1) ;
}
printf("Enter byte to seek to: ");
scanf("%Id", &loc);
if(fseek(fp, loc, SEEK_SET)) {
printf("Seek error.\n”);
exit (1);
printf("Value at loc %ld is %d", loc, getc(fp));
fclose(fp);
return 0;
}
2. Следвашата програма използва fscck() и ftell(), за да ко-
пира съдържанието на файл в друг, но в обратен ред.
Обърнете внимание как бива открыт краят на файла за че-
тене. Тъй като програмата е тьрсила до края на файла, тя
се връща с един байт, гака че текущата позиция па асоци-
ирания файл е всъщност послсдният знак от файла.
/* Копиране на файл в обратен ред */
^include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[J)
{
long loc;
FILE *in, *out;
char ch;
/* проверка за коректен брой аргумента в
командния ред */
if(argc'=3) {
printf("Usage: rcvcopy <source> <destination>.\n");
exit (1) ;
}
if((in = fopen(argv[1], "rb"))==NULL) {
printf("Cannot open input file.Xn");
exit (1) ;
)
if((out = fopen(argv[2], "wb")J==NULL) {
printf("Cannot open output file.Xn");
exit(1);
}
/* намиране на края на файла-източник */
Файлов вход/изход 283
fseeMin, OL, seek end) ;
loc = ftell (in);
/* копиране на файла в обратен ред */
loc = 1ос-1; /* зарада маркера за край на файл */
while(loc >= OL) {
fseek(in, loc, SEEK_SET);
ch = fgetc(in);
fputc(ch, out);
loc—;
}
fclose(in);
fclose(out);
return 0;
)
3. Следващата програма записва десет double стойности па
диска. След това пита коя от тях искате да видите Този
пример показва как можете да достигнете до произволни
данни от някакъв тип. Просто трябва да умножите размера
на базовия тип по индекса му във файла.
^include <srdio.h>
#include <scdlib.h>
double d[10] = {
10.23, 19.87, 1002.23, 12.9, 0.89T,
11.45, 75.34, 0.0, 1.01, 875.875
);
int main(void)
{
long loc;
double value;
FILE *fp;
if((fp = fopen("myfile", "wb"))==NULL) {
printf("Cannot open file.\n");
exit (1);
)
/* записване на целия масив в една стъпка */
if(fwrite(d, sizeof d, 1, fp) != 1) {
printf("Write error.\n");
exit(1);
)
fclose(fp);
if((fp = fopen("myfile", "rd”))==NULL) {
printf("Cannot open file.\n");
exit (1) ;
)
284 С - Практически самоучител
printf (’’Which element? ’’) ;
scanf("%ld”, sloe);
if(fseek(fp, loc*sizeof(double), SEEK_SET)) {
printf("Seek error.\n");
exit(1) ;
fread(svaiue, sizeof(double), 1, fp) ;
printf("Element %id is %f", loc, value);
fclose(fp);
return 0;
1. Напишете прел рама, използваща fseek( ) за изве.кдане на
всеки байт от даден текстов файл. (Запомнете, че трябва да
отворите файла като двоичен, за да използвате нормално
fseek().) Нека потребителя;' укаже файла в командния ред.
2. Създайте програма, претърсваща файл, зададен в команд-
ния ред, за определена целочислена стойност (съшо зада-
дена в командния ред). Ако бъде намерена стойностга, не-
ка програмата да извежда нейната позиция в байтове
спрямо началото на файла.
9.7
Различии функции на файловата
система
Можете да преименувате файл, като използвате rename(), пока-
зана тук:
int rename(char *староиме, char *новоимеУ,
Тук староиме указва оригиналното име на файла, а новоиме -
новото му име. Ф1 нкцията връща нула, ако е завършила
успешно, и ненулева стойност, ако има грешка.
Можете да изтриете файл чрез remove() Нсйният прототип е:
int remove(char ’шленафаъл);
Файлов вход/изход 285
Функцията ще изтрие файл, чието име съвлада с указаното име-
нафайл. Тя връща нула, ако е завършила успешно, и ненулева
стойност, ако се псяъила грешка.
Можете да позиционирате текущата позиция в началото на
файл чрез rewind(). Ненния прототип е:
void rewind(FILE *fp);
Тя “пренавива” файла, асоцииран с fp. Функцията rewind() нс
връща стойност, тъй като всеки отворен файл би могьл да бъде
“пренавит”.
Въпреки че рядко се налога, поради начина, по който работы
файловата система в С, можете да записвате па диска буфера на
файл чрез fflush(). Тя има следния прототип:
int fflush(FILE *ф);
Тя изчиства буфера на файла, асоцииран cfp. Функцията връща
нула, ако е завършила успешно, и EOF, ако има грешка. Ако из-
викате fflush() и използвате NULL за fp, ще бъдат изчистени
всички съществуващи дискови буферы.
ПРИМЕРИ
1. Тази програма демонстрира remove( ). Тя пита за файл,
който трябва да бъде изтрит, а сыцо така осигурява и про-
верка, в случай че е въведено грешно име.
if include <stdio.h>
#include. <stdlib.h>
ifinclude <ccype.h>
int main(void)
{
char fname [80];
printf("Enter name cf file to erase: ");
gets(fname);
printf ("Are you sure? (Y/N) ’’) ;
if(toupper(getchar())=='Y') remove(fname);
return 0;
}
2. Следващата програма демонстрира rewind(), като извеж-
да два пъти съдържанието на файла, зададен в командния
ред.
286 С - Практически самоучител
ftinelude <stdio.h>
ftinclude <stdlib.h>
int main(int argc, char *argv(])
(
FILE *fp;
/* проверка дали e зададен файл */
if(argc!=2) [
printf("File name missing.\n");
exit(1);
if((fp = fopen(argv[l], "r"))==NULL) {
printf V*Cannot open file.Xn") ;
exi t (1);
/* първо изписване */
while(!feof(fp))
putcha r(getc(fp));
rewind(fp);
/* второ изписване */
while(!feof(fp))
putchar(getc(fp));
fclose(fp);
return 0;
}
3. Този фрагмент изчиства буфера, ассцииран с fp.
FILE *fp;
fflush(fp);
4. Тази поограма нреименува файла MYF1LE.TXT на
YOURFILE.TXT.
ftinclude <stdio.h>
int ma Ln(void)
{
if (rename ("iryfile.txt", "yourfile.txt") )
printf("Rename failed.\n");
else
printf("Rename successful.\n");
return 0;
Файлов вход/изход 287
1. Подобреге изтривагцата програма от пример 1 така, че да
уведомява за опит за изтриване на песъществуващ фай.!.
2. Самостоятелно аомислете за начини за употрсбата на
fflush() и rewind() в реални задачи
9.8
СТАНДАРТНЕЕ ПОТОЦИ
При изпълнение на програма на С, автоматично се отварят три
потока и те са достьпни за употреба. Тези потопи се наричат
стандартен вход (stdin), стандартен изход (stdout) и стандар-
тна грешка (stderr). По подразбиране те са свързани с конзола-
та, но в среда, подържаша пренасочване на входа'изхода, те мо-
гат да бъдат пренасочгни от операционната система към друго
устройство.
Обикновсно stdin въвежда чрез клавиатурата, stdout и stderr
отпечатват на екрана. Тези стандартни потоци са FILE указатели
и могат да се използват във всяка функция, изискваща промен-
лива от тип FILE *. Например можете да използвате fprintf(), за
да печатате фсрматирани символи към екрана, като посочит е
stdout за извеждащ поток Следващите две конструкции са фун-
кционално еднакви:
fprintf(stdout, "%d %с %s", 100, ’с’, "this is a
string");
printf("%d %c %s", 100, 'c', "this is a string");
На практика С прави малка разлика между конзолния и фай-
ловия вход/изход. Както видяхте, възможно е да изьършвате
конзолни операции посредством някси от файловите функции.
Въпреки че може да ви се стопи малке странно, възможно е из-
вършването на дискови операции с конзолните входно/изходни
функции, като например prinif(). Следва пояснение защо.
Всички функции, описани в глава 8 и посочени като “конзол-
ни входно/изходни функции” са всъщност специални файлови
функции, конто автоматично оперират със stdout и stdin. Така
конзолните входно/изходни функции просто са удобство за
програмиста Що се огнася до С, конзолата е просто едно харду-
ерно устройство. На практика ня мате нужда от конзолните фун-
кции, за да имате достъп до конзолата. Всяка файлова функция
може да има достъп до нея. (Разбира се, нестаядартните вход-
но/изходни функции, като getche(), сс различават от стандарт-
ните и работят само с конзолата.) В среда, позволяваща прсна-
288 С - Практически самоучител
сочване на входа/изхода, stdout и stdin могат да сочат друго уст-
ройстве, различно от клавиатурата и екрана. Тъй като конзолни-
те функции оперират със stdout и stdin, ако тези потоци са пре-
насочени, то и “конзолните” функции ще работяг с друго уст-
ройство. Например чрез прсиасочването на stdout към дисков
файл можете да използвате конзолните функции, за да записвате
в дисков файл.
И нещо важно: stdout, stdin и stderr не са променливи. На
тях не можете да присвояваге стойност посредством fopen(),
нито бихте могли да ги затваряте с fclose(). Тези потоци се по-
държат вътрешно от компилатора. Можете да ги използвате, но
не можете да ги променяте.
ПРИМЕРИ
1. Разгледайте програмата:
ftinclude <stdio.h>
int main(void)
printf("This is an example of redirection.\n");
return 0;
)
Приемете, че програмата се нарича TEST. Ако я изпълните
нормално, тя извежда низа на екрана. Ако средата позьо-
лява пренасочване на входа/изхода, stdout може да бъде
пренасочен към файл. Например ако в DOS, OS/2,
Windows или UNIX среда изпълните TEST така
TEST > OUTPUT
изходът на TEST ща бъде записан във файл, наречен
OUTPUT. Опитайте това самисюятелно.
2. Входът също може да бъде пренасочен. Например, разгле-
дайте следната приграма:
ftinclude <stdio.h>
int main(void)
int i;
scanf("%d", si);
Файлов вход/изход 289
printf("%d", i);
return 0;
}
Приемете, че тя е наречена TEST и следного й изпълнение
TEST < INPUT
веди до пренасочването на stdin към файла INPUT. Ако
приемом, че INPUT съдържа ASCII представяне на цяло
число, стойността на това число ще бъде прочетено и из-
ведено на екрана.
3. Както бе споменато по-рано, при използването на gets() е
възможно да пренълните масива, изнолзван за получаваие
на въведените знакове, защото gets() няма проверка на
границите. Едно решение на проблема е използването на
fgets( ), указвайки stdin за въвеждащ поток. Тъй като
fgets() изисква да зъведете максимална дължина, то става
възможно да предотвратите препълване на масива. Единс-
твената нсприятност е, че fgets( ) не нремахва знака за нов
ред, a gets() го прави. Това означава, че трябва да го пре-
махнете сами, това с демонстрирано в следвашата п|хл рама:
iinclude <stdio.h>
ttinclude <string.h>
int main(void)
char str [10 J;
int i;
printf("Enter a string: ");
fyets(str, 10, stdin);
/* премахнане на знака за нов ред */
1 = strlen (str)-1;
if(str[i]==*\n') str[i] = *\0';
printf("This is your string: Is", str);
return 0;
)
290 С - Практически самоучител
1. Напишете програма, която копира съдържанието на един
текстов файл в друг. Използвайте само “конзолни “ вход-
по/изходни функции и прснасочванс, за да извършите ко-
пиранетэ.
2. Ексяериментирайте самое тоятел но чстенето на низове с
fgetsf) от клавиатурата.
Проверка науменията, представени в главата
Преди да продължим, трябва да можете да отговорите на
въпросите и да изпълните упражненията:
1. Напишете програма, извеждаща съдържанието на текстов
файл (зададена в командния ред) ред по ред. След извеж-
дането на всеки ред изисквайте потвърждение за извежда-
пето на следващия.
2. Създайте програма, която копира текстов файл. Нека пот-
ребителя! да задава имената на двата файла в командния
ред. Програмата трябва да преобразува всички малки бук-
ви в главки.
3. Как действат fprintf() и fscanf()?
4. Напишете програма, използваща fwrite() за записване във
файл RAND 100 случайни числа.
5. Напишете програма, използваща fread() за извеждане на
целите числа от файла RAND, създаден в упражнение 4.
6. Използвайки файла RAND, създайте програма, която чрез
fseek() позволява да изведете всяка стойност от файла.
7. Спадат ли “конзолните” входно/изходни функции към
файловите функции?
Файлиь аход/изход 291
Проверка на натрупаните умения
Тази секция проверява как сте интегрирали материала от тази
глава с научено ! о от предпишите.
1. Подобреге програма!а-каталог, която написахтс в глава 8,
така че да записва информацията във файл на диска, наре-
чен CATALOG. При стартиране, нека програмата да за-
режда каталога от файла в паметта. Добавете и опция за
записване на информацията па диска.
2. Напишете програма за копиране на файл. Нека потребите -
лят задава файла-източник и файла-приемник като аргу-
менти в командния ред. Програмата да премахва табула-
циитс, като ги замества със съответен брой интервали.
3. Самостоятелно създайте малка база данни за поддръжка
на каквото поискате - например вашата колекция от ком-
пакт-дискове.
10
СТРУКТУРИ И ОБЕДИНЕНИЯ
Разглеждани теми в главата
10.1 Основи на структурите
10.2 Деклариране на указатели
към струю ури
10.3 Работа с вложени структуои
10.4 Какво представляват
побитовите полета
10.5 Създа гане на обединения
294 С - Практически самоучител
т тази глава ще научите двата най-важни потребптелс-
ки-дефинирани типа: структура га и обединението.
Проверка на знанията
Преди да продолжим, трябва да сте в състояние да отговорите
на следвашите въпроси и да изпълните следните упражнения.
1. Напишете програма за копиране на файл. Нека потребите-
лят да задава имената на изгочника и целта в командния
ред. Включете цялостна проверка за грешки.
2. Напишете програма, използваща fprirttf() за създаване на
файл със следната информация:
this is a string 1230.23 1FFF А
Използвайте форматни спецификатори и стойности за низ,
double стойност, шестнадссетичио цяло число и char
стойност
3. Напишете програма, съдържаща масив от 20 елемента.
Инициализирайте масива така, че да съдържа числата от 1
до 20. Чрез само едва конструкция fwrite( ) запишете този
масив във файл, наречен 1ЕМР.
4. Напишете програма, прочиташа файла TEMP, създаден в
упражнение 3, в целочислен масив посредством само една
конструкция fread( ). Изпишете съдържанието на масива.
5. Какво са stdin, stdout и stderr?
6. Как функции, като printЦ ) и scanf(), се евързват с файло-
вата система на С?
ЮЗПН Основи НА СТРУКТУРИТЕ
Cmpyxmypama е агрегатен (или конгломератен) тип данни, със-
тавен от две или повече евързани промеиливи, наречен и членове.
За разлика от масива, при който всички елемепти са от един и
Структури и обединения 295
сыц тип, всеки член на структурата може да бъде от тип, разли-
чен от този на останалите членове. Структурите се дефинират в
С посредством следната обша форма:
struct име-на-егпикет {
тип член1
тип члон2
тип членЗ
тип uneiiN
} списъкс-променлиеи;
Ключовата дума struct показва на компилатора, че се дефинира
структурен тип. Тип е валиден С тип. Името-на-етикет всъщ-
ност е името на типа на структурата, а списък-с-променливи е
декларацията на сыцинските инстанции на структурата. Както
име-на-етикет, така и списък-с-променливи не са задължителни
но пене едното трябва да присъства (скоро ще видите защо).
Членовете на структурата често се наричат полета или елемен-
ти. Всички тези термини ще се използват взаимозаменяемо в
книгата.
Обикновено информацията, съдържаяа в структурата, е логи-
чески свьрзана. Например можете да използвате структура, за да
съхранявате адресите на различии хора. Друга структура може
да използвате за поддръжката на програма за инвентаризация,
съхраняваща името, цената на дребно и едро и количеството на
всяка налична стока. Показаната по-долу структура дефинира
полета, конто могат да съдържат картотечна информация:
struct catalog { char name[40]; /* име на автора */
char ti tie[40]; /* заглавие */
char pub[40]; /* издател */
unsigned date; /* дата на аптсрското право */
unsigned char ed; /* издание */
} card;
В този случай catalog е името на типа на структурата. То не е
името на променливата. Единствената променлива, дефинирана
от тази структура, е card. Важно е да разберете, че декларацията
на структура просто дефинира едно логическо цяло, което пред
ставлява нов тип данни. Докато не декларирате променливи от
този тип, не съществува сбект от това логическо цяло. Това оз-
начава, че catalog е логическия шаблон; card е физическаза ре-
алност.
296 С - Практически самоучител
name 40 bytes '
title 40 bytes / /
pub 40 bytes \ \
date 2 bytes
ed 1 byte
игура 10-1
’ДДщДуД Как изглежда структурната променлива card в паметта
редполагат се 2-байтови цели числа)
Фигура 10-1 показва как тази структура ще изглежда в памечта
(използвани са 2-байтови цели числа).
За да достигнете до член на структурата, трябва да зададете
името на структурната променлива и името на члена, разделени
с точка. Например при използване на променливата card, след-
ващата конструкция присвоява на полете date стойкостта 1776:
card.date - 1776;
С програмистите често наричат точката операторът точка. За
да отпечатате датата на авторского право, можете да използвате
подобна конструкция:
printf("Copyright date: card.date);
За да въведете датата, използвайте конструкция scanf( ), подобна
на тази:
scanf("%u", &card.date);
Обърнете внимание, че операторът <& се слага пред името на
структурата, а не пред името на члена. По подобен начин след-
ните консгрукции въвеждат името на автора и отпечатват загла-
вието:
gets(card.name);
printf("%s", card.title);
За да достигнете до отделен знак от полото title, просто го
индексирайте. Например следната конструкция отпечатва трета-
та буква:
printf ("fcc'*, card.title[2]);
След каго ведпъж стс дефинирали един структурен тип, мо-
жете да създавате променливи от този тип посредством тази об-
ща форма:
Структура и обединения 297
struct име_на_етикет списък_с__променливи,
Ако приемом например, че catalog е дефипиран, как го беше по-
казано по-рано, тази конструкция декларира три промеиливи от
тип struct catalog.
struct catalog varl, var2, var3;
Ето защо не e задължително да декларирате каквито и да било
промеиливи, когато дефинирате структурата. При нужда, можете
да ги декларирате отделно.
Ключовият момент, който трябва да разберете, е, че всяка ин-
станция на структурата съдържа собствено копие на членовете
на структурата. Ако е дадена например предипшата декларация,
полете title на varl е съвсем отделно от полето title на var2.
Всъщност единствената връзка между varl, var2 и var3 е, че те
са промеиливи от един и същтип структура. Не същсствува ни-
какво друго евързване помежду им.
Ако знаете, че ще ви трябеат точно определен брой структур-
ни промеиливи, не е нужно да задавате име на етикста. Следва-
щия код например създава две структурна промеиливи, но сама-
та структура е безименна:
struct {
int а;
char ch;
} varl, var.2;
В лрактиката обикновено ще искате да задавате име на етикега.
Структурите могат да се поставят в масив, също както всички
останали типове данни. Например дадената по-долу дефиниция
на структура създава масив със 100 елемента от тип catalog:
struct catalog cat[100];
За да достигнете до дадена струкгура от масива, трябва да ин-
дексирате неговото име. Например този код достига до първата
структура:
cat[0]
За да достигнете до член от определена структура, поставете
точка след индекса и напишете името на члена. Например след-
ващата конструкция зарежда полето cd на структура 33 със
стойност 2:
cat[33].ed = 2;
298 С - Практически самоучител
Структурите могат да се предават като параметри на функ-
ции, както всеки друг тин данни. Освен това функциите могат да
връщат структури.
Имате възможност да присвоявате съдържанието на една
структурна инстанция на друга, стига и цвете ла са от един и същ
тип. Следващият фрагмент например е напълно галиден:
struct s_type (
int а;
float f;
} varl, var2;
va r1.a = 10;
varl.f = 190.23;
var2 = varl;
След изпълнението на този фрагмент var2 ще има еднакво съ-
държание с тола на varl.
ПРИМЕРИ
1. Тази програма демонстрира някои начини за достигане до
членсвете на структура:
#include <stdio.h>
struct s_type {
int i;
char ch;
double d;
char str[80];
} s;
int main(void)
printf("Enter an integer: ") ;
scanf("%d:", &s.i);
printf("Enter a character: ") ;
scanf(" %c", &s.ch);
printf("Enter a floating point number: ");
scanf("%lf", &s.d);
printf("Enter a string: ");
scanf("%s", s.str);
printf("%d %c %f %s", s.i, s ch, s.d, s.str);
return 0;
Структуры и обединения 299
2. Когато искате да разберете размера на дадена структура,
трябва да използваге оператора по време на компилания
sizeof. Не се опитвайте сами да събирате броя на байговеге
от всяко поле. Това е неправилно поради три эсновни
причини. Първо, както научихте в предишната глава, из-
ползването на sizeof осигурява прсносимостта на вашия
кед в различии среди. Второ, в никои ситуации компила-
торът .може да иска да разположи определени типсве дан-
ни на място, кратно на дума. В този случай размерът на
структурата ще е пс-голям, отколкото сумата на отделяйте
й елементи. Накрая. при компютрите, базирани на семейс-
твото процесори 8086 (например 80486 или Pentium), ком-
пилаторите могат да използват няколко различии начина
на организация на паме'та. В някои ст тези случаи указа-
телите заемат два иъти повече памет, отколкото в други.
Когато използвате sizeof със структурен тип, трябва да
поставите ключовата дума struct пред името на етикета,
както е показано в тази програма:
ttinclude <stdio h>
struct s type {
int i;
char ch;
int *p;
double d;
} s;
int main(void)
{
printf ("s_type is %d bytes long*', sizeof (struct
s_type));
return 0;
}
3. За да видите колко са полезни масивитс от струкгури, раз-
гледайте подобрената версия на програмата за картотеки-
ране, разработена в предишните две глави. Обърнете вни-
мание как използването на структура улеснява организа-
цията на информацията за всяка книга. Забележете също
така как целият масив от струкгури се записва и чете от
диска с една операция.
/* Електрснна картотека. */
ttinclude <staio.h>
ttinclude <string.h>
ttinclude <stdlib.h>
ttdefrne MAX ICO
300 С - Практически самоучител
int menu(void); void display(int i); void auLhor_search(void) ; void title search(void); void enter(void); void save(void); void load(void);
struct catalog {
char name[80]; /* име на автора */
char title[90]; /* заглавие */
char pub[80); /* издател */
unsigned date; /* дата на авторского право */
unsigned char ed; /* издание */
} cat[MAX];
int top = 0; /* последнего изполэвано място */
int main(void)
{
int choice;
load!); /* прочитано на каталога */
do {
choice = menu();
switch(choice) {
case 1: enter(); /* въвеждане на книги */
break;
case 2: author_search(); /* търсене no
автор */
break;
case 3: title search(); /* търсене no
заглавие */
break;
case 4: save ();
}
) while(choice 1 = 5) ;
return 0;
}
/* Връщане на изОор от меню. */
menu(void)
{
inc i ;
char str [8G];
printf (" Card catalog:\n");
printf(" 1. Enter\n");
printf(" 2. Search by Author\n");
printf (" 3. Search by TitleXn");
printf(" 4 Save cataiog\n");
printf(" 5. Quit\n");
Структури и соединения 301
do {
printf("Choose your selection: ");
gets(str);
i = atoi (str);
printf("\n");
} while(i<l I I i>5);
return i;
}
/* Эъвеждане на книги в базата данни. */
void enter(void)
(
int i;
char temp[HO];
for(i=top; i<MhX; i++) {
printf("Enter author name (ENTER to quit): ");
gets(cat[i].name);
if(!*cat[i].name) break;
printf("Enter title: ");
gets (cat[i].title);
printf("Enter publisher: ");
gets(cat[i].pub);
printf("Enter copyright date; ");
gets(temp);
cat[ij.date = (unsigned) atoi(temp);
printf("Enter edition: ");
gets(temp);
cat[i].ed = (unsigned char) atoi(temp);
)
top - i;
)
/* Тьрсене по автор. */
void author search(void)
{
char name[80];
int i, found;
printf("Name: ");
gets(name);
found = 0;
for(i=0; ictop; i++)
if(!strcmp(name, cat[i].name)) {
display(i);
found = 1;
printf("\n");
}
if (! found) printf ("Not Foundin'');
)
/* Търсенепо заглавие. */
302 С - Практически самоучител
void title search(void)
{
char title ISO];
int i, found;
printf("Title. ");
gets(title);
found = 0;
for(i=0; i<top; i+t)
if(!strcmp(title, cat[i].title)) {
display(i);
found = 1;
printf("\n");
if((found) printf("Not Foundin''};
}
/* Показване на каталога. */
void display(int i)
{
printf ("%s\n", cat [i] . title);
printf("by %s\n", cat[i].name);
printf("Published by %s\n", cat[i].pub);
prinef("Copyright: %u, %u editionin",
cat[i].date,
cat[i]. ed);
}
/* Зареждане на файла с каталога. * /
void load(void)
{
FILE *Ep;
if((fp = fopen("catalog", "rb") ) ==NULI.) {
printf("Catalog file not on disk.\n");
return;
)
if(fread(&top, sizeof top, 1, fp) != 1) {
/* прочитане на броя */
printf("Error reading countin'1);
exit (1);
}
if(fread (cat, sizeof cat, 1, fp) != 1) {
/* прочитане на данните */
printf("Error reading catalog data.in");
exit (1);
}
fclose(fp);
}
/* Записване на данните от каталога. */
void save(void)
{
Структури и обеднения 303
FILE *fp;
if((fp = fopen("catalog", "wb")) —NULL) {
printf("Cannot open catalog file.Xn");
exit(1);
}
if(fwrite(&top, sizeof top, 1, fp) != 1) {
/* записване на броя */
printf("Error writing count.\n”);
exi t(1);
}
if (fwrite (cat, sizeof cat, 1, fp) !== 1) {
/* записване на данните */
printf("Error writing catalog data.Xn");
exit(1);
}
fclose(fp);
4. В предиш ния пример, целият масив на каталога се съхра-
нява на диска, дори и да не е пълен. Ако искате, можете да
промените функциите load() и $avc(), както е показано
пс-долу, така че на диска да се записват само структури,
конто наистина съдържат данни.
/* Зареждане на файла на каталога. */
void lead(void)
{
FILE *fp;
int i ;
if((fp = fopen("catalog", "rb"))—NULL) {
printf("Catalog file not on disk.Xn");
return;
}
if(fread(&top, sizeof top, 1, fp) 1= 1) {
/* прочитане на броя */
printf("Error reading count.\n");
exit (1.);
}
for(i=0; i<=top; i++) /* прочитан© на данните */
if(fread(&cat[i], sizeof(struct catalog), 1,
fp)!= 1) (
printf("Error reading catalog data.Xn");
exit(1);
}
fclose(fp);
}
/* Записване на данните на каталога. */
304 С - Практически самоучител
void save(void)
{
FILE *fp;
int i;
if((fp = fopen("catalog", "wb"))==NULL) {
printf("Cannot open catalog file.Xn");
exit (1);
)
if(fwrite(stop, sizeof top, 1, fp) != 1) {
/* записване на броя */
printf("Error writing count.\n");
exit (1) ;
}
for(i==0; i<=top; i++) /* записване на данните */
if(fwrite(&cat[i], sizeof(struct catalog), 1,
fp)!= 1) {
printf("Error writing catalog data.Xn");
exit (1);
}
fclose(fp);
)
5. Имената на структурните членове няма да влизат в конф-
ликт с други променливи, използващи същите имена. Тъй
като името на члена е свързано с името на структурата, то
е отделено от другите променливи със същото име. Нап-
ример тази програма огпечатва на екрана 10 100 101.
^include <stdio.h>
int main(void)
{
struct s_type {
int i ;
int j ;
} s;
int i;
i = 10;
s.i = 100;
s.j = 101;
printf("%d %d %d", i, s.i, s.j);
return 0;
Променливата i и членът i на структурата нямат връзка
помежду си.
Струкгури и обединения 305
6. Както беше казано по-рано, функциите могат да връщат
структуры на извикващите процедуры. Следващата прог-
рама например зарежда членовсте на varl със стойностите
100 и 123.23 и след това ги показва на екрана:
^include <stdio.h>
struct s type {
int i;
double d;
};
struct s_type f(void);
int main(void)
{
struct s_type varl;
varl = f () ;
printf("%d %f", varl.i, varl.d);
return 0;
)
struct s_cype f(void)
(
struct s type temp;
temp.i = 100;
temp.d = 123.23;
return temp;
7. Тази програма предава струкгура на функция:
ttinclude <.stdio.h>
struct s_type {
int i;
double d;
void f(struct s type temp);
int main(void)
{
struct s_type varl;
varl.i = 99;
varl.d = 98.6;
f(varl);
return 0;
306 С - Практически самоучител
}
void f (struct s_type temp)
{
printf("%d %f", terop.i, temp.d);
}
УПРАЖНЕНИЯ
1. В глава 9 написахте програма, създаваша телефонен ука-
зател, записван на диска. Подобрете програмата така, че да
използва масив от структури, всяка от който съдържа име-
то на човека, пощенския му кол и телефонния му номер.
Запишете пощенския код като цяло число, а името и теле-
фонния номер - като низове. Нанравете масива с големина
МАХ, където МАХ е със стойност по ваш избор.
2. Какво не е наред с този фрагмент?
struct s type {
int i;
long 1;
char str[80];
} s;
i = 10;
3. Разгледайте самостоятслно хедърния файл S FDIO.H и
проучете дефияипията на структурата FILE.
10.2
Деклариране на указатели към
структури
Достигапето до структура чрез указател е рядко срещапо. Дскла-
рацията на указател към структура става по ст щи я начин, както
и към всеки друг тип данни. Например следващият фрагмент де-
финира структура, наречена s_type, и декларира две променли-
ви. Първата, s, е действителна структурна променлива, а втсрата,
р. е указател към структури от тип s_type.
Структури и обединения 307
struct s type {
int i;
char str[80];
} s, *p;
При дадената дефиниция, тази конструкция присвоява адреса на
s на р:
р - &s:
След като вече р сочи s, можете да достигате до s чрез р. За да
имате достъп обаче до отделен елемент от s чрез р, не можете да
се осланяте на оператора точка. Вместо това трябва да използва-
те оператора стрелка, както е показано в следващия пример:
p->i = 1;
Тази конструкция присвоява стойността 1 на елемента i от s чрез
указателя р. Операторът стрелка се формира от знак минус,
следван от знак за по-голямо. Не трябва да има интервали между
тях.
С предана структурите на функциите изнялс. Ако обаче даде-
на структура е много голяма, нейното предаване може да пре-
дизвика значително иамаляване на скороотта на изпълпение на
програмата. Пора ди тази причина при работа с големи структури
може да поискате да предадете указател към структурата. в слу-
чай че е възможно.
Когато достигате до член посредством структурна промен-
лива, използвайте оператооа точка. Когато достигате до
член посредством указател, използвайте оператора стрелка
ПРИМЕРИ
1. Следващата програма илюстрира как се използва указател
към структура:
ftinclude <stdio.h>
ftinclude <string.h>
struct s_type {
int i ;
char str [CO];
} s, *p;
int main(void)
308 G - Практически самоучител
р = is;
s.i = 10; /* toeа функционално е едно и също */
p->i = 10; /* с това */
strcpy(p->str, "I like structures.”);
printf("%d %d %s", s.i, p->i, p->str);
return 0;
2. Едно много полезно приложение на указателите към
структура се среща при функциите за час и дата в С. Ни-
кои оз тези функции използват указатели към текущата
дата и час на системата. Функциите за час и дата изискват
хедърния файл TIME.H, в който е дефинирана структура,
наречена tin. Тази структура може да съдържа датата и ча-
са, разбита на отделки елементи. Това се нарича раздро-
бено време. Структурата tm е дефинирана така:
struct tm {
int tir_sec;
int tm_min;
int tir. hour;
int tm mday;
int tm_mon;
int tir year;
int tir wday;
int tm_yday;
int tin isdst;
/* секунди, C-61 */
/* иииути, 0-59; */
/* часоЕе, 0-23 */
/* ден от месеца, 1-31*/;
/* месец след януари, 0-11 */
/* години от )900 */
/* дни след неделя, С-6*/
/* дни след 1 януари, 0-365 */
/* индикатор за лятно часово време */
Стойностга на tm_ isdst е положителна, ако s приложено
лятното часово време, нула, ако е астрономического, и от-
рицателнс, ако няма достьпна информация по въпроса. В
Т1МЕ.Н е дефиниран и типы time_t. Това на практика е
long int със способност да представя системиия час и дата
в специален кодиран формаг. Това се нарича календарно
време. За да добиете календарного време на системата,
трябва да използвате функцията time(), чиито прототип е:
ime_t time(time_t *системнэвремеу,
Функцията time() връща или кодираното календарно вре-
ме на системата, или -1, ако то не е достьпно. Освен това
тя поставя тази кодирана форма на времето в променлива-
та, сочена от ситемновреме. Ако обаче ситемновреме е
null, аргументы се игнорира.
Тъй като календарного време се представя посредством
вътрешен формат, специфичен за имплементация"а, тряб-
Структури и обединения 309
еа да използвате друга С функция за час и дата, за да го
коивертирате в десна за използване форма. Една от тези
функции се нарича localtime(). Нейният прототип е:
struct tm *locaiiime(time_t * системновреме)]
Функцията Iacaltiine() връща указател към ’’раздробената”
форма на системновреме. Структурата с раздробекия час
се заделя въгрешно от компилатора и се препокрива при
всяко следващо извикване.
Тази програма демонстрира time() и localtime() чрез
изписване на текушия час на системата:
#include <stdio.h>
#include <time.h>
int main(void)
(
struct tm *systime;
t ime_t t;
t = time(NULL);
systime = localtime(&t);
printf("Time is %.2d:%.2d:%.2d\n",
systime->tm hour, syscim.e->tm min,
systim.e->tm_sec) ;printf ("Date:
% . 2d/% . 2d/% . 2d", systime->tm_mon-t 1,
systime->tm mday, systime->tm year);
return 0;
}
Ето примерен резултат or програмата:
Time is 1G 32,49
Date: 03/15/97
1. Верен ли с този фрат мент?
struct s_type (
int а;
int b;
} S, *p
310 С - Практически самоучител
int main(void)
{
р = &s;
р.а = IGO;
2. Друга функция за час и дата на С се нарича gmtime( ).
Нейният прототип е:
struct tm *gmtime(tirne_t *време):
Функцията gmtime() работи по абсолютно същия начин,
както locakime(), с изключение на това, че връща Coordi-
nated Universal Time (което всъщност е време по Гринуич)
на системата. Променетс програмата от пример 2 така, че
да изписва както мсстното време, така и времето по Гри-
нуич. (Забележка: вашата система може да не разполага с
време по Гринуич.)
.3
Работа с вложени структури
До този момент работпхме със структури, чиито членове се със-
тояха единствено от основните типове на С. Полстата обаче мо-
гат да представляват и други структури. Те се наричат вложени
структури. По-долу е даден пример, използващ вложени струк-
тури с информация за продетавянето на две поточни линии, вся-
ка от конто е с по десет работника:
#definc NUM_ON_LINE 1С
struct worker {
char name[80];
int avg_units_per_hour;
int avg errs_per_hcur;
};
struct asm_line {
int prcduct__code;
double material_cost;
struct worker wkersLNUM_ON_LINE];
} linel, line2;
За да присвоите стойност 12 на полето avg units per hour на
втората структура wkers от linel, използвайте тази конструкция:
Структури и соединения 311
linel.wkers [1] .avg_ur.its_per_hour = 12;
Както можете да видите, структурите се достигат от най-външ-
ната към най-вътрешната. Това сыцо е обща фирма. Винаги, ко-
гато имате вложени структури, започвайте с най-външната и за-
върш вайте с иай-вътрешната.
ПРИМЕРИ
1. Вложена структура можете да използвате, за да подобритс
програмата за картотекиране. В този случай техническата
информация за всяка книга се съхранява в отделпа струк-
тура, която от своя страна е част от структурата catalog.
По-долу е показана цялата програма за картотекиране, из-
ползваща този подход. Обърнете внимание как сега прог-
рама га съхранява големината на книгите в страници.
/* Електрояна картотека - трете подозрение. */
#include <stdio.h>
#incluae <string.h>
#include <stdlib.h>
#define MAX 100
int nenu(void);
void display(int i);
void author_search(void);
void title_search(void);
void enter(void);
void save(void);
void load(void);
s truct book_type {
unsigned dare; /* дата на автсрскс право */
unsigned char ed; /* издание */
unsigned pages; /* големина на кяигата */
} ;
struct catalog {
char name [80]; /* име на автора */
char title 180]; /* заглавие */
char pub[80]; /* издател */
struct bcok_sype book; /* технически информация */
} cat[MAX];
int tep = 0; /* последно използване място */
int irain(void)
{
int choice;
load(); /* прочитана на каталога */
312 С - Практически самоучител
do {
choice = menu();
switch(choice) {
case 1: enter (); /* въвеждане на книгите */
break;
case 2: author_search(); /* търсене no автор */
break;
case 3: title search (); /* търсене no
заглавие */
break;
case 4: save();
}
} while(choice!=5);
return 0;
}
/* Връщане на избор от меню. */
menu(void)
{
int i;
char str[80];
printf("Card catalog:\n");
printf(" 1. Enter\n");
printf(" 2. Search by Author\n");
printf(" 3. Search by Title\n");
printf(" 4. Save catalog\n");
printf(" 5. Quit\n");
do {
printf("Choose your selection: ");
gets(str);
i = atoi (str);
printf("\n");
} while(i<l || i>5);
return i;
)
/* Въвеждане на книги в базата данни. */
void enter(void)
{
int i;
char temp[80);
for(i=top; i<MAX; i++) {
printf("Enter author name (ENTER to quit): ");
gets(cat[i].name);
if(I*cat[ij.name) break;
printf("Enter title: ") ;
gets(cat[i].title);
printf("Enter publisher: ");
gets(cat[i].pub);
printf("Enter copyright date: ");
Структури и обединения 313
gets(temp);
cat[i].book.date = (unsigned) atoi(temp);
printf("Enter edition: ");
gets(temp);
cat[i].book.ed = (unsigned char) atoi(temp);
printf("Enter number of pages: ");
gets(temp);
cat[i].book.pages = (unsigned) atoi(temp);
)
top = i;
)
/* Търсене но автор. */
void author_search(void)
{
char name(80];
int i, found;
printf("Name: ");
gets(name);
found = 0;
ior(i=0; ictop; i++)
if(!strcmp(name, cat[i].name)) {
display(i);
found = 1;
printf("Xn");
}
if(!found) printf("Not FoundXn");
}
/* Търсене по заглавие. */
void title_search(void)
{
char title 180];
int i, found;
printf("Title: ");
gets (title);
found = 0;
for(i=0; i<top; i++)
if(1strcmp(title, cat[i].title)) {
display(i);
found = 1;
printf("Xn");
}
if((found) printf("Not FoundXn");
)
/* Изписване на елемент от каталога. */
void display(int i)
{
314 С - Практически самоучител
printf("%s\n", cat[i].title);
printf("by %s\n", cat[i].name);
printf("Published by %s\n", cat [i].pub);
printf ("Copyright: %u, edition: %u\n",
cat[i] .book.date, cat Ii] .book.ed);
printf ( "Pages: %u\n", cat[i] .book.pages);
)
/* Зареждане на файла на каталога. */
void load(void)
{
FILE *fp;
if((fp = fopen("catalog", "rb”))==NULL) {
printf("Catalog file not on disk.Xn");
return;
}
if(fread(&top, sizeof top, 1, fp) != 1) {
/* прочитане на броя */
printf("Error reading count.\n");
exit (1);
}
if(fread(cat, sizeof cat, 1, fp) != 1) {
/* прочитане на данните */
printf ("Error reading catalog data.Xn");
exit(1);
fclose(fp);
)
/* Записване на файла на каталога. */
void save(void)
{
FILE *fp;
if((fp = fopen("catalog", "wb"))==NULL) {
printf("Cannot open catalog file.Xn");
exit (1);
}
if (fwrite(&top, sizeof top, 1, fp) != 1)
/* записване на броя */
printf ( "Error writing count.\n");
exit (1);
}
if(fwrite(cat, sizeof cat, 1, fp) != 1) {
/* записване на данните */
printf ("Error writing catalog data.Xn")
exit (1);
}
fclose(fp);
Структури и обединения 315
I Подобрсте програмата за телефопеп указател, която папи-
сахте по-рано в тази глава, така че да вклгочва и пощенс-
кия адрес на всеки човек. Съхранявайте адреса в отделча
структура, наречена address, вложена в структурата на те-
лефонния указател.
кхн
Какво представляват побитовите
ПОЛЕТА
С предоставя възможност за същсствуване на един вариант на
структурно поле, наречен побитово поле. Побитовото поле се
състои от един или повече битове. Чрез изпслзването на побито-
во поле, посредством едно име ви се предоставя достъп до един
или повече бита от байт или дума. За да дефинирате побитово
поле, използвайте тази обща форма:
тип име. размер;
В тази декларация, тип е или int или unsigned. Ако западете по-
битово поле със знак, тогава при възможност най-старшият бит
се третира като бит за знак. Броят на битовеге в полего се задава
от размер. Обърнете внимание, че името на побитовото поле и
размера му са разделени с двоеточие.
Побитовите полета са полезни, когато искате да съберете ин-
формацията в най-малкото възможно място. Например тук е да-
дена структура, използваща побитови полета за съхранение на
информацията за запасите.
struct b type {
unsigned department: 3;
unsigned instock: 1;
unsigned backordered: 1;
unsigned lead time: 3;
/* до 7 отдела */
/* 1 ако e налична, 0 ако
не е */
/* 1 ако вече е поръчана,
О ако не е */
/* предварително време за
поръчка, в месеци */
} inv[MAX ITEM];
В този случай един байт може да се използва за съхранение на
информация за инвентара, която обикновено би заела четири
байта, ако не се използват побитови полета. Референцирането
към побитовите полета става по същия начни, както всеки друг
316 С - Практически самоучител
член на структурата. Следващата конструкция например присво-
ява стойност 3 на полото department на позиция 10:
ir.v [ 9] . department = 3,
'Гази конструкция определя дали позиция 5 има наличност:
if(!inv[4].instock) printf("Out of Stock");
else printf("In Stock");
He e необходимо да дефинирате всички битове от байт или
дума. Например това е напълно вярно:
struct b_type {
int а: 2;
int D: 3;
} ;
Компилаторът на С има пълната свобода да съхранява поби-
товите полета там, където може. Обикновеио обаче той автома-
тично ги поставя в най-малката единица памет, която може да ги
иобсре. Дали съхраняването на побитовите полета върви от стар-
шите към младшите битове или обратно, зависи от имплемента-
цията. Повечето комиилатори обаче използват първия начин.
В дефинициите на структури можете да смесвате побитови
полета с членове от други типове. Тази версия на структурата за
запасите например включва и място за името па всяка позиция:
struct b type {
char name[40];
unsigned department: 3;
unsigned instock: 1;
unsigned backordered: 1;
unsigned lead time: 3;
} inv[MAX_ITEM];
/* име на позицията */
/* до 7 отдела */
/* 1 ако е налична,
0 ако не е */
/* 1 ако вече е поръчана,
0 ако не е */
/* предварително нреме за
поръчка, в месеци */
Тъй като най-малката единица памет, която може да се адре-
сира, е байт, не можете да получите адреса на побитова промен-
лива. Побитовите полета често се използват за съхранение на
булеви данни (true/false), защото те дават възможност за ефек-
тивно използване на паметта - запомнете, че можете да постави-
те осем булеви стойности в един-единствен байт.
Структури и обединения 317
ПРИМЕРИ
I. При използването иа побитови полета не е необходимо да
именувате всеки бит. Тук например е дадена структура,
използвана побитови полета, за да достига до първия и
последняя бит от един байт.
struct b гуре {
unsigned first: 1;
int : 6;
unsigned last: 1;
};
Употребата на безименни побитови полета улеснява дос-
тигането до набелязаните битове.
2. За да видите колко полезни могат да се окажат побитовите
полета при работа с булеви данни, тук е дадена груба си-
мулация на рекордер от космически полет. Чрез побиране-
то на цялата важна информация в един байт, за записване-
то на полета се използва сравнително малко дисково прос-
транство.
/* Симулация на 100 минутен рекордер на
космически полет.*/
#include <stdlib.h>
#include <stdio.h>
/* всички полета означават ОК с 1,
повреда или ниско ниво - с 0 */
struct telemetry {
unsigned fuel; 1;
unsigned radio: 1;
unsigned tv; 1;
unsigned water; 1;
unsigned food: 1;
unsigned waste: 1;
} flt__recd;
void display(struct telemetry i);
inn main(void)
FILE *fp;
int i;
if((fp = fopen("flight", "wb"))=«NULL) {
printf("Cannot open file.Xn");
exit (1) ;
}
318 С - Практически самоучител
/* Представете си, че всяка минута на диска се
записва отчет за състоянието на космическия
кораб. */
for(i=0; i<100; i++) {
flt_recd.fuel = rand()%2;
flt_recd.radio = rand()^2;
flt_recd.tv = rand()%2;
flt_recd.water = rand()$2;
flt_recd.food = rand()%2;
fit. recd.waste = rand()%2;
di splay(flt_recd);
fwrite(&flc_recd, sizeof fit recd, 1, fp);
)
fclose(fp);
return 0;
void display(struct telemetry i)
{
if(i.fuel) printf("Fuel OK\n");
else printf("Fuel lowin’’);
if(i.radio) printf("Radio OK\n");
else printf("Radio failure in");
if(i.tv) printf("TV system OK\n");
else printf ("TV malfunctionin'') ;
if(i.water) printf("Water supply OKin");
else printf("Water supply lowin’’);
if (i.food) printf("Food supply 0K\n");
else printf ("Food supply lowin’’);
if(i.waste) printf("Waste containment OKin");
else printf("Waste containment failurein"),
printf("in");
В зэвисимост от начина на пакстиране на побитовите по-
лета, използван от вашия компилатор, след изпълнението
на тази програма, файлът на диска може да се скаже с
дължина само 100 байта. Сега изпробвайте програмата от-
ново, но след като промените структурата telemetry, както
е показано тук:
struct telemetry {
char fuel;
char radio;
char tv;
char water;
char foed;
char waste;
} fit recd;
Структури и обединения 319
При тази версия не се използват побитови полета и резул-
татнйя файл е дълъг ионе 600 байта. Както можете да ви-
дите. употребата на побитови полета може да доведе до
зиачителни икономии на място.
I. Напишете програма, създаваща структура с три побитови
полета, наречени а, Ь и с. Нека а и b да са с дължина 3 би-
та, а с - с 2. След това присвоете на всяко от тях някаква
стойност и ги изпишете на екрана.
2. Много компилатори предоставят библиотечки функции,
връщащи текущото състояние на различии устройства, ка-
то например серией порт или клавиатура. Тази информа-
ция се предоставя в кодиран начин бит-по-бит. Консулти-
райте се самостоятелпо с ръководството па вашия компи-
лятор, за да видите дали той поддържа такива функции.
Ако е така, напишете няколко програми, конто четат и де-
кодират състоянието на различии устройства.
10.5
Създаване на обединения
В С, обединение е определена част от паметта. която се поделя от
две или повече променливи. Променливите, поделящи паметта,
могат да са от различии типове. В даден момент обаче, може да
се използва само една променлива. Обединението се дефинира
много подобно на структура. Неговата обща форма е:
union име-на-етикет {
тир член1;
тип член2;
тип членЗ;
тип членМ,
} имена-на-променливи'.
Подобно на структурата, или име-на-етикет, или имена-на-про-
менливи може да липсва. Членовете могат да са от вески валиден
320 С - Практически самоучител
тип в С. Тук например е дадено обединение, съдържащо три
елсмента: цяло число, масив от знакове и double променлива.
union u_cype {
int i ;
char c[2] ,
double d;
} sample;
Това обединение ще се представи в памегга, както е показано на
фигура 10-2.
Фигура 10-2
Как се представя в паметта една инстан-
ция на обединението и type (предпопагат се 2-байтов int и
8-байтов double)
За да достигнете до член на обединението, използвайте оле-
раторите точка и стрелка, както при структурите. Следващата
конструкция например присвоява 123.098 на d от sample:
sample.d = 123.098:
Ако достигате до обединение чрез указател, трябва да използва-
те оператора стрелка. Например да предположим, че р сочи
sample. Следващата конструкция присвоява на i стойност 101:
р->1 = 101;
Важно е да разберете, че размсрът на обединението се фикси-
ра по време на компилирането и той е достатьчно голям, за да
побере пай-големия член на обединението. Ако предположим 8-
байтов double, това означава, че sample ще е с дължина 8 байта.
Дори и в даден момент sample да се използва за съхранение на
int стойност, той все пак ще заема 8 байта памет. Както и при
структурите, така и при обединенията трябва да използвате опе-
ратора по време на компилиране sizeof, за да определите размера
на обединението. Не трябва просто да приемате, че той ще е с
размера най-големия елемент, защото в някои среди компилато-
рът може да подравни обединението в цяла дума.
Струкгури и обединения 321
ПРИМЕРИ
I. Сбсдииеннята са много полезно, когато определени данни
трябва да се интерпретират по два или псвече начина
Например показзната по-долу функция encode( ) използва
обединение, за да кодира цяло число, като размена двата
младши байта. Същата функция може да използвате за де-
кодиране на закодирано цяло число посредством врьщане
на вече разменените байтове на първоначалните им места.
^include <stdio.h>
int encode(int i);
int main(void)
int i;
i = encode(10); /* кодиране */
printf("10 encoded is %d\n", i);
i = encode(i); /* декодиране */
printf ("i decoded is %d", i);
return 0;
}
/* Кодиране и декодиране на цели числа. */
int encode lint i)
I
union crypt type {
int num;
char c[2] ;
} crypt;
unsigned char ch;
crypt, num = i;
/* размяна на Оайтовесе * /
ch = crypt.c[0];
crypt.c(0] = crypt.c[l);
crypt.c[l] = ch;
/* връдцане на кодирано число */
return crypt.num;
}
Програмата изписва следното:
10 encoded is 2560
i decoded is 10
322 С - Практически самоучител
2. Следващата програма използва обединение на структура,
съдържаща побитови полета, и зпакова променлива, за да
изобрази двоичного лредставяне на знак, въведен от кла-
виатурата:
/* Тази програма изобразява двоичния код на знак,
въведен от клавиатурное. */
#include <stdio.h>
ftinclude <conio.h>
struct sample {
unsigned a: 1;
unsigned b: 1;
unsigned c: 1;
unsigned d: 1;
unsigned e: 1;
unsigned f: 1;
unsigned g: 1;
unsigned h- 1;
};
union key type !
char ch;
struct sample bits;
} key;
int main(void)
(
printf ("Strike a key: ");
key.ch = getche();
printf("\nBinary code is: ") ;
if(key.bits.h) printf ("1 ’’) ;
else printf("0 ") ;
if(key.bits.g) printf("1 ") ;
else printf("0 ") ;
if(key.bits.f) printf("1 '•) ;
else printf("0 ") ;
if(key.bits.e) printf ("1 ");
else printf ("0 ") ;
if(key.bits.d) printf ("1 •’ ) •
else printf("0 ") ;
if(key.bits.c) printf("1 ”) ;
else printf("0 ") ;
if(key.bits.b) printf("1 ") ;
else printf("0 ") ;
if(key.bits.a) printf ("1 ");
else printf("0 ");
return 0;
}
Структури и соединения 323
Когато бъде натиснат клавиш, неговият ASCII код се при-
своява на key,ch, която е от тип char. Гази информация се
преинтерпретира като поредица от побитови полета, да-
ваши възможност за двоичното представяне на клавиша.
Тук с даден примерен резултат:
Strike a key: X
Binary code is- 0 1 0 1 10 0 0
1. Чрез обединение, състоящо се от double стойност и
8-байтов знаков масив, напишете функция, записваща
double стойността във файл на диска, знак по знак. Напи-
шете друга функция, ксято пропита тази сгойност от фай-
ла и конструира othobg стойността чрез същото обсдине-
ние. (Забележка: Ако дължината на double на вашия ком-
пилятор не е 8 байта, използвайте знаков масив от съот-
ветния размер.)
2. Напишете програма, използваща обединение за конверти-
ране на int в long. Демонстрира йте, че тя работи.
Проверка на уменията, представени в главата
На този етап би трябвало да сте в състояние да отговорите па
тези въпроси и да изпълните упражнепията:
1. В най-общи линии, какво е структура и какво е обединение?
2. Покажете как се създава структурен тип, наречен s_ty ре,
който съдържа тези пет елемента:
char ch;
float d;
int i;
char str[80];
double balance;
324 С - Практически самоучител
Освен това дефинирайте променлива, наречена s_var, като
използвате структурата.
3. Какво не е наред в този фрагмент?
struct s_type {
int а;
char b;
float bal;
} inyvar, *p;
p = &myvar;
p.a = 10;
4. Напишете програма, използваща масив от струкгури за
съхранение на имената, телефонните номера, отработени-
те часове и часовата ставка на служителите в една фирма.
Дайте възможност за записване на информация за 10 слу-
жителя. Нека програмата изисква въвеждането на инфор-
мацията и да я записва на диска. Наречете файла ЕМР.
5. Напишете програма, която прочита файла ЕМР, създаден в
упражнение 4, и изписва информацията на екрана.
6. Какво е побитово поле?
7. Напишете програма, изписваща поотделно стойностите на
младшия и старшия байт на short int. (Съвет: използвайте
обединение, съдържащо два елемента - short int и двубай-
тов знаков масив.)
ПРОВЕРКА НА НАТРУПАНИТЕ УМЕНИЯ
Тази секция проверява колко добре сте интегрирали материа-
ла от тази глава с този от предишните.
1. Напишете програма, съдържаща две структурни промен-
ливи, дефинирани като:
struct s_type {
int i;
char ch;
double d;
} varl, var2;
Структури и обединения 325
Нека програмата даде на всеки член на двете структури
начална стойност, но тези стойности трябва да са различии
за двете структури. Чрез функция, наречена struct_swap(),
нека програмата разменя съдържанието на varl и var2.
2. Както вече знаете от глава 9, fgetc() връща целочислена
стойност, въпреки че прочита знак от файл. Напишете
програма, която копира един файл в друг. Присвоявайте
връщаната от fgetc() стойност на обединение, съдържащо
целочислен и знаков член. Използвайте целочисления
член, за да проверите за EOF. Запишете знаковия елемент
във файла-цел. Нека потребителят определя в командния
ред имената на файла-източник и файла-цел.
3. Какво не е наред в този фрагмент?
struct s_type {
int а;
int b: 2;
int c: 6;
} var;
scanf("%d", &var);
4. В С, както знаете, не можете да предавате масив като па-
раметър на функция. (Може да предавате само указател
към масив.) Съществува обаче начин да заобиколите това
ограничение. Ако капсулирате масива в структура, той се
предава чрез стандартната конвенция за извикване по
стойност. Напишете програма, демонстрираща това, като
предава низ, намиращ се в структура, променя неговото
съдържание във функцията и показва, че оригиналният
низ е непроменен след завършването на функцията.
11
Разширени типове
ДАННИ ИОПЕРАТОРИ
Разглеждани теми к главата
11.1 Използване на
специфика™ рите от класа за
съхранение
11.2 Изпопзване на
модиспикаторите за достъп
11.3 Дефиниранена изброявания
11.4 Какво представлява typedef
11.5 Използване на побитовите
оператори на С
11.6 Овладяване на опэраторите
за изместване
11.7 Операторът?
11.8 Още за оператора за
присвояване
11.9 Операторът запетая
11.10 Приоритетите на
сперациите
328 С - Практически самоучител
Езикът С включва богат набор от модификатори на типо-
ве данни, даващи възможност за по-добро съгл асу ване
на типа на дадена променлива с информацията, която тя
ще съхранява. Освен това С включва редица специални
оператори, разрешавший сьздаването на много ефективен код.
Това са двете теми, разглеждани в настоящата глава
Проверка на знанията
Преди да продължим, трябва да сте в състояние да отговорите
на следващите въпроси и да изпълните следшгге упражнения.
1. Напишете програма, използваща масив от структури за
съхранение на вторите и третите степени на числата от 1
до 10. Изпишете съдържанието на масива.
2. Напишете програма, използваща union за изобразяване на
байтовете на цяло число, въведено от потребителя.
3. Какво отпечатва този фрагмент1? (Предположете 2-байтов
int и 8-бактов double.)
union {
int i;
double d;
} uvar;
printf("%d", sizeof uvar);
4. Какво не e наред в този фрагмент?
struct {
int i ;
char str[80];
double balance;
) svar;
svar->i = 100;
5. Какво e побитово поле?
Разширени типове данни и оператори 329
11-1
Използване на спецификаторите
ОТ КЛАСА ЗА СЪХРАНЕНИЕ
С дефинира четири типа модификатора конто определят как да
се съхранява дадена променлива. Те са
auto
extern
register
static
Тези спецификатори се поставят преди името на типа. Нека сега
да разгледаме всеки от тях.
Спецификаторът auto е напълно ненужен. Той се предоставя
със С, за да се постигне съвместимост с неговия предшественик
- В. Той се прилага за деклариране на автомашични промеиливи.
Автоматичните промеиливи са просто локални промеиливи, кон-
то по пс-дразбиране са auto. Почти нпкога няма да срешнете auto
з програма на С.
Въпреки че програмите от тази книга са доста кратки, тези от
реалния свят стават доста мащабни. С нарастването па размера
на една програма, нараства и времето, необходимо за нейното
компилиране. Поради тази причина С предоставя възможност за
“раздробявапе” на програмите на два или повече файла. При то-
ва положение можете да компилирате файловете поотделно и
след това да ги евържете. Така се съкращава времето за компи-
лиране и се улеснява работата с нроектите. (Същинският метод
за разделяне па компилацията и евързването е обяснеп в доку-
ментацията на вашия компилатор.) Когато работите с множество
соре файлове обаче, трябва да имате нещо предвид. Като общо
правило, глобалните данни могат да се дефинират само веднъж.
Тези данни обаче може да трябва да се достигат от два или пове-
че файла на програмата. В такъв случай всеки соре файл трябва
да информира компилатора за използваните от него глобални
данни. За да постигнете това, ще трябва да използвате ключовата
дума extern. За да разберете защо, разгледайте следващата прог-
рама, която е разделена в два файла:
ФАЙЛ #1
#include <stdio.h>
int count;
void fl(void);
330 С - Практически самоучител
int main(void)
{
int i;
fl(); /* настройка на стойността на count */
for(i=C; i<count; i++)
printf("%d ", i);
return 0;
ФАЙЛ #2
fincluae <stdlib.h>
void fl(void)
{
count = rand();
}
Ако се опитате да компилирате втория файл, ще се получи греш-
ка, защото променливата count не е дефинирана. Не можете
обаче да примените ФАЙЛ #2 така-
#include <stdlib.h>
int count;
void fl(void)
{
count - rand();
}
Ако декларирате count за втори път, много от свързващитс прог'
рами ще докладват за дублиране па символ, коего означава, чс
count е дефинирана два пъти, и свързвашата програма не знае
коя променлива да използва.
Решението на този проблем е в спецификатора extern на С.
Чрез поставляете на extern прел декларация™ на count във
ФАЙЛ #2, казвате на компилатора, че count е цяло число, декла-
рирано друг аде. С други думи, използването на extern информи-
ра компилатора за съществуването и типа на променливата, коя-
то той предшества. но това не предизвиква ново заделяне на па-
мет за нея. Правилната версия на ФАЙЛ #2 е:
♦include <stdlib.h>
extern int count;
void fl(void)
{
Разширони типоЕб данни и оператори 331
count = rand();
Въпреки че се прави рядко, не е некорсктпо да използвате
extern във функция за деклариране иа глобална променлива, ко-
ято е декларирана някъде другаде в съгция файл. Например след-
ният фрагмент е верен:
^include <stdio.h>
int count;
int irain(void)
extern int count; /* -гона референцира глобалната
count */
count = 10;
printf("%d", count);
return 0;
Причината тази у потреб а на extern да се среща рядко е, че е из-
лишня. Когато компилаторът сре дне име на променлива, която
не е дефинирана от функцията като локална, той предполага, че
тя е глобална
Един от важните спецификатори от класа за съхранение е
register. Когато задавате register променлива, вие казвате на
компилатора, че искате достъпът до тази променлива да бъде
възможно най-бърз. В първите версии на С, register можеше да
се припага само за локални променливи (включително формални
параметри) от тип int, char или указател. По този начин промен-
ливите се съхраняваха в регисгрите на процесора. (Така се стиг-
на до името register.) Посредством пзползвансто на регистрите
на процесора се постигат изключителио малки времена за дос-
тъп В модерните версии на С, дефиницията на register е разши-
рена, така че да обхваща всички типове и е премахнато изисква-
нето тези променливи да се съхраняват в регистрите на процесо-
ра. Вместо това стандарты /XNSI С повелява, че register про-
менливите трябва да се съхраняват така, че времето за достъп да
е минимално. На практика обаче това означаза, че register про-
менливите от тип int и char отново щс се съхраняват в регистри
те на процесора - това все още е най-бързият начни за достигаке
до тях.
Без значение кой метод за съхранение използвате, само из-
вестно количество променливи могат да бъдат допуснати до най-
бързото време за достъп. Например процесорът има ограничен
брой регистри. Когато се заемат местата за бърз достъп. комки-
332 С - Практически самоучител
лагоръ г има правою да преобразува register променливите в
обикновени. Поради това трябва много внимателно да избирате
кои промеиливи ше модифицирате с register.
Един от добрите избери е да модифицирате с register тази
променлива, която се използва най-често, като например про-
менливата за управление на цикъл. Колкою повече една промен-
ива се достьпва, толкова повече се подобрява изпълнението, ко-
гато времето за достъп до нея се намали. Общо взето, можете да
приемете, че поне две промеиливи от всяка функция могат наис-
тина да се оптимизират.
Важно: тъй като дадена register променлива вероятно се съх-
ранява в регистър на процесора, тя може да няма адрес в памет-
та. Това означава, че не можете да използвате оператора &, за да
разберете адреса на register променлива
Когато използвате модификатора static, можете да предизви-
кате запазванего на съдържанието на дадена локална променли-
ва между отделяйте извикваиия на функцията, в която е декла-
рирана. Освен това за разлика от обикяовените локални промен-
ливи, инициализирани при всяко влизане във функцията, локал-
ната static променлива се инициализира само веднъж. Например,
разгледайте слсдната програма:
#include <stdio.h>
void f(void);
int main(void)
{
int i;
for(i=0; i<10; i++) f();
return 0;
void f(void)
static int count = 0;
count++;
printf (’’count is %d\n", count);
Тази програма отпечатва следния резултат:
count is 1
count is 2
count is 3
count is 4
count is 5
Разширени типове данни и оператори 333
count is 6
count is 7
count is 8
count is 9
count is 10
Както можете да видите, count задържа своята стойност между
отделимте извиквания на функцията. Предимството при използ-
ването на static локална променлива вместо глобална е, че ло-
калната static променлива е известна и достъпна само във функ-
цията, която я декларира.
Модификаторы' static може да се използва и за глобални
променливи. В този случай, глобалиата променлива става дос-
тьпна само за функциите от файла, в конто е декларирана. Фун-
кциите, който не са декларирани в един и същ файл с глобалиата
static променлива, не само не могат да я използват, но те дори не
знаят името й. Това означава, че не съществуват конфликта с
имената, ако една глобална static променлива има същото име,
както друга глобална променлива от друг файл на същата прог-
рама. Например, разгледайте следните два фрагмента, конто са
части от една и съща програма;
ФАЙЛ #1
int count;
ФАЙЛ #2
static int count;
count = 10;
count = 5;
printf("%d",
count);
printf
count);
Тъй като count e декларирана като static във ФАЛЛ #2, няма
да възникнат конфликта с имената. Конструкцията printf( ) от
ФАЙЛ #1 отпечатва 10, а тази от ФАЙЛ #2 - 5, защото двете
променливи count са напълно отделив една от друга.
ПРИМЕРИ
1. За да придобиете представа колко ло-бърз е достъпът до
register променливите, опитайте следващата програма. Тя
използва друга стандартна библиотечка функция на С, на-
речена с|оск( ). Тази функция връща броя на циклите на
системния часовник от започването на изпълнението на
програмата до текущия момент. Нейният прототип е:
clock_t clock(void);
334 С - Практически самоучител
Тя използва хедърния файл TIME.H. TIME.H дефинира и
типа clockt, който е повече или по-малко същото като
long. За да засечете времето на събитие посредством
clock(), извикайте функцията точно преди събитието и за-
пазете, върнатата стойност. След това я извикайте за втори
път, след приключване на събитието, и извадете началната
стойност от крайната. Това е подходът, използван от след-
ващата програма за отчитане на времето за изпълнение на
цикъл. Единият набор от цикли се контролира от register
променлива, а другият - от обикновена.
ftinclude <stdio.h>
ftinclude <time.h>
int i; /* Това няма да бъде трансформирано в
register променлива, защото i е
глобална.*/
int main(void)
{
register int j;
int k;
clock_t start, finish;
start = clock ();
for(k=0; k<100; k++)
for(i=0; i<32000; i++) ;
finish = clock();
printf("Non-register loop: %ld ticks\n",
finish - start);
start = clock ();
for(k=0; k<100; k++)
for(j=0; j<32000; j++);
finish = clock();
printf("Register loop: %ld ticks\n",
finish - start);
return 0;
При повечето компилатори цикълът, управляван с register
променлива, ще е около два пъти по-бърз от другия.
Обикновената променлива е глобална, защото при въз-
можност, практически всички компилатори автоматично
ще конвертират локалните променливи в register промен-
ливи, като резултат от заложената автоматична оптимиза-
ция. Ако не видите предсказаните резултати, това може да
означава, че компилаторът автоматично е оптимизирал и i.
Въпреки че вие не можете да декларирате глобалните
Разширени типове даинй й оператори 335
промеиливи като register, ййщо не спира компилатора да
оптимизира програмата по този начин. Ако не забележите
особена разлика между двата цикъла, опитайте като съз-
дадете допълнителни глобални промеиливи преди i, таке
че тя да не може автоматично да бъде оптимизирана.
2. Както вече знаете, компилаторът може да оптимизира с
бърз достъп само ограничен брой register промеиливи от
всяка функция (най-вероятно около две). Това обаче не
означава, че вашата програма може да притежава само ня-
колко register промеиливи. Поради начина на изпълнение
на програмите на С, всяка функция може да използва мак-
сималния брой register промеиливи. Например при стан-
дартните компилатори, всички промеиливи от следващата
програма ще бъдат оптимизирани:
#include <stdio.h>
void f2(void);
void f(void);
int main(void)
i
register int a, b;
void f(void)
{
register int i, j;
void f2(void)
register int j, k;
3. Локалните static промеиливи имат няколко приложения.
Едно от тях е да дават възможност на дадена функция да
извършва различии инициализации само веднъж - когато
се извиква за първи път. Например, разгледайте тази фун-
кция:
336 С - Практически самоучител
void myfunc(void)
{
static int first = 1;
if(first) { /* инициализиране на системата */
rewind ifp);
a = 0;
loc = 0;
fprintf("System Initialized");
first = 0;
Тъй като first e static, тя ще запазва своята стойност меж-
ду отделяйте извиквания. Затова кодът за инициализация
ще се изпълнява само при първото извикване на функцията.
4. Друго интересно приложение на локалните static промен-
ливи е за контролиране на рекурсивни функции. Например
следващата програма отпечатан на екрана числата от 1 до 9.
#include <stdio.h>
void f(void);
int main(void)
(
f 0 ;
return 0;
}
void f (void)
{
static int stop=0;
s top++;
if(stop==10) return;
printf("%d ", stop);
f(); /* рекурсивно извикване */
}
Обърнете внимание как stop се използва за предотвратят
ване па рекурсивно извикване на it ), когато нейната стой-
ност стане 10.
5. По-долу е даден още един пример за използването на ex-
tern, така че да се позволи използването на глобални дан-
ни от два файла:
Разширени типове данни и оператори 337
ФАЙЛ#1
finclude <stdio.h>
char str [80];
void getname(void);
int main(void)
{
getname();
printf("Hello %s", str);
return C;
ФАЙЛ #2
#include <stdio.h>
extern char str[80];
void getname(void)
(
printf("Enter your first name: ");
gets(str);
}
1. Предположен, не вашият компилятор може в дсйствител-
ност да оптимизира достъпа само до две register промен-
ливи във всяка функция. Кои две промеиливи са подходя-
щи да ее модифицират с register?
^include <stdio.h>
Цinclude <conio.h>
int main(void)
(
int i, j, k, m;
do {
printf("Enter a value: ");
scanf("%d", &i);
m = 0 ;
fcr(j=0; j<i,4 j++)
for(k=0; k<10C; k++)
338 С - Практически самоучител
m = к + го;
) while(1>Ь);
return 0;
1
2. Напишете програма. съдържатца функция на име sumjt()
със следния прототип:
void sum_it (int value);
Нека тази функция да използва локална static променлива,
за да поддържа и отпечатва текущата сума от стойностите
на параметрите, с конто се извиква. Например ако
sum_it() се извика три пъти със стойностите 3, 6. 4, тя ще
изпише 3, 9 и 13.
3. Изпрсбвайте програмата, описана в пример 5. Задължи-
тслно използвайте два отдслни файла. Ако не знаете как да
компилирате и свьржете програма, състояща се от два
файла, консултирайте се с ръководството на компилатора.
4. Какво не е наред с този фрагмент?
register int i;
int *p;
p = &i;
11.2
ИЗПОЛЗВАНЕ НА МОДИФИКАТОРИТЕ
ЗА ДОСТЪП
С включва два типа модификатора влияещи върху начина на
достъп до промснливите, от страна на програмата и компилато-
ра. Това са const и volatile. Тази секция изследва тези видове мо-
дификатора
Ако преди типа на дадена променлива поставите const, тя ня-
ма да се променя от програмата. Все пак променливата може да
получи начална стойност посредством инициализация при ней-
пото деклариране. Компилаторът има пълната свобода да поста-
вя const променливи в ROM (read-only memory - памел само за
четене), където средата позволява това. const променливите мо-
гат да променял своите стойности и посредством хардуерни
средства.
Разширени типове данни и сператори 339
Модификатора const има и друго приложение. Той може да
предотврати промяната на обект, сочен от параметьр, от страна
на някоя функция. Това означава, че ако един параметър-указа-
тел е предшестван от const, нито една конструкция от функцията
не може да променя променливата, сочена от него.
Ако пред името на топа на дадена променлива поставите
volatile, вие казвате на компилатора, че сгойностга па променли-
вата може да се променя по начини, конто не са изрично дефи-
нирани в програмата. Например адресът на дадена променлива
може да се предаде на процедура за обработка на прекъсване, и
нейната стойност ще се променя при всяко възникване на пре-
късването. Причината volatile да е много важен модификатор е,
че повечето компилатори па С нрилагат сложни и изтьнчени оп-
тимизации върху програмата, за да създадат по-5ърз и по-ефек-
тивен код Ако компилаторът не знае, че съдържането на една
променлива може да се променя по начини, конто не са изрично
зададени в програмата, той всъщност може да не проверява съ-
държанието на променливата при всяка референция към нея.
(Това, разбира се, не се отнася за случая, когато променливата
стой от лявата страна конструкция за присвояване.)
ПРИМЕРИ
1 Следващата програма демонстрира как на една const про-
менлива може да се зададе начална стойност и след това
да се използва в програмата, стига да не се поставя в лява-
та част на конструкции за присвояване.
ttinclude <stdio.h>
int main(void)
const int i = 10,-
printf("%d", i); /* това e правилно */
return 0;
)
Следващата програма се опитва да присвой друга стой-
ност на 1, но тя няма да се компилира, защото променлива-
та не може да се променя от програмата.
ttinclude <stdio.h>
int main(void)
{
const int i = 10;
340 С - Практически самоучител
i - 20; /* това е грешно */
printf("%d"r i);
return 0;
}
2. Следващата програма показва как указателен параметьр
може да се декларира като const, за да се предотврати
промяната на сочения от него обект,
♦include <staio.h>
void pr_str(const char *p) ;
int main(void)
{
char str [80];
printf("Enter a string: ");
gets (str);
pr_str(str);
return 0;
void pr str(const char *p)
{
while(*p) putchar(*p+t);
}
Ако примените програмата, както e показано по-долу,
тя няма да се компилира, защото тази версия прави опити
да променя низа, сочен ст р.
К-include <stdio.h>
♦include <ctype.h>
void pr_str(const char *p);
int main(void)
[
char str[80];
printf("Enter a string: ");
gets(str);
pr str (Str);
return 0;
}
void nr_str(const char *p)
{
Разширени типове данни и оператори 341
while(жр) {
*р = tcuppcr(*p); /* това няма да сс компилира */
putchar(*р++);
}
3. Вероятно най-важната характерна черта на const укдзатсл-
ните параметр и е, че те гарантират, че много от стандарт-
ните библиотечки функции няма да променят променли-
вите, сочена от техните параметры. Например тук е даден
истинският прототип на strlen(), зададен от стандарта
ANSI С:
size„t strlen(con$t char *str);
Тъй като str е зададен като const, соченият от него низ не
може да се прсменя.
4. Въпреки чс е трудно да се намерят кратки примери за
приложение на volatile, следвашият фрагмент ще ви даден
някаква лредстава за това:
volatile unsigned u;
give_address_co_some_interrupt(5u);
for(;;) { /* наблюдаване на стойността на u */
printf("%d", u);
В този пример, ако и не беше декларирана като volatile,
компилаторът можеше да оптимизира повгорясмитс из-
виквания на printff) по такъв начин, че стойността на и да
не се проверява всеки път. Употребата на volatile лринуж-
дава компилатора наистина да получава стойността на и
при всяко нейно приложение.
I. Добро приложение на const е, ако искате да вградите в
програмата число за контролиране на версията. Чрез из-
ползвапето на const променлива за съдържане на версия га,
вне я предпазвате от неволното й променяне. Напишете
342 С - Практически самоучител
кратка програма, илюстрираща как може да се направи то-
ва. За число на версията използвайте 6.01.
2. Напишете ваша версия на strcpy(), наречена mystrcpy(),
която да притежава следния прототип:
char *mystrcpy(char *е, const char *c-m);
Функцията връща указател към е. Демонстрирайте вашата
версия на mystrcpy() чрез програма.
3. Помислете самостоятелно дали можете да измислите на-
чини за използване на volatile.
11.3
Дефиниране на изброявания
В С можете да дефинирате списък от именувани целочислени
константи, наречен изброяване. Тези константи могат да се из-
ползват на мястото на всяко цяло число. За да дефинирате изб-
рояване, използвайте тази обша форма:
enam име-на-етикет {списък на изброяването} списък-с-
променливц;
Както име-на-етикет, така и списък-с-променливи не са задъл-
жителни, но лоне едното трябва да присъства. Име-на-етикет
всъщност е името на типа на изброяването. Например
enum color type (red, green, yellow} color;
В този случай се създава изброяване, състоящо се от константиге
red, green и yellow. Етикетът на изброяването е color_type и се
декларира променлива, наречена color.
По подразбиране компилаторът присвоява целочислени стой-
ности на колстантите от изброяването, като започва с 0 за най-
лявата част на списъка. Всяка константа вдясно е по-голяма с
единица от предшестващата Ето защо в изброяването на цвето-
вете red е 0. green е I, a yellow е 2. Въпреки това ви се предоста-
вя възможност да ирипокриете подразбиращите се стойности на
компилатора, като изрично зададете стойност на дадена констан-
та. Например в този фрагмент
enum color_type {red, green=5, yellow} color;
red все още e 0. но green e 9, a yellow e 10.
Разширени типове данни и оператори 343
След като веднъж сте дефинирали изброяване, можете да из-
ползвате иеговото име на етикет, за да декларирате изброителни
промеиливи в други точки от програмата. Например ако използ-
ваме изброяването color type, следващата конструкция е напьл-
но валидна и тя декларира mycolor като colcr_type променлива:
enuir. color type mycolor;
Тъй като изброяването всъщност е целочислен тип, всяка
променлива от типа на изброяването може да съдържа всякакви
целочислени стойности - не само дефинираните от изброяване-
то. За по-голяма яснота и структурност обаче, трябва да използ-
вате тези промеиливи за съхранение само на стойности, дефини-
рани о г типа на изброяването
Две от основните приложения на изброяването са за предос-
тавяне на само-документиращ се код и за изясняване на структу-
рата на програмата.
ПРИМЕРИ
1. Следващата къса поограма създава изброяване, състоящо
се от частите на компютър Тя присвоява стойността CPU
на променливата comp и след това изписва нейната стой-
ност (която el). Обърнете внимание как се използва иметс
на етикета на изброяването, за да се декларира comp като
изброителна променлива, отделно от същинската деклара-
ция на computer.
^include <stdio.n>
enum computer {keyboard, CPU, screen, printer};
int main(void)
{
enum computer comp;
comp = CPU;
printfcomp);
return 0;
2. Необходимо e малко усилие за изписване на низовия екви-
валент на константа от изброяване. Запомнете, че констан-
тите от изброяванията не са низове; те са именувани цело-
числени константи. Следващата програма използва конст-
рукция switch, за да отпечата низовия еквивалент на стой-
344 С - Практически самоучител
ноет ст изброяване. Програмата използва генератор на
случайни числа, за да избере начин на транспортиране.
След това изписва избора на екрана. (Тази програма е за
хора, коитс не могат да решат сами!)
ftinclude <stdio.h>
ftinclude <stdlib,h>
ftinclude <conio.h>
enum transport {car, train, airplane, bus} tp;
int main(void)
{
printf("Press a key to select transport: ");
/* генериране на произволне число при всяко
стартиране на програмата */
while(!kbhit()) rand О;
getch(); /* прочитана и изхвърляне на знака */
tp = rand() % 4;
switch(tp) {
case car: printf("car");
break;
case train: printf("train");
break;
case airplane: printf("airplane");
break;
case bus: printf("bus");
}
return 0;
}
При някои случаи e възможен по-лесен начин за полу-
чаване на низовите еквивалента на стойност от изброява-
пе. Ако не инициал изирате нито една от константите, мо-
жете да създадете двумерен низов масив, съдържащ низо-
вите константи на константите от изброяването, в същия
ред, както са подредени константите в изброяването. След
това можете да индексирате масива посредством стойност
от изброяването, за да добиете съответния низов еквива-
лент. Следваща версия на програмата за избор на превоз
например използва този подход.
ftinclude <stdic.h>
ftinclude <stdlib.h>
ftinclude <conio.h>
enum transport {car, train, airplane, bus} tp;
char trans[][2G] - {
Разшир&ни типове данни и оператори 345
"car", "train", "airplane", "bus"
};
int main (void)
(
printf("Press a key to select transport: ");
/* Генериране на ново случайно число при всяко
стартиране на програмата */
while(!kbhit()) rand();
getch(); /*' прочитане и изхвърляне на знака */
tp = rand() % 4;
printf("%s", trans[tp]);
return 0;
}
3. Запомнете, че имената на константите от изброяванията са
известии само на програмата, а не на каквито и да било
библиотсчни функции. Ако например е даден следният
фрагмент
enum numbers [zero, one, two, three] num;
printf("Enter a number: ");
scanf("%d", &num);
не можете до отговорите на scanl() като въведете one.
1. Компилирайте и изпълнете примерните програми.
2, Създайте изброяване за монетите от цент до долар.
3. Верен ли е този фрагмент? Ако не, защо?
enum cars (Ford, Chrysler, GM) make;
make = GM;
printf("car is %s", make);
346 С * Практически самоучител
11-4
Какво представлява typedef
В С можете да създавате ново име на тип посредством typedef.
Общата форма на typedef е:
typedef старо-име ново-име,
Новою име можете да използвате за деклариране на променли-
ви. Например в следващата програма smaHint е ново име за
signed char и се използва за деклариране на i.
#include <stdio.h>
typedef signed char smallint;
int main(void)
{
smallint i;
for(i-C; i<10; i++)
printf("%d ", i);
return 0,-
}
Две неща трябва добре да запомните: първо. typedef не предиз-
виква деактивирансто па оригиналиото име. Например в дадсна-
та програма, signed char все още е валиден тип. Второ, можете
да използвате няколко конструкции typedef, за да създадете мно-
го различии нови имена на един и същ тип.
Съществуват, общо взето, две причини за използването на
typedef. Например ако знаете, че ще пишете про1рама, която ше
се изпълнява на компютри, използващи 16-битови цели числа,
както и на такива с 32-битови, и искате да сте сигурни, че опре-
делени промеиливи ще са 16-битови и в двете среди, можете да
използвате typedef при компилирането на програмата за
16-битови машини така:
typedef int myint;
След това преди да ксмпилирате кода за 32-битов компютър,
можете да промените конструкцията typedef по този начин:
typedef short int myint;
Това ще работа вярно, защото при компютрите, използващи
32-битови цели числа, short int ще бъде с дължина 16 бита. Ако
предположим, че сте използвали myint за деклариране на всички
целочислени стойности, който искате да са 16-битови, ще трябва
Разширени типове данни и оператори 347
да промените само една конструкция, за да смените типа на
всички променливи, използващи myint.
Втората причина, пора ди която може би ще ви се наложи да
използвате typedef, е за да предоставите самодокументиращ се
код. Например ако създавате програма за инвентара, можете да
използвате подобна конструкция typedef:
typedef double subtotal;
В този случай, ако някои чете програмага и види променлива,
декларирана от тип subtotal, той или тя ще знае, че тя се използ-
ва за съхраняване на междинна сума.
ПРИМЕРИ
1. Новото име, създадено от една конструкция typedef, може
да се използва в следващи typedef за създаване на други
имена. Разгледайте например този фрагмент:
typedef int height;
typedef height length;
typedef length depth;
depth d;
В то.зи случай d все сше е от тип int.
2. В допълнение към базовите типове, можете да използвате
typedef с по-слс жни типове. Например следният фрагмент
е напълно валиден:
enum e_type {one, two, three } ;
typedef enum e_type mynums;
mynums num; /* деклариране на променлива */
Тук num е променлива от типа e_type.
УПРАЖНЕНИЯ
1. Покажете как бихте могли да направите UL ново име за
unsigned long. Покажете, че то работи, като напишете
кратка програма, която декларира променлива от тип UL,
348 С - Практически самоучител
а след това й присвоява стойност и изписва тази стойност
на екрана.
2. Какво не е наред с този фрагмент?
typedef balance float;
Използване на побитовите
оператори на С
С съдържа четири снециални оператора, извършващи своитс
операции на побитово ниво. Тези оператори са:
лобитов AND (И)
лобитов OR (ИЛИ)
побигов XOR (изключващо ИЛИ)
инвертиране
Тези оператори работяг с типовете char и int; те не могат да се
използват с типове с плаваша запетая.
Операторите AND. OR и XOR генерират резултат, базиран на
сравнение™ на съответните битове на всеки операнд. Опсратс-
рът AND вдига в единица един бит, ако и двата сравнявани бита
са единици. OR вдига в единица, ако пене един от сравняваните
битове е вдигнат. Операцията XOR вдига даден бит в единица,
когато някой от двата бита е единица, но не и когато и двата са 1
или 0. Тук е даден пример с побитой AND:
10100110
Л 0011 10 11
00 1 0 00 1 0
Обърнете внимание как се настройва резултатния бит в резултат
от изхода на операцията, приложена върху съответните битове
от двата операнда.
Операторът за инвсртиране с единичен оператор, който ин-
вертера състоянието на всеки бит от дадено цяло число или знак.
Разширени типове данни и оператори 349
ПРИМЕРИ
1. Операцияга XOR има едно интересно свойство, Нека са
дадени две стойности на А и В и върху з ях е приложен
операторът XOR. Ако върху резултата от тази операция се
приложи XOR с В, тогава се генерира А. Например този
резултат
initial value of i: 100
i after first XOR: 21895
i after second XOR: 1GO
се получава от следната програма:
tfinclude <stdio.h>
int main(void)
{
int i;
i = 100;
printf("initial value of i: %d\n", i) ;
i = i л 21987;
printf("i after first XOR: %d\n", i);
i = i л 21987;
printf ("i after second XOR: %d\n", i);
return 0;
2. Следващата програма използва побитое AND, за да изоб-
рази двоично ASCII стойността на знак, вьведен от клави-
атурата:
#include <stdio.n>
#include <conio.h>
int main(void)
{
char ch;
int i;
printf("Enter a character: ");
ch = getche ();
printf ("\n");
/* показване на двоичната репрезентация */
for(i=128; i>0; i=i/2)
if(i & ch) printf("1 ");
else printf("0 ");
350 С - Практически самоучител
return 0;
Програмата работа чрез нагласяването на i така, че да се
настройва само по един бит при всяко сравнение. Тъй като
пай-стар шият бит от даден байт прсдставлява 128, тази
стойност се използва като начална точка. При всяко пре-
минаване през цикъла, i се намалява наполозина. По този
начин се вдига следващият бит, а останалите се нулират.
Така, при всяко изпълнение на цикъла се проверява разли-
чен бит на ch. Ако този бит е 1, сравнението генерира ве-
рен резултат и се отпечатва 1. В другия случай се изписва
0. Този процес продължава, докато се проверят всички би-
тове.
3. Чрез модифициране на програмата от пример 2, тя може '
да се използва за демонстриране на ефекта от прилагането
на оператора за инвертиране.
((include <stdio.h>
#include <conio.h>
int inain (void)
char ch;
int i;
ch = ’a’;
/* показание на двоичнага репрезентация */
for(i=129; i>0; i=i/2)
if (i & ch) printf("1 ");
else printf("0 ");
/* инвертиране на битовете */
ch = -ch;
printf ("\n");
/* показване на двоичната репрезентация */
for(i=128; i^O; i=i/2)
if(i s ch) printf("1 ");
else printf("0 ");
return 0;
}
Когато стартирате тази програма, ще видите, че състояни-
ето па битовете на ch се инвертира след прилагането на
оператора ~
Разширени типове данни и оператори 351
4 Следващата програма показва как да използвате оператора
&, за да определите дали едно цяло число със знак е по-
ложително или отрицателно. (Програмата предполага, че
short int е с дължина 16 бита.) Тъй като отрицателните
числа се представят чрез вдигнат най-старши бит, сравне-
ниетс ще е вярно само ако i е отрицателно. (Стойността
32768 е стойността на unsigned short int, само когато е
вдигнат най-старшият бит. Двоично тази стойност е
10000000.)
ftinclude <stdio.h>
int main(void)
short i;
printf("Enter a number: ");
scant("%hd", &i);
if(i & 32768) printf("Number is negative.\n");
return 0;
}
5. Следващата програма преврыца i в отрицагелно число, ка-
то вдига най-стартия му бит. Ютновс се предполага
16-битов short int)
ftinclude <stdio.h>
int main(void)
{
short i;
i = 1;
i = i I 32768;
printf("%hd", i);
return 0;
Програмата отпечатва-32 767.
1. Един от лес ните начини за закодиране на файл е чрез ин-
вертиране на всеки бит посредством оператора Напише-
352 С - Практически самоучител
те програма, която кодира файл по този метод. (За да де-
кодирате файла, просто стартирайте програмата втори
път.) Нека потребителят задава името на файла в команд-
ния ред.
2. По-добър метод за кодиране на файл е да използвате XOR
операция в комбинация с потребителски-дефиниран ключ.
Напишете програма, която кодира файл по този метод.
Нека потребителят задава файла за кодиране заодно с сд-
нознаковия ключ в командния ред. (За да разкодирате
файла, изпълнете отново програмата със същия ключ.)
3. Какъв е резултатът от тези операции?
А. 1010 0011 & 0101 1101
В 0101 1101 | 1111 1011
С 0101 0110 А 1010 1011
4. Понякога най-старии£ят бит от байта се използва като
контролен бит от програмите за модеми. Той се използва
за проверка на целостта на всеки прехвърлен байт. Същес-
твуват два вида контрол: по четност и по нечетност. Ако
се използва контрол по четност, контролния бит се изпол-
зва, за да се осигури четен брой 1 във всеки байт. При кон-
трола по нечетност, контролнияг бит се използва за осигу-
ряване на нечетен брой I във всеки байт. Тъй като конт-
ролния бит не е част от преносимата информация, покаже-
те как бихте могли да смъкнете най-старшия бит от стой-
ността на един байт
.6
ОВЛАДЯВАНЕ НА ОПЕРАТОРИТЕ ЗА
ИЗМЕСТВАНЕ
С включва два оператора, конто не ее срещат чест о в другите
комнютърни езици: операторите за побитово изместване вляво и
вдясно. Операторът за изместване вляво е «, а вдясно е ». Тези
оператори могат да се прилагал само върху char и int операнди.
Те приемат следните общи форми:
стойност « брой-битово
стойност » брой-битсве
Цело числения г израз, зададен от брой-битове, определи колко
позиции наляво или надясно трябва да се изместят битовете на
Разширени типсве данни и оператори 353
стойност. Всяко изместване наляво кара всички битове на даде-
ната стойност да се изместят с одна позиция наляво, а отдясно се
вкарва нула. При изместването надясно, битовете се изместват
надясно, а в най-лявата позиция се слага нула. (Освен ако число-
то е отрицателно, в който случай се слага единица.) Когато се
изместват битове от краищата, те се изгубват.
Отместване вдясно е еквивалентно на деление на 2, а отмест-
ването вляво - на умножение по 2. Поради вътрешното оиерира-
не на почти всички процесори, операциите с изместване обикно-
вено са по-бързи от еквивалентните аритметични операции.
ПРИМЕРИ
1. Тази програма демонстрира операторите за изместване
вляво и вдясно •
((include <stdio.h>
void show_binary(unsigned u) ;
int main(void)
{
unsigned short u;
u = 45678;
show_binary(u);
u = u « 1;
show binary (u) ;
u = u » 1;
show_binary(u);
return 0;
)
void show binary(unsigned u)
{
unsigned n;
for(n=32768; n>0; n=n/2)
if(u & n) printf("1 ");
else printf("0 ");
printf("\n");
}
Резултатът от тази програма е:
1011001001101110
0110010011011100
0011001001101110
354 С - Практически самоучител
Обърнете внимание, че след измесгването вляво се изгуби
бит с информация. При извършване на изместване вдяс но
се вмъква нула. Както беше казано по-рано, изместваните
бито кете от краищата се губят.
2. След като изместването вдясно е еквивалентно на деление
на две, но по-бързо, функцията show binary() може да се
направи по-ефективна, както е показано тук:
void show binary(unsigned u)
{
unsigned n;
for(n=32768; n; n=n>>l)
if(u & n) printf("1 ");
else printf("0 ");
printf ("\n") ;
}
1. Напишете програма, използваща оператори за отместване
за умножаване и деление на цели числа. Нека пот ребите-
лят да въвежда начатната стойност. Отпечатайте резултата
от всяка операция.
2. С няма оператор за ротиране. Ротацията е подобна на
измесгването, с изключение на това, че битьт, изместен от
единия край, се вкарва в другим. Например ако 1010 0000
се ротира с една позиция наляво, се получава 0100 0001.
Напишете функция, наречена rotate( ), ротираща с една
позиция наляво при всяко извикване. (Съвет: ще трябва да
използвате обединение, за да имате достъп до бита, от-
менен от края на байта.) Демонстрирайте функцията в
програма.
ОПЕРАТОРЪТ ?
С съдържа един троен оператор. ?. Тройният оператор изисква
три операнда. Операторът ? се използва на мястото на конструк-
ции от рода на:
Разширени типове данни и оператори 355
if(condition) var - expl;
else var = exp2;
Общата форма на оператора ? е:
променлива =усповие ? израз 1:израз2,
Тук условие е израз, генерираш true или false. Ако е true, на про-
менлива се присвоява стойността на израз!. Ако е false, се прис-
воява стойността на израз2. Причината за оператора ? е, че ком-
пилаторът на С може да произволе по-ефективен код като изпол-
зва него вместо еквивалентната конструкция if/else.
ПРИМЕРИ
1. Следващата програма илюстрира оператора ?. Тя прочита
число и след това го конвертира в 1, ако е положително, и
в -1, ако е отрицателно.
#include <stdio.h>
int main(void)
{
Int i;
printf ("Enr.er a number: ");
scanf("%d”, &i);
i = i>0 ? 1: -1;
printf("Outcome: %d", i);
return 0;
2. Следващата програма представлява компюгьрно подхвър-
ляне на монета. Тя изчаква да натиснете клавиш и след то-
ва отпечатва Heads (ези) или Tails (тура).
#include <stdio.h>
^include <stdlib.h>
#include <conio.h>
int main(void)
{
int i ;
while(!kbhit()) rand();
i = rand() %2 ? 1: 0;
356 С - Практически самоучител
if(i) printf("Heads*);
else printf("Tails");
return 0;
Програмата за подхвърляне на монета може да се налише
по по-ефективен начин. Ияма технически причина опера-
торът ? задължително да присвоила стойностга си на про-
менлива. Поради това програмата може да се напише така:
ttinclude <stdio.h>
ttinclude <stdlir>.h>
ttinclude <conio.h>
int main(void)
{
while (! kthit () ) rand();
rand()%2 ? printf("Heads”) : printf("Tails");
return 0;
}
Тъй кото извикването на функция e валиден израз в С. на-
пълно допустимо е да извикате printfQ в конструкцията ?.
1. Едно особено добро приложение на оператора ? е за пре-
доставяне на начини за нредиазване от деление на нула.
Напишете програма. която прочита две цели числа от пот-
ребителя и отпечатва резултата от първото, разделено на
второго Използвайте оператор ?, за да избегнете деление
на нула.
2. Конвертируйте следващата конструкция в еквивалентна ?
конструкция.
if(a>b) count = 100;
else count = С;
Разширеии типове данни и оператори 357
11.8
Още за оператора за
ПРИСВОЯВАНЕ
Операторът за присвояване в С е по-мощен, отколкото в повече-
то дру1 и компютърни езици. В тази секция ще научите някои
нови неща за него.
Имате възможност да присвоявате една и съща стойност на
няколко променливи чрез следната обща форма:
применлива1 = променпива2 = променливаЗ = ... = променливаН =
= стойност,
Например тази конструкция
i - j = k = 100;
присвоява на i, j и к стойността 100. В професионално написани-
те програми доста често може да видите такива присвоявания.
Друга вариация на оператора за присвояване е т.нар. стеног-
рафско присвояване. В С, тази конструкция
а = а + 3;
може да се преобразува така:
а += 3;
По принцип при всяка конструкция от вида
променлива = променлива оператор израз,
можете да запишете стенографската форма така:
променлива оператор= израз;
В този случай оператор о един от следните оператори.
+ - ‘ / % « » & | А
Не трябва да има интервал между оператора и знака за равенст-
во. Причината, поради която ще искате да използвате стенограф-
ското присвояване, е не защото така си спестявате малко писане,
а защото компилаторът на С може да създаде по-ефсктивен из-
пълним код.
35В С - Практически самоучител
ПРИМЕРИ
1. Следващата програма илюстрира конструкцията за прис-
вояване на множество промеиливи:
^include <stdio.h>
int main(void)
int i, j, k;
i = j = k = 99;
printf("%d %d %d", i, j, k);
return 0;
}
2. Следващата програма брой до 98 през едно. Обърнете
внимание, че тя използва стенографско присвояване, за да
инкрементира променливата за управление на цикъла.
#include <stdio.h>
int main(void)
int i;
/* броене през едно */
for(i=0; i<100; i+=2)
printf("%d ", i);
return 0;
3. Следващата програма използва стенографска форма на
оператора за изместване вляво, за да умножи стойността
на i по 2 три пъти. (Резултатната стойност е 8.)
#include <stdio.h>
int main(void)
(
int i = 1;
i <<= 3; /* три пъти умножение по 2 */
printf ("%d", i);
return 0;
Разширени типове данни и оператори 359
упражнения
•г'* ?<»-&<* .-Ё'Х'Ч»1 П*'»* JV ЛЧ •?*&
1. Компилирайте и изпьлнете програмата от пример 1, за да
видите, че конструкцията за присвояване на множество
променливи работа.
2. Как ще се напише следвашага конструкция в стенографска
форма9
X = X & у
3. Напишете програма, изписваща всички нечетни числа, де-
лими на 17, в интервала от 17 до 1 000. Използвайте сте-
нографски форми.
11.9
Операторът запетая
Последният разглеждан оператор е запетаята. Той притежава
много уникална функция: запетаята казва на компилатора "‘нап-
рави това и това”. Това означава, че запетаята се използва за на-
вързване на няколко операции заедно. Най-честага употреба на
запетаята е в цикъла for. В следващия цикъл запетаята се изпол-
зва в частта за инициализация, за да се инициализират две про-
менливи за управление на цикъла, както и в чаезта за инкремен-
тиране, за да се инкремеитират i и j.
for(i=0, j = 0; i+j<count; i++, j++) . . .
Стойностга па списък от изрази, разделени със залетай, е най-
десният от тях. Например следващата конструкция присвоява на
value стойностга 100:
value - (count, 99, 33, 100);
Скобите са нужни, защото операторы запетая е с по-нисък при-
оритет от оператора за присвояване.
ПРИМЕРИ
1. Тази програма отпечатва числата от 0 до 49 Тя използва
оператора запетая, за да поддържа две променливи за уп-
равление на цикъла.
^include <stdio.b>
int main(void)
360 С - Практически самоучител
int i, j;
/* брсене до 49 */
for(i-*0, j=100; i<j; i++, j —)
printf("td ", i);
return 0;
2. Па много места в С e синтактично вярно да използвате за-
петая вместо точка и запетая. Разгледайте например след-
ната кратка програма:
((include <stdio.h>
int main(void)
{
char ch;
ch = getcnarO, /* забележете запетаята тук */
putchar(ch+1) ;
return 0;
1
Тъй като запетаят а казва на компилатора да “направи това
и това”, програмата върви по един и същ начин със запе-
тая след getchar( ), както и с точка и запетая. Използване-
то на запетая по този начин обаче се счита за много лошо
процедиране. Възможно е появата на странични сфекти.
(Тази употреба на запетаята обаче е интересна за обсъж-
дане в обедните почивки! Много програмисти па С не са
запознати с тази особеност на синтаксиса на С.)
1. Напишете програма, изпслзваща оператора запетая за
поддръжка на три промеиливи за управление на цикъл for
Иска едната от променливите да върви от 0 до 99, втората
- от -50 до 49, а третата да е настроена на сумата на пър-
вите две, както първоначално, така и при всяка итерация.
Нека цикълът да сире, когато първата променлива достиг-
не 100, а програмата да отпечатва стойността на трета
променлива при всяко изпълнение на цикъла.
Разширени типове данни и оператсри 361
2. Каква е стойността на i след изпълнениего на следната
конструкция?
i = (1, 2, 3) ;
11.10
Приоритетите на операциите
Следващата таблица илюстрира приоритетите на всички опера-
тори в С.
Най- зисок () [ ] ->
I ~ + - ++ — (преобразуване на тип) * & sizeof
* / %
+ -
« >>
<<=>>=
= = i=
&
л
Я&
II
?.
= +- -= *= /= и т н
Най-нисък
Проверка на уменията, представени в главата
На този етап би трябвало да сте в състояние да отговорите на
тези въпроси и да изпълните упражненията:
1. Каква е ролята на спецификатора register?
2. Каква е ролята па модификаторите const и volatile?
3. Напишете програма, сумираща числата от 1 до 100. Нап-
равете я така, че да се изпълнява възможно най-бързо.
362 С - Практически самоучител
4. Вярна ли е тази конструкция? Ако да, какво прави тя?
typedef lonq double bigfloat;
5. Напишете програма, която прочита две знакови промен-
ливи и сравняла съответните битове. Нека програмата да
отпечатва номера на всеки съвпадаш бит. Например ако
двете чел и числа са:
1001 0110
1110 10 10
Програмата трябва да отпечата , че битове 7, 1 и 0 съвпа-
дат. (Използвайте побитовите оператори, за да разрешите
този проблем.)
6. Каква е ролята на операторите « и »?
7. Покажете как може да пренапишеге следната конструкция:
с = с + 10;
8. Пренапишете следната конструкция чрез оператор ?:
if(!done) count = 100;
else count = 0;
9. Какво e изброяване? Покажете пример за изброяване, кое-
то изрежда плане гите.
Проверка на натрупаните умения
Тази секция проверява колко добре сте интегрирали материа-
ла от тази глава с този от предишните.
1. Напишете прог рама, която размена младшите четири бита
на един байт със старшите. Демонстрирайте работоспо-
собността на програмата, като изпишете съдържанието на
байта преди и след размяната посредством разработената
по-рано функция show_binary() (Трябва обаче да проме-
ните show_blnary( ) така, че да работа с осембитови стой-
ности.)
Разширени типове данни и оператори 363
2. По-рано създадохте програма, която кодира файлове пос-
редством оператора за инвертиране. Напишете програма,
четяща текстов файл, кодиран по този метод, и изобразя-
ваща декодираното му съдържание Трябва обаче да зала-
зите кодирането на същинския файл.
3. Верен ли е този фрагмент?
reqister FILE *fp;
4 Използвайте програмата, създалена за глава 10, секция
10.3, упражнение 1, за да я оптимизирате, като изберете
определени промеиливи да станат от тип register.
ПРЕДПРОЦЕСОРЪТ НА С и
НЯКОИ ПРОФЕСИОНАЛНИ
ТЕМИ
Разглеждани теми в главата
12.1 Още за #define и #include
12.2 Условно компилиране
12.3 #еггог, #undef, #line и
//pragma
12.4 Вградените в С макроси
12.5 Използване на операторите
#и##
12.6 Указатели към функции
12.7 Овладяване на динамичното
заделяне
366 С - Практически самоучител
ПОЗДРАВЛЕНИЯ! Ако сте работали достатъчно в пре-
дпишите глави, определено можете да се наречете
програмист на С. Настоящата глава разглежда три те-
ми: предпроцесорът на С, указатели кьм функции и
системата за динамично заделяне в С. Всички пъзможнссти, раз-
глеждани в тази глава, са важни и трябва да знаете за тяхното
съществуване. Много от тях обаче няма да използвате веднага.
Това не е зашото някоя от темите, обсъждана в главата, е особе-
но трудна, а защото някои от тях са прилсжими при програмира-
нето и поддържанего на големи и сложни системи. С нараства-
нето на уменията ви по С обаче ще започнете да оценявате цен-
ността на тези характеристики.
Проверка на знанията
Преди да продължим, трябва да сте в състояние да отговорите
па следващите въпроси и да изпълните следните упражнения
1 Какво е основиото предимство, когато една променлива се
декларира като register?
2. Какво не е наред с тази функция?
void myfunc(const int *i)
{
*i = *i / 2;
}
3. Какъв e резултатът от тези операции?
а. 1101 1101 & 11100110
Ь. 1101 1101 I 11100110
с. 1101 1101 л 11100110
4. Напишете програма, изголзваща операторите за измества-
не вляво и вдясно, за да удвой или намали наполовина
число, въведено от потребителя.
5. По какъв друг начин могат да се папигаат следните конст-
рукции?
Предпроцесорът на С и някои професионални теми 367
а = 1 ;
b = 1;
с = 1;
if(a<b) max = 100;
else max = 0;
*
2;
6. За какво служи спецификаторы за тип extern?
12.1
Още за #d efine и # include
Въпреки че от известно време пасам използвате #define и
^include. те притежават повече възможности, отколкото досега
сте прочели. В тази секция всяка от тези директивы се изследва
подробно.
В допълнение към приложение™ на #define за дефиниране на
име на макрос, който да се замести със знаковата поредица, асо-
циирана с този макрос, можете да използвате tfdefine, за да съз-
дадете макрос, подобен на функция. При такьв макрос, па него
могат да се подавят аргумента, когато той се разширява от пред-
процесора. Разгледайте следния пример;
ttinclude <stdic.h>
ttdefine SUM(i, j) i + j
int main(void)
{
int sum;
sun = SUM(10, 20);
printf("%d", sum);
return 0;
J
Редът
sum = SUM(10, 20) ;
се преобразува от предпроцесора на
sum = 10+20;
Както можете да видите, стойносгите 10 и 29 автоматично за-
местват параметрите i и j
368 С - Практически самоучител
Ho-практически пример е RANGE( ), илюстриран в следва-
щата проста програма. Той се използва за потвърждение дали
параметьрът i е в интервала, затворен от min и max. Можете да
си представите колко полезен може да се окаже макрос като
RANGE() в програми, конто трябва да извършват редица про-
верки за интервала. Тази програма го използва, за да отпечатва
случайни числа в интервала от 1 до 100.
ftinclude <stdic.h>
ftinclude <conic.h>
ftinclude <stdlib.h>
ftdefine RANGE(i, min, max) (i<min) I I (i>max) ? 1 : 0
int main(void)
{
int r;
/* отпечатване на случайните числа между 1 и 100 */
do {
do {
г = rand();
) while(RANGE(г, 1, 100));
printf("%d ", r);
} while(!kbhit());
return 0;
Прсдимството от използването на макроси, подобии на функ-
ции, вместо функции е, че инлайн кодът се генери ра от макроса
и по този начин се спестява времето, необходимо за извикване и
връщане от функция. Разбира се, само относително прости опе-
рации могат да се извършват в макроси, подобии на функции.
Освен това, порали дублиранего на кода, резултатната програма
може да е по-дълга, отколкото ако използвате функции.
Директивата ftinclude притежава две общи форми:
ftinclude <име-на-файл>
ftinclude '‘име-на-файл"
Досега всички примерни програми използваха първата фор-
ма. Причината за това ще стане ясна след като прочетете след-
вашите описания.
Лко зададете името на файла между ъглови скоби, вие инст-
руктиратс компилатора да тьрси файла по някакъв имплемента-
ционно-дефиниран начин. За повечето компилатори това означа-
ва търсене в специална директория, предназначена за стандарт-
нитс хедърни файлове. Ето зато примерните програми използ-
ваха тази форма, за да включат хедърните файлове, изисквани от
Предпроцесорът на С и някои профссионапни теми 369
стандартните библиотечки функции, Ако заградите името на
файла в кавички, компилаторът ще търси файла по друг импле-
ментационно-дефиниран начин. Ако това търсене пропадне, то-
гава започва ново такова, все едно че файлът е зададен с ъглови
скоби. При по-голямата част от компилаторите, заграждането на
името в кавички предизвиква претърсването първо на текущата
директория. Обикновено ще използвате кавички, за да включите
хедърни файлове, създадени от вас.
ПРИМЕРИ
1. По-долу е дадена програма, използваща макроса, подобен
на функция, МАХ( ), за да изчисли кой аргумент е по-го-
лям. Обърнете особено внимание на последната конструк -
ция printf().
#include <stdio.h>
#define MAX(i, j) i>j ? i ; j
int main(void)
{
printf (,,%d\n", MAX(1, 2));
printf("%d\n", MAX(1, -1));
/* тази конструкция не работи вярно */
printf("%d\n", МАХ(100 && -1, 0));
return 0;
}
Когато предпроцесорът разгъва последнага printf( ) конс-
трукция, макросы' МАХ( ) се преобразува в този израз:
10G && -1 > 0 ? 1G0 && -1 : 0
Поради правилата за приоритетите в С обаче, този израз
ще се изпълни, все едно че скобите са посгавени така:
100 && (-1 > 0) ? 100 && -1 : О
Както можете да видше, това ще предизвика изчисляьане-
то на грешен отговор. За да поправим този проблем, мак-
росы трябва да се пренапише така:
ttdefine MAX(i, j) ((i)>(j)) ? (i) : (j)
370 С - Практически самоучител
Сега макросы ще работи при всички възможни ситуации.
По принцип ще се налага да ноставяте скоби около всички
параметри на макросите, подобии на функции.
Макросът RANGE(), обсъден по-рано, също ще има нужда от
подобно поставяне на скоби, за да може да работи правилно
във всички случаи. Това е оставено като упражнение
2. Следващата програма използва кавички в директивата
//include.
ttinclude "stdio.h"
int main(void)
printf("This is a test");
return 0;
Въпреки че не e толкова ефективно, както използването на
ъглови скоби, конструкцията //include все пак ще успее да
намери и включи хедърния файл STD1O.H.
3. Позволено е използването на двете форми на директивата
//include в една и съща програма. Например
ttinclude <stdio.h>
ttinclude "stdlib.h"
int main(void)
printf ("This is a random number: 3,d", rand'));
return 0;
}
1. Поправете макроса RANGE(), като добавите скоби на
правилните места.
2. Напишете програма, използваща параметризиран макрос
за изчислявапе на абсолютната стойност- на цяло число.
Предпроцесорът на С и някои професионални теми 371
3. Комнилирайге пример 2 Ако вашият компилятор не на-
мери STDIO.H, проверете отново инструкпиите за инста-
лация, дадени с него.
12.2
УСЛОВНО КОМПИЛИРАНЕ
Предпроцесорът на С включва няколко директиви, конто позво-
ляват някои части от кода да се компилират условно. Това се на-
рича условно компилиране. Тези директиви са:
#if
#else
#elif
#endif
#ifdef
#ifndef
Настоящата секция разглежда тези директиви.
Общата форма на #if е показана тук:
#if израз-от-константи
поредица-ст-конструкции
#erdtf
Ако стойността на израз-от-константи е true, конструкцията,
или конструкциите, между #if и tfendif се компилират. Ако изра-
зът-от-константи е false, компилаторът пропуска норедица-от-
кинет рукции. Не забравяйте, че предпроцесорът е първият етап
от компилацията, така че израЗ-от-констанпш означава точно
това. В него не могат да се използват променливи.
Можете да използвате #else, за да формулирате алтернатива
на #if. Тази обща форма е показана тук:
#if израз-от-константи
поредица-от-конструкции
#else
поредица-от -к онстр у кии и
#endif
Обърнете внимание, че има само един #endif. #else автоматично
терминира блока с конструкции на #if. Ако изразып-от-
константи е грешен, се компилират конструкцията или конст-
рукциите, асоциирани с #else.
Мсжете да създавате if-else-if стълбица посредством директи-
вата #eiif, както е показано тук:
372 С - Практически самоучител
#if израз-от-константи-1
порсдица -от-конструкции
#elif израз-от-константи-2
поредица-от-конструкции
#elif израз-от-константи-3
поредица-от-конструкции
#endif
Щом се срещне израз със стойност true, асоциирачите с него ре-
дове се компилират, а останалата част от кода се пропуска.
Друг подход при условного компилиране е директивата
#ifdef. Нейната обща форма е:
#ifdef име-на-макрос
псредица-от-конструкции
#endif
Ако име-на-макрос е дефинирано, тогава поредица-от-кон-
струкции, асоциирана с директивата #ifd ef, се компилира, в об-
ратен случай се пропуска. И тух може да добавите #else, за да
предоставите алтсрнатива.
Допълнението на #ifdef е #ifndef. Неговата обща форма е съ-
щата, както на #ii d ef, с тази разлика, че поредицата от конструк-
ции, асоциирана с #ifndef, се компилира само ако име-на-макрос
не е дефинирано.
В дспълнение към #ifdef, съществува още един начин да оп-
ределите дали дадено име на макрос е дефинирано. Можете да
използвате директивата #if в обединение с оператора по време на
компилиране defined. Операторът defined е със следната обща
форма:
defined име-на-макрос
Ако име-на-макрос е дефинирано, тогава резултатът е true. В
противен случай е false. Например следващите две предпроце-
сорни дирекгиви са еквивалентни:
fcifdef WIN32
#if defined WIN32
Освен това можете да използвате оператора ! заедно с defined, за
да инвергирате условието.
Предпроцесорът на С и някои профасионални теми 373
ПРИМЕРИ
1. Понякога ще искате поведен ието на програмата да завись
от кякоя стойност, дефинирана в програмата. Въпреки че е
трудно да се намерят кратки и съшенременно смислени
примери за такъв случай, следващата програма дава ня-
каква представа. Тази програма може да се компилира да
изписва пли самага ASCII кодова таблица, или разширсна-
та таблица, в зазисимост от стойността на CHAR SET.
Както знаете, ASCII таблицата дефинира знакове за стой-
ностите от 0 до 127. Повечето компютри обаче резервират
стойностите от 128 до 255 за знакове на други езици, ма-
тематически или други символи. (Не е лошо да тествате
програмата с CHARSET, равно на 256. Ще видите някои
наистина интерссни знакове!)
((include <stdio.h>
/* дефиниране на CHAR_SET или ка’го 256, или 128 */
#define CHAR_SET 256
int main(void)
int i;
#if CHAP SET ==256
printf("Displaying full ASCII character set
plus extensions.\n");
Helse
printf("Displaying only ASCII character set.Xn");
#endif
for(i=0; i<CHAR_SET/ i++)
printf("%c", i);
return 0;
2. Добро приложение на #ifdef e за вграждане на информа-
ция за дебъгване в програмите. Например тук е дадена
програма, която копира съдържанието на един файл в
друг:
/* Копиране на файлу */
йinclude <stdio.h>
((include <stdlib.h>
((define DEBUG
int main(int argc, char *argv[])
FILE *from, *toj
374 С - Практически самоучител
char ch;
/* проверка за верен срой аргумента в
командния ред */
if(argc!=3) {
printf("Usage: copy <source> <desuination>\n");
exit(1);
}
/* отваряне на файла източник */
if((frcm= fopen(argv[l|, "rb"))==NULL) {
printf("Cannot open source file.Xn");
exit (1);
}
/* отваряне на файла цел */
if((to = fopen (argv[2], "wb")) =-NULL) {
printf("Cannot open destination file.Xn");
exit(1) ;
/* кспиране на файла */
while(!feof(from)) {
ch = fgetc(from);
if(ferror(from)) {
printf("Error reading source file.Xn");
exit(1);
}
if(!feof(from) ) {
fputc(ch, to);
#ifdef DEBUG
putohar(ch);
#endif
if(ferror(to)) {
printf("Error writing destination file.Xn");
exit (1) ;
}
fclose(from);
fclose(to);
return 0;
Ako DEBUG e дефиниран, програмата изписва всеки пре-
несен байт, Това може да се окаже доста полезно във фа-
зата на разработване. След като програмата е готова, кснс-
трукцията, дефинираща DEBUG, се отстранява, и няма
отпечатване Ако обаче в бъдеще програмата не работи
вярно, DEBUG може отново да се дефинира и тогава пак
щс се отпечагват байтовсте. Докато това може би ви изг-
лежда много работа за такава проста програма, в практи-
Предпроцесорът на С и някои професионални теми 375
ката програмите може да ймат много конструкции за де-
бъгване и тази процедура би могла значително да улесни
цикъла на разработване и тёстване.
Както е показано в програмата, за да дефинирате име на мак-
рос, не е нужно да асоциирате каквато и да е кодова таблица.
3. Нека продължим с темата за дебъгването. Имате възмож-
ност да използвате #if, за да управлявате лесно няколко
нива на код за дебъгване. Например по-долу е дадена една
от програмите за кодиране от отговорите на глава 11, коя-
то поддържа три нива на дебъгване:
ttinclude <stdio.h>
ttinclude <stdlib.h>
/* нива на DEBUG:
0: няма дебъгване
1: изписване на байта, прочетен от
файла източник
2: изписване на байта, записан във
файла цел
3: изписване на прочетените и
записаните байтове
*/
ttdefine DEBUG 2
int main(int argc, char *argv[])
{
FILE *in, *out;
unsigned char ch;
/* проверка за верен брой аргументи в
командния ред */
if(argc!=4) {
printf("Usage: code <in> <out> <key>");
exit(1);
/* отваряне на файла източник */
if((in = fopen(argv[l], "rb"))==NULL) {
printf("Cannot open input file.\n");
exit(1);
}
/* отваряне на файла цел */
if ((out - fopen(argv[2], "wb"))==NULL) {
printf("Cannot open output file.\n");
exit(1);
376 С - Практически самоучител
ch = fgetc(in);
#if DEBUG == 1 || DEBUG == 3
putchar(ch);
#endif
ch = *argv[3] Л ch;
#if DEBUG >= 2
putchar(ch);
#endif
if(!feof(in)) fputc(ch, out);
}
fclose(in);
fclose(out);
return 0;
}
4. Следващият фрагмент илюстрира #elif. Той отпечатва на
екрана NUM is 2.
#define NUM 2
# if NUM == 1
printf("NUM is 1");
# elif NUM == 2
printf("NUM is 2”);
# elif NUM == 3
printf("NUM is 3") ;
# eJif NUM == 4
printf("NUM is 4") ;
ttendif
5. В този случай операторът defined се използва, за да се оп-
редели дали е дефиниран TESTPROJECT.
ttinclude <stdio.h>
^define TESTPROJECT 29
#if defined TESTPROJECT
int main(void)
{
printf("This is a test.Xn");
return 0;
}
#endif
Предпроцесорът на С и някои професионални теми 377
1. Напишете програма, дефинираща три макроса, наречени
INT, FLOAT и PWRTYPE. Дефинирайте INT като О,
FLOAT като 1, а РWR_TYPE или като INT, или като
FLOAT. Нека програмата да изисква от потребителя да
въведе две числа и след това да показва едното от тях пов-
дигнато на стелен другого. Посредством #if и в зависи-
мое? от стойността на PWR.TYPE, нека двете числа да са
цели или ггьрвото да е double
2. Верен ли е гози фрагмент? Ако не е, покажете някой от
начините за поправянето му.
#define MIKE
#ifdef 1MIKE
ttendif
12.3
#ERROR, #UNDEF, #LINE И #PRAGMA
Предпроцесорът на С подцържа четири директиви за специални
случаи: #error. #undef, #line и #pragma Сега ше изеледваме
всяка от тях поотделно.
Директивата #еггог е със следната обща форма:
terror съобщение-за-грешка
Тя предизвиква спирането на компилатора и издаването на съ-
общение-за-грешка заедно с друга имплементационно-зависима
информация, която обикновено включва номера на реда на ди-
рективата #еггог и името на файла. Обърнете внимание, че съ-
общение-за-грешка не е оградено с кавички. По принцип прило-
жението на директивата #еггог е за дебъгване.
Директивата #undef отдеф инира име на макрос. Нейната об-
ща форма е:
#undef име-на-макрос
Ако име-на-макрос не е лефинирано, #undef няма ефект. Обик-
новено #undef се използва за локаишзиране на имената на макроси,
378 С - Пра ктически самоучител
Когато компилаторът на С компилира файл със соре код, той
поддържа два вида информация: номера на реда и името на фай-
ла, конто се компилират в момента. Директивата #line се използ-
ва, за да се променят тези стойности. Нейната обща форма е:
#line номер-на-ред “име-на-файл”
В този случай, номер-на-ред става номера на следващия ред от
сере кода, а име-на-файл става името, което компилаторът ще
асоциира с името на файла със соре кода. Стойността на номер-
на-ред е между 1 и 32 767. Име-на-файл може да е низ, състоящ
се от всяко валидно име на файл. Обикновено #Нпе се използва
за дебьгване и за управление на големи проекта.
Директивата ^pragma дава възможност на имплементатора
на компилатора да дефинира други предпроцесорни инструкции,
който да се дадат на компилатора. Тя е със следната обща форма:
tfpragma инструкции
Ако компилаторът срещне непозната конструкция #pragma, той
я игнорира, Дали вашият компилатор поддържа някакви ^pragma
конструкции, завися от имплементацията на компилатора.
ПРИМЕРИ
1. Тази програма демонстрира директивата terror:
♦include <stdio.h>
int main(void)
{
int i;
i = 10;
♦error This is an error message.
printf("%d", i); /* този ред няма да се
компилира */
return 0;
}
Щом се срещне директивата tferror, компилирането спира.
2. Слсдваща програма демонстрира директивата #undef.
Както указва програмата, се компилира само първата кон-
струкция printf().
♦include <stdio.h>
♦define DOG
Прэдпроцесорът на С И някой професионапнй теми 379
int main(vcid)
ttifdef DOG
printf("DOG is defined.\n");
ttendif
ttundef DOG
ttifdef DOG
printf("This line is not compiled.\n");
ttendif
return 0;
}
3. Следващата програма демонстрира директивата #line Тъй
като практически всички имплементации па terror отпе-
чатват номера на реда и името па файла, тя се използва, за
да се потвърди, че #line наистина е извършила правилно
своята функция. (В следващата секция ще видите как
програма на С може директпо да досгигне до номера на
реда и името на файла.)
ttinclude <stdio.h>
inc main(void)
{
int i;
/* настройка на номера на реда на 1000 и името на
файла на myprog.c
*/
ttline 1000 "myprog.c"
flerror Check the line number and file name.
return 0;
1
4. Въпреки че стандартът ANSI С не задава никакви
#pragma директиви, проверете самостоягелно ръководст-
вото за потребителя на вашия компилатор и научете за
поддьржапите от вашата система директиви.
1. Изпробвайте примерните програми. Вижте как работяг те-
зи директиви па вашата система.
380 С - Практически самоучител
12.4
ВГРАДЕНИТЕ В С МАКРОСИ
Ако вашият С компилаюр спазва стандарта ANSI С, той ще при-
тежава поне пег предварително дефинирани имена на макроси,
конто могат да се използват от вашите програми. Те са:
__LINE___
-FILE-
DATE__
__TIME
_ _STDC_ _
Тук e обленена подробно всяка от тях.
Макросът____L1NE__ дефинира целочислена стойност, конто
е еквивалентна на номера на реда, компилиран в момента.
Макросът____FILE____дефинира низ, който представлява име-
то на текущо-компилирания файл.
Макросът____DATE____дефинира низ, съдържащ текущата да-
та на системата. Неговата обща форма е:
месеи/ден/го дина
Макросът__TIME___дефинира низ, съдържащ времето на
началото на компилиранею на програмата. Неговата обща фор-
ма е:
часове:минути:секунди
Макросът -STDC_____с дефиниран като стойност 1, ако ком-
пилаторът спазва стандарта ANSI.
ПРИМЕРИ
1. Тази програма демонстрира макросите_LINE
_ _FILE_ _ JDATE- _ и _ TIME______.
#include <stdio.h>
int main(void)
{
printf ("Compiling %s, line: %d, on Is, at 3.s",
_ _FILE_____________,____LINE ________DATE
_TIME_ _);
return 0;
Предпроцесорът на G и някои професионални теми 381
Важно е да разберете, че стойноитите на макросите се за-
дават по време на компилирането. Например ако горната
програма се нарича Т.С и е компилирана на 18.03.1997 в
10:00, тя ще отпечата този резултат независимо от датата и
часа на изпъднението й:
Compiling Т.С line: 6, on Маг 18 1997, at 10 0-3:00
Основного приложение на тези макроси е за създаване на
маркер с часа и датата, показваща кота е компилирана
програмата.
2. Както разбрахге от предишната секция, можете да използ-
вате директивата #line, за да промелите номера на теку-
щий ред от кода и името на файла. Ако направите това,
вие всыцност променяте стойностите на_____LINE____и
___FILE___. Например следващата програма настройва
___________LINE _ на 100 и_FILE _ на myprog.c;
#inclxide <stdro.h>
int main(void)
I
frline 100 "myprog.c"
printf("Compiling %s, line: %d, on %s, at %s",
_FILE_______________,____LINE _ _ _DATE
’ -TIME___) ;
return 0;
}
Програмата отпечатва следного:
Compiling myprog.c line: 101, on Mar 18 1997, at 10:00:00
при положение че e компилирана на 18.03.1997 в 10:00.
1. Компилирайте и изпълнете примеряйте програми.
382 С - Практически самоучител
12.5
ИЗПОЛЗВАНЕ НА ОПЕРАТОРИТЕ # И ##
Предпроцесорът на С съдържа два малко използвани, но с голям
потенциал, оператора: # и ##. Операторът # превръща аргумента
на даден макрос, подобен на функция, в низ с кавички. Операто-
рът ## конкатенира два идентификатора.
ПРИМЕРИ
I. Тази програма Демонстрира оператора #:
«include <stdio.h>
«define MKSTRING(str) # str
int main(void)
int value;
value = 10;
printf ("%s is %d", MKSTRING(value), value);
return 0;
Програмата отпечатва value is 10. Този резултат се полу-
чава, защото MKSTRING( ) превръща идентификатора
value в низ, ограден с кавички.
2. Следващата програма демонстрира оператора ##. Тя съз-
дава макроса output(), който извиква функцията printf().
Изписват се стойностите на две променливи, завършващи
па 1 и 2.
«include <stdio.h>
«define output(i) printf("%d %d\n", i «« 1, i ## 2)
int main(void)
int countl, count2;
int il, i2;
countl = 10;
count2 = 20;
il = 99;
i2 = -10;
output(count);
output (i);
return 0;
Предпроцесорът на С и някои професионаяни теми 383
Програмата отпечатва 10 20 99 -10. В извикванията на
outpuf(), count и i се конкатенират с 1 и 2, за да се полу-
чат имената count 1, countZ, il, i2 в конструкцията
printf().
1. Компилирайтс и изпълнете примеримте програми.
2. Какво отпечатва тази програма?
#include <stdic.h>
ftdefine JOIN(a, b) a ## b
int main(void)
printf(JOIN("one ", "two"));
return 0;
}
3. Експериментирайте саместоягелио с операторите # и ##.
Опитайте се да измислите как те биха могли да бъдат по-
лезни във вашите проекта.
12.6
Указатели към функции
Тази секция представя една от най-важните лрофесионални теми
в С: указатслят към функция. Въпреки че обсъждането на всички
нюанси и импликации на указателите към функции са извън об-
сега на настоящата книга, тук се разглеждат основните момента.
Указатечят към функция е променлива, съдържаща адреса на
началото на дадена функция. Когато компилаторът компилира
програмата, той създава входна точка за всяка ф} нкция от прог-
рамата. Входната точка е адресът, на който се пренася изпълне-
нието при извикване на функцията. Тъй като входната точка
притежава адрес, е възможно да съществува указател, сочещ към
този адрес. След като имате указател към дадена функция, на-
пълно възможно е да извикате тази функция посредством указа-
теля. Скорс ще видите защо може да ви се наложи това.
384 С - Практически самоучител
За да създадете променлива, която да може да сочи функция,
декларирайте указателя от типа на резултата на функцията,
следван от някакви параметри. Например следващата конструк-
ция декларира р като указател кьм функция, връщаща цяло чис-
ло и притежаваща два целочислени параметъра х и у.
int (*р) (int х, int у) ;
Скобите около *р са необходими заради правилата за приорите-
тите в С.
За да присвоите адреса на определена функция на указател
към функция, просто използвайте името му без никакви скоби.
Например ако предположим, че sum() е със следния прототип;
int sum(int a, int b) ;
тази конструкция за присвояване:
р = sum,
е вярна След това можете да извиквате sum() икдиректно чрез р
посредством подобна конструкция:
result = (*р) (10, 20);
01 ново поради правилата за приоритетите на С, скобите около
*р са необходими. Всъщност бихте могли директио да използва-
те р така:
result = р(10, 20);
Все пак формата (*р) нодсказва на всеки, който чете кода, че е
излолзван указател към функция за индиректно извикване на
функция, а не функция, наречена р.
ПРИМЕРИ
1. Като първи пример, тази програма запълва подробностите
и демонстрира току-що описания указател към функция.
^include <stdlo.h>
int sum(int a, int b) ;
int main(void)
{
int (’p) (int x, int y);
int result;
Предпроцесорът на С и никои професионални теми 385
р = sum; /* получаване на адреса на sum() */
result = (*р) (10, 20);
printf("%d", result);
return 0;
}
int sum(int a, int b)
{
return a+b;
)
Програмата изисква две числа то потребителя, извиква
индиректно sum( ) чрез р и изписва резултата.
2. Въпреки че програмата от пример 1 илюстрира механика-
та па употребата на указатели към функции, тя дори и не
загатва за техните възможности. Следващата програма
обаче ще повдигне леке завесата.
Едно от най-важнизе приложения на указатели към
функции се среща при създаването на масиви от указатели
към функции. Всеки елемент от масива може да сочи към
различна функция. За да извикатс някоя определена функ-
ция, просто трябва да индексирате масива. Такъв масив
дава възможност за написването на много ефективен код,
когато трябва да се извикват различии функции според об-
стоятелствата. Масивите от указатели към функции обик-
новено се изнолзъат при създаване на системен софтуер,
като компилатори, асемблери и интерпретатори. Въпреки
това обаче те не са ограничени само в тези приложения.
Въпреки че е трудно да намерим кратки и смислени прог-
рами, използващи масиви от указатели към функции,
следващата програма дава някаква прсдстава за тяхната
стойност. Подобно на програмата от пример 1, тази прог-
рама изисква от потребителя да въведе две числа, а след
това иска номера на операцията, която да извърши. След
това този номер се използва, за да се индексира масива от
указатели към функции и да се изпълни подходящатд фун-
кция. Накрая ее отпечатва резултата.
ttinclude <stdio.h>
int sum(int a, int b);
int subtract(int a, int mul(int a, int int b); b) ; b) ;
in t div(int a, int
int (*p[4]) (int x, int y);
386 С - Практически самоучител
int main(void)
{
int result;
int i, j, op;
p[0] = sum; /* получаваие на адреса на sum() */
р[1] = subtract; /* получаваие на адреса на
subtract() */
р[2] = mul; /* получаваие на адреса на mul() */
p[3j = div; /* пслучаване на адреса на div() */
printf("Enter two numbers: ");
scanf("%d%d", Si, &j);
printf("0: Add, 1: Subtract, 2: Multiply,
3: Di vide \n’’);
do {
printf("Enter number of operation: ");
scanf("%d", Sop);
) while(cp<0 || cp>3);
result = (*p[op]) (i, j);
printf("%d", result);
return 0;
int sumfint a, int b)
(
return a+b;
}
int subtract(int a, int b)
{
return a-b;
}
int mui (int a, int b)
{
return a*b;
}
int div(int a, int b)
{
if(b) return a/b;
else return 0;
)
Когато разучите този код, става ясно, че употребата на ма
сив от указатели към функции за извикване на правилната
функция е по-ефективно от използването на конструкция
switch()
Преди да свършим с този пример, можем да го използ-
ваме, за да илюстрираме още нещо: масивите от указатели
Предпроцесорът на С и някои професионални теми 387
към функции moi ат да се инициализират както всеки друг
масив. Следващата версия на програмата демонстрира това.
#include <stdio.h>
int sum(int a, int b) ;
int subtract(int a, int b);
int mul(int a, int b) ;
int div(int a, int b);
/* инициализиране на масива то указатели */
int (*р[4]) (int х, int у) = {
sum, subtract, mul, div
int main(void)
int result;
int i, j, op;
printf("Enter two numbers: ");
scanf("%did", &i, &j);
printf("O: Add, 1: Subtract, 2: Multiply,
3: DivideXn");
do (
printf (’’Enter number of operation: ") ;
scanf("%d", &op) ;
} while(op<0 || op>3);
result = (*plop]) (i, j);
printf(”%d", result);
return 0;
}
int sum(int a, int b)
{
return a+b;
}
int subtract(int a, int b)
{
return a-b;
int mul(int a, int b)
{
return a*b;
)
int divfint a, int b)
{
if(b) return a/b;
else return 0;
388 С - Практически самоучител
3. Едно от най-честите приложения на указател към функция
е при използването на една друга стандартна библиотечка
С функция: qsort(). Тази функция е вградена процедура за
сортнране, която може да сортира всякакъв вид оразмгрен
масив, посредством алгоритъма Quicksort. Нейният прото-
тип е:
void qsort(void масив, size_t брой, size_t размер,
i nt (*соп?р) (const void *а, const void 4й));
Тук масив е указател към първия елемент от масива за
сортиране. Броят на елемен гите на масива се задава от
брой, а размера на всеки елемент - от размер. (Запомнете,
че size t е дефиниран от компилатора на С и е същото като
unsigned.) Последният параметьр е указател към функция
(създадена от вас), сравнивай а два елемента от масива и
врынаща следни ге резу лтати:
*а < *Ь
*а = = *Ь
*а > *Ь
връща отрицателна стойност
връща нула
връща положителна стойност
Функцията qsort() не връща стойност. Тя използва хедър-
ния файл STDLIB.H.
Следващата програма зарежда един 100-елемснтен ма-
сив със случайна числа, сортира го и го отпечатва в сор-
тиран вид. Обърнете внимание на преобразуването на ти-
пове във функцията сошр().
ttinclude <stdio.h>
ttinclude <stdlib.h>
int comp-const void *ir const void *j);
int main(void)
{
int sort[100], i;
for(i=0; i<100; i++)
sort[i] = rand();
qsort(sort, 100, sizeof(int), comp);
for(i=0; i<100; i++)
printf("%d\n", sort[i]);
return 0;
Предпроцесорът на С и някои професионални томи 389
int comp(const void *i, const void * j)
{
return *(int*)i - *{int*)j;
1. Компилирайте и изпълнете всички примерли програми.
Експериментирайте с тях, като правите малки промени.
2. Друга стандартна библиотечка функция се нарича
bsearch(). Тази функция търси в сортиран масив по зада-
ден ключ. Тя връща указател към първия елемент, конто
съвнада с ключа. Ако не намери съвпадение, връща нулей
указател. Нейният прототип е:
void *bsearch(const void *ключ, const void * масив, sizej брой,
size t размер, int(*conjp)(const void *a, const void *6));
Всички параметри на bsearch() са същите, както на
qsort( ), с изключение на първия, конто е указател към
ключ - търсеният обект. Функцията сотр() оперира с
bsearch(), по същия начин, както с qsort().
Промелете програмата от пример 3 така, че след сорти-
рането на масива да се изисква от потребителя да въведе
число След това посредством bsearchf) претьрсете сор-
тирания масив и докладвайге, ако няма съвпадение.
3. Добавете функция, наречена modulus(), към крайната
версия на аригмстичната програма от пример 2. Нека фун-
кцията да връща резултата а%Ь. Добавете тази опция към
менюто и я интегрирайте напълно в програмата.
12-7
ОВЛАДЯВАНЕ НА ДИНАМИЧНОГО
ЗАДЕЛЯНЕ
Тази последна секция па книгата представя системата за дина-
мично заделяне на С. Динамичното заделяне е процесът, чрез
който се осътцествява заделяне на памет по време на изпълнени-
ето, според нуждите. Тази заделена памет може да се използва за
множество нужди. Най-често памет се заделя от приложения,
390 С - Практически самоучител
пуждаещи се от цялата памет на компютъра. Например една
програма за текстообработка ще иска да дадс възможност на
потребителя да редактира възможно най-голям документ. Ако
обаче програмата използва обикновен знаков масив, тя трябва да
фиксира нсгоъата големина по време на компилирането. Поради
това тя ще трябва да се компилира така, че да се изпълнява на
компютри с минимално количество памет, а няма да дава въз-
можност на потребителите с повече памет да редактират по-го-
леми документа. Ако паметта се заделя динамично (когато е не-
обходима, докато има свободна) обаче, всеки по т ребител може
напълно да се възползва от паметта на системата си. Други при-
ложения на динамичного заделяне включват свързани списъпи и
двоични дървета.
Сърцето на функциите за динамично заделяне в С са пл alloc(),
кс-ятс заделя памет, и free(), освобождаваща запеленала памет.
Техните прототипи са:
void *malloc(size_t Ьрой-байтовеу,
vcid free-void Дказател);
Тук брой-байтове е броят на байтовете памет, конто искате да
заделите. Функцията malloc() връща указател към началото на
заделената памет Ако malloc() не може да изпълни заявката за
памет - например възможно е да няма достатъчно свободна па-
мет - тя връща null За да освободите памет, извикайте free() с
указател към началото на блока от паметта (предварително заде-
лен с malloc()), който искате да освободите. И двете функции
използват хедърния файл STDLIB.H.
Паметта се заделя от регион, наречен хийп. Въпреки че ис-
тинското физическо расположение може да е различно, по прин-
цип хийлът лежи между ваша га програма и стека. Тъй като това
е крайна облает, някоя заявка за заделяне може да иропадне по-
ради изчерпване на паметта.
Когато програмата се терминира, цялата заделена памет се
освобождава.
ПРИМЕРИ
1. Трябва да сте сигурни, че извикването на malloc() е ус-
пешно, преди да използвате връщания указател. Ако из-
вьршите операция с null оператор, може да блокирате про -
рамата и дори целия компютър. Най-лесният начин да
прорите дали указателят е валиден е даден в този фрагмент:
Предпроцесорът на С и някои професионални теми 391
р = malloc(SIZE) ;
if(lp) {
printf("Allocation Error");
ex i t (1) ;
}
2. Следващата програма задела 80 байта памет и асоциира
един char указател към тях. Така се създава динамичен
знаков масив. След това програмата използва заделената
памет, за да въведе низ посредством gets(). Накрая низът
се изписва повторно и указателя!’ се освобождава. (Както
беше споменато по-рано, цялата памет се освобождава при
завършвакето на програмата, така че извикването на
free() е включено просто за се демонстрация на нейната
употреба.)
ttinclude <stdio.h>
ttinclude <stdlib.h>
int main(void)
{
char *p;
p = malloc(80);
if('p) {
printf("Allocation Failed");
exit. (1) ;
}
printf("Enter a string: ");
gers(p);
printf(p);
free(p);
return 0;
}
3. Следващата програма ви казва приблизителното количест-
во свободната памет.
ttinclude <stdio.h>
ttinclude <stdlib.h>
int main(void)
{
char *p;
long 1;
1 = 0;
392 С - Практически самоучител
do {
р = malior(1000);
if (р) 1 += 1000;
} while(р);
printf("Approximately %ld bytes of free
memory,", 1);
return G;
Програмата работа като заделя 1000-байтови “парчета”
памет, докато заявката за заделяне пропадне. Когато mal-
1ос() върне null - хийпът е изчерпан. От това следва, че
стойността на I представлява (в 1000 байта) количсството
свободна памет за програмата.
4. Друго добро приложение на динамичното заделяне е за
създаването па буфери при входно/изходни операции с
fread() и/или fwrite(). Често се налага да буферирате са-
мо за кратък период от време, така че е добре да заделяте
паметга при необходимост и да я освобождавате, когато
приключите. Следващата програма демонстрира как ди-
намичною заделяне може да се използва за създаването па
буфер. Програмата заделя достатъчно място, за да съхра-
нява десет дробни стойности. След това присвоява десет
случайни числа на заделената памет, индсксирайки указа-
теля като масив. Следва записване на стойностите на дис-
ка и осзобождаване на паметга. Накрая паметта отново се
заделя, прочита се файлът и се изписват десеттс случайни
числа. Въпреки че няма нужда ст освобождаване и след
това ст ново заделяне на памет, служеща за буфер, така се
демонстрира основната идея.
♦include <stdio.h>
♦include <stdlib.h>
int main(void)
{
int i ;
double *p;
FILE *fp;
/* взимане на памет */
p = malloc(10 * sizeof (double));
if(!p) {
printf("Allocation Error");
exit (1) ;
}
/* генериране на 10 случайни числа */
Предпроцесорът на С и никои професионални теми 393
for(i=0, i<10; i++)
p[i] = (double) rand(J;
if((fp = fopen("myfile", "wb"))==NULL) {
printf("Cannot open fileAn");
exit(1) ;
/* записване на целия масив с една стънка */’
if (fwrite (р, 10*sizeof'(double) , 1, fp) 1= 1) {
printf ("Write Error An");
exit(1);
1
fclose(fp);
free(p); /* паметта не e нужна повече */
/*
предстанете си, че тук се случва нещо
*/
/* отново взимане на памет */
р = malloc(10 * sizeof(double));
if(!p) (
printf("Allocation Error");
exit(1);
)
if((fp = fopen("myfile", "rb"))==NULL) {
printf ("Cannot open file An") ;
exit(1);
)
/* прочитане на целия масив в една стъпка */
if(fread(p, 10*size.of (doubJе) , 1, fp) 1= 1) (
printf ("Read Error An") ;
exit(1);
}
fclose(fp);
/* изписване на масива */
for(i=0; i<10; i++) printf("%f ", p[i)) ;
free(p);
return 0;
5. Както могат да бъдат преминати границите на масива, така
могат да бъдат преминаги и границите на заделсната па-
мет Например този фрагмент е синтактично верен, но е
грешен:
394 С - Практически самоучител
р = malloc(10) ;
fcr(i=0; i<100; i) p[i] = i;
1. Компилирайтс и изпълнете примеряйте програми.
2. Напишете програма, създаваща десетелементен динами-
чей масив от целочислени стойности. След това посредст-
вом указателна аритметика или индексиране на масив
присвсете стойностите от 1 до 10 на целите числа, състав-
ляващи масива. Накрая, изпишете стойностите и освобо-
дете паметта.
3. Какво не е наред с този фрагмент?
char *р;
*р = malloc(10) ;
gets(р);
Проверка на уменията, представени в главата
На този етап би трябзало да сте в състояние да отговорите па
тези въпроси и да изтлните упражненията:
1. Каква е разликата между използването на кавички и ъгло-
ви скоби в директивата ftinclude?
2. Посредством ftifdef покажете как се компилира условно
следният фрагмент, в зависимост от това, дали DEBUG е
дефинирано или не.
if(l(j%2)) {
printf("j = %d\n", j);
j = 0;
}
Предпроцесорът на С и някои професионапни томи 395
3. Като използвате фрагмента от упражнение 2, покажете как
да се компилира условно кода, когато DEBUG е дефини-
рая като 1. (Съвет: използвайте #if.)
4. Как се “стдефинира” макрос?
5. Какво е___FILE_ _ и какво представлява той?
6. Каква е ролята на предпроцесорните оператори # и ##?
7. Напишете програма, сортираща низа “this is a test of qsort”.
Изпишете сортирания резултат.
8. Напишете програма, която динамично заделя памет за
double стойност. Нека програмата да присвоява на това
местоположение стойността 99.01, да изписва стойността
и след това да освобождава яамегта.
Проверка на натрупаните умения
Тази секция проверява колко добре сте интегрирали материа-
ла от тази глава с този от предпишите.
1. Секция 10.1, пример 3 представя програма за компютърна
картотека, използваща масив от структури за съхранение
на информация за книги. Променете тази програма така,
че на практика да създава масив от указатели към структу-
ри и да използва динамично заделена памет за същипското
запазване на информациям за всяка книга. По този начин
се използва по-малко памет при съхранение на информа-
ция само за няколко книги.
2. Покажете еквивалснтния макрос на тази функция:
char code it(char с)
{ .
return -с;
1
Демонстрирайте в програма, че вашата версия на макроса
работа.
3. Прегл едайте самостоятелно програмите, конто написахте
в книгата. Опитайте се да намерите места, на конто може-
те да използвате:
> Условно компилиране.
> Заместване на кратка функция с макрос, подобен на
функция.
396 С - Практически самоучител
> Заместване на статично заделени масиви с динамичны
такива.
> Указатели към функции.
4. Проучете самостоятелно ръководството на потребителя
или онлайн документацията на вашия компилатор, като
отделите специално внимание на описан ието на неговите
стандартен библиотечни функции. Стандартната библио-
тека на С съдържа неколкостотин функции, конто могат
значително да улеснят програмирането. Освен това при-
ложение А на тази книга обсъжда някои от най-често сре-
щаните библиотечни функции.
5. След като вече завършихте тази книга, се върнете и проме-
тете бегло всяка глава и се замислете как всеки аспект от
езика С е евързан с останалите. Както ще видите, С е ви-
сскоинтегриран език, в който всяка характеристика взаи-
модейства с други. Връзката между указатели и масиви
например е чиста елегантност.
6 Се език, който се учи най-добре с практика! Продължете
да пишете програми на С и изучавайте програмите на дру-
ги програмисти. Ще се изненадатс колко бързо С ще ви
стане втора природа!
7. Накрая, сега притежавате необходимата основа на С, за да
можете да преминете към C++ - обсктно-ориенчираното
разширение на С. Ако вашего бъдеще е евързано със C++,
продължете с книгата C++ Практически самоучител
(издателство СофтПрес, 2001 г.). Тя продължава от там,
откъдето тази книга спира.
A
Често използвани
БИБЛИОТЕЧНИ ФУНКЦИИ
НАС
398 С - Практически самоучител
Настоящото приложение разглежда редицэ от най-често
използваните библиотечни функции на ANSI С. Ако сте
разгледали библиотечната част от дскументацията на
вашия C/C++ компилатор, без съмнение знаете, че съ-
ществуват множество библиотечни функции. Разглеждането на
всяка от тях с далеч отвъд обсега на тази книга. Тук обаче ще
обсъдим онези, конто ще използвате най-често.
Библиотечните функции могат да бъдат групирани в следните
категории:
> Входно/изходни функции
> Низови и знакови функции
> Математически функции
> Функции за час и дата
> Функции за динамично заделяне
> Други функции
Входно/изходните функции бяха подробно разгл едани в гла-
вк 8 и 9 и на тях няма да се спираме отново тук.
Всяко описание на функция започва с хедърния файл, изиск-
ван от функцията, следван от нейния прототип. Прототипът ви
предосгавя лесеи начин да разберете броя и типовете на аргу-
ментите, както и типа на резултата на функцията.
Не забравяйте, че ANSI С определя много типове данни, де-
финирани в хедърните файлове, използвани от функциите. По-
вите имена на типове ще се обсъждат при тяхпата поява
Низови И ЗНАКОВИ ФУНКЦИИ
Стандартната библиотека на С притежава богато разнообразие
от низови и знакови функции. В С, низът е масив от знакове,
терминираи от нула Декларациите на низовите функции се на-
мират в хедърния файл STRING.Н. Знаковитс функции използ-
ват хедърния файл CTYPE.H.
Тъй като С не извършва проверка на границите при операции
с низове, отговорност на програмиста е да не се получи “прели-
ване” на масива.
Знаковите функции са декларирани с целочислен параметьр.
Поради това се използва само младшият байт. По принцип, мо-
жете спокойно да използвате char аргумент, тъй като той авто-
матично ще бъде повишен в int при извикването на функцията.
Место използвани библиотечни функции на С 399
#include <ctype.h>
int isalnum(int ch);
Описание: Функцията isalnum( ) връща ненулев резултат, ако
нейният аргумент е буква или цифра. Ако знакът не е буква или
цифра, връща нула.
Пример: Следващата npoipaMa проверява всеки знак от stdin и
докладва всички букви и цифри:
ftinclude <ctype.h>
ftinclude <stdio.h>
int main(vcid)
{
char ch;
for(;;) {
ch = getchar ();
if(ch==' ') break;
if(isalnum(ch)) printf("%c is alphanumeric\n", ch);
return 0;
}
#include <ctype.h>
int isalpha(int ch);
Описание: Функцията isaipiia() връща ненулев резултат, ако ch
е буква; в противен случай връща 0.
Пример: Следващата програма проверява всеки знак от stdin
докладва всички, конто са букви:
ftinclude <ctype.h>
ftinclude <stdio.h>
int main(void)
I
char ch;
for(;;) {
ch = getchar();
if(ch==' ') break;
if(isalpha(ch)) princf("%c is a letter\n", ch);
return 0;
400 С - Практически самоучител
#include <ctype.h>
int iscntrl(int ch);
Описание: Функцията iscntrl() връща ненулев резултат, ако ch е
между 0 и 0x1 F или е равно на 0x7F (DEL); в противен случай
връща 0.
Пример: Следващата програма проверява всеки знак от stdin и
докладва всички контролни знакове:
#include <ctype.h>
#include <stdio.h>
int main(void)
{
char ch;
for(;;) {
ch = getchar();
if(ch==' ’) break;
if(iscntrl(ch) )
printf("%c is a control character\n", ch);
return 0;
#include <ctype.h>
int isdigit(int ch);
Описание: Функцията isdigit() връща ненулев резултат, ако ch е
число (от 0 до 9); в противен случай връща 0.
Пример: Следващата програма проверява всеки знак от stdin и
докладва всички, конто са цифри:
ftinclude <ctype.h>
#include <stdio.h>
int main(void)
{
char ch;
for(;;) {
ch = getchar();
if(ch==* ’) break;
if(isdigit(ch)) printf("%c is a digit\n", ch);
)
Место използвани библиотечни функции на С 401
#include <ctype.h>
int isgraph(int ch);
Описание: Функцията isgraph() връща ненулев резултат, ако ch
е всеки печатям знак, различен от интервал; в противен случай
връща 0. Печатимите знакове са в интервала от 0x21 до 0х7Е.
Пример: Следващата програма проверява всеки знак or stdin и
докладва всички печатями знакове:
ftinclude <ссуре-Ь>
ftinclude <stdio.h>
int main(void)
(
char ch;
for(;;) I
ch = getchar();
if(ch==' ') break;
if (isgraph (ch) )
printf("%c is a printing characterin'', ch);
}
return 0;
)
^include <ctype.h>
int islower(int ch);
Описание: Функцията islower( ) връща ненулев резултат, ако ch
е малка буква (от а до z); в противен случай връща 0.
Пример: Следващата програма нроверява всеки знак от stdin и
докладва всички малки букви:
ftinclude <ctype.h>
ftinclude <stdio.h>
int main(void)
char ch;
402 С - Практически самоучител
for(;;) {
ch = getchar ();
if(ch==' ') break;
if(islower(ch)) printf(”%c is lowercaseXn”, ch);
return 0;
#includo <ctype.h>
int isprintfint ch);
Описание: Функцията isprint() връща ненулев резултат, ако ch е
печалим знак, включително интервал; в противен случай врыца
О Печатимите знакове обикновено са в интервала от 0x20 до
0х7Е.
Пример: Следващата програма проверява всеки знак от stdin и
докладва всички печатими знакове:
#include <ctype.h>
^include <stclio.h>
int main(void)
{
char ch;
for(;;) {
ch = gecchar();
if(ch==’Q') break;
if(isprint(ch)) printf("%c is printableXn", ch);
return 0;
#include <ctype.h>
int ispunct(int ch);
Описание: Функцията ispunct( ) връща ненулев резултат, ако ch
е пунктуационен знак, с пзключение на интервал; в противен
случай връща 0. Термины “пунктуационен знак’*, дефиниран от
функцията, включва всички печатими знакове, конто не са нито
букви, нито цифри, нито интервали.
Пример: Следващата програма проверява всеки знак от stdin и
докладва всички, конто пунктуационни:
Место използвани библиотечки функции на С 403
#include <ctype.h>
#inciude <stdio.h>
int main(void)
{
char ch;
for(;;) {
ch = getchar ();
if (ch==' ') break;
if(ispunct(ch)) printf("%c is punctuation\n", ch);
return 0;
}
#include <ctype.h>
int isspace(int ch);
Описание: Функцията isspacef) връща ненулев резултат. ако ch е
интервал, табуляция, вертикална табуляция, преминавапе на
следваща страница, връщане на каретката или знак за нов ред; в
противен случай връща 0.
Пример: Следващата програма проверява всеки знак от stdin и
докладва всички празни знакове:
ftinclude <ctype.h>
#include <stdio.h>
int main(void)
char ch;
for(;;) (
ch = getchar ();
if(isspace(ch)) printf("%c is whitespace\n", ch);
if(ch==* ’) break;
)
return 0;
)
#include <ctype.h>
int isupperfint ch);
Описание: Функцията isupper() връща ненулев резултат, ако ch
е главна буква (от А до Z); в противен случай връща 0.
404 С - Практически самоучител
Пример: Следващата програма проверява всеки знак от stdin и
докладва всички главки букви:
#include <ctype.h>
#incluae <stdio.h>
.int main (void/
{
char ch;
for(;; ) {
ch = getcha.rO;
if(che«' ') break;
if (isupper(ch)) princf("%c is uppercase\n", ch);
return 0;
}
#include <ctype.h>
int isxdigit(int ch);
Описание: Функцията isxdigit() връща ненулев резултат, ако ch
е шестнадесетична цифра; в противен случай връща 0. Всяка
шестнадесетична цифра ще е в един от еле цните интервали: А до
F, а до f или 0 до 9.
Пример: Следващата програма проверява всеки знак ст stdin и
докладва всички, конто са шестнадесетичии цифри:
#include <ctype.h>
^include <stdio.h>
int mein(void)
{
char ch;
for(;;) {
ch = getchar();
if(ch=s' ') break;
if(isxdigit(ch)) printf("%c is hexadecimal \n", ch);
return 0;
Место използвани библиотечки функции на С 405
^include <string.h>
char *strcat(char *str1, const char *str2);
Описание: Функцията strcat() конкатенира копие на str2 към
strl и терминира strl с нула Нулевия терминатор, който по
принцип терминира strl, се црецокрива от първия знак от str2.
Низът str2 не се променя при тази операция. Функцията strcat()
връща strl.
Не се извършва проверка на границите, така че е отговор-
ност на програмиста да се увари, че strl е достатъчно голям,
за да сьдържа първоначалната си стойност плюс тази на str2
Пример: Следващата програма добавя първия низ, црочетен от
stdin, към втория. Например ако потребителят въведе hello и
there, програмата ще отпечата therebello:
#include <string.h>
ttinclude <stdio h>
inc main(void)
char si [80], s2[8OJ;
printf("Enter two strings: ");
get s (si);
gets (s2);
strcat(s2, si);
printf(s2);
return 0;
#include <string.h>
char *strchr(const char *str, int ch);
Описание: Функцията strchr() връща указател към първото
срещане на младшия байт на ch в низа, сочен от str. Ако не на-
мери такъв знак, връща нулев указател.
Пример: Следващата програма отпечатва is a test:
И include <strir.g.h>
ttinclude <stdio.h>
int main(void)
406 С - Практически самоучител
(
char *р;
р = s'trchr ("this is a test", (int) ' ');
printf (p)
return 0;
}
#include <string.h>
char *strcmp(const char *str1, const char *str2);
Описание: Функцията strcmp() сравнява лексикографски два ну-
лево терминирани низа и врыца пяло число, както е показано тук:
Резултат
по-малко от О
О
по-голямо ст С
Знамен.13
strl е по-малък от str2
strl е равен на str2
strl е по-гслям ст str2
Пример: Следващата функция може да се използва за проверка на
парола. При несъответствие ше врътпа 0, а в противен случай - 1:
#include <string.h>
int password(void)
{
char s Г 8 0] ;
printf("Enter password: ");
gets(s);
if(strcmp(s,"pass")) {
printf("Invalid Password\n"
return 0;
)
return 1;
#include <string.h>
char *strcpy(char *str1, const char *str2);
Описание: Функцията strcpy() се използва за копи ране на съ-
държанието на sir2 в str Г, str2 трябва да е указател към нулево-
терминиран низ. Функцията връща указател към strl.
Место използвани библиотечни функции на С 407
Ако strl и str2 се застъЕват, поведениетэ на strcpyi ) е неиз-
вестно
Пример: Следващият фрагмент ще копира “hello” в низа str:
char str[80];
strcpy(str, "hello");
#include <string.h>
size, t strlen(const char str);
Описание: Функцията strlen() връща дължината на нулево-
терминиран низ, сочен от str Нулата не се брои. Типът size_t е
дефиниран в STRING.H.
Ако strl и str2 се застъпват, поведениетэ на strcpyt) е неиз-
вестно.
Пример: Следващият фрагмент ще отпечата на екрана 5.
strcpy(s, "hello");
printfstrlen(s));
#inciude <string.h>
char *strstr(const char *str1, const char *str2);
Описание: Функцията strstr() връща указател кьм пързото с ре-
шане на низа, сочен от str2, в низа, сочен от strl (с изключение
па нулевая терминатор на str2). Ако не намери съвпадение, връ-
ща нула.
Пример: Следвашата програма отпечатва съобщението is a test:
ttinclude <string.h>
#include <stdio.h>
int main(void)
{
char *p;
p = strstr(“this is a test", "is");
printf(p);
return 0;
}
408 С - Практически самоучител
#include <string.h>
char *strtok(char *str1, const char *str2);
Описание: Функцията strtok() връща указател към следвашия
контролен маркер в низа, сочен от strl. Знаковете в низа str2 са
разграничителите за всеки маркер. Ако няма повече такива зна-
кове, връща нула.
При първото извикване на функцията, всъщност се използва
strl В следващиге извиквания за първи аргумент се и ре дава ну-
ла. По този начин целият низ може да се “раздроби” на свэите
части.
Възможно е да използвате различен набор от разграничители
при всяко извикване на strtok().
Пример: Следващата програма “раздробява” низа “The summer
soldier, the sunshine pairiot” като за разделители използва интер-
вали и запетаи. Резултатът ще бъде The | summer | soldier | the |
sunshine ) patriot.
#include <string.h>
#include <stdio.h>
int main(void)
{
char *p;
p = strtok("The summer soldi.er, the sunshine
patriot", " , ") ;
printf(p);
do {
p = strtok('\0', ", ");
if(p) printf("|%s", p) ,
} whi1e(p);
return 0;
#include <ctype.h>
int tolower(int ch);
Описание: Функцията tolower() връща еквивалента на ch в дилен
регистър, ако ch е буква.; в противен случай ch не се променя.
Пример: Следващият фрагмент отпечатва q:
putchar(tolower('Q'));
Често използвани библиотечни функции на С 4U9
#include <ctype.h>
int toupper(int ch);
Описание: Функцията toupper() връща еквивалента на ch в го-
рец регистьр, ако ch е буква; в противен случай ch не се променя.
Пример: Следващият фрагмент отпечатва А:
put char(toupper ('а'));
А.2
Математически функции
ANSI С дефинвра няколко математически функции, приемащи и
връшащи стойности ст тип double. Тези функции са разделени в
следните категории:
> Тригонсметрични функции
> Хиперболични функции
> Експоненциални и логаритмични функции
> Други
Всички математически функции изискват включването на хе-
дърния файл МАТН.Н в програмата. Освен дефинирането на ма-
тематическите функции, този файл дефинира и макрос, наречен
HUGE_VAL. Ако дадена операция произведе резултат, който е
твърде голям, зада се побере в double, се появява препълване и
се връща HUGEVAL. Това се нарича грешка на интервала. За
всички математически функции важи правилого, че ако входната
стойност не е от допустимата облает, в която е дефинирана фун-
кция га, се докладва за грешка в областта.
Всички ъгли се задаваг в радиани.
include <math.h>
double acos(double arg);
Описание: Функцията acos() врыца аркус косинуса на arg. Ар-
гументы на функцията трябва да е в интервала от -1 до 1; в про-
тивен случай се докладва за грешка в областта.
Пример: Следващата програма отпечатва аркус косинусите на
стойностите от -1 до 1 при стьпка от една десета:
410 С - Практически самоучител
Jj include <math.b>
ftinclude <stdio.h>
int main(void)
double val = -1.0;
do {
printf ("arc cosine of %f is %f\n", val, aces (val.));
va1 += 0.1;
} while(val<~l.0);
return 0;
)
#include <math.h>
double asin(double arg);
Описание: Функцията asin() връща аркус синуса на arg. Лргу-
ментът на функцията трябва да е в интервала от -1 до 1; в проти-
вен случай се докладва за грешка в областта.
Пример: Следващата програма отпечатва аркус синусите на
стойностите от -1 до 1 при стъпка от една десета:
ftinclude <math.h>
ftinclude <stdio.h>
int main(void)
(
double val—1.0;
do {
printf("arc sine of %f is %f\n", val, asin(val));
val += 0.1;
) while(val<=l.0);
return 0;
}
#include <math.h>
double atan(double arg);
Описание: Функцията atan( ) връща аркус тангенса на arg.
Пример: Следващата програма отпечатва аркус тангенсше на
стойностите от -1 до 1 при стъпка от една десета:
Место използвани библиотечки функции на С 411
fl include <math-h>
^include <stdio.h>
int main(void)
{
double val=-1.0;
do {
printf("arc tangent of %f is %f\n", val,
atan(val));
val += 0.1;
} while(val<-1.0);
return 0;
#include <math.h>
double atan2(double y, double x);
Описание: Функцията atan2( ) връща аркус тангенса на у/х. Тя
използва знаковете на своите аргумента, за да определи квадран-
та на връшаната стойност.
Пример: Следващата програма отпечатва аркус тангелсите на у
от -1 до 1 при стьпка от одна десета:
flrnclude <math.h>
ttinclude <stdio.h>
int main(void)
{
double y=-l.Q;
do {
printf("atan2 of %f is y, atan2 (y, 1.0);
у += 0.1;
} while(y<=l. 0) ;
return 0;
#include <math.h>
double ceil(double num);
Описание: Функцията ceiJ() връща най-малкото цяло число
(представепо като double), което нс е по-малко от пит. Ако нап-
ример е дадено 1.02, ceil() ще върне 2.0; при -1.02, ceil() ще
върне -1.
412 С - Практически самоучител
Пример: Следващият фрагмент отпечатва на екрана 10.0:
printfceil (9.9));
#include <math.h>
double cosfdouble arg);
Списание: Функцията cos( ) връща косинуса от arg. Стойностга
на arg трябва да бъде в радиани.
Пример: Следващата програма отпечатва косинусите на стойнос-
те от -1 до 1 при стъпка от една десета:
frinclude <math.h>
#include <stdio.h>
int main(void)
{
double val=-1.0;
do {
printf("cosine of %f is %f\n", val, cos(val));
va1 += 0.1;
} while(val<=l.0);
return U;
#include <math.h>
double cosh(double arg);
Описание: Функцията cosh() връща хиперболичиия косинус от
arg.
Пример: Следващата програма отпечатва хиперболичиия коси-
нус от стойностите от -1 до 1 при стъпка от една десета:
#include <math.h>
#include <stdio.h>
int main(void)
double val=-l 0;
do {
printf("hyperbolic cosine of %f is %f\n", val,
cosh(val));
Место използвани библиотечни функции на С 413
val += 0.1;
) while(va1<=1. 0) ;
return 0;
#include <math.h>
double exp(double arg);
Описание: Функцията exp( ) връща натурал ним логаритьм е,
повдигнат на стелен arg.
Пример: Следващият фрагмент отпечатва стойността на е (зак-
ръгленада 2.718282):
printf("Value of е to the first: %f", exp(l.O));
#include <math.h>
double fabs(double num);
Описание: Функцията fabs() връща абсолютиста стойност на ниш.
Пример: Следващата програма отпечатва на екрана 1.0 1.0:
ftinclude <math.h>
ftinclude <stdio.h>
int main(void)
{
printf("%l If %l.lf", fabs(l.O), fabs(-l.O));
return 0;
)
#include <math.h>
double floorfdouble num);
Описание: Функцията floor() връща иай-голямото цяло число
(представено като double), което не е по-голямо от ниш. Ако
например е дадено1.02, floor() ще вьрне 1.0; при-1.02,11оог()
ще върне -2.0.
Пример: Следващият фрагмент отпечатва на екрана 10.0:
printf ("%f", floor (10.9П:
414 С - Практически самоучител
#include <math.h>
double log(double num);
Описание: Функцията log() връща натурален логаритьм от пит.
Ако пит е отрицателно, се генерира грешка в областта, а ако е
нула - грешка в интервала.
Пример: Следващата програма отпечатва на екрана натуралните
логаритми на числата от 1 до 10:
ttinclude <math.h>
tfinclude <stdio.h>
int main(void)
{
double val=1.0;
do {
printf("%f %f\n", val, log(val));
val++;
} while (vaKll.O) ;
return 0;
#include <math.h>
double Iog10(double num);
Описание: Функцията Iogl0() връща логаритьм от пит при ос-
нова 10. Ако пит е отрицателно, се генерира грешка в областта,
а ако е нула - грешка в интервала.
Пример: Следващата програма отпечатва на екрана логаритмите
на числата от 1 до 10 при основа 10:
#include <math.h>
ttinclude <stdio.h>
int main(void)
double val=1.0;
do {
printf("%f %f\n", val, loglO (val));
val++;
} while(valcll.0);
return 0;
Често използвани библиотечни функции на С 415
#include <math.h>
double pow(double base, double exp);
Описание: Функцията pow() връша base на степей exp (baseexp).
Грешка в областта може да възникне, ако base е 0, а ехр е по-
малко или равно на 0. Грешка в областта се генерира и ако base с
отрицателно и ехр не е цяло число. Препълването води до греш-
ка в интервала.
Пример: Следващата програма отпечатва на екрана първите де-
сет степени на 10:
ttinclude <math.h>
ttinclude <stdio.h>
int main(void)
{
double x=10.0, y=0.0;
do {
printf("%f ", pow(x, y));
y + +;
} while(yell);
return 0;
#include <math.h>
double sin(double arg);
Описание: Функцията sin() връща синус от arg. Стойностга на
arg трябва да бъде в радиани.
Пример: Следващата програма отпечатва синусите на стойнос-
тите от -1 до 1 при стъпка от една десета:
ttinclude <math.h>
ttinclude <stdio.h>
int main(void)
{
double val=-1.0;
do {
printf("sine of %f is %f\n", val, sin(val));
val += 0.1;
} while(val<=l.0);
416 С - Практически самоучител
return 0;
#include <math.h>
double sinh(double arg);
Описание: Функцията sinh( ) връща хиперболичния синус от arg.
Пример: Следващата програма отпечатва хиперболичния синус
на стойностите от-I до I при стъпка от една десета:
#include <math.h>
^include <stdio.h>
int main(void)
{
double val=-1.0;
do {
printf("hyperbolic sine of %f is %f\n", val,
sinh(val));
val += 0.1;
} while(val<=1.0);
return 0;
#include <math.h>
double sqrt(double num);
Описание: Функцията sqrt() връща квадратен корен от нит. Ако
се извика с отрицателен аргумент, ще генерира грешка в областта.
Пример: Следващият фрагмент отпечатва на екрана 4.0
printf("%f", sqrt(lo.O));
#include <math.h>
double tan(double arg);
Описание: Функцията tan() връща тангенс от arg. Стойността на
arg трябва да бъде в радиани.
Место използвани библиотечни функции на С 417
Пример: Следващата програма отпечатва тангенсите на стойнос-
тите от-1 до 1 при стъпка от една десета:
ftinclude <rnath.h>
ftinclude <stdio.h>
int main(void)
double val=-1.0;
do {
printf("tangent of %f is %f\n", val, tan(val));
val += 0.1;
} while(val<=l.0);
return 0;
include <math.h>
double tanh(double arg);
Описание: Функцията tanh() връща хиперболичния тангенс от
arg.
Пример: Следващата програма отпечатва хиперболичните тан-
генс и на стойностите от -1 до 1 при стъпка от една десета:
ftinclude <math.h>
ftinclude <stdio.h>
int main(void)
double val=-1.0;
do {
printf("tanh of %f is %f\n", val, tanh(val));
val += 0.1;
} while(val<=l.0);
return 0;
ФУНКЦИИ ЗА ЧАС И ДАТА
Функциите за час и дата изискват хедърния файл TIME.H. Този
хедьрен файл дефинира и четири типа и два макроса. Типът
е в състояние да представи системния час и дата каго long
int, Това се нарича колендарно време. Структу рния тип tin съ-
418 С - Практически самоучител
държа датата и часа като отделки елементи. Дефиницията на
ст руктурата tm е:
struct tm {
int tir._sec; int tm_min; /* секунди, 0-61 */
/* минути, 0-59 */
int tm_iiour. /* часоее, 0-23 */
int tm mday; /* ден от меседа, 1-31*/
int tm_mon; /* месеци след януари, 0-11 */
int tm_year; /* години след 1900 */
int tm wday; /* дни след неделя, 0-6 */
int tm_ yday; /* дни след 1 януари, 0-365 *;
int tm_isdst; /* индикатор за лятно часово време */
Стойностга на tmjsdst ще е положителна, ако е включено лят-
Пото часово време, 0 ако не е, и отрицателна ако няма подобна
информация. Когато часът и дата се представят в този вид, това
се нарича раздробено време.
Типы ciock_t е дефиниран по съшйя начин, както time t Хе-
дър.чият файл дефинира и size 1.
Дефинираните макроси са NULL и CLOCKS_PER SEC.
#include <time.h>
char asctime(const struct tm *ptr);
Описание: Функцията asctime() връща указател към низ, съдър-
жащ часа и датата, съхранявани в структура, сочена от ptr, след
като са преобразувапи в следната форма:
ден месец дата часове:минути.секунди година \п\0
Например:
Wed Jun 19 12:05:34 1999
Структурния указател, предавай на asctime(), обикновсно се по-
лучава о г localtime() или gintime( ).
Буферы, използван от asctime() за съхранение на формаги-
рания изходеп низ, е статични заделен масив от знакове и се
припокрива при всяко извикване на функцията. Ако искате ла
залазите съдържаниетс на низа, трябва да го Кондрате другаде.
Пример: Следващата програма отпечатва местного време, дефн-
нирано ст системата:
Место използвани библиотечни функции на С 419
Ifinclude <time.h>
((include <stdio.h>
int main(void)
{
struct tm *ptr;
time t It;
It = time(NULL);
ptr = localtime(<);
printf(asctime(ptr));
return 0;
#include <time-h>
clock_t clock(void);
Описание: Функцията cJock( ) връща броя на циклите на систем-
нич часовник от стартирането на програмата до настоящия мо-
мент. За да преобразувате това число в секуиди, разделете стой-
носгта му на макроса CL()CKS_PER_SEC.
Пример: Следващата програма отпечатва броя на циклите на
системния часовник от ст артиране го си.
fl include <stdio.h>
fl include <time.h>
int main (void)
int i;
for(i=0; i<10000; i++);
printf("%u", clock());
return 0;
)
tfinclude <time.h>
char *ctime(const time t *time);
Описание: Функцияга ctitne() връща указател към низ от след-
ната форма:
ден месец дата часове:минути.секунди година \п\0
420 С - Практи чески самоуч ител
при предаване на указател към календарного време. Календарно-
го време обикновено се получава чрез извикване на lime() Фун-
кцията ctime() е еквивалентна на:
asctine(localtime(time))
Буферът, използван от ctime() за съхранение на форматирания
изходен низ. е статични заделен масив от знакове и се припокри-
ва при всяко извикване на функцията. Ако искате да запазите
съдържанието на низа, трябва да го копирате другаде.
Пример: Следващата преграма отпечатва местного време, дефи-
цитно от системата:
((include <time.h>
((include <stdio.h>
int main(void)
{
time_t It;
It = time(NULL);
printf(ctime(&11) ) ;
return 0;
#include <time,h>
double difftime(time_t time2, time_t timel);
Описание: Функцията difftime() ьрыца разишката, в секунди,
между time! и tbne2, т.е. time2 - timel.
Пример: Следващата програма измерва времето, необходимо на
един празен цикъл foi да преброи от 9 до 500000:
((include ctime.h>
(} include <stdio.h>
int main(void)
(
time_t start, end;
long unsigned int t;
start = time(NULL);
for(t=0; t<500000L; t++);
end = time(NULL);
Често използвани библиотечни функции на С 421
printf("Loop required %f seconds.\n", difftime(end,
start));
return 0;
ttinclude <time.h>
struct tm *gmtime(const time_t *time);
Описание: Функцията gmtime() връша указател към раздробе-
ната форма на time пол формата на tm структура. Часът е предс-
тавен по Гринуич. Стойностга на time обикновено се получава
чрез извикване на timc().
Структурата, използвана от gintinie( ) за съхранение на разд-
робеното време, е статично заделена, и се нрипокрива при всяко
извикване на функцията Ако искате да залазите нейното съдър-
жание, трябва да я копирате другаде
Пример: Следващата програма отпечатва местного и гринуччко-
то време, дефинирани от системата:
ttinclude <time.h>
ttinclude <stdio.h>
/* отпечатване на местното и гринуичкото време */
int main(void)
{
struct tm *local, ^coordinated;
t ime _t t ;
t = time(NULL);
local = localtime (&t) ,-
printf("Local time and date: %s", asctime(local )) ;
coordinated = gmtime(&t);
printf("Coordinated Universal time and date: %s",
asctime(coordinated)),
return 0;
}
ttinclude <time.h>
struct tm *localtime(const time t *time);
Описание: Функцията localtimef ) връща указател към раздробе-
ната форма на time под формата на tm структура. Часът е предс-
тавши в местния часови пояс. Стойностга на time обикновено се
получава чрез извикване на time().
422 С - Практически самоучител
Структурата, използвана от localtime() за съхранение на раз-
дробеното време, е статично заделена. и се припокрива при вся-
ко извикване на функцията. Ако искате да запазите нейното съ-
държание, трябва да я копирате другаде.
Пример: Следвашата програма отпечатва местного и гринуичко-
то време, дефинирани от системата:
iinclude <time.h>
iinclude <stdio.h>
/* отпечатване на местното и гринуичкото време */
int main(void)
{
struct tm * Local, *coordinated;
t ime_t t;
t = time(NULL);
local = localtimo(&t);
printf ("Local time and date: %s", asctiire (local)) ;
coordinated = gmtime(&t);
printf("Coordinated Universal time and date: %s",
asctime(coordinated));
return 0;
}
#include <time.h>
time J time(time_t *systime);
Описание: Функцията time() връща текущего календарно време
на системата. Ако системата няма мсханизъм за отмерване на
времето, тогава връша -1.
Функцията time() може да се извиква или с нуле в указател,
или с указател кьм променлива от тип time_t- Ако използвате
последнего, на аргумента сыцо ще бъде присвоено календарного
време.
Пример: Следващата програма отпечатва местното време, дефи-
нирано от системата:
((include <time.h>
((include <stdio.h>
int main(void)
{
struct tm *ptr;
time_t It;
Место използвани библиотечни функции на С 423
It = time(NULL);
ptr = localtime(&±t);
printf(asctime (ptr));
return 0;
A.4
Функции за динамично заделяhe
Съществуват два основни начина, но който една програма на С
може да съхранява информация в основната памет на компютъ-
ра. Първияг използва глобалнн и локални променливи - включи-
телно масиви и структури. В случая с глобални и статични ло-
кални променливи, мястото за тях е фиксирано за цялото време
на изпълнение на програмата. При динамичните локални про-
менливи, мястото се заделя в стека. Въпреки че тези променливи
са ефективно имплементирани в С, те изискват от програмиста
предварително да знае необходимото място за всяка ситуация
Вторият начин за съхранение на информация в С е чрез него вата
система за динамично заделяне При този метод мястото за ин-
формацията се заделя в свободната памет (наречена хийп), когато
е необходимо.
Стандартът ANSI С опрелеля, че хедърната информация, не-
обходима на системата за динамично заделяне, се намира в
STDL1B.H. В този файл е дефиниран типът size_t. Този тип се
използва доста широко от функциите за заделяне и по същество
е еквивалентен на unsigned.
#include <stdlib.h>
void *calloc(size_t num, size_t size);
Описание; Функцията calloc() връща указател към заделената
памет. Копичеството памет е равно на num*size. Това означава,
че функцията заделя достатъчно памет за масив от пит обекта с
размер size.
Функцията саПос( ) връща указател към първия байт от заде-
лената облает. Ако няма достатъчно памет за изпълнение на за-
явката, връща null.
Изключително важно е винаги да проверявате дали върнатата
стойност не е null преди да се опитате да я използвате.
Пример: Следващата функция връща указагел към динамично
заделен масив от 100 елемента от тип float:
424 С - Практически самоучител
ttinclude <stdlib.h>
ttinclude <stdic.h>
float *get_rcem;void)
{
float *p;
p = calloc(100, sizeof(float));
if(!p) {
printf ("Allocation error - aborting. \n11) ;
exit(1);
}
return p;
//include <stdlib.h>
void free(void *ptr);
Описание: Функцията free() освобождава паметта, сочена от plr.
По този начин паметта става достъпна за бъдещо заделяне.
Изключитслно важно с винаги да извиквате функцията free()
само с указател, който преди това е заделен посредством никоя
от функциите на системата за динамично заделяне, като malloc( )
или са!1ос(). Използването на грешен указател вероятно ще раз-
руши механизма за управление на паметта и ще доведе до бло-
киране на системата
Пример: Следващата програма първо заделя място за 100 потре-
бителем! низа и след това ги освобождава
ttinclude <stdlib.h>
ttinclude <stdio.h>
int main(void)
{
char *str[10C];
int i ;
for(i=0; i<100; i++) {
if((str[i] = malloc(128))==NULL) {
printf("Allocation error - aborting.\n");
exit(0) ;
}
gets(str [i]);
}
/* сега се освобождава паметта */
for(i=Q; iclCO; i++) free(str[i]);
return 0;
}
Место използвани библиотечки функции на С 425
#include <stdlib.h>
void *malloc(sizej size);
Описание: Функцията malloc( ) връща указател към първия байт
от облает от паметта с размер s’ze, който е заделе» в хийпа. (За-
помнеге, че хийпът е облает от свободна та памет, управляв ана
от системата за динамично заделяне на С.) Ако има недостиг на
памет за изпълнение на заявката, функцията връща нулев указа-
тел. Изключително важно е винаги да проверявате дали връща-
ната стойност не е nul] преди да се опитаге да я използвате Опи-
гьт за използване на нулев указател обикновено води до блоки-
ране на системата
Пример: Следващата функция заделя достатъчно памет за съх-
ранение на структури от тип addr:
#include <stdlib.h>
#in.Clude <stdio.h>
Struct addr {
char name[43];
char street[40];
char city[40];
char state [ 3 ] ;
char zip[10];
};
struct addr *get_struct(void)
{
struct addr *p;
if((p = malloc(sizeof(struct addr)))==NULL)
{
printf("Allocation error - aborting.\n");
exit(0),
}
return p;
#include <stdlib.h>
void *realloc(void ’ptr, size_t size);
Описание: Функцията reailocf) променя размера на заделената
памет. сочена от ptr, до зададения от size размер. Стейн ост га на
size меже да е по-голяма или по-малка от първоначалния размер.
Функцията връща указател към блока от паметта, тъй като може
426 С - Практически самоучител
да се наложи функцията да го промести, за да увеличи неговия
размер. Ако това се случи, съдържанието на стари я блок се ко-
пира в новия - не се губи информация.
Ако в хийпа няма достатъчно свободна памет за заделяне на
size байта, връша нулей указател. Това означава, че е важно да
прозерявате за успеха на извикването на realioc().
Пример: Следващата програма първо заделя 17 знака, копира ни
за “this is 16 chars” в това място, и след това използва realloc(),
за да увеличи размера на 18, за да може да постави точка в края
на низа.
ftinclude <stdlib.h>
ftinclude <stdio.h>
ftinclude <string.h>
int main(void)
{
char *p;
p = malice(17);
i f ( ! p) {
printf("Allocation error - aborting.\n");
exit(1) ;
strcpy(p, "this is 16 chars");
p = realloc(p,18);
i f ( ! p) {
printf("Allocation error - aborting.\n");
exit(1) ;
strcat(p, ".");
printf(p);
free(p);
return 0;
Други функции
Функциите, разглеждани в тази секция, са стандартни, но не се
вписват в никоя друга категория.
Често използвани библиотечни функции на С 427
#include <stdlib.h>
void abort(void);
Описание: Функцията abort( ) предизвиква незабашю термини-
ране на програмата. Дали тя ще затвори всички отворени файлове,
зависи от имплементацията, но по принцип няма да го направи.
Пример: В следващата програма, ако потребителят въведе А,
програмата ще се затвори:
ttinclude <stdlib.h>
ttinclude <conio.h>
int m&in(void)
{
for(;;)
if (getche () =-='A') abort ();
return 0;
}
#include <stdlib.h>
void abs(int num);
Описание: Функцията abs() врътца абсолютната стойност на ця-
лото число пит.
Пример: Следващата функция конвертира въведените от потре-
бителя числа в тех лите абсолютни стойности:
ttinclude <stdlib.h>
ttinclude <stdio.h>
int get sbs(void)
{
char num[801:
geta (num);
return abs(atoi(num));
428 С - Практически самоучител
^include <stdlib.h>
void atof(const char *str);
Описание: Функцията atof() преобразува низа, сочен от указате-
ля str, в стойност от тип double. Низът трябва да съдържа валид-
на стойност с плаваща запетая. Ако това не е така, функцията
връща О,
Числото може да се терминира от всякакъв знак, който не
може да е част ог валидно число с плаваща запетая. В това число
влпзат интервали, пунктуационни знакове (различии от точка) и
знакове, различии от ‘е’ и ‘Е’. Поради това ако извикате atof() с
“100.00HELLO”, функцията ще върне стойността 100.00.
Пример: Следващата програма прочита две числа с плаваща за-
петая и отпечатва тяхнага сума:
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char numl[80], num2[80]j
printf("Enter first: ");
gets(nural);
printf("Enter second: ");
gets (ашп2) ;
printf ("The sun is: %f", acof(nunil) + atof(num2));
return 0;
#include <stdlib.h>
void atoi(const char *str);
Описание: Функцията atoi( ) преобразува низа, сочен от указате-
ля str, в стойност от тип int. Низът трябва да съдържа валидна
целочислена стойност. Ако това не е така, функцията връша 0.
Числото може да се терминира от всякакъв знак, който не
може да е част от валидно цяло число. В това число влизат ин-
тервали, пунктуационни знакове и други такива. Поради това ако
извикате atoi( ) със 123.23, функцията ще върне стойността 123,
а остатъкът 0.23 ще бъде игнориран.
Пример: Следващата програма прочита две цели числа и отпе-
чатва тяхната сума:
Често използвани библиотечни функции на С 429
ttinclude <stdlib.h>
ttinclude <stdio.h>
int main(void)
{
char numl[80], nuiii2[80];
printf ("Enter first; ");
gets(numl);
printf("Enter second: ");
gets(num2);
printf("The sum is: %d", atoi(numl) + atoi(num2));
return 0;
ttinclude <stdlib. h>
void atol(const char *str);
Описание: Функцията atol() преобразува низа, сочен or указате-
ля str, в стойност от тип long int. Низът трябва да съдържа ва-
лидна целочислена стойност. Ако това не е така, функцията
връща 0.
Числото може да се терминира от всякакъв знак, който не
може да е част от валидно цяло число. В това число влизат ин-
тервали, пунктуациоини знакове и други такива. Поради това ако
извикате atol( ) със 12.3.23, функцията ще върне стойностга 123,
а остатъкът 0.23 ще бъде игнориран.
Пример: Следващата програма прочита две цели числа и отпе-
чатва тяхната сума:
ttincluae <stdlib.h>
ttinclude <stdio.h>
int main(void)
{
char numl[80], num2[80];
printf("Enter first: ");
gets(numl);
printf("Enter second: ");
gets(num2);
printf("The sum is: %ld", atol(numl) + atol(num2));
return 0;
)
430 С - Практически самоучител
#include <stdlib.h>
void *bsearch(const void *key. const void base,
size_t num, size_t size,
int(compare)(const void *, const void *));
Описание: Функцията bsearch() извършва двоично търсене в
сортиран масив, сочен от base, и връща ука зател към първия
елемент, който съвпада с ключа, сочен от key. Броят на елемен-
тите в масива се задава чрез пит, а размерът (в байтове) на всеки
елемент се определи от size. (Типът size_t е дефиниран в
STDLIB.H и по същество е еквивалентен на unsigned.)
Функцията, сочена от compare, се използва за сравняване на
елементиге на масива с ключа. Формата на compare трябва да е:
Int име-на-функция(соп5\ votd *агд1, const void *агр2)
Тя трябва да връща следните стойности:
по-малко от 0 эко агд1 е по-малък от агд2
О ако srg1 е равен на агу 2
по-голямо от 0 ако агд1 е по-гопям от агд2
Масивът трябва да се съхранява във възходящ ред, като най-
ниският адрес трябва да съдържа най-малкия елемент.
Ако масивът не съдържа ключа, тогава функцията връща null.
Пример: Следващата програма прочита знакове, въведени от
клавиатурата, и определя дали те са букви:
ftinclude <stdlib.h>
ftinclude <ctype.h>
ftinclude <stdio.h>
char *alpha = "abcdefghijklmnopqrstuvwxyz";
int ccmp(const void *ch, const void *s);
int main(void)
{
char ch;
char *p;
do {
printf("Enter a character: ");
scanf("%c%*c",&ch);
ch = tolcwer(ch);
Место използвани библиотечки функции на С 431
р = bsearch(sch, alpha, 26, 1, comp);
if(р) printf("is in alphabet.\n");
else printf("is not in aIphabet.\n");
} while(p);
return 0;
}
/* сравняване на два знака */
int comp(const void *ch, const void *s)
(
return *(char *)ch - * (char *)s;
}
#include <stdlib.h>
void exitfmt status);
Описание: Функцията exit() прсдизвиква незабавно нормално
затваряне па програмата.
Стойността на status се предана на извикващия процес, обик-
новено операционната система, ако средата поддържа това. По
конвенция, ако стойността на status е 0, се предполага нормално
завършване на програмата. Ненулевите стойности можете да из-
ползвате за индик и ране на грешки.
Освен това като артументи на exit( ) бихте могли да използва-
те прсдварителпо дефинираните макроси EXIT SUCCESS и
EXITFAILURE.
Пример: Следващата функция извършва избор о г меню па прог-
рама за пощенски списъци. Ако изберете Q , програмата се зат-
варя:
char menu(void)
{
char ch;
do {
printf("Enter names (E)\n");
printf("Delete name (D)\n");
printf("Print (P)\n");
printf("Quit (Q)\n");
} while(!strchr("EDPQ",toupper(ch)));
if(ch==’Q’) exit(0);
return ch;
}
432 С - Практически самоучител
#inc|ude <stdlib.h>
void |abs(long num);
Описание: Функцията |abs() връща абсолютната стойност на
пит, което е от тип long int.
Пример: Следващата функция конвертира въведените от потре-
бителя числа в техните абсолютни стойности:
ttinclude <stdlib.h>
ttinclude <stdio.h>
long int get_labs(void)
char num[80];
gets(num);
return labs(atol(num));
^include <stdlib.h>
void Iongjump0mp_buf envbuf, int val);
Описание: Функцията longjump() предизвиква изпълнението на
програмата да продължи от точката на последнего извикване на
setjmp(). Тези две функции са начинът, по който ANSI С пре-
доставя безусловен преход между функциите. Обърнете внима-
ние, че се изисква хедърният файл SETJMP.H.
Функцията longjmp() работа, като настройва стека, както е
описано в envbuf, който трябва да е зададен от предишно извик-
ване на setjmp( ). По този начин изпълнението на програмата
продължава с конструкцията, следваща извикването на setjmp()
- компютърът бива “подведен”, че никога не е напуска функция-
та, извикваща setjmp(). (Като някакво графично представяне,
функцията longjmp() се “отклонява” във времето и пространст-
вото, без да се налага да преминава през стандартния процес на
връщане от функция.)
Буферът envbuf е от тип jmpbuf, който е дефиниран в хедър-
ния файл SETJMP.H. Буферът трябва да е настроен посредством
извикване на setjmp() преди извикването на longjmp().
Стойностга на val се превръща във връщаната от setjmp()
стойност, и можете да я използвате, за да определите откъде е
станал преходът. Единствената неразрешена стойност е 0.
Место използвани библиотечни функции на С 433
Важно е да разберете, че функцията longjmp() трябва да бъде
извикана преди функцията, извикала setjrnp(), да завърши. Ако
това не стане, резултатьт е технически недефиниран. На практи-
ка почти винаги това ще доведе до блокиране на системата.
За сега най-честото приложение на longjmp( ) е за връщане от
дълбоко вложени процедури, когато се появи фатална грешка.
Пример: Следващата функция отпечатва 12 3:
ftinclude <setjmp.h>
ftinclude <stdio.h>
void f2(void);
jmp_buf ebuf;
int main(void)
{
char first=l;
int i;
printf ("1 ");
i = setjmp(ebuf);
if(first) {
first = !first;
f 2 () ;
printf("this will not be printed");
printf("%d", i);
return 0;
void f2(void)
{
printf ( "2 ");
longjmp(ebuf, 3);
#include <stdlib.h>
void qsort(void *base, size_t num, size_t size,
int(compare)(const void *, const void *));
Описание: Функцията qsort() сортира масива, сочен от base,
посредством метода Quicksort (разработен от C.A.R. Ноаге). Този
метод се счита за най-добрият за сортиране с общо предназначе-
ние. При терминирането масивът ще бъде сортиран. Броят на
елементите в масива се задава чрез пит, а размера (в байтове) на
434 С - Практически самоучител
всеки елемент се определи от size. (Типът sizet е дефиниран н
STDLIB.H п по същество е еквивалентен на unsigned.)
Функцията, сочена от compare, се използва за сравняване на
елементите на масива с ключа. Формата на compare трябва да е:
int име-на-функция( con st void *агд1, const void *arg2)
Тя трябва да връща следните стойности:
по-малко от О
О
по-гопямл от О
ако агд1 е пэ-малък от агд2
ако агд1 е равен на агд2
ако агд1 е по-голям от агд2
Масивът бива сортиран във възходящ ред, като най-пиският ад-
рес съдържа най-малкия елемент.
Пример: Следващата програма сортира списък от цели числа и
изписва резултата:
ttinclude <stdlib.h>
#include <stdio.h>
int comp(const void *i, const void *j);
int num[10]= {
1,3, 6, 5, 8, 7, 9, 6, 2, 0
int main(void)
int i;
printf("Original array: ");
for(i=0; i<10; i++) printf("%d ", num[i]);
printf("\n");
qsort(num, 10, sizeof (int), comp);
printf("Sorted array: ");
for(i=0; i<10; i++) printf("%d ", numji]);
return G;
/* сравняване на целите числа */
int comp(const void *i, const void *j)
return ★ (int *) i
(int *)j;
Често използвани библиотечни функции на С 435
#include <stdlib.h>
int rand(void);
Описание: Функцията rand() генерира поредица от псевдослу-
чайни числа. При всяко нейно извикване тя връща число между
О и RAND-MAX. RAND_MAX е дефиниран в STDLIB.H. Стан-
дартьт ANSI С повелява, че макросът RAND_MAX трябва да
има стойност ионе 32 767.
Пример: Следващата програма изобразява десет псевдослучайни
числа:
ttinclude <stdlib.h>
ttinclude <stdio.h>
int main(void)
(
int i;
for(i=0; i<10; i++)
printf("%d ", rand());
return 0;
}
#include <setjmp.h>
void setjmp(jmp_buf envbuf);
Описание: Функцията setjmp( ) запазва съдържанието на стека
на системата в буфера envbuf за по-късно използване от
longjmp().
Функцията setjmp( ) връща 0 при извикване. longjmp() обаче
предава аргумента на setjmp(), когато се изпълни, и това е стой-
ността (винаги различна от нула), която ще е стойностга на
setjmp() след извикване на ]ongjmp().
За повече информация вижте секцията за Iongjmp( ).
Пример: Следващата функция отпечатва 12 3:
ttinclude <setjmp.h>
ttinclude <stdio.h>
void f2(void) ;
jmp_buf ebuf;
436 С - Практически самоучител
int main(void)
(
char first^l;
int i;
printf ("1 ");
i = setjmp(ebuf);
if(first) {
first = (first;
f 2 ( ) ;
printf("this will not be printed");
}
printf("%d",i);
return 0;
void f2(void)
{
printf("2 ");
longjmp(ebuf, 3) ;
ftinclude <stdlib.h>
int srand(unsignea seed);
Описание: Функцията srand() се използва за задаване на начал-
на точка за поредицата, генерирана от raud(), която връща псев-
дослучайна числа.
По принцип srand() се използва, за да се даде възможност
различии стартирания на програмата да използват различии по-
ре дици от псевдослучайни числа.
Пример: Следващата програма използва ситемното време, за да
инициализира случайно функцията гand() посредством srand():
# include <stdio.h>
#include <stdlib-h>
# Include <time.h>
/* Задаване на начална точка на rand от ситемното време
и изписване на първите 100 числа.
* /
int main(void)
(
int i, utime;
long Itime;
/* получаване на текущото календарно време */
Место използвани библиотечни функции на С 437
It ime utime = time(NULL); = (unsigned int) ltime/2;
stand(utime) ;
for(i-O; i<10; i++) printf("%d ", rand());
return 0;
Преглед на ключовите
думив С
440 С - Практически самоучител
Съществуват 32 ключови думи, конто в комбинация с
официалния синтаксис на С формират езика С, както е
дефиниран от стандарта ANSI С. Тези ключови думи са
показани в таблица 1.
Всички ключови думи в С използват малки букви. В С, мал*
ките и главните букви са различии; например else е ключова ду-
ма, докато ELSE не е.
Отгук нататък слсдва представяне на ключовите думи по аз-
бучен ред:
auto
auto се използва за създаване на временни променливи, създавз-
ни при влизане в блок с код и разрушавани при излизане. Нап-
ример:
#include <btdio.h>
#include <conio.h>
int main(void)
for(;;) {
if (getche ()==•=' a') {
auto int t;
for(t=0; t<’a'; t++)
printf("%d ", t);
break;
}
return 0?
auto double int struct
break else long switch
case enum register typedef
char extern return union
const float short unsigned
continue for signed void
default goto sizeof volatile
do if static while
Таблица B-1
Списък hs ключовите думи
Преглед на ключовите думи в С 441
В този пример променливата t се създава само ако потребителят
натисне а. Извън блока if, t е напълно неизвестна и всяка рефе-
ренция към нея ще генерира синтактична грешка но време на
компилиране. Упогребата на auto е абсолютно незадължителна,
защото по подразбиране всички локални променливи са auto.
break
break се използва за излизане от цикли do, while и for, като зао-
бикаля нормалното условие на цикъла. Освен това тя се използва
и за излизане от конструкции switch.
Тук е показан пример за употребата на break:
whi 1е (х<100) {
х = get_new_x();
if(kbhitO) break; /* яатискаке на клавиш */
process(х);
}
В този случай, ако бъде натиснат клавиш, цикълът ще бъде тер-
миниран без значение от стойностга на х.
В конструкцията switch, break ефектиьно предпазва програм-
ното изпълнение от “неуспех” в следващия case. (За подробнос-
ти, прегледайте секцията switch.)
case
case се разглежда заедно със switch.
char
char с тип за данни, използван за деклариране на знакови про
менливи. Например, за да декларирате ch като знакова промен-
лива, трябва да напишете:
char ch;
В С, знаковете са с големина един байт.
const
Модификаторът const казва на компилатора, че съдържанието на
дадена променлива не може да се променя. Освен това той се из-
442 С - Практически самоучитеп
иопзва, за да предотврати промяната на някои от параметрите на
дадена функция.
continue
continue се използва, за пропускане на част от кода на даден ци-
къл и за предизвикванс на пресмятането на израза с условието.
Например следващият цикъл while просто прочита знакове от
клавиатурата до въвеждане на s:
while (ch=getche()) {
if (ch != ’s’) continue; /* прочитане на следващия
знак */
process(ch);
}
Изьикването на process() няма да се осъществи, докато ch не
съдържа знака s.
default
default се използва в конструкцията switch, за определяне на
блока с код, който да се изпълни по подразбиране ако не бъде
намерено съвпадение в switch. Вижте секцията switch
do
Цикълът do е един от трите “строителя” на цикли в С. Основната
форма на цикъла do е:
do {
бпок-с-нонструкции
} while (условие)',
Ако се повтаря само една конструкция, скобите не са необходи-
ми, но те придават яснота на конструкцията. Цикълът do се пов-
таря, докато условие е със стойност true.
Цикълът do е единственият цикъл в С, който винаги ще се из-
пълни поне веднъж, защото условието му се проверява в края
Често приложение на цикъла do е за четене на файлове от
диск. Следващият код чете даден файл, докато срещне EOF.
do {
ch = getc(fp);
if ( !feof(fp)) printf("%c", ch);
} while(!feof(fp));
Преглед на ключовите думи п С 443
double
double е тип за данни, използван за деклариране на променливи
с плаьаща запетая и с двойна точност. За да декларирате d от тип
double, грябва да напишете следната конструкция:
double d;
else
Вижте секцията за if.
enum
Спецификаторът на типове enum се използва за създаване на из-
брсителни типове Изброяването е просто списък от именувани
целочислени константи. Например следващият код декларира
изброяване, наречено color, състоящо се от три константи: red,
green и yellow.
^include <stdio.h>
enum color {red, green, yellowJ;
enum color c;
int main(void)
{
c = red;
if(c==red) printf("is red\n");
return 0;
}
extern
Мэдификаторът на типове extern указва на компилатора, че да-
дена променлива е дефинирана аякъде другаде в програмата. То-
ва се прилага често в програми с отделно компилирани файлове,
конто поделят общи глобални данни и след това се евързват за-
едно. С други думи, така уведомявате компилатора за дадена
променлива без да я дефинирате отново.
Като пример, ако first е декларирана в друг файл кате цялэ
число, в останэлитс файлове ще използвате следната декларация
extern int first;
444 С - Практически самоучител
float
float е спецификатор за типове данни, който се използва за дек-
лариране на променливи с плаваща запетая. За да декларирате f
от тип float, трябва да напишете:
float f;
for
Цикълът for дава възможност за автоматично инициализация и
инкрементиране на променливата на цикъла. Общата му форма е:
^(инициализация; условие; инкрементиране} {
блок-с-конструкиии
}
Ако блок-с-конструкции е само една конструкция, скобите не са
необходими.
Въпреки че for дава възмсжност за редица вариации, по
принцип инициализация се използва за настройка на променли-
вата -брояч в нейната начална стойност. Условие обикновено е
условна конструкция, която проверява променливата-брояч за
някаква стойност, а инкрементиране инкремеитира (или декре-
ментира) стойността на брояча. Цикълът продължава, докато ус-
ловието стане false.
Следващият код отпечатва hello десет пъти:
for(t=0; t<10; t++) printf("Hello\n")J
goto
goto води до безусловен преход на програмата към етикет, за-
даден в конструкцията goto. Основната форма на goto е:
goto етикег;
етикет
Всички етикети трябва да завършват с двоеточие и не трябва
да съвпадат с ключови думи или имена на функции. Освен това
goto може да извършва преход само в гекущата функция, а не от
една функция в друга.
ПрегледнаключсвитедумивС 445
Следващият пример ще отпечата съобщението right, но не и
wrong:
goto labl;
printf("wrong");
labl :
printf("right");
if
Основната форма на конструкцията if е:
if(yc ловие) {
блок-с-конструкции 1
}
else {
блок-с-конструкции2
}
Ако използвате само една конструкция, скобите не са необходи-
ми. else не е задължителен.
Условие може да бъде всякакъв израз. Ако изразът начисли
стойност, различна от 0, ще бъде изпълнен блок-с-конструкциц I;
в противен случай, ако сьщсствува, ще бъде изпълнен блок-с-
конструкции2.
Може да използвате следващия фрагмент за въвеждане от
клавиатурата и следене за ‘q’, коего с сигнал за “край”.
ch - getche(),
if(ch=='q') {
printf("Program Terminated"),
exit(0);
}
else proceed();
int
int e спецификатор за тип , който се използва за деклариране на
целочислени промеиливи. Например, за да декларирате count ка
то цяло число, напишете:
int count;
long
long e модификатор на тип за данни, който се използва за декла-
риране на “дълги” цели числа или double промеиливи.
446 С - Практически самоучител
Например, за да декларирате count като ”‘дълго" пяло число,
трябва да напишете:
long int count;
register
Модификаторы- register изисква дадена променлива да се съх-
ранява по начин, позволяващ чай-бързия възможен достъп до
ноя. В случая със знакове или цели числа, това обикновено озна-
чава в регистрите на пропесора. За да декларирате i като register
цяло число, трябва да напишете:
register int i;
return
Конструкцията return предизвиква завършване на функпията и
може да се използва за връщане на стойност на извикващата
процедура. Следващата функция например връша произведение-
то на своите два аргумента.
int mul(int a, int b)
{
return a*b;
He забравяйте, че след като срещне return, функцията завършва
и останалият й код се пропуска.
short
short е модификатор на типове за данни, използван за деклари-
ране на малки цели числа. Например, за да декларирате sh като
малко цяло число, напишете:
short int sh;
signed
Модификаторы на типове за данни signed най-често се използва
за задаване на signed char тип за данни.
Преглед на ключовите думи в С 447
sizeof
Ключовата дума sizeof е оператор по време на компилиране,
връщащ дължината на дадена променлива. Ако е поставен пред
тип за данни, последният трябва да е затворен в скоби. Например
printf("%d", sizeof(short int));
ще отпечата 2 при повечето имплементации на С.
По принцип конструкцията sizeof се използва. за да се пре-
достави преносимост на кода, когато той зависи от размера на
вгра дените в С типове за данни.
static
Ключовата дума static е модификатор на типове за данни, каращ
компилатора да създаде постоянно “хранилище” за дадена ло-
кална променлива. По този начин се д.тва възможност на тази
променлива да запазва своята стойност между отделяйте извик-
вания на функцията. Например, за да декларирате last_time като
static цяло число, трябва да напишете:
static int last_tiip.e;
static може да се използва и за глобални променливи, за да се
ограничи тяхна облает на видимост само във файла, в който са
декларирани.
struct
Конструкцията struct се използва за създаване на агрегатни ти-
пове данни, наречени структури, конто се обстоят от един или
повече членове. Обшата форма на структурата е:
struct име-наОструктурата {
тип член 1;
тип член2;
тип членМ;
} списък-с-променливи;
Отделните членове се реферетщират посредством операторите
точка и стрелка.
448 С - Практически самоучител
switch
Конструкцията switch е конструкцията в С за множество разкло-
неиия. Тя се използва за насочване на изпълнението по един от
няколко възможни пътя. Общата форма на конструкцията е:
switch(/nt-u3paa) {
case константаТ: наоор-от-конструкции 7;
break;
case константа2'. набор-ст-конструкции 2;
break;
case константа/^, набор-от-конструкции N;
break;
default: default-конструкции;
}
Всеки набор-от-конструкции може да е с произволен брой конс-
трукции. Частта default не е задължителна. Изразът, контроли-
ращ switch, както и всички case константи трябва да са от тип int
или char.
switch работи, като сравнява стойността на ini-израз с конс-
тантите. След като намери съвпадение. изпълнява набора от кон-
струкции. Ако конструкцията beak бъде пропусната, изпълнени-
сто щс продължи със следващия case. Можете да миелите за case
като за етикети. Изпълнението ще продължи, докато се срещне
break или края па switch.
Можете да използвате следващия пример за обработка на из-
бор от меню:
ch = gerche();
switch(ch) {
case 'e': enter();
break;
case '1': list ();
break;
case 's': sort ();
break;
case 'q': exit(0);
break;
default: printf("Unknown CommandXn");
printf("Try Again\n");
}
Преглед на ключслите думи в С 449
typedef
Конструкцията typedef дава възможност за създаване на ново
име на съществуващ тип за данни. Общата форма на typedef е:
typedef спецификатор-за-тип ново-име',
Например, за да използвате думата “Ьа1апсе”вместо “float”, тряб-
ва да напишете
typedef float balance;
union
Ключовата дума union създава агрегатен тип, в който две или
повече променливи споделят една и съща памет. Формата на
декларацията и начините за достъп до членовете са същите, как-
то при struct. Общата форма е:
union име-на-обединението {
тип член!;
тип члеи2\
тип ypenN;
} списък-с-променливи;
unsigned
Модификаторы' на типове unsigned указва на компилатора да
създаде променлива, която съдържа само беззнакови (т.е., поло-
жителни) стойности. Например, за да декларирате big като без-
знаково цяло число, трябва да напишете:
unsigned int big;
void
Спецификаторы на тип void се използва основно за деклариране
на void функции (функции, който не връщат стойност), за създа-
ване на void указатели (указатели към void) - общи указатели,
сиособни да сочат към обекти от всякакъв тип - и за задаване на
цразен списък с параметри.
450 С - Практически самоучител
volatile
Модификатора volatile указва на компилатора, че дадена про-
менлива може да променя своята стойност по начини, конто не
са изрично зададени в програмата. Променливи, променяни от
хардуера, като например часовници, прекъсвания и т н., са при-
мери за такива променливи.
while
Цикълът while е със следната .обща форма:
while (условие) {
блок-с-конструкции
Лко обсктът на while е една конструкция, скобите може да бъдат
пропуснати. Цикълът ще се повтаря, докато условие е true.
while проверява условието в началото на цикъла. Затова, ако
условието е false още от самото начало, цикълът няма да се из-
пълни изобщо. Условието може да е всякакъв израз.
Следва пример за while. Той чете знакове, докато срещне end-
of-file
t = 0;
while(!feof(fp)) {
s[t] = getc(fp);
t++;
План на Windows
програма
452 С - Практически самоучител
Се популярен език за "Windows програмирапе, Затова
изглежда доста смислено да разгледаме подобна тема в
тази книга. Трябва обаче да знаете едно: прсграмиране-
ю за Windows изисква солидпи познания както по С,
така и по Windows. Честно казано, преди да започнете да пишете
полезны Windows програми, ще трябва да усъвършенствате сво-
ите умения по С и след това да инвестирате зпачителпо време в
изучаването на входовете и изходите на операционната система
Windows. Не забравяйте, че само описаниетэ на функциите. дос-
тъпни в W’indows. изисква около 2 000 печатни страници!
Ако ще се занимавате с програмирапе под Windows, вероятно
сте нет ьрпеливи да започнете Целта на това приложение е да ви
представи крагьк преглед на Windows програмирането и да обяс-
ии някои от неговите най-основни елементи. Накратко казано,
представената тук информация е проектирана да ви даде “летящ
старт” в света на Windows програмирането.
Това приложение обсъжда какво, в обши линии, е Windows,
как една програма трябва да взаимодейства с него и кои са пра-
вилата, който всяко приложение трябва да следва. Освен това
тук се разоаботьа план на приложение, което можете да използ-
вате като основа за вашите-собствени Windows програми. Както
ще видите, всички W’indows програми имат няколко общи особе-
ности. Тези общи атрибута ще се съдържат в плана.
Коя версия на Windows?
По време на създаването на това приложение сьществуваха три
широко разпространени версии на операционната система
Windows: Window's 3.1, Windows 95/98 и Windows NT/2000. Ски-
цата, разработена тук, е проектирана за 32-битовите версии на
Windows, като Windows 95/98 или Windows NT/2000, тъй като
това са най-широко използваните версии. Въпреки всичко ос-
новните принципи са приложими за всички версии на Windows.
Перспективите на Windows
ПРОГРАМИРАНЕТО
Целта на Windows е даде възможност на всеки. който има основ-
на представа за системата, да седне и да работа практически с
всяко приложение без предварително обучение. За да иосгигне
това, Windows предоставя па потребителя однотипен интерфейс.
На теория, ако можете да работите с една Windows-базирана
програма, значи можете да работите с всички. Разбира се, на
План на Windows npoi рама 453
практика най-полезните програми все пак ще изискват някакъв
виц обучение, за да могат да бъдат използвани ефективно, но те-
зи инструкции може поне да се ограничат до това какво нрава
програмата, а не как потребителят трябва да взаимодейства с
нея. Всъщност повечето от кода в едно Windows приложение е
там единствено за поддръжка на потребителския интерфейс.
Преди да продължим трябва да отбележим, че не всяка прог-
рама, която работа под Windows, ще предостави па потребителя
интерфейс в стал Windows Възможно е да бъдат създадени про-
грами, който не се възползват от интерфейсните елементи на
Windows. За да създадете програма в стал Windows, трябва да си
поставите това за цел. Само програмите, написани така, че да се
възползват от Windows, ще изглеждат и ще се държат като
Windows програми. Въпреки че можете да не зачете!е основните
принципи на Windows дизайна, добре е да имате основателна
причина за това, защото потребитслитс па вашите програми ве -
роятно ще са много разочаровали. По принцип всички приложил
програми, създавани за Windows, трябва да използват стандарт-
ния Windows интерфейс и трябва да следват утвьрдените прак-
тики в Windows дизайна.
Windows е графично ориентирам, което означава, че той пре-
доставя графичен потребителем! интерфейс (Graphical User Inter-
face -GUI) Въпреки че графичният хардуер и видео режимите са
доста различии, доста от различията се управляват от Windows.
Това означава, че в по-голямата си част вашата програма не
трябва да се тревожи за вида на използваните графичен хардуер
и видео режим.
Нека разгледаме някои от най-важните възможности на
Windows.
Моделът работно поле
Освен с няколко изключения, целта на Windows-базирания
потребителем! интерфейс е да предостави на екрана сквивалент
на работно поле (десктоп). На всяко бюро можете да намерите
няколко различии хартиени листа, скупчени един върху друг,
често с фрагмента от странини, който се виждат изпод най-гор-
ната страница. Еквивалентът на работного тюле в Windows е ек-
ранът. Късчетата хартия се представят на екрана като прозорци.
На бюрото можете да местите хартийките насам-натам, да сме-
ните коя да е най-отгоре и колко да се вижда от никоя страница.
Windows дава възможност за извьршването на същите тези опе-
рации със неговите прозорци. С избирането па даден прозорсц,
можете да го направите текущ, което означава да го поставите
върху всички останали огворени прозорци Можете да уголемя-
вате и намалявате прозорците и да ги местите по екрана. Накрат-
454 С - Практически самоучител
ко казано, Windows дава възможност за контролиране на повър-
хностга на екрана по същия начин, по конто се контролират еле-
ментите на бюрото.
Въпреки че моделът “работно поле” формира основите на
потребптелския интерфейс на Windows, Windows не е ограничен
само в него. Всъщност някои от елементите на Windows интер-
фейса емулират други видсве познати устройства, като плъзгачи,
контроля за презъртане, страницы със свойства и ленти с инст-
румента. Windows предоставя на програмистите голям набор ст
възможности, от конто те могат да изберат само най-
подходящите за всяко приложение.
Мишката
Windows предоставя възможност за използване на мишката
почти за всякакво действие, селекция и рисуване. Разбира се, да
кажем, че Windows дава възможност за използване на мишката
е доста меко казано. Истината е, че интерфейсът на Windows е
проектиран за мишка - той дава възможност за използването
на клавиагурата! Въпреки че е възможно дадсно приложение да
игнорира мишката, това ще е в противоречие с освоения прин-
цип на Windows дизайна.
И ко ни и pa сте рн и изображения
Windows насърчава употребата на икони и растерни изобра-
жения (графични изображения). Теорията, псдплатила тази
употреба, се съдържа в старата поговорка “един поглед е равен
на хиляда думи”.
Иконата е малък символ, представляващ някаква операция
или програма. По принцип операпията или програмата могат да
се активират посредством селектиране на иконата Растерното
изображение често се използва за бьрзо и лесно доставяне на
информация на потребителя. Растерните изображения могат да
се използват и за меню елементи.
Менюта и диалогови прозорци
Освен стандартните прозорци, Windows предоставя и няколко
прозореца със специално предназначение. Най-чесго срещаните
такива са менютата и диалоговите прозорци. Менюто е, както
можете да предположите, специален прозорец, съдържат въз-
можности, от конто пот ребителят може да избира Причината за
гслямото иредимство на менютата е, че те могат да се автомати-
зират в значителна степей. Вместо да се начата сами да управля-
вате избсра от менюто на ьашата програма, просто създайте
План на Windows програма 455
стандартно меню - Windows ше се справи с подробностите вмес-
то вас.
Диалоговият прозорец е специален прозорец, позволяващ по-
слсжно взаимодействие с приложение, в сравнение с менюто.
Например дадена програмата може ца използва диалогов прозо-
рец, за да изиска име на файл. Освен с някои изключения, зхо-
дът, който нс се осъществява с меню, сс рсализира чрез диалого-
ви прозорци
ИИИ Как взаимодействат Windows и
ВАШАТА ПРОГРАМА
Когато създавате програма за псвече операционки системи. тя е
тази, която започва взаимодействие с операционната система.
Например всяка DOS програма изисква неща като вход и изход.
Казано с други думи, програмите, написани традиционно, извик-
ват операционната система. Операционната система не извиква
вашата програма. По принцип Windows работа точно обратно.
Windows извиква вашата програма. Пронесът работа така: ваша-
та програма чака, докато получи съобщение от Windows. Сьоб-
шението се предава на програмата чрез специална функция, из-
виквана от Windows. След като съобщението бъде получено,
програмата ви трябва да извърши съответно действие. Въпреки
че програмата може да извика Windows, когато отговаря на съ-
общението, все пак W indows е този, който започва дейността.
Повече от всичко друго, взаимодействието, базирано на съобще-
ния, диктува общата форма на всички Windows програми.
Съществуват много различии видове съобщения, конто Win-
dows може да изпрати на вашата програма. Например всеки дът,
когато шракнете с мишката в никой прозорец па програмата, па
нея ще се изпрати съответното съобщение. Друг вид съобщение
се изпраща, когато даден прозорец от програмата трябва да бъде
пре начертан. А съвсем друг вид съобщение се изпраща, ако пот-
рсбителят натисне клавиш, когато вашата програма е във фокуса
на зхода Не трябва да забравяте едно: що се касае да вашата
програма, съобщенията пристигат случайно. Ето защо Windows
програмите наподобяват програми с управление чрез прекъсва-
ния. Не можете да знаете какво щс е следващото съобщение.
После дно: изпращанитс на вашата програма съобщения се
складират в опашка от съобщения. асоциирана с вашата програ-
ма. Поради това нс може да се изгуби някое съобщение, дори ако
програмата е заета с обработката на друго. Съобщението просто
ще изчака в опашката, докато програмата стане готова за него.
456 С - Пракгически самоучител
Забележка
Windows е многозадачен
Още от самого начало Windows е многозадачна операционна
система. Това означава, че той може да работы едновременно с
две или повече приложения. Всички 32-битови версии на Win-
dows (като Windows NT/2000 и Windows 95/98) използват изпре-
варваща многозадачност. Посредством този подход всяко ак-
тивно приложение получава част от времето на процесора. Това
е времето, в което дадено приложение всъщност се изпълнява.
Когаго времето на приложението изтече, започва изпълнението
на следващото такова. (Предишнэто приложение прсминава б
режим на изчакване на следващата част от своего процесорно
време.) По този начин всяко приложение от системата получава
част от времего на процесора Въпреки че схема!а, разработена
тук, не засяга многозадачните аспект и на Windows, те ще са
важна част от всяко ваше приложение
По-старите, 1б-битови, версии на Windows използват форма
на многозадачносттэ, наречена неизпреварваща многозадач-
ност. При този поход всяко приложение задържа процесора,
докато изри^но не го освободи. Това дава възможност на при-
ложенията да монополизират процесора и ефективно да “изк-
лючват" другите програми. Изпреварващата многозадачност
елиминира този проблем
WIN32 API
По принцип срсдата на W indows се достига чрез интерфейс, ба-
зиран на извиквания и наречен Application Program Interface
(API). API съдържа неколкостотин функции, конто при нужда
програмите могат да извикат. APJ функциите предоставят всич-
ки систсмш! услуги, извършвапи от Windows. Съществува подм-
ножество на API, наречено Graphic Device Interface (GDI), което
e частта от Windows, предоставяща поддръжката на графики, не-
зависимо от устройствата. GDI функциите дават възможност на
Windows приложениям да се изпълняват от различен хардуер.
Програмите, проектирани за 32-битовите версии на Windows,
като Windows 95/98 и Windows NT/2000, използват Win32 API. В
по-голямата си част, Win32 е надмножество на Windows 3.1 API
(Winl6). Всъщност повечето функции имат същитс имена и се
използват по същия начин. Въпреки че са доста подобии, двете
API множества са различии, защото Win32 поддържа 32-битово
адресиране, докато Win 16 поддържа само 16-битово - сегментен
мод ел на паметта. Поради тази разлика някои от по-старите API
функции са разширени да приемат 32-битови аргумента и да
връщат 32-битови стойности. Няколко API функции трябваше да
План на Windows програма 457
се промепят, за да се приспособят към 32-бит овата архитектура.
Освен това към API функциите е добавена поддръжката на изп-
реварвашата многозадачност, нови интерфейсни елементи и дру-
ги възможности.
Тъй като модерните зерсии на Windows пэддържат 32-битово
адресиране, изглежда смислено целите числа да са с дължина 32
бита. Това означава, че типовете int и unsigned са дълги 32 бита,
а не 16, както при Windows 3.1. Ако искате да използвате 16-би-
тово цяло число, то трябва да е деклариране като short. Както ще
видите скоро, Windows предоставя прсносими typedef имена за
тези типове.
ММ КОМПОНЕНТИТЕ НА ЕДИН ПРОЗОРЕЦ
Преди да преминем към специфичпите аспекта от Windows
програмирането, трябва да дефинираме някои важни термини.
Фигура С-1 показва стандартен прозореи с отбелязани отделки
елементи.
Поле за минимизиране
Икона на системното менкз
Рамка
Клиентска облает
Хооизонтална лента
за скролиране
ФИГУРА С-1
Елементите на стандартен прозорец
Всички прозорци притежават рамка, която дефинира грани-
ците на прозореца; те се използват и за сразмерязането му. В
горната част на прозореца се намират няколко елемента. Най-
вляво е иконата на системното меню (наричана още икона на
заглавната лента). Щракването върху тази икона води до показ-
ването на системното меню, Вдясно от системното меню е загла-
вием на прозореца. В най-десния край са полетата за минимизи-
ране, максимизиране и затваряне. Клиентската облает е частта от
прозореца, в която работи програмата. Повечето прозорци имат
458 С - Практически самоучител
също гака вертикални и хоризонтални ленти за скролиране, из-
ползванл за премесгване на информациям в прозореца.
ЦИВ1 Някои основи на Windows
приложенията
Преди да разаботим плана на Windows приложение, трябва да
обсъдим някои ссновни концепции, коиго са общи за всички
Windows програми.
WinMainf)
Всички Windows програми започват изпълпяването си с из-
викване на WinMain(). (Windows програмите нямат функция
main().) WinMain() притежава някои специаляи свойства, кон-
то я от.тичават от станалите функции на приложением. Първо,
тя трябва да се компилира посредством конвенцията за извиква-
ния WIN API. (Както ще видите, използва се и APIENTRY. И
двете означават едно и също.) По подразбиране. функциите в
програмите на С използват концепцията за извиквания на С. Въз-
можно е обаче да компилирате дадена функция така, че тя да из-
ползва друга конвенция; често срещана алтернатива е Pascal
Поради различии технически причини, конвенцията за извиква-
ния, изпелзвана от Windows за извикването на WinMain(), се на-
рича WIN API. Типы на резултата на WinMain() трябва да с int.
Функцията Window
Всички Windows програми трябва да съдържат една специал-
на функция, която не се извиква от програмата, но се извиква от
Windows. Тази функция обикновено се нарича функцията на
прозореиа или процедурами на прозореца. Функцията на прозо-
реца се извиква от Windows, когато трябва да се предаде съоб-
щение на програмата. Функцията на прозореца приема съобще-
нието и яеговите параметри. Всички функции на прозореца
трябва да се декларират с тип на резултата LRESULT CALL-
BACK. Типы LRESULT е typedef, който по време на създава-
нето на настоящего приложение, беше друго име за long int.
Конвенцията за извикване CALLBACK се използва с онези
функции, конто ще се извикват от Windows. В терминологията
на Windows, всяка функция, извиквана от Windows, се нарича
callback функция.
В додълнение към получаването на съобщенията, изпращани
от Windows, функцията на прозореца трябва да започне някакви
действия, предвидени от съобщението. Обикновено тялото на
функцията на прозореца се състои от конструкция switch, евърз-
План на Windows програма 459
ваща определен отговор със съобщение, на което програмата ще
отговаря. Програмата не трябва задължително да отговаря на
всяко съобщение, изпратено от Windows. За сьобщения, от конто
вашата програма нс се иитересува, можете да оставите Windows
да извърши обработка по гюдразбиране. Тъй като съществуват
стотици различии съобщения, генерирани от Windows, най-чес-
то повечето от тях се обработваг от Windows, а не от вагпата
програма.
Всички съобщения са 32-битови стойности. Освен това всич-
ки съобщения са свързани с някаква допълнителпа информация,
изисквана от тях.
Window класовете
Когато започне първоначалното изпълнение на програмата,
тя трябва да дефинира и регистрира клав на прозореца. Когато
регистрирате клас на прозореца, вие указвате на Windows
формата и функцияга на прозореца Регистрирането на класа на
прозореца обаче не предизвиква създаването на самия прозорец.
За същинското създаване на прозореца са необходими Допълни-
телни стъпки,
Цикълът на съобщенията
Както беше споменато по-рано, Windows комуникира с ваша-
та програма, като й изпраща съобщения. Всички Windows при-
ложения трябва да установят цикъл на съобщенията във функ-
цията WinMain(). Този цикъл прочита всяко чакащо съобщение
от опашката и изпраща това съобщение обратно па Windows,
който от своя страна, извиква функцията на прозореца на вашата
програма с параметър самото съобщение. Това може да изглежда
доста сложен начин за предаване на съобщения, но въпреки
всичко така трябва да функционират всички Windows програми.
(Част от причините за тази схема на работа е, за да се върне кон-
трола на Windows, така че “разпределителя” да може да задели
процесорно време, както сметне за уместно, вместо да чака за-
вършването на времето на вашего приложение.)
Типовете данни е Windows
Както скоро ще видите, Windows програмите не използват
широко стандартните типове данни на С, като int или char *.
Вместо това всички топове данни, използвани от Window's, са
дефинирани с typedef във файла WINDOWS.!! и/или свързаяите
с него файлове. Файлът WINDOWS.!! се предоставя с вашия
Windows-съвмсстим компилатор и трябва да се включва във
всички Windows програми. Някои от най-често използваните ти-
460 С - Практически самоучител
лове са HANDLE, HWND, BYTE, WORD, DWORD, HINT,
LONG, BOOL, LPSTR и LPCSTR. HANDLE e 32-битово цяло
число, използване за “манипулятор”. Както те видите, същест-
вуват редица такива типове, но те всички са с размера на HAN-
DLE Манипулаторът е просто стойност, която идентифициоа
някакъв ресурс. Освен това всички типове данни за манипулато-
ри започват с Н. Например HWND е 32-битово цяло число, из-
ползвано като манипулатор за прозорец. BYTE е &-битов беззна-
ков char. WORD е 16-битово беззнаково цяло число. DWORD е
беззнаково дълго цяло числе. UINT е 32-битово беззнаково цяло
число. LONG е друго име за long. BOOL е цяло число; този тип
се използва за индикиране на стойности, конто са или tnie, или false.
LPSTR е указател към низ, a LPCSTR е const указател към низ.
В допълнение към тези основни типове, Windows дефинира
няколко структури. Двете, необходими на програмата-план, са
MSG и WNDCLASSEX. Структурата MSG съдържа Windows съ-
общение, a WNDCLz^SSEX е структура, дефшшраша клас па про-
зорец. Тези структури ще разгледаме по-късно в това приложение.
Windows план
След като вече имаге основнага информация, е време да раз-
работим едно минимално Windows приложение. Както беше
споменато, всички Windows програми имат някои обши цеша.
Тази секция разработва Windows план, който предоставя тези
необходими характеристики. В света на Windows програмиране-
то, чес то се използват планове на приложенията, тъй като това
улеснява разработването, Например кратките програми, показпи
в тази книга, са проектирани за интерфейс с команден ред (като
DOS), при което минималната прох рама е от около 5 реда Ми-
нималната Windows програма обаче е ог около 50 реда.
Минималната Windows програма съдържа две функции:
WinMain() и функция па прозсрсца. Функцията WinMain()
трябва да извършва следните основни стъеки
1. Дефиниране на клас на прозореца.
2. Регистриране на този клас в Windows.
3. Създаване на прозорец от този клас.
4. Изобразяване на този прозорец.
5. Започване на цикъла на съобщенията.
Функцията на прозореца трябва да огговаря на всички умест-
ни съобщения. Тъй като програмата -план не прапи ннщо, освен
да изобрази прозореца. единственото съобщение, на което тя
трябва да отговаря. е това за затваряне на програмата от страна
на потребителя.
План на Windows програма 461
Преди да разгледаме особепостите, вижге следващата прог-
рама, която представлява минимален Windows план. Тя създава
стандартен прозорец, притежаващ заглавие. Прозорецът съдържа
системното меню и поради това е възможно той да бъде мини*
мизиран, максимизиран, преместен, оразмерен и затворен Освен
това той съдържа и стандартните полета за минимизиране, мак-
симнзиране и затваряне.
/* Минимална Windows скица. */
ftinclude <windows.h>
LRESULT CALLBACK WindowFunс(HWND, UINT, WPARAM, LPARAM);
char szWinNamel] = "MyWin"; /* име на клас на прозореца */
int WINAPI WinMain(HINSTANCE hThisInst, HINSTANCE
hPrevInst, LPSTR IpszArgs,
int nWixiMode)
{
HWND hwnd;
MSG msg;
WNDCLASSEX wcl;
/* Дефиниране на клас на прозореца. */
wcl.cbSize = sizeof(WNDCLASSEX); /* размер на
WNDCLASSEX */
wcl.hlnstance = hThisInst; /* манидулагор на тази
инстанция */
wcl.IpszClassName = szWinName; /* име на клас на
прозореца */
wcl.IpfnWndProc = WindowFunc; /* функцията на
прозореца */
wcl.style = 0; /* стил по подразбиране */
wcl.hlcon = Loadlcon(NULL, IDI_APPLICATION);
/* стил на иконата */
wcl.hlconSm = Loadicon(NULL, IDI_WINLGGO);
/* стил на малката икона */
wcl.hCursor = LoadCursor(NULL, IDC_ARROW);
/* стил на курсора */
wcl.IpszMenuName = NULL; /* няма меню */
wcl.cbClsExtra =0; /* няма нужда от */
wcl.cbWndExtra = 0; /* допълнителна информация */
/*• Цзетът на фона на прозореца да бъде бял. * /
wcl .hbrBackgrour.d = (HBRUSH) GetStockOb-
ject(WHITE_BRUSH);
/* Регистриране на класа на прозореца. */
if (IRegisterClassEx(&wcl)) return 0;
462 С - Практически самоучител
/* След като класът на прозореца е регистриран,
може да бъде създаден прозорец. */
hwnd = CreateWindow(
szWinName, /* име на класа на прозореца */
"Windows Skeleton", /* заглавие */
WS_OVERLAPPEDWLNDOW, /* стил на прозореца - normal */
CW_USEDEFAULT, /* X координата - нека Windows да
реши */
CW_USEDEFAULT, /* Y координата - нека Windows да
реши */
CW_USEDEFAULT, /* широчина - нека Windows да реши */
CW USEDEFAULT, /* височина - нека Windows да реши */
HWND DESKTOP, /* няма родителски прозорец */
NULL, /* няма меню */
hThisInst, /* манипулатор на тази инстанция на
програмата */
NULL /* няма допълнителни параметри */
) ;
/* Изобразяване на прозореца. */
ShowWindow(hwnd, nWinMode);
UpdateW i ndow(hwnd);
/* Създаване на цикъла за съсбщенията. */
while(GetMessage(&msg, NULL, 0, 0))
TranslateMessage(&msg); /* пресбразуване на
съобщенията от <
клавиатурата */,
DispatchMessage(&msg); /* връщане на управлението
на Windows */
)
return msg.wParam;
}
/* Тази функция се извиква от Windows и
получава съобщения от опашката.*/
LRESULT CALLBACK WindowFunc(HWND hwnd, UINT message,
WPARAM wParam, LFARAM
IParam)
switch(message) (
case WM DESTROY: /* затёаряне на програмата */
PostQuitMessage(0);
break;
default:
/* Нека Windows да обработка съобщенията, копте
не са спределени в предишната конструкция
switch. */
return DefWindowProc(hwnd, message, wParam,
IParam);
}
return 0;
План на Windows програма 463
Прозорецът, създаден от тази програма, е показан на фигура
С-2. Сега нека ца преминем през програмата стьпка по стьпка.
Първо, всички Windows програми трябва да включват заглав-
ния файл WINDOWS.H. Както споменахме, този файл (заедно с
прилежащите файлове) съдържа прототипите на API функциите,
както и различии типове, макроси и дефиниции, използвани от
Windows. Например типовете данни HWND и WNDCLASSEX
са дефинирани в WINDOWS.H.
Функцията на прозореца, използвана от програмата, се нари-
ча WindowFunc(). Тя е декларирана като callback функция, за-
щото това е функцията, извиквана от Windews за кимуникация с
програмата
Изпълнението на програмата започва с WinMain(), на която
се предават четири параметъра. hThisInst и hPrevJnst са мани-
пулатори. hThisInst се отнася за тскушата инстанция на програ-
мата. Заномнече, че Windows е многозадачна система, така че
може да се изпълняват повече от една инстанции на програмата
едновременно. hPrevlnst винаги ще е NULL.
Прозорецът, създаден от Windows програмата
Фигура С-2
(В програмите за Windows 3.1, hPrevlnst няма да е нула, ако
има други инстанции, конто се изпълняват в момента, но това не
ее отнася за 32-битовитс версии на Windows.) Параметьрът
IpszArgs е указател към низ, съдържащ ар1ументите от команд-
ния ред, зададени при стартирането на програмата.. Параметьрът
nWinMode съдържа стойност, която определя как ще се изобра-
зи прозореца, когато започне изпълнението на програмата
Вьв функцията се сьздаваттри промепливи. Променливата
hwnd ще съдържа манипулатора на прозореца на програмата.
Сгруктурната променлива msg ще пази съобщенията към прозо-
реца, а структурната променлива wcl ще се използва за дефини-
ране на класа на прозореца.
464 С - Практически самоучител
Дефинирано на класа на прозореца
Първите две действия, извършвани от WinMain(), са дефи-
ниране на класа на прсзсреца и неговото регистриране. Класът
на прозореца се дефинира посредством попълване на полет ата,
дефинирани от структурата WNDCLASSEX. Нейните полета са:
UINT cbSize; /* размер на структурата WNDCLASSEX */
UINT style; /* вид на прозореца */
WNDPROC IpfnWndProc; /* адрес на функцията на
прозореца */
int cbCisExtra; /* допълнителна информация за класа */
int cbWndExtra; /* допълнителна информация за
прозореца */
HINSTANCE hlnstance; /* манипулатор на тази инстанция */
EICON hlcon; /* манипулатор на стандартната икона */
HICON hlconSm; /* манипулатор на малката икона */
HCURSOR LCursor; /* манипулатор на курсора на мишката */
HBRUSH hbrBackground; /* цвят на фона */
LPCSTR IpszMenuNane; /* име на главното меню */
LPCSTR IpszClassName; /* име на класа на прозореца */
Както можете да забелсжите от програмата, на cbSize се присво-
ява размера на структурата WNDCLASSEX. На полето
hlnstance се присвоява манипулатора на текущата инстанция,
определена от hThlsInst. Името на класа на прозореца се сочи от
IpszClassName, която в този случай сочи “MyWin”. Адресът на
функцията на прозореца се присвоява на IpfnWndProc. Не се за-
дана стил по подразбиране и не е необходима допълнителна ин-
формация.
Всички Windows приложения трябва да дефинират форма по
подразбиране за курсора на мишката и иконите на приложение-
то. Всяко приложение може да дефинира своя-собствена версия
на тези ресурси или да използва някои от вградените стилове,
както прави този план. Във всеки случай, манипулаторите на те-
зи ресурси трябва да се присвояват на съответните членове на
WNDCLASSEX. За да видите как се прави това, нека да започ-
нем с иконите.
Всяки модерно Windows приложение има асоциирани пене
две икони: една със стандартен размер и една малка. Малката
икона се използва, когато приложението е минимизирано и това
е иконата, използвани в системного меню. Стандартната икона
се изобразява, когато мсстите или копирате приложението на ра-
ботного поле. Обикновено стандартните икони са растерни
изображения с размори 32 на 32, а малките - 16 на 16. Стилът на
всяка икона се зарежда от API функцията Loadlcon(), чиито
прототип е:
HICON Loadlcon(HiNSTANCE hlnst, LPCSTR IpszName)-,
План на Windows програма 465
Тази функция връща манипулатор на икона. В този случай, Must
задаьа манипулатора на модула с иконата, а името на иконата се
определя от IpszNume. За да използвате никоя от вградените ико-
ни, трябва да използвате NULL за първи парамегър, а за втори
да поставите никой от следните макроси:
Макрос
IDIJXPPLICATION
IDI_ASTERISK
IDI-EXCLAMATION
IDI_HAND
IDI_QUESTION
IDI-WINLCGO
Форма
Икона по подразбираие
Икона за информация
Икона удивителен знак
Знак стоп
Икона въпросителен знак
Windows лого
В плана изиолзваме 1D1APPLICAT1ON за стандартна
икона, а IDIJWINLOGO за малка.
За да заредите курсора на мишката, използвайте API функци-
ята LoadCursor(). Нейният прототип е:
HCURSOR LoadCursor(HINSTANCE h/nsf, LPCSTR /psz/Va/ne);
Тази функция връща манипулатор към ресурс на курсор. В този
случай, hlnst задава манипулатора на модула с курсора на миш-
ката, а неговото име се определя от IpszName. За да използвате
някой от вградените курсори, трябва да използвате NULL за
първи парамегър, а за втори някои от изброените тук:
Макрос
I DC_ARROW
IDC-CROSS
IDCJBEAM
IDC_WA!T
Форма
Курсор по подразбиране
Кръст
Вертикална черта
Пясъчен часовник
Фонът на прозореца, създаден от плана, е бял, а манинулато-
рът към тази четка се получава посредством API функцията
GetStockObject(). Четката е ресурс, който оцветява екрана па
базата на предваригелно зададени размер, свят и шарка. Функ-
цията GetStockObject( ) се използва за получаваие на манипула-
тор до редица стандартни обекти, в това число четки, писалки
(конто чертаят липни) и шрифтове. Нейният прототип е:
466 С - П рактически самоучител
HGDlOBJ GetStockObject(int оЬект);
Функцията връща манипулятор на обекта, определен от обект
(Типът HGDIOBJ е GDI манипулатор.) Тук са дадени някои от
вградените и достъпни четки:
Макрос
BLACK_BRUSH
DKGPAY_BRUSH
HOLLOW_BRUSH
WHITE-BRUSH
Тип на фона
Черен
Тъмно сив
Прозрачен
Бял
Можете да използвате тези макроси като параметри на
GetStockObject(), за да получите съответната четка.
След като класът на прозореца е напълно зададен, той се ре-
гистрира в Windows посредством APT функцията RegisterClass-
Ех(), чиито прототип е:
ATOM RegisterC!assEx(CONST WMDCLASS HpWClass)'
Функцията връща стойност, която идснтифицира класа на прозо-
реца ATOM е typedef, означаващ WORD На всеки клас на про-
зорец се дава упикална стойност. IpWClass трябва да е адресът на
структурата WNDCLASSEX.
Създаване на п]эозореи,
След като класът на прозореца е дефиниран и регистриран,
приложениего меже вече наистина да създаде прозорец посредс-
твом API функцията CreateWindow(), чиито прототип е:
HWND CreateWindow(
LPCSTR ipCIassName, /* име на кпаса на прозореца 7
LPCSTR IpWinName, /’ заглавие на прозореца 7
DWORD dwStyle, Г тип на прозореца 7
int X int У, /* координатите на горния ляв ъгъл 7
int Width, int Height, /* размери на пр ;зореца 7
HWND hParent, /* манипулатор на родителския прозорец 7
HMENU hMenu, /* манипулатор на главного меню 7
HINSTANCE hThisInst, /* манипулатор на създатепя 7
LPVOiD ipszAdditionalГ указател към долълнителната
информация 7
);
План на Windows програма 467
Както можете да видите ако разгледате програмата-план, много
от параметрите на CreateWindow() може да са по подразбираие
или NULL. Всъщност най-често параметрите X Y, Width и Height
просто използват макроса CW_USEDEFAULT, което нарежда
на Windows да избере правилните размери и местоположение на
прозореца. Ако прозорецът няма родител, какъвто е случаят с на-
шия план, hParent трябва да е nWND_DESKTOP. (Спокойно
можете да използвате и NULL.) Ако прозорецът не съдържа гла-
вно меню, hMemi трябва ла е NULL. Освен това ако не е необхо-
дима допълнителна информация, което е най-честият случай, и
IpszAdditional е NULL. (Типът LPVOID е typedef за void *.)
Погледнато исторически, LPVOID означава “long pointer to void”
- “дълъг указател към void”.
Останалите четпри параметъра трябва изрично да се зададат
от програмата. Първо, Ip.szClassHame трябва ла сочи името на
класа на прозореца. (Това е даденото от вас име, когато го регис-
трирахтс.) Заглавието на прозореца е низ, сочен от IpszWinName.
Това може да е нулев низ, но обикновено на прозорците се задава
заглавие. Стипът (или типът) на същинският прозорец се задава
от стойността на dwStyle. Макросът WS_OVERL APPEDWINDOW
задава стандартен прозорец, притежаващ системно меню, рамки
и полета за минимизиране, максимизиране и затваряне Това е
най-често използваният стил за прозорец, но можете да създава-
те и такива с ваши-собствени спецификации. За да постигнете
това, просто свържете с OR различпите макроси за стилове, кон-
то искате. Някои от най-често използваните макроси са:
Макрос
WS_OVERLAPPED
WS_MAXIMIZEBOX
WS_MINIMIZEBOX
WS_SYSMENU
WS_HSCROLL
WSJ/SCROLL
Характеристика на прозореца
Застьпен прозорец с рамка
Поле за максимизиране
Поле за минимизиране
Системно меню
Хоризонтална лента за скрслиране
Вертикална лента за скролиране
Параметьрът hThisInst трябва да съдържа манипулятора на те-
кущата инстанция на приложението.
Функцията CreateWindow() връща манипулятора на създа-
дения от нея прозерец или NULL, ако той не може да бъде съз-
даден.
468 С - Практически самоучител
След като прозорсцът е създаден, той все още не е изобразен
на екрана За да направите това, извикайте API функцията
ShowWindow(). Нейният прототип е:
BOOL ShowWindow(HWND hwnd, int nHow);
Маиипулаторът на прозореца за изобразяване се задава от hwnd.
Режимът на визуализиране се определя or nHow. При първото
изо'бразяване на прозореца трябва да предадете nWinMode на
WinMain() като параметьр nHow. Запомнете, че стойностга на
nWinMode определя как ще се изчертае прозорсцът, когато
програмата започне своего изпълнение. Следващите извиквания
могат да изобразяват (или скриват) прозореца, според необходи-
мостта. Ето някои стойности на nHow.
Макрос
SWJ1IDE
SW_MINIMIZE
SW_MAXIMIZE
SW_RESTORE
Ефект
Скриване на прозореца
Минимизиране на прозореца в икоча
Максимизиране на прозореца
Врьщане на прозореца в нормалнте му
размери
Функцията ShowWindow() врыца предишното състояние на
прозореца. Ако прозорецът е бил изобразен, връща число, раз-
лично от нула, в противен случай връща нула,
Въпреки че не е технически необходимо за плана, се извърш-
ва извикване на UpdateWindow(), защото тя е необходима на
практически всяка Windows програма. Тя изрично нарежда на
Windows да изпрати съобщение на приложението, че главният
прозорец трябва да се обнови.
Цикълът на съобщенията
Последната част на WinMain() на плана е цикълът на съоб-
щенията. Този цикъл е част от всички Windows приложения.
Неговата пел е да приема и обработва съобщенията, изпратени
от Windows. Когато одно приложение се изпълнява, то постоян-
но получава съобщения. Тези съобщения се съхраняват в опаш-
ката за съобщения на приложението, докато могат да бъдат про-
четени и обработени Всеки път, когато вашего приложение е го-
тово за следващото съобщение, то трябва да извика API функци-
ята GetMessage( ), чиито прототип е:
BOOL GeiMessage(LPMSG msg. HWND hwnd, UINT min, LKNT max);
План на Windows програма 469
Съобщението ще се получи от структурата, сочена от msg. Всич-
ки Windows съобщения се съдържат в структура от тип MSG,
показана тук:
/* Структура на съобщение */
typedef struct tagMSG
HWND hwnd; /* прозорец, за който е съобщението */
DINT message; /* съобщението */
WPARAM wParam; /* информация, свързана със съобщението */
LPARAM 1Param, /* още информация, свързана със
съобщението */
DWORD time; /* часът на изпращане на съобщението */
POINT pt; /* X, У местоположение на мишката х/
} MSG;
В MSG, манипу.чаторът на прозореца, за който е предназна-
чено съобщението, се съдържа ь hwnd. Всички Win32 иъобще-
ния са 32битоьи цели числа и съобщението се съдържа в mes-
sage. Допълнителната информация, свързана със съобщението,
се предава посредством wParam и IParam Типът WPARAM е
typedef за UINT, a LPARAM е typedef за LONG.
Часът на изпращане на съобщението се задана в милисекунди
в полего time.
Членът pt ще съдържа координатите на мишката, когато е би-
ло изпратено съобщението. Координатите се съдържат в струк-
тура POINT, чиято дефиниция е:
typedef struct tagPOINT {
LONG x, у;
} POINT;
Ако няма съобщения в опашката за съобщения на приложе-
нието, извикването на GetMessage() ще върне управлението об-
ратно на Windows.
Параметърът hwnd на GetMessage() определи прозореца,
който ще получи съобщението. Възможно е, даже е доста често
срещано, приложепието да притежава няколко прозореца, но вис
да искате да получавате съобщенията само за определен прозо-
рец. Ако искате да получавате всички съобщения, насочени към
приложение™, този параметър трябва да е NULL.
Другите два параметъра на GetMessagef) задавай набора от
съобщения, конто ще се лриемат. Обикновено ще искате вашего
приложение да получава всички съобщения. За да постигнете
това, задайте стойност на min и max 0, както прави този план.
470 С - Практически самоучител
GetMessage() връща нула, когато потребителят затвори
програмата, което предизвиква завършване на цикъла на съоб-
щенията, в противен случай връща нещо, различно от нула.
В цикъла на съобщенията се извикват функции. Първата е
API функцията TransIateMessage(). Тази функция преобразува
суровия клавиатурен вход в знакови съобщения. Въпреки че за
повечето приложения не е необходимо, повечето от тях извикват
TranslateMessage(), защото е необходимо пълно интегриране на
клавиатурата в приложната програма.
След като съобщението е прочетено и преобразувано, то се
изпраща обратно на Windows посредством API функцията Di-
patchMessage(). След това Windows задържа съобщението, до-
като то може да се предаде на функцията на прозореца на прог-
рамата.
След като цикълът на съобщенията завърши, функцията
WinMain() приключва като връща на Windows стойността
msg.wParam. Тази стойност съдържа кода при затваряне на
програмата.
Функцията на прозореца
Втората функция на приложението-план е функцията на прозо-
реца. В този случай, тази функция се нарича WindowFunc(), но
името й може да е производно. На функцията на прозореца се
предават като параметри първите четири члена на MSG. За при-
ложението-план, единственият параметър е самого съобщение.
Истинските приложения обаче ще използват и другите парамет-
ри на тази функция.
Функцията на прозореца на нриложението-план отговаря са-
мо на едно съобщение: WMJDESTROY. Това съобщение се из-
праща, когато потребителят затваря програмата. Когато това съ-
общение се получи, програмата трябва да изпълни извикване на
API функцията PostQuitMessage(). Аргументьт на тази функция
е кодът за изход, който се връща в msg.Param в WinMain(). Из-
викването на PostQuitMessage() предизвиква изпращането на
съобщението WM_QUIT на вашего приложение, което довежда
до връщането на false от страна на GetMessage() и затваряието
на програмата.
Всички други съобщения, получени от WindowFunc(), се из-
прашат обратно на Windows чрез извикване на DefWindowProc()
за обработка по подразбиране. Тази стъпка е важна, защото всич-
ки съобшения трябва да се обработят по един или друг начин.
План на Windows програма 471
НЯКОЛКО ДУМИ ЗА ДЕФИНИЦИОННИТЕ
ФАЙЛОВЕ
Може би сте чували или чели за дефнницноннн файпове, За
16-битовите версии на Windows 3.1, програмите трябва да прите-
жават асоцииран дефиниционен файл. Дефиниционния файл е
просто текстов файл, задавапг определена информация и настрой-
ки, изисквани от програмата за Windows 3.1. Поради 32-битовата
архитектура (и други подобрения) на модервите версии на Win-
dows, дефиниционните файлове не са необходими.
ЦЦ КОНВЕНЦИИТЕ ЗА ИМЕНАТА
Преди да приключим с това приложение, трябва да направим
кратък коментар на конвенциите за имената на функциите и про-
менливите. Някои от имената на променливите и параметрите в
приложението-план и техните обяснения вероятно ви се струват
доста странпи. Това с така, защото те следват набор от конвен-
ции за имената, съставени за Windows от Microsoft. За функции-
те, името трябва да се състои от глагол, следван от сыцсствител-
но. Първата буква на глагола и съществителното трябва да са
главни.
За имената на променливите, Microsoft избраха да използват
доста по-сложна система на вграждане на типа на данните в име-
ната. За целта към името на променливата се добавя малка буква
Самото име започва с главна. Префикситс за типовете за показа -
ни в таблица С-1. Честно казано, прилагането на префиксите за
типа е спорно и не винаги ее прилага. Много Windows програ-
мисти използват този метод, но много не го използват. Имате
пълната свобода да използвате която конвенция искате.
ИШ За да научите повече
За вършвашият преглед на Windows програмирането едва загатва
за какво става въпрос. За да пишете програми, конто са полезни,
трябва да научите доста повече за Windows програмирането. За
да научите повече за програмите за Windows 95/98, добре ще е
да прочетете следните книги:
Schildl's Windows 95 Programming in C and C++
Schildt’s Advanced Windows 95 Programming in C ano C++
472 С - Практически самоучи тел
Префикс, Тип данни
Ь Булев (един байт)
с Знак (един байт)
dw long unsigned int
f 16-бигаво побитой э поле (флагове)
fn Функция
h Манипулатор long int
Ip Дълъг указател
n short int
P Указател
pt Дълго цяло число, съдържа щс екранни координати
w short unsigned int
sz Указател към нулево-терминиран низ
Ipsz rgb Дълъг указател към нулево терминиран низ Дълго цялс число, съдържащо RGB стойности за цйяг
Знаковете за префикса на имената на променливите За да научите повече за Windows NT/2000 програмирането, ще намерите книгата Windows NT 4 Programming From the Ground Up доста полезна. Тези книги са написани от Хърбърг Шилдт и са публикузапи на английски език от издатслство Osbome/McGraw- Hill.
Отговори
474 С - Практически самоучител
Глава 1
Упражнения
2. ftinclude <stdio.h>
int wain(void)
{
int num;
num = 1030;
printf("%d is the value of num", num);
return 0;
УПРАЖНЕНИЯ
2. ftinclude <stdio.h>
int main(void)
{
float a, b;
printf("Enter two numbers: ");
scanf("% f", &a) ;
scanf("%f", fib);
printf("Their sum is %f.", a+b);
return 0;
Упражнения
1. ftinclude <stdio.h>
int main(void)
int len, width, height;
printf("Enter length: ");
scanf("%d", &len);
printf("Enter width: ");
scanf("%d", &width);
printf("Enter height: ");
scanf("%d", fiheight);
Отговори 475
printf("Volume is %d.", len * width * height);
return 0;
}
2. ttinclude <stdio.h>
int main(void)
{
printf("Number of seconds in a year: ");
printf(”%f", 60.0 * 60.0 * 24.0 * 365.0);
return 0;
}
Упражнения
2. Да, коментарът може да не съдържа нищо.
3. Да, можете да премахнете временно ред с код, като го
направите на коментар. Това понякога се нарича “комен-
тиране” на ред.
Упражнения
2. ttinclude <stdio.h>
void one(void);
void tvzo(void);
int main(void)
one() ;
two();
return 0;
}
void one(void)
{
printf("The summer soldier, ");
}
void two(void)
(
printf("the sunshine patriot.");
)
3. Компилатсрът ще докладва за грешка. Нрототипът е необ-
ходим, за да може компилаторът да извика правилно
fund ().
476 С - Практически самоучител
1.8
Упражнения
2. ^include <stdio.h>
int convert(void);
int main(void)
{
printf ("%d", convert());
return 0;
)
int convert(void)
int dollars;
printf("Enter number of dollars: ");
scanf("%d", &dollars);
return dollars / 2;
)
3. Няма нишо технически грешно в програмата Функцията
П() обаче връща цяло число, което се присвоява на про-
меплива от тип double. Това може да доведе до ногрешно-
то впечатление, че програмистьт не е разбрал правилно
цел га на функцията П( ).
4. Функция, декларирана с тип на резултата void, не може да
връща стойност.
1.9
Упражнения
I. ttinclude <stdio.h>
void outnum(int num);
int main(void)
{
out num (JO);
return 0;
void outnumdnt num)
{
printf("%d", num);
2. Функцията sqr _it() изисква целочислен ар1умент, но e из-
виква на с дробен.
Отговори 477
Проверка на уменията,
ПРЕДСТАВЕНИ В ГЛАВАТА
1. ttinclude <'stdic.h>
int main(void)
(
flcat weight;
printf("Enter your weight' ");
scanf ("%f", bweight);
printf ("Effective moon weight: %f",
weight * 0.17);
return 0;
2. Коментарът не e терминиран с ♦/.
3. ttinclude <stdio.h>
int o_to_c(int o) ;
int main(void)
I
int ounces;
int cups;
printf("Enter ounces: ");
scanf("%d", bounces);
cups = o_Lo_c(ounces);
printf("%d cups", cups);
return 0;
)
int о to c(int o)
return о / 8;
1
4. char, int, float, double и void
5. Имената на променливите са грешки, защото
а. Не може да използвате тире.
Ь. Не може да използвате знак за долар.
с. Не може да използвате знак +.
d Име не може да започва с цифра.
478 С - Практически самоучител
Глава 2
Преглед на знанията
1. Всички програми трябва да имат функция main(). Това е
първата функция, конто се извиква при стартиране на
програмата.
2. ^include <stdio.h>
int main(void)
{
printf("This is the number %d", 100);
return 0;
}
3. За да включите хедърен файл, използвайте компилаторна-
та директива ^include. Например
^include <stdio.h>
включва STDIO.H.
4. Петте основой типове дакни са char, int, float, double и
void.
5. Грешните имена на променливи са Ь, с и е.
6. Функцията scanf() се използва за въвеждане на информа-
ция от клавиатурата.
7. #include <stdio.h>
int main(vcidl
int i;
printf("Enter a number: ");
scanf("%d", &i);
printf ("%d", i*i);
return 0;
8. Коментарите трябва да са оградени с “/♦” и “*/”. Например
, това е валиден С коментар
/* Това е коментар. *
Отговори 479
9. Функциите връщат стойност посредством процедурата
return.
10 void Myfunc(int count, float balance, char ch);
2.1
Упражнения
I. b, c, d и e са верни.
2. #include <stdio.h>
int main(void)
{
int i;
printf("Enter a number: ");
scanf("%d", &i) ;
if((i%2)==0) printf("Even");
ff((i%2)==l) printf("Odd");
return 0;
}
2.2
Упражнения
I. #include <stdio.h>
int main(void)
{
int a, b, op;
printf("Enter first number: ");
scanf("%d", &a);
printf("Enter second number: ");
scanf("%d", &b);
printf("Enter 0 to add, 1 to multiply: ");
scanf("%d", &op);
if(op==0) printf("%d”, a+b) ;
else printf("%d", a*b);
return 0;
}
2. #include <stdio.h>
int main(void)
int i;
480 С - Практически самоучител
printf("Enter a number: ");
scanf("%d", &i);
if((i%2)==0) printf("Even");
else printf("Odd");
return 0;
Упражнения
1. ttinclude <stdio.h>
int main(void)
{
int a, b, op;
printf("Enter 0 to add, 1 to subtract: "
scanf("%d", &op);
if(op==0) { /* добавяне */
printf("Enter first number: ");
scanf("%d", &a);
printf ("Enter second number: ");
scanf("%d", &b);
printf("%d", a+b);
else { /* изваждане */
printf("Enter first number: ");
scanf("%d", &a);
printf ("Enter second number: ");
scanf("%d", &b);
printf("%d", a-b);
return 0;
2. He, липсва, отварящата фигурна скоба.
Упражнения
1. ttinclude <stdio.h>
int main(void)
int i;
for(i=l; i<101; i=i+l) printf("%d ", i) ;
return 0;
Отговори 481
2. #include <stdio.h>
int train (void)
{
int i;
for(i=17; i<101; i=i+l)
if((i%17)==0) printf("%d ", i);
return 0;
3. ^include <stdio.h>
int main(void)
{
int num, i ;
printf("Enter the number to test: ");
scanf("%d", &num);
for(i«2; i<(num/2)+l; i=i+3)
if ((nuir.ii) ===G) printf ("%d ", i);
return 0;
2.5
Упражнения
I #include <stdio.h>
int main(void)
{
int i;
for(i=l; i<101; i++) printf("%d ", i);
return 0;
}
#include <stdio.h>
int main(void)
int i;
for(i~17; i<101; i++)
if(i%17)==0) printf("%d ", i);
return 0,-
#include <stdic.h>
int main(void)
{
int num, i;
printf("Enter the number to test: ");
scanf("%d", Snum);
for(i=2; i<(num/2+l; i++)
if(num%i)==0) printf("%d ", i) ;
return 0;
}
2. ^include <stdio.n>
int main(void)
{
482 С - Практически самоучител
int а, Ь;
а = 1;
а++;
b = а;
b—;
printf("%d %d", a, b);
return 0;
2.6
Упражнения
1. ^include <stdio.h>
int main(void)
(
int i ;
for(i=l; i<ll; i++)
printf("%d %d %d\n", i, i*i, i*i*i);
return 0;
}
2. #include <stdio.h>
int main(void)
int i, j;
printf("Enter a number: ");
scanf("%d", &i);
for(j=i; j>0; j--) printf("%d\n", j);
printf("\a");
return 0;
Упражнения
1. Цикълът отпечатва числата от 0 до 99.
2. Да.
3. Не, първото е true, а второто е false.
Проверка на уменията,
ПРЕДСТАВЕНИ В ГЛАВАТА
1. #include <stdio.h>
int main(void)
{
int magic; /* магического число */
int guess; /* предположението на потребителя */
int i;
magic = 1325;
guess = 0;
Отговори 483
for(i=0; i<lC && guess!=magic; i++) {
printf("Enter your guess: ");
scanf("%d", &guess);
if(guoss == magic) {
printf("RIGHT!") ;
printf(" %d is the magic number.\n", magic);
)
else {
printf("...Sorry, you’re wrong...");
if(guess > magic)
printf(" Your guess is too high.\n");
else printf (’’ Your guess is too low.\n");
}
)
return 0;
2. ^include <stdio.b>
int main(void)
{
int rooms, len, width, total;
int i;
printf("Number of rooms? ");
scanf("Id", &rooms);
total = 0;
for (i-= rooms; i>0; i—) (
printf("Enter length: ");
scanf("%d", &len);
printf("Enter width: ");
scanf("Id", &width);
total = total + len * width;
)
printf("Total square footage: %d", total);
return 0;
)
3. Операторът за инкрементиране увеличава дадена промен-
лива с единица, а този за декрементиране я намалява с
единица.
4. #include <stdio.h>
int main(void)
{
int answer, count;
int right, wrong;
right = 0;
484 С - Практически самоучител
right = 0;
wrong = 0;
for(count=l; count < 11; count=count+1) {
printf("What is %d + %d? ", count, count);
scanf("%d", ^answer);
if(answer == count+count) {
printf("Right! ');
right++;
}
else {
printf("Sorry, you're wrong. ");
printf('‘The answer is %d. ", count+count);
wrong++;
}
}
printf("You got %d right and %d wrong.", right,
wrong);
return 0;
}
5. ftinclude <stdio.h>
int main(void)
{
int i;
for(i=l; i<=100; i++) {
printf("%d\t", i),
if((i%5)==0) printf(“\n");
}
return 0;
) •
ГЛА BA 3
Преглед на знанията
1. Операторите за сравнение и логическите оператори на С
са: <, >, <=, >=, !=, = =,!, && и II.
2. Блок с код е трупа от логически свьрзани конструкции. За
да направите блок, оградете конструкциите с фигурны
скоби.
3. За да огпечатате нов ред. използвайте знаковия код с об-
ратно наклонена черта.
4. ftinclude <stdio.h>
Отговори 485
int i;
for(i=-100; i<101; 1++) printf ("%d ", i);
return 0;
)
5. #include <stdio.h>
int main(void)
{
int i;
printf("Enter proverb number: ");
scanf("%d", &i);
if(i==l) printf("A bird in the hand..."),
if(i==2) printf("A rolling stone...");
if(i==3) printf("Once burned, twice shy.");
if(i=-4) printf("Early to bed, early to
rise.. . ") ;
if(i==5) printf("A penny saved is a penny
earned."),
return 0;
}
6. count++;
/* или */
++count;
7. В C, true e всяка ненулева стойност. Нулата е false.
3-1
Упражнения
I. ^include <stdio.h>
ttinclude <conio.h>
int main(void)
t
int i;
char ch, smallest;
printf ( "Enter 10 letters.\n");
smallest = ' z' ; /* започване с най-годямото */
for(i=0; i<10; i++) {
ch = getche();
if(ch < smallest) smallest = ch;
)
printf("\nThe smallest character is %c.",
486 С - Практически самоучител
smallest);
return 0;
2. ttinclude <stdio.h>
int main(void)
{
char ch;
for(ch=’A'; ch<=’Z'; ch++)
printf("%d ", ch);
printf("\n");
for(ch='a’; ch<='z'; ch++)
printf("%d ", ch);
return 0;
Кодовете са с разлика 32.
3.2
Упражнения
1. Конструкцията else се отнася за първия if; не е същият
блок, както втория.
2. ttinclude <stdio.h>
int main(void)
{
char ch;
int si, s2;
float radius;
printf("Compute area of Circle, Square, or
Triangle? ");
ch = getchar();
printf("\n");
if(ch==’C’) (
printf ("Enter radius of circle: ");
scanf ("%f", &radius);
printf("Area is: %f", 3.1416*radivs*radius);
else if(ch==*S’) {
printf("Enter length of first side: ");
scanf("%d", &sl);
printf("Enter length of second side: ");
scanf("%d", &s2);
printf("Area is: %d", sl*s2);
Отговори 487
else if(ch=="Г ' ) {
printf ( "Enter length of base: ");
scanf("%d", &sl);
printf("Enter height: ");
scanf("%d", is2);
printf("Area is: %d", (sl*s2)/2);
return 0;
}
3.3
Упражнения
1. #inciude <std±o.h>
int main(void)
{
float dist, speed;
int num;
printf("Enter number of drive time confutations: ");
scanf("%d", &num);
for(; num; num-- ) {
printf("\nEnter distance. ");
scanf("%f", &dist);
printf("Enter average speed: ");
scanf("%f", &speed);
printf("Drive time is %f\n", dist/speed);
return 0;
}
2. ttinclude <stdio.h>
int main(void)
{
int i ;
printf("Enter a number: ");
scanf("%d", &i);
for( ; i; i--) ;
printf("\a"),
return 0;
}
488 С - Практически самоучител
3. ftinclude <stdio.h>
int main(void)
{
int i;
for(i=l; i<1001; i=i+i) printf("%d ", i);
return 0;
3.4
Упражнения
1. ftinclude <stdio.h>
int main(void)
{
float dist, speed;
int num;
printf("Enter number of drive time computations: ");
scanf("%d", &num);
while(num) {
printf("\nEnter distance: ");
scanf("%f", &dist);
printf("Enter average speed: ");
scanf ("%f", &speed);
printf("Drive time is %f\n", dist/speed);
num— ;
return 0;
}
2. ftinclude <stdio.h>
ftinclude <conio.h>
int main(void)
{
char ch;
printf("Enter your encoded message.\n");
ch = getche ();
while(ch!=*\r') {
printf("%c", ch-1);
ch = getche();
}
return 0;
}
Отгоеори 489
3.5
Упражнения
1, ((include <stdio.h>
int main(void)
float gallons;
pr1ntf("\nEnter gallons: ");
scanf ("%f", &gallons);
do (
printf("Liters: %f\n", gallons*3.7854);
printf("Enter gallons or 0 to quit. ");
scanf("%f", ^gallons);
} while(gallons!=0;;
return 0;
2. #include <stdio.h>
int main(void)
{
int choice;
printf("Mailing list menu;\n\n");
printf(" 1. Enter addresses\n"),
printf (" 2. Delete addresses\n")
printf(" 3. Search the list\n");
printf (" 4 . Print the list\n");
printf(" 5. Quit\n");
do {
printf("Enter the number of the choice (1-5); ") ;
scanf("%d", &choice);
} while(choice<l || choice>5);
return 0;
}
3.6
Упражнения
1. /* Тази програма открива простите числа от
2 до 1000. */
^include <stdio.h>
int main(void)
I
int i, j, prime;
490 С - Практически самоучител
for(i=2; i<1000; i++) {
prime = 1;
for(j=2; j <= i/2; j++)
if (! (i%j)) prime=0;
if (prime) printf("%d is prime. \n", i) ;
return 0;
2. ftinclude <stdio.h>
ftinclude <conio.h>
int main(void)
{
int i;
char ch;
for(i=0; i<10; i++) (
printf("\nEnter a letter: ");
ch = qetche();
printf("Xn");
for( ; ch; ch—) printf("%c”,
return 0;
Упражнения
2. ftinclude <stdio.h>
ftinclude <conio.h>
int ma in(void)
float i;
char ch;
printf("Tip Computer\n");
for(i=1.0; iClOl.O; i=i+1.0) {
printf("%f %f %f\n", i, i+i*.l, i+i*.15,
i+i*.2);
printf("More? (Y/N) ");
ch = getche();
printf("\n");
if(ch=»’N') break;
return 0;
Отговори 491
3.8
Упражнения
I. #include <stdio.h>
int main(void)
{
int i;
for(i=l; i<101; i++) t
if(!(i%2)) continue;
printf("%d ", i);
}
return 0;
}
3.9
Упражнения
I. He може да използвате дробни стойности за управление на
конструкция switch.
2. #include <stdio.h>
#include <conio.h>
int main(void)
{
char ch;
int digit, punc, letter;
printf("Enter characters, ENTER to stop.\n");
digit = 0;
punc = 0;
letter = 0;
do {
ch = getche();
switch(ch) {
case '11:
case '2 ' :
case '3':
case '4 ' :
case '5 1 :
case '6 ' :
case ' 71 :
case '8' :
case ' 9 ' :
case 'O':
digit++;
break;
case '. ' :
case ',':
case '?' :
492 С - Практически самоучител
сазе ' 1 ' :
case * : ' :
сазе '; ' :
рипст+;
break;
default:
letter++;
}
} while(ch!='\r1);
printf (*'\nDigits: %d\n", digit);
printf("Punctuation: %d\n", punc);
printf("Letters: %d\n", letter);
return 0;
Упражнения
1. #include <stdio.h>
int nia in (void)
int i ;
i = 1;
j тир i abel:
if(i>=ll) goto done_label;
printf ("%d ", i);
i++;
goto jump_label;
done_iaoel: printf("Done");
return 0;
)
Проверка на уменията,
ПРЕДСТАВЕНИ В ГЛАВАТА
1. ttinclude <stdio.h>
ttinclude <conio.h>
int main(void)
{
char ch;
printf("Enter lowercase letters. ");
printf("(Press ENTER to Quit.)\n");
do {
ch = getche();
if(ch!='\r') printf("%c", ch-32);
Отговори 493
} while(ch!='\r');
return G;
2. (I include <stdio.h>
int main(void)
{
int i;
printf("Enter a number: ");
scanf("%d", &i);
if(!i) printf("zero");
else if (i<0) printf("negative");
else printf("positive") ;
return 0;
}
3. Цикълът for e валиден. С позволяла всеки от неговите из-
рази да е нразен.
4. for ( ; ; ) ...
5. /* for */
for(i=l; i<ll; i++) printf("%d ", i);
/* de */
i = 1;
do {
printf("%d ",i);
i++;
} while(i<ll) ;
/* while */
i=l;
while (i<ll) {
printf("%d ", i) ;
}
6. Конструкцията break предизвиква незабавно излизане от
цикъла.
7. Да.
8. Нс, на стикета липсва двоеточието.
494 С - Практически самоучител
Проверка на натрупаните знания
1. tfinclude <stdio.h>
#include <conio.h>
int main (void)
(
char ch;
printf ("Enter charecters (q to quit) : \n") ;
do {
ch = getche();
switch(ch) {
case ’\t’ : printf("tab\n");
break;
case '\b' : printf("backspase\n");
break;
case '\r' : printf("EnterXn");
}
} writen(ch 1 = ’q');
return 0;
J
2. #include <stdio.h>
int main(void)
int i, j, k;
for(k=0; k<10; k++-) { /* използване на оператора
за инкрементиране */
printf ("Enter first nuinber: ");
scanf("%d", &i);
printf("Enter second number: ");
scanf("%d”, &j);
if (j) printf ("%d\n", i/j); /* опростяеане на
условието */
else printf("Cannot divide by zero.Xn");
/* use else */
)
return 0;
I
Глава 4
Преглед на знанията
1. int i;
for(i=l; i<ll; i++) printf(”%d ");
i = 1;
do {
printf("%d ", i);
i++;
} while(i<ll);
i = 1;
while(i<ll){
printf("%d ", i);
Отгспори 49S
2. switch (ch) {
case 'L': load ();
break;
case 'S': save();
break;
case ' E ’ : enter();
break;
case 'D': di splay();
break;
case 'O': quit();
break;
)
3. #include <;stdio.h>
ttinclude <conio.h>
int main(void)
{
char ch;
do (
ch = getche();
} while(ch1='\r');
return 0;
}
4. Конструкцията break предизвикза незабавно излизане от
цикъла, който я сьдържа Освен тсва тя затваря поредица
от конструкции в switch.
5. Конструкцията continue предизвиква премикаване към
следващата итерация на цикъла.
6. ttinclude <stdio.h>
int main(void)
(
int i;
float feet, meters, ounces, pounds;
do {
printf ("Convert\n\n");
printf("1. feet to meters\n");
printf("2. meters to feetin");
printf("3. ounces to pounds\n");
printf("4. pounds to cunces\n");
printf("5. Quit\n\n");
do {
printf("Enter the number of your choics: ");
scanf("%d", si);
} while(i<0 || i>5);
switch (г) |
case 1:
printf("Enter feet: ");
scanf("%f", &feet);
printf("Meters: %f\n", feet / 3.28);
break;
case 2:
496 С - Практически самоучител
printf("Enter meters: ");
scanfimecersj;
printf("Feet: %f\n", meters * 3.28);
break;
case 3:
printf("Enter ounces: ");
scanf("%f", bounces);
printf("Pounds: %f\n", ounces / 16);
break;
case 4:
printf("Enter pounds: ");
scanf("%f", bpounds);
printf("ounces: %f\n", pounds * 16);
break;
} while (i!=5);
return 0;
Упражнения
1. unsigned short int loc counter;
2. ^include <stdio.h>
int main'void)
{
unsigned leng int distance,
printf("Enter distance; ");
scanf ("%lu", &distance) ;,
printf("%ld seconds", distance / 186000);
return 0;
3. Конструкцията може да се напише посредством краткия
запис на С така:
short i;
УПРАЖНЕНИЯ
1. Локалните промеиливи са известии само във функцията, в
която са декларирани. Глобалните промеиливи са известии
и достъпни във всички функции. Освен това локалните
промеиливи се създават при влизане във функцията и се
Отгсвори 497
унищожават при нейното приключване. Поради това те не
могат да запазват своите стойности между отделите из-
виквания на функцията. От друга страна глобалните про-
менливи съществуват през целия живот на програмата и
запазват своите стойности.
2. Това е необобщената версия.
ftinclude <stdio.h>
void soundspeed(void);
double distance;
int main(void)
{
printf("Enter distance in feet: ");
scanf("%lf", &distance);
scundspeed();
return 0;
void soundspeed(void)
printf("Travel time: %f", distance / 1129);
}
Това e параметризираната версия.
ftinclude <stdio.h>
void soundspeed(double distance);
int main(void)
{
double distance;
printf("Enter distance in feet: "),
scanf("%lf", &distance);
soundspeed(di stance);
renurn 0;
)
void soundspeed(double distance)
(
printf("Travel time: %f", distance / 1129);
)
498 С - Практически самоучител
Упражнения
1. За да накарате дадена константа да се третира изрично ка-
то float, добавете след стойностга й F.
2. #include <stdio.h>
int main(void)
{
long int i;
printf("Enter a number: ") ;
scanf&i);
printf ("%ld", i);
return 0;
3, #include <stdio.h>
int main(void)
{
printf ("%s %s %s", "I", "like", "C");
return G;
Упражнения
1. ttinclude <stdio.h>
int main(void)
int i«100;
for( ; i>0; i--) printf("%d ", i);
return 0;
2. He He можете да инициализирате една глобална промен-
лива посредством друга такава.
3. Да, Локалните променливи могат да се инициализират
посредством изрази, конто са валидни по време на иници-
ализацията.
Упражнения
I. Целият израз е float.
2. Подизразът е unsigned long.
Отговори 499
4.6
Упражнения
1 Програмата изписва 10
2. Програмата изписва 3.0,
Упражнение
1. #include <stdic.h>
int main(void)
{
float f;
for(f=1.0; (int) f<=9; f=f + 0.1)
printf (' %f ", f) ;
return C;
}
2. Ето поправената конструкция
x = (int)123.23 % 3; /* сега e правилно */
Проверка на уменията,
ПРЕДСТАВЕНИ В ГЛАВАТА
1. Модификаторите на типове данни са-
unsigned
long
short
signed
Те се използват за модифициране на базовия тип. така че
да мсжете да добиете промеиливи, конто най-добре пасват
на нуждите на програмата.
2. За да дефинирате unsigned константа, поставете U след
стойността й. За да дефинирате long константа, поставете
L след стойността и. За да дефинирате long double конс-
танта, поставете L след стойността й.
3. float balance = 0.0;
4. Когато компилаторът на С изчислява даден израз, той ав-
томатично конвертира всички char и short промеиливи в
int
500 С - Практически самоучител
5. signed целите числа използват най-старшия бит като флаг
за знак. Когато битът е вдигнат, числото е отрицателно, а
когато е свален - е положително. unsigned целите числа
използват всички битове като част от числото и могат да
представляват само полсжителни стойности.
6. Глобалните променливи поддържат своите стойности през
целил живот на програмата. Освен това те са достьпни от
всички функции.
ftinclude <stdio.h>
int series(void);
int nun - 21;
int main(void)
{
int i;
for(i-0; i<10; i + + )
printf("%d ", series());
return 0;
}
int series(void)
{
num = (num*1468) % 4 6?;
return num;
8. Преобразувзнето на тип променя временно типа на про-
менлива. Например тук int i временно се преобразува в
double:
(double) i
МММЛ Проверка на натрупаните умения
I. Фрагменты не е валиден, защото в С, както ‘А’, така и 65
са една и също нсщо, а не може да има две еднакви case
константи.
2. Причината, поради която врьщаните or getchar() и
gctche() стойности могат да се присвэяват на char е, че С
автоматично премахва старшия байт.
3. Не. Защото i е цяло число със знак и неговата максимална
стойност е 32 767. Затова то никога няма да превиши
33 000.
Отговори 501
Глава 5
Преглед на знанията
I. Локалните промеиливи са известии само във функцията, в
която са декларирани. Глобалните промеиливи са известии
и досгьпни от всички функции. Освен това локалните
промеиливи се създават при влизане във функцията и се
унишожават при нейното приключване. Поради това ге не
могат да запазват своите стойности между отделяйте из-
виквания на функцията. От друга страна глобалните про-
менливи съществуват през целия живот на програмата и
запазват своите стойности.
2. Компилаторът на С ще присвой следните типове:
a. int
b. int
с. double
d. long
е. long
3. #include <stdio.h>
int ma in(void)
{
1ong 1;
short s;
double d;
printf("Enter a long value: ");
scanf ("%ld", Ы) ;
printf("Enter a short value: ");
scanf ("%hd", &s);
printf("Enter a double value: ");
scanf("%lf", &d);
printf("%ld\n", 1);
printf("%hd\n", s);
printf("%f\n", d) ;
return 0;
502 С - Практически самоучител
4. Преобразуването на тип променя временно типа на про- менлива. 5. Конструкцията else се асоциира с конструкцията if(j), об- ратно на това, което показва (нскоректната) стълбица. 6. Когато i е 1, а е 2. Когато i е 4, а е 5.
Упражнения 1 Масивът count се нрепълва. Той е с големина само 10 еле- мента, а програмата изисква такъв с дължина 100. 2. #include <stdio.h> int main(void) int i[10], j, k, match; printf("Enter 10 numbers:\n"); for(j=0; j<10; j++) scanf(’'%d”, &i[jl); /* проверка за съяпадения */ for(j=0; j<10; j++) { match = i[j1; for(k=j+l; k<10; k-t-4) i f(match==i[k]) printf("%d is duplicaced\n", match); return 0; ) 3. ^include <stdio.h> int main(void) { float item[100], t; int a, b; int count; /* прочитане на числата */ printf("How many numbers? "); scanf("%d", &count); for(a=0; a<count; a+4) scanf("%f", &item[a]); /* сортиране по метода на мехурчето */ for(a=l; account; ++a) for(b=count-l; b>=a; —b) { /* сравняване на съседните елементи */ if(item[b-l] > iremfbl) { /* размяяа на елементи */ t = item[b-l];
Отговори 503
item[b-l] = itemfb];
itemfb] - t;
}
}
/* изобразяване на сортирания списък */
for(a=Q; a<count; a++) printf("%f ", item[a]);
return 0;
}
5.2
Упражнения
1. /'* Обръщане на низ. */
#include <stdio.h>
^include <string.h>
int main(void)
{
char str[80];
int i;
printf("Enter a string: ’);
gets(str);
for(i = strlen(str)-1; i>=0; i--)
printf("%c", str[il);
return 0;
}
2. Низът str не e достатъчно дълъг, за да съдържа “this is а
test”.
3. #incluae <stdio.h>
ftinclude <string.h>
int main(void)
{
char higstr[1000] = str[80];
for ( ; ; ) {
printf("Enter a string: ");
gets(str);
if(1 st romp(str, "quit")) break;
streat (str, "\n'');
./* предотвратяване на препълване *./
if(strlen(bigstr)+$trlen(str) >= 1000) break;
strcat(bigstr, str);
}
printf(bigstr);
return 0;
)
5.3
504 С - Практически самоучител
Упражнения
1. #include <stdio.h>
int main(void)
{
int three_d[3][3][3];
int i, j, k, x;
x = 1;
for(i=0; i<3; i++)
for(j=0; j<3; j++)
for(k=0; k<3; k++) {
three_d[i][j][k] = x;
x++;
printf("%d ", three_d[i][j][k]);
)
return.0;
2. #include <stdio.h>
int main(void)
(
int three_d[3][3][3];
int i, j, k, sum;
for(i=0; i<3; i++)
for(j=0; j<3; j++)
for(k=0; k<3; k++) {
three_d[i][j][k] = (i+1) * (j+1) * (k+1);
printf("%d ", three_d[i][j][k]);
}
/* сумиране на всички елементи */
sum = 0;
for(i=0; i<3; i++)
for(j=0; j<3; j++)
for(k=0; k<3; k++)
sum = sum + three_d[i][j][k];
printf("\n%d", sum);
return 0;
5.4
Упражнения
1. He. Списъкът трябва да e ограден от фигурни скоби.
2. Не. Масивът name е с дължина само 4 знака. Опитьт за
извикване на strcpy() ще предизвика препълване.
Отговори 505
3. ttinclude <stdio.h>
int main(void)
(
int cube[][3] = {
1, 1, 1,
2, 4, 8,
3, 9, 27,
4, 16, 64,
5, 25, 125,
6, 36, 216,
7, 49, 343,
8, 64, 512,
9, 81, 729,
10, 100, 1000
int num, i;
printf("Enter cube: ") ;
scanf("%d", inum);
for(i=0; i<10; i++)
if(cube[i][2]==num) {
printf("Root: %d\n", cube[i][0]);
printf("Square: %d", cube[i][l]);
break;
if(i==10) printf("Cube not found.\n");
return 0;
5.5
Упражнения
1. ttinclude <stdio.h>
ttinclude <conio.h>
int main(void)
char digits[10][10] = {
"zero", "one", "two", "three",
"four", "five", "six", "seven",
"eight", "nine"
} r
char num;
printf("Enter number: ");
num = getche();
printf("\n");
num
num
•O';
506 С - Практически самоучител
if (пшг>=0 &S num<10) printf (”%з", digits[num]);
return О,
}
Проверка на уменията,
ПРЕДСТАВЕНИ В ГЛАВАТА
1. Масив е списък от еднотипни данни.
2. Конструкцията няма да генерира грешка, защото С не пре-
доставя проверка на границите при операции с масиви, но
е грешна, защото count ще се препълпи.
3. ftinclude <stdio.h>
int main(vcid)
int stats [2.0], i, j;
int mode, count, oldcount, oldmode;
printf("Enter 20 numbers: \n");
for(i=0; i<20; i++) scanf (' %d", &stats[i]);
oldcount = 0;
/* откриеане на режима */
for(i=0; i<20; i++) [
mode = stats [i];
count = 1;
/* преброяпане на появяваничта на тази
стойност */
for(j=i+l; j<20; j++)
if(mode==stats[j ] ) count++;
/* ако count e по-го.пямо от oldcount,
използване на нсв режим */
if (count>oldcount.) {
oldmode = mode;
oldcount = count;
}
printf("The mode is %d\n", oldmode);
return 0;
4 int itemsf] = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
5. ([include <stdio.h>
ftinclude <string.h>
int main(void)
Отговори 507
char str[80];
do {
printf("Enter a string: ");
gets(str);
) while(strcmp("quit", str));
return 0;
6. /* Компютъризиран речник. */
ttinclude <stdio.h>
ttinclude <string.h>
int main(void)
{
char diet[][2][40] = {
"house", "a place of dwelling",
"car", "a vehicle",
"computer", "a thinking machine",
"program", "a sequence of instructions",
tf n ft ft
};
char word[80];
int i;
printf("Enter word: ");
gets(word);
/* търсене на думата */
i = 0;
/* търсене до срещане на null низ */
while(strcmp(diet[i][0], "" )) {
if(!strcmp(word, dict[i][0])) (
printf("meaning: %s", dict[i][l]);
break;
i++;
if(!strcmp(diet[i][0], ""))
printf("Not in dictionary\n");
return 0;
Проверка на натрупаните умения
1. ttinclude <stdio.h>
ttinclude <string.h>
int main(void)
508 С - Практически самоучител
char str [80],
int i;
printf("Enter a string: ”);
gets (str);
/* завършнане на низа ако е необходимо */
for(i-strlen(str); i<79; i++)
strca t (str, ".");
printf(str);
return 0:
2. /* Програма за просто кодиране. */
ttincluae <stdio.h>
[[include <string.h>
int main(void)
char str[80];
int i, j;
printf ("Enter message: ");
gets (str);
/* кодиране */
1=0; j = strlen(str) - 1;
while(i<=j) (
if(icj) printf("%c%c", str[i], str[j]);
else printf ("%c", str[i]);
i++; j—;
}
return 0;
3. #inciude <stdio.h>
#include <string.h>
int main(void)
char str[80];
int spaces, periods, commas;
int i;
printf ("Enter a string: ’’) ;
gets (str);
spaces = 0;
commas = 0;
periods = 0;
for(i=0; i<strlen(str) ; i++)
Отговори 509
switch (str [ i] ) {
case ’.': p-eriods + + ;
break;
case 1cornasH;
break;
case ' ’: spaces++;
printf("spaces: %d\n", spaces);
printf("commas: %d\n", commas);
printf ("periods: %d’', periods);
return 0;
}
4. Функцията getchar() връща знак, а нс низ. Попади това не
може да се използва по показания начин. Трябва да изпол-
звате gets( ), за да нрочетете низ от клавиатурата.
5. /* Опростена игра на бесеница */
ftinclude <stdio.h>
ftinclude <string.h>
int main(void)
(
char wcrd[] = "concatenation";
char temp[] = "--------------";
char ch;
int i, count;
count = 0; /* брой на отгатванията */
do {
printf("%s\n", temp);
printf("Enter your guess: ");
ch = getchar();
printf("\n");
/* проверка дали буквата съвлада с някои от
думата */
for(i=0; i<str]еп(word); i++)
if (ch==wordlij) tempfi] = ch;
count+4;
} while(strcmp(temp, word));
printf("%s\n", temp);
printf("You guessed the word and used %d
guesses", count);
return 0;
}
510 С - Практически самоучител
Глава 6
Преглед на знанията
I. ttinclude <stdio.h>
int main(void)
{
int num[10], i, even, odd;
printf("Enter 10 integers: ");
for (i=0; i<10; i++) scanf("%d", £num[i]);
even = 0; odd = 0;
for(i=0; i< 10; i++) {
if(num[i]%2) odd = odd + num[i];
else even = even + num[i];
)
printf("Sum of even numbers: %d\n", even);
printf("Sum of odd numbers: %d", odd);
return 0;
)
2. ttinclude <stdio.h>
ttinclude <string.h>
int main(void)
(
char pw[80];
int i;
for(i=0; i<3; i++d {
printf("Password: ");
gets(pw);
if(!strcmp("Tristan", pw)) break;
if(i==3) printf("Access Denied");
else printf("Log-on Successful");
return 0;
}
3. Масивът name нс e достатъчно гопям, за да съдържа прис-
воявания му низ.
4. Нулев низ е низ, който съдържа само нулевия знак.
5. Функцията strcpy() копира съдържанието на един низ в
друг. Функцията strcmp() сравнява два низа и връща по-
Отговори 511
малко от пула, ако първият низ с по-малък от втория, нула,
ако двата низа съвпадат, и по-голямо от нула, ако първият
е по-голям от втория.
6. /* Прост комшотърен телефонен указател. */
#include
#include
<sLdio.h>
<string.h>
char phone[][2][40] = {
''Fred'1, "555-1010",
"Barney", "555-1234",
"Ralph", "555-2347",
"Tom", "555-S3 96",
II II II II
int main(void)
{
char name[80];
int i;
printf("Name? ");
gets(name);
for(i=0; phone[i] [0] [0]; i++)
if(1stremp(name, phone[i][0]))
printf("number: %s", phone[i] [1] ) ;
return 0;
}
Упражнения
1. Указател с променлива, съдържаща адреса на друга про-
менлива.
2. Указателите операции са * и &. Операторы * връща
стойността на обекта, сочен от указателя. Операторы &
връща адреса на следващата го променлива.
3. Базовият тип на указателя е важен, защото цялата указа-
телна аритметика е евързана с него.
4. #include <stdio.h>
int main(void)
{
int i, *p;
p = &i;
for(i=0; i<10; i++) printf("%d
*p) ;
512 С - Практически самоучител
return 0;
}
6.2
Упражнения
1. Не можете да у множа вате указател.
2. Не. можете само да прибавите или изваждате целочислени
стойности.
3. 108
/
6.3
Упражнения
1. Нс, не можете да промсняте стойностга на указател, която
с генерирана посредством име на масив без индекс.
2. 8
3. ttinclude <stdio.h>
int main(void)
{
char str[80] , *p;
printf("Enter a string: ");
gets (str) ;
p = str;
/* Докато не се стигне до интервал пли
края на низа, р се увеличаза, така че
да сочи следващия знак.
*/
while(*p && *р!=' ') р++;
printf(р);
return 0;
}
6.4
Упражнения
1. ttinclude <stdio.h>
int main(void)
char *one = "one";
char ‘two = "two";
char *three = "three";
Отговори 513
printf("%s %s %s\n", one, two, three);
printf("%s %s %s\n", one, three, two);
printf("%s %s %s\n", two, one, three);
printf ("%s %s %s\n", two, three, one);
printf("%s %s %s\n", three, one, two);
printf("%s %s %s\n", three, two, one);
return 0;
Упражнения
I. hnclude <stdio.h>
ftinclude <string.h>
int main(void)
char *p[3] = {
"yes", "no",
"maybe - rephrase the question"
} ;
char str[80];
printf("Enter your question: \n") ;
gets (str);
printf (pfstrlen(str) % 3] ) ;
return 0;
Упражнения
1. ftinclude <stdio.h>
int main(void)
{
int i, *p, **mp;
P = & i ;
rap = &p;
**mp = 10;
printf("%p %p %p", &i, p, mp);
return 0;
514 С - Практически самоучител
6.7
Упражнения
1. #include <stdio.h>
#include <string.h>
void mystrcat(char *to, char ‘from);
int main(void)
char str[90];
strcpy(str, "first part");
mystrcat(str, " second part");
printf(str);
return 0;
void mystrcat(char *to, char *from)
{
/* намиране на края на to */
while(*to) to++;
/* конкатиниране на низа */
while(*from) *to++ = *from++;
/* добавяне на null терминатора*/
*to = ’\0’;
2. ^include <stdio.h>
void f(int *p);
int main(void)
int i ;
f(&i) ;
printf("%d", i);
return 0;
void f(int *p)
(
*p = -1;
Отговори 515
Проверка на уменията,
ПРЕДСТАВЕНИ В ГЛАВАТА
I. double *р;
2. ttinclude <stdio.h>
int main(void)
int i, *p;
p = &i;
*p = 100;
printf("%d", i);
return 0;
3. He. Указателях p не e инициализиран да сочи валидна част
от паметта, която може да съдържа низ.
4. Указателите и масивите всъщност са два погледа към едно
и също нещо. Те са практически взаимозаменяеми.
5. str(2]
*(str+2)
*(р+2)
6. 108
Проверка на натрупаните умения
1. Указателите често са по-удобни от индексираните масиви,
а в някои случаи може да са по-бързи.
2. ttinclude <stdio.h>
int main(void)
{
char str[30] , *p;
int spaces;
printf("Enter a string: ");
gets(str);
spaces — 0;
p = str;
while(*p) {
if(*p~’ ') spaces++;
516 С - Практически самоучител
р++;
printf("Number of spaces: %d", spaces);
return 0;
3. *((int *)count + (44 * 10) + 8) = 99;
Глава 7
Преглед на знанията
1. Фрагментьт присвоява индиректно стойността 19 на i пос-
редством указател.
2. Име на масив без индекс генерира указател към началото
на масива.
3. Да, фрагментьт е верен. Той работи, защото компилаторът
създава нов елемент от низовата таблица за низа “this is а
string” и насочва р към неговото начало.
4. #include <stdio.h>
int main(void)
{
double d, *p;
p = &d;
*p = 100.99;
printf (,h%f", d);
return 0;
}
5. ttinclude <stdio.h>
int mystrlen(char *p) ;
int main(void)
{
char str[80];
printf("Enter a string: ");
gets (str) ;
printf("Length is %d", mystrlen(str));
Отговори 517
return 0;
int mystrlen(char *p)
int i;
i = 0;
while(*p) {
i++;
p++;
}
return i;
6. Фрагменты e верен. Той изобразлва С.
Упражнения
I. ttindude <stdio.h>
double avg();
int main(void)
(
printf ("%f", avg());
return 0;
)
aouole avg()
int i;
double sum, num;
sum = 0.0;
for(i=0; i<10; i++) {
printf("Enter next number: ");
scanf ("%lf“, ir.um) ;
sum = sum + num;
)
return sum / 10.0;
}
2. ((include <stdio.h>
double avg(void);
int main(void)
{
printf("%f", avg());
return 0;
51Р С - Практически самоучител
double avg(void)
int i;
double sum, num;
sum = 0.0;
for(i=0; i<10; i++) {
printf ("Enter next number: ") ;
scanf("%lf", Snum);
sum = sum + num;
return sum I 10.0;
}
3. Програмата e вярна. Тя ще e по-добра обаче, ако се изпол^
зва иълен прототип на функцията myfunc().
4. double *Purge(void);
Упражнения
1. ttinclude <stdio.h>
int fact (int i);
int main(void) .
(
printf ("5 factorial is %d", fact(S));
return 0;
}
int fact(int i)
{
if(i==l) return 1;
else return i * fact(i-l);
}
2. Функцията ще извиква себе си многократно, докато блс-
кира програмата, защото няма условие, коего да спре ре-
ку реи ята
3. ttinclude <stdio.h>
void display (char *p);
int main(void)
display ("this is a test");
return 0;
Отговори 519
void di splay(char *p)
{
if(*p) {
print f("%c", *p);
display(p*l);
7.3
Упражнения
1. He. Функцията myfunc() се извиква с указател към първия
параметьр, вместо със самият параметьр.
2. ttinclude <stdio.h>
void pronrot(char *msg, char *str);
int main(void)
{
char str[80];
prompt("Enter a string: ", str);
printf("Your string is: %s", str);
return 0;
}
void prompt(char *msg, char *p)
(
printf(msg);
gets(p);
}
3. При извикването по стойност, на функцията се предана
стойностга на аргумента. При извикването по адрес, на
функцията се предава адресът на аргумента.
Упражнения
1. ttinclude <stdio.h>
ttinclude <string.h>
ttinclude <stdlib.h>
int main(int argc, char *argv[])
{
int i ;
if(argc!=3) {
printf ("You must, specify two arguments.");
exi t(1);
520 С - Практически самсучител
i = stremp(argv[lj , argv(2]);
if(i < 0) printf("%s > %s", argv[2], argv[l]);
else if(i > 0) printf("%s > %s", argvfl],
argv[2]);
else printf("They are the same"),
return 0;
2. ttinclude <stdio-h>
#inciuae <string.h>
#include <stdlib.h>
int main(int argc, char *arqvf])
if(argc!=3) {
printf("You must specify two numbers.");
exit (1) ;
}
printf("%f", atof (argv[1]) + atof(argv[2])),
return 0;
}
3. # Include <stdio.h>
#include <strrng.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
if(argc!=4) {
printf("You must specify the operation ");
printf("followed by two numbers.");
exit (1) ;
}
if(!stremp("add", argv[l]))
printf("%f", atof(argv[2]) + atof(argv[3]));
else if(1stremp("subtract", argvfl]))
printf("%f", atof(argv[2]) - atof(argv[3]));
else if(!stremp("multiply", argv[l]))
printf("%f", atof(argv[2]) * atof(argv[3]));
if(!Stremp("divide", argvfl]))
printf ("%f", atof(argv[2J) / atof(argv [3]));
return 0;
}
Отговори 521
7.5
Упражнения
I. ftinclude <stdio.h>
double f_to_m(double f) ;
int main(void)
double feet;
printf ( "Enter feet: ");
scanf("%lf", &feet);
printf("Meters: %f", f to_m(feet));
return 0;
/* Използване на старин вид декларация. */
double f_to_m(f)
double f;
return f / 3.28;
Проверка на уменията,
ПРЕДСТАВЕНИ В ГЛАВАТА
I. Функция, която няма параметри, трябва да задава void в
списъка с параметри на своя прототип.
2. Проготипът на една функция казна на компилатора тези
три неща: тина на резултата на функцията, типа на пара-
метрите й и техния брой. Той е полезен, защото дава въз-
можност на компилатора да открива грешки при непра-
вилно извикване на функцията.
3. Аргументите от командния ред се предава г чрез парамет-
рите argc и argv на main().
4. ftinclude <stdio.h>
void alpha(char ch);
int main(void)
{
alpha('A');
return G;
)
void alptia(char ch)
{
522 С - Практически самоучит ел
printf("%с", ch);
if (ch < 'Z') alpha(ch+1);
}
5. ttinclude <stdio.h>
ttinclude <stdlib.h>
int main(int argc, char *argv[])
{
char *p;
if(argc! =2) {
printf("You need to specify a string");
exit (1);
}
p = argv[l];
while(*p) {
printf("%c", (+p)+l);
p++;
}
return 0;
}
6. Прототипът e:
double myfunc(int x, int y, char ch);
7. Според старият качин на деклариране, функцията от уп-
ражнение 6 ще изглежда така:
double myfunct(x, у, ch)
int х, у;
char ch;
{
)
8. Функцията exit() предизвиква незабавното затваряне на
програмата. Освен това тя връща стойност на операцион-
ната система.
9. Функцията atio( ) конвертира своя низов аргумент в съот-
ветната еквивалептна целочислена форма. Низът трябва да
представлява (в низова форма) валидно цяло число.
Отговори 523
Проверка на натрупаните умения
1. ttinclude <stdio.h>
#include <string.h>
ttinclude <stdlib.h>
int main(int argc, char *argv[})
I
if(argc!=2) {
printf("Specify a password");
exit (1) ;
}
if(!stremp(argv[ 1 ] , "password"))
printf("Access Permitted");
else printf("Access Denied");
return 0;
2. {{include <stdio.h>
#include <ctype.h>
void string up(char *p);
int main(void)
{
char strf] = "this is a test";
string_up (str) ;
printf(str);
return 0;
}
void string up(char *p)
{
while(*p) {
*p = toupper(*p);
pi+;
)
}
3. {(include <stdio.h>
void avg(double *d, int num) ;
int main(void)
{
double nums [ ] = {l.G, 2.0, 3.0, 4.0, 5.0,
6.0, 7.0, 8.0, 9.0, 10.0};
avg(nums,.10);
return 0;
524 С - Практически самоучител
void avg(double *d, int num)
{
double sum;
int temp;
temp = num-1;
for(sum=0; temp>=0; temp--)
sum = sum + d[temp);
printf("Average is %f", sum / (double) num);
4. Указателях съдържа адреса на друга променлива. Когато
на някоя функция се предава указател, тя може да променя
съдържанието на променливата, сочена от него. Това е из-
викване по адрес.
Глава 8
Преглед на знанията
1. За да дадете възможност на компилатора да провери дали
дадена функция се извиква правилно, трябва да включите
нейния прототип.
2. Прототипите на функции дават възможност на компилато-
ра да предостави по-добро проверяване на типовете, из-
ползвани при извикването на функцията и тези в самата
нея. Освен това те позволяват на компилатора да потвър-
ди, че функцията се извиква с правилния брой аргумента.
3. #include <stdio.h>
^include <math.h>
double hypot(double si, double s2);
int main(void)
printfhypot(12.2, 19.2));
return 0;
double hypot(double si, double s2)
double h;
h = s1* s1 + s 2 * s 2 ;
return sqrt(h);
Отговори 525
4. Когато една функция не връща стойност, нейният тин на
резултата трябва да е void.
5. ttinclude <stdio.h>
int rstrlen(char *p);
int main(void)
printf("%d", rstrlen("hello there"));
return 0;
int rstrlen(char *p)
{
if(*p) {
p++;
return 1+rstrlen(p);
}
else return 0;
6. ttinclude <stdio.h>
int main(int argc, char *argv[J)
{
printf("There were %d arguments.\n", argc);
printf("The last one is %s.", argv[argc-1]);
return 0;
}
7. funcfa, ch, d)
int a;
char ch;
double d;
{
Упражнения
1. ttinclude <stdio.h>
♦define MAX 100
♦define COUNTBY 3
int main(void)
int i;
for(i=0; i<MAX; i++)
if(!(i%COUNTBY)) printf("%d ", i);
return 0;
526 С - Практически самоучител
2. Не, фрагментът е грешен, защото макрос нс може да сс
дефинира чрез друг макрос, преди вторият да е дефини-
ран. Казане по друг начин, MIN не е дефиниран, когато се
дефинира МАХ.
3. Както се използва макросы; фрагменты е грешен. Низът
трябва да е в кавички.
4. Да.
8.2
Упражнения
1. flinclude <stdio.h>
int main(void)
{
int i;
do (
i = getchar();
if(i==EOF) {
printf("Error on input.");
break;
if(putchar(' . ’)==EOF) {
printf("Error on output.");
break;
)
} whi l.e ( (char) i != ’ \n ' ) ;
return 0;
2. Функцията putchar() изписва знак. Тя не може да огпе-
чагва низ.
8.3
Упражнения
1, ttinclude <conio.h>
((include <stdio.h>
int main(void)
{
char ch;
ch = getch();
printf ("%d'*, ch);
return 0;
)
Отговори 527
8.4
8.5
8.6
2. ttinclude <stdio.h>
ttinclude <conio.h>
int main(void)
{
do {
printf("%c", ’ . ' ) ;
} while(1kbhi t());
return 0;
Упражнения
2. He. Програмата e грешна, защото gets() трябва да се изви-
ка с указател към истински масив.
Упражнения
1. ttinclude <stdio.h>
int main(void)
{
unsigned long i;
for(i=2; i<=100; i++)
printf("%-101u %-lClu %-101u\n", i, i*i, i*i*i);
return 0;
2. printf("Clearance price: 40%% off as marked");
3. printf("%.2f", 1023.03);
Упражнения
1. #include <stdio.h>
int main(void)
{
char first[21], middle[21], last[21];
printf("Enter your entire name: ");
scanf("%20s%20s%20s", first, middle, last);
printf(”%s %s %s", first, middle, last);
return 0;
528 С - Практически самоучител
2. #include <stdio.h>
int main(void)
{
char num[80];
printf("Enter a floating point number: ");
scanf ("%[0-9.]", num);
printf (num) ;
return 0;
)
3. He, знакът може да има максимална дължина 1.
4. ttinclude <stdio.h>
int main(void)
{
char str[80];
double d;
int i, num;
printf("Enter a string, a double, and an
integer: ");
scanf("%s%lf%d%n", str, 4<d, &i, banim) ;
printf("Number of characters read: %d", num);
return 0;
)
5. ttinclude <stdio.h>
int main(void)
unsigned u;
printf("Enter hexadecimal number: ");
scanf ( "%x", &u);
printf("Decimal equivalent: %u", u);
return 0;
)
Проверка на уменията,
ПРЕДСТАВЕНИ В ГЛАВАТА
1. Всички тези функции четат знакове от клавиатурата. Фун-
кция га getchar() често се имплементира посредством ли-
нейно буфериран вход/изход, което я прави нежепана за
интерактивните среди. getche() е интерактивният еквива-
Отговори 529
лент на getcharf). Функция getchf) е същата като getchef)
с изключение на това, че не отпечатва въведения знак.
2. Спецификаторът %е отпечатва число в научен формат с
малка буква “е”. Спецификаторът %Е отпечатва число в
научен формат с главна буква “Е”.
3. Множество за търсене е набор от знакове, конто scanff)
сравнява с въведените. Докато прочитайте знакове са
част от множество™ за търсене, scanff) продължава да ги
чете в масива, сочен от съответния аргумент.
4. ((include <stdic.h>
int main(void)
{
char name[80], date(8OJ, phone(80];
printf("Enter first name, birthdate ");
printf("and phone number:\n");
scanf("%s%8s^8s", name, date, phone);
printf ("%s %s %s", name, date, phone);
return 0;
}
5. Функцията puts() e доста по-малка и по-бърза от printff).
Тя обаче може да отпечатва само низове.
6. ((include <stdio.h>
^define COUNT 100
int main(void)
t
int i;
for(i=0; i<COUNT;i++)
printf("%d ", i);
return 0;
7 EOF e макрос, който означава end-of-file. Той e дефиниран
в STDIO.H.
Проверка на натрупаните умения
1. ((include <stdio.h>
int main(void;
char nam€[9][80);
530 С - Практически самоучител
double b_avg[ 9 ] ; int i, h, 1; double high, low, team_avg; for(i=0; i<9; i++) { printf("Enter name %d: ", i + 1); scanf ("%s", name[i]); printf("Enter batting average: "); scanf("%lf", &b_avg[i]); printf("\n"); } high = 0.0; low = 1000.0; team_avg = 0.0; for(i=0; i<9; i++) { if(b_avg[i]>high) { h = i; high - b avg[i]; } if(b_avg[i]<low) { 1 = i; low = b avg[i] ; } team avg = team avg+b avg[i); } printf ("The high is %s %f\n", name[h], b__avg[h]); printf("The low is %s %f\n", named], b_avg[l]); printf("The team average is %f", team avg/9.0); return 0; ) 2. Забележка: Съществуват много начини, по конто можете да напишете тази програма. Този е просто един от тях. /* Електронна картотека. */ ftinclude <stdio.h> ftinclude <stdlib.h> ftinclude <string.h> ftdefine MAX 100 int menu(void); void displayfint i); void author_search(void) ; void title_search(void); void enter(void); char names[MAX][80]; /* имена на автори */ char titles[MAX][80]; /* заглавия */ char pubs[MAX][80]; /* издатели */
int top = 0; /* последнего използвано място*/
Отговори 531
int main(void)
{
int choice;
do {
choice - menu();
switch(choice) {
case 1: enter!); /* въвеждане на книги */
break;
case 2: author_search(} ; /* търсене no
автор */
break;
case 3: title_search(); /* търсене no
заглавие */
break;
}
} while(choice!-4);
return 0;
}
/* Връщане на избор ст меню. */
menu(void)
{
char str[80];
int i;
printf( ; "Card Catalogin');
printf1 " 1. Enter\n”);
printf( 2. Search by AuthorXn"
printf( )" 3. Search by Title\n”)
printfI ? 4. Quit\n");
do {
printf(“Choose your selection: ");
pecs(str);
1 - atoi(str);
printf("\n");
} while(i<l I I i>4);
return i;
}
/* Въвеждане на книги в базата данни. */
void enter(void)
{
inc i;
for(i=top; i<MAX; i++) {
printf("Enter author name (ENTER to quit): ");
gees(names [i]);
if(!*names[i]) break;
printf("Enter title: ");
gees(titles [i]);
printf("Enter publisher: ");
532 С - Практически самоучител
gets(pubs[i]);
}
top = i;
)
/* Търсене по автор. */
void author search(void)
(
char name (80] ;
int i, found;
printf("Name: ");
gets(name);
found = 0;
for(i=0; i<top; i++)
if(!strcmp(name, names[i])) {
display(i);
found = 1;
printf("\n");
if ((found) printf ("Not Foundin'');
}
/* Търсене по заглавие.*/
void title search(void)
(
char title[80];
int i, found;
printf("Title: ");
gets (title);
found = 0;
for(i=0; i<top; i++)
if(!stremp(title, titles[i])) {
display(i);
found = 1;
printf("\n");
}
if([found) printf("Not Found\n"J;
/* Изписвзне на каталога. */
void display(int i)
printf("%s\n", titles [i]);
printf("by %s\n", names[i]);
printf("Published oy %s\n", pubs[i]);
}
Отговори 533
Глава 9
Преглед на знанията
1. Функцията getchar() е дефинирана от ANSI и се използва
за четене на знакове от клавиатурата. Тя обаче често се
имилементира посредством линейно буфериран вход/из-
ход, което я прави нежелапа за интерактивните среди.
getche() е интерактивният еквивалент на getcliar(), но не
е дефинирана от стандарта ANSI.
2. Когато scanf() чете низ, тя спира при срещането на пър-
вия празен знак.
3. ftinclude <stdio.h>
int isprime(int i);
int main(void)
int i, count,
count = 0;
for(i=2; i<1001; i++)
if(isprime(i)) {
printf("%10d”, i);
count++;
if(count^=4) {
printf ("\n");
count = 0;
}
return 0;
int isprime(int i)
int j ;
for(j=2; j<=(i/2); j++)
if(! (i%j)) return 0;
return 1;
}
4. ftinclude <stdio.h>
int main(void)
double d;
char ch;
char str[80];
534 С - Практически самоучител
printf("Enter a double, a character, and а
stringXn");
scanf("%lf%c%20s", &d, &ch, str);
printf("%f %c %s”, d, ch, str);
return 0;
5. ttinclude <stdio.h>
int main(void)
{
char str [30];
printf("Enter leading digits followed by a
stringXn");
scanf ("%*[0-9]%s", str);
pri ntf ('% s", str);
return 0;
Упражнения
1. ttinclude <stdio.h>
ttinclude <stdlib,h>
int main (int argc, char ,rargv[])
I
FILE *fp;
char ch;
/* проверка дали e зададено име на файл */
if(argc!=2) {
printf("File name missing.\n");
exit (1);
if((fp = fopen(argv[1], "r"))^=NULL) (
printf("Cannot open file.Xn");
exit(1);
while ( (ch>=fgetc (fp) ) != EOF) putchar(ch);
fclose(fp);
return 0;
2. ttinclude <stdio.h>
ttinclude <stdlib,h>
ttinclude <ctype.h>
Отговори 535
int count 126 J;
int main(int argc, char *argv[])
{
FILE *fp;
char ch;
int i ;
/* проверка дали e зададенс име на файл */
if(argc!=2) {
printf (''File name missing.\n");
exit. (1) ;
if((fp = fopen(argvL1], "r"))==NULL) {
printf("Cannot open file.Xn");
exit(1);
while((ch-fgetc(fp))!=EOF) {
ch = toupper(ch);
if(ch>='A' && cb<-'Z') count[ch-'A']+ +;
for(i=0; i<26; i++)
printf("%c occurred %d timesXn", i+'A',
count[i] ) ;
fclose(fp);
return G;
3. /* Копиране на файл. */
tfinclude <stdio.h>
^include <stdlio h>
frinclude <string h>
int. main (int argc, char *argv[] )
{
FILE *frcm, *to;
char ch, watch;
/* проверка за верен Орой аргументи в командния
ред */
if(argc<3) {
princf("Usage: copy <source>
<destination>\n");
exit(1);
/* отваряне на файла-иэточник */
if(( from = fopen(argv[1], "r"))==NULL){
printf("Cannot open source file.Xn");
536 С - Практически самоучител
exit(1) ;
/* отваряне на файла-цел */
if((to = fopen(argv[2], "w"))==NULL) {
printf("Cannot open destination file.Xn");
exit(1) ;
if (argc—4 && ! stramp (argv [3], "watch")) watch = 1;
else watch = 0;
/* копиране на файла */
while((ch=fgetc(from))!=EOF) {
fputc(ch, to);
if(watch) putchar(ch);
fclose(from);
fclose(to);
return 0;
}
9.3 Упражнения
1. ftinclude <stdio.h>
ftinclude <stdlib.h>
int main(int argc, char *argv[])
(
FILE *fp;
unsigned count;
/* проверка дали e зададено име на файл */
if(argc!=2) (
printf("File name missing.Xn");
exit (1);
)
if((fp = fopen(argv[1], "rb"))==NULL) (
printf("Cannot open file.Xn");
exit(1);
)
count = 0;
while(!feof(fp)) {
fgetc(fp);
if(ferror(fp)) {
printf("File error.Xn");
exit (1) ;
}
count++;
I
Отговори 537
printf("File has %u oytes", count-1);
fclose(fp);
return 0;
}
2. /* Размяна на два файла. */
^include <stdio.h>
ttinclude <stdlib.h>
linclude <string.h>
int main(int argc, char *argv[J)
{
FILE *fl, *f2, *temp;
char ch;
/» проверка за верен брой аргументи в командния
ред */
if(argc|=3) {
printf("Usage: exchange<fl> <f2>\n");
exit (1) ;
}
/* отваряне на първия файл */
if((fl = fopen(argv[1], "rb"))==NULL) {
printf("Cannot open first file.Xn");
exit(1) ;
/* отваряне на втория файл*/
if((f2 - fopen(argv[2],"rb"))==NULL) {
printf("Cannot open second file.Xn");
exit(1);
}
/* отваряне на временкия файл */
if((temp = fopen("temp.trap", "wb”))=-NULL) {
printf("Cannot open temporary file.Xn");
exit(1);
}
/* копиране на fl в temp */
while(!feof(f1)) (
ch = fgetc(fl);
if(!feof(f1)) fputc(ch, temp);
)
fclose(f1);
/* отваряне на първия файл за изход */
if((fl = fopen(argv[1], "wb"))==NULL) {
printf("Cannot open first file.Xn");
exit (1);
538 С - Практически самоучител
/* копиране на f2 във fl */ while(!feof(f2)) { ch = fgetc(f2); if(!feof(f2)) fputc(ch, fl); } fclose (f2); fclose(temp); /* отваряне на втория файл за изход */ if((f2 = fopen(argv[2], "wb"))==NULL) { printf("Cannot open second file.Xn"); exit(1); } /* отваряне на временния файл за input */ if((temp = fopen("temp.tmp", "rb"))==NULL) ( printf("Cannot open temporary file.Xn"); exit (1); } /* копиране на temp в f2 */ while(!feof(temp)) { ch = fgetc(temp); if ( ! feof (temr>) ) fputc(ch, f2) ; ) fclose (fl); fclose(f2); fclose(temp); return 0; }
Упражнения
1. /* Прост компотърен телефонен указател. */ #include <stdio.h> ttinclude <string.h> ttinclude <stdlib.h> char names[100][40]; char numbers[100][40]; int loc=0; int menu(vcid); void enter(void); void load(void); void save(void); void find(void); int main(void) {
int choice;
Отгопори 539
do {
choice = menu();
switch(choice) {
case 1: enter();
break;
case 2: find();
break;
case 3: save ();
break;
case 4: load ();
}
} while(choice!=5);
return 0;
}
/* Получаваие на избора от менюто. */
int menu(void)
int i;
char str[8C];
printf("1. Enter names and numbersXn");
printf("2. Find numbersXn");
printf("3. Save directory to diskXn");
printf("4. Load directory from diskXn");
printf("5. Quit\n");
do {
printf ("Enter your choice:
gets (str);
i = atoi(str);
printf("\n");
} while(i<l || i>5);
return i;
void enter(void)
{
for(;loc<100; loc++) {
if(l.oc<100) {
printf("Enter name and phone number:\n")
gets(names[loc]);
if(!*names[Loc]) break;
gets(numbers[loc]);
}
}
void find(void)
{
char name[80];
int i;
printf("Enter name: ");
540 С - Практически самоучител
gets(папе);
for(i=0; i<100; 1++)
if(!strcmp(name, names[i]))
printf ("%s %s\n", names [i], mmbeis [i] ) ;
}
void load(void)
{
FILE *fp;
if((fp = fopen("phone", "r"))==NULL) {
printf ("Cannot open file An");
exit (1);
}
loc = 0;
while(!feof(fp)) {
fscanf(fp, "%s%s", names[loc], numbers[locj);
loc++;
}
fclose(fp);
void save(void)
{
FILE *fp;
int i;
if((fp = fopen("phone", "w"))==NULL) {
printf ( "Cannot open file An");
exit (1) ;
}
fcr(i-0; i<loc; i++) {
fprintf(fp, "%s %s ", names[i], numbers[1]);
)
fclose(fp);
2. ftinclude <stdio.h>
[[include <stdlib.h>
ftinclude <ctype.h>
int main(int argc, char *argv[])
{
FILE *fp;
char ch;
char str[80];
int count;
/* проверка за верен брой аргументи в командния
ред */
if(argc!=2) {
printf("Usage: display <file>\n");
Отговори 541
exit(1);•
}
/* отваряне на файла */
if ( (fp = fopen(argv[1], "г"))==NULL) {
printf("Cannot open the file.\n");
exit (1);
count = 0;
while(Ifeof(fp)) {
fgets (str, 79, fp);
printf("%s", str);
count++;
if(count==23) {
printf("More? (y/n) ");
gets (str);
if(toupper(*str)=='N’) break;
count = 0;
}
)
fclose(fp);
return 0;
3. /* Копиране на файд. */
ttinclude <stdio.h>
ttinclude <stdlib.h>
ttinclude <string.h>
int main tint argc, char *argv[])
{
FILE *from, *to;
char str[128I;
/* проверка за верен брой аргумента в командния
ред */
if(argc<3) {
printf("Usage: copy <source> <destination>\n");
exit (1) ;
}
/* отваряне на файла-изтсчник */
if((from = fopen(argv[1], "r"))==NULL) {
printf ("Cannot open source file.W);
exit(1) ;
}
/* отваряне на файла-цел */
if((to = fopen(argv[2], "w"))==NULL) {
printf("Cannot open destination fiJe.\n");
exit(1);
542 С - Практически самоучител
/* копиране на файла */
while (! feof (from)) {
fgets(str, 127, from);
if(ferror(from)) {
printf("Error on input.Xn");
break;
}
if(!feof(from)) fputs(str, to);
if(ferror(to)) {
printf("Error on output.\n");
break;
}
}
if(fclose(from)==EOF) {
printf("Error closing source file.Xn");
exit (1) ;
if(fclose(to)==EOF) {
printf("Error closing destination file.Xn");
exit (1);
return 0;
9.5
Упражнения
1. ^include <stdio.h>
({include <sudlib.h>
int main(void)
{
FILE *fpl, *fp2;
double d;
int i;
if((fpl = fopen("values", "wb"))==NULL) (
printf("Cannot open file.Xn");
exit(1);
if((fp2 = fopen("count", "wb"))==NULL) (
printf("Cannot open file.Xn");
exit (1) ;
d = 1.0;
for(i=0; d!=0.0 && К32766; i+ + ) (
printf("Enter a number (0 to quit): ");
scanf("%lf", &d);
Отгооори 543
fwrite(&d, sizeof d, 1, fpl);
fwrite(&i, sizeof i, 1, fp2);
fclose(fpl);
fclose(fp2);
return 0;
2. #include <stdic.h>
#include <stdlib.h>
int main(void)
(
FILE *fpl, *fp2;
double d;
int i ;
if ((fpl = fopen("values", "rb"))=~NULL) {
printf("Cannot open file.Xn");
exit (1) ;
if((fp2 = fopen("count", "rb"))==NULL) {
printf("Cannot open file.Xn");
exit (1) ;
fread(&i, sizeof i, 1, fp2); /* попучаване на
броя */
for(; i>0; i--) {
fread(&d, sizeof d, 1, fpl);
printf("%f\n", d);
fclose(fpl);
fclose(fp2);
return 0;
9.6
Упражнения
1. #include <stdio.h>
^include <stdlib.h>
int main(int argc, char *argv[l)
FILE *fp;
char ch;
544 С - Практически самоучител
long 1;
if(argc!=2) {
printf ("You must specify the file.Xn");
exit (1);
if((fp = fopen(argv[1], "rb"))== NULL) (
printf("Cannot open file.Xn");
exit(1);
fseek(fp, 0, SEEK_END); /* намиране на края на
файла */
1 = ftell(fp);
/* обратно в началото на файла */
fseekffp, 0, SEEK_SET);
f or ( ; 1>=0; 1 = 1 - 2L) (
ch = fgetc(fp);
putchar(ch);
fseek(fp, IL, SEEK_CUR);
fclose(fp);
return 0;
2. ttinclude <stdio.h>
ttinclude <stdlib.h>
int main(int argc, char *argv[J)
{
FILE *fp;
unsigned char ch, val;
if(argc!=3) {
printf ("Usage: find <filename> <value>");
exit(1);
if((fp = fopen(argv[1], "rb"))==NULL) {
printf ("Cannot open file.Xn");
exit(1);
val = atci(argv[2]);
while(1feof(fp)) {
ch = fgetc(fp);
if(ch == val)
printf("Found value at %ld\n", ftell(fp));
Отговори 545
fclose(fp);
return 0;
Упражнения
1. linclude <stdio.h>
#include <stdlib.h>
((include <ctype.h>
int main(void)
I
char fname(80];
printf("Enter name of file to erase: ");
gets(fname);
printf("Are you sure? (Y/N) ");
if(toupper(getchar())==' Y')
if(remove(fname))
printf("\nFile not found or write
protected.\n");
return 0;
Упражнения
1. /* Копиране посредством пренасочране.
Изпълнете така:
ONAME < in > out
((include <stdio.h>
int main(void)
{
char ch;
while(!feof(stdin)) (
scanf ("%c", A<ch);
if(1feof(stdin)) printf("%c", ch);
return 0;
546 С - Практически самоучител
Проверка на уменията,
ПРЕДСТАВЕНИ В ГЛАВАТА
1. ttinclude <stdio.h>
ttinclude <stdlib.h>
ttinclude <ctype.h>
int main(int argc, char *argv[J)
{
FILE *fp;
char str [80];
/* проверка дали e зададено име на файл */
if(argc! =2) {
printf("File name missing,\n");
exit(1);
}
if((fp = fopen(argv[1], "r"))==NULL) (
printf("Cannot open file.Xn");
exit (1);
}
while (ifeof(fp)) {
fgets(str, 79, fp);
if ( 1feof(fp)) printf("%s", str);
printf("...More? (y/n) ");
if(toupper(getchar())=='N') break;
printf("\n");
)
fclose(fp);
return 0;
}
2. /* Копиране на файл и конвертиране в главни букви. */
ttinclude <stdio.h>
ttinclude <stdlib.h>
ttinclude <ctype.h>
Int main(int argc, char *argv[])
{
FILE *from, *to;
char ch;
/* проверка за верен брой аргументи в командния
ред */
if(argc!=3) {
printf ("Usage: copy <source>
<destination>\n");
exit(1);
}
Отговори 547
/* отваряне на файла -източнтлк */
if((from= fopen(argv[1J, ”r"))==NULL) {
printf("Cannot open source file.Xn");
exit(1);
}
/* отваряне на файла-цел */
if((to = fopen(argv[2], "w"))==NULL) {
printf("Cannot open destination file.Xn");
exit (1) ;
/* копиране на файла */
while(!feof(from)) {
ch = fgetc(from);
if(!feof(from)) fputc(toupper(ch), to);
}
fclose(from);
fclose(to);
return 0;
}
3. Функциите fprintf( ) и fscanf( ) са същите като printf( ) и
scanf(), само че рабстят с файлове.
4, #include <stdio.h>
ftinclude <stdlib.h>
int main(void)
{
FILE *fp;
int i, num;
if((Ep = fopen("rand", "wb"))==NULL) {
printf("Cannot open file.Xn");
exit(1);
for(i=0; i<100; i++) .{
num = rand();
fwrite(Snum, sizeof num, 1, fp);
fclose(fp);
return 0;
5, ftinclude <stdic.h>
ftinclude <stdlib.h>
int main(void)
{
FILE *fp;
int i, num;
548 С - Практически самсучител
if((fp = fopen("rand", "rb"))=-NULL) {
printf("Cannot open file.Xn");
exj t (1) ;
}
for(i=Q; i<100; i++) {
fread(&num, sizeof num, 1, fp);
printf("%d\n", num);
fclose(fp);
return } 0;
6. ♦include ♦ include <stdio.h> <stdlib.h>
int main(void)
{
FILE *fp;
long i;
int num;
if((fp = fopen("rand", "rb"))==NULL) {
printf("Cannot open file.Xn");
exit (1) ;
}
printf("Which number (0-99)? ");
scanf("%ld", &i);
fseekffp, i * sizeof(int), SEEE SET);
fread(&num, sizeof num, 1, fp);
printf("%d\n", num);
fclose(fp);
return 0;
}
7. “Конзолните” входно/изходни функции са просто специал-
ни случаи на файловата система.
Проверка на натрупаните умения
1. /х Електронна картотека. */
♦include <stdio.h>
♦include <string.h>
♦include <stdlib,h>
♦define MAX ICO
int menu(void);
void display(int i) ;
void author searcn(void);
void title search(void);
Отговори 549
void enter(void);
void save(void);
void 1oad(void);
char names[MAX][30]; /* имена на автора */
char titles[MAX][80?; /* заглавия */
char pubs[MAX][80]; /* издател */
int top = 0; /* последно използвано място */
int main(void)
{
int choice;
load(); /* прсчитане на картотеката */
do {
choi ce = menu();
switch(choice) {
case 1: enter)); /* въвеждане на книги*/
break;
case 2: author search)); /* търсене no
автор */
break;
case 3; title search О; /* търсене no
заглавие */
break;
case 4: save();
)
} while(choice!-5);
return 0;
)
/* Връщане на из&ор от менюто. */
menu(void)
inL i;
char str[80];
printf( "Card Catalog:\n");
printf( " 1. Enter\n");
printf ( " 2. Search by author\n");
printf( " 3. Search by TitleXn”),
printf( " 4. Save catalcgXn");
printf( " 5. QuitXn");
do {
printf("Choose your selection: ");
gets(str);
i = atci(str);
printf("Xn");
) while(i<l || i>5);
return i;
550 С - Практически самоучител
)
/* Въвеждане на книги в базата данни. */
void enter(void)
{
int i ;
for(i=top; i<MAX; i++) {
printf("Enter author name (ENTER to quit) : ") ;
gets(names[i j);
if(!*names[i]) break;
printf("Enter title: ”);
gets(titles[i]);
printf("Enter publisher: ") ;
gets(pubs[i]);
too = i;
}
/* Търсене по автор. */
void author search(void)
char паше[80];
int i, found;
printf("Name: ");
gets(name);
found = 0;
for(i=0; i<top; i++)
if(!strcmp(name, names[i])) {
display (i);
found = 1;
printf("\n");
)
if (! found) printf (’’Not Foundin'') ;
)
/* Търсене по заглавие. * /
void title search(void)
{
char title[80];
int i, found;
printf("Title: ") ;
gets (title);
found = 0;
for(i=0; i<top; i++)
if (! strcmp (title., titles[i])) {
display(i);
found = 1;
printf("In");
}
if ((found) printf ("Not Foundin'*);
Отовори 551
)
/* Изписване на елемент от картотеката. */
void display(int i)
{
printf(’%s\n", titlesfi]);
printf("by %s\n", names[i]);
printf("Published by %s\n", pubs[i]);
}
/* Зареждане на файла на картотеката. */
void load(void)
{
FILE *fp;
if((fp = fopen("catalog", "r"))==NULL) {
printf ("Catalog file not on disk..\n");
return;
}
freadfktop, sizeof top, 1, fp), /* read count */
fread(numes, sizeof names, 1, fp) ;
fread(titles, sizeof titles, 1, fp) ;
freadfpubs, sizeof pubs, 1, fp) ;
fclose(fp);
}
/* Записване на файла на картотеката. */
void save(void)
{
FILE *fp;
if((fp = f open (" catalog'1, “w"))=-NULL) {
printf("Cannot open catalog file.Xn");
exit(1) ;
)
fwrite(«top, sizeof top, 1, fp) ;
fwrite(names, sizeof names, 1, fp);
fwrite(titles, sizeof titles, 1, fp);
fwrite(pubs, sizeof pubs, 1, fp) ;
fclose(fp);
}
2. /* Копиране на файл и премахване на табулациите. */
♦include <stdio.h>
♦include <stdlib.h>
♦include <string.h>
int main(int argc, char *argv[])
{
FILE *from, *to,
char ch;
552 С - Практически самоучител
int tab, count;
/* проверка за верен брой аргумента в командния
ред */
if(argc!=3) {
printf ("Usage: copy <source> <destination>\n");
exit (1);
/* отваряне на файла-източник */
if((from = fopen(argv[1], "r"))==NULL) {
printf("Cannot open source file An");
exit(1);
/* отваряне на файла-цел */
if ((to = fopen (argv [2] , "w'*)) —NULL) {
printf("Cannot open destination file An");
exit (1) ;
}
/* копиране на файла */
count = 0;
while(!feof(from)) {
ch = fgetc(from);
if(ch=='\t’) (
for (tab = count; tab<8; tab+H
fputc(’ ', to);
count = 0;
}
else {
if(!feof(from)) fputc(ch, to) ;
count++;
if(count==8 || ch=='\n') count = 0;
}
)
fclose(from);
fclose(to);
return 0;
Глава 10
Преглед на знанията
1. /* Копиране на файл. *7
ttinclude <stdio.h>
ttinclude <stdlib.h>
int main(int argc, char *argvf])
{
FILE *from, *to;
Отговори 553
char ch;
/* проверка за верен брой аргументи в командния
ред */
if(argc!=3) {
printf("Usage: copy <source> <destinatiori>\n"
exit (1) ;
}
/* отваряне на файла-източник */
if((from = fopen(argv(1], "rb"))==NULL) (
printf("Cannot open source file.Xn");
exit (1);
}
/* отваряне на файла-цел */
if((to = fopen(argv[2], "wb"))==NULL) {
printf("Cannot open destination file.Xn");
exit (1);
)
/* копиране на файла */
while(!feof(from)) (
ch = fgetc(from);
if(ferror(from)) {
printf("Error on input.Xn");
break;
)
if(1feof(from)) fputc(ch, to);
if(ferror(to)) {
printf("Error on output.Xn");
break;
1
}
if(fclose(from)-»EOF) {
printf("Error closing source file.Xn");
exit(1);
}
if (fclose(to)==EOF) {
printf("Error closing destination file.Xn")
exi t (1);
return 0;
}
2- #include <stdio.h>
^include <stdlib.h>
int main(void'
(
FILE *fp;
554 С - Практически самоучител
/* отваряне на файл */
if((fp = fopen("myfile", "w"))==NULL) {
printf("Cannot open file.Xn");
exit (1);
fprintf(fp, "%s %.2f %X %c", "this is a string"
1230.23, OxlFFF, 'A');
fclose(fp);
return 0;
3. #include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp;
int count[20], i;
/* отваряне на файл */
if((fp = fopen("TEMP", "wb"))==NULL) (
printf("Cannot open file.Xn");
exit (1);
for(i=0; i<20; i++) count[i] = i+1;
fwrite(count, sizeof count, 1, fp);
fclose(fp);
return 0;
4. #include <stdio.h>
#include <stdlib.h>
int main(void)
FILE *fp;
int count[20], i;
/* отваряне на файл */
if((fp = fopen("TEMP", "rb"))==NULL) {
printf("Cannot open file.Xn");
exit (1);
fread(count, sizeof count, 1, fp);
for(i=0; i<20; i++) printf("%d ", countfi]);
Отговори 555
fclose(fp);
return 0;
}
5. stdin, stdout и stderr са три потока, конто са отварят авто-
матично, когато програмата на С се стартира. По подраз-
биране те рефенцират конзолата, но в операционните сис-
теми, поддържащи пренасочване на входа/изхода, те могат
да се пренасочат към други устройства.
6. Функциите printf() и scanf() са част от файловата систе-
ма на С. Те просто са специални функции, конто автома-
тично използват stdin и stdout.
10.1
Упражнения
1. /* Прост компютърен телефонен указател. */
ttinclude <stdio.h>
ttinclude <string.h>
ttinclude <stdlib.h>
ttdefine MAX 100
struct phone_type {
char name[40];
int areacode;
char number[9];
} phone[MAX];
int loc=0;
int menu(void);
void enter(void);
void load(void);
void save(void);
void find(void);
int main(void)
{
int choice;
do {
choice = menu();
switch(choice) {
case 1: enter();
break;
case 2: find();
break;
case 3: save();
break;
case 4: load();
55G С - Практически самоучител
}
) while(choice!=5);
return 0;
}
/* Получаване на набора от менюто. */
menu(void)
{
int i;
char str[80];
printf("1. Enter names and numbers\n") ;
printf("2. Find numbers\n");
printf("3. Save directory to disk\n");
printf("4. Load directory from disk\n");
printf("5. Quit\n");
do {
printf("Enter your choice: ");
gets(str);
i = atoi (str);
printf ("\n");
} while (i<l || i>5);
return i;
}
void enter(void)
{
char temp 180];
for(;locClOO; loc++) {
if(loc<100) {
printf("Enter name: ");
gets(phone[loc].name);
if(!*phone[loc].name) break;
printf("Enter area code: ");
gets(temp);
phone[loc].areacode = atoi(temp);
printf("Enter number: ");
gets(phone[loc].number);
}
)
}
void find(void)
{
char name[80];
int i;
printf("Enter name: ");
gets(name);
if(!*name) return;
for(i=0; i<100; i++)
if(Istrcmp(name, phone[i].name))
Отговори 557
printf ("%s (%d) %s\n", phone[i].name,
phone[i].areacode, phone[i].number);
void load(void)
{
FILE *fp;
if((fp = fopen("phone", "r"))~=NULL) {
printf("Cannot open file.Xn");
exit(1);
)
loc = 0;
while(1feof(fp)) {
fscanf(fp, "%s%d3>s", phone [loci .name,
&phone[loc].areacode,
phone[loc].number);
loc++;
}
fclose(fp);
1
void save(void)
(
FILE *fp;
int i;
if((fp = fopen("phone", "w"))==NULL) {
printf("Cannot open file.Xn");
exit (1);
}
for(i=0; i<loc; i++) {
fprintf(fp, "%s %d %s ", phone[i].name,
phone [ i ] . areacode, phone [ i ] . number);
)
fclose(fp);
}
2. Променливата i e член на структурата stype. Поради това
тя не може да се използва самостоятелно. Вместо това
трябва да се достъпва посредством s и оператора точка,
както е показано тук:
s.i = 10;
.2
Упражнения
1. Не. Тъй като р с указател към структура, трябва да изпол-
звате оператора стрелка, а не точка.
558 С - Практически самоучител
2. ttinclude <atdio,h>
ttinclude <time.h>
int main(void)
{
struct tm *systime, *gm.t;
time_L t;
t = time(NULL);
systime = localtime(St);
printf("Time is %.2d:%.2d:%.2d\n"(
systime->tm_hour, systime->tm min,
systime->tir sec) ;
ent = gmt ime(S t);
printf("Coordinated Universal Time is
%.2d:%<2d:%.2d\n", gmt->tm_hour,
gmt~>tm min, gmt->tm sec);
printf("Date: %.2d/%.2d/%.2d",
systime->tm_mon+l, systime->tm_mday,
systime->tm year);
return 0;
)
10.3
Упражнения
1. /* Прост компютърен телефонен указател. */
ttinclude <stdio.h>
ttinclude <string h>
ttinclude <stdlib.h>
ttdefine MAX 100
struct address {
char street[40j;
char city[40];
char state[3];
char zip[12];
struct phone type {
char name[40];
int areacode;
char number[9];
struct address addr;
) phone[MAX];
int loc=0;
int menu(void);
void enter(void);
Отговори 559
void lead(void);
void save(void);
void find(void);
int main(void)
{
int choice;
do {
choice = menu( );
switch(choice) {
case 1: enter( );
break;
case 2: find ( );
break;
case 3: save ( );
break;
case 4: load( );
}
} while (choice ^5) ;
return 0;
}
/* Получаване на избора от менюто. */
menu(void)
(
int i;
char str[80];
printf("1. Enter names and numbers\n") ;
printf("2. Find numbers\n");
printf("3. Save directory to disk\n");
printf("4. Load directory from disk\n");
printf("5. Quit\n");
do {
printf("Enter your choice: ");
gets (str);
i = atoi(str);
printf("\n");
} while(i <1 II i>5);
return i;
}
void enter(void)
{
char tomp[80);
for(;locclOO; loc++) {
if(loc<100) {
printf("Enter name: ");
gets (phone[loc].name);
if(I*phone[loc].name) break;
printf("Enter area code: ");
gets(temp);
560 С - Практически самоучител
phone(Joe].areacode = atoi 'temp) ;
printf("Enter number: ");
gets(phone(loc].number);
/* въвеждане на ииформацията за адреса */
printf("Enter street address: ");
gets(phone[loc].addr.st reet);
printf("Enter city: ");
gets(phone[Зое].addr.city);
printf("Enter State: ");
gets(phone[loc].addr.state);
printf("Enter zip code: ");
gets(phone[loc].addr.zip) ;
}
void find(void)
char name[80];
int i;
printf("Enter name: ");
gets(name);
if(!*name) return;
for(i=0; i<100; i++)
if(!stremp(name, phone[iJ.name)) (
printf("%s (%d) %s\n", phone[i] name,
phone[i].areacode, phone[i].number);
printf("%s\n%s %s %s\n",
phone[i].addr.street,
phcnefi].addr.city,
phone[i].addr.state,
phone[i].addr.zip);
}
void load(void)
(
FILE *fp;
if((fp = fopen("phone", "rb"))=~NULL) (
printf("Cannot open file.\n");
exi t (1) ;
}
loc =0;
while(!feof(tp)) {
fiead(&phone[loc], sizeof phone[loc], 1, fp);
lcc++;
fclose(fp);
void save(void)
Отговори 561
FILE *fp;
int i;
if((fp = fopen("phone", "wb"))==NULL) (
printf("Cannot open file.Xn");
exit(1);
for(i=0; icloc; i++) {
fwrite(&phonefi], sizeof phone[i], 1, tp) ;
)
fclose (fp);
Упражнения
1. ftinclude <stdio.h>
int main(void)
{
struct btype {
int a: 3;
int b: 3;
int c: 2;
) bvar;
bvar.a ~ -1;
bvar.b = 3;
bvar.c = 1;
printf("%d %d %d", bvar.a, bvar.b, bvar.c);
return 0;
J
10.5
Упражнения
1. ftinclude <stdio.h>
ftinclude <stdlib.h^>
union u_type {
double d;
unsigned char c[8];
) ;
double uread(FILE *fp) ;
void uwrite(double num, FILE *fp);
int main(void)
(
FILE *fp;
double d;
562 С - Практически самоучител
if((fp = fopen("myfile", "wb+"))==NULL) {
printf ( "Cannot, open file.\n");
exit (1) ;
write (100.23, fp) ;
d = иread(fp);
printf("%lf", d);
return 0;
void uwrite (double num, FILE *fp)
(
int i;
union u_type var;
var.d = num;
for(i=0; i<8; i++) fputc(var.c(i], fp) ;
double uread(FILE *fp)
{
int i ;
union u_tyoe var;
rewind(fp);
for(i=0; i<8; i++) var.cfi] = fgetc(fp);
return var.d;
2. ttinclude <stdio.h>
int main(void)
(
union t_type {
long 1;
int i;
} uvar;
uvar.l = 0L; /* изчистване на 1 */
uvar.i = 100;
printf("%ld", uvar.l);
return 0;
Отговори 563
Проверка на уменията,
ПРЕДСТАВЕНИ В ГЛАВАТА
1. Структура е имеиувана трупа от свързани данни. Обеди-
нението дефинира част от паметта, поделано от две или
повече промеиливи от различен тип.
2. struct s_type {
char ch;
float d;
int i;
char str[80];
double balance;
} s_var;
3. Тъй като p e указател към структура, трябва да използвате
оператора стрелка, а не точка.
4. ttinclude <stdio.h>
#include <stdlib.h>
struct s type {
char name[40];
char phone[14];
int hours;
double wage;
} emp|10];
int main(void)
{
FILE *fp;
int i;
char temp[80];
if((fp - fopen("emp", "wb"))=~NULL) {
printf ("Cannot: open EMP file.Xn") ;
exit(1);
}
for(i=0; i<10; i++) {
printf("Enter name: ");
gets(emp[i].name);
printf ("Enter telephone number: ");
gets(emp[i].phone);
printf("Enter hours worked: ");
gets(temp);
emp[i].hours = atci(temp);
printf("Enter hourly wage: ");
gets(temp);
emp[i].wage = atof(temp);
)
fwrite(emp, sizeof emp, 1, fp);
fclose(fp);
564 С - Практически самоучител
return 0;
5. ttinclude <stdio.h>
ttinclude <stdlib.h>
struct s_type {
char name[40 J;
char phone[14j;
int hours;
double wage;
} emp Г1C j;
int main(void)
{
FILE *fp;
int i;
if((fp = fopen ("emp", "rb") ) ==-NULL) {
printf("Cannot open EMP file.\n");
exit (1);
freadtemp, sizeof emp, 1, fp) ,
for(i=0; i<10; i++) {
printf("%s %s\n", emp[i].name, emp[i).phone) ;
printf("%d %f\n\n", emp[i].hours, emp[i).wage);
)
fclose(fp);
return 0;
6. Побитово поле e член на сгруктура, задаващ своята дъл-
жина в битове.
7. ttinclude <stdio.h>
int main(void)
(
union u_type {
short int i;
unsigned char c[2);
) uvar;
uvar.i = 9 9;
printf("High order byte: %u\n", uvar.cfl]);
printf("Low order byte: %u\n", uvar.c[0]);
return 0;
}
Отговори 565
Проверка на натрупаните умения
1 . #include <sLdio.h>
struct s type {
int i;
char ch;
double d;
} varl, var2;
void struct_swap(struct s_type *i, struct s_type *j);
int main(void)
varl.i = 100,
var2.i = 99;
varl.ch = ' a';
var2.ch = 1b’;
varl.d = 1.0;
var2.d = 2.0;
printf("varl: %d %c %f\n", varl.i, varl ch,
varl. d);
printf("var2: %d %c %f\n", var2.i, var2.ch,
var2.d);
struct_swap(&varl, &var2);
printf ("After swap:\n");
printf("varl: %d %c %f\n", varl.i, varl.ch,
varl.d) ;
printf("var2: ?d %c %f", var2.i, var2.ch, var2.d);
return 0;
void struct_swap(struct s_type *i, struct s type *j)
{
struct s type temp;
temp = * i;
*i = *j;
*j = temp;
2 /* Копиране на файл. */
finclude <stdio.h>
#incJude <stdlib.h>
int main(int argc, char *argv(])
(
FILE *from, *to;
union u type {
int i;
char ch;
566 С - Практически самоучител
} uvar; /* проверка за верен брой аргументи в командния ред */ if(argc!=3) { printf("Usage: copy <source> <destination>\n"); exit (1) ; /* отваряне на файла източник */ if((from = fopen(argv[1], "rb"))==NULL) { printf("Cannot open source file.Xn"); exit(1); /* отваряне на файла-цел */ if((to = fopen(argv(2], "wb"))==NULL) { printf("Cannot open destination file.Xn"); exit (1); } /* копиране на файла*/ for(;;) ( uvar.i = fgetc(from); if(uvar.i==EOF) break; fputc(uvar.ch, to); } fclose(from); fclose(to); return 0;
3. He можете да използвате структура като аргумент на scnaf(). Можете обаче да използвате елемент на структура. scanf("%d", &var.a);
4. #include <string.h> #include <stdio.h> struct s_type { char str[80]; ) var; void f (struct s_type i) ;' int main(void) strcpy(var.str, "this is original string"); f (var) ; printf("%s", var.str);
return 0;
Отговори 567
void f(struct s_type i)
{
strcpy(i.str, "new string");
printf("%s\n", i.str);
)
ШН Глава 11
I ПРЕГЛЕД НА ЗНАНИЯТА
1. ttinclude <stdio.h>
struct num_type {
int i;
int sqr;
int cube;
} nums[10];
int main(void)
{
int i;
for(i=l; i<ll; i-n-) {
nums[i-1].i - i;
nwns [i-1] . sqr = i*i;
nums[i-1].cube = i*i*i;
for(i=0; i<10; i++) {
printf("%d ", nums[i].i);
printf("%d ", nums[i].sqr);
printf("%d\n", nums[i].cube);
return 0;
2. ttinclude <stdio.h>
union i_to_c {
char c[2];
short int i;
} ic;
int main(void)
{
printf("Enter an integer ");
scanf("%hd", &ic.i);
printf("Character representation of each byte:
%c %c", ic.c[0], ic.c[l]);
568 С - Практически самоучител
return 0;
}
3. Фрагментьт изписва 8 - размеры на най-големия елемент
от обединението.
4. За да достигнете до член на структура, когато използвате
структурна променлива, използвайте оператора точка.
Операторът сгрелка се използва за достигане до член на
структура посредством указател към структура.
5. Побитово поле е елемент от структура, чиито размер се
задава в битове.
11.1
Упражнения
1. Най-добрите променливи, конто трябва да се направят
register, са: к и ш, защото те са най-често използваните.
2. tinclude <stdio.h>
void sum_it(int value);
int main(void)
(
sum_it(10);
sum_it(20);
sum it(30);
sum it(40);
return 0;
}
void sum_it(int value)
{
static int sum=0;
sum = sum + value;
printf("Current value: %d\n", sum);
}
4. He можете да получите адреса на register променлива.
11.2
Упражнения
I. ((include <stdio.h>
const double version = 6.01;
int main(void)
{
Отговори 569
printf("Version %.2f", version);
return 0;
)
2. #include <stdic.h>
char *mystrcpy(char *to, const char ‘troraj;
int main(void)
{
char *p, str [80];
p = mystrcpy(str, "testing");
printf("%s %s", p, str);
return 0;
}
char *mystrcpy(char *to, const char *frcm)
f
char *temp;
temp = to;
while(*from) *10++ = *from++;
*to = '\0’ ; /* null терминатор */
return temp;
}
11.3
Упражнения
2. enum money {penny, nickel, quarter, half_dolla
dollar};
3. He, не можете да отпечатате константа от изброяване к
низ, както се опитва конструкцията printf( ).
11.4
Упражнения
1. #include <stdio.h>
typedef unsigned long UL;
int main(void)
{
UL count;
570 С - Практически самоучител
count = 312323;
printf (”%lu’', count);
return 0;
2. Конструкцията typedef e грешна. Вярната й форма е:
typedef старо-име ново-име;
Упражнения
1. ttinclude <stdio.h>
ttinclude <stdlib.h>
int main(int argc, char *arqv(])
{
FILE *in, *out;
unsigned char ch;
if(argc!=3) {
printf("Usage: code <in> <out>\n");
exi t(1);
if ((in = fopen(arqv[1], "rb"))==NULL) {
printf("Cannot open input file.\n");
exit(1);
if((out = fopen(argv(2), "wb"))==NULL) {
printf("Cannot open output fiie.\n");
exi t (1);
while(1feof(in)) {
ch = fgetc (in);
if(!feof(in)) fputc(~ch, out);
fclose (in);
fclose (out);
return 0;
2. ttinclude <stdio.h>
ttinclude <stdlib.h>
int main(int argc, char *argv [ ])
{
FILE *in, ‘out;
Отговори 571
unsigned char ch;
if(argcJ=4) {
printf("Usage: code <in> <out> <key>\n");
exi t (1) ;
if(( in = fopen(argv[1], “rb") ) j==NULL) {
printf("Cannot open input f_le.\n");
exit(1);
if((out = fopen(argv[2], "wb"))==NULL) {
printf("Cannot open output file.Xn");
exit(1),
while(!feof(in)) {
ch = fgetc(in);
ch = *argv[3] л ch;
if(1feof(in)) fputc(ch, out);
fclose(in);
fclose(out);
return 0;
}
3 a. 0000 0001
b. 1111 1111
c. 1111 1101
4 char ch;
/* За да нулирате старшия Рит, извършете И със
127, което двоично е 0111 1111. По този
начин най-старшият бит се нулира, а останалите
битоде сстават непрсменени.
*/
ch = ch & 127;
11.6
Упражнения
1. ttinclude <stdio.h>
int main(void)
{
int i, j, k;
572 С -I Практически самоучител
printf("Enter a number: ");
scanf("%d”, £1);
j = i « 1;
k = i >> 1;
printf("%d doubled: %d\n", i, j);
printf("%d halved: %d", i, k) ;
return 0;
}
2. #include <stdio.h>
void rotate(unsigned char *c) ;
int main(void)
{
unsigned char ch;
int i;
ch = 1;
for(i=0; i<16; i++) {
rotate(&ch);
printf(”%u\n", ch);
}
return 0;
)
void rotate(unsigned char *c)
{
union {
unsigned char ch[2);
unsigned u;
} rot;
rot.u =0; /* изчистване на 16 бита */
rot.ch[0] = *c;
/* изместване вляво */
rot.u = rot.u « 1;
/* Проверка дали битът в с[1] е изместен.
Ако е така, се прилага ИЛИ в другия край. */
if(rot.ch[1]) rot.ch[0 J = rot.ch[0] | 1;
*c = rot.ch[0];
}
Отговори 573
им
Упражнения
1. ttinclude <stdio.h>
int main(void)
{
int i, j, answer;
printf("Enter two integers: ");
scanf("%did", &i, &j);
answer = j ? i/j: 0;
printf("%d", answer);
return 0;
)
2. count = a>b ? 100 : 0;
1.8
Упражнения
2. x &= у;
3. ttinclude <stdio.h>
int main(void)
{
int i;
for(i=17; i<=1000; H=17)
printf("%d\n", i);
return 0;
11.9
Упражнения
1. ttinclude <stdio.h>
nit main(void)
int i, j, k;
for(i=0, j=-50, k=i+j; i<100; i++, j++, k=i+j)
printf("k = %d\n", k);
return 0;
2. 3
574 С - Практически самоучител
ПРОВЕРКА НА УМЕНИЯТА,
ПРЕДСТАВЕНИ В ГЛАВАТА
1. Спецификаторы register кара компилатора да предостави
най-бързия възможен достъп до следващата го променлива.
2 Спецификаторы const казва на компилатора. че нито едпа
конструкция от програмата не може да променя следваща-
та го променлива. Освен това параметьр, който е const
указател, пе може да се използва за промяна на обекта, со-
чен от указателя. Спецификаторы volatile указва на ком-
пилатора, че следващата го променлива може да променя
своята стойност по начини, конто не са изрично зададени
в програмата.
3. ttinclude <stdio.h>
int main(void)
{
register int i, sum;
sum = 0;
for(i=l; i<101; i++)
sum = sum + 1;
printf("%d", sum);
return 0;
4 Да, конструкцията e вярна. Тя създава друго име за типа
long double.
5. #include <stdio.h>
Uinclude <conio.h>
int main(void)
{ char chi, ch2;
char mask, 1;
printf (’’Enter two characters: ");
chi = getche ();
ch2 = getche ();
printf("Xn”);
mask = 1;
for(i=0; i<8; i++) {
if((mask & chi) && (mask & ch2))
printf("bits %d tne same\n", i);
mask <<= 1;
return 0;
Отговори 575
6. « и » са операторите съответно изместване вляво и
вдясно.
7. с += 10;
8. count = done ? 0 : 100;
9. Изброяване е списък от именувани целочислени констан-
ти. Ето изброяване на планетите:
enum planets {Mercury, Venus, Earth, Mars,
Jupiter, Saturn, Neptune, Uranus,
Pluto) ;
Проверка на натрупаните умения
1. ttinclude <stdio.h>
void show_binary(unsigned u) ;
int main(void)
{
unsigned char ch, tl, t2;
ch = 100;
show_binary(ch);
t1 = ch;
t2 = ch;
tl <<= 4;
t2 »= 4;
Ch = tl I t2;
show_binary(ch);
return 0;
void show_binary(unsigned u)
{
unsigned n;
for(n=128; n>0; n=n/2)
if(u & n) prinnf("1 ");
else printf("0 ");
printf("\n");
}
576 С - Практически самоучител
2. ^include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
FILE *in;
unsigned char ch;
if(argc1=2) {
printf("Usage: code <in>\n");
exit (1) ;
if((in = fopen(argv[1], "rb”))==NULL) {
printf("Cannot open input file.Xn");
exit (1);
while(!feof(in)) {
ch = fgetc (in);
if(!feof(in)) put char(~ch);
fclose(in);
return 0;
3. Да, всеки тип даняи може да се зададе като register. За ня-
кои типове обаче това може да няма ефект.
4. /* прост компютърен телефонен указател. */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX 100
struct address {
char street[40];
char city[40],
char state(3];
char zip[12];
struct phone_type {
char name[40];
int areacoder-
char number[9j;
struct address addr;
) phone[MAX];
int loc =0;
Отговори 577
int menu(void);
void enter(void);
void Load(void);
void save(void);
void find(void);
int main(void)
{
register int choice;
do {
choice = menu();
switch(choice) {
case 1: enter();
break;
case 2: find();
break;
case 3: save ();
break;
case 4: load();
} while(choice 1=5);
return 0;
}
/* Получаваие на набора от менюто. */
menu(void)
{
register int i;
char str[80];
printf("1. Enter names and numbers \n");
printf ("2. Find numbers\n");
printf("3. Save directory to disk\n");
printf("4. Load directory from diskin'');
printf("5. Quit\n");
do {
printf("Enter your choice: ");
gets (str);
i = atoi(str);
printf("\n");
} while (i<l | | i>5) ;
return i;
}
void enter(void)
{
char temp[80];
for(; loc<100; loc++) [
if (loc<i00) {
printf("Enter name: ");
gets(phone[loc].name);
if(!*phone[loc].name) break;
578 С - Практически самоучител
printf("Enter area code: ");
gets(temp);
phone [ loc j . areacode = atoi(temp);
printf("Enter number: ");
gets(phone[loc].number);
/* въвеждане на информадията за адреса */
printf("Enter street address: ");
gets(phone[loc] addr.street);
printf("Enter city: ");
gets(phone[loc].addr.city);
printf("Enter State: ");
gets(phone[loc].addr. state) ;
printf("Enter zip code: ");
gets(phone[loc].addr.zip);
}
}
void find(void)
{
char name[GO];
register int i;
printf("Enter name: ");
gets(name);
if(!*name) return;
for(i=0; i<100; i++)
if(!strcmp(name, phone[i].name)) {
printf("%s (%d) %s\n", phone[i].name,
phone[iJ.areacode, phone[i].number);
printf("%s\n%s %s %s\n", phone[i].addr.street,
phone[i].addr.city, phone[i].addr.state,
phone[i].addr.zip);
}
void load(void)
{
FILE *fp;
if((fp = fopen("phone", "rb"))==NULL) {
printf("Cannot open file.Xn");
exit (1);
}
loc = 0;
while(1feof(fp)) {
fread(&phone[loc], sizeof phone[loc], 1, fp);
loc++;
}
fclose(fp);
void save(void)
Отговори 579
FILE *fp;
register int i;
if((fp = fopen("phone", "wb"))==NULL) {
printf("Cannot open file.\n");
exit (1) ;
for(i=0; i<loc; i++) {
fwrite (Sphene I. i] , sizeof phonefi], 1, fp) ;
}
fclose(fp);
Глава 12
Преглед на знанията
1. Модифицирането на променлива чрез register кара компи-
латора да я съхранява по такъв начин, че доегьпът да ноя
да е възможно най-бърз. За int и char това обикновено оз-
начава в регистрите на процесора.
2. Тъй като i е декларирана като const, функцията не може да
променя обекта, сочен от нея.
3. а. 1100 0100
Ь. 11111111
с. 011 1011
4 ttinclude <stdio.h>
int main(void)
{
int i;
printf("Enter a number: ");
scanf("%d", &i);
printf("Doubled: %d\n", i « 1);
printf("Halved: %d\n", i >> I);
return 0;
}
5. a = b = c = 1;
max = a<b ? 100 : 0;
i *= 2;
580 С - Практически самоучител
6. Модификаторы extern обикновено сс използва за инфор-
миране на компилатора, че глобалната променлива е де-
финирана в друг файл. Поставянето на extern пред декла-
рация на променлива казва на компилатора, че променли-
вата е дефинирана другаде, но текущият файл може да я
използва.
12.1
УПРАЖНЕНИЯ
1. ttdefine RANGE(i, min, max) ((i)<(min)) I I
( (i) > (max) ) ? 1 : 0
2. ^include <stdio.h>
fcdeflne ABS(i) (i)<0 ? -(i) : i
int main(void)
{
printf("%d %d", ABS(-l), ABS(l));
return 0;
12.2
Упражнения
1. #include <stdio.h>
^define INT 0
ttdefine FLOAT 1
ttdefine PWR_TYPE INT
int main(void)
(
int e;
# if PWR_TYPE==FLOAT
double base, result;
# elif PWR _TYPE==INT
int base, result;
I)endj f
# if PWR_TYPE==FLOAT
printf("Enter floating point base: ");
scanf("%lf", &base);
# elif PWR_TYPE==INT
printf("Enter integer base: ");
scanf("%d", &base);
ftendif
printf("Enter integer exponent (greater than 0): ");
scanf("%d", ie);
result = 1;
Отговори 581
for(; e; e--)
result = result * base;
ft if PWR_TYPE==FLOAT
printf("Result: %f", result);
#elif PWR_TYPE==INT
printf("Result: %d", result);
Sendif
return 0;
}
2. He. He можете да използвате израз като !М1КЕ с #ifd
Ето двете възможни решения:
#ifndef MIKE
#endif
/* или */
#if !defined MIKE
#endi f
12.5
12.6
Упражнения
2. Програмата отпечатва one two.
Упражнения
2. ttinclude <stdio.h>
^include <stdlrb.h>
int comp(const void *i, const void *j);
int main(void)
{
int sort[100], i, key;
int *p;
for(i=0; i<100; i++)
sort[i] = rand();
qsort(sort, 100, sizeof(int), comp);
for(i=Q; i<100; i++)
582 С - Практически самоучител
printf("%d\n", sort Гi]);
printf("Enter number to find: ");
scanf("%d", &key);
p = bsearch(&key, sort, 100, sizeof(int), comp);
if (p) printf("Number is in array.\n");
else printf("Number not found.\n");
return 9;
int comp(const void *i, const void *j)
{
return *(int*)i - *(int*)j;
3 ttinclude <stdio.h>
int sum(inc a, int b),
int subtract(int a, int b) ;
int muKint a, int b) ,-
int div(int a, int b);
int modulus (int a, int. b) ;
/* инициализиране на масива от указатели */
int(*p[5]) (int х, int у) = {
sum, subtract, mul, div, modulus
} ,
int main(void)
(
int result;
int i, j, op;
printf("Enter two numbers: ");
scanf("%d%d", &i, &j);
printf("0: add, 1: subtract, 2: multiply, 3:
divide, ");
printf(”4: modulus\n");
do {
printf("Enter number of operation: ");
scanf("%d", &op);
} while(op<0 || op>4);
result = (*рГор]) (i, j);
printf("%d", result),
return 0;
}
int sum(int a, int b)
(
return a+b;
}
Отговори 583
int subtract(int a, int b)
{
return a-b;
}
int mul(int a, int b)
{
return a*b;
}
inc div(int a, int b)
{
if(b) return a/b;
else return 0;
}
int modulus(inc a, int b)
{
if(b) return a%b;
else return 0;
}
12-7
Упражнения
2. ftinclude <stdio.h>
ftinclude <stdlib.h>
int main(void)
r
1
int *d, i;
p = malloc(10*sizeof(int)} ;
if(!p) {
printf ( "Allocation Error") ;
exit(1);
}
for(i=0; i<10; i + +) p[ij = i+1;
for(i=0; i<10; i++) printf("%d ", *(p+i));
free(p);
return 0;
3. Конструкцията
*p = malloc(10);
трябва да e
p = malloc(10);
534 G - Практически самоучител
Освен това стойността, върлага от malloc(), не се прове-
рява дали е валиден указател.
Проверка на уменията,
ПРЕДСТАВЕНИ В ГЛАВАТА
1. Ако зададете името на файла в ъглови скоби, компилата-
рът търси файла по имплементационно-зависим начин.
Когато заградите името на файл в кавички, компилаторът
първо опитва някакъв друг имплементационно-зависим
начин да намери файла. Ако това не стане, той започва
търсенето отновэ, все едно че името е в ъглови скоби
2. # if def DEBUG
if(!(j%2)l {
printf ("j = fcdXn", j);
j = 0;
}
ttendif
3. #if DEBUG=-1
if('(j%2)) {
printf("j = %d\n", j);
j = 0;
}
Uendif
4. За да отдефинирате име на макрос, използвайте #undef
5. _ _FILE___е предварително дефиниран макрос, съдържащ
името на соре файла, който се компилира в момента
6. Операторът # прсвръща следващият го аргумент в низ в
кавички. Операторът ## конкатеиира два аргумента.
7. ^include <stdio.h>
tfinclude <stdlib.h>
^include <string.h>
int comp(const void *i, const void *j);
int main(void)
{
char str[] = "this is a test of qsort";
qsort(str, strlen(str), 1, comp);
printf (str) ;
return 0;
int ccmp(const void *i, const void *j)
Отговори 585
{
return *(char*)i - *(char*)j;
}
8. ftinclude <stdio.h>
ftinclude <stdlib.h>
int main(void)
{
double *p;
p = malloc(sizeof(double));
if(Ip) (
printf (’'Allocation Error”);
ex i t (1);
}
*p = 99.01;
printf("%f“, *p);
free(p);
return 0;
Проверка на натрупаните умения
1. /* Електронна картотека. */
ftinclude <stdio.h>
ftinclude <string.h>
ftinclude <stdlib.h>
ftdefine MAX ICO
int menu(void);
void display(int i) ;
void author._search(void) ;
void title_search(void);
void enter(void);
void save(void);
void loadivoid);
struct catalog {
char name[80); /* ине на автор */
char title[80]; /* заглавие */
char pub[8Q]; /* издател */
unsigned date; /* пата на изданане */
unsigned char ed; /* издание */
} *oat[MAX]; /* оЬърнете внимание, че тук се
дефинира масив от указатели */
int top = 0; /* последнего използвано място */
int main(void)
586 С - Практически самоучител
int choice;
load(); /* прочитане на каталога */
do {
choice ~ menu();
switch(choice) {
case 1: enter)); /* въвеждане на книги */
break;
case 2: author search)); /* търсене no
автор */
break;
case 3: title search)); /* търсене no
заглавие */
break;
case 4: save));
}
} while(choice!=5);
return 0;
)
/* Връщане на избора от менюто. */
int menu(void)
{
int i;
char str[80];
printf("Card Catalog:\n");
printf(" 1. Enter\n");
printf(" 2. Search by Author\n");
printf(" 3. Search by Title\n");
printf(" 4. Save cataloq\n");
printf(" 5. Quit\n");
do {
printf("Choose your selection: ");
gets (str);
i = atoi (str);
printf("\n");
} while(i<l II i>5);
return i;
)
/* Въвеждане на книги в базата данни. */
void enter(void)
(
int i ;
char temp[80];
for(i=top; i<MAX; i++){
/* заделяне на памет за информацията за
книгите */
cat[i] = malloc(sizeof(struct catalog));
if(!cat[i]) {
Отговори 587
print.ffOut of memory. \n") ;
return;
}
printf("Enter author name (ENTER to quit):
gets(cat[i]->name);
if(1*cat[i]->name) break;
printf (''Enter title: ");
gets(cat[i]->title);
printf("Enter publisher: ");
gets(cat[i]->pub);
printf("Enter copyright date: ");
gets(temp);
cat[i]->date = (unsigned) atoi(temp);
printf("Enter edition: ");
gets(temp);
cat[i]->ed = (unsigned char) atoi(temp);
}
top = i;
}
/* Търсене по автор */
void author_search(void)
{
char name[80];
int i, found;
printf("Name: ");
gets(name);
found = 0;
fcr( i=0; i<top; i++)
if(!stremp(name, cat[i]->name)) {
display(i);
found = 1;
printf("\n");
}
if(!found) printf("Not Foundin'');
}
/* Търсене по заглавие. */
void title_search(void)
{
char title [80];
int i, found;
printf("Title: ");
gets (title);
found = 0;
for(i=0; i<top; i++)
if(!stremp(title, cat[i]->title)) {
display(i);
found = 1;
printf("\n");
}
588 С - Практически самоучител
if(!found) printf("Mot Found\n");
}
/* Изписване на елемент от картотеката. */
void display(int i)
{
printf("%s\n", cat[i]->titie);
printf ("by %s\n", catfi]->name);
printf("Published by %s\n", cat[i]->pub);
printf("Copyright: %a, %u edition\n",
cat[i]->date, cat[i]->ed);
}
/* Зареждане на файла на картотеката. */
void load(void)
{
FILE *fp;
int i;
if((fp = fopen("catalog", "rb"))==NbLL) {
printf("Catalog file not on disk.\n");
return;
if (fread(&Lop, sizeof top, 1, fp) ! = 1) {
/* прочитане на Ьрся */
printf("Error reading count.\n");
exit (1);
for(i=C; i<top; i++) {
cat[i] = malloc(sizeof(struct catalog));
if ( !cat f i]) {
printf("Out of memory.\n");
top = i-1;
break;
}
if(fread(cat[r], sizeof(struct catalog),
1, fp)1= 1) (
printf("Error reading catalog data \n");
exit(1);
)
}
fclose (fp);
}
/* Записване на файла на картотеката. */
void save(void)
{
FILE *fp;
int i ;
if((fp = fopen ( "catalog" , "wb" ) ) ==N(JLL) {
printf("Cannot open catalog file.\n");
exit (1);
Отговори 589
}
if(fwrite(&top, sizeof top, 1, fp) != 1) {
/* записване на броя */
printf("Error writing count.\n");
exi t (1) ;
for(i=0; i<top; i + + )
if(fwrite(cat[i], sizeof(struct catalog)
1, fp)! = 1) {
printf("Error writing catalog data.\n"
exit(1);
}
fclose (fp);
2. ftinclude <stdio.h>
ftdefine CODE_IT(ch) ~ch
int main(void)
{
int ch;
printf("Enter a character: ");
ch - getchar();
printf("%c coded is %c", ch, CODE_IT(ch));
return 0;
Практически
самоучител
Трею «здание
СЛЕДВАИТЕ СЪВЕТИТЕ 1/1 ПРИМЕРИТЕ,
ПРЕДСТАВЕНИ В СЕРИЯТА „ПРАКТИЧЕСКИ
САМОУЧИТЕЛ", ЗА ДА ОВЛАДЕЕТЕ
ПРОФЕСИОНАЛНОТО ПРОГРАМИРАНЕ
Най-успешният и доказан метод
за научаване на C++
Авторы на бестсельри Хьрб Шилдг е обучил милиони хора да програмират на най-популярния днес език.
В тоеа трето издание на бестселъра С - Практически самоучител, неговият доказан план за успех е об-
новен, разширен и усъвършенстван. Наистина няма по-добър начин да научите С.
Написана с безкомпромисна яснота и внимание кьм детаила, С - Пр актически самоучител заппчва с ос-
новите, разглежда всички важни моменти и заеършва с някои от наи-професионапните възможности на
С В книгата има многобройни примери, самостоятелни тестове за уменията ви и упражнения. Отговори-
те на всички упражнения са приложени в края на книгата, за да можете да прнверявате доколко сте ус-
воили представените знания.
С помощта на тази книга: Нггучабате структурата на една С програма Използвате конзолен и файлов Вход/изход
Научабате Всички контролни конструкции 8 С ИзследВате типоВе данни, променлиВите и изразите Работите със структури и обединения ИзследВате разширени типоВе данни и оператори
ИзследВате масиВите и назовете РазучаВзте предпроцеса на С
Научабате да работите с указатели 0гг4срц8ате мошта на функциите Научабате как на С да създабате Windows програми
Но тоба, което е най-добре - ше се учите по доказалия се метод на Шилдт: бисокоефектибни-
ят и ясен подход, помогнал на милиони да станат истински програмисти. Този подход би даба
бъзмоЖност сами да определяте сксростта на обучение Дори ако преди С би се е струбал
объркбащ, ясната и последобателна презентация на Шилдт ще напраби дори професионалните
теми лесни за разбиране.
Тъй като С фирмира оснобата на C++, след като забършите С - Практически самоучител, ще
имате необходимите умения за преминабане към C++.
Хърб Шилдт е сбетобноизбестен абтор на книги за програмиране с над 2 милиона продадени
книги. Той е експерт по С, C+ + , Java и Windows програмиране.
□SBDRNE
СОФТПРЕС