DevioLabавтоматичне керування капіталом
ПРАЦЮЄ 24/7
КРИПТОВАЛЮТИ · АКЦІЇ · 10 БІРЖ

Портфель, який керується автоматично

DevioLab підбирає стратегії, відстежує ринки та автоматизує купівлю і продаж активів. Кількість доступних криптовалют, акцій і торгових можливостей зростає щодня.

10підключених біржCrypto + Stocksв одному портфелі24/7автоматичний контроль

Хрестоматія з програмування на C в Unix

Інформація про навчальний заклад

ВУЗ:
Одесский национальный морской университет
Інститут:
Інституті комп'ютерних систем
Факультет:
Факультет автоматики і комп'ютерних систем управління
Кафедра:
Кафедра програмного забезпечення

Інформація про роботу

Рік:
2026
Тип роботи:
Навчальний посібник
Предмет:
Програмування
Група:
КН-11
Коротко про документ

Про що ця робота

Ця хрестоматія містить задачі, приклади, алгоритми та поради щодо використання мови програмування C в середовищі операційної системи UNIX. Видання призначене для практичного закріплення навичок програмування та вивчення типових прийомів і розв'язання складних завдань.

У роботі розглядаються

  • Мова програмування C
  • Операційна система UNIX
  • Алгоритми та структури даних
  • Системні виклики
  • Текстова обробка
  • Екранні бібліотеки
  • Управління пам'яттю

Зміст і розділи

  1. Напутствие в качестве вступления
  2. Простые программы и алгоритмы
  3. Массивы, строки, указатели
  4. Мобильность и машинная зависимость программ
  5. Работа с файлами
  6. Структуры данных
  7. Системные вызовы и взаимодействие с UNIX
  8. Текстовая обработка
  9. Экранные библиотеки и работа с видеопамятью
  10. Приложения
  11. Примеры
  12. Список литературы
Для кого:Студенти технічних спеціальностей, програмісти-початківці та розробники, які вивчають мову C та ОС UNIX.
Викладачі:Подра

Ключові поняття

CUNIXуказателимассивысистемные вызовыалгоритмыфункциикомпиляцияфайлыпроцессыпамятьтерминал

Частина тексту файла (без зображень, графіків і формул):

Хрестоматия по программированию на Си в Unix © Copyright . 1992-95 Email: abs@opentech.olvit.ru Txt version is located at Ученью не один мы посвятили год, Потом других учить пришел и нам черед. Какие ж выводы из этой всей науки? Из праха мы пришли, нас ветер унесет. Омар Хайям Оглавление. 0. Напутствие в качестве вступления. .......................................... 1 1. Простые программы и алгоритмы. Сюрпризы, советы. ......... 3 1.11. Треугольник из звездочек. ............................................... 6 1.34. Простые числа. .......................................................... 10 1.36. Целочисленный квадратный корень. ........................................ 12 1.39. Вычисление интеграла по Симпсону. ....................................... 14 1.49. Сортировка Шелла. ....................................................... 20 1.50. Быстрая сортировка. ..................................................... 21 1.67. Функция чтения строки. .................................................. 28 1.88. Перестановки элементов. ................................................. 38 1.117. Схема Горнера. ......................................................... 58 1.137. Системная функция qsort - формат вызова. ............................... 67 1.146. Процесс компиляции программ. ........................................... 76 2. Массивы, строки, указатели. ................................................ 81/100 2.58. Функция bcopy. .......................................................... 108 2.59. Функция strdup. ......................................................... 111 2.61. Упрощенный аналог функции printf. ....................................... 112 3. Мобильность и машинная зависимость программ. Проблемы с русскими буквами... 122/154 3.9. _ctype[] .................................................................. 126 3.12. Программа транслитерации: tr. ........................................... 129 3.16. Функция записи трассировки (отладочных выдач) в файл. ................... 132 3.18. Условная компиляция: #ifdef .............................................. 132 4. Работа с файлами. .......................................................... 137 4.39. Быстрый доступ к строкам файла. ......................................... 161 4.45. Эмуляция основ библиотеки STDIO, по мотивам 4.2 BSD. .................... 165 5. Структуры данных. .......................................................... 172 5.12. Отсортированный список слов. ............................................ 180 5.16. Структуры с полями переменного размера. ................................. 183 5.17. Список со "старением". .................................................. 184 6. Системные вызовы и взаимодействие с UNIX. ............................ 186 6.1. Файлы и каталоги. ........................................................ 189 6.1.1. Определение типа файла. ................. 189 6.1.3. Выдача неотсортированного содержимого каталога (ls). ................... 191 6.1.5. Рекурсивный обход каталогов и подкаталогов. ............................ 192 6.2. Время в UNIX. ............................................................ 198 6.2.9. Функция задержки в микросекундах. ...................................... 201 6.3. Свободное место на диске. ................................................ 207 6.4. Сигналы. ................................................................. 212 6.4.3. Функция sleep. ......................................................... 217 6.5. Жизнь процессов. ......................................................... 219 6.6. Трубы и FIFO-файлы. ...................................................... 230 6.7. Нелокальный переход. ..................................................... 233 6.8. Хозяин файла, процесса, и проверка привелегий. ........................... 235 6.9. Блокировка доступа к файлам. ............................................. 240 6.10. Файлы устройств. ........................................................ 244 6.10.1. Определение текущего каталога: функция getwd. ......................... 252 6.10.2. Канонизация полного имени файла. ...................................... 257 6.11. Мультиплексирование ввода-вывода ......................................... 259 6.11.1. Мультиплексирование ввода из нескольких файлов. ....................... 259 6.11.2. Программа script. ..................................................... 261 6.12. Простой интерпретатор команд. ........................................... 271 7. Текстовая обработка. ....................................................... 283 7.12. Программа uniq. ......................................................... 285 7.14. Расширение табуляций в пробелы, функция untab. .......................... 285 7.15. Функция tabify. ......................................................... 285 7.25. Поиск методом половинного деления. ...................................... 288 7.31. Программа печати в две полосы. .......................................... 292 7.33. Инвертирование порядка строк в файле. ................................... 296 7.34. Перенос неразбиваемых блоков текста. .................................... 298 7.36. Двоичная сортировка строк при помощи дерева. ............................ 300 7.41. Функция match. .......................................................... 309 7.43. Функция контекстной замены по регулярному выражению. .................... 313 7.44. Алгоритм быстрого поиска подстроки в строке. ............................ 316 7.52. Коррекция правописания. ................................................. 321 7.67. Калькулятор-1. .......................................................... 330 7.68. Калькулятор-2. .......................................................... 336 8. Экранные библиотеки и работа с видеопамятью. ................ 348/458 8.1. Осыпающиеся буквы. ....................................................... 350 8.13. Использование библиотеки termcap. ....................................... 359 8.17. Разбор ESC-последовательностей с клавиатуры. ............................ 371 9. Приложения. ................................................................ 391/518 9.1. Таблица приоритетов операций языка C++ .................................... 391 9.2. Правила преобразований типов. ............................................ 391 9.3. Таблица шестнадцатеричных чисел (HEX). ................................... 392 9.4. Таблица степеней двойки. ................................................. 393 9.5. Двоичный код: внутреннее представление целых чисел. ...................... 393 10. Примеры. .................................................................. 394/523 Пример 1. Размен монет. ...................................................... Пример 2. Подсчет букв в файле. .............................................. Пример 3. Центрирование строк. ............................................... Пример 4. Разметка текста для nroff. ......................................... Пример 5. Инвертирование порядка слов в строках. ............................. Пример 6. Пузырьковая сортировка. ............................................ Пример 7. Хэш-таблица. ....................................................... Пример 8. Простая база данных. ............................................... Пример 9. Вставка/удаление строк в файл. ..................................... Пример 10. Безопасный free, позволяющий обращения к автоматическим переменным. Пример 11. Поимка ошибок при работе с динамической памятью. .................. Пример 12. Копирование/перемещение файла. .................................... Пример 13. Обход поддерева каталогов в MS DOS при помощи chdir. .............. Пример 14. Работа с сигналами. ............................................... Пример 15. Управление скоростью обмена через линию. .......................... Пример 16. Просмотр файлов в окнах. .......................................... Пример 17. Работа с иерархией окон в curses. Часть проекта uxcom. ............ Пример 18. Поддержка содержимого каталога. Часть проекта uxcom. .............. Пример 19. Роллируемое меню. Часть проекта uxcom. ............................ Пример 20. Выбор в строке-меню. Часть проекта uxcom. ......................... Пример 21. Редактор строки. Часть проекта uxcom. ............................. Пример 22. Выбор в прямоугольной таблице. Часть проекта uxcom. ............... Пример 23. UNIX commander - простой визуальный Шелл. Головной модуль проекта uxcom. .. Пример 24. Общение двух процессов через "трубу". ............................. Пример 25. Общение процессов через FIFO-файл. ................................ Пример 26. Общение процессов через общую память и семафоры. .................. Пример 27. Протоколирование работы программы при помощи псевдотерминала и процессов. ..... Пример 28. Оценка фрагментированности файловой системы. ...................... Пример 29. Восстановление удаленного файла в BSD-2.9. ........................ Пример 30. Копирование файлов из MS DOS в UNIX. .............................. Пример 31. Программа, печатающая свой собственный текст. ..................... Пример 32. Форматирование текста Си-программы. ...............................         11. Список литературы. 1) Б.Керниган, Д.Ритчи, А.Фьюер. Язык программирования Си. Задачи по языку Си. - М.: Финансы и статистика, 1985. 2) М.Уэйт, С.Прата, Д.Мартин. Язык Си. Руководство для начинающих. - М.: Мир, 1988. 3) М.Болски. Язык программирования Си. Справочник. - М.: Радио и связь, 1988. 4) Л.Хэнкок, М.Кригер. Введение в программирование на языке Си. - М.: Радио и связь, 1986. 5) М.Дансмур, Г.Дейвис. ОС UNIX и программирование на языке Си. - М.: Радио и связь, 1989. 6) Р.Берри, Б.Микинз. Язык Си. Введение для программистов. - М.: Финансы и ста- тистика, 1988. 7) М.Беляков, А.Ливеровский, В.Семик, В.Шяудкулис. Инструментальная мобильная опе- рационная система ИНМОС. - М.: Финансы и статистика, 1985. 8) К.Кристиан. Введение в операционную систему UNIX. - М.: Финансы и статистика, 1985. 9) Р.Готье. Руководство по операционной системе UNIX. - М.: Финансы и статистика, 1986. 10) М.Банахан, Э.Раттер. Введение в операционную систему UNIX. - М.: Радио и связь, 1986. 11) С.Баурн. Операционная система UNIX. - М.: Мир, 1986. 12) П.Браун. Введение в операционную систему UNIX. - М.: Мир, 1987. 13) M.Bach. The design of the UNIX operating system. - Prentice Hall, Englewood Cliffs, N.J., 1986. 14) S.Dewhurst, K.Stark. Programming in C++. - Prentice Hall, 1989. 15) M.Ellis, B.Stroustrup. The annotated C++ Reference Manual. - Addison-Wesley, 1990. 0 0. Напутствие в качестве вступления. Ум подобен желудку. Важно не то, сколько ты в него вложишь, а то, сколько он сможет переварить. В этой книге вы найдете ряд задач, примеров, алгоритмов, советов и стилистических замечаний по использованию языка программирования "C" (Си) в среде операционной системы UNIX. Здесь собраны этюды разной сложности и "штрихи к портрету" языка Си. Также описаны различные "подводные камни" на которых нередко терпят крушение новички в Си. В этом смысле эту книгу можно местами назвать "Как не надо программировать на Си". В большинстве случаев в качестве платформы используется персональный компьютер IBM PC с какой-либо системой UNIX, либо SPARCstation 20 с системой Solaris 2 (тоже UNIX svr4), но многие примеры без каких-либо изменений (либо с минимумом таковых) могут быть перенесены в среду MS DOS[**], либо на другой тип машины с системой UNIX. Это ваша ВТОРАЯ книга по Си. Эта книга не учебник, а хрестоматия к учебнику. Она не является ни систематическим курсом по Си, ни справочником по нему, и предназ- начена не для одноразового последовательного прочтения, а для чтения в несколько про- ходов на разных этапах вашей "зрелости". Поэтому читать ее следует вместе с "настоя- щим" учебником по Си, среди которых наиболее известна книга Кернигана и Ритчи. Эта книга - не ПОСЛЕДНЯЯ ваша книга по Си. Во-первых потому, что кое-что в языке все же меняется со временем, хотя и настал час, когда стандарт на язык Си наконец принят... Но появился язык C++, который развивается довольно динамично. Еще есть Objective-C. Во-вторых потому, что есть библиотеки и системные вызовы, которые раз- виваются вслед за развитием UNIX и других операционных систем. Следующими вашими (настольными) книгами должны стать "Справочное руководство": man2 (по системным вызо- вам), man3 (по библиотечным функциям). Мощь языка Си - в существующем многообразии библиотек. Прошу вас с первых же шагов следить за стилем оформления своих программ. Делайте отступы, пишите комментарии, используйте осмысленные имена переменных и функций, отделяйте логические части программы друг от друга пустыми строками. Помните, что "лишние" пробелы и пустые строки в Си допустимы везде, кроме изображений констант и имен. Программы на Си, набитые в одну колонку (как на FORTRAN-e) очень тяжело читать и понимать. Из-за этого бывает трудно находить потерянные скобки { и }, потерянные символы `;' и другие ошибки. Существует несколько "школ" оформления программ - приглядитесь к примерам в этой книге и в других источниках - и выберите любую! Ничего страшного, если вы будете мешивать эти стили. Но - ПОДАЛЬШЕ ОТ FORTRAN-а !!! Программу можно автоматически сформатировать к "каноническому" виду при помощи, например, программы cb. cb < НашФайл.c > /tmp/$$ mv /tmp/$$ НашФайл.c но лучше сразу оформлять программу правильно. Выделяйте логически самостоятельные ("замкнутые") части программы в функции (даже если они будут вызываться единственный раз). Функции - не просто средство избежать повторения одних и тех же операторов в тексте программы, но и средство структурирования процесса программирования, делающее программу более понятной. Во- первых, вы можете в другой программе использовать текст уже написанной вами ранее функции вместо того, чтобы писать ее заново. Во-вторых, операцию, оформленную в виде функции, можно рассматривать как неделимый примитив (от довольно простого по смыслу, вроде strcmp, strcpy, до довольно сложного - qsort, malloc, gets) и забыть о его внутреннем устройстве (это хорошо - надо меньше помнить). ____________________ [**] MS DOS - торговый знак фирмы Microsoft Corporation. (читается "Майкрософт"); DOS - дисковая операционная система. Не гонитесь за краткостью в ущерб ясности. Си позволяет порой писать такие выра- жения, над которыми можно полчаса ломать голову. Если же их записать менее мудрено, но чуть длиннее - они самоочевидны (и этим более защищены от ошибок). В системе UNIX вы можете посмотреть описание любой команды системы или функции Си, набрав команду man названиеФункции (man - от слова manual, "руководство"). Еще одно напутствие: учите английский язык! Практически все языки программирова-ния используют английские слова (в качестве ключевых слов, терминов, имен переменных и функций). Поэтому лучше понимать значение этих слов (хотя и восприятие их как просто неких символов тоже имеет определенные достоинства). Обратно - программирование на Си поможет вам выучить английский. По различным причинам на территории России сейчас используется много разных восьмибитных русских кодировок. Среди них: КОИ-8 ISO 8859/5 Это американский стандарт на русскую кодировку. А русские программисты к ее разработке не имеют никакого отношения. Ею пользуется большинство коммерческих баз данных. Microsoft 1251 Это та кодировка, которой пользуется Microsoft Windows. Возможно, что именно к этой кодировке придут и UNIX системы (гипотеза 1994 года). Альтернативная кодировка для MS DOS Русская кодировка с псевдографикой, использовавшаяся в MS DOS. Кодировка для Macintosh Это великое "разнообразие" причиняет массу неудобств. Но, господа, это Россия - что значит - широта души и абсолютный бардак. Relax and enjoy. Многие примеры в данной книге даны вместе с ответами - как образцами для подра- жания. Однако мы надеемся, что Вы удержитесь от искушения и сначала проверите свои силы, а лишь потом посмотрите в ответ! Итак, читая примеры - делайте по аналогии. 1. Простые программы и алгоритмы. Сюрпризы, советы. 1.1. Составьте программу приветствия с использованием функции printf. По традиции принято печатать фразу "Hello, world !" ("Здравствуй, мир !"). 1.2. Найдите ошибку в программе #include <stdio.h> main(){ printf("Hello, world\n"); } Ответ: раз не объявлено иначе, функция main считается возвращающей целое значение (int). Но функция main не возвращает ничего - в ней просто нет оператора return. Корректно было бы так: #include <stdio.h> main(){ printf("Hello, world\n"); return 0; } или #include <stdio.h> void main(){ printf("Hello, world\n"); exit(0); } а уж совсем корректно - так: #include <stdio.h> int main(int argc, char *argv[]){ printf("Hello, world\n"); return 0; } 1.3. Найдите ошибки в программе #include studio.h main { int i i := 43 print ('В году i недель') } 1.4. Что будет напечатано в приведенном примере, который является частью полной программы: int n; n = 2; printf ("%d + %d = %d\n", n, n, n + n); 1.5. В чем состоят ошибки? if( x > 2 ) then x = 2; if x < 1 x = 1; Ответ: в Си нет ключевого слова then, условия в операторах if, while должны браться в ()-скобки. 1.6. Напишите программу, печатающую ваше имя, место работы и адрес. В первом варианте программы используйте библиотечную функцию printf, а во втором - puts. 1.7. Составьте программу с использованием следующих постфиксных и префиксных операций: a = b = 5 a + b a++ + b ++a + b --a + b a-- + b Распечатайте полученные значения и проанализируйте результат. 1.8. Цикл for ________________________________________________________________________________ for(INIT; CONDITION; INCR) BODY ________________________________________________________________________________ INIT; repeat: if(CONDITION){ BODY; cont: INCR; goto repeat; } out: ; Цикл while ________________________________________________________________________________ while(COND) BODY ________________________________________________________________________________ cont: repeat: if(CONDITION){ BODY; goto repeat; } out: ; Цикл do ________________________________________________________________________________ do BODY while(CONDITION) ________________________________________________________________________________ cont: repeat: BODY; if(CONDITION) goto repeat; out: ; В операторах цикла внутри тела цикла BODY могут присутствовать операторы break и continue; которые означают на наших схемах следующее: #define break goto out #define continue goto cont 1.9. Составьте программу печати прямоугольного треугольника из звездочек * ** *** **** ***** используя цикл for. Введите переменную, значением которой является размер катета тре- угольника. 1.10. Напишите операторы Си, которые выдают строку длины WIDTH, в которой сначала содержится x0 символов '-', затем w символов '*', и до конца строки - вновь символы '-'. Ответ: int x; for(x=0; x < x0; ++x) putchar('-'); for( ; x < x0 + w; x++) putchar('*'); for( ; x < WIDTH ; ++x) putchar('-'); putchar('\n'); либо for(x=0; x < WIDTH; x++) putchar( x < x0 ? '-' : x < x0 + w ? '*' : '-' ); putchar('\n'); 1.11. Напишите программу с циклами, которая рисует треугольник: * *** ***** ******* ********* Ответ: /* Треугольник из звездочек */ #include <stdio.h> /* Печать n символов c */ printn(c, n){ while( --n >= 0 ) putchar(c); } int lines = 10; /* число строк треугольника */ void main(argc, argv) char *argv[]; { register int nline; /* номер строки */ register int naster; /* количество звездочек в строке */ register int i; if( argc > 1 ) lines = atoi( argv[1] ); for( nline=0; nline < lines ; nline++ ){ naster = 1 + 2 * nline; /* лидирующие пробелы */ printn(' ', lines-1 - nline); /* звездочки */ printn('*', naster); /* перевод строки */ putchar( '\n' ); } exit(0); /* завершение программы */ } 1.12. В чем состоит ошибка? main(){ /* печать фразы 10 раз */ int i; while(i < 10){ printf("%d-ый раз\n", i+1); i++; } } Ответ: автоматическая переменная i не была проинициализирована и содержит не 0, а какое-то произвольное значение. Цикл может выполниться не 10, а любое число раз (в том числе и 0 по случайности). Не забывайте инициализировать переменные, возьмите описание с инициализацией за правило! int i = 0; Если бы переменная i была статической, она бы имела начальное значение 0. В данном примере было бы еще лучше использовать цикл for, в котором все операции над индексом цикла собраны в одном месте - в заголовке цикла: for(i=0; i < 10; i++) printf(...); 1.13. Вспомогательные переменные, не несущие смысловой нагрузки (вроде счетчика повторений цикла, не используемого в самом теле цикла) принято по традиции обозначать однобуквенными именами, вроде i, j. Более того, возможны даже такие курьезы: main(){ int _ ; for( _ = 0; _ < 10; _++) printf("%d\n", _ ); } основанные на том, что подчерк в идентификаторах - полноправная буква. 1.14. Найдите 2 ошибки в программе: main(){ int x = 12; printf( "x=%d\n" ); int y; y = 2 * x; printf( "y=%d\n", y ); } Комментарий: в теле функции все описания должны идти перед всеми выполняемыми операторами (кроме операторов, входящих в состав описаний с инициализацией). Очень часто после внесения правок в программу некоторые описания оказываются после выполняемых операторов. Именно поэтому рекомендуется отделять строки описания переменных от выполняемых операторов пустыми строками (в этой книге это часто не делается для экономии места). 1.15. Найдите ошибку: int n; n = 12; main(){ int y; y = n+2; printf( "%d\n", y ); } Ответ: выполняемый оператор n=12 находится вне тела какой-либо функции. Следует внести его в main() после описания переменной y, либо переписать объявление перед main() в виде int n = 12; В последнем случае присваивание переменной n значения 12 выполнит компилятор еще во время компиляции программы, а не сама программа при своем запуске. Точно так же про- исходит со всеми статическими данными (описанными как static, либо расположенными вне всех функций); причем если их начальное значение не указано явно - то подразумевается 0 ('\0', NULL, ""). Однако нулевые значения не хранятся в скомпилированном выполняемом файле, а требуемая "чистая" память расписывается при старте программы. 1.16. По поводу описания переменной с инициализацией: TYPE x = выражение; является (почти) эквивалентом для TYPE x; /* описание */ x = выражение; /* вычисление начального значения */ Рассмотрим пример: #include <stdio.h> extern double sqrt(); /* квадратный корень */ double x = 1.17; double s12 = sqrt(12.0); /* #1 */ double y = x * 2.0; /* #2 */ FILE *fp = fopen("out.out", "w"); /* #3 */ main(){ double ss = sqrt(25.0) + x; /* #4 */ ... } Строки с метками #1, #2 и #3 ошибочны. Почему? Ответ: при инициализации статических данных (а s12, y и fp таковыми и являются, так как описаны вне какой-либо функции) выражение должно содержать только константы, поскольку оно вычисляется КОМПИЛЯТОРОМ. Поэтому ни использование значений переменных, ни вызовы функций здесь недопустимы (но можно брать адреса от переменных). В строке #4 мы инициализируем автоматическую переменную ss, т.е. она отводится уже во время выполнения программы. Поэтому выражение для инициализации вычисляется уже не компилятором, а самой программой, что дает нам право использовать переменные, вызовы функций и.т.п., то есть выражения языка Си без ограничений. 1.17. Напишите программу, реализующую эхо-печать вводимых символов. Программа должна завершать работу при получении признака EOF. В UNIX при вводе с клавиатуры признак EOF обычно обозначается одновременным нажатием клавиш CTRL и D (CTRL чуть раньше), что в дальнейшем будет обозначаться CTRL/D; а в MS DOS - клавиш CTRL/Z. Используйте getchar() для ввода буквы и putchar() для вывода. 1.18. Напишите программу, подсчитывающую число символов поступающих со стандартного ввода. Какие достоинства и недостатки у следующей реализации: #include <stdio.h> main(){ double cnt = 0.0; while (getchar() != EOF) ++cnt; printf("%.0f\n", cnt ); } Ответ: и достоинство и недостаток в том, что счетчик имеет тип double. Достоинство - можно подсчитать очень большое число символов; недостаток - операции с double обычно выполняются гораздо медленнее, чем с int и long (до десяти раз), программа будет работать дольше. В повседневных задачах вам вряд ли понадобится иметь счетчик, отличный от long cnt; (печатать его надо по формату "%ld"). 1.19. Составьте программу перекодировки вводимых символов со стандартного ввода по следующему правилу: a -> b b -> c c -> d ... z -> a другой символ -> * Коды строчных латинских букв расположены подряд по возрастанию. 1.20. Составьте программу перекодировки вводимых символов со стандартного ввода по следующему правилу: B -> A C -> B ... Z -> Y другой символ -> * Коды прописных латинских букв также расположены по возрастанию. 1.21. Напишите программу, печатающую номер и код введенного символа в восьмеричном и шестнадцатеричном виде. Заметьте, что если вы наберете на вводе строку символов и нажмете клавишу ENTER, то программа напечатает вам на один символ больше, чем вы набрали. Дело в том, что код клавиши ENTER, завершившей ввод строки - символ '\n' - тоже попадает в вашу программу (на экране он отображается как перевод курсора в начало следующей строки!). 1.22. Разберитесь, в чем состоит разница между символами '0' (цифра нуль) и '\0' (нулевой байт). Напечатайте printf( "%d %d %c\n", '\0', '0', '0' ); Поставьте опыт: что печатает программа? main(){ int c = 060; /* код символа '0' */ printf( "%c %d %o\n", c, c, c); } Почему печатается 0 48 60? Теперь напишите вместо int c = 060; строчку char c = '0'; 1.23. Что напечатает программа? #include <stdio.h> void main(){ printf("ab\0cd\nxyz"); putchar('\n'); } Запомните, что '\0' служит признаком конца строки в памяти, а '\n' - в файле. Что в строке "abcd\n" на конце неявно уже расположен нулевой байт: 'a','b','c','d','\n','\0' Что строка "ab\0cd\nxyz" - это 'a','b','\0','c','d','\n','x','y',z','\0' Что строка "abcd\0" - избыточна, поскольку будет иметь на конце два нулевых байта (что не вредно, но зачем?). Что printf печатает строку до нулевого байта, а не до закрывающей кавычки. Программа эта напечатает ab и перевод строки. Вопрос: чему равен sizeof("ab\0cd\nxyz")? Ответ: 10. 1.24. Напишите программу, печатающую целые числа от 0 до 100. 1.25. Напишите программу, печатающую квадраты и кубы целых чисел. 1.26. Напишите программу, печатающую сумму квадратов первых n целых чисел. 1.27. Напишите программу, которая переводит секунды в дни, часы, минуты и секунды. 1.28. Напишите программу, переводящую скорость из километров в час в метры в секундах. 1.29. Напишите программу, шифрующую текст файла путем замены значения символа (например, значение символа C заменяется на C+1 или на ~C ). 1.30. Напишите программу, которая при введении с клавиатуры буквы печатает на терминале ключевое слово, начинающееся с данной буквы. Например, при введении буквы 'b' печатает "break". 1.31. Напишите программу, отгадывающую задуманное вами число в пределах от 1 до 200, пользуясь подсказкой с клавиатуры "=" (равно), "<" (меньше) и ">" (больше). Для угадывания числа используйте метод деления пополам. 1.32. Напишите программу, печатающую степени двойки 1, 2, 4, 8, ... Заметьте, что, начиная с некоторого n, результат становится отрицательным из-за пере- полнения целого. 1.33. Напишите подпрограмму вычисления квадратного корня с использованием метода касательных (Ньютона): x(0) = a 1 a x(n+1) = - * ( ---- + x(n)) 2 x(n) Итерировать, пока не будет | x(n+1) - x(n) | < 0.001 Внимание! В данной задаче массив не нужен. Достаточно хранить текущее и предыду- щее значения x и обновлять их после каждой итерации. 1.34. Напишите программу, распечатывающую простые числа до 1000. 1, 2, 3, 5, 7, 11, 13, 17, ... /*#!/bin/cc primes.c -o primes -lm * Простые числа. */ #include <stdio.h> #include <math.h> int debug = 0; /* Корень квадратный из числа по методу Ньютона */ #define eps 0.0001 double sqrt (x) double x; { double sq, sqold, EPS; if (x < 0.0) return -1.0; if (x == 0.0) return 0.0; /* может привести к делению на 0 */ EPS = x * eps; sq = x; sqold = x + 30.0; /* != sq */ while (fabs (sq * sq - x) >= EPS) { /* fabs( sq - sqold )>= EPS */ sqold = sq; sq = 0.5 * (sq + x / sq); } return sq; } /* таблица прoстых чисел */ int is_prime (t) register int t; { register int i, up; int not_div; if (t == 2 || t == 3 || t == 5 || t == 7) return 1; /* prime */ if (t % 2 == 0 || t == 1) return 0; /* composite */ up = ceil (sqrt ((double) t)) + 1; i = 3; not_div = 1; while (i <= up && not_div) { if (t % i == 0) { if (debug) fprintf (stderr, "%d поделилось на %d\n", t, i); not_div = 0; break; } i += 2; /* * Нет смысла проверять четные, * потому что если делится на 2*n, * то делится и на 2, * а этот случай уже обработан выше. */ } return not_div; } #define COL 6 int n; main (argc, argv) char **argv; { int i, j; int n; if( argc < 2 ){ fprintf( stderr, "Вызов: %s число [-]\n", argv[0] ); exit(1); } i = atoi (argv[1]); /* строка -> целое, ею изображаемое */ if( argc > 2 ) debug = 1; printf ("\t*** Таблица простых чисел от 2 до %d ***\n", i); n = 0; for (j = 1; j <= i; j++) if (is_prime (j)){ /* распечатка в COL колонок */ printf ("%3d%s", j, n == COL-1 ? "\n" : "\t"); if( n == COL-1 ) n = 0; else n++; } printf( "\n---\n" ); exit (0); } 1.35. Составьте программу ввода двух комплексных чисел в виде A + B * I (каждое на отдельной строке) и печати их произведения в том же виде. Используйте scanf и printf. Перед тем, как использовать scanf, проверьте себя: что неверно в нижеприведенном операторе? int x; scanf( "%d", x ); Ответ: должно быть написано "АДРЕС от x", то есть scanf( "%d", &x ); 1.36. Напишите подпрограмму вычисления корня уравнения f(x)=0 методом деления отрезка пополам. Приведем реализацию этого алгоритма для поиска целочисленного квадратного корня из целого числа (этот алгоритм может использоваться, например, в машинной графике при рисовании дуг): /* Максимальное unsigned long число */ #define MAXINT (~0L) /* Определим имя-синоним для типа unsigned long */ typedef unsigned long ulong; /* Функция, корень которой мы ищем: */ #define FUNC(x, arg) ((x) * (x) - (arg)) /* тогда x*x - arg = 0 означает x*x = arg, то есть * x = корень_квадратный(arg) */ /* Начальный интервал. Должен выбираться исходя из * особенностей функции FUNC */ #define LEFT_X(arg) 0 #define RIGHT_X(arg) (arg > MAXINT)? MAXINT : (arg/2)+1; /* КОРЕНЬ КВАДРАТНЫЙ, округленный вниз до целого. * Решается по методу деления отрезка пополам: * FUNC(x, arg) = 0; x = ? */ ulong i_sqrt( ulong arg ) { register ulong mid, /* середина интервала */ rgt, /* правый край интервала */ lft; /* левый край интервала */ lft = LEFT_X(arg); rgt = RIGHT_X(arg); do{ mid = (lft + rgt + 1 )/2; /* +1 для ошибок округления при целочисленном делении */ if( FUNC(mid, arg) > 0 ){ if( rgt == mid ) mid--; rgt = mid ; /* приблизить правый край */ } else lft = mid ; /* приблизить левый край */ } while( lft < rgt ); return mid; } void main(){ ulong i; for(i=0; i <= 100; i++) printf("%ld -> %lu\n", i, i_sqrt(i)); } Использованное нами при объявлении переменных ключевое слово register означает, что переменная является ЧАСТО ИСПОЛЬЗУЕМОЙ, и компилятор должен попытаться разместить ее на регистре процессора, а не в стеке (за счет чего увеличится скорость обращения к этой переменной). Это слово используется как register тип переменная; register переменная; /* подразумевается тип int */ От регистровых переменных нельзя брать адрес: &переменная ошибочно. 1.37. Напишите программу, вычисляющую числа треугольника Паскаля и печатающую их в виде треугольника. C(0,n) = C(n,n) = 1 n = 0... C(k,n+1) = C(k-1,n) + C(k,n) k = 1..n n - номер строки В разных вариантах используйте циклы, рекурсию. 1.38. Напишите функцию вычисления определенного интеграла методом Монте-Карло. Для этого вам придется написать генератор случайных чисел. Си предоставляет стандартный датчик ЦЕЛЫХ равномерно распределенных псевдослучайных чисел: если вы хотите получить целое число из интервала [A..B], используйте int x = A + rand() % (B+1-A); Чтобы получать разные последовательности следует задавать некий начальный параметр последовательности (это называется "рандомизация") при помощи srand( число ); /* лучше нечетное */ Чтобы повторить одну и ту же последовательность случайных чисел несколько раз, вы должны поступать так: srand(NBEG); x=rand(); ... ; x=rand(); /* и повторить все сначала */ srand(NBEG); x=rand(); ... ; x=rand(); Используемый метод получения случайных чисел таков: static unsigned long int next = 1L; int rand(){ next = next * 1103515245 + 12345; return ((unsigned int)(next/65536) % 32768); } void srand(seed) unsigned int seed; { next = seed; } Для рандомизации часто пользуются таким приемом: char t[sizeof(long)]; time(t); srand(t[0] + t[1] + t[2] + t[3] + getpid()); 1.39. Напишите функцию вычисления определенного интеграла по методу Симпсона. /*#!/bin/cc $* -lm * Вычисление интеграла по методу Симпсона */ #include <math.h> extern double integral(), sin(), fabs(); #define PI 3.141593 double myf(x) double x; { return sin(x / 2.0); } int niter; /* номер итерации */ void main(){ double integral(); printf("%g\n", integral(0.0, PI, myf, 0.000000001)); /* Заметьте, что myf, а не myf(). * Точное значение интеграла равно 2.0 */ printf("%d итераций\n", niter ); } double integral(a, b, f, eps) double a, b; /* концы отрезка */ double eps; /* требуемая точность */ double (*f)(); /* подынтегральная функция */ { register long i; double fab = (*f)(a) + (*f)(b); /* сумма на краях */ double h, h2; /* шаг и удвоенный шаг */ long n, n2; /* число точек разбиения и оно же удвоенное */ double Sodd, Seven; /* сумма значений f в нечетных и в четных точках */ double S, Sprev;/* значение интеграла на данной и на предыдущей итерациях */ double x; /* текущая абсцисса */ niter = 0; n = 10L; /* четное число */ n2 = n * 2; h = fabs(b - a) / n2; h2 = h * 2.0; /* Вычисляем первое приближение */ /* Сумма по нечетным точкам: */ for( Sodd = 0.0, x = a+h, i = 0; i < n; i++, x += h2 ) Sodd += (*f)(x); /* Сумма по четным точкам: */ for( Seven = 0.0, x = a+h2, i = 0; i < n-1; i++, x += h2 ) Seven += f(x); /* Предварительное значение интеграла: */ S = h / 3.0 * (fab + 4.0 * Sodd + 2.0 * Seven ); do{ niter++; Sprev = S; /* Вычисляем интеграл с половинным шагом */ h2 = h; h /= 2.0; if( h == 0.0 ) break; /* потеря значимости */ n = n2; n2 *= 2; Seven = Seven + Sodd; /* Вычисляем сумму по новым точкам: */ for( Sodd = 0.0, x = a+h, i = 0; i < n; i++, x += h2 ) Sodd += (*f)(x); /* Значение интеграла */ S = h / 3.0 * (fab + 4.0 * Sodd + 2.0 * Seven ); } while( niter < 31 && fabs(S - Sprev) / 15.0 >= eps ); /* Используем условие Рунге для окончания итераций */ return ( 16.0 * S - Sprev ) / 15.0 ; /* Возвращаем уточненное по Ричардсону значение */ } 1.40. Где ошибка? struct time_now{ int hour, min, sec; } X = { 13, 08, 00 }; /* 13 часов 08 минут 00 сек.*/ Ответ: 08 - восьмеричное число (так как начинается с нуля)! А в восьмеричных числах цифры 8 и 9 не бывают. 1.41. Дан текст: int i = -2; i <<= 2; printf("%d\n", i); /* печать сдвинутого i : -8 */ i >>= 2; printf("%d\n", i); /* печатается -2 */ Закомментируем две строки (исключая их из программы): int i = -2; i <<= 2; /* printf("%d\n", i); /* печать сдвинутого i : -8 */ i >>= 2; */ printf("%d\n", i); /* печатается -2 */ Почему теперь возникает ошибка? Указание: где кончается комментарий? Ответ: Си не допускает вложенных комментариев. Вместо этого часто используются конструкции вроде: #ifdef COMMENT ... закомментированный текст ... #endif /*COMMENT*/ и вроде /**/ printf("here");/* отладочная выдача включена */ /* printf("here");/* отладочная выдача выключена */ или /* выключено(); /**/ включено(); /**/ А вот дешевый способ быстро исключить оператор (с возможностью восстановления) - конец комментария занимает отдельную строку, что позволяет отредактировать такой текст редактором почти не сдвигая курсор: /*printf("here"); */ 1.42. Почему программа печатает неверное значение для i2 ? int main(int argc, char *argv[]){ int i1, i2; i1 = 1; /* Инициализируем i1 / i2 = 2; /* Инициализируем i2 */ printf("Numbers %d %d\n", i1, i2); return(0); } Ответ: в первом операторе присваивания не закрыт комментарий - весь второй оператор присваивания полностью проигнорировался! Правильный вариант: int main(int argc, char *argv[]){ int i1, i2; i1 = 1; /* Инициализируем i1 */ i2 = 2; /* Инициализируем i2 */ printf("Numbers %d %d\n", i1, i2); return(0); } 1.43. А вот "шальной" комментарий. void main(){ int n = 10; int *ptr = &n int x, y = 40; x = y/*ptr /* должно быть 4 */ + 1; printf( "%d\n", x ); /* пять */ exit(0); } /* или такой пример из жизни - взят из переписки в Relcom */ ... cost = nRecords/*pFactor /* divided by Factor, and */ + fixMargin; /* plus the precalculated */ ... Результат непредсказуем. Дело в том, что y/*ptr превратилось в начало комментария! Поэтому бинарные операции принято окружать пробелами. x = y / *ptr /* должно быть 4 */ + 1; 1.44. Найдите ошибки в директивах препроцессора Си [*] (вертикальная черта обозначает левый край файла). ____________________ [*] Препроцессор Си - это программа /lib/cpp | | #include <stdio.h> |#include < sys/types.h > |# define inc (x) ((x) + 1) |#define N 12; |#define X -2 | |... printf( "n=%d\n", N ); |... p = 4-X; Ответ: в первой директиве стоит пробел перед #. Диез должен находиться в первой позиции строки. Во второй директиве в <&lt;>&gt; находятся лишние пробелы, не относящиеся к имени файла - препроцессор не найдет такого файла! В данном случае "красота" пошла во вред делу. В третьей - между именем макро inc и его аргументом в круглых скобках (x) стоит пробел, который изменяет весь смысл макроопределения: вместо макроса с параметром inc(x) мы получаем, что слово inc будет заменяться на (x)((x)+1). Заметим однако, что пробелы после # перед именем директивы вполне допустимы. В четвертом случае показана характерная опечатка - символ ; после определения. В результате написанный printf() заменится на printf( "n=%d\n", 12; ); где лишняя ; даст синтаксическую ошибку. В пятом случае ошибки нет, но нас ожидает неприятность в строке p=4-X; которая расширится в строку p=4--2; являющуюся синтаксически неверной. Чтобы избежать подобной ситуации, следовало бы написать p = 4 - X; /* через пробелы */ но еще проще (и лучше) взять макроопределение в скобки: #define X (-2) 1.45. Напишите функцию max(x, y), возвращающую большее из двух значений. Напишите аналогичное макроопределение. Напишите макроопределения min(x, y) и abs(x) (abs - модуль числа). Ответ: #define abs(x) ((x) < 0 ? -(x) : (x)) #define min(x,y) (((x) < (y)) ? (x) : (y)) Зачем x взят в круглые скобки (x)? Предположим, что мы написали #define abs(x) (x < 0 ? -x : x ) вызываем abs(-z) abs(a|b) получаем (-z < 0 ? --z : -z ) (a|b < 0 ? -a|b : a|b ) У нас появилась "дикая" операция --z; а выражение a|b<0 соответствует a|(b<0), с совсем другим порядком операций! Поэтому заключение всех аргументов макроса в его теле в круглые скобки позволяет избежать многих неожиданных проблем. Придерживайтесь этого правила! Вот пример, показывающий зачем полезно брать в скобки все определение: #define div(x, y) (x)/(y) При вызове z = sizeof div(1, 2); превратится в z = sizeof(1) / (2); что равно sizeof(int)/2, а не sizeof(int). Вариант #define div(x, y) ((x) / (y)) будет работать правильно. 1.46. Макросы, в отличие от функций, могут порождать непредвиденные побочные эффекты: int sqr(int x){ return x * x; } #define SQR(x) ((x) * (x)) main(){ int y=2, z; z = sqr(y++); printf("y=%d z=%d\n", y, z); y = 2; z = SQR(y++); printf("y=%d z=%d\n", y, z); } Вызов функции sqr печатает "y=3 z=4", как мы и ожидали. Макрос же SQR расширяется в z = ((y++) * (y++)); и результатом будет "y=4 z=6", где z совсем не похоже на квадрат числа 2. 1.47. ANSI препроцессор[*] языка Си имеет оператор ## - "склейка лексем": #define VAR(a, b) a ## b #define CV(x) command_ ## x main(){ int VAR(x, 31) = 1; /* превратится в int x31 = 1; */ int CV(a) = 2; /* даст int command_a = 2; */ ... } Старые версии препроцессора не обрабатывают такой оператор, поэтому раньше использовался такой трюк: #define VAR(a, b) a/**/b в котором предполагается, что препроцессор удаляет комментарии из текста, не заменяя их на пробелы. Это не всегда так, поэтому такая конструкция не мобильна и пользоваться ею не рекомендуется. 1.48. Напишите программу, распечатывающую максимальное и минимальное из ряда чисел, вводимых с клавиатуры. Не храните вводимые числа в массиве, вычисляйте max и min сразу при вводе очередного числа! #include <stdio.h> main(){ int max, min, x, n; for( n=0; scanf("%d", &x) != EOF; n++) if( n == 0 ) min = max = x; else{ if( x > max ) max = x; if( x < min ) min = x; } printf( "Ввели %d чисел: min=%d max=%d\n", n, min, max); } Напишите аналогичную программу для поиска максимума и минимума среди элементов массива, изначально min=max=array[0]; 1.49. Напишите программу, которая сортирует массив заданных чисел по возрастанию (убыванию) методом пузырьковой сортировки. Когда вы станете более опытны в Си, напишите сортировку методом Шелла. /* * Сортировка по методу Шелла. * Сортировке подвергается массив указателей на данные типа obj. * v------.-------.------.-------.------0 * ! ! ! ! * * * * * * элементы типа obj * Программа взята из книги Кернигана и Ритчи. */ #include <stdio.h> #include <string.h> #include <locale.h> #define obj char static shsort (v,n,compare) int n; /* длина массива */ obj *v[]; /* массив указателей */ int (*compare)(); /* функция сравнения соседних элементов */ { int g, /* расстояние, на котором происходит сравнение */ i,j; /* индексы сравниваемых элементов */ obj *temp; for( g = n/2 ; g > 0 ; g /= 2 ) for( i = g ; i < n ; i++ ) for( j = i-g ; j >= 0 ; j -= g ) { if((*compare)(v[j],v[j+g]) <= 0) break; /* уже в правильном порядке */ /* обменять указатели */ temp = v[j]; v[j] = v[j+g]; v[j+g] = temp; /* В качестве упражнения можете написать * при помощи curses-а программу, * визуализирующую процесс сортировки: * например, изображающую эту перестановку * элементов массива */ } } /* сортировка строк */ ssort(v) obj **v; { extern less(); /* функция сравнения строк */ int len; /* подсчет числа строк */ len=0; while(v[len]) len++; shsort(v,len,less); } /* Функция сравнения строк. * Вернуть целое меньше нуля, если a < b * ноль, если a == b * больше нуля, если a > b */ less(a,b) obj *a,*b; { return strcoll(a,b); /* strcoll - аналог strcmp, * но с учетом алфавитного порядка букв. */ } char *strings[] = { "Яша", "Федя", "Коля", "Гриша", "Сережа", "Миша", "Андрей Иванович", "Васька", NULL }; int main(){ char **next; setlocale(LC_ALL, ""); ssort( strings ); /* распечатка */ for( next = strings ; *next ; next++ ) printf( "%s\n", *next ); return 0; } 1.50. Реализуйте алгоритм быстрой сортировки. /* Алгоритм быстрой сортировки. Работа алгоритма "анимируется" * (animate-оживлять) при помощи библиотеки curses. * cc -o qsort qsort.c -lcurses -ltermcap */ #include "curses.h" #define N 10 /* длина массива */ /* массив, подлежащий сортировке */ int target [N] = { 7, 6, 10, 4, 2, 9, 3, 8, 5, 1 }; int maxim; /* максимальный элемент массива */ /* quick sort */ qsort (a, from, to) int a[]; /* сортируемый массив */ int from; /* левый начальный индекс */ int to; /* правый конечный индекс */ { register i, j, x, tmp; if( from >= to ) return; /* число элементов <= 1 */ i = from; j = to; x = a[ (i+j) / 2 ]; /* значение из середины */ do{ /* сужение вправо */ while( a[i] < x ) i++ ; /* сужение влево */ while( x < a[j] ) j--; if( i <= j ){ /* обменять */ tmp = a[i]; a[i] = a[j] ; a[j] = tmp; i++; j--; demochanges(); /* визуализация */ } } while( i <= j ); /* Теперь обе части сошлись в одной точке. * Длина левой части = j - from + 1 * правой = to - i + 1 * Все числа в левой части меньше всех чисел в правой. * Теперь надо просто отсортировать каждую часть в отдельности. * Сначала сортируем более короткую (для экономии памяти * в стеке ). Рекурсия: */ if( (j - from) < (to - i) ){ qsort( a, from, j ); qsort( a, i, to ); } else { qsort( a, i, to ); qsort( a, from, j ); } } int main (){ register i; initscr(); /* запуск curses-а */ /* поиск максимального числа в массиве */ for( maxim = target[0], i = 1 ; i < N ; i++ ) if( target[i] > maxim ) maxim = target[i]; demochanges(); qsort( target, 0, N-1 ); demochanges(); mvcur( -1, -1, LINES-1, 0); /* курсор в левый нижний угол */ endwin(); /* завершить работу с curses-ом */ return 0; } #define GAPY 2 #define GAPX 20 /* нарисовать картинку */ demochanges(){ register i, j; int h = LINES - 3 * GAPY - N; int height; erase(); /* зачистить окно */ attron( A_REVERSE ); /* рисуем матрицу упорядоченности */ for( i=0 ; i < N ; i++ ) for( j = 0; j < N ; j++ ){ move( GAPY + i , GAPX + j * 2 ); addch( target[i] >= target[j] ? '*' : '.' ); addch( ' ' ); /* Рисовать '*' если элементы * идут в неправильном порядке. * Возможен вариант проверки target[i] > target[j] */ } attroff( A_REVERSE ); /* массив */ for( i = 0 ; i < N ; i++ ){ move( GAPY + i , 5 ); printw( "%4d", target[i] ); height = (long) h * target[i] / maxim ; for( j = 2 * GAPY + N + (h - height) ; j < LINES - GAPY; j++ ){ move( j, GAPX + i * 2 ); addch( '|' ); } } refresh(); /* проявить картинку */ sleep(1); } 1.51. Реализуйте приведенный фрагмент программы без использования оператора goto и без меток. if ( i > 10 ) goto M1; goto M2; M1: j = j + i; flag = 2; goto M3; M2: j = j - i; flag = 1; M3: ; Заметьте, что помечать можно только оператор (может быть пустой); поэтому не может встретиться фрагмент { ..... Label: } а только { ..... Label: ; } 1.52. В каком случае оправдано использование оператора goto? Ответ: при выходе из вложенных циклов, т.к. оператор break позволяет выйти только из самого внутреннего цикла (на один уровень). 1.53. К какому if-у относится else? if(...) ... if(...) ... else ... Ответ: ко второму (к ближайшему предшествующему, для которого нет другого else). Вообще же лучше явно расставлять скобки (для ясности): if(...){ ... if(...) ... else ... } if(...){ ... if(...) ... } else ... 1.54. Макроопределение, чье тело представляет собой последовательность операторов в {...} скобках (блок), может вызвать проблемы при использовании его в условном опера- торе if с else-частью: #define MACRO { x=1; y=2; } if(z) MACRO; else .......; Мы получим после макрорасширения if(z) { x=1; y=2; } /* конец if-а */ ; else .......; /* else ни к чему не относится */ то есть синтаксически ошибочный фрагмент, так как должно быть либо if(...) один_оператор; else ..... либо if(...){ последовательность; ...; операторов; } else ..... где точка-с-запятой после } не нужна. С этим явлением борются, оформляя блок {...} в виде do{...}while(0) #define MACRO do{ x=1; y=2; }while(0) Тело такого "цикла" выполняется единственный раз, при этом мы получаем правильный текст: if(z) do{ x=1; y=2; }while(0); else .......; 1.55. В чем ошибка (для знающих язык "Паскаль")? int x = 12; if( x < 20 and x > 10 ) printf( "O'K\n"); else if( x > 100 or x < 0 ) printf( "Bad x\n"); else printf( "x=%d\n", x); Напишите #define and && #define or || 1.56. Почему программа зацикливается? Мы хотим подсчитать число пробелов и табуляций в начале строки: int i = 0; char *s = " 3 spaces"; while(*s == ' ' || *s++ == '\t') printf( "Пробел %d\n", ++i); Ответ: логические операции || и && выполняются слева направо; как только какое-то условие в || оказывается истинным (а в && ложным) - дальнейшие условия просто не вычисляются. В нашем случае условие *s==' ' сразу же верно, и операция s++ из второго условия не выполняется! Мы должны были написать хотя бы так: while(*s == ' ' || *s == '\t'){ printf( "Пробел %d\n", ++i); s++; } С другой стороны, это свойство || и && черезвычайно полезно, например: if( x != 0.0 && y/x < 1.0 ) ... ; Если бы мы не вставили проверку на 0, мы могли бы получить деление на 0. В данном же случае при x==0 деление просто не будет вычисляться. Вот еще пример: int a[5], i; for(i=0; i < 5 && a[i] != 0; ++i) ...; Если i выйдет за границу массива, то сравнение a[i] с нулем уже не будет вычисляться, т.е. попытки прочесть элемент не входящий в массив не произойдет. Это свойство && позволяет писать довольно неочевидные конструкции, вроде if((cond) && f()); что оказывается эквивалентным if( cond ) f(); Вообще же if(C1 && C2 && C3) DO; эквивалентно if(C1) if(C2) if(C3) DO; и для "или" if(C1 || C2 || C3) DO; эквивалентно if(C1) goto ok; else if(C2) goto ok; else if(C3){ ok: DO; } Вот еще пример, пользующийся этим свойством || #include <stdio.h> main(argc, argv) int argc; char *argv[]; { FILE *fp; if(argc < 2 || (fp=fopen(argv[1], "r")) == NULL){ fprintf(stderr, "Плохое имя файла\n"); exit(1); /* завершить программу */ } ... } Если argc==1, то argv[1] не определено, однако в этом случае попытки открыть файл с именем argv[1] просто не будет предпринято! Ниже приведен еще один содержательный пример, представляющий собой одну из воз- можных схем написания "двуязычных" программ, т.е. выдающих сообщения на одном из двух языков по вашему желанию. Проверяется переменная окружения MSG (или LANG): ЯЗЫК: 1) "MSG=engl" английский 2) MSG нет в окружении английский 3) "MSG=rus" русский Про окружение и функцию getenv() смотри в главе "Взаимодействие с UNIX", про strchr() - в главе "Массивы и строки". #include <stdio.h> int _ediag = 0; /* язык диагностик: 1-русский */ extern char *getenv(), *strchr(); #define ediag(e,r) (_ediag?(r):(e)) main(){ char *s; _ediag = ((s=getenv("MSG")) != NULL && strchr("rRрР", *s) != NULL); printf(ediag("%d:english\n", "%d:русский\n"), _ediag); } Если переменная MSG не определена, то s==NULL и функция strchr(s,...) не вызывается (ее первый фргумент не должен быть NULL-ом). Здесь ее можно было бы упрощенно заменить на *s=='r'; тогда если s равно NULL, то обращение *s было бы незаконно (обращение по указателю NULL дает непредсказуемые результаты и, скорее всего, вызовет крах программы). 1.57. Иногда логическое условие можно сделать более понятным, используя правила де - Моргана: a && b = ! ( !a || !b ) a || b = ! ( !a && !b ) а также учитывая, что ! !a = a ! (a == b) = (a != b) Например: if( c != 'a' && c != 'b' && c != 'c' )...; превращается в if( !(c == 'a' || c == 'b' || c == 'c')) ...; 1.58. Пример, в котором используются побочные эффекты вычисления выражений. Обычно значение выражения присваивается некоторой переменной, но это не необходимо. Поэтому можно использовать свойства вычисления && и || в выражениях (хотя это не есть самый понятный способ написания программ, скорее некоторый род извращения). Ограничение тут таково: все части выражения должны возвращать значения. #include <stdio.h> extern int errno; /* код системной ошибки */ FILE *fp; int openFile(){ errno = 0; fp = fopen("/etc/inittab", "r"); printf("fp=%x\n", fp); return(fp == NULL ? 0 : 1); } int closeFile(){ printf("closeFile\n"); if(fp) fclose(fp); return 0; } int die(int code){ printf("exit(%d)\n", code); exit(code); return 0; } void main(){ char buf[2048]; if( !openFile()) die(errno); closeFile(); openFile() || die(errno); closeFile(); /* если файл открылся, то die() не вычисляется */ openFile() ? 0 : die(errno); closeFile(); if(openFile()) closeFile(); openFile() && closeFile(); /* вычислить closeFile() только если openFile() удалось */ openFile() && (printf("%s", fgets(buf, sizeof buf, fp)), closeFile()); } В последней строке использован оператор "запятая": (a,b,c) возвращает значение выражения c. 1.59. Напишите функцию, вычисляющую сумму массива заданных чисел. 1.60. Напишите функцию, вычисляющую среднее значение массива заданных чисел. 1.61. Что будет напечатано в результате работы следующего цикла? for ( i = 36; i > 0; i /= 2 ) printf ( "%d%s", i, i==1 ? ".\n":", "); Ответ: 36, 18, 9, 4, 2, 1. 1.62. Найдите ошибки в следующей программе: main { int i, j, k(10); for ( i = 0, i <= 10, i++ ){ k[i] = 2 * i + 3; for ( j = 0, j <= i, j++ ) printf ("%i\n", k[j]); } } Обратите внимание на формат %i, существует ли такой формат? Есть ли это тот формат, по которому следует печатать значения типа int? 1.63. Напишите программу, которая распечатывает элементы массива. Напишите программу, которая распечатывает элементы массива по 5 чисел в строке. 1.64. Составьте программу считывания строк символов из стандартного ввода и печати номера введенной строки, адреса строки в памяти ЭВМ, значения строки, длины строки. 1.65. Стилистическое замечание: в операторе return возвращаемое выражение не обязательно должно быть в ()-скобках. Дело в том, что return - не функция, а оператор. return выражение; return (выражение); Однако если вы вызываете функцию (например, exit) - то аргументы должны быть в круглых скобках: exit(1); но не exit 1; 1.66. Избегайте ситуации, когда функция в разных ветвях вычисления то возвращает некоторое значение, то не возвращает ничего: int func (int x) { if( x > 10 ) return x*2; if( x == 10 ) return (10); /* а здесь - неявный return; без значения */ } при x < 10 функция вернет непредсказуемое значение! Многие компиляторы распознают такие ситуации и выдают предупреждение. 1.67. Напишите программу, запрашивающую ваше имя и "приветствующую" вас. Напишите функцию чтения строки. Используйте getchar() и printf(). Ответ: #include <stdio.h> /* standard input/output */ main(){ char buffer[81]; int i; printf( "Введите ваше имя:" ); while((i = getstr( buffer, sizeof buffer )) != EOF){ printf( "Здравствуй, %s\n", buffer ); printf( "Введите ваше имя:" ); } } getstr( s, maxlen ) char *s; /* куда поместить строку */ int maxlen; /* длина буфера: макс. длина строки = maxlen-1 */ { int c; /* не char! (почему ?) */ register int i = 0; maxlen--; /* резервируем байт под конечный '\0' */ while(i < maxlen && (c = getchar()) != '\n' && c != EOF ) s[i++] = c; /* обратите внимание, что сам символ '\n' * в строку не попадет */ s[i] = '\0'; /* признак конца строки */ return (i == 0 && c == EOF) ? EOF : i; /* вернем длину строки */ } Вот еще один вариант функции чтения строки: в нашем примере ее следует вызывать как fgetstr(buffer,sizeof(buffer),stdin); Это подправленный вариант стандартной функции fgets (в ней строки @1 и @2 обменяны местами). char *fgetstr(char *s, int maxlen, register FILE *fp){ register c; register char *cs = s; while(--maxlen > 0 && (c = getc(fp)) != EOF){ if(c == '\n') break; /* @1 */ *cs++ = c; /* @2 */ } if(c == EOF && cs == s) return NULL; /* Заметьте, что при EOF строка s не меняется! */ *cs = '\0'; return s; } Исследуйте поведение этих функций, когда входная строка слишком длинная (длиннее maxlen). Замечание: вместо нашей "рукописной" функции getstr() мы могли бы использовать стандартную библиотечную функцию gets(buffer). 1.68. Объясните, почему d стало отрицательным и почему %X печатает больше F, чем в исходном числе? Пример выполнялся на 32-х битной машине. main(){ unsigned short u = 65535; /* 16 бит: 0xFFFF */ short d = u; /* 15 бит + знаковый бит */ printf( "%X %d\n", d, d); /* FFFFFFFF -1 */ } Указание: рассмотрите двоичное представление чисел (смотри приложение). Какие приведения типов здесь происходят? 1.69. Почему 128 превратилось в отрицательное число? main() { /*signed*/ char c = 128; /* биты: 10000000 */ unsigned char uc = 128; int d = c; /* используется 32-х битный int */ printf( "%d %d %x\n", c, d, d ); /* -128 -128 ffffff80 */ d = uc; printf( "%d %d %x\n", uc, d, d ); /* 128 128 80 */ } Ответ: при приведении char к int расширился знаковый бит (7-ой), заняв всю старшую часть слова. Знаковый бит int-а стал равен 1, что является признаком отрицательного числа. То же будет происходить со всеми значениями c из диапазона 128..255 (содержащими бит 0200). При приведении unsigned char к int знаковый бит не расширяется. Можно было поступить еще и так: printf( "%d\n", c & 0377 ); Здесь c приводится к типу int (потому что при использовании в аргументах функции тип char ВСЕГДА приводится к типу int), затем &0377 занулит старший байт полученного целого числа (состоящий из битов 1), снова превратив число в положительное. 1.70. Почему printf("%d\n", '\377' == 0377 ); printf("%d\n", '\xFF' == 0xFF ); печатает 0 (ложь)? Ответ: по той же причине, по которой printf("%d %d\n", '\377', 0377); печатает -1 255, а именно: char '\377' приводится в выражениях к целому расширением знакового бита (а 0377 - уже целое). 1.71. Рассмотрим программу #include <stdio.h> int main(int ac, char **av){ int c; while((c = getchar()) != EOF) switch(c){ case 'ы': printf("Буква ы\n"); break; case 'й': printf("Буква й\n"); break; default: printf("Буква с кодом %d\n", c); break; } return 0; } Она работает так: % a.out йфыв Буква с кодом 202 Буква с кодом 198 Буква с кодом 217 Буква с кодом 215 Буква с кодом 10 ^D % Выполняется всегда default, почему не выполняются case 'ы' и case 'й'? Ответ: русские буквы имеют восьмой бит (левый) равный 1. В case такой байт при- водится к типу int расширением знакового бита. В итоге получается отрицательное число. Пример: void main(void){ int c = 'й'; printf("%d\n", c); } печатает -54 Решением служит подавление расширения знакового бита: #include <stdio.h> /* Одно из двух */ #define U(c) ((c) & 0xFF) #define UC(c) ((unsigned char) (c)) int main(int ac, char **av){ int c; while((c = getchar()) != EOF) switch(c){ case U('ы'): printf("Буква ы\n"); break; case UC('й'): printf("Буква й\n"); break; default: printf("Буква с кодом %d\n", c); break; } return 0; } Она работает правильно: % a.out йфыв Буква й Буква с кодом 198 Буква ы Буква с кодом 215 Буква с кодом 10 ^D % Возможно также использование кодов букв: case 0312: но это гораздо менее наглядно. Подавление знакового бита необходимо также и в опера- торах if: int c; ... if(c == 'й') ... следует заменить на if(c == UC('й')) ... Слева здесь - signed int, правую часть компилятор тоже приводит к signed int. Приходится явно говорить, что справа - unsigned. 1.72. Рассмотрим программу, которая должна напечатать числа от 0 до 255. Для этих чисел в качестве счетчика достаточен один байт: int main(int ac, char *av[]){ unsigned char ch; for(ch=0; ch < 256; ch++) printf("%d\n", ch); return 0; } Однако эта программа зацикливается, поскольку в момент, когда ch==255, это значение меньше 256. Следующим шагом выполняется ch++, и ch становится равно 0, ибо для char вычисления ведутся по модулю 256 (2 в 8 степени). То есть в данном случае 255+1=0 Решений существует два: первое - превратить unsigned char в int. Второе - вста- вить явную проверку на последнее значение диапазона. int main(int ac, char *av[]){ unsigned char ch; for(ch=0; ; ch++){ printf("%d\n", ch); if(ch == 255) break; } return 0; } 1.73. Подумайте, почему для unsigned a, b, c; a < b + c не эквивалентно a - b < c (первое - более корректно). Намек в виде примера (он выполнялся на 32-битной машине): a = 1; b = 3; c = 2; printf( "%u\n", a - b ); /* 4294967294, хотя в нормальной арифметике 1 - 3 = -2 */ printf( "%d\n", a < b + c ); /* 1 */ printf( "%d\n", a - b < c ); /* 0 */ Могут ли unsigned числа быть отрицательными? 1.74. Дан текст: short x = 40000; printf("%d\n", x); Печатается -25536. Объясните эффект. Указание: каково наибольшее представимое короткое целое (16 битное)? Что на самом деле оказалось в x? (лишние слева биты - обрубаются). 1.75. Почему в примере double x = 5 / 2; printf( "%g\n", x ); значение x равно 2 а не 2.5 ? Ответ: производится целочисленное деление, затем в присваивании целое число 2 приводится к типу double. Чтобы получился ответ 2.5, надо писать одним из следующих способов: double x = 5.0 / 2; x = 5 / 2.0; x = (double) 5 / 2; x = 5 / (double) 2; x = 5.0 / 2.0; то есть в выражении должен быть хоть один операнд типа double. Объясните, почему следующие три оператора выдают такие значения: double g = 9.0; int t = 3; double dist = g * t * t / 2; /* 40.5 */ dist = g * (t * t / 2); /* 36.0 */ dist = g * (t * t / 2.0); /* 40.5 */ В каких случаях деление целочисленное, в каких - вещественное? Почему? 1.76. Странслируйте пример на машине с длиной слова int равной 16 бит: long n = 1024 * 1024; long nn = 512 * 512; printf( "%ld %ld\n", n, nn ); Почему печатается 0 0 а не 1048576 262144? Ответ: результат умножения (2**20 и 2**18) - это целое число; однако оно слишком велико для сохранения в 16 битах, поэтому старшие биты обрубаются. Получается 0. Затем в присваивании это уже обрубленное значение приводится к типу long (32 бита) - это все равно будет 0. Чтобы получить корректный результат, надо чтобы выражение справа от = уже имело тип long и сразу сохранялось в 32 битах. Для этого оно должно иметь хоть один операнд типа long: long n = (long) 1024 * 1024; long nn = 512 * 512L; 1.77. Найдите ошибку в операторе: x - = 4; /* вычесть из x число 4 */ Ответ: между `-' и `=' не должно быть пробела. Операция вида x @= expr; означает x = x @ expr; (где @ - одна из операций + - * / % ^ >&gt;>&gt; <&lt;<&lt; & |), причем x здесь вычисляется единственный раз (т.е. такая форма не только короче и понятнее, но и экономичнее). Однако имеется тонкое отличие a=a+n от a+=n; оно заключается в том, сколько раз вычисляется a. В случае a+=n единожды; в случае a=a+n два раза. #include <stdio.h> static int x = 0; int *iaddr(char *msg){ printf("iaddr(%s) for x=%d evaluated\n", msg, x); return &x } int main(){ static int a[4]; int *p, i; printf( "1: "); x = 0; (*iaddr("a"))++; printf( "2: "); x = 0; *iaddr("b") += 1; printf( "3: "); x = 0; *iaddr("c") = *iaddr("d") + 1; for(i=0, p = a; i < sizeof(a)/sizeof(*a); i++) a[i] = 0; *p++ += 1; for(i=0; i < sizeof(a)/sizeof(*a); i++) printf("a[%d]=%d ", i, a[i]); printf("offset=%d\n", p - a); for(i=0, p = a; i < sizeof(a)/sizeof(*a); i++) a[i] = 0; *p++ = *p++ + 1; for(i=0; i < sizeof(a)/sizeof(*a); i++) printf("a[%d]=%d ", i, a[i]); printf("offset=%d\n", p - a); return 0; } Выдача: 1: iaddr(a) for x=0 evaluated 2: iaddr(b) for x=0 evaluated 3: iaddr(d) for x=0 evaluated iaddr(c) for x=0 evaluated a[0]=1 a[1]=0 a[2]=0 a[3]=0 offset=1 a[0]=1 a[1]=0 a[2]=0 a[3]=0 offset=2 Заметьте также, что a[i++] += z; это a[i] = a[i] + z; i++; а вовсе не a[i++] = a[i++] + z; 1.78. Операция y = ++x; эквивалентна y = (x = x+1, x); а операция y = x++; эквивалентна y = (tmp = x, x = x+1, tmp); или y = (x += 1) - 1; где tmp - временная псевдопеременная того же типа, что и x. Операция `,' выдает значение последнего выражения из перечисленных (подробнее см. ниже). Пусть x=1. Какие значения будут присвоены x и y после выполнения оператора y = ++x + ++x + ++x; 1.79. Пусть i=4. Какие значения будут присвоены x и i после выполнения оператора x = --i + --i + --i; Пусть x=1. Какие значения будут присвоены x и y после выполнения оператора y = x++ + x++ + x++; 1.81. Пусть i=4. Какие значения будут присвоены i и y после выполнения оператора y = i-- + i-- + i--; 1.82. Корректны ли операторы char *p = "Jabberwocky"; char s[] = "0123456789?"; int i = 0; s[i] = p[i++]; или *p = *++p; или s[i] = i++; или даже *p++ = f( *p ); Ответ: нет, стандарт не предусматривает, какая из частей присваивания вычисляется первой: левая или правая. Поэтому все может работать так, как мы и подразумевали, но может и иначе! Какое i используется в s[i]: 0 или уже 1 (++ уже сделан или нет), то есть int i = 0; s[i] = i++; это s[0] = 0; или же s[1] = 0; ? Какое p будет использовано в левой части *p: уже продвинутое или старое? Еще более эта идея драматизирована в s[i++] = p[i++]; Заметим еще, что в int i=0, j=0; s[i++] = p[j++]; такой проблемы не возникает, поскольку индексы обоих в частях присваивания независимы. Зато аналогичная проблема встает в if( a[i++] < b[i] )...; Порядок вычисления операндов не определен, поэтому неясно, что будет сделано прежде: взято значение b[i] или значение a[i++] (тогда будет взято b[i+1] ). Надо писать так, чтобы не полагаться на особенности вашего компилятора: if( a[i] < b[i+1] )...; или *p = *(p+1); i++; ++p; Твердо усвойте, что i++ и ++i не только выдают значения i и i+1 соответственно, но и изменяют значение i. Поэтому эти операторы НЕ НАДО использовать там, где по смыслу требуется i+1, а не i=i+1. Так для сравнения соседних элементов массива if( a[i] < a[i+1] ) ... ; /* верно */ if( a[i] < a[++i] ) ... ; /* неверно */ 1.83. Порядок вычисления операндов в бинарных выражениях не определен (что раньше вычисляется - левый операнд или же правый ?). Так пример int f(x,s) int x; char *s; { printf( "%s:%d ", s, x ); return x; } main(){ int x = 1; int y = f(x++, "f1") + f(x+=2, "f2"); printf("%d\n", y); } может печатать либо f1:1 f2:4 5 либо f2:3 f1:3 6 в зависимости от особенностей поведения вашего компилятора (какая из двух f() выполнится первой: левая или правая?). Еще пример: int y = 2; int x = ((y = 4) * y ); printf( "%d\n", x ); Может быть напечатано либо 16, либо 8 в зависимости от поведения компилятора, т.е. данный оператор немобилен. Следует написать y = 4; x = y * y; 1.84. Законен ли оператор f(x++, x++); или f(x, x++); Ответ: Нет, порядок вычисления аргументов функций не определен. По той же причине мы не можем писать f( c = getchar(), c ); а должны писать c = getchar(); f(c, c); (если мы именно это имели в виду). Вот еще пример: ... case '+': push(pop()+pop()); break; case '-': push(pop()-pop()); break; ... следует заменить на ... case '+': push(pop()+pop()); break; case '-': { int x = pop(); int y = pop(); push(y - x); break; } ... И еще пример: int x = 0; printf( "%d %d\n", x = 2, x ); /* 2 0 либо 2 2 */ Нельзя также struct pnt{ int x; int y; }arr[20]; int i=0; ... scanf( "%d%d", & arr[i].x, & arr[i++].y ); поскольку i++ может сделаться раньше, чем чтение в x. Еще пример: main(){ int i = 3; printf( "%d %d %d\n", i += 7, i++, i++ ); } который показывает, что на IBM PC [*] и PDP-11 [**] аргументы функций вычисляются справа налево (пример печатает 12 4 3). Впрочем, другие компиляторы могут вычислять их слева направо (как и подсказывает нам здравый смысл). 1.85. Программа печатает либо x=1 либо x=0 в зависимости от КОМПИЛЯТОРА - вычисляется ли раньше правая или левая часть оператора вычитания: #include <stdio.h> void main(){ int c = 1; int x = c - c++; printf( "x=%d c=%d\n", x, c ); exit(0); } Что вы имели в виду ? left = c; right = c++; x = left - right; или right = c++; left = c; x = left - right; А если компилятор еще и распараллелит вычисление left и right - то одна программа в разные моменты времени сможет давать разные результаты. ____________________ [*] IBM ("Ай-би-эм") - International Buisiness Machines Corporation. [**] PDP-11 - (Programmed Data Processor) - компьютер фирмы DEC (Digital Equipment Corporation), у нас известный как СМ-1420. Эта же фирма выпускает машину VAX. Вот еще достойная задачка: x = c-- - --c; /* c-----c */ 1.86. Напишите программу, которая устанавливает в 1 бит 3 и сбрасывает в 0 бит 6. Биты в слове нумеруются с нуля справа налево. Ответ: int x = 0xF0; x |= (1 << 3); x &= ~(1 << 6); В программах часто используют битовые маски как флаги некоторых параметров (признак - есть или нет). Например: #define A 0x08 /* вход свободен */ #define B 0x40 /* выход свободен */ установка флагов : x |= A|B; сброс флагов : x &= ~(A|B); проверка флага A : if( x & A ) ...; проверка, что оба флага есть: if((x & (A|B)) == (A|B))...; проверка, что обоих нет : if((x & (A|B)) == 0 )...; проверка, что есть хоть один: if( x & (A|B))...; проверка, что есть только A : if((x & (A|B)) == A)...; проверка, в каких флагах различаются x и y : diff = x ^ y; 1.87. В программах иногда требуется использовать "множество": каждый допустимый элемент множества имеет номер и может либо присутствовать в множестве, либо отсутство- вать. Число вхождений не учитывается. Множества принято моделировать ...
Допоможи студентському архіву

Ми допомогли тобі — допоможи наступному студенту

Файл ще не обрано

Оголошення від адміністратора

Антиботан аватар за замовчуванням

Подякувати Студентському архіву довільною сумою

Admin

26.02.2023 12:38

Дякуємо, що користуєтесь нашим архівом!