
- •1.1 Перші кроки
- •1.2 Змінні й арифметичні вирази
- •1.3 Твердження for
- •1.4 Символічні константи
- •1.5 Ввід і вивід знаків
- •1.5.1 Копіювання файла
- •1.5.2 Відлік символів
- •1.5.3 Відлік рядків
- •1.6 Масиви
- •1.7 Функції
- •1.8 Аргументи - виклик за значенням
- •1.9 Символьні масиви
- •1.10 Зовнішні змінні й область дії
- •2.1 Назви змінних
- •2.2 Типи даних і розміри
- •2.3 Константи
- •2.4 Оголошення
- •2.5 Арифметичні операції
- •2.6 Реляційні та логічні оператори
- •2.7 Перетворення типів
- •2.8 Оператори приросту та спаду
- •2.9 Розрядні оператори
- •2.10 Оператори та вирази присвоєння
- •2.11 Вирази умов
- •2.12 Пріоритет і послідовність обчислення
- •3.1 Вирази та блоки
- •3.3 Else if
- •3.4 Switch
- •3.5 Цикли while та for
- •3.6 Цикли do-while
- •3.7 Break і continue
- •3.8 Goto та мітки
- •4.1 Основні знання про функції
- •4.2 Функції, які не повертають цілих
- •4.3 Зовнішні змінні
- •4.4 Правила області дії
- •4.5 Файли заголовка
- •4.6 Статичні змінні
- •4.7 Регістрові змінні
- •4.8 Структура блоків
- •4.10 Рекурсія
- •4.11 Препроцесор c
- •4.11.1 Включення файлів
- •4.11.2 Заміна макросів
- •4.11.3 Обумовлене включення файлів
- •5.1 Покажчики й адреси
- •5.2 Покажчики й аргументи функцій
- •5.3 Покажчики та масиви
- •5.4 Арифметика адрес
- •5.5 Покажчики на символи та функції
- •5.6 Масив покажчиків; покажчики на покажчики
- •5.7 Багатовимірні масиви
- •5.8 Ініціалізація масиву покажчиків
- •5.9 Покажчики в порівнянні з багатовимірними масивами
- •5.10 Аргументи командного рядка
- •5.11 Покажчики на функції
- •5.12 Складні оголошення
- •6.1 Основні поняття про структури
- •6.2 Структури та функції
- •6.3 Масиви структур
- •6.4 Покажчики на структури
- •6.5 Структури зі зворотнім звертанням
- •6.6 Пошук по таблиці
- •6.7 Typedef
- •6.8 Сполуки
- •6.9 Розрядні поля
- •7.1 Стандартний ввід і вивід
- •7.2 Форматований вивід - printf
- •7.3 Списки аргументів довільної довжини
- •7.4 Форматований ввід - scanf
- •7.5 Доступ до файлів
- •7.6 Обробка помилок - stderr і exit
- •7.7 Ввід і вивід рядків
- •7.8 Додаткові функції
- •7.8.1 Операції з ланцюжками
- •7.8.2 Перевірка і перетворення класів символів
- •7.8.3 Ungetc
- •7.8.4 Виконання команд
- •7.8.5 Керування пам'яттю
- •7.8.6 Математичні функції
- •7.8.7 Генератор випадкових чисел
- •8.1 Дескриптори файлів
- •8.2 Низькорівневий ввід/вивід - read і write
- •8.3 Open, creat, close, unlink
- •8.4 Довільний доступ - lseek
- •8.5 Приклад: втілення fopen і getc
- •8.6 Приклад - перелік вмісту каталогів
- •8.7 Приклад - розподільник пам'яті
4.10 Рекурсія
Функції у C можуть використовуватись рекурсивно; тобто, функція може викликати сама себе безпосередньо або опосередково. Розглянемо вивід числа як символьний ланцюжок. Як ми вказали раніше, цифри генеруються у неправильній послідовності: молодші числа доступні раніше за старші числа, хоча їх потрібно виводити навпаки.
Існує два способи розв'язання цієї проблеми. Одним буде збереження цифр у масив під час їхнього генерування, потім видрукувати їх у зворотній послідовності, як ми це здійснили з itoaу Розділі 3.6. Альтернативою є рекурсивне вирішення, у якому printd спочатку викликає себе щоби впоратись з початковими цифрами, після чого виводить кінцеві цифри. Майте на увазі, що ця версія може зазнати невдачі на найбільшому від'ємному числі.
#include <stdio.h>
/* printd: вивести n як десяткове */
void printd(int n)
{
if (n < 0) {
putchar('-');
n = -n;
}
if (n / 10)
printd(n / 10);
putchar(n % 10 + '0');
}
Під час виклику функцією самої себе рекурсивно, кожне звернення отримує свіжий набір усіх автоматичних змінних, незалежних від попередьного набору. Таким чином, у printd(123), перша printd отримує аргумент n = 123. Вона передає 12 другому printd, яка в свою чергу передає 1 третьому. printd третього рівня виводить 1 після чого повертається до printdдругого рівня, яка виводить 2, потім повертається до першого рівня. Він виводить 3 і завершується.
Інший хороший приклад рекурсії, це quicksort, алгоритм сортування, розроблений C.A.R. Hoare у 1962 році. Маючи масив, вибирається один елемент і решта розбиваєтся на дві групи — ті, що менші за вибраний елемент і ті, що більше або дорівнюють йому. Той самий процес потім застосовано рекурсивно до цих двох груп. Коли підгрупа залишилась з мешне ніж двома елементами, вона не потребує жодного сортування; це зупиняє рекурсію.
Наша версія quicksort не являється найшвидшою з можливих, але це одна з найпростіших. Ми використовуємо середній елемент кожної частини масиву для подальшого поділу.
/* qsort: сортує v[left]...v[right] у зростаючій послідовності */
void qsort(int v[], int left, int right)
{
int i, last;
void swap(int v[], int i, int j);
if (left >= right) /* нічого не робити, якщо масив */
return; /* містить менше ніж два елементи */
swap(v, left, (left + right)/2); /* перемістити елемент поділу */
last = left; /* до v[0] */
for (i = left + 1; i <= right; i++) /* поділити */
if (v[i] < v[left])
swap(v, ++last, i);
swap(v, left, last); /* відновити елемент поділу */
qsort(v, left, last-1);
qsort(v, last+1, right);
}
Ми перемістили операцію переставляння у окрему функцію swap, оскільки вона відбувається три рази у qsort.
/* swap: поміняти місцями v[i] і v[j] */
void swap(int v[], int i, int j)
{
int temp;
temp = v[i];
v[i] = v[j];
v[j] = temp;
}
Стандартна бібліотека включає власну версію qsort, спроможну сортувати об'єкти будь-якого типу. Рекурсія може займати місце, оскільки десь потрібно втримувати стек значень для обробки. Також вона не є швидшою. Але рекурсивний код компактніший і часто набагато простіший у написанні і розумінні, чим не-рекурсивний еквівалент. Рекурсія особливо зручна для рекурсивно-визначених даних на зразок деревовидних структур; ми побачимо гарний приклад цього у Розділі 6.6.
Вправа 4-12. Пристосуйте ідеї printd для написання рекурсивної версії itoa; тобто перетворіть ціле у ланцюжок, шляхом виклику рекурсивної функції.
Вправа 4-13. Напишіть рекурсивну версію функції reverse(s), яка обертає ланцюжок s на місці.