Текст
                    серия- ПРОСТО О СЛОЖНОМ -СЕРИЯ
"Наука и Техника'
Санкт-Петербург


Кольцов Д. М. 100 примеров на Си "Наука и Техника" Санкт-Петербург
УДК 004.42 ISBN 978-5-94387-740-7 Кольцов Д. М. 100 ПРИМЕРОВ НА СИ — СПб.: "Наука и Техника", 2017. — 256 с, ил. Серия "Просто о сложном" Данная книга предназначена для практического изучения языка программирования Си. Изложение материала ведется на основе примеров. Перед примерами дается небольшая теоретическая часть, а затем разбираются конкретные примеры. Такой подход позволит читателю уже с первых шагов писать работающие программы. Примеры в книге приведены по нарастающей сложности: от простых программ с использованием простых конструкций до небольшой компьютерной игры и клиент-серверного приложения. Книга будет полезна всем, кто хочет максимально быстро и эффективно начать программировать на языке Си, а также тем, кто хочет получить набор готовых решений для разных ситуаций. Книга не требует начальных знаний программирования, лучший выбор для начинающих.1 978-5-94387-740-7 Контактные телефоны издательства: (812)412 70 26 Официальный сайт: www.nit.com.ru © КольцовД.М., ПРОКДИРГ © Наука и техника (оригинал-макет)
Содержание ЧАСТЫ. ВВЕДЕНИЕ 15 Пример 1. Программа "Привет, мир!" 16 Пример 2. Выводим целое число, введенное пользователем 17 ПримерЗ. Сумма двух чисел 19 Пример 4. Умножение двух вещественных чисел 20 Пример 5. Определение ASCII-значения символа 21 Примерб. Вычисляем частное и остаток 22 Пример 7. Вычисляем размер int, float, double и char 24 Пример 8. Как работает ключевое слово long 25 Пример 9. Меняем местами два числа 26 ЧАСТЬ 2. ПРИНЯТИЕ РЕШЕНИЙ И ЦИКЛЫ 30 Пример 10. Проверяем, является ли число четным или нет 33 Пример 11. Проверяем, является ли символ гласным или согласным : 34 Пример 12. Определяем максимум среди трех чисел 36 Пример 13. Вычисляем все корни квадратного уравнения. Подключение библиотеки math 38 Пример 14. Является ли год високосным 41 Пример 15. Проверяем, является ли число отрицательным или положительным 42 Пример 16. Вычисляем сумму натуральных чисел 44 Пример 17. Проверяем, является ли символ алфавитным или нет 48 Пример 18. Вычисление факториала 50 Пример 19. Выводим таблицу умножения 51 Пример 20. Выводим последовательность Фибоначчи 53 Пример 21. Вычисления НОД двух чисел 55 Пример 22. Наименьшее общее кратное 57 *
100 примеров на Си Пример 23. Подсчитываем количество цифр целого числа 59 Пример 24. Вычисляем обратное число 61 Пример 25. Вычисляем степень числа 62 Пример 26. Проверяем, является ли число палиндромом или нет 63 Пример 27. Является ли число простым 65 Пример 28. Выводим простые числа в интервале 66 Пример 29. Проверяем число Армстронга 69 Пример 30. Выводим числа Армстронга в заданном диапазоне .71 Пример 31. Создаем пирамиду и структуру 72 Пример 32. Делаем простой калькулятор с использованием switch..case 73 ЧАСТЬ 3. ФУНКЦИИ 77 Пример 33. Проверка простого числа или числа Армстронга с , использованием пользовательской функции 79 Пример 34. Отображаем простые числа между двумя интервалами с использованием функции 83 Пример 35. Проверяем, может ли число быть выраженным как сумма двух простых чисел 85 Пример 36. Сумма п натуральных чисел с использованием рекурсии 86 Пример 37. Факториал с использованием рекурсии 88 Пример 38. НОД с использованием рекурсии 90 Пример 39. Конвертируем двоичные числа в десятичные и наоборот 91 Пример 40. Конвертируем восьмеричные числа в десятичные и наоборот 93 Пример 41. Конвертируем двоичные числа в восьмеричные и наоборот 95 Пример 42. Выводим предложение в обратном порядке 97 *
Содержание ЧАСТЬ 4. МАССИВЫ И УКАЗАТЕЛИ 99 Пример 43. Вычисляем среднее с использованием массивов.. 102 Пример 44. Вычисляем наибольший элемент массива 103 Пример 45. Вычисляем среднеквадратичное отклонение 105 Пример 46. Сложение двух матриц с использованием многомерных массивов 106 Пример 47. Умножение на матрицу с использованием многомерных массивов 108 Пример 48. Транспонированная матрица 111 Пример 49. Умножение двух матриц с передачей матрицы в функции 112 Пример 50. Доступ к элементам массива с использованием указателей 116 Пример 51. Своп чисел в циклическом порядке с помощью вызова по ссылке 117 Пример 52. Поиск максимума с использованием динамического выделения памяти 118 ЧАСТЬ 5. СТРОКИ 121 Пример 53. Поиск частоты знаков в строке '. 124 Пример 54. Программа для подсчета количества цифр и пробелов 125 Пример 55. Удаляем все символы в строке, кроме цифровых... 127 Пример 56. Определение длины строки 128 Пример 57. Конкатенация двух строк без функции strcat() 130 Пример 58. Копирование строки без функции strcpyQ 131 Пример 59. Сортировка элементов в лексикографическом порядке 132 ЧАСТЬ 6. СТРУКТУРЫ И ОБЪЕДИНЕНИЯ 135 Пример 60. Храним информацию о студенте в структуре 136 Пример 61. Сложение двух структур 138 *
100 примеров на Си Пример 62. Сложение двух комплексных чисел с использованием структуры и передачей структуры функции 139 Пример 63. Вычисление разницы между двумя периодами времени 141 Пример 64. Структуры и динамическое выделение памяти 143 ЧАСТЬ 7. ФАЙЛЫ 146 Пример 65. Запись предложения в файл 147 Пример 66. Чтение строки из файла и ее отображение 148 Пример 67. Отображаем исходный код программы 150 ЧАСТЬ 8. ГОТОВЫЕ ПРИЛОЖЕНИЯ 152 Пример 68. Приложение клиент-сервер 153 Пример 69. Мини-игра "Змейка" 160 Пример 70. Программа word count на С 171 ЧАСТЬ 9. АЛГОРИТМЫ ПОИСКА И СОРТИРОВКИ 173 Пример 71. Сортировка вставкой связного списка. Сортировка реального файла 174 Пример 72. Бинарный поиск в целочисленном массиве 181 Пример 73. Бинарный поиск по массиву указателей строк 183 Пример 74. Сортировка пузырьком 186 Пример 75. Пузырьковая сортировка связного списка 188 Пример 76. Пузырьковая сортировка массива строк. Сортировка реального файла 193 Пример 77. Пирамидальная сортировка 196 Пример 78. Сортировка вставкой массива по убыванию и по возрастанию 199 Пример 79. Сортировка слиянием. Связный список 202 Пример 80. Сортировка слиянием массива 206 Пример 81. Быстрая сортировка массива 209
Содержание Пример 82. Сортировка массива строк библиотечной функцией qsort() 212 Пример 83. Сортировка массивов указателей на структуры с помощью функции qsort() 214 Пример 84. Сортировка выбором 217 Пример 85. Сортировка с помощью бинарного дерева 221 Пример 86. Реверс связного списка 223 ЧАСТЬЮ. ЕЩЕ НЕМНОГО ПРАКТИКИ 226 Пример 87. Делаем свой shell 227 Пример 88. Получение информации о системе 229 Пример 89. Пишем сообщения в системный журнал 229 Пример 90. Обработка полученного сигнала 231 Пример 91. Преобразование времени в формате UTC в строку и обратно 232 Пример 92. Слияние двух файлов 235 Пример 93. Получение информации о файле 237 Пример 94. Скрываем вводимый пользователем пароль 239 Пример 95. Сколько времени работает система? Показываем uptime 240 Пример 96. Удаляем HTML-разметку 241 Пример 97. Выводим IP-адреса, e-mail и URL, найденные в тексте 243 Пример 98. Выводим текст в картинку. Компиляция программы с использованием библиотеки gd 248 Пример 99. Создание временного файла 249 Пример 100. Открываем лоток DVD 252 ВМЕСТО ЗАКЛЮЧЕНИЯ 253 t
100 примеров на Си
ЧАСТЬ 1. Введение
ЧАСТЬ 1. Введение Вместо введения Данная книга - это не очередной самоучитель по С. Таких самоучителей достаточно много, и все они скучные, поскольку в большинстве случаев они повторяют официальную документацию. Возьмите два-три самоучителя или справочника по С - они будут все как один похожи друг на друга. Данная книга - совсем другое. Скорее, это сборник реальных задач, решен- ных с помощью языка программирования С. Книга будет полезна студен- там, изучающим программирование на С, - они найдут в ней большинство задач, которые им придется решать в процессе обучения программирова- нию на С. Все примеры тщательно протестированы. Для их компиляции вы можете использовать любой компилятор. Для каждого примера приводится его ис- ходный код и скриншот, подтверждающий работоспособность программы. Для компиляции программ мы использовали компилятор gcc, но вы можете использовать любой из них. В большинстве случае откомпилировать программу можно командой gcc <имя файла исходного кода>. Если компиляция программы потребует до- полнительных опций, как, например, в примере 13, мы об этом сообщим. Книга разделена на несколько частей: • Часть 1 знакомит читателя с самыми простыми примерами. Подобные программы может написать любой, кто только-только познакомился с синтаксисом С. • Часть 2 описывает операторы принятия решений и циклы. Здесь про- граммы уже значительно сложнее. • Часть 3 демонстрирует, как создавать и использовать собственные функ- ции. • Часть 4 одна из самых сложных, поскольку затрагивает самую сложную для новичков тему - указатели. Очень много ошибок в программах на- чинающих программистов связано с указателями. • Часть 5 - показывает, как работать со строками. • Часть 6 демонстрирует несколько примеров, показывающих, как рабо- тать с различными структурами данных. • Часть 7 содержит несколько примеров по работе с файлами.
100 примеров на Си • Часть 8 описывает готовые приложения. Самое простое из них - про- грамма подсчета слов - аналог Linux-программы we. Также мы создадим игру змейку и приложение клиент-сервер. Последнее приложение мо- жет стать даже предметом курсовой работы. • Часть 9 содержит описание различных алгоритмов поиска и сортировки. Рассматриваются алгоритмы сортировки массивов, связных списков, со- ртировка бинарным деревом и многое другое. • Часть 10, как и часть 8, также содержит готовые приложения. Это сугубо практическая глава, из которой вы узнаете, как создать свою командную оболочку, как очистить текст от HTML-кода, как записать сообщение в системный журнал, как преобразовать дату и время в формат UTC и об- ратно, как вывести все e-mail из текста и многое другое. Приятного чтения!
ЧАСТЬ 1. Введение Часть 1. Введение Первая часть содержит самые простые примеры и знакомит читателя с син- таксисом С на практике. Поскольку это не учебник по С, скучного объяс- нения синтаксиса вы здесь не увидите: смотрите, учитесь, повторяйте, как в книге. Если вы допустите ошибку, то компилятор подскажет, что именно вы сделали не так. Пример 1. Программа "Привет, мир!" Пример 2. Выводим целое число, введенное пользователем Пример 3. Сумма двух чисел Пример 4. Умножение двух вещественных чисел Пример 5. Определение ASCII-значения символа Пример 6. Вычисляем частное и остаток Пример 7. Вычисляем размер int, float, double и char Пример 8. Как работает ключевое слово long Пример 9. Меняем местами два числа
100 примеров на Си Пример 1. Программа "Привет, мир!' По традиции нашу книгу начнем с программы "Привет, мир!". Код програм- мы, как и любой другой подобной программы, очень прост (лист. 1). Листинг 1. Код программы "Привет, мир!" (1 .с) #include <stdio.h> int main() { // printf() displays the string inside quotation printf("Привет, мир!"); return 0; Вывод программы будет таким: Привет, мир! : $ пс den@den-pc:~/c-ex$ gcc l.c den$den-pc:~/c~ex$ ./a.out Привет, мир! den@den~pc:~/oex$ | Рис. 1. Вывод программы "Привет, мир!"
Разберемся, как работает программа: • Первая строка #include <stdio.h> - это команда препроцессора. Она го- ворит компилятору добавить содержимое заголовочного файла stdio.h (название расшифровывается как Standard Input and Output - стандарт- ный ввод/вывод) в нашу программу. Файл stdio.h содержит функции вроде scanf() и printf() для работы с вводом и выводом соответственно. • Выполнение программы на С начинается с функции main() - это точка входа в программу. • Функция prinf() - это библиотечная функция, отправляющая отформа- тированный вывод на экран. В нашем случае данная функция выводит строку "Привет, мир!" (без кавычек) на экран. • Выражение return 0; означает код возврата (exit code) программы. С по- мощью кода возврата вы можете дать знатьдругим программам, запу- стившим вашу программу, как прошло ее выполнение. Код возврата 0 обычно соответствует отсутствию ошибок. Любое другое значение озна- чает код ошибки. Пример 2. Выводим целое число, введенное пользователем Теперь усложним нашу задачу. Мы не просто выведем на экран что-то, а выведем число, введенное пользователем. Пользователь введет целое зна- чение, которое мы сохраним в переменную и затем отобразим с помощью функции printf(). Код программы приведен в листинге 2. Листинг 2. Вывод целого числа, введенного пользователем #include <stdio.h> int main() { int k; // printf() отображает приглашение printf("Введите целое число: "); // scanf () читает форматированный ввод пользователя и записывает // его в переменную scanf("%d", &k); // printf () отображает отформатированный вывод
100 примеров на Си printf("Вы ввели: %d", к); return 0; Как показано на рис. 2, программа вывела приглашение ввести целое число, после этого программа выводит введенное пользователем число. Пройдемся по строкам программы. В функции main() мы объявляем пере- менную к типа int (целое число). Функция printf() отображает приглаше- ние для пользователя, поясняющее, что он должен сделать. Затем функция scanf() читает целочисленные данные (модификатор %d) и записывает их в переменную к. Обратите внимание на знак & перед именем переменной - функция должна не просто обратиться к переменной, но и записать в нее данные. Наконец, функция printf() выводит результат - строку "Вы ввели:" и цело- численные данные (%d), хранящиеся в переменной к. den$den-pc:~/c«ex$ ./a.out Введите целое число: 100 вы ввели: 100 den@den-pc:~/c-ex$ | Рис. 2. Результат работы программы
ЧАСТЫ. Введение Пример 3. Сумма двух чисел Немного усложним предыдущую программу. Мы попросим пользователя ввести два целых числа, а потом выведем их сумму. Код программы при- веден в листинге 3. Листинг 3. Сумма двух целых чисел (З.с) #include <stdio.h> int main() { int firstNumber, secondNumber, sum; printf("Введите два целых числа: "); // Читаем 2 целых числа функцией scanf() scanf("%d %d", &firstNumber, &secondNumber); // вычисляем сумму двух чисел sum = firstNumber + secondNumber; // Отображаем сумму printf("%d + %d = %d\n", firstNumber, secondNumber, sum); return 0; } Вывод программы изображен на рис. 3. > ./a.out Введите два целых числа: 23 19 23 + 19 s 42 $ Рис. 3. Результат работы программы из листинга 3. Сумма чисел
100 примеров на Си Программа запрашивает у пользователя два целых числа. Числа, введенные пользователем, сохраняются в переменных firstNumber и secondNumber со- ответственно. Далее в переменную sum записывается сумма этих двух пере- менных. Наконец, сумма отображается на экране с использованием функ- ции printf(). Обратите внимание, что в функции printf() мы выводим символ новой стро- ки - \п. Он необходим, чтобы курсор был переведен на новую строку по окончанию работы программы. Иначе приглашение командной оболочки будет выведено сразу после результата (после суммы) работы программы, что неправильно. Пример 4. Умножение двух вещественных чисел Рассмотрим более сложный пример - умножение двух вещественных чи- сел. Работать программа будет аналогично предыдущей - пользователь вводит два числа, программа вычисляет умножение и выводит результат. Листинг 4. Умножение двух вещественных чисел (4.с) #include <stdio.h> int main() { double firstNumber, secondNumber, product; printf("Введите два числа: "); // Читаем 2 вещественных числа функцией scanf () scanf("%lf %lf", &firstNumber, &secondNumber); $ ./a.out Введите два числа: 1.44 2.88 Product =4.15 Рис. 4. Умножение двух вещественных чисел
ЧАСТЫ. Введение // Результат умножения сохраняем в переменную product product = firstNumber * secondNumber; // Выводим результат, после запятой 2 знака, формат %.21f printf("Product = %.21f\n", productofTwoNumbers); return 0; Как обычно, программа запрашивает два числа. Только на этот раз перемен- ные, в которые заносятся введенные пользователем значения, принадлежат к типу double, а не int, то есть способны хранить вещественные значения. Также обратите внимание на другой модификатор функции printf() - вме- сто модификатора формата %d мы используем %lf. Далее результат умножения переменных firstNumber и secondNumber зано- сится в переменную product, значение которой мы и выводим. Для вывода мы используем формат %.21f, что означает 2 знака после десятичной точки. Пример 5. Определение ASCII-значения символа В языке программирования С символьная переменная (тип char) хранит ASCII-значение символа, а не сам символ. В этой программе вы узнаете, как получить ASCII-значение символа и вывести его на экран. Например, ASCII-значение символа 'А (английский алфавит) равно 65. Листинг 5. Выводим ASCII-значение символа #include <stdio.h> int main() { char с; printf("Введите символ: "); // Читаем ввод пользователя scanf("%c", &с); // %d отображает целочисленное значение символа // %с отображает сам символ printf("ASCII-код %с = %d\nf\ с, с); return 0;
100 примеров на Си | $ ./a.out [Введите символ: А JASCII-код А = 65 j $ ./a.out '[Введите символ: В IASCII-код В = 66 i Введите символ: С ASCII-код С = 67 \ ■Введите символ: D JASCII-код D s 68 $ ./a.out $ ./a.out si Рис. 5. ASCII-код символа В программе мы просим пользователя ввести символ, который будет сохра- нен в переменную с. Как мы знаем, вместо самого символа в переменной с будет храниться его ASCII-код. Далее мы выводим код и сам символ. При использовании формата %d будет выведен код символа, при использовании %с - сам символ. Пример 6. Вычисляем частное и остаток Теперь немного математики: попробуем вычислить частное и остаток при делении двух чисел. Для тех, кто в школе пропустил урок математики, да- вайте разберемся, что есть что: • Делимое - это число, стоящее слева от знака деления. • Делитель - это число, стоящее справа от знака деления, по сути, это чис- ло, на которое делим. • Частное - это число, стоящее после знака равно, результат деления. • Остаток - это число, оставшееся не делимым, которое меньше делителя. Теперь несколько примеров: 10 / 5 = 2
ЧАСТЫ. Введение Здесь 10 - делимое, 5 - делитель, 2 - частное. Теперь другой пример: 13 / 5 = 2 (3) Здесь 13 - делимое, 5 - делитель, 2 - неполное частное (так как есть оста- ток) и 3 - остаток от деления. Осталось только все это запрограммировать, см. листинг 6. Листинг 6. Вычисляем частное и остаток отделения (6.с) #include <stdio.h> int main(){ int dividend, divisor, quotient, remainder; printf("Введите делимое: "); scanf("%d", sdividend); printf("Введите делитель: "); scanf("%d", Sdivisor); // Computes quotient quotient = dividend / divisor; // Computes remainder remainder = dividend % divisor; printf("Частное = %d\n", quotient); printf("Остаток = %d\n", remainder); return 0; Результат работы программы приведен на рис. 6. Работает программа так: • Мы вводим делимое и делитель - dividend и divisor соответственно. • Используя оператор /, мы вычисляем частное (выражаясь математиче- ским языком - неполное частное во многих случаях). • Используя оператор %, мы вычисляем остаток от деления.
100 примеров на Си ;$ ./a.out Введите делимое: 13 Введите делитель: 5 Частное ~ 2 Остаток = 3 $ Рис. 6. Вычисляем частное и остаток Наконец, мы выводим частное и остаток с помощью функции printf(). Пример 7. Вычисляем размер int, float, double и char Переменные разных типов данных занимают в памяти разное количество пространства. Эта программа вычисляет размер переменных четырех типов - int, float, double и char. Размер вычисляется с помощью оператора sizeof. Листинг 7. Размер int, float, double и char (7.с) #include <stdio.h> int main() { int integerType; float floatType; double doubleType; char charType; printf("Размер int: %ld байт\п",sizeof(integerType)); printf ("Размер float: %ld байт\п", sizeof (floatType) ); printf("Размер double: %ld байт\п",sizeof(doubleType));
ЧАСТЬ 1. Введение printf("Размер char: %ld байт\п",sizeof(charType)); return 0; Вывод программы будет таким, как показано на рис. 7. Как видите, пере- менная типа int занимает 4 байта, float и double - 8 байтов, a char - 1 байт. Пример 8. Как работает ключевое слово long Ключевое слово long - это модификатор размера, позволяющий увеличить размер переменной при ее объявлении. Следующая программа демонстри- рует, как работает long. Листинг 8. Как работает long (8.с) #include <stdio.h> int main() { int a; long b; long long c; double e; long double f; printf("Размер int = %ld байт \n", sizeof (a)); printf("Размер long = %ld байт\п", sizeof(b)); printf("Размер long long = %ld байт\п", sizeof (c)); printf("Размер double = %ld байт\п", sizeof (e)); printf("Размер long double = %ld байт\п", sizeof(f)); return 0; Результат (размер переменных с модификатором long) приведен на рис. 8. В этой программе оператор sizeof используется для вычисления размера int, long, long long, double и long double. Обратите внимание, что ключевое слово long не может быть использовано с типами данных float и char.
100 примеров на Си > ./a.out Размер int » 4 байт Размер long ~ 8 байт Размер long long ~ 8 байт Размер double ~ 8 байт Размер long double * 16 байт $ Рис. 8. Размер переменных с модификатором long Пример 9. Меняем местами два числа Задача проста: пользователь должен ввести два числа, а наша программа - поменять числа местами. Для этого мы будем использовать временную переменную temp. Листинг 9а. Меняем местами два числа (9.с) #include <stdio.h> int main() { double А, В, temp; printf("Введите A: ") ; scanf (fl%lffI, &A) ; printf("Введите В: "); scanf("%lf",&B); // Значение А будет присвоено переменной temp temp = A; // Значение В будет назначено переменной А А = В;
ЧАСТЫ. Введение // Значение temp будет присвоено В В = temp; printf("ХпПосле замены, А = %.21f\n", А); printf ("После замены, В = %.21f", В); return 0; Все предельно просто - результат приведен на рис. 9. Но теперь усложним немного задачу - как мы можем сделать то же самое, но без использования временной переменной? Ведь еще одна переменная типа double - это еще 4 байта памяти. Сейчас лишние 4 байта кажутся смешными. Но это только сейчас. Привыкайте программировать правильно и не используйте лишние ресурсы. Если бы все программисты эффективно использовали ресурсы, то современные телефоны не были бы оснащены 2 Гб оперативной памяти, а внутренняя память не забивалась бы на 80% в течение двух дней использо- вания телефона. Введите А: 5 Введите В: 7 > ./a.out После замены, А = 7.00 После замены, В = $.00 $ Рис. 9. Результат работы программы Оказывается, с помощью математики можно легко решить эту задачу. Нуж- но от первого числа отнять второе число и присвоить результат первому (А). Ко второму числу нужно добавить новое значение первого числа. За-
100 примеров на Си тем от второго отнять новое значение первого и присвоить первому. Код приведен в листинге 96. #include <stdio.h> int main() { double A, B; printf("Введите А: "); scanf("%lf", &A); printf("Введите В: "); scanf("%lf",&B); A = A - B; В = A + B; A = В - A; printf ("ХпПосле замены, А = %.21f\n", A); printf("После замены, В = %.21f", В); return 0; Вывод программы будет таким же, как и в предыдущем случае.
ЧАСТЫ. Введение
ЧАСТЬ 2. Принятие решений и циклы
тм^ решений и циклы Какая же серьезная программа обходится без конструкций принятия реше- ний и циклов? В данной части мы рассмотрим множество примеров, демон- стрирующих на практике конструкции принятия решений и циклов. Пример 10. Проверяем, является ли число четным или нет Пример 11. Проверяем, является ли символ гласным или согласным Пример 12. Определяем максимум среди трех чисел Пример 13. Вычисляем все корни квадратного уравнения Пример 14. Является ли год високосным Пример 15. Проверяем, является ли число отрицательным или положитель- ным Пример 16. Вычисляем сумму натуральных чисел Пример 17. Проверяем, является ли символ алфавитным или нет Пример 18. Вычисление факториала Пример 19. Выводим таблицу умножения
100 примеров на Си Пример 20. Выводим последовательность Фибоначчи Пример 21. Вычисления НОД двух чисел Пример 22. Выводим символы от А до Z с использованием цикла Пример 23. Подсчитываем количество цифры целою числа Пример 24. Вычисляем обратное число Пример 25. Вычисляем степень числа Пример 26. Проверяем, является ли число палиндромом или нет Пример 27. Является ли число простым Пример 28. Выводим простые числа между двумя интервалами Пример 29. Проверяем число Армстронга Пример 30. Проверяем число Армстронга между двумя интервалами Пример 31. Создаем пирамиду и структуру Пример 32. Делаем простой калькулятор с использованием switch.xase
ЧАСТЬ 2. Принятие решений и циклы Пример 10. Проверяем, является ли число четным или нет Для проверки, является ли число четным, используется оператор %, позво- ляющий узнать остаток от деления. Напомню, что число является четным, если оно делится на 2 без остатка, например, 0, 8, 10, -50. А нечетное число не делится на 2 без остатка, например, 1,3,9,11. Код примера приведен в листинге 10. Мы используем оператор if, который имеет следующий вид: if (логическое выражение) оператор_1; else оператор_2; Если логическое выражение в скобках окажется истинным, то будет выпол- нен оператор_1 (или группа операторов, заключенных в фигурные скобки), в противном случае будет выполнен оператор_2. Условный оператор if можно использовать без else, например: if (логическое выражение) оператор_1; В этом случае, если логическое выражение окажется истинным, будет вы- полнен оператор_1, а если нет - программа ничего делать не будет и пере- йдет к выполнению следующего за if оператора. Листинг 10. Является ли число четным (Ю.с) #include <stdio.h> int main () { int number; printf("Введите целое число: "); scanf("%d"/ &number); if(number % 2 == 0) printf("%d - четное\п", number); else
100 примеров на Си printf("%d - нечетное\п", number); return 0; Введите целое 18 - четное Введите целое 119 - нечетное Введите целое 0 - четное $ число $ число $ число $ ./a.out : 18 ./a.out : 119 ./a.out Рис. 10. Программа, определяющая, является ли число четным или нет Пример 11. Проверяем, является ли символ гласным или согласным В этом примере мы проверим, является ли введенный пользователем сим- вол гласным или согласным. Мы знаем, что гласными в русском языке яв- ляются символы А, Е, И, О, Ы, Э, Ю, Я. Следовательно, все остальные сим- волы являются согласными. Чтобы не перегружать программу конструкциями if, мы будем хитрее. Ведь можно было бы написать вот такую конструкцию: if (с == "А") isVowel = true; else if (с == "E") isVowel = true; Вместо этого мы объявим две логических переменных isLowercase Vowel и isUppercase Vowel. Первая переменная станет истинной (ей будет присвоено
ЧАСТЬ 2. Принятие решений и циклы true), если пользователь введет гласный символ в нижнем регистре, вторая - если будет введен гласный в верхнем регистре. Для проверки истинности мы будем использовать вот такие конструкции: // возвращает 1 (true), если с - гласный (нижний регистр) isLowercaseVowel = (с == 'а1 || с == 'е1 || с == 'и1 || с == 'о1 || с == !у! || с == 'э' || с == |ю| ||с == 'я' II с == 'ы'); // возвращает 1 (true), если с - гласный (верхний регистр) isUppercaseVowel = (с == fAf || с == 'Е' || с == 'И' || с == f0f ||с == !У! || с == 'Э* || с == 'Ю' || с == 'Я' || с == 'Ы'); Посмотрите, что происходит. Переменным isLowercaseVowel и isUppercaseVowel будет присвоен результат выражения логического выра- жения. Выражение вроде с == ' а' вернет true, если символ с равен 'а1. Оператор || (логическое ИЛИ) возвращает true, если хотя бы один из его операндов истинный. Следовательно, все это большое выражение будет ис- тинным, если будет встречено хотя бы одно совпадение. Далее нам останется проверить, является ли истинной хотя бы одна из пере- менных isLowercaseVowel и isUppercaseVowel, если да, то введенный символ является гласным, в противном случае - согласным. Полный код програм- мы приведен в листинге 11. Листинг 11. Символ гласный или согласный (11 .с) #include <stdio.h> int main() ' { char с; int isLowercaseVowel, isUppercaseVowel; printf("Введите символ: "); scanf(nc", &c) ; // возвращает 1 (true), если с - гласный (нижний регистр) isLowercaseVowel = (с == 'а1 II с == fef I I с == 'и1 | | с == 'о1 || с == fyf ||с == f3f ||с == 'ю* ||c == 'я1 ||c == 'ы'); // возвращает 1 (true), если с - гласный (верхний регистр)
100 примеров на Си isUppercaseVowel = (с == fAf И с == fEf || с == 'И' || с == 'О1 || с == 'У1 || с == 'Э' || с == 'Ю' || с == 'Я' ||с == 'Ы'); // возвращает 1 (true), если true одна из переменых - isLowercaseVowel или isUppercaseVowel if (isLowercaseVowel || isUppercaseVowel) printf("Введенный символ - гласный\п"); else printf("Введенный символ - согласный\п"); return 0; $ ./a.out Введите символ: П Введенный символ - согласный $ Рис.11 Пример 12. Определяем максимум среди трех чисел В этом примере мы определим максимум среди трех чисел. При всей своей простоте программа демонстрирует, как использовать логический оператор && (логическое И). Данный оператор возвращает true, если оба его операн- да истинны. Например: а && b
ЧАСТЬ 2. Принятие решений и циклы Вернет true, если а - true И b - true. В противном случае оператор возвра- щает false. Зная, как работает этот оператор, написать нашу программу тру- да не составит. Код программы приведен в листинге 12. Листинг 12. Максимум среди трех чисел (12.с) #include <stdio.h> int main() double nl, n2, n3; printf("Введите три числа: "); scanf("%lf %lf %lf", &nl, &n2, &n3); if( nl>=n2 && nl>=n3 ) printf("%.2f - максимум.\n", nl); if( n2>=nl && n2>=n3 ) printf ("%.2f - максимум.\n", n2); if( n3>=nl && n3>=n2 ) printf("%.2f - максимум.\n", n3); return 0; $ ./a.out Введите три числа: 160 555 21 555.80 - максимум. $ i Рис. 12. Программа в действии (12.с)
100 примеров на Си Конечно, это не единственный способ решения задачи. Можно определить максимум немного иначе, например, так: #include <stdio.h> int main () { double nl, n2, n3; printf("Введите три числа: "); scanf("%lf %lf %lf", &nl, &n2, &n3); if (nl>=n2) { if(nl>=n3) printf("%.21f - максимум.\n ", nl); else printf("%.21f - максимум.\n ", n3); else if(n2>=n3) printf("%.21f - максимум.\n", n2) ; else printf("%.21f - максимум.\n",n3); return 0; Думаю, программа настолько проста, что не нуждается в комментариях. Ее отличие от первого варианта в том, что она использует оператор if..else, а в первом случае мы использовали просто оператор if (сокращенную форму условного оператора). Пример 13. Вычисляем все корни квадратного уравнения. Подключение библиотеки math Теперь немного математики. Стандартная форма квадратного уравнения выглядит так:
ЧАСТЬ 2. Принятие решений и циклы ахЛ2 + Ьх + с = О, где а, Ь, с - вещественные числа, а а - не равно 0. Термин ЬА2 - 4ас также известен как детерминант квадратного уравнения. Детерминант позволяет определить природу корней: • Если детерминант больше 0, корни являются вещественными и они раз- ные. Корней два. • Если детерминант равен 0, корни вещественные и одинаковые. Корень, по сути, один. • Если детерминант меньше 0, корни являются комплексными и разными. Всего корней 2. Если вы забыли курс математики, освежить знания можно на странички Википедии https://ru.wikipedia.org/wiki/KBaApaTHoe_ypaBHeHHe. А мы же приступим к написанию кода программы (лист. 13). Листинг 13. Вычисляем все корни квадратного уравнения. #include <stdio.h> #include <math.h> int main() { double a, b, c, determinant, rootl,root2, realPart, imaginaryPart; printf("Введите коэффициенты a, b и с: "); scanf("%lf %lf %lf",&a, &b, &c) ; determinant = b*b-4*a*c; if (determinant > 0) { // sqrt() возвращает квадратный корень rootl = (-b+sqrt(determinant))/(2*a) ; root2 = (-b-sqrt(determinant))/(2*a); printf ("rootl = %.21f и root2 = %.21f\n",rootl , root2); }
100 примеров на Си // когда корень один else if (determinant == 0) { rootl = root2 = -b/(2*a); printf("rootl = root2 = %.21f;\n", rootl); // если корни комплексные else { realPart = -b/(2*a); imaginaryPart = sqrt(-determinant)/(2*a); printf ("rootl = %.21f+%.21fi и root2 = % .2f-%.2fi\n", realPart, imaginaryPart, realPart, imaginaryPart); return 0; Работает программа довольно просто: сначала мы вычисляем детерминант, а затем вычисляем корни по формуле, полученной на страничке Википедии. Корни вычисляются по-разному в зависимости от значения детерминанта. den@den-pc:~/oex$ gcc 13.с /tPip/ctbr$d7r.o: In function main': 13.c:(»text+8x96); undefined reference to *sqrt' 13.c:(.text+Oxel): undefined reference to "sqrt1 13.c:(.text+8xlcf): undefined reference to *sqrt' collect2: error: Id returned 1 exit status den§den-pc gcc: ~/c-ex$ ~$ gcc 13.с -о 13 -In 13.с: Нет такого файла или каталога *<$ cd с-ех/ ~/с-«х$ gcc 13,с -о 13 -1го ~/с-«х$ ./13 Введите коэффициенты a, b и с: 1 2 3 rootl » -1.08+1.411 и root2 « -1.00-1.411 Рис. 13. Компиляция программы и ее работа Данная программа интересна не столько работой условного оператора, сколько математической библиотекой math.h и ее правильным подключе- нием. Для вычисления квадратного корня мы используем функцию sqrt(). Чтобы эта функция стала доступна, мы подключаем заголовочный файл math.h, однако при компиляции программы мы получаем сообщение о том,
ЧАСТЬ 2. Принятие решений и циклы что ссылка на sqrt не определена. Оказывается, математическую библиоте- ку нужно подлинковать, для этого необходима опция -lm компилятора: дсс 13.с -о 13 -lm Здесь 13.с - имя файла исходного кода, опция -о 13 задает имя выходного (исполнимого) файла, a -lm подключает библиотеку math. Процесс компи- ляции программы и результат ее работы приведен на рис. 13. Пример 14. Является ли год високосным Високосные года (в которых есть 29 февраля и, соответственно, - 366 дней) - это те, которые делятся на 4 без остатка: 2004,2008,2012,2016,2020,2024... Однако в григорианском католическом календаре, по которому мы ныне живем ("новый стиль"), есть еще редкое и малоизвестное правило: те года, которые нацело делятся на 100 (т.е. оканчиваются на -00) и которые делятся нацело на 400, - високосные, а которые делятся с остатком - не високосные. Поэтому 1700,1800,1900 года были невисокосными (хотя и делятся нацело на 4), 2000 - был високосным, как обычно (делится нацело на 400), 2100, 2200,2300 - также будут невисокосными. Как видите, держать всю эту информацию в уме сложно, поэтому давайте напишем программу, проверяющую, является ли год високосным (лист. 14). Листинг 14. Високосный год? #include <stdio.h> int main() int year; printf("Введите год: "); scanf("%d",&year); if(year%4 == 0) if ( year%100 == 0) // year делится на 400, поэтому високосный if ( year%400 == 0) printf("%d - високосный\п", year); else
100 примеров на Си printf("%d - невисокосный\п", year); else printf("%d - високосный\п", year ); } else printf("%d - невисокосный\п", year); return 0; $ gcc 14.с -о 14 $ ./14 введите год: 2616 2016 - високосный $ ./14 Введите год: 2017 2817 - невисокосный Ь «/14 Введите год: 2808 2069 - високосный $ ./14 Введите год: 3000 3608 - невисокосный $1 Рис. 14. Программа в работе Пример 15. Проверяем, является ли число отрицательным или положительным Самая простая программа в этом разделе, чтобы вы отдохнули от всяких математических вычислений. Попробуйте написать ее сами - не смотря в готовый код. Первым делом нужно прочитать введенное пользователем число: scanf("%lf", &number);
ЧАСТЬ 2. Принятие решений и циклы Далее нужно проверить, больше число 0 или нет. Что же касается 0, то быту- ют разные мнения относительного его нейтральности, а некоторые же счи- тают его положительным числом. Чтобы не придерживаться всяких теорий О, мы просто будем сообщать пользователю, что он ввел 0, и не будем сооб- щать, каким он является, - положительным или отрицательным. if (number <= 0.0) { if (number == 0.0) printf("Bbi ввели 0.\n"); else printf("Отрицательное.\n"); else printf("Положительное.\n"); Полный код программы приведен в листинге 15. Листинг 15. Определяем, является ли число положительным или отрицательным #include <stdio.h> int main() { double number; printf("Введите число: "); scanf("%lf", &number); if (number <= 0.0) { if (number == 0.0) printf("Вы ввели 0.\n"); else printf ("Отрицательное.\n"); } else printf("Положительное.\n"); return 0;
100 примеров на Си Введите число: Положительное. Введите число: Отрицательное. Введите число: Вы ввели 0. $ 7 9 $ -0 $ ./15 ./15 ./15 Рис. 15. Работа программы Конечно, приведенное здесь решение не является единственным правиль- ным. Вы бы могли написать программу, используя вложенный оператор if., else, например, так: if (number < 0.0) printf("Отрицательное.") ; // true, если number > 0 else if ( number > 0.0) printf("Положительное."); // иначе else printf("Вы ввели 0."); Пример 16. Вычисляем сумму натуральных чисел Натуральное число - это целое положительное число. Наша программа бу- дет работать так: пользователь вводит число п, а программа вычисляет сум- му чисел от 1 до п в цикле. Вот только какой цикл использовать? Мы знаем, что в С существует три цикла - for, while и do..while.
ЧАСТЬ 2. Принятие решений и циклы Первый цикл называют циклом-счетчиком. Его удобно использовать, когда мы знаем, сколько итераций (повторений) должно быть. В нашем случае удобно использовать именно его. Общий синтаксис выглядит так: for (оператор_1; условие; оператор_2) { телоцикла Здесь оператор_1 выполняется в самом начале и один раз, перед первым выполнением тела цикла. Его удобно использовать для инициализации пе- ременной-счетчика. Оператор 2 выполняется после каждой итерации, в нем удобно увеличивать счетчик. Наш цикл будет выполнять операции в теле цикла до тех пор, пока истинно условие, заданное при объявлении цикла. Код программы, использующий цикл for, приведен в листинге 16а. Листинг 16а. Вычисляем сумму натуральных чисел с помощью цикла for #include <stdio.h> int main() { int n, i, sum = 0; printf("Введите положительное целое число: "); scanf("%d",&n); for(i=l; i <= n; sum += i; // sum = sum+i; printf("Сумма = %d\n",sum); return 0; Первым делом цикл присваивает переменной i значение 1 - он инициали- зирует счетчик (нам нужно вычислить сумму от 1 до п). Далее цикл прове- ряет, истинно ли условие - если пользователь ввел 3 в качестве п, да, усло- вие истинно, поскольку i (1) меньше 3. Если условие истинно, выполняется тело цикла, а именно к переменной sum добавляется значение переменной i. И так до тех пор, пока i не станет больше п (у нас условие выполнения цикла меньше или равно).
100 примеров на Си Как уже было отмечено, поскольку мы знаем количество итераций (п), нашу задачу удобнее всего решить именно с циклом for. Однако ее можно решить и с помощью других циклов. Цикл while() выполняется, пока ис- тинно его условие: while (условие) { тело_цикла; Такой цикл удобно использовать, когда мы не знаем заранее количество итераций, например: tinclude <stdio.h> #include <stdlib.h> int main() { int k = 0, i = 0; while (k < 7) { k = 1 + rand() % 10; printf("%d ", k) ; printf("Количество итераций %d\n", i); return 0; Программа вызывает функцию rand(), которая возвращает случайное чис- ло до 10. В цикле while у нас условие: работать, пока к < 7, если полученное значение будет больше 7, например 8, то цикл прекратит свою работу. Про- грамма выведет счетчик итераций - сугубо для информации - и все сгене- рированные случайным образом числа. Например: 4 7 Количество итераций 2 Что же касается нашего примера, то решение задачи с помощью цикла while приведено в листинге 166.
ЧАСТЬ 2. Принятие решений и циклы Листинг 166. Сумма натуральных чисел с помощью цикла while() #include <stdio.h> int main() { int n, i, sum = 0; printf("Введите n: "); scanf("%d",&n); i = 1; while ( i <=n ) { sum += i; printf("Сумма = %d",sum); return 0; $ ./a.out Введите положительное целое число: 1 Сумма - 1 $ ./a.out Введите положительное целое число: 7 Сумма ~ 28 $ ,/a»out Введите положительное целое число: 108 Сумма = 5850 I Рис. 16. Работа программы
100 примеров на Си В отличие от цикла for, мы инициализируем счетчик i до цикла, затем уве- личиваем его в теле цикла. При написании подобных циклов главное не забыть увеличить переменную-счетчик в теле цикла, иначе цикл будет вы- полняться вечно. Цикл do..while, в отличие от цикла while, сначала выполняет тело цикла, а затем уже проверяет условие. Его удобно использовать, когда нужно, чтобы тело цикла выполнилось хотя бы один раз. Давайте переделаем программу из листинга 16а так, чтобы она запрашивала у пользователя ввод, пока он не введет целое положительное число. Далее вычислим сумму с помощью цикла for. Код программы в листинге 16с. Листинг 16с. Демонстрация цикла do..while tinclude <stdio.h> int main() int n, i, sum = 0; do { printf("Введите целое положительное число > 0: ") ; scanf("%d",&n); while (n <= 0); for(i=l; i <= n; ++i) sum += i; // sum = sum+i; printf("Сумма = %d",sum); return 0; } Пример 17. Проверяем, является ли символ алфавитным или нет В языке программирования С переменная типа char хранит ASCII-значение (целое число между 0 и 127). Алфавитными считаются символы с кодами от 97 до 122 (для нижнего регистра) и от 65 до 90 (для верхнего регистра). Если ASCII-значение, символа, введенного пользователем, лежит в диапа- зоне от 97 до 122 или от 65 до 90, то символ является алфавитным. В про- грамме мы можем использовать 'а' вместо 97 и 'zf вместо 122, аналогично,
ЧАСТЬ 2. Принятие решений и циклы 'А' используется вместо 65 и Z1 вместо 90. Поэтому код будет таким, как показано в лист. 17. Листинг 17. Определяем, является ли символ алфавитным или нет #include <stdio.h> int main() { char с; printf("Введите символ: "); scanf("%с",&с); if( (с>='а' && c<='z') || (c>='A' && c<='Z')) printf("%c - алфавитный\п",с); else printf ("%c - не алфавитныйХп'^с) ; return 0; Введите символ: S S - алфавитный введите символ: 7 7 - ке алфавитный введите символ: ( ( - не алфавитный $ дсс 17.с -о 17 $ ./17 $ ./17 Рис. 17. Программа в действии
100 примеров на Си Пример 18. Вычисление факториала Факториал положительного целого числа п - это результат умножения чи- сел от 1 до п: 0! = 1 1! = 1 2! = 2 3! = 6 4! = 24 5! = 120 6! = 720 7! = 5040 8! = 40320 9! = 362880 10! = 3628800 Листинг 18. Вычисление факториала #include <stdio.h> int main() int n, i; unsigned long long factorial = 1; printf("Введите n: "); scanf("%d",&n); // проверяем, чтобы введенное число было положительным if (n < 0) printf("Факториал отрицательного числа не существует.\п"); else { // factorial for(i=l; i<=n; { factorial *= i; factorial*i; } printf("Факториал %d = %llu\n", n, factorial); return 0;
ЧАСТЬ 2. Принятие решений и циклы Результат выполнения программы приведен на рис. 18. Поскольку факто- риал - это операция умножения, очень легко выйти за пределы диапазона. Для переменной factorial мы использовали тип unsigned long long, чтобы сделать максимальное значение максимально большим. Но и это не позво- ляет вычислить факториал даже 66. Максимальное значение, которое по- мещается в unsigned long long, - это 65! В случае переполнения вы получите результат 0. Введите п: 56 Факториал 56 ■ Введите п: 86 Факториал 86 = Введите п: 75 Факториал 75~ Введите п: 76 Факториал 78 = Введите п: 55 Факториал 55 * Введите п: 68 Факториал 66 = Введите п: 65 Факториал 65 - Введите п: 69 Факториал 69 = Введите п: 68 Факториал 68- Введите п: 67 Факториал 67 « Введите п: 66 Факториал 66 ~ $ ./18 15188249665818642432 } ./18 $ ./18 > ./18 $ ./18 6711489344688881664 $ ./18 9727775195126271368 9223372836854775868 $ ./18 $ ./18 } -/18 8 * ./18 6 Рис. 18. Результат выполнения программы Пример 19. Выводим таблицу умножения Напишем простенькую программку, выводящую таблицу умножения за- данного пользователем числа. Логика программы проста: пользователь вво- дит число п, и мы его последовательно (в цикле for) умножаем на числа от 1 до 10 и выводим результат. Код программы приведен в листинге 19.
100 примеров на Си Листинг 19. Выводим таблицу умножения (19.с) #include <stdio.h> int main() { int n, i; printf("Введите число: "); scanf("%d",&n); for(i=l; i<=10; printf("%d return 0; %d = %d \n", n, i, n*i) ; Введите число: 7 7 * 1 ш 7 7 * 2 г 14 7 * 3 * 21 4-28 5 * 35 6 ш 42 7 * 49 8 « 56 9 * 63 18 « 76 $ . Рис. 19. Таблица умножения Однако, если честно, такая табличка смотрится уж очень "жиденько" для книги примеров. Давайте выведем полноценную таблицу умножения для чисел от 1 до 9. Для ее построения мы будем использовать два цикла: пер- вый будет внешний и будет считать строки - от 1 до 9. Счетчиком этого цикла будет переменная i. Второй будет внутренним - он будет выполнять- ся внутри первого цикла for. Его счетчик - это переменная]. Этот цикл бу- дет умножать i * j и выводить результат. Мы используем формат %2d, чтобы отделить числа друг от друга на читабельное расстояние - чтобы наша та- бличка не сливалась в единое месиво цифр. Результат приведен на рис. 196, а код - в листинге 196.
ЧАСТЬ 2. Принятие решений и циклы 1 2 3 4 5 6 7 8 9 2 4 б 8 18 12 14 16 18 3 б 9 12 15 18 21 24 27 4 8 12 16 20 24 28 32 36 $ 5 10 15 28 25 38 35 40 45 $ ./19Ь б 12 18 24 38 36 42 48 54 7 14 21 28 35 42 49 56 63 8 16 24 32 40 48 56 64 72 9 18 27 36 45 54 63 72 81 Рис. 196. Полноценная табличка умножения Листинг 196. Программа, генерирующая полноценную табличку умножения #include <stdio.h> int main() { int i, j;• for (i = 1; i <= 9; for (j = 1; j <= 9 printf("%2d ", i printf("\n"); return 0; Пример 20. Выводим последовательность Фибоначчи Числа Фибоначчи - элементы последовательности в которой каждое по- следующее число равно сумме двух предыдущих чисел. Напишем програм- му, выводящую последовательность Фибоначчи. В этой программе мы ис- пользуем две переменных tl и t2 для хранения двух предыдущих чисел.
100 примеров на Си Листинг 20. Вычисление последовательности Фибоначчи (20.с) #include <stdio.h> int main() int i, n, tl = 0, t2 = 1, nextTerm = 0; printf("Введите количество элементов последовательности: scanf("%d", &n); printf("Последовательность Фибоначчи: "); for (i = 1; i <= n; if(i == 1) printf("%d, ", tl); continue; } if (i == 2) { printf("%d, ", t2); continue; } nextTerm = tl + t2; tl = t2; t2 = nextTerm; $ ./20 Введите количество элементов последовательности: 18 Последовательность Фибоначчи: 8, 1, 1 2 3 5 8 13 21 34 Рис. 20. Последовательность Фибоначчи
ЧАСТЬ 2. Принятие решений и циклы printf("%d ", nextTerm); printf("\n"); return 0; Пример 21. Вычисления НОД двух чисел Наибольший общий делитель (НОД) двух чисел - это максимальное число, на которое могут быть без остатка разделены оба числа. Пример: для чисел 70 и 105 наибольший общий делитель равен 35. Существует множество способов определить НОД программно. Первый способ заключается в использовании цикла for. В нем мы перебираем дели- тели в порядке возрастания: если будет найден такой, на который делятся без остатка оба числа, мы будем считать его общим делителем. Поскольку делители перебираются в порядке возрастания, то последний общий дели- тель будет наибольшим. Первый способ решения приведен в листинге 21а. Листинг 21а. Определяем НОД с помощью цикла for #include <stdio.h> int main() { int nl, n2, i, gcd; printf("Введите два целых числа: "); scanf("%d %d", &nl, &n2); for(i=l; i <= nl && i <= n2; // Проверяем, делятся ли на i оба числа без остатка if(nl%i==0 && n2%i==0) gcd = i; printf("НОД - %d\n"/ gcd); return 0;
100 примеров на Си $ ./21 Введите два числа: 106 128 НОД - 20 $1 Рис. 21. Результат работы программы (определение НОД) Для определения НОД мы можем использовать и цикл while (лист. 216). Листинг 216. Определение НОД с помощью цикла while tinclude <stdio.h> int main() { int nl, n2; printf("Введите два целых числа: "); scanf("%d %d",&nl,&n2); while(nl!=n2) { if(nl > n2) nl -= n2; else n2 -= nl; } printf("НОД = %d",nl); return 0;
ЧАСТЬ 2. Принятие решений и циклы В прошлых примерах мы подразумевали, что введенные пользователем числа будут положительными. Напишем программу, вычисляющую НОД для отрицательных и положительных чисел (лист. 21 в). Листинг 21 в. НОД для отрицательных и положительных чисел #include <stdio.h> int main() { int nl, n2; printf("Введите два целых числа: "); scanf("%d %d",&nl,&n2); // Если введены отрицательные числа, меняем знак на положительный nl = ( nl > 0) ? nl : -nl; п2 = ( п2 > 0) ? п2 : -п2; while(nl!=n2) { if(nl > n2) nl -= n2; else n2 -= nl; } printf("НОД = %d",nl); return 0; Пример 22. Наименьшее общее кратное Наименьшее общее кратное (НОК) двух целых чисел тип есть наимень- шее натуральное число, которое делится на m и п без остатка. Как и в преды- дущем примере, есть несколько способов вычислить НОК. Первый способ решения - с помощью деления. Этот способ приведен в листинге 22а. Листинг 22а. Определение НОК с помощью бесконечного цикла #include <stdio.h> int main () int nl, n2, minMultiple; printf("Введите два целых положительных числа scanf("%d %d", &nl, &n2); ");
100 примеров на Си // определяем максимум сред nl и п2 и сохраняем в minMultiple minMultiple = (nl>n2) ? nl : n2; // Бесконечный цикл while(1) if ( minMultiple%nl==O && minMultiple%n2==0 ) printf("HOK = %d.\n", minMultiple); break; ++minMultiple; return 0; Особенность этого метода в том, что мы используем бесконечный цикл while. В этом и заключается опасность программы. Если вы не предусмо- трите (или неправильно предусмотрите) условие выхода из цикла, то про- грамма "зациклится" - цикл будет выполняться бесконечно. Если такое произошло, нажмите Ctrl + С для аварийного завершения программы. $ ./21 Введите два числа: 100 120 НОД - 2Э $ дсс 22.с -о 22 $ ./22 Введите два целых положительных числа: 72 16 НОК ~ 144. _.__*■ Рис. 22. Вычисление НОК двух чисел
ЧАСТЬ 2. Принятие решений и циклы В нашем случае условием выхода из цикла является нахождение НОК, по- сле чего выполняется оператор break, который и закрывает цикл while. Новичкам я бы не рекомендовал в своих программах использовать бес- конечные циклы, поэтому рассмотрим второй вариант. Если вы помните школьный курс математики, то вычислить НОК можно так: Icm = (nl*n2)/gcd; То есть сначала можно вычислить НОД, а затем разделить на него результат умножения двух чисел. Такой вариант гораздо безопаснее и не приведет к зацикливанию программы, поскольку используется цикл со счетчиком for. Второй вариант определения НОК приведен в листинге 226. Листинг 226. Определение НОК через НОД #include <stdio.h> int main() { int nl, n2, i, gcd, lcm; printf("Введите два целых положительных числа: "); scanf("%d %d",&nl,&n2); // Вычисляем НОД for(i=l; i <= nl && i <= n2; { if(nl%i==0 && n2%i==0) gcd = i; } // Вычисляем НОК lcm = (nl*n2)/gcd; printf("НОК = %d.\n", lcm); return 0; Пример 23. Подсчитываем количество цифр целого числа Попробуем вычислить количество цифр в целом числе. Например, если пользователь введет 1983, то программа выведет 4. Для этого мы будем по- следовательно делить число на 10 и подсчитывать, сколько раз мы этого сделали. Количество операций делений и будет равно количеству цифр.
100 примеров на Си Листинг 23. Определяем количество цифр в целом числе #include <stdio.h> int main() { long long n; int count = 0; printf("Введите целое число: "); scanf("%lld", &n); while(n != 0) { // n = n/10 n /= 10; ++count; printf("Количество цифр: %d\n", count); $ . введите целое число: Количество цифр: 1 $ . Введите целое число: Количество цифр: 2 > . Введите целое число: Количество цифр: 3 $ . Введите целое число: Количество цифр: 4 ..* . Введите целое число: Количество цифр: 7 . _ .. . $ /23 1 /23 20 /23 121 /23 1983 /23 1880008 Рис. 23. Определяем количество цифр
ЧАСТЬ 2. Принятие решений и циклы Пример 24. Вычисляем обратное число Напишем программу, которая вычисляла бы обратное целое число, напри- мер, если пользователь вводит 2211, то программа возвращает 1122. Алгоритм работы программы такой: • В цикле мы вычисляем остаток от деления числа на 10 • Постепенно формируем обратное число - постепенно умножаем его на 10 и добавляем остаток от предыдущей операции деления Листинг 24. Вычисляем обратное число #include <stdio.h> int main() { int n, reversedNumber = 0, remainder; printf("Введите целое число: "); scanf("%d", &n); while(n != 0) { remainder = n%10; $ ./24 Введите целое число: 1S89 Обратное число ~ 9851 $ ./24 введите целое число: 1122 Обратное число ~ 2211 I -/24 Введите целое число: 122 Обратное число = 221 $■ Рис. 24. Вычисляем обратное число
100 примеров на Си reversedNumber = reversedNumber*10 + remainder; n /= 10; printf("Обратное число = %d\n", reversedNumber); return 0; } Пример 25. Вычисляем степень числа Напишем программу, возводящую число в заданную степень. Для вычисле- ния степени принято использовать функцию pow(), находящуюся в math.h. Однако если вам не хочется подключать всю математическую библиотеку из-за одной функции, то вам пригодится этот пример. Листинг 25. Вычисления степени числа без функции pow() #include <stdio.h> int main() { int base, exponent; $ ./25 Введите число: 2 Введите степень: 8 Результат = 256 $1 Рис. 25. Вычисляем степень числа
ЧАСТЬ 2. Принятие решений и циклы long long result = 1; printf("Введите число: "); scanf("%d", &base); printf("Введите степень: ") ; scanf("%d", &exponent); while (exponent != 0) { result *= base; —exponent; printf("Результат = %lld\n", result) return 0; Если бы мы использовали функцию pow(), то все вычисление реализова- лось бы одной строчкой: result = pow(base, exponent); Только не забудьте подключить math.h и указать опцию -lm при компиля- ции программы. Пример 26. Проверяем, является ли число палиндромом или нет Палиндром - это число, которое одинаково выглядит в прямом и обратном направлении, например, 121,53235 - это числа палиндромы. Напишем про- грамму, которая будет определять, является ли введенное пользователем число палиндромом или нет. Работать программа будет так: сначала она вычислит обратное число, а по- том сравнит его с исходным числом. Если они одинаковые, то число явля- ется палиндромом.
100 примеров на Си Листинг 26. Проверяем, является ли число палиндромом #include <stdio.h> int main() int n, reversedlnteger = 0, remainder, originallnteger; printf("Введите целое число: "); scanf("%d", &n); originallnteger = n; // вычисляем обратное число while( n!=0 ) remainder = n%10; reversedlnteger = reversedInteger*10 + remainder; n /= 10; // палиндром, если исходное число и обратное одинаковые if (originallnteger == .reversedlnteger) printf("%d - палиндром\п", originallnteger); else $ ./26 Введите целое число: 121 121 - палиндром S ./26 Введите целое число: 789 789 - не палиндром $ Рис. 26. Результат работы программы
ЧАСТЬ 2. Принятие решений и циклы printf("%d - не палиндром\п", originallnteger); return 0; Пример 27. Является ли число простым Простое число - это то, которое делится только на 1 и на себя. Например: 2, 3, 5, 7, 11, 13. Код программы, определяющей, является ли число простым, приведен в листинге 27. Листинг 27. Является ли число простым #include <stdio.h> int main () { int n, i, flag = 0; printf("Введите целое положительное число: "); scanf("%d",&n); for(i=2; i<=n/2; // condition for nonprime number if(n%i==0) { flag=l; break; if (flag==0) printf("%d - простое\п",n); else printf("%d - не является простым\п",п); return 0; Если цикл for заканчивается, когда тестовое выражение i<=n/2 = false, вве- денное число является простым. Значение переменной flag при этом будет равно 0. Если цикл for заканчивается из-за оператора break внутри цикла, то число не является простым, при этом значение flag будет равно 1.
100 примеров на Си > ./27 Введите целое положительное число: 7 7 - простое fr ./27 Введите целое положительное число: 24 24 - не является простым $1 Рис. 27. Определяем, является ли число простым Пример 28. Выводим простые числа в интервале Усложним предыдущую задачу: представим, что нужно вывести простые числа, находящиеся в интервале между двумя введенными числами. Дан- ная задача решается с использованием вложенного цикла for и условного оператора if..else. В этой программе (лист. 28) цикл while выполняет (high - low - 1) итера- ций. При каждой итерации проверяется, является ли число простым. Если да, оно выводится, если нет, просто происходит переход на следующую ите- рацию. Для определения, является ли число простым, мы используем уже знакомый по предыдущему примеру цикл for. Листинг 28. Выводим простые числа между а и b (между low и high) #include <stdio.h> int main() { ' int low, high, i, flag; printf("Выводим простые числа между а и Ь, введите а и Ь: "); scanf("%d %d", slow, &high);
ЧАСТЬ 2. Принятие решений и циклы while (low < high) flag = 0; for(i =2; i <= low/2; if(low % i == 0) flag = 1; break; if (flag == 0) printf("%d ", low); printf("\n"); return 0; Числа а и b в программе называются low и high, подразумевается, что поль- зователь введет сначала меньшее число, а потом большее. А что если наобо- рот? Чтобы наша программа была более универсальной, мы должны пред- усмотреть этот вариант развития событий. Если low окажется больше, чем high, мы должны поменять их местами: if (low > high) { temp = low; low = high; high = temp; Соответственно, в листинге 286 приведен полный исходный код програм- мы, учитывая эту поправку. Листинг 286. Выводим простые числа #include <stdio.h> int main()
100 примеров на Си int low, high, i, flag; printf("Выводим простые числа между а и b, введите а и Ь: scanf("%d %d", &low, &high); if (low > high) { temp = low; low = high; high = temp; while (low < high) flag = 0; for(i =2; i <= low/2; if(low % i == 0) flag = 1; break; $ ./28 Выводим простые числе между а и Ь, введите а и Ь: 1 20 1 2 3 5 7 11 13 17 19 $ I Рис. 28. Выводим простые числа между а и b
ЧАСТЬ 2. Принятие решений и циклы if (flag == 0) printf("%d ", low); printf("\n"); return 0; Пример 29. Проверяем число Армстронга Самовлюблённое число, или совершенный цифровой инвариант (англ. pluperfect digital invariant, PPDI), или число Армстронга - натуральное число, которое в данной системе счисления равно сумме своих цифр, воз- ведённых в степень, равную количеству его цифр. Пример числа Армстронга: 1634 = 1А4 + 6А4 + 3А4 + 4А4 153 = 1А3 + 5А3 + 3А3 Для решения этой задачи нам нужно определить количество разрядов в числе. Именно для этого мы и писали программу в одном из предыдущих примеров. Ведь нам нужно знать, в какую степень возводить составляющие числа. Код программы приведен в листинге 29. Листинг 29. Является ли число числом Армстронга (29.с) #include <stdio.h> #include <math.h> int main() { int number, originalNumber, remainder, result = 0, n printf("Введите число: "); scanf("%d", &number); 0 ;
100 примеров на Си originalNumber = number; while (originalNumber != 0) { originalNumber /= 10; originalNumber = number; while (originalNumber != 0) { remainder = originalNumber%10; result += pow(remainder, n); originalNumber /= 10; if (result == number) printf("%d - число Армстронга\п", number); else printf("%d - не является числом Армстронга\п", number); return 0; $ ./29 Введите число: 153 153 - число Армстронга $ ./29 введите число: 1634 1634 - число Армстронга $ Рис. 29. Результат работы программы из лист. 29
ЧАСТЬ 2. Принятие решений и циклы Пример 30. Выводим числа Армстронга в заданном диапазоне Ранее мы писали программу, выводящую простые числа, находящиеся в за- данном диапазоне. Напишем аналогичную программу, только выводящую числа Армстронга, если таковые имеются. Работает программа просто: в цикле for перебираются числа заданного диапазона и каждое проверяется, является ли оно числом Армстронга. Листинг 30. Выводим числа Армстронга в заданном диапазоне #include <stdio.h> #include <math.h> int main() { int low, high, i, tempi, temp2, remainder, n = 0, result 0; printf("Введите два числа (начало и конец диапазона): "); scanf("%d %d", Slow, &high); printf("Выводим числа Армстронга:"); for(i = low + 1; i < high; temp2 = i; tempi = i; // k-bo разрядов while (tempi != 0) tempi /= 10; // результат содержит сумму цифр, возведенных в степень п while (temp2 != 0) remainder = temp2 % 10; result += pow(remainder, n); temp2 /= 10; // проверяем, является ли число числом Армстронга if (result == i) {
100 примеров на Си printf("%d ", i) ; // сброс значений перед след. итерацией п = 0; result = 0; prints "\n") ; return 0; $ ./30 Введите два числа (начало и конем диапазона): 1 2006 Выводим числа Армстронга:2 3 4 5 6 7 8 9 153 376 371 407 1634 $1 Рис. 30. Числа Армстронга в диапазоне от 1 до 2000 Пример 31. Создаем пирамиду и структуру Чтобы отдохнуть от математики (если, конечно, вы выполняете примеры последовательно), давайте напишем программу, которая выводит пирами- ду, состоящую из *. Для решения данной задачи мы будем использовать два вложенных цикла for. Первый будет "считать" строки, а второй - выводить звездочки в каждой строке.
ЧАСТЬ 2. Принятие решений и циклы Листинг 31. Создаем пирамиду из звездочек (*) #include <stdio.h> int main() int i, j, rows; printf("Количество строк: scanf("%d",&rows); "); for(i=l; i<=rows; { for(j=l; j<=i; ++ { printf (lf* ") ; } printf("\n"); } return 0; Пример 32. Делаем простой калькулятор с использованием switch..case Давайте напишем простенький калькулятор, демонстрирующий пример ис- пользования оператора switch.xase. Алгоритм программы будет такой. Сначала пользователь вводит оператор (например, *), затем два операнда - числа. После этого программа возвра- щает результат операции. Оператор выбора switch.xase позволяет произве- сти действия (в нашем случае - математические) в зависимости от пере- данного ему значения. Оператор break внутри case нужен, чтобы прервать выполнение оператора switch - ведь нам нужно выполнить только одной из действий, а не все нижестоящие после того, как было встречено совпадение. Листинг 32. Простой калькулятор # include <stdio.h> int main() { char operator;
100 примеров на Си double firstNumber, secondNumber; printf("Введите оператор ( + , -, *,): "); scanf("%c", &operator); printf("Введите два операнда: "); scanf ("%lf %lf", sfirstNumber, &secondNumber); switch(operator) { case л + ': printf ("%.llf + %.llf = %.llf"/firstNumber, secondNumber, firstNumber + secondNumber) ; break; case y-': printf ("%.llf - %.llf = %.llf", firstNumber, secondNumber, firstNumber - secondNumber) ; break; case л*' : printf ("%.llf * %.llf = %.llf",firstNumber, secondNumber, firstNumber * secondNumber) ; break; case V : if (secondNumber != 0) printf ("%.llf / %.llf = %.llf",firstNumber, secondNumber, firstNumber / firstNumber) ; else printf("На ноль делить нельзя!"); break; // оператор неизвестен (+, -, *, /) default: printf("Ошибка! Неправильный оператор"); printf("\n"); return 0; На рис. 32 показано тестирование нашего калькулятора. Продемонстриро- вано, как программа реагирует на деление на 0, на ввод некорректного опе- ратора.
ЧАСТЬ 2. Принятие решений и циклы $ >/32 Введите оператор (+, -, *,): + Введите два операнда: 2 2 2.в + 2.0 * 4.0 $ ./32 Введите оператор (+, -, *,): / Введите два операнда: 2 в На ноль делить нельзя! $ ./32 Введите оператор (+, -, *,): * Введите два операнда; 5 9 S.8 * 9.8 * 45.8 $ ./32 Введите оператор (+, -, *,): 79 81 Введите два операнда: Ошибка! Неправильный оператор $ ./32 Введите оператор (+, -, *,): - Введите два операнда: 79 81 79.0 - 81.8 ж -2.8 I Рис. 32. Тестирование калькулятора
100 примеров на Си
ЧАСТЬ 3. Функции ^ р^^щ
100 примеров на Си Пример 33. Проверка простого числа или числа Армстронга с использованием пользовательской функции Пример 34. Отображаем простые числа между двумя интервалами с ис- пользованием функции Пример 35. Проверяем, может ли число быть выраженным как сумма двух простых чисел Пример 36. Сумма натуральных чисел с использованием рекурсии Пример 37. Факториал с использованием рекурсии Пример 38. НОД с использованием рекурсии Пример 39. Конвертируем двоичные числа в десятичные и наоборот Пример 40. Конвертируем восьмеричные числа в десятичные и наоборот Пример 41. Конвертируем двоичные числа в восьмеричные и наоборот Пример 42. Вычисляем степень числа с помощью рекурсии
ЧАСТЬ 3. Функции Пример 33. Проверка простого числа или числа Армстронга с использованием пользовательской функции В предыдущей части книги мы писали программы, определяющие, явля- ется ли введенное пользователем число простым или числом Армстронга. Данная часть книги посвящена функциям, поэтому сейчас мы напишем две функции, позволяющие определить, является ли число простым и является ли оно числом Армстронга. Прежде, чем перейти к коду программы, немного теории. Что такое функ- ция? Функция - это подпрограмма, возвращающая некоторое значение. Как правило, функции передается какое-то значение (или несколько зна- чений), она производит какие-то действия (например, определяет, являет- ся ли число простым) и возвращает определенный результат (например, 1, если число является простым, и 0, если это не так). Конечно, бывают и частные случаи, например, когда функции вообще не передается никаких значений, и функции, которые ничего не возвращают. В других языках (например, в Паскале), такие подпрограммы называются процедурами, в С другого названия не предусмотрено. Для чего нужны подпрограммы? Чтобы программисту не пришлось писать один и тот же код в разных частях программы. Во-первых, так неудобно. Во- вторых, если программист допустил ошибку, то проще исправить ее один раз - в теле функции, чем искать, где он использовал ошибочный код по всей программе. В наших небольших программах преимущество использования функций не совсем очевидно, но когда вы станете успешным программистом, вы не смо- жете без них обходиться - уж поверьте. Определить функцию можно с помощью ключевого слова function: <тип> function имя (параметры) { тело; Тип - это тип возвращаемого значения. В скобках после имени функции указываются параметры, для каждого параметра обязательно указывается его тип. В теле функции должен быть оператор return, возвращающий зна- чение указанного при объявлении функции типа. Если функция не возвращает значения, то ее тип void:
100 примеров на Си void function separator () printf("================ ="); В коде программы гораздо правильнее вызывать функцию separator() для разделения блоков выводимого текста, чем просто printf(). Если вам захо- чется со временем изменить символ разделителя и вместо = выводить, ска- жем, *, то для этого нужно будет изменить код одной функции, а не править код всей программы и искать, где еще вы вызывали тот злосчастный printf(). Теперь возвращаемся к нашей программе. У нас будет функция main() и еще две функции - checkPrimeNumber() и checkArmstrongNumber(). Обе функции принимают значение типа int, возвращают тоже тип int. Чтобы функции могли быть вызваны в функции main(), нужно объявить их до функции main. Но это не всегда удобно, учитывая, что код некоторых функций может быть очень длинным. Поэтому мы можем использовать так называемое предварительное объявление функции - это когда вы описыва- ете прототип функции (тип возвращаемого значения, имя, параметры), но не пишете сам код функции. А после функции main() можно будет полно- стью описать наши функции. Предварительное объявление дает компиля- тору понять, что мы не забыли о функции и что она будет объявлена далее в коде программы. Полный код программы приведен в листинге 33. Листинг 33. Проверка на простое число и число Армстронга с использованием функций (33.с) ♦include <stdio.h> ♦include <math.h> int checkPrimeNumber(int n); int checkArmstrongNumber(int n); int main() int n, flag; printf("Введите положительное целое число: scanf("%d", &n); // Проверка на простое число flag = checkPrimeNumber (n) ; if (flag ==1)
ЧАСТЬ 3. Функции else printf("%d - простое.\п", п); printf("%d - не является простым.\п", п); // Проверка на число Армстронга flag = checkArmstrongNumber (n) ; if (flag == 1) printf("%d - число Армстронга.\n", n); else printf("%d - не является числом Армстронга.\п",n); return 0; int checkPrimeNumber(int n) { int i, flag = 1; for(i=2; i<=n/2; // Условие для не простого числа if(n%i == 0) { flag = 0; break; return flag; } int checkArmstrongNumber(int number) { int originalNumber, remainder, result originalNumber = number; while (originalNumber != 0) { originalNumber /= 10; = 0, n = 0, flag; originalNumber = number; while (originalNumber != 0)
100 примеров на Си remainder = originalNumber%10; result += pow(remainder, n); originalNumber /= 10; // Условие для числа Армстронга if(result == number) flag = 1; else flag = 0; return flag; Как работает программа и как ее правильно компилировать (нужно указать опцию -lm, иначе компилятор будет "ругаться"), показано на рис. 33. $ cd c-ex $ gcc 33.с - /tnp/ccA4cGq0.o: In function 33.c:(.text+Gxl9f): undefinec collect2: error: Id returned $ gcc 33.с - $ ./33 о 33 "checkArrcstrongNumber4: [ reference to *pow* 1 exit status о 33 -In введите положительное целое число: 2 2 - простое. 2 - число Армстронга. : ./. $ -/33 введите положительное целое число: 1634 1634 - не является простым. 1634 - число Армстронга. $ Рис. 33. Программа в действии (33.с)
ЧАСТЬ 3. Функции Пример 34. Отображаем простые числа между двумя интервалами с использованием функции Рднее мы писали программу, выводящую простые числа в заданном диапа- зоне. Давайте модернизируем ее с использованием уже готовой функции checkPrimeNumber(). Код программы будет таким же, но вместо того, чтобы в цикле производить вычисления, мы просто будем вызывать нашу функ- цию. Листинг 34. Выводим простые числа с помощью пользовательской функции tinclude <stdio.h> int checkPrimeNumber(int n); void separator(); int main() int nl, n2, i, flag; printf("Введите начало и конец диапазона чисел: "); scanf("%d %d", &nl, &n2); printf("Выводим простые числа в заданном диапазоне:") separator(); for(i=nl+l; i<n2; ++i) // будет равен 1, если число простое flag = checkPrimeNumber(i); if(flag == 1) printf("%d ",i); separator(); return 0; void separator() { printf("\n"); for (int i=0; i<80; printf("="); printf("\n");
100 примеров на Си // пользовательская функция для проверки на простое число int checkPrimeNumber(int n) { int j, flag = 1; for(j=2; j <= n/2; ++j) { if (n%j == 0) { flag =0; break; } } return flag; В данной программе мы использовали две функции - одну для вывода нуж- ного количества знаков ||=:", а вторую - для проверки на простое число. $ ./34 Введите начало и конец диапазона чисел: 1 1866 Выводим простые числа в заданном диапазоне: SSSSSSSSSSSSSSSSBSSSSSSXXSSKSSaSSSSSSSSSXBKSSSS 2 3 S 7 11 87 109 113 23 227 229 37 347 349 57 461 463 93 599 661 19 727 733 57 859 863 97 13 17 19 23 127 131 137 233 239 241 353 359 367 467 479 487 687 613 617 739 743 751 877 881 883 29 31 37 41 139 149 151 251 257 263 373 379 383 491 499 563 619 631 641 757 761 769 887 987 911 43 47 53 59 157 163 167 269 271 277 389 397 401 589 521 523 643 647 653 773 787 797 919 929 937 SSSSSSSSS 61 67 71 73 173 179 181 281 283 293 409 419 421 541 547 $57 659 661 673 889 811 821 941 947 953 79 83 89 97 191 193 197 307 311 313 431 433 439 563 569 571 677 683 691 823 827 829 967 971 977 161 183 1 199 211 2 317 331 3 443 449 4 577 587 5 781 709 7 839 853 8 983 991 9 $1 Рис. 34. Вывод простых чисел между 1 и 1000
ЧАСТЬ 3. Функции Пример 35. Проверяем, может ли число быть выраженным как сумма двух простых чисел Продолжаем дальше наше путешествие по миру простых чисел. Давайте напишем программу, позволяющую проверить, может ли быть число выра- женным в виде суммы простых чисел. Наша программа в цикле for будет пытаться найти сумму простых чисел, равную заданному пользователем числу. Листинг 35. Может ли число быть выраженным в виде суммы простых чисел #include <stdio.h> int checkPrime(int n); int main() { int n, i, flag = 0; printf("Введите целое положительное число: "); scanf("%d", &n); for(i = 2; i <= n/2; // проверяем, является ли i простым if (checkPrime(i) == 1) // является ли n-i простым if (checkPrime(n-i) == 1) // n = primeNumberl + primeNumber2 printf("%d = %d + %d\n", n, i, n - i); flag = 1; if (flag == 0) printf("%d не может быть выражено как сумма простых чисел.", п); printf("\п"); return 0; Ч Function to check prime number
100 примеров на Си int checkPrime(int n) int i, isPrime = 1; for<i =2; i <= n/2; if(n % i == 0) isPrime = 0; break; } return isPrime; Введите 34 m 34 « 34 * 34 ж 3 + 5 + 11 17 Введите 7S * 2 + Введите 166 163 160 166 166 168 168 168 в 3 « 11 « 23 a 29 я 47 ■ 53 ж 59 ■ 71 целое 31 29 + 23 ♦ 17 целое 73 целое + 157 + 149 + 137 + 131 ♦ 113 * 167 + 181 + 89 $ ./35 положительное число; 34 $ ./35 положительное число; 75 5 ./35 положительное число; 168 $ Рис. 35. Результат работы программы (35.с) Пример 36. Сумма п натуральных чисел с использованием рекурсии Рекурсия - это явление, когда функция вызывает саму себя. Начинающим программистам не рекомендуется использовать рекурсию, поскольку она может привести к переполнению стека программы, если не предусмотреть корректного условия выхода из рекурсии, то есть завершения функции.
ЧАСТЬ 3. Функции Для знакомства с рекурсией давайте рассмотрим простую программу, вы- числяющую сумму натуральных чисел (лист. 36). Листинг 36. Сумма п натуральных чисел с использованием рекурсии ♦include <stdio.h> int addNumbers(int n); int main() int num; printf("Введите положительное целое число: scanf ("%d", &num) ; printf("Сумма = %d\n",addNumbers(num)); return 0; "); int addNumbers(int n) { if (n != 0) return n + addNumbers(n-1); else return n; Давайте разберемся, что происходит. Допустим, пользователь ввел цифру 2. Функция addNumbers() вызывается с параметром 2. Функция проверяет, что переданное ей значение не равно 0, и поэтому возвращает значение: return 2 + addNumbers(1); Снова вызывается функция, но уже с параметром 1. Так как 1 - не равно 0, то функция возвращает значение: 1 + addNumbers(0); Функция проверяет, что переданное ей значение равно 0, и возвращает его. Теперь, что у нас есть: • 0 • 1 • 2 Сумма всех этих значений равна 3, что и показано на рис. 36.
100 примеров на Си Введите Сумма ■ введите Сумма = введите Сумма = $ ./36 положительное целое 3 $ ./36 положительное целое 15 * ./36 положительное целое 55 $ число: число: число: 2 5 16 Рис. 36. Результат работы программы Очень важно предусмотреть условие выхода из рекурсии. В нашем случае условием выхода является ns0: функция просто возвращает 0 и не вызы- вает снова саму себя. Пример 37. Факториал с использованием рекурсии Ранее (в примере 18) было показано, как вычислить факториал. Давайте напишем рекурсивный вариант программы. Вообще на практике большин- ство рекурсивных алгоритмов можно заменить на нерекурсивные, но при обучении программированию очень важно понять суть рекурсии. Именно поэтому несколько примеров в этой части книги посвящено именно рекур- сии. Листинг 37. Вычисление факториала с использованием рекурсии (37.с) #include <stdio.h> long int fact(int n); int main() int n; printf("Введите положительное число: ");
ЧАСТЬ 3. Функции scanf("%d", &n); printf("Факториал %d = %ld\n", n, fact(n)); return 0; } long int fact(int n) { if (n >= 1) return n*fact(n-l); else return 1; Здесь рекурсивной является функция fact(). Она вызывает саму себя, а ус- ловием выхода является п >-1, в противном случае функция просто возвра- щает значение 1. Пройдемся по вызовам функции. Пользователь передает число 5: return 5 * fact (4); return 4 * fact(3); return 3 * fact(2); return 2 * fact(l); return 1; Результат 1 * 2 * 3 * 4 * 5 - 120 $ ./37 Введите положительное число: 5 Факториал 5 = 128 $ ./37 Введите положительное число: 7 Факториал 7 « 5946 $ ./37 Введите положительное число: 18 Факториал 16 » 3628866 $ Рис. 37. Факториал с использованием рекурсии
100 примеров на Си Пример 38. НОД с использованием рекурсии Еще один пример использования рекурсии. На этот раз рекурсию мы будем использовать для вычисления наибольшего общего делителя. Здесь мы ис- пользуем рекурсивную функцию hcf(), условие выхода - п2 * 0. Листинг 38. Вычисляем НОД с использованием рекурсии (38.с] #include <stdio.h> int hcf(int nl, int n2) ; int main() int nl, n2; printf("Введите 2 положительных целых числа: "); scanf("%d %d", &nl, &n2); printf("НОД = %d\n", hcf(nl,n2)); return 0; int hcf(int nl, int n2) { if (n2 != 0) return hcf(n2, nl%n2); else Рис. 38. НОД с использованием рекурсии
ЧАСТЬ 3. Функции return nl; Пример 39. Конвертируем двоичные числа в десятичные и наоборот Компьютер "думает" на языке двоичной системы счисления. Да, даже самый современный. Он оперирует только двумя числами - 0 и 1. Все, что есть в компьютере, - от простого текста, фотографии до программы, видеролика, музыкального файла - можно представить как набор нулей и единиц. Давайте же напишем программу, позволяющую переводить числа в при- вычной нам, десятичной, системе счисления в двоичную. Для преобразо- вания двоичного числа в десятичное мы будем использовать функцию convertBinaryToDecimal(). Конвертирование осуществляется последова- тельным делением на 10, при этом остаток от деления умножается на сте- пень двойки. Думаю, вы знаете, как переводить двоичные числа в десятич- ные со школьного курса информатики. Конвертирование из десятичной в двоичную осуществляется функцией convertDecimailTo Binary(). Данная функция выполняет последовательное деление на 2. Мы вычисляем остаток от деления, вычисляем частное и фор- мируем двоичное число. Чтобы было нагляднее, функция выводит каждый свой шаг, и вы сможете увидеть, как формируется двоичное число. Полный код программы приведен в листинге 39. Листинг 39. Преобразование между двоичной и десятичной системами счисления (39.с) #include <stdio.h> #include <math.h> int convertBinaryToDecimal(long long n) ; long long convertDecimalToBinary(int n); int main() { long long n; printf("Введите двоичное число: "); scanf("%lld", &n); printf("%lld (двоичное) = %d (десятичное)\n", n, convertBinaryToDecimal(n)); printf("Введите десятичное число: "); scanf("%d", &n);
100 примеров на Си printf("%d (десятичное) = %lld (двоичное)\n", п, convertDecimalToBinary(n)); return 0; int convertBinaryToDecimal(long long n) { int decimalNumber =0, i = 0, remainder; while (n!=0) { remainder = n%10; n /= 10; decimalNumber += remainder*pow(2,i); return decimalNumber; long long convertDecimalToBinary(int n) long long binaryNumber = 0; int remainder, i = 1, step = 1; while (n!=0) Введите $ ./39 двоичное число: 101 161 (двоичное) » 5 Введите Уаг 1: Уаг 2: Шаг 3: десятичное 5/2, Остаток 2/2, Остаток 1/2, Остаток (десятичное) число: 5 » 1, Частное » 2 « 8, Частное ■ 1 * « 1, Частное » 8 5 (десятичное) - 101 (двоичное) Рис. 39. Преобразование между системами счисления
remainder = n%2; printf("UIar %d: %d/2, Остаток step++, n, remainder, n/2); n /= 2; binaryNumber += remainder*i; i *= 10; } return binaryNumber; ЧАСТЬ З. Функции = %d, Частное = %d\n", Пример 40. Конвертируем восьмеричные числа в десятичные и наоборот Кроме двоичной и десятичной системы счисления в информатике частень- ко используется и восьмеричная система. Для представления чисел в ней используются цифры от 0 до 7. Восьмеричная система чаще всего использу- ется в областях, связанных с цифровыми устройствами. Перевод в восьмеричную систему из десятичной осуществляется последо- вательным делением на 8. Обратное преобразование осуществляется путем умножения остатка от деления на 10 на pow(8, i). При этом 8 - это основа системы счисления, a i - счетчик. Листинг 40. Конвертируем восьмеричные числа в десятичные и наоборот (40.с) #include <stdio.h> #include <math.h> int convertDecimalToOctal(int decimalNumber); long long convertOctalToDecimal(int octalNumber); int main() { int decimalNumber; int octalNumber; printf("Введите десятичное число: "); scanf("%d", &decimalNumber); printf("%d (десятичное) = %d (восьмеричное)\n", decimalNumber, convertDecimalToOctal(decimalNumber)); printf("Введите восьмеричное число: "); scanf("%d", SoctalNumber);
100 примеров на Си printf("%d (восьмеричное) = %lld (десятичное)\п", octalNumber, convertOctalToDecimal(octalNumber)); return 0; } int convertDecimalToOctal(int decimalNumber) { int octalNumber =0, i = 1; while (decimalNumber != 0) { octalNumber += (decimalNumber % 8) * i; decimalNumber /= 8; i *= 10; return octalNumber; } long long convertOctalToDecimal(int octalNumber) int decimalNumber = 0, i = 0; while(octalNumber != 0) decimalNumber += (octalNumber%10) * pow(8,i); octalNumber/=10; i = 1; return decimalNumber;
ЧАСТЬ 3. Функции % ./48 Введите десятичное число: 55 55 (десятичное) = 67 (восьмеричное) Введите восьмеричное число: 67 67 (восьмеричное) = 55 (десятичное) Рис. 40. Результат работы программы (40с) Пример 41. Конвертируем двоичные числа в восьмеричные и наоборот Этот пример содержит код программы, конвертирующий двоичные числа в восьмеричные и наоборот (лист. 41). Листинг 41. Конвертируем двоичные числа в восьмеричные и наоборот (41 .с) #include <stdio.h> #include <math.h> int convertBinarytoOctal(long long binaryNumber); long long convertOctalToBinary(int octalNumber); int main() { long long binaryNumber; int octalNumber; printf("Введите двоичное число: "); scanf("%lld", &binaryNumber); printf("%lld (двоичное) = %d (восьмеричное)\n", binaryNumber, convertBinarytoOctal(binaryNumber));
100 примеров на Си printf("Введите восьмеричное число: "); scanf("%d", &octalNumber); printf("%d (восьмеричное) = %lld (двоичное)\п", octalNumber, convertOctaLToBinary(octalNumber)); . return 0; long long convertOctalToBinary(int octalNumber) int decimalNumber =0, i = 0; long long binaryNumber = 0; while(octalNumber != 0) decimalNumber += (octalNumber%10) * pow(8,i); ++i; • octalNumber/=10; } i = 1; while (decimalNumber != 0) binaryNumber += (decimalNumber % 2) * i; decimalNumber /= 2; i *= 10; return binaryNumber; } int convertBinarytoOctal(long long binaryNumber) int octalNumber = 0, decimalNumber =0, i = 0; while(binaryNumber != 0) decimalNumber += (binaryNumber%10) * pow(2,i); binaryNumber/=10; i = 1;
ЧАСТЬ 3. Функции while (decimalNumber != 0) { octalNumber += (decimalNumber % 8) * i; decimalNumber /= 8; i *= 10; return octalNumber; Пример 42. Выводим предложение в обратном порядке В завершение этой части мы рассмотрим еще один пример с рекурсией. На этот раз мы напишем рекурсивную функцию reverseSentence(), которая бу- дет выводить введенное пользователем предложение в обратном порядке. Листинг 42. Выводим предложение в обратном порядке (42.с) #include <stdio.h> void reverseSentence(); int main() { printf("Введите предложение: "); reverseSentence(); printf ("\n"); return 0; void reverseSentence() { char c; scanf("%c", &c); if( с != Лп') { reverseSentence(); printf ("%c",c);
100 примеров на Си Сначала программа выводит приглашение. Затем вызывается наша рекур- сивная функция. Она сохраняет первый введенный пользователем символ в переменной с. Если это не \п, то функция вызывается снова. Когда функция вызывается во второй раз, второй символ сохраняется в с. Но эта переменная с - не та, которая была в первый раз, поскольку функция была вызвана снова. Обе эти переменные занимают разное место в памяти. Этот процесс продолжается, пока пользователь не нажмет Enter (символ '\п'). Когда пользователь нажал Enter, последняя функция reverseSentence() возвращает последний символ - print("%c", с) и возвращается ко второй с конца функции. Та функция выводит, соответственно, второй последний символ, и так продолжается до тех пор, пока не будет выведено все пред- ложение в обратном порядке. Данный пример демонстрирует всю силу и гибкость рекурсии.
ЧАСТЬ 4. Массивы и указатели
ЧАСТЬ 4. Массивы и указатели Пример 43. Вычисляем среднее с использованием массивов Пример 44. Вычисляем наибольший элемент массива Пример 45. Вычисляем среднеквадратичное отклонение Пример 46. Сложение двух матриц с использованием многомерных массивов Пример 47. Умножение на матрицу с использованием многомерных массивов Пример 48. Транспонированная матрица Пример 49. Умножение двух матриц с передачей матрицы в функции Пример 50. Доступ к элементам массива с использованием указателей Пример 51. Своп числа в циклическом порядке с помощью вызова по ссылке Пример 52. Поиск максимума с использованием динамического выделения па- мяти
100 примеров на Си Пример 43. Вычисляем среднее с использованием массивов Данная часть книги посвящена массивам и указателям. Начнем с массива. Представим, что есть некий массив типа float, введенный пользователем, и нам нужно вычислить среднее арифметическое его элементов. Наша программа будет демонстрировать: 1. Заполнение массива. В цикле for мы читаем каждый элемент массива с помощью функции scanf() 2. Параллельный подсчет среднего арифметического. Обратите внимание, мы вычисляем среднее в том же цикле, что и ввод данных, - это пример оптимизации. Мы отказались от лишнего прохода по элементам массива. Листинг 43. Вычисление среднего арифметического (43.с) ♦include <stdio.h> int main() { int n, i; float num[100], sum = 0.0, average; printf("Введите количество элементов: "); scanf("%d", &n); while (n > 100 || n <= 0) { printf("Количество должно быть в пределах от 1 до 100.\п"); printf("Введите количество еще раз: "); scanf("%d", &n); // Заполняем массив и параллельно подсчитываем среднее арифметическое for(i =0; i < n; printf("%d. Введите число: ", i+1) ; scanf("%f", &num[i]); sum += num[i]; average = sum / n; printf("Среднее = %.2f\n", average);
ЧАСТЬ 4. Массивы и указатели Рис. 43. Результат вычисления среднего арифметического return 0; Пример 44. Вычисляем наибольший элемент массива В предыдущем примере мы вычисляли среднее прямо в том же массиве, в котором организовали ввод пользователя. Аналогично, можно было бы вы- числить и максимум массива. Но в этом примере мы покажем другой трюк. Пусть у нас есть массив элементов агг[100]. Мы пройдемся по элементам массива от 1 до 100, точнее, до п (п вводит пользователь, а максимум п = 100), и попробуем найти максимум в массиве. При этом максимум мы будем хранить не в отдельной переменной, а в самом массиве - в элементе агг[0]. Листинг 44. Вычисляем максимум массива (44.с) #include <stdio.h> int main() { int i, n; float arr [100];
100 примеров на Си printf("Введите количество элементов массива (1-100): "); scanf("%d", &n); printf("\n"); // Читаем ввод пользователя for(i = 0; i < n; printf("Введите элемент %d: ", i+1) ; scanf("%f", &arr[i]); // Проходимся по массиву, сохраняя наибольший элемент в arr[0] for(i = 1; i < n; // Если arr[0] < текущего элемента, значит, у нас есть новый максимум if(arr[0] < arr[i]) arr[0] = arr[i]; } printf("Максимум = %.2f\n", arr[0]); return 0; Рис. 44. Вычисляем максимум массива
ЧАСТЬ 4. Массивы и указатели Пример 45. Вычисляем среднеквадратичное отклонение Данный пример вычисляет среднеквадратическое отклонение. Для его вы- числения мы создали функцию calculateSD(), поэтому данный пример бу- дет демонстрировать передачу массива данных функции и работу с ним в функции (лист. 45). Результат работы программы, а также команда ее ком- пиляции изображены на рис. 45. Листинг 45. Передача массива функции (45.с) #include <stdio.h> #include <math.h> float calculateSD(float data[]); int main() { int i; float data [10]; printf("Введите 10 элементов: "); for(i=0; i < 10; ++i) scanf("%f", &data[i]); printf("ХпСреднеквадратическое отклонение * %.6f\n", calculateSD(data)); return 0; } float calculateSD (float data[]) { float sum = 0.0, mean, standardDeviation = 0.0; int i; for(i=0; i<10; ++i) { sum +« data[i]; mean - sum/10; for(i=0; standardDeviation pow(data[i] - mean, 2);
100 примеров на Си Рис. 45. Вычисляем среднеквадратическое отклонение return sqrt(standardDeviation/10); Пример 46. Сложение двух матриц с использованием многомерных массивов Довольно сложный пример - сложение двух матриц с использованием многомерных массивов. У нас будет три матрицы: а[ 100][100], Ь[ 100][100] и sum[100][100]. Последняя, как вы уже догадались, будет содержать сумму матриц а и Ь. Пользователь вводит количество строк г и количество колонок с. Значения г и с в этой программе должны быть меньше 100. После ввода г и с, пользователь должен будет ввести элементы обеих ма- триц. Далее программа выполнит сложение матриц и отобразит результат. Листинг 46. Сложение двух матриц с использованием многомерных массивов #include <stdio.h> int main(){ int г, с, а[100][100], b[100][100], sum[100][100], i, j; printf("Введите количество строк от 1 до 100: ");
ЧАСТЬ 4. Массивы и указатели scanf("%d", &r); printf("Введите количество колонок от 1 до 100: ") ; scanf("%d", &c); printf("ХпВведите элементы первой матрицы:\п"); for(i=0; i<r; for(j=0; j<c; printf("Введите элемент a%d%d: ",i+l,j+l); - scanf("%d",&a[i][j]); printf("Введите элементы второй матрицы:\п"); for(i=0; i<r; ++i) for(j=0; j<c; printf("Введите элемент b%d%d: ",i+l, j+1); scanf("%d", &b[i][j]); // Сложение 2 матриц for(i=0;i<r;++i) for(j=0;j<c;++j) sum[i] [j]=a[i] [j]+b[i] [j]; // Отображаем результат printf("ХпСумма 2 матриц: \n\n"); for(i=0;i<r;++i) for(j=0;j<c;++j) printf("%d ",sum[i][j]); if(j==c-l) printf("\n\n"); return 0;
100 примеров на Си Рис. 46. Сложение двух матриц Пример 47. Умножение на матрицу с использованием многомерных массивов Рассмотрим еще один пример работы с многомерными массивами - умно- жение двух матриц. Чтобы умножить две матрицы, число колонок первой матрицы должно быть равно количеству строк второй матрицы. Программа отобразит ошибку, если это не так. Чтобы умножить две матрицы, нужно каждую строку первой матрицы ум- ножить на каждый столбец второй матрицы. Умножая первую строку пер- вой матрицы на каждый столбец второй матрицы, мы получим все элементы первой строки матрицы произведения, затем делаем то же самое для второй строки первой матрицы и т.д. Листинг 47. Умножение матриц tinclude <stdio.h> int main() { int a[10][10], b[10][10], result[10][10], rl, cl, r2, c2, printf("Введите количество строк и колонок первой матрицы:
ЧАСТЬ 4. Массивы и указатели scanf("%d %d", &rl, &cl); printf("Введите количество строк и колонок второй матрицы: "); scanf("%d %d",&r2, &c2); // Проверяем, можем ли мы умножить две матрицы while (cl != г2) { printf("Ошибка! К-во колонок первой матрицы не равно количеству строк второй\п\п"); printf("Введите количество строк и колонок первой матрицы: "); scanf("%d %d", &rl, &cl) ; printf("Введите количество строк и колонок второй матрицы: "); scanf("%d %d",&r2, &c2); // Вводим элементы 1 матрицы printf("ХпВведите элементы 1 матрицы:\п"); for(i=0; i< for(j=0; j printf ("Введите элемент a%d%d: ",i+l, j+1); scanf("%d", &a[i][j]); // Вводим элементы 2 матрицы printf("ХпВведите элементы 2 матрицы:\п"); for(i=0; i<r2; ++i) for(j=0; j<c2; printf("Введите элемент b%d%d: "fi+l, j+1); scanf("%d",&b[i][j]); // Заполняем все элементы результирующей матрицы нулями for(i=0; i<rl; ++i) for(j=0; j<c2; ++j) { result[i][j] » 0; // Умножаем матрицы а и b // Результат сохраняем в матрице result
100 примеров на Си for(i=0; i for(j=0; j<c2; for(k=0; k<cl; result [i] [j]+=a[i] [k]*b[k] [j]; // Отображаем результат printf("ХпРезультат умножения матриц:\п") for(i=0; i<rl; ++i) for(j=0; j<c2; printf("%d ", result [i] [j]); if (j == c2-l) printf("\n\n"); return 0; $ gcc 47.с -о 47 $ ./47 Введите количество строк и колонок первой матрицы: 2 2 Введите количество строк и колонок второй матрицы: 2 2 Введите элементы 1 матрицы: Введите элемент all: 2 Введите элемент а 12: 2 Введите элемент а21: 2 Введите элемент а22: 2 Введите элементы 2 матрицы: Введите элемент Ы1: 5 Введите элемент Ы2: S Введите элемент Ь21: 5 Введите элемент Ь22: 5 Результат умножения матриц: 26 20 20 20 $1 Рис. 47. Результат умножения двух матриц
ЧАСТЬ 4. Массивы и указатели Пример 48. Транспонированная матрица В этом примере пользователь вводит количество строк и колонок (г и с со- ответственно). Значения обоих переменных в этой программе должны быть меньше 10. Затем программа вычисляет транспонированную матрицу и вы- водит результат на экран (лист. 48). Листинг 48. Транспонированная матрица #include <stdio.h> int main() int a[10][10], transpose[10][10], r, c, i, j; printf("Введите количество строк и колонок: "); scanf("%d %d", &r, &c); // Сохраняем элементы printf("ХпВведите элементы матрицы:\n"); for(i=0; i<r; ++i) for(j=0; j<c; printf("Введите элемент a%d%d: ",i+l, j+1) scanf("%d"r &a[i][j]); // Показываем а[][] */ printf("ХпИсходная матрица: \п"); for(i=0; i<r; for(j=0; printf("%d ", a[i][j]); if (j == c-1) printf("\n\n"); // Вычисляем транспонированную матрицу for(i=0; i<r; ++i) for(j=0; j<c; transpose!j][i] = a[i][j]; // Результат printf("ХпТранспонированная матрица:\n");
100 примеров на Си for(i=0; for(j=0; printf("%d ",transpose[i][j]) ; if (j==r-l) printf("\n\n"); return 0; Рис. 48. Вычисление транспонированной матрицы Пример 49. Умножение двух матриц с передачей матрицы в функции Ранее (пример 47) мы рассматривали умножение матриц. Перепишем этот пример с использованием функций. Как обычно, программа просит пользователя ввести размер матрицы (коли- чество строк и колонок). Затем она просит пользователя ввести элементы двух матрицы, выполняет умножение и отображает результат. Для осуществления всего вышеизложенного программа использует следу- ющие функции: • enterData() - ввод данных от пользователя. • multiplyMatricesQ - умножение двух матриц.
ЧАСТЬ 4. Массивы и указатели display() - отображение результата матрицы после умножения. Листинг 49. Умножение двух матриц с использованием функций (49.с] #include <stdio.h> void enterData (int firstMatrix [] [10], int secondMatrix [ ] [10], int rowFirst, int columnFirst, int rowSecond, int columnSecond); void multiplyMatrices(int firstMatrix[] [10], int secondMatrix[] [10], int multResult[][10], int rowFirst, int columnFirst, int rowSecond, int columnSecond); void display(int mult[][10], int rowFirst, int columnSecond); int main() { int firstMatrix[10][10], secondMatrix[10][10], mult[10][10], rowFirst, columnFirst, rowSecond, columnSecond, i, j, k; printf("Введите количество строк и колонок матрицы 1: "); scanf("%d %d", &rowFirst, ScolumnFirst); printf("Введите количество строк и колонок матрицы 2: "); scanf("%d %d", &rowSecond, &columnSecond); // Проверяем, можем ли мы умножить 2 матрицы while (columnFirst != rowSecond) { printf("Ошибка! К-во колонок первой матрицы не равно количеству строк второй\п"); printf("Введите количество строк и колонок матрицы 1: "); scanf("%d%d", &rowFirst, &columnFirst); printf("Введите количество строк и колонок матрицы 2:"); scanf("%d%d", SrowSecond, &columnSecond); // Вводим данные enterData (firstMatrix, secondMatrix, rowFirst, columnFirst, rowSecond, columnSecond); // Умножаем 2 матрицы multiplyMatrices (firstMatrix, secondMatrix, mult, rowFirst, columnFirst, rowSecond, columnSecond); // Выводим результат display(mult, rowFirst, columnSecond);
100 примеров на Си return 0; void enterData(int firstMatrix[] [10], int secondMatrix[] [10], int rowFirst, int columnFirst, int rowSecond, int columnSecond) { int i, j; printf("ХпВведите элементы матрицы 1:\п"); for(i =0; i < rowFirst; for(j = 0; j < columnFirst; ++j) { printf("Введите элемент a%d%d: ", i + 1, j + 1) ; scanf ("%d", &firstMatrix[i] [j]); printf("ХпВведите элементы матрицы 2:\п"); for(i =0; i < rowSecond; for(j = 0; j < columnSecond; printf("Введите элемент b%d%d: ", i + 1, j + 1); scanf("%d", &secondMatrix[i][j]); void multiplyMatrices(int firstMatrix[][10], int secondMatrix[] [10], int mult[][10], int rowFirst, int columnFirst, int rowSecond, int columnSecond) { int i, j, k; // Заполняем результирующую матрицу нулями for(i = 0; i < rowFirst; for(j =0; j < columnSecond; ++j) { mult[i][j] = 0; // Умножаем 2 матрицы и сохраняем результат в mult
ЧАСТЬ 4. Массивы и указатели for(i = 0; i < rowFirst; { for(j = 0; j < columnSecond; ++j) { for(k=0; k<columnFirst; ++k) { mult[i][j] += firstMatrixfi] [k] * secondMatrix[k] [ j ] ; void display(int mult[][10], int rowFirst, int columnSecond) int i, j; printf("ХпРезультат:\n"); for(i = 0; i < rowFirst; for(j =0; j < columnSecond; ++j) printf("%d ", mult[i][j]); if(j == columnSecond - 1) printf ("\n\n"); m Рис. 49. Умножение 2 матриц с использованием функций
100 примеров на Си Пример 50. Доступ к элементам массива с использованием указателей В данном примере мы сохраняем элементы целочисленного массива в пере- менную data. Далее мы просто выводим элементы массива с использовани- ем метода указателей, без использования квадратных скобок. Листинг 50. Доступ к элементам массива с использованием указателей (50.с) #include <stdio.h> int main() { int data[5], i; printf("Введите 5 элементов: "); for(i = 0; i < 5; scanf("%d", data + i); printf("Содержимое массива: \п"); ford = 0; i < 5; ++i) printf ("%d\n", Mdata + i)) ; $ gcc 50.с -о S6 $ ./5в Введите 5 элементов: 5 4 3 2 1 Содержимое массива: 5 4 3 2 $1 Рис. 50. Вывод элементов массива с использованием указателей *
ЧАСТЬ 4. Массивы и указатели return 0; Пример 51. Своп чисел в циклическом порядке с помощью вызова по ссылке Рассмотрим еще один пример. Пользователь вводит три числа, которые мы сохраняем в переменные а, Ь, с соответственно. Затем эти переменные передаются функции cyclicSwap(). Вместо передачи значений переменных мы передаем в функцию адреса переменных - посмотрите, что возле имени переменной есть *, а в функцию мы передаем переменные с помощью &. После того как cyclicSwap() закончит работу, переменные будут поменяны местами и в основной программе. Листинг 51. Своп чисел с помощью вызова по ссылке #include<stdio.h> void cyclicSwap(int *a,int *b,int *c) ; int main() { int a, b, c; printf("Введите a b и с: "); scanf("%d %d %d",&a,&b,&c); printf("flo замены:\п"); printfC'a = %d \nb = %d \nc - %d\n", a,b, c) ; cyclicSwap(&a, &b, &c); printf("После:\n"); printf("a = %d \nb = %d \nc = %d\n",a, b, c) return 0; } void cyclicSwap(int *a,int *b,int *c) int temp; // меняем местами переменные temp = *b; *b = *a;
100 примеров на Си Введите а Ь и с; До замены: а ■ 1 b « 2 с ~ 3 После: а в 3 b « 1 с в 2 5 дсс 51.с -о S1 $ ./51 12 3 Рис. 51. Результат замены местами чисел с помощью указателей *а = *с; *с = temp; Пример 52. Поиск максимума с использованием динамического выделения памяти В примере 44 было показано, как вычислить максимум в массиве. Тогда раз- мер массива у нас был ограничен 100 элементами. Но это не совсем хорошо. А что если нужно будет больше элементов? Тогда придется перекомпили- ровать программу. А если элементов меньше, то наша программа попусту занимает больше оперативной памяти, чем можно было. Именно поэтому сейчас мы рассмотрим динамическое выделение памяти под массив и поиск максимума в таком массиве (лист. 52). Листинг 52. Поиск максимума с использованием динамического выделения #include <stdio.h> #include <stdlib.h>
ЧАСТЬ 4. Массивы и указатели int main() int i, num; float *data; printf("Введите количество элементов: "); scanf("%d", &num); // Выделяем память под 'num1 элементов data = (float*) calloc(num, sizeof (float)); if(data == NULL) { printf("Ошибка выделения памяти\п."); exit(l); printf("\n"); // Вводим элементы for(i =0; i < num; printf("Введите элемент %d: ", i + 1) ; scanf("%f", data + i); // Ищем максимальный элемент for(i = 1; i < num; // Сохраняем максимальный элемент if(*data < *(data + i) ) *data = *(data + i) ; printf("Максимум = %.2f\n", *data); return 0;
100 примеров на Си $ ./52 Введите количество элементов: 5 Введите элемент 1: 9 Введите элемент 2: S Введите элемент 3: 8 Введите элемент 4: 16 Введите элемент 5: 1 Максимум » 18.60 Рис. 52. Результат работы программы из лист. 52
ЧАСТЬ 5. Строки
ЧАСТЬ 5. Строки Пример 53. Поиск частоты знаков в строке Пример 54. Программа для подсчета количества гласных, согласных и т.д. Пример 55. Удаляем все символы в строке, кроме алфавитных Пример 56. Определение длины строки Пример 57. Конкатенация двух строк Пример 58. Копирование строки без функции strcpyQ Пример 59. Сортировка элементов в лексикографическом порядке
100 примеров на Си Пример 53. Поиск частоты знаков в строке Напишем программу, подсчитывающую, сколько раз искомый символ встречается в строке, которую ввел пользователь. Программа просит поль- зователя ввести сначала строку, а затем символ, частоту которого нужно вы- числить. Далее программа в цикле "проходится" по строке и подчитывает частоту символа. Код программы приведен в листинге 53. Листинг 53. Поиск частоты знаков в строке (53.с) #include <stdio.h> int main() char str[1000], ch; int i, frequency = 0; printf("Введите строку: "); gets (str); printf("Введите символ: "); scanf("%c"/&ch); for(i = 0; str[i] != f\0»; { if (ch == str[i]) ++frequency; printf("Частота = %d\n", frequency); return 0; Результат работы программы приведен на рис. 53. В этой программе мы по- лучаем строку, введенную пользователем, в переменную str. Далее введен- ный пользователем символ мы сохраняем в переменной ch. После этого в цикле мы проходимся по всем символам строки str. Если текущий символ равен ch, мы увеличиваем переменную frequency, в которой мы и храним частоту знака. В конце программы выводится полученный результат.
ЧАСТЬ 5. Строки $ ./a.out Введите строку: Строке для примера Введите символ: а Частота символа » z $ Рис. 53. Подсчет частоты символов Пример 54. Программа для подсчета количества цифр и пробелов Задача проста: дан текст и нужно вычислить: • Общее количество символов • Количество пробелов в тексте • Количество цифр в тексте То есть нужно написать довольно простую статистическую программу. В последней части книги мы напишем аналог приложения we в Linux - пол- ноценную программу для подсчета слов. А пока небольшая разминка. Исходный код нашей программы приведен в листинге 54. Листинг 54. Статистика о строке ♦include <stdio.h> int main() t
100 примеров на Си char line[150]; int i, total, digits, spaces; total = digits = spaces = 0; printf("Введите строку "); scanf ("%[л\п]", line); for(i=0; line[i]!='\0'; { if(line[i]>='0' && line[i]<='9') { ++digits; } else if (line[i]==' л) { ++spaces; } +total; printf("Всего символов: %d",total); printf("\пЦифр: %d",digits); $ ./a.out Введите строку (498) 456-15-79 Всего символов: 15 Цифр; 1Э Пробелов: 1 Рис. 54. Статистика о строке
ЧАСТЬ 5. Строки printf("\пПробелов: %d", spaces); return 0; Программа работает так: в цикле она перебирает все символы строки. Если будет найдена цифра, то увеличивается значение переменной digits, если найден пробел, то увеличивается переменная spaces, переменная total уве- личивается при каждой итерации цикла. В принципе, количество символов можно было бы узнать с помощью функции strlen(), но раз у нас все равно есть цикл, то мы подсчитывали количество символов в нем. Пример 55. Удаляем все символы в строке, кроме цифровых В данном примере мы напишем программу, удаляющую из строки все сим- волы, кроме цифровых. Такая задача часто встречается при очистке строк, содержащих номера телефонов, номера банковских карт и др. Работает программа так: • Пользователь вводит строку, которую мы записываем в переменную line. • В цикле мы проверяем, является ли символ цифровым. • Если нет, то все символы после него, включая нулевой символ, смещают- ся на 1 позицию влево. • С помощью функции puts() выводим на экран результат. Листинг 55. Удаляем все символы в строке, кроме цифровых (55.с) #include<stdio.h> int main() { char line[150]; int i, j; printf("Введите строку: "); gets(line); for(i = 0; line[i] != Л0' ; { while (!( (line[i]>='0' && line[i]<='9') || line[i] Л0') )
100 примеров на Си for(j = i; != Л0' line[j] = Л0'; printf("Результат: "); puts(line); return 0; $ ./a.out Введите строку: 5167 7423 1212 1111 Результат: 5167742312121111 $ Рис. 55. Результат работы программы Пример 56. Определение длины строки Для определения длины строки используется функция strlen(). Давайте на- пишем программу, определяющую длину строки, без использования этой функции (лист. 56).
ЧАСТЬ 5. Строки Листинг 56. Определение длины строки без функции strlen() (56.с) #include <stdio.h> int main() { char s[1000], i; printf("Введите строку: "); scanf("%s", s); ford = 0; s[i] != Л0'; printf("Длина: %d\n", i) ; return 0; Ранее мы уже разрабатывали подобную программу, но теперь мы удалили из нее все лишнее, и она вычисляет только длину строки. $ ./a.out введите строку: fsghfejghfjghjfhgjhrhjvhjh jfhgjfhgjhfg Длина: 26 $ Рис. 56. Программа в действии
100 примеров на Си Пример 57. Конкатенация двух строк без функции strcat() Для конкатенации (объединения) двух строк обычно используется функ- ция strcat(). Но мы напишем программу, показывающую, как устроена эта функция, - чтобы вы знали, как можно объединить две строки без ее ис- пользования. Объединенная строка записывается в переменную si. Листинг 57. Конкатенация двух строк без функции strcat() (57.с) #include <stdio.h> int main() char sl[100], s2[100], i, j; printf("Первая строка: "); scanf("%s", si); printf("Вторая строка: "); scanf("%s", s2); // вычисляем длину si и помещаем ее в i for(i = 0; sl[i] != f\0f; $ ,/a.out Первая строка: Первая вторая строка: Вторая Результат: ПерваяВторая $ Рис. 57. Результат работы программы
ЧАСТЬ 5. Строки for(j = 0; s2[j] != Л0'; sl[i] = Л0'; printf("Результат: %s\n"/ si); return 0; Пример 58. Копирование строки без функции strcpy() Для копирования строки можно использовать функцию strcpy(). В этом примере будет приведена программа, выполняющая таковое копирование без этой функции. Программа принимает ввод пользователя и посимвольно копирует симво- лы из строки si в строку s2 - пока не будет встречен символ конца строки \0. Обратите внимание, что мы читаем строку с помощью функции gets(), a не scanf(), поскольку последняя не позволит прочитать всю строку полно- стью, если она содержит пробелы. Листинг 58. Копирование строки без strcpy() #include <stdio.h> int main() char si [100], s2[100], im- print f ("Введите строку si: gets(si); for(i = 0; sl[i] != Л0'; { s2[i] = sl[i]; "); s2[i] = Л0'; printf("Строка s2: %s\n", s2) ; return 0;
100 примеров на Си $ ./a.out Введите строку si: Привет, мир! Строка s2: Привет, мир! $1 Рис. 58. Результат работы программы из листинга 58 Пример 59. Сортировка элементов в лексикографическом порядке Напишем программу, которая отсортирует в лексикографическом поряд- ке массив строк. Для сравнения строк мы используем функцию strcmp(), а функция strcpy() используется для копирования строки в переменную temp в процессе сортировки. Алгоритм прост: во время перебора двухмер- ного массива строк str мы сравниваем две строки. Если первая строка боль- ше (лексикографически), чем вторая, то функция strcmp() возвращает 0. При этом мы меняем местами эти две строки и так до тех пор, пока строки не будут расположены в лексикографическом порядке. Листинг 59. Сортировка элементов (59.с) #include<stdio.h> #include <string.h>
ЧАСТЬ 5. Строки char str[10][50], temp[50]; printf("Введите 10 слов:\п"); for(i=0; i scanf ("%s[A\n]",str[i]); for(i=0; for(j=i+l; j if(strcmp(str[i], str[j])>0) { strcpy(temp, str[i]); strcpy(str[i]f str[j]); strcpy(str[j], temp); printf("AnB лексикографическом порядке: \п"); for(i=0; i puts(str[i]); Введите id слое: Яблоко Арбуз Дыня Апельсин Банан Мандарин Груыа Клубника Тыква Ананас В лексикографическом порядке: Ананас Апельсин Арбуз Банан Груаа Дыня Клубника Мандарин Тыква Яблоко я Рис. 59. Сортировка строк
100 примеров на Си return 0;
ЧАСТЬ 6. Структуры и объединения
100 примеров на Си Пример 60. Храним информацию о студенте в структуре Пример 61. Сложение двух структур Пример 62. Сложение двух комплексных чисел с использованием структуры Пример 63. Вычисление разницы между двумя периодами времени Пример 64. Структуры и динамическое выделение памяти Пример 60. Храним информацию о студенте в структуре Цель данного примера - продемонстрировать, как можно хранить ин- формацию о студенте (имя, номер студенческого билета, номер группы) в структуре. В этой программе мы создадим структуру student, у нее будет три члена - name (string), roll (integer), group (integer). Для хранения ин- формации мы будем использовать переменную s. Также будет показано, как вывести информацию из структуры на экран. Код программы находится в листинге 60. Листинг 60. Храним информацию о студенте в структуре tinclude <stdio.h> struct student char int int } s; int main name[50]; roll; group; 0
ЧАСТЬ 6. Структуры и объединения printf("Введите информацию:\п"); printf("Имя: "); scanf ("%s", s.name); printf("Номер билета: "); scanf("%d", &s.roll); printf("Номер группы: "); scanf("%f", &s.group); printf("Выводим информацию:\n"); printf("Имя: "); puts(s.name); printf("Номер билета: %d\n",s.roll); printf("Номер группы: %.lf\n", s.group); return 0; $ ./a.out Введите информацию: Имя: Денис Номер билета; 121111 Номер группы: 45 Отображение информации: Имя: Денис Номер билета: 121111 Номер грулпы: 4$ $ Рис. 60. Результат работы программы
100 примеров на Си Обратите внимание на использование символа & при формировании (за- полнении) структуры и при ее отображении. Пример 61. Сложение двух структур Представим, что у нас есть две структуры, содержащие информацию о рас- стоянии - в шагах и в метрах. Программа из этого примера выполнит сло- жение двух структур и отобразит результат на экране. Листинг 61. Сложение двух структур #include <stdio.h> struct Distance int feet; float m; } dl, d2, sumOfDistances; int main() printf("Заполняем первую структуру\п"); printf("Количество шагов: "); scanf("%d", &dl.feet); printf("Количество метров: "); scanf("%f", &dl.m); printf("ХпЗаполняем вторую структуру\п"); printf("Количество шагов: "); scanf("%d", &d2.feet); printf("Количество метров: "); scanf("%f", &d2.m); sumOfDistances.feet = dl.feet+d2.feet; sumOfDistances.m = dl.m+d2.m; printf("ХпРезультат = %d - %.If",sumOfDistances.feet, sumOfDistances.m) ; return 0;
ЧАСТЬ 6. Структуры и объединения $ ./a.out Заполняем первую структуру Количество шагов: 16 Количество метров: 7 Заполняем вторую структуру Количество шагов: 23 Количество метров: 14 Результат = 38 -21.0 $ Рис. 61. Результат работы программы В этой программе мы определили структуру Distance, состоящую из двух членов - feet (int) и m (float). Первый член - это количество шагов, второй - количество метров. Далее мы создаем три переменных типа Distance, вы- полняем сложение структур поэлементно и выводим результат. Пример 62. Сложение двух комплексных чисел с использованием структуры и передачей структуры функции Данная программа похожа на предыдущую, но в ней сложение будет вы- полнять функция add(). В нее мы будем передавать две структуры, и она же будет вычислять результат. Код программы приведен в листинге 62. Листинг 62. Сложение двух структур с использованием функции (62.с) #include <stdio.h> typedef struct complex { float real; float imag; } complex;
100 примеров на Си complex add(complex nl,complex n2); int main() { complex nl, n2, temp; printf("Первое комплексное число \п"); printf("Введите действительную и мнимую часть соответственно:\п"); scanf("%f %f", &nl.real, &nl.imag); printf("\пВторое комплексное число \п"); printf("Введите действительную и мнимую часть соответственно:\п"); scanf("%f %f", &n2.real, &n2.imag); temp = add(nl, n2); printf ("Сумма = %.lf + %.lfi", temp.real, temp.imag); return 0; // Сложение двух структур complex add(complex nl, complex n2) { complex temp; temp.real = nl.real + n2.real; temp.imag = nl.imag + n2.imag; return(temp); Функция add() вычисляет сумму и возвращает переменную temp функции main(). Результат работы программы показан на рис. 62.
ЧАСТЬ 6. Структуры и объединения Первое Введите 1 2 • Второе Введите 3 5 Сумма « $ ./a.out комплексное число действительную и комплексное число действительную и 4.0 + 7.8г Ъ 1 мнимую части соответственно: мнимую части соответственно: Рис. 62. Результат работы программы: сложение комплексных чисел Пример 63. Вычисление разницы между двумя периодами времени Усложним нашу задачу. Напишем программу, вычисляющую разницу меж- ду двумя периодами времени. Для этого мы определим структуру TIME (содержащую информацию о часах, минутах, секундах) и пользователь- скую функцию differenceBetweenTimePeriodO, которой мы передадим три структуры. Первые две содержат начальное и конечное время соответствен- но, третья - результат, то есть разницу между этими двумя структурами. В этой программе мы просим пользователя вести два периода времени, мы сохраняем их в переменных типа TIME - это наша структура, содержащая информацию о времени. Далее функция differenceBetweenTimePeriodO вычисляет разницу. Обратите внимание, поскольку мы используем техни- ку вызова по ссылке, то данная функция ничего не возвращает в функцию main() - результат сразу записывается в переменную diff. Листинг 63. Вычисление разницы между двумя периодами времени (63.с) #include <stdio.h> struct TIME
100 примеров на Си int seconds; int minutes; int hours; }; void differenceBetweenTimePeriod(struct TIME tl, struct TIME t2, struct TIME Miff); int main() { struct TIME startTime, stopTime, diff; printf ("Начальное время: \п"); printf("Введите часы, минуты, секунды: "); scanf("%d %d %d", &startTime.hours, SstartTime.minutes, SstartTime.seconds); printf("Конечное время: \п"); printf("Введите часы, минуты, секунды: "); scanf("%d %d %d", &stopTime.hours, &stopTime.minutes, &stopTime.seconds); // Вычисляем разницу. differenceBetweenTimePeriod(startTime, stopTime, &diff); printf("ХпРазница: %d:%d:%d - ", startTime.hours, startTime.minutes, startTime.seconds); printf("%d:%d:%d ", stopTime.hours, stopTime.minutes, stopTime.seconds); printf("= %d:%d:%d\n", diff.hours, diff.minutes, diff. seconds); return 0; void differenceBetweenTimePeriod(struct TIME start, struct TIME stop, struct TIME Miff) { if(stop.seconds > start.seconds){ --start.minutes; start.seconds += 60; diff->seconds = start.seconds - stop.seconds; if(stop.minutes > start.minutes){ —start.hours;
ЧАСТЬ 6. Структуры и объединения start.minutes += 60; diff->minutes = start.minutes - stop.minutes; diff->hours = start.hours - stop.hours; $ gcc 63.с $ ./a.out Начальное время: Введите часы, минуты, секунды; 18 11 15 Конечное время: введите часы, минуты, секунды: 12 12 45 Разнима: 10:11:15 - 12:12:45 * «3:58:30 Рис. 63. Разница между двумя периодами времени Пример 64. Структуры и динамическое выделение памяти В примере 60 было показано, как создать одну структуру, хранящую инфор- мацию о студенте. Представим, что нам нужно хранить несколько структур в памяти - для нескольких объектов. Когда мы знаем количество объектов, мы можем объявить массив структур: struct student char name[50]; int roll; int group;
100 примеров на Си Далее можно заполнить массив структур в цикле так: for(i=0; i { s[i].roll = i+1; printf ("\пДля номера билета%с!/ \n", s [i] . roll) ; printf("Введите имя: "); scanf("%s",s[i].name); printf("Введите группу: "); scanf("%d",&s[i].group); printf("\n"); Все хорошо, но, что делать, если мы не знаем точного количества структур? Выделить память под очень большое количество структур - неправильно. Можно, например, объявить массив структур так: struct student { char name[50]; int roll; int фгоир; } s[1024]; Но что делать, если придется заполнить информацию о 1025-м студенте? Мы получим ошибку. А до этого, когда количество студентов будет меньше, мы будем просто нерационально использовать память компьютера. Выход есть, и он заключается в динамическом выделении памяти. Мы мо- жем выделить ровно столько памяти, столько нам будет нужно, с помощью функции malloc(): ptr = (struct course*) malloc (noOfRecords * sizeof (struct course)); Здесь course - это наша структура (мы должны знать ее тип), noOfRecords - количество записей, которое введет пользователь (мы его не знаем), а функ- ция sizeof() возвращает объем памяти, необходимый для хранения одной структуры. Готовый пример программы приведен в листинге 64. ^
ЧАСТЬ 6. Структуры и объединения Листинг 64. Динамическое выделение памяти (64.с) #include <stdio.h> #include <stdlib.h> struct course int marks; char subject[30]; int main() { struct course *ptr; int i, noOfRecords; printf("Количество записей: ") ; scanf("%d", &noOfRecords); // Выделяем память, ptr будет указывать на базовый адрес ptr = (struct course*) malloc (noOfRecords * sizeof (struct course)); for(i =0; i < noOfRecords; printf("Введите название предмета и оценку:\п"); scanf("%s %d", &(ptr+i)->subject, &(ptr+i)->marks); printf("Результат:\n"); for(i =0; i < noOfRecords ; printf("%s\t%d\n", (ptr+i)->subject, (ptr+i)->marks); return 0; $ ./a.out Количество записей; 2 Введите название предмета и оценку: Математика 5 Введите название предмета и оценку: Физика 3 Результат: Математика 5 Физика 3 $ I Рис. 64. Динамическое выделение памяти
ЧАСТЬ 7. Файлы
ЧАСТЬ 7. Файлы Пример 65. Запись предложения в файл Пример 66. Чтение строки из файла и ее отображение Пример 67. Отображаем исходный код программы Пример 65. Запись предложения в файл В этом примере мы рассмотрим, как записать введенное пользователем предложение в файл с использованием функции fprintf(). Код программы приведен в листинге 65. Листинг 65. Запись предложения в файл (65.с) ♦include <stdio.h> ♦include <stdlib.h> /* Для функции exit() */ int main () { char sentence[1000]; FILE *fptr; fptr = fopen("text.txt", "w"); if(fptr == NULL) { printf("Ошибка!"); exit(l); printf("Введите предложение:\n"); gets(sentence); fprintf(fptr,"%s", sentence);
100 примеров на Си fclose (fptr); return 0; Результат работы программы показан на рис. 65. Программа запускается, запрашивает ввод пользователя, читает его с помощью функции gets() в пе- ременную sentence. Далее программа записывает введенное пользователем предложение в файл text.txt функцией fprintf(). Затем мы командой cat вы- водим содержимое файла text.txt на экран и видим введенное предложение. den$den-pc:~/c~ex$ ./a.out Введите предложение: Просто предложение den0den-pc:^/c-ex$ cat text.txt Просто предложение $i Рис. 65. Программа в действии Обратите внимание, что мы открываем файл в режиме записи - "w". Имен- но этот режим мы выбираем в функции fopen(). В этом режиме, если файл существует, он будет перезаписан, если нет - он будет создан. Пример 66. Чтение строки из файла и ее отображение Теперь решим обратную задачу. Попробуем прочитать строку из файла и вывести ее на экран. Мы будем читать не весь файл, а только одну строку - до символа \п (новая строка). Код программы приведен в листинге 66.
ЧАСТЬ 7. Файлы Листинг 66. Чтение строки из файла (66.с) #include <stdio.h> ♦include <stdlib.h> int main() { char c[1000]; FILE *fptr; if ((fptr = fopen("text.txt", "r")) == NULL) { printf("Ошибка при открытии файла"); // Код возврата 1 - произошла ошибка. exit(l); // Читаем до символа \п fscanf(fptr,"%[A\n]", с); printf("Прочитано из файла:\п%8", с); fclose(fptr); return 0; Результат работы приведен на рис. 66. Программа отобразила записанное ранее в файл предложение. Сначала мы открываем файл функцией fopen() в режиме чтения, затем функцией fscanf() читаем из файла данные до сим- вола новой строки и записываем в переменную с (это массив из 1000 сим- волов - такая максимальная длина строки в нашем случае). Далее мы выво- дим прочитанные данные на экран функцией printf().
100 примеров на Си $ ./a»out Прочитано из файла: Просто предложение $ Рис. 66. Результат работы Пример 67. Отображаем исходный код программы В этом примере мы напишем программу, выводящую собственный исход- ный код. Макрос FILE содержит имя С-файла с исходным кодом про- граммы, из которого она была откомпилирована. При этом исполнимый файл может называться как угодно, данный макрос будет содержать имя файла исходного кода. Просто вывести имя файла с исходным кодом можно так: #include <stdio.h> int main(){ printf("%s", FILE ); Код программы, выводящей свой исходный код, приведен в листинге 67. Этот же код вы можете использовать для отображения содержимого произ- вольного текстового файла. Просто в функции fopen() укажите имя файла.
ЧАСТЬ 7. Файлы Листинг 67. Отображение исходного кода (67.с) #include <stdio.h> int main () { FILE *fp; char c; fp = fopen( FILE ,"r"); do { с = getc(fp); putchar(c) ; } while(c != EOF) ; fclose(fp); return 0; $ gcc 67.с $ ./a.out «include <stdlo.h> int mainC) { FILE *fp; char c; fp ■ fopen(_FILE_,V#t); do { с » getc(fp}j putchar(c); while(c !- EOF)j fclose(fp); //printfCW); return 0; I :. Рис. 67. Результат работы программы
ЧАСТЬ 8. Готовые приложения
ЧАСТЬ 8. Готовые приложения Пример 68. Приложение клиент-сервер В этом примере мы напишем две программы - сервер и клиент. Программа- сервер после запуска сразу же перейдет в режим ожидания ("прослушива- ния") новых клиентов. Максимальное количество клиентов - 3. Как только подключится клиент, мы отправим ему сообщение "Version", в ответ на которое клиент передаст свое имя - "Client v.0.1". Сервер прочита- ет переданную от клиентов информацию и выведет ее на консоль. Клиент, в свою очередь, выведет на консоль запрос сервера. С целью упрощения исходного кода как сервера, так и клиента, обработку ошибок производить не будем, поэтому будьте готовы к тому, что ваш кли- ент выдаст сообщение Segmentation fault и завершит работу, если вы, напри- мер, укажете неправильное имя сервера или порт. Исходный код программы-сервера приведен в листинге 68а. Листинг 68а. Программа-сервер (server.с) #include <sys/types.h> #include <netdb.h> #include <memory.h> #include <sys/socket.h> ♦include <netinet/in.h> ♦include <stdio.h> ♦ define SERVER_PORT 1234 ♦ define BUF_SIZE 64 ♦define MSG_TO_SEND "Version\n" int main () { int sockl, sock2; int ans_len, total=0; char buffer[BUF_SIZE]; struct sockaddr in sin, client;
100 примеров на Си sockl = socket (AF_INET, SOCK_STREAM, 0) ; memset ((char *)&sin, Л0', sizeof (sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; sin.sin_port = SERVER_PORT; bind(sockl, (struct sockaddr *)&sin, sizeof(sin)); printf("Server running..An"); listen (sockl, 3); while (1) { ans_len = sizeof(client); sock2 = accept (sockl, &client, &ans_len); write (sock2, MSG_TO_SEND, sizeof(MSG_TO_SEND)); total+=l; ans_len - read (sock2, buffer, BUF__SIZE) ; write (1, buffer, ans_len); printf("Client no %d\n",total); shutdown (sock2, 0); close (sock2); }; return 0; } Теперь разберемся, что есть что. Сначала мы определяем некоторые макро- сы: ♦ define SERVER_PORT 1234 #define BUF_SIZE 64 #define MSG_TO_SEND "Version\n" Первый - это номер порта сервера, именно этот порт будет прослушивать наша программа. Второй макрос - это размер буфера передаваемых данных. Третий - это наш запрос клиенту. Нам понадобятся сразу два сокета: int sockl, sock2; Первый сокет - это сокет сервера, а через второй сокет мы будем произво- дить обмен данными с клиентом.
ЧАСТЬ 8. Готовые приложения Следующие две переменные int ans_len, total=0; Переменная ans_len используется для хранения размера передаваемой кли- ентом информации - фактически размер структуры struct sockaddr_in. Вто- рая переменная (total) - это общий счетчик числа клиентов. Данная пере- менная используется для вывода порядкового номера клиента. Переменная buffer размера BUF_SIZE - это наш буфер для обмена инфор- мацией. Нам нужны две структуры типа sockaddr_in - одна для сервера (sin) и одна для клиента (client). В строке sockl = socket (AF_INET, SOCK_STREAM, 0); мы создаем наш, "серверный", сокет. Набор протоколов - TCP/IP, режим с установлением соединения. Затем мы инициализируем структуру sin: memset ((char *)&sin, Л\0', sizeof(sin)); sin.sin_family = AF_INET; // TCP/IP sin.sin_addr.s_addr = INADDR_ANY; // можем работать на любом адресе sin.sin_port = SERVER_PORT; // указываем порт (1234) После создания сокета и инициализации структуры sin нужно связать наш сокет с адресом и портом сервера: bind (sockl, (struct sockaddr *)&sin, sizeof (sin)); Оператор listen (sockl, 3); означает, что мы будем прослушивать сокет sockl (порт 1234), и максималь- ное число клиентов не должно превышать 3. Как и любой нормальный сервер, мы должны работать в бесконечном ци- кле, постоянно обрабатывая запросы клиентов:
100 примеров на Си while (1) { ans_len = sizeof (client); sock2 = accept (sockl, &client, &ans_len); write (sock2, MSG_TO_SEND, sizeof(MSG_TO_SEND)); total+=l; ans__len = read (sock2, buffer, BUF_SIZE) ; write (1, buffer, ans_len); printf("Client no %d\n",total); shutdown (sock2, 0) ; close (sock2); Конечно, любой нормальный сервер при поступлении определенных сигна- лов, например, SIG_HUP, должен корректно перезапуститься или вообще завершить работу. Наш сервер этого не делает - обработку сигналов, я наде- юсь, вы можете добавить сами. Установить обработчик сигнала можно так: #include "sock.h" #include <signal.h> /* обработчик сигнала SIGPIPE */ sigpipe_handler() { err_quit("Получен SIGPIPE \n"); } main () { /* установка обработчика сигнала SIGPIPE */ signal(SIGPIPE, sigpipe_handler); В бесконечном цикле мы: • получаем размер структуры client ans_len = sizeof(client); • создаем сокет sock2, через который будем обмениваться данными с кли- ентом. Если в очереди listen нет клиентов, мы переходим в состояние ожидания sock2 = accept (sockl, &client, &ans_len); • как только подключится клиент, мы отправим ему сообщение MSG_ ТО SEND
ЧАСТЬ 8. Готовые приложения write (sock2, MSG_TO_SEND, sizeof(MSG_TO_SEND)); • увеличиваем счетчик клиентов total+=l; • получаем размер прочитанных данных, сами данные записываются в бу- фер buffer ansjen - read (sock2, buffer, BUF_SIZE); • выводим прочитанные данные на стандартный вывод write (I, buffer, ans_len); • завершаем сеанс связи shutdown (sock2,0); • закрываем сокет close (sock2); Теперь мы можем откомпилировать нашу программу: дсс -о server server.с Запускаем: ./server Программа перешла в состояние ожидания новых клиентов. Далее будет приведена иллюстрация, демонстрирующая в работе обе программы. Программа-клиент несколько проще, чем сервер. Листинг 686. Программа-клиент (client.с) #include #include #include #include #include #include <sys/types.h> <sys/socket.h> <netinet/in.h> <netdb.h> <memory.h> <stdio.h> #define SERVER_HOST "localhost" #define SERVER PORT 1234
100 примеров на Си #define CLIENT_PORT 1235 #define MSG "Client v.0.1\n" main () { int sock; int ans_len;int BUF_SIZE = 64; char buffer[BUF_SIZE]; struct hostent *h; struct sockaddr_in client, server; sock = socket (AF_INET, SOCK_STREAM, 0); memset ((char *)&client, Л0', sizeof (client)); client.sin_family = AF_INET; client.sin_addr.s_addr = INADDR_ANY; client.sin_port = CLIENT_PORT; bind (sock, (struct sockaddr *)&client, sizeof (client)); memset ((char *)&client, л\0', sizeof(server)); h = gethostbyname (SERVER_HOST); server.sin_family = AF_INET; memcpy ( (char *)&server.sin_addr,h->h_addr,h->h_length); server.sin_port = SERVER_PORT; connect (sockf Sserver, sizeof(server)); ans_len = recv (sock, buffer, BUF_SIZE, 0); write (1, buffer, ans_len); send (sock, MSG, sizeof(MSG), 0); close (sock); exit (0); } Для подключения к серверу нам понадобится следующая информация: имя узла сервера, номер порта сервера, номер порта клиента: #define SERVERJHOST "localhost" #define SERVER_PORT 1234 #define CLIENT PORT 1235
ЧАСТЬ 8. Готовые приложения #define MSG "Client v.0.1\n" Константа MSG - это сообщение, которое будет передано серверу Как и в случае с сервером, нам понадобятся две структуры типа sockaddr_in: struct hostent *h; struct sockadcir_in client, server; Структура типа hostent нам нужна для получения адреса сервера. Создаем сокет, заполняем информацию о клиенте и связываем сокет: sock = socket (AF_INET, SOCK_STREAM, 0); memset ((char *)&client, Л0', sizeof(client)); client.sin_family = AF_INET; client.sin_addr.s_addr = INADDR_ANY; client.sin_port = CLIENT_PORT; bind (sock, (struct sockaddr *)&client, sizeof(client)); Перед подключением к серверу нужно определить его IP-адрес: h = gethostbyname (SERVERJiOST); Подключаемся к серверу: // набор протоколов server.sin_family = AF_INET; // задаем адрес сервера memcpy ((char *)&server,sin_addr,h->h_addr,h->h_length); // указываем порт сервера server.sin_port = SERVER_PORT; connect (sock, &server, sizeof(server)); После подключения к серверу принимаем его запрос, выводим его на стан- дартный вывод, отправляем ему свое сообщение и закрываем сокет: ans_len = recv (sock, buffer, BUF__SIZE, 0); write (1, buffer, ans_len); send (sock, MSG, sizeof(MSG), 0); close (sock);
100 примеров на Си Рис. 68. Клиент и сервер Вот, в принципе, и все. Результат работы сервера и клиента показан на рис. 68. Пример 69. Мини-игра "Змейка" В данном примере мы попробуем написать игру "Змейка". Игра будет кон- сольной, чтобы не усложнять программу разработкой графического интер- фейса. Смысл игры в следующем: нужно управлять змейкой (или червяч- ком - это еще одно название игры) с помощью клавиш w (вверх), s (вниз), а (влево) и d (вправо), собирая еду - символы +. Каждый такой плюс удлиня- ет змейку на один символ. Следовательно, чем длиннее змейка, тем лучше. Рассмотрим основные моменты кода. В функции main() есть бесконечный цикл while(): while (true) { // Если нажата клавиша, обрабатываем нажатую клавишу, if (kbhitO != 0) change__direction () ; // Двигаем змейку next_step();
ЧАСТЬ 8. Готовые приложения // Если нет еды, то ставим ее if (!food_check()) place__food() ; // Рисуем змейку show_table(); // "Усыпляем" программу на заданный интервал. Sleep(INTERVAL); Управление змейкой осуществляется функцией change_direction(), кото- рая обрабатывает нажатые клавиши: void change__direction () - { // Считываем нажатую клавишу с помощью функции getch(). symbol = getch(); switch (symbol) { // Управление змейкой case *w' : if (change___x != 1 | | change__y != 0) { change__x = -1; change_y = 0; } break; case 4a': if (change_x != 0 || change_y != 1) { change_x = 0; change_y = -1; } break; case *s' : if (change__x != -1 | | change_y != 0) { change_x =1; change_y = 0; } break; case M': if (change_x != 0 |I change_y != -1) { change_x = 0; change_y = 1; } break; #ifndef WINDOWS case *q': nonblock(NB_DISABLE); std::exit(0) ; #endif default: break; Выход из программы осуществляется при нажатии клавиши q. За следующий шаг змейки отвечает функция next_step(): t
100 примеров на Си void next_step() { // Чистим таблицу от змейки. clear_snake_on_table(); // Передвигаем тело змейки. for (int i = snake_size; i >= 2; —i) { coordinates_x[i] = coordinates_x[i - 1]; coordinates_y[i] = coordinates_y[i - 1]; } // Передвигаем голову змейки. coordinates_x[1] += change_x; coordinates_y[1] += change_y; // Проверяем в порядке ли координаты. check_coordinates() ; // Если голова змейки там же, где и еда, то увеличиваем размер змейки //и очищаем координаты змейки. if (coordinates_x[1] == food_x && coordinates_y[1] == food_y) { snake_size++; food_x = -1; food_y = -1; // Рисуем змейку. show_snake_on_table(); // Если змея укусила себя, if (game_over()) { // Сообщаем всю правду об игроке. std::cout « "You're looser! \n"; // Приостанавливаем игру. #ifdef WINDOWS system("pause"); #endif // Завершаем программу. std::exit(0); Функция place_food() помещает "еду" на карту. Если вам захочется устано- вить свой символ для "еды", то это можно сделать в этой функции:
ЧАСТЬ 8. Готовые приложения void place_food() { std: :srand(std: .-time (NULL) ) ; // Ставим в случайное место еду. for (int i = 1; i <= 9; ++i) { int x = std::rand(), у = std::rand(); if if X У if if if } (x < 0) x *= -1; (y < 0) у *= -1; %= (N + 1); %= (M + 1) ; (x == 0) ++x; (y == 0) ++y; (a[x][y] != *@' && a[x][y] != a[x][y] != V && a[x][y] != food_x = x; food у = у; a[x][у] = ^+'; return; yv' && a[x][y] != *>') { При этом мы должны не только поставить "еду" на карту, но и не поставить ее на змейку. Именно поэтому после получения случайных координат мы их немного модифицируем, чтобы "еда" не была поставлена на змейку. Изображает таблицу и края карты функция show_table(). Обратите внима- ние, как она очищает экран, - для этого она вызывает или системную ко- манду els (если программа запущена в Windows), или системную команду clear (обычно эта команда используется в Linux и других UNIX-подобных системах для очистки экрана): void show_table() { // Очищаем консоль. #ifdef WINDOWS system("els"); #else system("clear"); #endif // Выводим таблицу и края, for (int i = 0; i <= N + 1 for (int j = 0; j <= M + std::cout « (i == 0 || j == 0 1; ++j) i == N + 1 || j M + 1 ?
100 примеров на Си '#' : a[i][j]) « (j <= М ? "" : "\n"); Полный исходный код игры тщательно закомментирован и представлен в листинге 69. Листинг 69. Полный исходный код игры #include <iostream> #include <cstdio> #include <cstdlib> #include <ctime> #ifdef WINDOWS // Библиотека, нужна для использования функции Sleep(). #include <windows.h> // Библиотека, нужна для использования функций kbhit() и getch () . #include <conio.h> #else #include <unistd.h> #include <termios.h> #include <sys/select.h> #define STDIN_FILENO 0 # define NBJDISABLE 0 # define NB__ENABLE 1 #define Sleep (x) usleep(x*1000) int kbhitO { struct timeval tv; fd_set fds; tv.tv_sec = 0; tv.tv_usec = 0; FD_ZERO(&fds) ; FD_SET(STDIN_FILENO, &fds); select(STDIN_FILENO+1, &fds, NULL, NULL, &tv); return FD_ISSET(STDIN_FILENO, &fds); void nonblock(int state) { struct termios ttystate; // Получаем состояние терминала
ЧАСТЬ 8. Готовые приложения tcgetattr(STDIN_FILENO, fittystate); if (state == NB_ENABLE) { // Выключаем ICANON-режим ttystate.c_lflag &= -ICANON; ttystate.c_cc[VMIN] = 1; } else if (state == NB_DISABLE) { // Включаем ICANON-режим ttystate.c_lflag |= ICANON; // Устанавливаем атрибуты терминала tcsetattr(STDIN_FILENO, TCSANOW, Sttystate); int getch() {• return fgetc(stdin); #endif // snake__size - размер змейки. // change_x, change__y - в какую сторону движется змейка. // coordinates_x[1000], coordinates_y[1000] - массивы, хранящие координаты // ' частей тела змейки. // Координаты головы змейки хранятся в coordinates_x[l], coordinates__y [1] . // food__xf food_y - координаты еды. int snake_size, change_x, change_y, coordinates_x[1000], coordinates__y[1000] ; int food_x = -1, food_y = -1; // symbol - хранит в себе ASCII код нажатой клавиши. // а[1000][1000] - наша таблица, в которой происходит вся игра. char symbol, a[1000][1000]; // Константы: // N - размер таблицы, а именно высота. // М - ширина таблицы. // INTERVAL - интервал в миллисекундах, через каждый этот промежуток // времени змейка будет передвигаться. const int N = 13, М = 17, INTERVAL = 200;
100 примеров на Си // функция, считывающая нажатую клавишу, void change_direction() { // Считываем нажатую клавишу с помощью функции getch(). symbol = getch(); switch (symbol) { // Управление змейкой у нас через wasd. case *w': if (change_x != 1 || change_y != 0) { change_x = -1; change_y = 0; break; case ya': if (change_x != 0 I| change_y != 1) { change_x = 0; change_y = -1; break; case ys': if (change_x != -1 || change_y != 0) { change_x =1; change_y = 0; break; case M': if (change_x != 0 I I change_y != -1) { change_x = 0; change_y = 1; break; #ifndef WINDOWS case *q': nonblock(NB_DISABLE); std::exit(0); #endif default: break; // функция для вывода таблицы void show_table() { // Очищаем консоль. #ifdef WINDOWS system("els"); #else system("clear") ; #endif // Выводим таблицу и края, for (int i = 0; i <= N + 1; for (int j = 0; j <= M + 1; ++j) std::cout « (i == о || j == 0 || i == N + 1 || j == M + 1 ? f#f : a[i][j]) « (j <= M ? "" : "\n");
ЧАСТЬ 8. Готовые приложения // Очищаем координаты, в которых располагалась змейка, void clear_snake_on_table() { for (int i = 1; i <= snake_size; ++i) a[coordinates_x[i]][coordinates_y[i]] = ' Л; // Красим координаты змейки, void show__snake_on_table {У { II Изменяем тип головы, if (change__x == 1 && change__y == 0) a[coordinates_x[1]][coordinates_y[1]] if (change_x == -1 && change_y == 0) a [coordinates__x [ 1 ] ] [ coordinates_y [ 1 ] ] if (change_x == 0 && change_y == 1) a[coordinates_x[l]][coordinates_y[1]] if (change_x == 0 && change_y == -1) a[coordinates_x[l]][coordinates_y[1]] // Красим змейку, for (int i = 2; i <= a[coordinates_x[i] snake_size; [coordinates_y[i] = V; — Л А Г . 43'; // Проверяем, съела ли змейка саму себя, bool game_over() { for (int i = 2; i <= snake_size; ++i) // Если координаты головы змейки равны координате какой- либо части тела // змейки, то змейка съела саму себя. if (coordinates_x[1] == coordinates_x[i] && coordinates_y[1] == coordinates_y[i]) return true; // Если все координаты различны, то все в порядке - играем дальше. return false; // Проверяем, не вышла ли змейка за поле, если да, то возвращаем ее обратно, void check_coordinates() { if (coordinates_x[1] > N) if (coordinates_x[1] < 1) if (coordinates_y[1] > M) if (coordinates__y [1] < 1) coordinates_x[1] coordinates_x[l] coordinates_y[1] coordinates_y [1] 1; N; 1; M;
100 примеров на Си // функция следующего хода, в которой наша змейка сдвигается в сторону // на 1 ячейку. void next_step() { // Чистим таблицу от змейки. clear_snake__on_table () ; // Передвигаем тело змейки. for (int i = snake_size; i >= 2; —i) { coordinates_x [i] = coordinates__x [i - 1]; coordinates_y[i] = coordinates_y[i - 1]; // Передвигаем голову змейки. coordinates__x[l] += change_x; coordinates_y[1] += change_y; // Проверяем, в порядке ли координаты. check_coordinates() ; // Если голова змейки там же/ где и еда, то увеличиваем размер змейки //и очищаем координаты змейки. if (coordinates_x[1] == food_x && coordinates_y[1] == food_y) { snake_size++; food_x = -1; food__y = -1; // Рисуем змейку. show_snake_on_table(); // Если змея укусила себя, if (game_over()) { // Сообщаем всю правду об игроке. std::cout « "You're looser! \n"; // Приостанавливаем игру. #ifdef WINDOWS system("pause") ; #endif // Завершаем программу. std::exit(0);
ЧАСТЬ 8. Готовые приложения // функция проверки на наличие еды на карте, bool food_check() { // Если координаты еды неопределенны, то возвращаем true. if (food_x == -1 && food_y == -1) return false; // В остальных случаях false. return true; // функция добавления еды на карту, void place_food() { std::srand(std::time(NULL)); // Ставим в рандомное место еду. for (int i = 1; i <= 9; ++i) { int x = std::rand(), у = std::rand(); if if X У if if if (x < (У < %= (N %= (M (x == (y == (a[x] a[x] food x food у 0) x 0) у + 1) + 1) 0) 0) [у] [у] = X = У a[x][y] = return г *= -1; *= -1; ; ; ++х; ++у; != Ч?' && а[х][у] ! != '<' && а[х] [у] ! / I Л+'; && а[х][у] != >л/ && | { // Начальные установки, void standart_settings() { // Размер змеи - 2. snake__size = 2; // Змейка занимает две клетки вправо от координаты {1,1}, coordinates__x[l] = 1; coordinates_y[l] = 2; coordinates__x [2] = 1; coordinates_y[2] = 1; // Змейка движется вправо. change_x = 0; change_y ■ 1; t
100 примеров на Си int main() { // Задаем стандартные настройки. standart_settings(); #ifndef WINDOWS std:rmemset(а, ч nonblock(NB_ENABLE); #endif sizeof(a)); // Бесконечный цикл, while (true) { // Если нажата клавиша, обрабатываем нажатую клавишу. if (kbhitO != 0) change_direction() ; // Двигаем змейку. ################### # # # # й # I Рис. 69
ЧАСТЬ 8. Готовые приложения next_step(); // Если нет еды, то ставим ее. if (!food_check()) place_food(); // Рисуем змейку. show_table(); // "Усыпляем" программу на заданный интервал. Sleep(INTERVAL); Пример 70. Программа word count на С В Linux есть интересная программа we, подсчитывающая количество слов/ строк/символов в текстовом файле. Попробуем написать ее аналог. Его осо- бенно оценят пользователи Windows, где такой программы нет, и приходит- ся использовать средства Word или другого редактора для подсчета слов/ строк в текстовом файле. Листинг 70. Подсчет символов, слов, строк в файле #include <ctype.h> #include <stdio.h> int main(void) { int tot_chars = 0; int tot_lines = 0; int tot_words = 0; int in_space = 1; int c, last = y\nr , /* к-во символов */ /* k-bo строк */ /* k-bo слов */ while ((с = getcharO) != EOF) { last = c; tot_chars++; if (isspace(c)) { in_space = 1; if (c == y\n') { tot_lines++; } else { tot_words += in_space; in_space = 0;
100 примеров на Си if (last != Лп') { /* учитываем последнюю строку */ tot__lines++; printf("Строк, Слов, Символов\п"); printf(" %3d %3d %3d\n", tot_lines, tot_words, tot_chars) return 0; Использовать программу можно так: cat <имя файла> | <имя_программы> type <имя файла> | <имя программы> Первая команда подойдет для Linux, вторая - для Windows. Наша програм- ма читает данные со стандартного ввода, следовательно, на ее ввод можно отправить любой вывод, например, подсчитаем, сколько символов, слов и строк в выводе команды dmesg (сообщения ядра Linux): dmesg | ./70 $ dmesg | ./70 Строк, Слов, Символов 1517 14086 187931 $1 Рис. 70. Результат работы программы
ЧАСТЬ 9. Алгоритмы поиска и сортировки
100 примеров на Си Какая же серьезная программа на С обходится без поиска и сортировки дан- ных? Огромное внимание алгоритмам поиска и сортировки уделяется в на- стоящем шедевре - книге Дональда Кнута "Искусство программирования". Вот только "Искусство программирования" - отличный выбор, когда есть время на его изучение. А вот когда времени нет, а программа нужна прямо здесь и сейчас, то приходится обращаться к другим источникам, например, к этой книге. В этой части мы рассмотрим множество примеров, которые, я надеюсь, помогут вам при написании лабораторной, курсовой работы или даже собственного реального приложения. Пример 71. Сортировка вставкой связного списка. Сортировка реального файла Сортировка вставками (Insertion Sort) - это простой алгоритм сортировки. Суть его заключается в том, что на каждом шаге алгоритма мы берем один из элементов массива, находим позицию для вставки и вставляем. Нужно отметить, что массив из 1-го элемента считается отсортированным. Данный пример демонстрирует не только алгоритм сортировки вставками, но и работу со связным списком. Связный список - это базовая динами- ческая структура данных в информатике, состоящая из узлов, каждый из которых содержит как собственно данные, так и одну или две ссылки на следующий и/или предыдущий узел списка. Понятно, что первый узел спи- ска содержит ссылку только на следующий элемент, а последний - только на предыдущий. Для реализации связного списка мы используем структуру node, состоя- щую из двух членов: number - это число, которое несет в себе узел списка, и node - указатель на следующий узел. В нашем случае можно обойтись без указателя на предыдущий узел - для алгоритма сортировки вставками он не нужен. Первый узел списка называется head (голова списка). У последнего узла списка член node равен NULL. Сортировка вставками осуществляется функцией insertnode(), которая вставляет новый элемент в нужное место списка. Элементы берутся из массива test. Затем программа выводит мае-
ЧАСТЬ 9. Алгоритмы поиска и сортировки сив test и получившийся список - как показано на рис. 71, список является отсортированным. Листинг 71 а. Сортировка вставками #include <stdio.h> #include <stdlib.h> struct node { int number; struct node *next; struct node *head = NULL; /* функция вставляет узел в правильное место связного списка */ void insert_node(int value); int main(void) { struct node ^current = NULL; struct node *next = NULL; int test[] = {8, 3, 2, 6, 1, 5, 4, 7, 9, 0}; int i = 0; /* вставляем некоторые элементы в связный список */ for(i = 0; i < 10; i++) insert_node(test[i]); /* выводим список */ printf(" Д о после\п"), i = 0; while(head->next != NULL) { printf("%4d\t%4d\n", test[i++], head->number); head = head->next; /* очищаем список */ for (current = head; current != NULL; current = next) next = current->next, free(current); return 0; void insert_node(int value) { struct node *temp = NULL; struct node *one = NULL;
100 примеров на Си struct node *two = NULL; // если список пуст, нужно выделить память под голову списка if(head == NULL) { head = (struct node *)malloc(sizeof(struct node *)); head->next = NULL; // первый элемент - голова, второй - следующий элемент one = head; two = head->next; // временный узел temp = (struct node *)malloc(sizeof(struct node *)); temp->number = value; // меняем one и two местами в случае необходимости •while(two != NULL && temp->number < two->number) { one = one->next; two = two->next; Д о после 8 6 3 9 2 8 $ ./71 Рис. 71. Результат сортировки вставками
ЧАСТЬ 9. Алгоритмы поиска и сортировки one->next = temp; temp->next = two; Рассмотрим еще один пример сортировки вставкой связного списка. Во- первых, здесь мы строки будем читать со стандартного ввода: while((fgets(line, 1024, stdin)) list = insert (line, list); NULL) На секунду - это позволит использовать нашу программу не в образова- тельных, а в реальных целях - для сортировки файлов, и я далее покажу, как это сделать. В остальном программа похожа на предыдущую - сортировка осуществля- ется при вставке, поэтому все, что нам нужно после вставки элементов в список, - вывести его и освободить память. Для этого используются функ- ции print_list() и free_list() соответственно. Всего, если не считать функции main(), в нашей программе будет три функции: struct lnode *insert(char *data, struct lnode *list); void free_list(struct lnode *list); void print__list (struct lnode *list) ; Что они делают, вы уже знаете. Также немного изменен код функции встав- ки элемента. Но в целом функция вставки осталась примерно такой же, если не считать, что теперь она возвращает список, а не просто производит манипуляции над ним (в прошлом примере наша функция ничего не воз- вращала - тип void). Итак, рассмотрим код, который приведен в листинге 716. Код снабжен комментариями, чтобы вам было понятнее. Листинг 716. Второй вариант сортировки вставкой ♦include <stdio.h> ♦include <stdlib.h> ♦include <string.h> struct lnode { char *str; struct lnode *next; // вставка и сортировка
100 примеров на Си struct lnode *insert(char *data, struct lnode *list); // освобождение памяти void free_list(struct lnode *list); // вывод списка void print_list(struct lnode *list); int main(void) { char line[1024]; struct lnode *list; list = NULL; while((fgets(line, 1024, stdin)) != NULL) list = insert (line, list); // сортировка осуществляется при вставке print_list(list); // выводим отсортированный список free_list(list); // освобождаем память return 0; struct lnode *insert (char *data, struct lnode *list) { struct lnode *p; struct lnode *q; /* Создаем новый узел */ p = (struct lnode *)malloc(sizeof(struct lnode)); /* сохраняем данные в новый узел */ p->str = strdup(data); /* сначала мы обрабатываем случай, где данные (data) должны быть первым элементом */ if (list == NULL || strcmp(list->str, data) > 0) { /* по всей видимости, это первый элемент */ /* теперь данные станут первым элементом */ p->next = list; return p; } else { /* производим поиск по связному списку, определяя правильную позицию */ q = list; while(q->next != NULL && strcmp(q->next->str, data) < 0) { q = q->next; } p->next = q->next; q->next = p; return list;
ЧАСТЬ 9. Алгоритмы поиска и сортировки void free_list(struct lnode *list) { struct lnode *p; while(list != NULL) { p = list->next; free(list); list = p; void print_list(struct lnode *list) { struct lnode *p; for (p = list; p != NULL; p = p->next) printf ("%sff, p->str) ; cadillac geely bentley ford roercedes audi brow acura toyota acura audi bentley brow cadillac ford geely rcercedes toyota $ cat sort.txt $ cat sort.txt | ./71b Si Рис. 716. Программа отсортировала файл и вывела на экран
100 примеров на Си Я обещал показать, как можно программу использовать в реальных услови- ях для сортировки реального файла. Поскольку программа читает данные со стандартного ввода, мы можем перенаправить на нее файл, она его отсо- ртирует и выведет на экран, что и показано на рис. 716. Аналогично, если вам нужно получить результат сортировки в файл, вы также можете использовать перенаправление ввода/вывода: cat <файл_который_нужно_отсортировать> | программа > <результат> Посмотрите на рис. 71 в. На ввод программы я подаю файл sort.txt, содержа- щий названия автомобильных марок. Результат сортировки будет помещен в файл sorted.txt. После этого я вывожу содержимое обоих файлов. Далее будет приведен еще один пример сортировки файла, но уже с помо- щью другого алгоритма. Преимущество именно этого варианта программы в том, что она использует связный список, а не массив, следовательно, коли- cadillac geely bentley ford mercedes audi brow acura toyota acura audi bentley bnw cadillac ford geely mercedes toyota $ cat sort.txt $ cat sort.txt ./71b > sorted.txt cat sorted.txt Рис. 71 в. Результат сортировки реального файла
ЧАСТЬ 9. Алгоритмы поиска и сортировки чество элементов массива ограничивается только количеством доступной памяти - или физической вообще, или, если вы работаете в Linux, лимитом памяти, заданным администратором (если такой лимит вообще был задан). Пример 72. Бинарный поиск в целочисленном массиве Бинарный (он же двоичный) поиск - классический алгоритм поиска эле- мента в отсортированном массиве (векторе), использующий дробление массива на половины. Данный метод также известен как метод деления по- полам. Если у нас есть массив, содержащий упорядоченную последовательность данных, то очень эффективен двоичный поиск. Да, вы все правильно по- няли, бинарный поиск работает только на уже отсортированных массивах, поэтому перед применением бинарного поиска к произвольному массиву (прочитанному из файла или введенному пользователем) его нужно отсо- ртировать. Переменные left и right содержат, соответственно, левую и правую границы Отрезка массива, где находится нужный нам элемент. Мы начинаем всегда с исследования среднего элемента отрезка (middle). Если искомое значение меньше среднего элемента, мы переходим к поиску в верхней половине от- резка, где все элементы меньше только что проверенного. Другими словами, значением right становится (middle - 1) и на следующей итерации мы ра- ботаем с половиной массива. Таким образом, в результате каждой проверки мы вдвое сужаем область поиска. Так, в нашем примере, после первой ите- рации область поиска - всего лишь 5 элементов. Двоичный поиск - очень мощный и эффективный метод. Если представить, что длина массива равна 1023, после первого сравнения область сужается до 511 элементов, а после второй - до 255. Легко посчитать, что для поиска в массиве из 1023 элементов достаточно 10 сравнений. Листинг 72. Двоичный поиск в целом (int) массиве ♦include <stdio.h> ♦define TRUE 0 ♦define FALSE ,1 int main(void) { int array[10] - {0, 1, 2, 3, 4, 6, 7, 8, 9, 10};
100 примеров на Си int left = 0; int right = 10; int middle = 0; int number = 0; int bsearch = FALSE; int i = 0; printf("Массив: "); for(i = 0; i < 10; i printf("[%d] ", array[i]); printf("ХпВведите искомый элемент: "); scanf("%d", &number); while(bsearch == FALSE && left <= right) { middle = (left + right) / 2; if(number == array[middle]) { bsearch = TRUE; printf("** Число есть в массиве! **\п"); } else { $ ./72 Массив: [0] [1] [2] [3] [4] [6] [7] [8] [9] [18] введите искомый элемент: 5 - - Элемент не найден - - $ ./72 Массив: [0] [1] [2] [3] [4] [6] [7] [8] [9] [10] Введите искомый элемент: 7 ** Число есть в массиве! ** $ Рис. 72. Результат двоичного поиска
ЧАСТЬ 9. Алгоритмы поиска и сортировки if(number < array[middle]) right = middle - 1; if(number > array[middle]) left = middle + 1; if (bsearch == FALSE) printf("— Элемент не найден return 0; —\n"); Посмотрите на рис. 72 и код программы. В нашем массиве элемента 5 нет. Мы вводим сначала 5, а потом элемент, который есть в массиве, чтобы про- верить правильность работы программы. Дополнительную информацию по этому методу поиска и дополнительный пример кода вы можете получить в Википедии: https://goo.gl/SKVJYx Пример 73. Бинарный поиск по массиву указателей строк Прошлый пример показывал, как выполнить поиск по упорядоченному массиву целых чисел. Но на практике чаще возникают задачи поиска опре- деленной строки, нежели определенного числа. Именно поэтому сейчас бу- дет рассмотрен пример двоичного поиска по массиву указателей строк. Принцип тот же. Исходный массив должен быть отсортирован. В функцию binsearch передается массив строк, размер массива и искомое значение. Функция возвращает 0, если значение не найдено, или же позицию найден- ного значения. Учитывая, что массив отсортирован, средняя позиция опре- деляется как сумма начальной и последней (begin + end), разделенная на 2. Далее нужно сравнить функцией strcmp() искомое слово со словом в полу- чившейся позиции. Функция strcmp() возвращает значение • < 0, если первый ее аргумент лексикографически меньше, чем второй; • > 0, если первый аргумент лексикографически больше, чем второй; • 0, если аргументы равны. Так вот, функция strcmp() не только сравнивает строки, но и еще и подска- зывает нам, в каком направлении двигаться, - в соответствии с этим мы или
100 примеров на Си увеличиваем позицию, или уменьшаем ее. Если функция вернула 0, то мы можем вернуть позицию (переменная position), в которой это произошло. Прототип функции strcmp() выглядит так: int strcmp(const char *strl, const char *str2) Код примера, реализующего бинарный поиск по массиву строк, приведен в листинге 73, а результат его работы, как обычно, показан на рис. 73. Листинг 73. Бинарный поиск по массиву строк #include <stdio.h> #include <string.h> static int binsearch(char *str[], int max, char *value); int main(void) { /* массив должен быть отсортирован... */ char *strings[] = { "audi", "bentley", "bmw", "cadillac", "ford" }; int i, asize, result; i = asize = result = 0; asize = sizeof (strings) / sizeof(strings[0]); for(i =0; i < asize; i printf("%d: %s\n", i, strings[i]); printf("\n"); if ((result = binsearch(strings, asize, "bmw")) != 0) printf("4bmwf найдено на позиции: %d\n", result); else printf("4bmw' не найдено..\n"); if((result = binsearch(strings, asize, "mercedes")) != 0) printf("smercedes' найдено на позиции %d\n", result); else printf(""mercedes' не найдено.An"); return 0; } static int binsearch(char *str[], int max, char *value) {
ЧАСТЬ 9. Алгоритмы поиска и сортировки int position; int begin = 0; int end = max - 1; int cond = 0; while(begin <= end) { position = (begin + end) / 2; if((cond = strcmp(str[position], value)) return position; else if(cond < 0) begin = position + 1; else end = position - 1; — 0) return 0; «: audi 1: bentley 2: brcw 3: cadillac 4: ford найдено на позиции; 2 d' не найдено*. Рис. 73. Результат двоичного поиска строки
100 примеров на Си Пример 74. Сортировка пузырьком Еще один популярный в программировании метод сортировки - это сорти- ровка пузырьком (buble sort в англ. литературе). Алгоритм пузырьковой сортировки считается самым простым, но доволь- но неэффективным. Его можно использовать разве что для сортировки небольших массивов. Алгоритм считается учебным и практически не при- меняется вне учебной литературы, вместо него на практике применяются более эффективные алгоритмы сортировки. Но поскольку мы как раз учим- ся программировать, данный алгоритм - настоящая находка. Суть алгоритма заключается в следующем. Программа несколько раз про- ходится по сортируемому массиву. При каждой итерации элементы после- довательно сравниваются попарно, и если порядок в паре неверный, выпол- няется обмен элементов. Получается, что элементы как бы выталкиваются вверх, как пузырьки в воде, отсюда и название алгоритма. Проходы (итерации) по массиву повторяются N - 1 {\displaystyle N-1} N-1 раз или до тех пор, пока на очередном проходе не окажется, что обмены больше не нужны, что означает - массив отсортирован. При каждой итерации очередной наибольший элемент массива ставится на свое место в конце массива - рядом с предыдущим наибольшим элементом, а наименьший элемент перемещается на одну позицию к началу массива - "всплывает". Думаю, принцип понятен. Осталось все это закодировать. В нашей програм- ме мы создадим функцию bubble_sort(), которой нужно передать массив элементов и его размер. Функция использует два цикла for - внутренний и внешний. Внешний проходится от 0 до size, а переменная size содержит ко- личество элементов в массиве. Во внутреннем цикле функция проходится от 0 до size - i. Если a[j] > a[j+l], то элементы a[j] и a[j+l] меняются места- ми. Переменная hold используется для хранения временного значения при свопе элементов. Листинг 74. Пузырьковая сортировка #include <stdio.h> void bubble_sort(int a[], int size); int main(void) { int arr[10] = {10, 2, 4, 1, 6, 5, 8, 7, 3, 9}; int i = 0;
ЧАСТЬ 9. Алгоритмы поиска и сортировки printf("До сортировки:\п"); for(i = 0; i < 10; printf("\n"); bubble__sort (arr, 10); printf("После:\n"); for(i = 0; i < 10; i printf("\n"); return 0; printf("%d ", arr[i]); printf("%d ", arr[i]); void bubble_sort(int a[], int size) { int switched = 1; int hold =0; int i = 0; int j = 0; size -= 1; for(i = 0; i < size && switched; i++) { switched = 0; for(j = 0; j < size - iv j $ ./74 До сортировки: 10 241658739 После: 123456789 10 $1 Рис. 74. Пузырьковая сортировка массива
100 примеров на Си if (a[j; 1 > a switched = hold = a[j; a[j] = a [ j + = a [ j 1] = [j+1]) 1; + l]; hold; Еще раз отмечу, что данный алгоритм очень неэффективный: общее число сравнений равно (N-l)N, то есть если массив состоит из 10 элементов, как у нас, то программа выполнила 90 сравнений, чтобы отсортировать массив. Это настоящее расточительство ресурсов: представьте, что будет, если эле- ментов будет не 10, а один миллион?! Тем не менее, этот алгоритм часто используется при обучении программированию. Если вы так и не разобра- лись, как он работает, на страничке в Википедии можно увидеть анимацию, демонстрирующую алгоритм в динамике: https://goo.gl/KGE6yn Пример 75. Пузырьковая сортировка связного списка Давайте усложним нашу предыдущую задачу и выполним пузырьковую со- ртировку связного списка. Алгоритм будет таким же, но работать мы будем не с массивом, а со связным списком. Подобная задача - хорошая практика по работе с указателями, а они играют в С очень важную роль - ни одна серьезная программа на этом языке программирования не обходится без указателей. В то же время большинство ошибок, допускаемых начинающи- ми программистами, связаны как раз с работой с указателями, поэтому чем больше практики по работе с указателями у вас будет, тем лучше. Как уже было отмечено, сам алгоритм сортировки останется тем же (только мы его слегка модифицируем). Но кроме него нам нужно реализовать еще две вспомогательных функции: /* добавляет новый узел в связный список */ void llist_add(struct lnode **q, int num); /* выводит результат */ void llist_print(void); Рассмотрим сначала функцию llist_add(). Ей передаются два параметра - указатель на список и число, которое нужно добавить в список. Если список пуст, то она создает первый узел - выделяет память с помощью malloc(): if(*q == NULL) {
ЧАСТЬ 9. Алгоритмы поиска и сортировки *q = malloc(sizeof(struct lnode)); Функция "перематывает" список, чтобы добраться к последнему узлу: /* переходим к последнему узлу */ while(tmp->next != NULL) tmp = tmp->next; /* добавляем узел в конец списка */ tmp->next = malloc(sizeof(struct lnode)); tmp = tmp->next; } Напомню, последним считается узел, у которого указатель на следующий узел (next) равен NULL. Поэтому в самой "перемотке" нет ничего сложного - нужно двигаться, пока next не будет равен NULL. Как только мы "перемотали" список и добрались до последнего элемента, нужно присвоить ему данные: tmp->data = num; tmp->next = NULL; Функция вывода связного списка очень проста. Она похожа на перемотку списка, только при этой самой перемотке мы выводим значение текущего элемента списка: void llist_print (void) { visit = head; while(visit != NULL) { printf("%d ", visit->data); visit = visit->next; } printf (ff\nfl) ; При программировании связных списков очень важно не "потерять голову". Следите за указателем head - одно "неправильное движение", и вы можете потерять весь список. Именно поэтому везде нужно работать с указателем visit (можете назвать его temp - это уже как вам захочется). А указатель head должен оставаться неизменным.
100 примеров на Си Сортировка связного списка осуществляется функцией llist_bubble_sort(). В ней, как и в предыдущем случае, есть два цикла - внешний и внутренний, только для большего удобства циклы заменены на while(): while(e != head->next) { с = а = head; b = a->next; while(a != e) { Полный код программы приведен в листинге 75, а результат ее выполне- ния - на рис. 75. В программе мы будем генерировать случайные числа, и ими же будем заполнять наш список - чтобы избавить вас от ввода чисел вручную. Листинг 75. Пузырьковая сортировка связного списка #include <stdio.h> #1пс1ц<1е <stdlib.h> #define MAX 10 struct lnode { int data; struct lnode *next; } *head, *visit; /* добавляем новый узел в связный список */ void llist_add(struct lnode **q, int num); /* осуществляем сортировку связного списка */ void llist_bubble_sort(void); /* выводим результат */ void llist_print(void); int main(void) { /* связный список */ struct lnode *newnode = NULL; int i = 0; /* общий счетчик */ /* загружаем случайные числа в связный список */ for(i = 0; i < MAX; i++) { llist_add(&newnode, (rand() % 100)); head = newnode;
ЧАСТЬ 9. Алгоритмы поиска и сортировки printf("До сортировки:\п"); llist_print(); printf("После сортировки:\п"); llist_bubble_sort(); llist_print(); return 0; /* добавляем узел в конец связного списка */ void llist_add(struct lnode **q, int num) { struct lnode *tmp; tmp = *q; /* если список пуст, создаем первый узел */ if(*q == NULL) { *q = malloc(sizeof(struct lnode)); tmp = *q; } else { /* переходим к последнему узлу */ while(tmp->next != NULL) tmp = tmp->next; /* добавляем узел в конец списка */ tmp->next = malloc(sizeof(struct lnode)); tmp = tmp->next; /* присваиваем данные последнему узлу */ tmp->data = num; tmp->next = NULL; /* выводим связный список */ void llist_print(void) { visit = head; while(visit != NULL) { printf("%d ", visit->data); visit = visit->next; ) printf("\n"); /* пузырьковая сортировка связного списка */
100 примеров на Си void llist_bubble_sort(void) { struct lnode *a = NULL; struct lnode *b = NULL; struct lnode *c = NULL; struct lnode *e = NULL; struct lnode *tmp = NULL; // Алгоритм пузырьковой сортировки, адаптированный // под связный список while(e != head->next) { с = а = head; b = a->next; while (a != e) { if(a->data > b->data) { if(a == head) { tmp = b -> next; b->next = a; a->next = tmp; head = b; с = b; } else { $ ./75 До сортировки: 83 86 77 15 93 35 86 92 49 21 После сортировки: 15 21 35 49 77 83 86 86 92 93 $ Рис. 75. Пузырьковая сортировка связного списка
ЧАСТЬ 9. Алгоритмы поиска и сортировки tmp = b->next; b->next = а; a->next = tmp; c->next = b; с = b; } } else { с = а; a = a->next; } b = a->next; if(b == e) e = a; Пример 76. Пузырьковая сортировка массива строк. Сортировка реального файла Ваша коллекция алгоритмов пузырьковой сортировки была бы неполной, если бы мы не рассмотрели программу для пузырьковой сортировки масси- ва строк. Для большего разнообразия в этом случае мы будем вводить стро- ки вручную. Программа сортировки массива строк будет гораздо более простой, чем в случае со связным списком. Во-первых, нам не нужно постоянно следить за указателями. Во-вторых, не нужно бояться "потерять голову" (как свою, так и списка). В-третьих, на помощь нам приходит уже знакомая функция срав- нения строк strcmp(), которая существенно упрощает процесс сортировки. Вспомогательная функция swap() меняет местами два слова, а сортировка выполняется функцией sort_words(). При этом у нас есть две константы: МАХ и N. Первая задает максимальную длину слова, вторая - максималь- ное количество слов. Обе константы равны 50, но вы можете указать другие значения. Для завершения ввода нажмите Ctrl + D — если вам не хочется вводить все 50 слов. Полный код программы приведен в листинге 76. Листинг 76. Пузырьковая сортировка массива строк #include <stdio.h>
100 примеров на Си tinclude <stdlib.h> #include <string.h> #define MAX 50 // максимальная длина слова #define N 50 // максимальное количество слов void sort_words(char *x[], int y); void swap(char **, char **); int main(void) { char word[MAX]; char *x[N]; int n = 0; int i = 0; printf("Введите слова, для завершения ввода нажмите Ctrl + D: \п"); for(i = 0; scanf("%s", word) == 1; if (i >= N) printf("Достигнут лимит: %d\n", N), exit(l); x[i] = calloc(strlen(word)+1, sizeof(char)); strcpy(x[i], word); n = i; printf("ХпРезультат:\n"); sort_words(x, n); for(i = 0; i < n; ++i) printf("%s\n", x[i]); return(0); void sort_words(char *x[], int y) { int i = 0; int j = 0; for(i =0; i < y; for(j = i + 1; j < y; ++j) if(strcmp(x[i], x[j]) > 0) swap(&x[i], &x[j]); void swap(char **p, char **q) {
ЧАСТЬ 9. Алгоритмы поиска и сортировки $ ./76 введите слова, для завершения ввода нажмите Ctrl + D: ford audi mazda acura bmw Результат: acura audi bmw ford nazda Рис. 76а. Результат сортировки массива char *tmp; tmp = *p; *p = *q; *q = tmp; У нашей программы есть один очень полезный "побочный эффект". Чтобы вы не думали, что все эти примеры не имеют практической ценности, со- общаю прекрасную новость. Наша программа умеет читать со стандартно- го ввода. Следовательно, ее можно использовать для сортировки реальных файлов. Все, что нужно для этого сделать, - перенаправить список, прочи- танный из файла на нашу программу, например: cat sort.txt I ./76 В данном случае ./76 - это название исполнимого файла программы (по но- меру примера). Посмотрите на рис. 766: я вывел сначала содержимое файла sort.txt, а затем перенаправил вывод команды cat на нашу программу, и она отсортировала список!
100 примеров на Си $ cat sort.txt catfillac geely bentley ford roercedes audi bnw acura toyota $ $ cat sort.txt | »/76 Введите слова, для завершения ввода нажните Ctrl + D: Результат: асига audi bentley bnw Cadillac ford geely nercedes toyota Рис. 766. Использование нашей программы для сортировки реального файла Чтобы превратить нашу учебную программу в "боевую", выполните следу- ющие рекомендации: 1. Увеличьте максимальную длину слова, скажем, до 100 символов. Тогда программу можно будет использовать для сортировки имен файлов, на- пример, для списка воспроизведения. 2. Увеличьте количество элементов до 2000. Можно и больше, но 2000, ду- маю, будет достаточно. 3. Уберите операторы printf(), выводящие подсказку и слово "Результат", иначе эти строки также будут помещены в вывод программы, что неже- лательно. Пример 77. Пирамидальная сортировка Наш следующий пример - пирамидальная сортировка, она же сортировка кучей (heap sort). Данный алгоритм является модификацией пузырьковой сортировки и представляет собой что-то среднее между сортировкой выбо- ром и пузырьковой сортировкой. Идея алгоритма заключается в следующем: ищем максимальный элемент в неотсортированной части массива и ставим его в конец этого подмассива.
ЧАСТЬ 9. Алгоритмы поиска и сортировки В поисках максимума подмассив перестраивается в так называемое сорти- рующее дерево (он же двоичная куча, он же пирамида), в результате чего максимум сам "всплывает" в начало массива. После этого над оставшейся частью массива снова осуществляется проце- дура перестройки в сортирующее дерево с последующим перемещением максимума в конец подмассива. Что такое сортирующее дерево? Это такое дерево, у которого любой роди- тель не меньше, чем каждый из его потомков, - так называемое неубываю- щее дерево. Есть и невозрастающее дерево - это когда любой родитель не больше, чем каждый из его потомков. Листинг 77. Пирамидальная сортировка #include <stdio.h> #include <stdlib.h> /* максимальная длина массива ... */ # define MAXARRAY 5 /* осуществляет пирамидальную сортировку */ void heapsort(int ar[], int len); /* помогает heapsort () "выталкивать" элементы, начиная с позиции pos */ void heapbubble(int pos, int ar[], int len); int main(void) { int array[MAXARRAY]; int i = 0; /* загружаем случайные элементы в массив */ for(i = 0; i < MAXARRAY; i++) array[i] = rand() % 100; /* выводим исходный массив */ printf("flo сортировки: "); for(i = 0; i < MAXARRAY; i printf(" %d ", array[i]); } printf("\n"); /* Сортировка */ heapsort(array, MAXARRAY); /* результат */
100 примеров на Си printf("ПОсле сортировки: "); for(i =0; i < MAXARRAY; i printf(" %d ", array[i]); } printf("\n"); return 0; void heapbubble(int pos, int array[], int len) { int z = 0; int max = 0; int tmp = 0; int left = 0; int right =0; z = pos; for(;;) { left = 2 * z + 1; right = left + 1; if(left >= len) return; else if(fight >= len) max = left; else if(array[left] > array[right]) max = left; else max = right; if(array[z] > array[max]) return; tmp = array[z]; array[z] = array[max]; array[max] = tmp; z = max; void heapsort(int array[], int len) { int i = 0; int tmp = 0; for(i = len / 2; i >= 0; —i) heapbubble(i, array, len);
ЧАСТЬ 9. Алгоритмы поиска и сортировки i > 0; i—) { for(i = len - 1; tmp = array[0]; array[0] = array[i]; array[i] = tmp; heapbubble(0, array, i); Как видно на рис. 77, программа сгенерировала случайные значения, поме- стила их в массив и выполнила сортировку этого массива. $ ./77 До сортировки: 83 86 77 15 93 ПОс/ie сортировки: 15 77 83 86 93 I Рис. 77. Результат работы программы Пример 78. Сортировка вставкой массива по убыванию и по возрастанию В примере 71 мы рассмотрели сортировку вставкой связного списка. Но сортировать вставкой можно не только связные списки, хотя, нужно при- знаться, что делать это в случае со связным списком - одно удовольствие, учитывая наличие указателей. В этом примере будет показана сортировка вставкой массива float-чисел. У нас будут два массива. Первый мы оставим в качестве исходного, чтобы его можно было вывести для сравнения, а второй отсортируем вставкой. Для сортировки мы будем использовать написанную нами же функцию
100 примеров на Си isort(), которой нужно передать массив элементов и его размер - количе- ство элементов в массиве. Функция fm() используется для поиска миниму- ма в массиве, точнее, в его промежутке, который задается параметрами b и п. Функция ищет минимум и возвращает его позицию. Листинг 78. Сортировка вставкой массива float-чисел #include <stdio.h> void isort (float arr[], int n) ; int fm(float arr[], int b, int n) ; int main(void) { float arrl[5] = {4.3, 6.7, 2.8, 8.9, 1.0}; float arr2[5] = {4.3, 6.7, 2.8, 8.9, 1.0}; int i = 0; isort(arr2, 5); printf("\пД о\£После\п- ■\n"); for(i = 0; i < 5; i printf("%.2f\t%.2f\n", arrl[i], arr2[i]); return 0; int fm (float arr[], int b, int n) { int f = b; int c; for(c = b + 1; с < n; с++) if(arr[c] < arr[f]) f = c; return f; void isort (float arr[], int n) int s, w; float sm; for(s =0; s < n - 1; s++) { w = fm(arr, s, n); sm = arr[w]; arr[w] = arr[s];
ЧАСТЬ 9. Алгоритмы поиска и сортировки $ дсс 78.с -о 78 $ ./78 После 4.36 6.79 2.80 8,96 1.60 1.66 2.86 4.36 6.76 8.96 $1 Рис. 78а. Сортировка массива чисел (по возрастанию) arr[s] = sm; Примечательно, что если изменить функцию fm() так, чтобы она возвра- щала не меньший, а больший элемент, то есть искала максимум, а не мини- мум, то сортировка будет не по возрастанию, а по убыванию, что и показано на рис. 786. Сначала я запустил программу 78. Потом я внес изменения в функцию fm(), перекомпилировал программу, не задав имя выходного фай- ла, - чтобы получился исполнительный файл ./a.out, и запустил его. Код функции fm() для сортировки по убыванию следующий: int fm(float arr[], int b, int n) { int f = b; int c; for(c = b + 1; с < n; с++) if(arr[c] > arr[f]) f = c; return f;
100 примеров на Си Д о После 4.30 6.70 2.80 8.90 1.00 1.08 2.88 4.38 6.70 8.96 $ den@den-рс:~/с-ех$ den@den-pc:~/c~ex$ Д о 4.30 6.70 2.80 8.90 1.00 После 8.98 6.70 4.38 2.88 1.00 тс 78.с дсс 78.с ./a.out den@den-pc:~/c«ex$ Рис. 786. Сортировка массива по убыванию Пример 79. Сортировка слиянием. Связный список Рассмотрим еще один алгоритм сортировки - сортировка слиянием (merge sort в англ. литературе). Это, нужно отметить, довольно эффективный ал- горитм. Сортировка слиянием - алгоритм сортировки, который упорядочивает списки (или другие структуры данных, доступ к элементам которых можно получать только последовательно, например - потоки) в определенном по- рядке. Слияние означает объединение двух (или более) последовательностей в одну упорядоченную последовательность при помощи циклического выбо- ра элементов, доступных в данный момент. Алгоритм довольно непростой. Попробую объяснить все по-простому. У нас есть два списка (или массива - не важно). Мы будем брать поочередно по одному элементу из каждого массива, сравнивать их и "сливать" в один массив. Меньший элемент будем ставить первым, больший - вторым. А что делать, если у нас есть только один список (массив)? Тогда его нуж- но разбирать на две части примерно одинакового размера. Далее каждая из получившихся частей сортируется отдельно, после чего два упорядоченных массива соединяются в один. Это и есть сортировка слиянием.
ЧАСТЬ 9. Алгоритмы поиска и сортировки В процессе сортировки мы рекурсивно вызываем функцию сортировки, пока размер массива не достигнет единицы. Любой массив (список), состо- ящий из одного элемента, можно считать упорядоченным. За сортировку слиянием отвечает функция mergesort(), которая была реализована специ- ально для этого примера: struct node *mergesort(struct node *head) { struct node *head_one; struct node *head__two; if((head == NULL) || (head->next == NULL)) return head; head__one = head; head_two = head->next; while((head_two != NULL) && (head_two->next !=NULL)) { head = head->next; head__two = head->next->next; } head__two = head->next; head->next = NULL; return merge(mergesort(head_one), mergesort(head_two)); Поскольку мы используем рекурсию, то мы должны предусмотреть условие выхода из рекурсии. В нашем случае условие выхода будет таким: if((head == NULL) || (head->next == NULL)) return head; To есть или список пуст, или список состоит из одного элемента (нет сле- дующего, поэтому next указывает на NULL). В этом случае мы возвраща- ем head, во всех остальных мы возвращаем merge(mergesort(head_one), mergesort(head_two)). Функция merge() выполняет непосредственно слияние списков. Мы пере- даем ей две головы двух списков, она выполняет слияние и возвращает его результат. Дополнительную информацию об этом алгоритме вы можете получить на страничке Википедии: https://goo.gl/natPWf. На ней также вы найдете реализацию алгоритма на разных языках программирования - С, C++. Не
100 примеров на Си будет лишним и просмотреть визуализацию алгоритма - как он работает. А я привожу собственную реализацию - см. листинг 79. Листинг 79. Сортировка связного списка слиянием #include <stdio.h> #include <stdlib.h> struct node { int number; struct node *next; /* добавляем узел в связный список */ struct node *addnode(int number, struct node *next); /* сортировка слиянием */ struct node *mergesort(struct node *head); /* слияние списков */ struct node *merge(struct node *head_one, struct node *head_ two) ; int main(void) { struct node *head; struct node ^current; struct node *next; int test[] = {8, 3, 2, 6, 1, 5, 4, 7, 9, 0}; int i; head = NULL; /* вставляем числа в связный список */ for(i = 0; i < 10; i++) head = addnode(test[i], head); /* сортируем список */ head = mergesort(head); /* выводим результат */ printf(" До После\п"), i = 0; for (current = head; current != NULL; current = current->next) printf("%4d\t%4d\n", test[i++], current->number); /* освобождаем память */ for(current = head; current != NULL; current = next) next = current->next, free(current); /* все */
ЧАСТЬ 9. Алгоритмы поиска и сортировки return 0; /* добавляем узел в связный список */ struct node *addnode(int number, struct node *next) { struct node *tnode; tnode = (struct node*)"malloc (sizeof (*tnode) ); if (tnode != NULL) { tnode->number = number; tnode->next = next; return tnode; /* сортировка слиянием связного списка */ struct node *mergesort(struct node *head) { struct node *head_one; struct node *head_two; if((head == NULL) || (head->next == NULL)) return head; head_one = head; head_two = head->next; while((head_two != NULL) && (head_two->next !=NULL)) { head = head->next; head_two = head->next->next; } head_two = head->next; head->next = NULL; return merge(mergesort(head_one), mergesort(head_two)); /* слияние списков */ struct node *merge(struct node *head_one, struct node *head_two) { struct node *head_three; if(head_one == NULL) return head two; if(head two == NULL)
100 примеров на Си return head_one; if(head_one->number < head_two->number) { head_three = head_one; head_three->next = merge(head_one->next, head_two); } else { head_three = head__two; head_three->next = merge(head_one, head_two->next); return head three; До После 8 3 2 6 1 5 4 7 9 0 $ ./79 $1 Рис. 79. Результат сортировки слиянием Пример 80. Сортировка слиянием массива Рассмотрим еще один пример сортировки слиянием, на этот раз сортиро- вать будем не связный список, а массив целых чисел. Сам алгоритм остает- ся тем же, но функция mergesort() будет адаптирована под работу с масси- вом. Также не будет функции merge(), а слиянием будем производить сразу в функции mergesort(). Переменная pivot - это центр массива. Мы разбиваем массив на две части (условно) и для каждой запускаем процесс сортировки:
ЧАСТЬ 9. Алгоритмы поиска и сортировки mergesort(a, low, pivot); mergesort(a, pivot + 1, high); Условие выхода из рекурсии - когда low = high, то есть массив у нас состоит из одного элемента: if(low == high) return; Полный код сортировки слиянием массива приведен в листинге 80. Резуль- тат сортировки массива показан на рис. 80. Листинг 80. Сортировка слиянием массива #include <stdio.h> #include <stdlib.h> # define MAXARRAY 10 void mergesort(int a[], int low, int high); int main(void) { int array[MAXARRAY]; int i = 0; /* загружаем в массив случайные данные */ for(i = 0; i < MAXARRAY; i++) array[i] = rand() % 100; /* До сортировки */ printf("До сортировки :"); for(i = 0; i < MAXARRAY; i++) printf(" %d", array[i]); printf("\n"); /* Сортировка */ mergesort(array, 0, MAXARRAY - 1); /* после */ printf("После сортировки :"); for(i = 0; i < MAXARRAY; i printf(" %d", array[i]); printf("\n");
100 примеров на Си return 0; void mergesort(int a[], int low, int high) { int i = 0; int length = high - low + 1; int pivot = 0; int mergel = 0; int merge2 = 0; int working[length]; if(low == high) return; pivot = (low + high) / 2; mergesort(a, low, pivot); mergesort(a, pivot + 1, high); for(i = 0; i < length; i++) working[i] = a[low + i] ; mergel = 0; merge2 = pivot - low + 1; for(i = 0; i < length; i if(merge2 <= high - low) if(mergel <= pivot - low) if(working[mergel] > working[merge2]) a[i + low] = working[merge2++]; else a[i + low] = working[mergel++]; else a[i + low] = working[merge2++]; else a[i + low] = working[mergel++];
ЧАСТЬ 9. Алгоритмы поиска и сортировки До сортировки После сортировки $ ./80 : 83 86 7? 15 93 35 86 92 49 21 : 15 21 35 49 77 83 86 86 92 93 $ Рис. 80. Сортировка массива слиянием Пример 81. Быстрая сортировка массива Быстрая сортировка, или сортировка Хоара (по имени разработчика алго- ритма) - широко известный алгоритм сортировки, разработанный англий- ским программистом Чарльзом Хоаром в 1960 году. Не удивляйтесь - боль- шинство алгоритмов сортировки были разработаны очень давно, примерно в 60-х годах XX века, но они не потеряли свою актуальность до сих пор - пока никто ничего лучше не придумал. Часто быструю сортировку называют qsort - по имени в стандартной би- блиотеке языка Си. Да, есть функция qsort(), можно использовать ее, но нам это не интересно. Гораздо интереснее написать собственную реализацию. Быстрая сортировка - это улучшенный вариант пузырьковой сортировки, но эффективность этого алгоритма значительно выше. Принципиальное отличие заключается в том, что первым делом производятся перестановки на наибольшем возможном расстоянии и после каждого прохода элементы делятся на две независимые группы. Интересно, что незначительное улуч- шение самого неэффективного алгоритма породило один из самых эффек- тивных алгоритмов сортировки. Он эффективен до такой степени, что его включили в стандартную библиотеку функций С.
ТОО примеров на Си Алгоритм заключается в следующем. Мы выбираем некоторый элемент - опорный элемент. Обычно это медиана - то есть элемент в середине масси- ва. Далее выполняется операция разделения: реорганизуем массив таким об- разом, чтобы все элементы со значением, меньшим или равным опорному элементу, оказались слева от него, а все элементы, превышающие по значе- нию опорный, - справа от него. Рекурсивно нужно упорядочить подмассивы, лежащие слева и справа от опорного элемента. Условие выхода из рекурсии - массив, состоящий из одного элемента (или пустой массив). Учитывая, что при каждой итерации длина обрабатываемого отрезка массива уменьшается как минимум на еди- ницу, условие выхода из рекурсии обязательно будет достигнуто, и обработ- ка массива гарантированно будет прекращена. Программная реализация приведена в листинге 81. Листинг 81. Быстрая сортировка массива #include <stdio.h> #include <stdlib.h> # define MAXARRAY 10 void quicksort(int arr[], int low, int high); int main(void) { int array[MAXARRAY] = {0}; int i = 0; /* загружаем в массив случайные числа */ for(i = 0; i < MAXARRAY; i array[i] = rand() % 100; /* выводим массив */ printf("До сортировки: "); for(i = 0; i < MAXARRAY; i printf(" %d ", array[i]); printf("Xn"); quicksort(array, 0, (MAXARRAY - 1)); /* выводим результат */ printf("После сортировки: ");
ЧАСТЬ 9. Алгоритмы поиска и сортировки for(i =0; i < MAXARRAY; i printf(" %d ", array[i] ); } printf("\n"); return 0; /* сортируем все между 4lowf <-> Nhighf */ void quicksort(int arr[], int low, int high) { int i = low; int j = high; int у = 0; /* опорный элемент */ int z = arr[(low + high) / 2]; /* разделение */ do { /* находим элемент левее */ while(arr[i] < z) i++; /* находим элемент правее */ while(arr[j] > z) j—; if (i <= j) { /* меняем местами 2 элемента */ у = arr[i]; arr[i] = arr[j]; arr[j] = y; j —; } } while(i <= j); /* рекурсия */ if(low < j) quicksort(arr, low, j); if (i < high) quicksort(arr, i, high);
100 примеров на Си $ ./81 До сортировки: 83 86 77 15 93 35 86 92 49 21 После сортировки: 15 21 35 49 77 83 86 86 92 93 $ I Рис. 81. Сортировка массива методом быстрой сортировки Пример 82. Сортировка массива строк библиотечной функцией qsort() В этом примере будет показано, как использовать библиотечную функцию qsort() для сортировки массива строк. Ведь раз у нас есть функция быстрой сортировки, то можно попробовать использовать ее, а не изобретать вело- сипед, - хотя от знания устройства велосипеда вас никто не освобождает! Как и другие программы из этой части книги, эта программа будет читать данные со стандартного ввода, следовательно, ее можно использовать для сортировки файла (как именно - было показано ранее). Функции qsort() нужно передать следующие параметры: • base - указатель на первый элемент массива, который нужно отсортиро- вать. • nitems - количество элементов в массиве. • size - размер каждого элемента в байтах. • compare - функция, которая будет сравнивать два элемента. Прототип функции выглядит так:
ЧАСТЬ 9. Алгоритмы поиска и сортировки void qsort(void *base, size__t nitems, size___t size, int (*compar)(const void *, const void*)) Функция просто сортирует массив и ничего не возвращает. Код примера приведен в листинге 82. В качестве собственной функции compare() мы ис- пользуем strcmp(). Листинг 82. Использование библиотечной функции qsort() ♦include <stdio.h> ♦include <string.h> ♦include <stdlib.h> void sortstrarr(void *array, unsigned n); static int cmpr(const void *a, const void *b); int main(void) { char line[1024]; char *line_array[1024] ; int i = 0; int j = 0; // Читаем данные со стандартного ввода while((fgets(line, 1024, stdin)) != NULL) if(i < 1024) line__array[i++] = strdup(line); else break; // Сортируем массив sortstrarr (line_array, i); // выводим результат while(j < i) printf("%s", line_array[j++]); return 0; // Наша пользовательская функция сравнения //Мы просто сделали "обертку" для strcmp static int cmpr(const void *a, const void *b) { return strcmp(*(char **)a, *(char **)b); l
100 примеров на Си void sortstrarr(void *array, unsigned n) { // Вызываем функцию qsort и передаем ей все необходимое qsort(array, n, sizeof(char *), cmpr); Как и в предыдущих случаях, проверяем работу программы на файле sort, txt. Смотрим, чтобы результат программы был такой же, как в предыдущих случаях (рис. 82). $ cat sort.txt | ./82 асигз audl bentley bnw Cadillac ford geely Mercedes toyota Рис. 82. Файл отсортирован! Пример 83. Сортировка массивов указателей на структуры с помощью функции qsort() Предыдущий пример был довольно простой. Сейчас представим, что у вас есть массив указателей на структуры. Попробуем отсортировать его функ- цией qsort(). Пример демонстрирует, как можно отсортировать данные пользовательских типов. Мы опять будем сортировать строки, только они будут "обернуты" в структуру node: struct node { char *str;
ЧАСТЬ 9. Алгоритмы поиска и сортировки Функции qsort() мы помимо всего прочего должны передать функцию сравнения. В нашем случае она будет выглядеть так: static int cmpr(const void *a, const void *b) { struct node * const *one = a; struct node * const *two = b; return strcmp((*one)->str, (*two)->str); Мы извлекаем строки из наших структур и передаем в strcmp(). Теперь по- смотрим на полный код программы (лист. 83). Листинг 83. Сортировка массива структур с помощью qsort() #include <stdio.h> #include <string.h> #include <stdlib.h> /* наша структура */ struct node { char *str; /* функция сравнения для qsort */ static int cmpr(const void *a, const void *b); int main(void) { struct node **strarray = NULL; int i = 0, count = 0; char line[1024]; while(fgets(line, 1024, stdin) != NULL) { /* добавляем 1 элемент в массив */ strarray = (struct node **)realloc(strarray, (count + 1) sizeof(struct node *)); /* выделяем память для одной структуры чstruct node4 */ strarray[count] = (struct node *)malloc(sizeof(struct node)); /* копируем данные в новый элемент (структуру) */ strarray[count]->str = strdup(line); count++;
100 примеров на Си /* до сортировки ... */ printf("До:\п"); for(i =0; i < count; i printf("[%d]->str: %s", i, strarray[i]->str); /* передаем массив структур */ qsort(strarray, count, sizeof(*strarray), cmpr); /* результат ... */ printf ("\n—\пПосле:\п"); for(i =0; i < count; i++) { printf("[%d]->str: %s", i, strarray[i]->str); printf("\n"); /* освобождаем память */ for(i = 0; i < count; i free(strarray[i]->str); free(strarray[i]); free(strarray); return 0; /* функция сравнения для qsort */ static int cmpr(const void *a, const void *b) { struct node * const *one = a; struct node * const *two = b; return strcmp((*orie)->str, (*two)->str); На рис. 83 показан результат работы программы. Программа выводит ис- ходный массив, а затем - результат сортировки.
ЧАСТЬ 9. Алгоритмы поиска и сортировки До: ~>str: ->str: ->str: ->str: ->str: ->str: «>str: ->str: 8]«>str: После: [6]->str: [2]->str: [3]->str: [4]*>str: $ cat sort.txt | ./83 ->str: ->str: •>str: ->str: Cadillac geely bentley ford nercedes audl bnw acura toyota acura audi bentley bnw cadlllac ford geely nercedes toyota $ Рис. 83. Быстрая сортировка массива структур библиотечной функцией qsort() Пример 84. Сортировка выбором Сортировка выбором (англ. selection sort) - еще один алгоритм сортировки. Алгоритм сам по себе довольно простой: 1. Находим номер минимального значения в текущем списке. 2. Производим обмен найденного значения со значением первой неотсо- ртированной позиции (обмен не нужен, если минимальный элемент уже находится на данной позиции). 3. Сортируем хвост списка, исключив из рассмотрения уже отсортирован- ные элементы. Несмотря на простоту описания алгоритма, сама программа не очень про- стая и занимает целых 145 (!) строк, см. лист. 84. Как обычно, результат вы- полнения программы приводится на рис. 84. Листинг 84. Сортировка выбором #include <stdio.h> #include <stdlib.h> #define MAX 10
100 примеров на Си struct lnode {. int data; struct lnode *next; } *head, *visit; /* добавляем новый узел в связный список */ void llist_add(struct lnode **q, int num); /* выборочная сортировка списка */ void llist_selection_sort(void); /* выводим связный список */ void llist_print(void); int main(void) { /* связный список */ struct lnode *newnode = NULL; int i = 0; /* общий счетчик */ /* добавляем в список случайные данные */ for(i = 0; i < MAX; i++) { llist_add(&newnode, (rand() % 100)); head = newnode; printf("До:\п"); llist_print(); printf ("После: \n,ff) ; llist_selection_sort (); llist_print(); return 0; } /* добавляем узел в список связного списка */ void llist_add(struct lnode **q, int num) { struct lnode *temp; temp = *q; /* если список пуст, создаем первый элемент */ if(*q == NULL) { *q = malloc(sizeof(struct lnode)); temp = *q; } else { /* переходим к последнему узлу */ while(temp->next != NULL) temp = temp->next;
ЧАСТЬ 9. Алгоритмы поиска и сортировки /* добавляем узел в конец списка */ temp->next = malloc(sizeof(struct lnode)); temp = temp->next; /* назначаем данные последнему узлу */ temp->data = num; temp->next = NULL; } /* выводим связный список */ void llist_print(void) { visit = head; /* проходимся по списку и выводим его */ while(visit != NULL) { printf("%d ", visit->data); visit = visit->next; } printf (V); /* функция сортировки выбором */ void llist_selection__sort (void) { struct lnode *a = NULL; struct lnode *b = NULL; struct lnode *c = NULL; struct lnode *d = NULL; struct lnode *tmp = NULL; a = с = head; while(a->next != NULL) { d = b = a->next; while(b != NULL) { " if (a->data > b->data) { /* соседний связанный узел списка */ if(a->next == b) { /* если а = голова */ if(a == head) { a->next = b->next; b->next = a; tmp = a; a = b; b = tmp; head = a; с = а; d = b;
100 примеров на Си b = b->next; } else { a->next = b->next; b->next = a; c->next = b; tmp = a; a = b; b = tmp; d = b; b = b->next; } } else { if(a == head) { tmp = b->next; b->next = a->next; a->next = tmp; d->next = a; tmp = a; a = b; b = tmp; d = b; b = b->next; head = a; } else { tmp = b->next; b->next = a->next; a->next = tmp; c->next = b; d->next = a; tmp = a; a = b; b = tmp; d = b; b = b->next; } else { d = b; b = b->next; с a a; a->next;
ЧАСТЬ 9. Алгоритмы поиска и сортировки $ ./84 До: 99 96 9 78 61 65 8 88 64 36 Посл«: 8 9 36 61 64 65 78 88 98 99 $ Рис. 84. Результат сортировки Пример 85. Сортировка с помощью бинарного дерева Еще один классический способ сортировки - это сортировка с помощью бинарного дерева (treesort). Этот же способ сортировки называют сорти- ровкой двоичным деревом, просто сортировкой деревом, а также treesort в англоязычной литературе. Алгоритм заключается в построении двоичного дерева поиска по ключам массива (списка) с последующей сборкой результирующего массива путем обхода узлов построенного дерева в необходимом порядке следования клю- чей. Данный алгоритм идеально подходит для сортировки данных, полу- ченных путем непосредственного чтения с потока (из файла, со стандарт- ного ввода, сокета и т.д.). Собственно, алгоритм состоит из двух частей: 1. Построение двоичного дерева. 2. Сборка результирующего массива путем обхода узлов в необходимом порядке следования ключей.
100 примеров на Си В отличие от предыдущего алгоритма, здесь простые и описание, и реализа- ция. Код программы, выполняющей сортировку стандартного ввода мето- дом двоичного дерева, приведен в лист. 85. Листинг 85. Сортировка двоичным деревом #include <stdio.h> #include <string.h> #include <stdlib.h> struct tnode { char *str; struct tnode *left; struct tnode *right; void insert(struct tnode **p, char *value); void print(struct tnode *root); int main(void) { char line[1024]; struct tnode *root; root = NULL; while((fgets(line, 1024, stdin)) !=NULL) insert(&root, line); print(root); return 0; /* вызов по ссылке */ void insert (struct tnode **p, char *value) { if(!*p) { *p = (struct tnode *)malloc(sizeof(struct tnode)); (*p)->left = (*p)->right = NULL; (*p)->str = strdup(value); return; if(strcmp(value, (*p)->str) < 0) insert(&(*p)->left, value); else insert(&(*p)->right, value);
ЧАСТЬ 9. Алгоритмы поиска и сортировки /* выводим дерево в нужно порядке */ void print(struct tnode *root) { if(root != NULL) { print(root->left) ; printf("%s", root->str); print(root->right); На этот раз я немного разнообразил наш файл с марками авто, добавив туда французских производителей. Результат сортировки показан на рис. 85. Как видите, эту программу тоже можно использовать для сортировки файла. Собственно, ради таких вот случаев данный метод сортировки и раз- рабатывался. Cadillac geely bentley ford roercedes audi b*iw acura toyota renault peugeot acura audi bentley bnw cadlllac ford geely mercedes peugeot renault toyota $ gcc 85.с -о 85 > cat sort.txt $ cat sort.txt i ./85 $1 Рис. 85. Сортировка методов двоичного дерева Пример 86. Реверс связного списка Данный пример не относится к сортировке, но поскольку мы в этой части книги часто рассматривали связные списки, то рассмотрим еще один при- мер, связанный с ними. Задача заключается в том, чтобы выполнить реверс списка. Но не просто отобразить элементы списка в обратном порядке, а именно поменять их в списке местами так, чтобы они следовали в обратном
100 примеров на Си порядке. Алгоритм довольно простой и интуитивно понятный, однако его реализация требует осторожной работы с указателями, чтобы не повредить исходный список. Реверс выполняется с помощью следующего цикла: while(a != NULL) { с = b, b = а, а = a->next; b->next = с; Полный код программы приведен в листинге 86. В этой программе мы раз- работали три вспомогательных функции: /* добавляет lnode в начало списка */ void llist_add_begin(struct lnode **n, int val); /* реверс списка */ void llist__reverse (struct lnode **n); /* отображение списка */ void llist_display(struct lnode *n) ; Первая добавляет узел в начало списка,* вторая, собственно, выполняет ре- верс списка, а третья - выводит список на экран. Листинг 86. Реверс связного списка #include <stdio.h> ♦include <stdlib.h> #define MAX 10 /* максимум 10 элементов */ struct lnode { int number; struct lnode *next; /* добавляет lnode в начало списка */ void llist_add_begin(struct lnode **n, int val); /* реверс списка */ void llist_reverse(struct lnode **n); /* отображение списка */ void llist_display(struct lnode *n); int main(void) { struct lnode *new = NULL; int i = 0; /* вставляем числа в список от 0 до 10 */ for(i = 0; i <= MAX;
ЧАСТЬ 9. Алгоритмы поиска и сортировки llist_add_begin(&new, printf("исходный список:"); llist_display(new); llist_reverse(&new); printf("список в обратном порядке:"); llist_display(new); return 0; } /* добавляет lnode в начало списка */ void llist_add_begin(struct lnode **n, struct lnode *temp = NULL; /* добавляет новый узел */ temp = malloc(sizeof(struct lnode)); temp->number = val; temp->next = *n; *n = temp; } /* реверс списка */ void llist_reverse(struct lnode **n) struct lnode *a = NULL; struct lnode *b = NULL; struct lnode *c = NULL; int val) { a = "n, b = NULL; while(a != NULL) с = b, b = a, a b->next = c; a->next; *n * b; /* отображение списка */ void llist_display(struct lnode *n) { while(n != NULL) printf(" %d", n->number), n = n->next; printf("\n"); Поскольку мы добавляем элементы в начало списка, то изначально список будет в обратном порядке: 10, 9, 8 и т.д. После того как мы выполняем ре- верс, список будет выводиться в прямом порядке.
ЧАСТЬЮ. Еще немного практики
ЧАСТЬ 10. Еще немного практики Прошлая часть была больше теоретической, чем практической. Мы рассмо- трели множество алгоритмов поиска и сортировки. Хотя были рассмотрены и некоторые практические примеры, связанные с сортировкой файлов. В этой части книги будет рассмотрен ряд сугубо практических и интересных примеров. Пример 87. Делаем свой shell Linux-пользователи знакомы с оболочкой bash. В отличие от Windows, где оболочка всегда одна (cmd), в Linux можно установить несколько оболочек (например, tcsh, zsh и др.) и любую из них. В этом примере мы напишем собственную, хотя и довольно простую оболочку. Наша оболочка будет позволять вводить команды и выполнять их. Оболоч- ку сделать достаточно просто. Нужно прочитать команду от пользователя, затем передать ее на выполнение системному вызову. В Linux мы будем ис- пользовать execlp(). Затем в цикле while мы будем ждать завершения вы- полнения введенной пользователем команды и снова отобразим приглаше- ние simpsh. Выйти из нашей оболочки можно по нажатию Ctrl + С. Обратите внимание на одну особенность: системному вызову нужно передать строку, заканчивающуюся нулем, поэтому мы присоединяем к вводу пользователя символ \0. Листинг 87. Простая оболочка #include <stdio.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <string.h> int main(void) { char command[BUFSIZ] ; int status; pid_t pid; for(;;) { printf("simpsh: ");
100 примеров на Си if(fgets(command, sizeof(command), stdin) == NULL) { printf("\n"); return 0; command[strlen(command) - 1] = y\Q'; if ((pid = fork()) == 0) execlp(command, command, 0); while(wait(&status) != pid) continue; printf("\n"); На рис. 87 показано, как введенная пользователем команда была выполнена и наша оболочка снова отобразила приглашение simpsh. sinpsh: 10.С 11.с 12.с 12.с 13 13.с 14 14.с 15 15.с 16а. с 17 17.с 18 18.с simpsh: U 19 19b 19b. с 19.с l.c 26 2Э.с 21 21. с 22 22.с 23 23.с 24 24.с 1 $ 25 25.с 26 26.с 27 27.с 28 28.с 29 29.с 2.с 30 ЗО.с 31 31.с ./87 32 32.с 33 33.с 34 34.с 35 35.с 36 36.с 37 37.с 38 38.с 39 39.с З.с 40 40.с 41 41.с 42 42.с 43 43.с 44 44.С 45 45.с 46 46.с 47 47.с 48 48.с 49 49.с 4.с so 50.С 51 51.с 52 52.с 53.с 54.с 55.с 56.с 57.с 58.с 59.с 5.с 60.с 61.с 62.с 63.с 64.с 65.с 66.с 67.с 6.с 78 78»с 71 71b 71b.с 71.с 72 72.с 73 73,с 74 74.с 75 75.с 76 76.с 77 77.с 78 78.с 79 79.с 7.с 80 80.с 81 81.с 82 82.с 83 83.с 84 84.с 85 85.с 86 86.с 87 87.с 8,С 9.с a.out client client.с CS2 rand randwhile.c server server.с sorted.txt sort.txt text.txt zneyka zneyka.c zmeyka.cpp Рис. 87. Простая оболочка в действии
ЧАСТЬ 10. Еще немного практики Пример 88. Получение информации о системе Напишем простенькую программу, выводящую информацию о системе - имя компьютера (для этого будем использовать функцию gethostname()), a также попытаемся вытащить информацию из uname() - название системы, тип архитектуры, название компьютера (посмотрим, отличается ли от того, что возвращает gethostnameQ), версию ядра (release) и тип дистрибутива. Листинг 88. Получение информации о системе #include <stdio.h> #include <unistd.h> #include <sys/utsname.h> int main(void) { char cmpname[256]; struct utsname uts; if(gethostname(cmpname, 255) -= 0) printf("gethostname : %s\n", cmpname); if(uname(&uts) ==0) { printf("uts.sysname : %s\n", printf("uts.machine : %s\n", printf("uts.nodename : %s\n", printf("uts.release : %s\n", printf("uts.version : %s\n", uts.sysname); uts.machine); uts.nodename); uts.release); uts.version); return 0; Пример 89. Пишем сообщения в системный журнал Попробуем написать syslog-версию программы Hello, World! Наша про- грамма будет выводить заветную строчку в... системный журнал. Для этого используется системный вызов syslog. Сначала нужно открыть системный журнал системным вызовом openlog(), а затем выполнить syslog(), указав уровень сообщения (в нашем случае LOGINFO) и само сообщение (см. лист. 89).
100 примеров на Си Листинг 89. Запись в системный журнал #include <stdio.h> #include <unistd.h> #include <syslog.h> int main(void) { openlog("slog", LOG_PID|LOG_CONS, LOG_USER) syslog(LOG_INFO, "Hello world ..• "); closelogO ; return 0; Откомпилируйте и запустите программу. Затем для просмотра выведенно- го ею сообщения введите команду: sudo tac /var/log/syslog I grep Hello $ ./89 $ sudo tac /var/log/syslog | grep Hello Mar 8 18:34:83 den-pc slog[1959]: Hell® world ... $ Рис. 89. Сообщение, записанное в системный журнал Степень важности сообщения задается первым параметром системного вы- зова syslog() и может быть следующим: • LOG EMERG - система остановлена;
ЧАСТЬ 10. Еще немного практики LOGALERT - требуется немедленное вмешательство; LOGCRIT - критические условия; LOG_ERR - ошибки; LOGWARNING - предупреждения; LOG_NOTICE - важные рабочие условия; LOGINFO - информационные сообщения; LOGDEBUG - сообщения об отладке. Пример 90. Обработка полученного сигнала Напишем программу, обрабатывающую сигнал. В части 8 шла речь об об- работке сигналов, но конкретного примера не было. Попробуем обработать сигнал. Для этого мы используем системный вызов signal() и устанавлива- ем обработчик сигнала - функцию ex_program. Тип сигнала SIGINT - пре- рывание программы с помощью Ctrl + С. Результат выполнения программы и перехвата сигнала приведен на рис. 90. Листинг 90. Обработка сигнала #include <stdio.h> #include <unistd.h> /* для sleep(1) */ #include <signal.h> void ex_program(int sig); int main(void) { (void) signal(SIGINT, ex_program); while (1) printf("Mbi спим .. ZZZzzzz ...An"), sleep (1); return 0; void ex_program(int sig) { printf("Просыпайся ... !!! - Поступил сигнал: %d sig) ; (void) signal(SIGINT, SIG_DFL);
100 примеров на Си Мы спим Мы спим Мы спим Мы спим с Мы спим Мы спим Мы спим Мы спим Мы спим $ гИгггг Пггггг гИгггг гИгггг Шгггг АСПросыпайся ... !! ТП-гггг ггггггг ггггггг ZZZzzzz /90 Поступил сигнал; 2 ... J! $1 Рис. 90. Пример обработки сигнала Пример 91. Преобразование времени в формате UTC в строку и обратно Сейчас мы напишем довольно серьезную программу, которая будет преоб- разовать целое значение UTC в удобный для чтения формат (строку). Так- же программа будет выполнять обратное преобразование - ойа будет пре- образовывать строку с информацией о времени (формат строки Моп Маг 6 10:00:04 2017). Это не просто демо-программа. Это полноценная программа, которая при- нимает и обрабатывает аргументы, выводит версию, делает проверку ввода опций. Код программы тщательно закомментирован, поэтому отдельных комментариев не будет. Внимательно прочитайте его, чтобы понять, как ра- ботает программа (листинг 91). Листинг 91. Преобразование времени #include <stdio.h> #include <getopt.h> #include <string.h> #include <stdlib.h> #include <unistd.h>
/* strptimeO */ #define USE_XOPEN #include <time.h> ЧАСТЬ 10. Еще немного практики #define PACKAGE "utconv" #define VERSION "0.0.1" void print_help(int exval); /* выводит справку по использованию программы */ int get_utc(char *str); /* извлекаем UTC-время из строки */ char *convert__utc(int uval); /* конвертирование UTC в строку */ int main(int argc, char *argv[]) { int opt = 0; if(argc == 1) print_help(0); while((opt = getopt(argc, argv, "hvu:s:")) != -1) { switch(opt) { case fhf: /* выводим справку и выходим */ print_help(0); break; case fvf: /* выводим версию и выходим */ fprintf(stdout, "%s %s\n", PACKAGE, VERSION); exit(0); break; case yu': /* конвертируем INT utc в строку */ printf("%s\n", convert_utc(atoi(optarg))); break; case ysr : /* convert STR to INT utc time value */ /* проверка корректности пользовательского ввода ... */ if(strlen(optarg) < 23 || strlen(optarg) > 24) { fprintf(stderr, "%s: Некорректный формат ? N%s'\n", PACKAGE, optarg); fprintf(stderr, "%s: Строка должна быть в формате: чМоп Маг 6 10:00:04 2О17'\п\п", PACKAGE); return 1; } printf("%d\n", get_utc(optarg)); break; case Л?':
100 примеров на Си fprintf(stderr, "%s: Ошибка - нет такой опции N%c'\n", PACKAGE, optopt); print_help(1); case л:' : fprintf(stderr, "%s: Ошибка - опция 4%c' требует аргумент\п", PACKAGE, optopt); print_help(l); } /* switch */ } /* while */ return 0; /* конвертируем INT UTC в строку */ char *convert_utc(int uval) { t ime_t t imeva1; char *tstr = NULL; timeval = uval; tstr = strdup(ctime(&timeval)); /* удаляем лишние пробельные символы */ tstr[strlen(tstr) - 1] = '\0f; return tstr; free(tstr); int get_utc(char *str) { struct tm timestruct; int retval = 0; /* Sun Sep 7 00:00:04 2003 */ if(strptime(str, "%a %b %d %T %Ey", &timestruct) == 0) return -1; else { timestruct.tm_year -= 1900; timestruct.tm_isdst = -1; retval = mktime(&timestruct); return retval; /* выводим справку */ void print_help(int exval) {
ЧАСТЬ 10. Еще немного практики printf ("%s,%s конвертирует время в формате UTC в строку и обратно\п", PACKAGE, VERSION); printf("Использование: %s [-h] [-v] [-s STR] [-u INT]\n\n", PACKAGE); printf(" -h отображение этой инормации\п"); printf(" -v информация о версии программы\п\п"); printf(" -u INT конвертирует UTC-время (целое число) в удобный для чтения формат\п"); printf (" -s STR конвертируем строку с временем в формат UTC\n"); printf (" формат строки должен быть:\п"); printf(" . Моп Маг 6 10:00:04 2017\п\п"); exit(exval); $ ./91 utconv,0.0.1 конвертирует время в формате UTC в строку и обратно Использование; utconv [-h] [-v] [-s STR] [~u INT] -h отображение этой инормации -v информация о версии программы -и INT конвертирует UTC-время (целое число) в удобный &пя чтения формат -s SIR конвертируем строку с временем в формат UTC формат строки должен быть: Моп Маг 6 10:08:34 2617 $ ./91 -и 129092323 Sun Feb 3 85:58:43 1974 l Рис. 91. Преобразование времени Пример 92. Слияние двух файлов Напишем еще одну полезную утилиту, объединяющую два файла. Работает она очень просто: в цикле while читает содержимое первого файла и запи-
100 примеров на Си сывает в конец второго файла. Ничего сложного, тем не менее, ее можно использовать на практике (лист. 92). Листинг 92. Объединение файлов #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(int argc, char **argv) { char buf[1024]; int n, in, out; if(argc != 3) { fprintf(stderr, "Использование: append SOURCE TARGET\n") return 0; Яблоко Груша Банан Слива Картошка Помидоры Огурцы Картошка Помидоры Огурцы Яблоко Груша Банан Слива cat l.txt cat 2.txt $ ./92 l.txt 2.txt $ cat 2.txt Рис. 92. Результат работы программы
ЧАСТЬ 10. Еще немного практики /* открываем первый файл для чтения */ in = open(argv[l], O_RDONLY); /* открываем второй файл для записи */ out = open(argv[2], O_WRONLY|O_APPEND); /* копируем данные из первого файла во второй */ while((n = read(in, buf, sizeof (buf))) > 0) write(out, buf, n); return 0; Посмотрим, как она работает. Сначала я вывожу содержимое файлов l.txt и 2.txt, затем запускаю программу, передав ей названия файлов в качестве параметров. После этого вывожу содержимого второго файла - как видите, фрукты присоединились к овощам. Пример 93. Получение информации о файле Используя функцию stat(), можно получить информацию о файле, такую как количество жестких ссылок, тип устройства, общий размер и т.д. Рас- смотрим пример такой программы (лист. 93). Листинг 93. Получение информации о файле ♦include <time.h> ♦include <stdio.h> ♦include <stdlib.h> ♦include <sys/stat.h> ♦include <sys/types.h> int main(int argc, char *argv[]) { struct stat file_stats; if(argc != 2) fprintf (stderr, "Usage: f stat FILE. .. \n") , exit (EXIT_ FAILURE); if ( (stat (argv[l], &file_stats) ) == -1) { perror("fstat"); return 1; printf("MMH файла: %s\n", argv[l]); printf(" устройство: %lld\n",file_stats . st_dev) ;
100 примеров на Си file_stats . st_ino) ; file_stats. st_nlink) , file_stats . st_uid) ; %d\n", file_stats . st_gid) ; (если это устройство) : %lld\n",file_ printf(" инод: %ld\n", printf(" число жестких ссылок: %d\n"/ printf (" UID владельца: %d\n", printf (" GID владельца: printf(" тип устройства stats.st_rdev); printf(" общий размер в байтах: %ld\n", printf (" размер блока файловой системы, stats.st_blksize); printf(" число выделенных блоков: %ld\n", blocks); printf(" время последнего доступа: %ld : %s", stats . st_atime, ctime (&file_stats. st_atime) ) ; printf(" время последнего изменения: %ld : %s" st_mtime, ctime (&file_stats . stjntime) ) ; printf (" время создания: %ld : %s", file ctime, ctime(&file stats.st ctime)); file_stats ,st_size) ; байты: %ld\n", file_ file stats.st file file stats. stats.st return 0; $ ./93 93.с имя файла: 93,с устройство: 2849 инод: 2119133 число жестких ссылок: 1 UID владельца: 1006 GID владельца: 1000 тип устройства (если это устройство): 0 общий размер в байтах: 1562 размер блока файловой системы, байты: 4096 число выделенных блоков: 8 время последнего доступа: 1488994443 : Wed Mar 8 19:34:03 2017 время последнего изменения: 1488994448 : Wed Наг 8 19:34:08 2017 время создания: 1488994440 : Wed Mar 8 19:34:80 2817 $ I Рис. 93. Информация о файле
ЧАСТЬ 10. Еще немного практики Пример 94. Скрываем вводимый пользователем пароль Наверняка вы видели, что дрограммы, требующие пароль, скрывают вводи- мые пользователем символы, чтобы никто не мог их подсмотреть. Давайте напишем такую программу Отменить показ символов при вводе можно с помощью вызова tcsetattrQ. Листинг 94. Скрываем вводимые пользователем символы #include <stdio.h> #include <termios.h> #define PASSMAX 8 int main(void) { struct termios defrsett, newrsett; char password[PASSMAX + 1]; tcgetattr (fileno (stdin) , &def rsett) ; newrsett = defrsett; newrsett. c__lflag &= -ECHO; / Введите пароль; введенный пароль 123456 Рис. 94. Сокрытие введенных символов
ЮОпримеровнаСи printf("Введите пароль: "); if (tcsetattr (fileno (stdin) , TCSAFLUSH, &newrsett) != 0) fprintf(stderr, "He установлены атрибуты\п"); else { fgets(password, PASSMAX, stdin); tcsetattr (fileno (stdin) , TCSANOW, &def rsett) ; fprintf(stdout, "\пВведенный пароль %s", password); printf("\n") ; return 0; Пример 95. Сколько времени работает система? Показываем uptime Попробуем узнать, сколько времени работает система. Информация об этом хранится в файле /proc/uptime. Все, что нам нужно - это вывести со- держимое этого файла (лист. 95). Листинг 95. Сколько времени работает система #include <stdio.h> #include <string.h> ♦include <stdlib.h> int main(void) { FILE * uptimefile; char uptime_chr[28] ; long uptime = 0; if ( (uptimefile = fopen ("/proc/uptime", "r")) == NULL) perror("supt"), exit(EXIT_FAILURE); fgets (uptime_chr, 12, uptimefile) ; f close (uptimefile) ; uptime = strtol(uptime_chr, NULL, 10); printf("Система работает %ld секунд, %ld часов\п", uptime, uptime / 3600); exit(EXIT_SUCCESS);
ЧАСТЬ 10. Еще немного практики $ uptime 19:55:49 up 1:29, 1 user, load average: 8,80, 0,00, 0,00 % ./95 Система работает 5389 секунд $ Рис. 95. Время работы системы Пример 96. Удаляем HTML-разметку Напишем небольшую программу, удаляющую HTML-разметку с текста, по- ступаемого на стандартный ввод. Листинг 96. Удаление HTML-тегов из текста #include <stdio.h> #include <string.h> #define IN 0 # define OUT 1 int main(void) { int с = 0; int state = OUT; int tstate = OUT; char tagbuff[2048]; char *ptrl = NULL; ptrl = tagbuff; while ((c = getcharO) != EOF) {
100 примеров на Си 0) /* копируем тег в tagbuff */ if(с == *<' || с == *&') state = IN; if(state == IN) *ptrl++ = c; if(c == f>f || с == f;f) { state = OUT; *ptrl++ = Л0' ; /* ищем теги, ess-стили, javascript */ if(strstr(tagbuff, "<s") != 0 || strstr(tagbuff, "<S") != tstate = IN; if (strstr(tagbuff, "</") != 0) tstate = OUT; /* ? V if (strstr(tagbuff, "nbsp") != 0 I I strstr(tagbuff, "NBSP") != 0) printf(" "); ptrl = tagbuff; } /* конец if */ /* не в теге, выводим символ */ if (state == OUT && tstate == OUT && с != Л>' && с != y;') printf("%c", c); $ cat l.html <head> <title>Test<7title> </head> <body>Body </body> </htnl> Test Body cat l.htrtl | ./96 Рис. 96. Очистка HTML
ЧАСТЬ 10. Еще немного практики return 0; Результат работы приведен на рис. 96. Сначала я создал файл l.html и вы- вел его на экран, затем передал содержимого этого файла нашей программе. Программа вывела текст, не находящийся внутри HTML/JS/CSS-тегов. Пример 97. Выводим IP-адреса, e-mail и URL, найденные в тексте Данный пример демонстрирует работу с регулярными выражениями в С. Мы используем регулярные выражения для поиска IP-адресов, e-mail и URL. Функционал программы разбит на три функции: void print_ipadd(FILE void print_email(FILE void print_url(FILE *fp) *fp); *fp); Первая выводит IP-адреса, вторая - электронные адреса, третья - URL. В программе мы определяем также три константы, содержащие регулярные выражения для поиска IP-адресов, e-mail и URL: #define IPEXPR "([0-9] {1,3})\\. ([0-9] {1, 3}) \\ . ([0-9] {1,3})\\. ([0-9]{1,3})" #define EMEXPR " . *@ . *\\ . ( [a-zA-Z] {1, 3}) $" #define UREXPR " (href | src) =" Остальное - уже дело техники, нужно вызвать regexec(), чтобы проверить, соответствует ли строка регулярному выражению. Недостаток этой про- граммы в том, что она считает URL-адресами все, что следует после href в теге <а>. В качестве домашнего задания вам будет следующее: изменить программу так, чтобы она реагировала на URL в тексте, а не в ссылках. Под- сказка: для этого нужно изменить регулярное выражение. Листинг 97. Выводим IP-адреса, e-mail и URL, найденные в тексте #include <stdio.h> #include <regex.h> #include <ctype.h> #include <getopt.h> #include <stdlib.h>
100 примеров на Си #include <string.h> ♦include <locale.h> #include <sys/types.h> #define PACKAGE "miep" #define VERSION "1.0.0" #define IPEXPR "([0-9] {l/3})\\. ([0-9] {1,3})\\. ([0-9] {1,3})\\. ([0-9]{l,3})" #define EMEXPR " . *@ . *\\ . ( [a-zA-Z] {1, 3}) $" #define UREXPR " (href | src) =" void print_ipadd(FILE *fp) ; void print_email(FILE *fp); void print_url(FILE *fp); void print_help(int exval); int main(int argc, char *argv[]) { FILE *fp = stdin; int opt = 0; int em_set = 0; int ip_set = 0; int ur_set = 0; setlocale(LC_ALL, ""); while((opt =■ getopt(argc, argv, "hvieu")) != -1) { switch(opt) { case лп': print_help(0); break; case V : fprintf(stdout, "%s %s\n", PACKAGE, VERSION); exit(0); break; case yi' : ip_set = 1; break; case ye' : em_set = 1; break; case *u': ur_set = 1; break; case yT :
ЧАСТЬ 10. Еще немного практики fprintf(stderr, "%s: Нет такой опции N%c'\n\n", PACKAGE, optopt); print_help(1); break; } /* switch */ } /* while */ if(argc == 1 || (ip_set == 0 && em_set == 0 && ur_set == 0)) print_help(1); if((optind - argc) == 0) { if(em_set == 1) print_email(fp); else if(ip_set == 1) print_ipadd(fp); else print_url(fp); } else { /* проходимся по оставшимся аргументам */ for(; optind < argc; optind++) { if(freopen(argv[optind], "r", fp) == NULL) { perror(argv[optind]); continue; if(em_set == 1) print_email(fp); else if(ip_set == 1) print_ipadd(fp); else print_url(fp); } /* for */ } /* else */ fclose(fp); return 0; void print__ipadd(FILE *fp) { char line[1024]; char *address = NULL; char delim[] = ",:;ч/\"+-_(){}[]<>*&A%$#@!?-/|\\= \t\r\n"; int retval = 0; regex__t re; if(regcomp(&ref IPEXPR, REG_EXTENDED) != 0)
100 примеров на Си return; while ((fgetsdine, 1024, fp) ) !=NULL) { if (strchrdine, л.') == NULL) continue; address = strtok(line, delim); while(address != NULL) { if(strlen(address) <= 15) if((retval = regexec(&re, address, 0, NULL, 0)) == 0) printf("%s\n", address); address = strtok(NULL, delim); } /* while */ } /* while */ } /* print_ipadd */ void print_email(FILE *fp) { char address[256]; char line[1024]; char *ptrl = NULL; char *ptr2 = NULL; int retval = 0; regex_t re; if(regcomp(&re, EMEXPR, REG_EXTENDED) != 0) return; while ( (fgetsdine, 1024, fp) ) != NULL) { if (strchrdine, л@') == NULL && strchrdine, л.') == NULL) continue; for(ptrl = line, ptr2 = address; *ptrl; ptrl++) { if(isalpha(*ptrl) II isdigit(*ptrl) II strchr(".-_@", *ptrl) != NULL) *ptr2++ = *ptrl; else { *ptr2 = Л0'; if(strlen(address) >= 6 && strchr(address, л@') != NULL && strchr(address, л.') != NULL) if((retval = regexec(&re, address, 0, NULL, 0)) == 0) printf("%s\n", address); ptr2 = address; } /* else */
ЧАСТЬ 10. Еще немного практики } /* for */ } /* while */ } /* print_email */ void print_url(FILE *fp) { char line[1024]; char delim[] = "<> \t\n"; char *url = NULL; int retval = 0; regex_t re; if(regcomp(&re, UREXPR, REG_ICASE|REG_EXTENDED) != 0) return; while((fgets(line, 1024, fp)) != NULL) { url = strtok(line, delim); while(url != NULL) { if((retval = regexec(&re, url, 0, NULL, 0)) == 0) printf("%s\n", url); url = strtok(NULL, delim); } /* while */ } /* while */ } /* print_url */ void print_help(int exval) { printf ("%s,%s выводим e-mail, URL или IP-адреса, найденные в тексте\п", PACKAGE, VERSION); printf ("%s [-h] [-v] [-i] [-e] [-u] ФАЙЛ...\n\n", PACKAGE); printf(" -h printf(" -v printf(" -i printf(" -e printf(" -u exit(exval); отображает эту справку\п"); выводит номер версии\п\п"); выводит 1Р-адреса\п"); выводит e-mail адреса\п"); выводит URL\n\n")/ Результат работы программы приведен на рис. 97. Сначала я вывожу файл sample.html, чтобы вы видели, что внутри, а затем запускаю программу, что- бы вывести IP-адреса, электронные адреса и URL.
100 примеров на Си $ clear $ cat sample,html den@example.com <a href~mallto:user@example.com>Admtn</a> IP: 192Л68.1Л08 URL http://www.exaple.con Link: <a href«http://example.com>$ite</a> : ■■$ ./97 п!ер,1.б.Э выводим e-mail» URL или IP-адреса, найденные в тексте miep [-h] [-v] [-1] [-в] [-и] ФАЙЛ,.. -h отображает эту справку -V выводит номер версии -1 выводит IP-адреса -е выводит e-mail адреса -и выводит URL $ ./97 -1 sample.html 192.168.1.180 $ ./97 -е sample.html den@example.com user@example.com $ ./97 -и sample.html href=mailto:usergexample * com href=http://example.com $ I Рис. 97. Результат работы программы Пример 98. Выводим текст в картинку. Компиляция программы с использованием библиотеки gd Библиотека gd позволяет создавать различные манипуляции с графиче- скими изображениями, в том числе рисовать различные примитивы, пре- образовывать форматы и т.д. Данный пример демонстрирует, как собрать программу с поддержкой этой библиотеки. Сам же пример будет довольно примитивным - просто будет выводить текст в графический файл. Чтобы у вас стал доступен заголовочный файл gd.h, нужно установить па- кет Iibgd2-noxpm-dev: sudo apt-get intstall Iibgd2-noxpm-dev Саму программу нужно откомпилировать с поддержкой библиотеки gd: дсс 98.с -о 98 -lgd
ЧАСТЬ 10. Еще немного практики Листинг 98. Выводим текст в изображение #include <stdio.h> tinclude <gd.h> tinclude <gdfontg.h> int main(int argc, char *argv[]) { gdlmagePtr img; FILE *fp = {0}; int width, white, black; width = white = black 0; if(argc ! = 3) { fprintf(stderr, "Использование: pngtxt image.png *text'\n"); return 1; fp = fopen(argv[l], "wb"); if (fp == NULL) { fprintf(stderr, "Ошибка - fopen(%s)\n", argv[l]); return 1; width = strlen(argv[2]); img = gdlmageCreate(width * 10, 20); white = gdlmageColorAllocate(img, 255, 255, 255); black = gdlmageColorAllocate(img, 0, 0, 0); gdlmageString(img, gdFontGiant, 2, 1, argv[2], black); gdlmagePng(img, fp); fclose(fp); gdlmageDestroy(img); return 0;
100 примеров на См Рис. 98. Результат работы программы Пример 99. Создание временного файла В процессе обработки различных данных часто появляется необходимость создать временный файл. Сначала мы получаем имя временного файла с помощью tmpnam() - оно может понадобиться в некоторых случаях. Далее функцией tmpfile() создаем временный файл. После этого мы можем работать с ним, как с обычным файлом. Файл открывается в режиме wb+ (запись, бинарный режим). Листинг 99. Создание временного файла #include <stdio.h> int main(void) { char tmpname[L_tmpnam]; char ^filename = NULL; FILE *tmpfp;
ЧАСТЬ 10. Еще немного практики filename = tmpnam (tmpname) ; printf ("Временный файл: %s\n", filename); tmpfp = tmpfile(); if (tmpfp) printf("Открытие файла: ОК\п"); else perror ("tmpfile") ; return 0; $ ./99 Временный файл: /trcp/fileFtZX6u Открытие файла: OK $ Рис. 99. Вывод программы
Пример 100. Открываем лоток DVD В завершении этой части и вообще этой книги рассмотрим программу, вы- полняющую некоторые операции над приводом DVD. Функция cdr_close() закрывает лоток DVD, cdr_eject() - открывает лоток, a cdlock() - блокирует лоток, чтобы никто не смог извлечь диск из привода, например, пока вы не прочитаете какой-то файл с диска. Листинг 100. Операции с DVD #include <stdio.h> ♦include <fcntl.h> ♦include <stdlib.h> ♦include <unistd.h> ♦include <sys/ioctl.h> ♦include <linux/cdrom.h> /* // закрыть */ int cdr__close (char *dev) { int fd; if((fd = open(dev, O_RDONLY|O_NONBLOCK)) return -1; else if(ioctl(fd, CDROMCLOSETRAY) == -1) return -1; else close(fd); return 0; == -1) /* // извлечение */ int cdr_eject(char *dev) { int fd; if((fd = open(dev, O_RDONLY|O_NONBLOCK)) return -1; else if (ioctl(fd, CDROMEJECT) == -1) return -1; else close(fd); -1)
return 0; /* // блокировка // - if lock == 1, lock // - if lock == 0, unlock */ int cdlock(char *dev, int lock) { int fd; if((fd = open(dev, O_RDONLY|O_NONBLOCK)) == -1) return -1; else if (ioctKfd, CDROM_LOCKDOOR, lock) == -1) return -1; else close(fd); return 0; Скриншота, увы, не будет, поскольку программа ничего не выводит. Но уж поверьте - все функции работают.
Вместо заключения Мы рассмотрели целых 100 различных примеров. Некоторые были сугу- бо теоретическими, например, пузырьковая сортировка, некоторые имеют практическую ценность. В мире множество задач и множество программ. Надеюсь, что эта книга поможет решить некоторые из поставленных перед вами задач или же направить вас в нужное русло при их решении. Листинги всех программ, разработанных в этой книге, доступны по адресу http://nit.com.ru
Издательство "Наука и Техника11 Книги по компьютерным технологиям, медицине, радиоэлектронике Уважаемые читатели! Книги издательства "Наука и Техника" вы можете: > заказать в нашем интернет-магазине WWW,nit,COm.ru (более 100 пунктов выдачи на территории РФ) > приобрести в магазине издательства по адресу: Санкт-Петербург, пр. Обуховской обороны, д. 107 (ежедневно с 10.00 до 18.30), тел. (8Ш 412-70-26 > приобрести в Москве: "Новый книжный" Сеть магазинов ТД "БИБЛИО-ГЛОБУС" Московский Дом Книги, "ДК на Новом Арбате" Московский Дом Книги, "Дом технической книги" Московский Дом Книги, "Дом медицинской книги" Дом книги "Молодая гвардия" > приобрести в Санкт-Петербурге: Санкт-Петербургский Дом Книги Буквоед. Сеть магазинов тел. (495) 937-85-81, (499) 177-22-11 ул. Мясницкая, д. 6/3, стр. 1, ст. М "Лубянка", тел. (495) 781-19-00,624-46-80 ул.Новый Арбат, 8, ст. М "Арбатская", тел. (495) 789-35-91 Ленинский пр., д.40, ст. М "Ленинский пр.", тел. (499) 137-60-19 Комсомольский пр.,д. 25, ст. М"Фрунзенская" тел. (499) 245-39-27 ул. Б. Полянка, д. 28, стр. 1, ст. М "Полянка", тел. (499) 238-50-01 Невский пр. 28, тел. (812) 448-23-57 тел. (812) 601-0-601 > приобрести в регионах России: г. Воронеж, "Амиталь" Сеть магазинов тел. (473) 224-24-90 г. Екатеринбург, "Дом книги" Сеть магазинов тел. (343) 289-40-45 г. Нижний Новгород, "Дом книги" Сеть магазинов тел. (831) 246-22-92 г. Владивосток, "Дом книги" Сеть магазинов тел. (423) 263-10-54 г. Иркутск, "Продалить" Сеть магазинов тел. (395) 298-88-82 г. Омск, "Техническая книга" ул. Пушкина, д. 101 тел. (381) 230-13-64 fWidbL
Группа подготовки издания: Зав. редакцией компьютерной литературы: М. В. Финков Редактор: Е. В. Финков Корректор: А. В. Громова ООО "Наука и Техника" Лицензия №000350 от 23 декабря 1999 года. 198097, г. Санкт-Петербург, ул. Маршала Говорова, д. 29. Подписано в печать 15.03.2017. Формат 70x100 1/16. Бумага газетная. Печать офсетная. Объем 16 п. л. Тираж 1500. Заказ 2880. Отпечатано с готовых файлов заказчика в АО "Первая Образцовая типография" филиал "УЛЬЯНОВСКИЙ ДОМ ПЕЧАТИ" 432980, г. Ульяновск, ул. Гончарова, 14.