- •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 Приклад - розподільник пам'яті
3.1 Вирази та блоки
Вираз на зразок x = 0 або i++ або printf(...) стають твердженням, якщо за ними слідує крапка з комою, як от
x = 0;
i++;
printf(...);
У C, крапка з комою позначає закінченням твердження, а не розділювач, як це прийнято в таких мовах як Pascal.
Фігурні дужки { та } використовуються для згруповування оголошень і тверджень разом ускладне твердження, або блок, тож вони синтаксично еквівалентні єдиному твердженню. Яскравим прикладом цього являються фігурні дужки, що оточують твердження всередині функцій, так само як дужки після if, else, while або for. (Змінні може бути оголошено всередині будь-якого блока; ми обговоримо це в Розділі 4). Крапка з комою не потрібні після правої фігурної дужки, що завершує блок.
3.2 If-else
Вирази if-else застосовуються для вираження ймовірних рішень. Формально, синтаксис наступний:
if (вираз)
твердження1
else
твердження2
де else-частина не є обов'язковою. Вираз обчислюється; якщо він істинний (тобто вираз не має нульового значення), буде виконано твердження1. Якщо хибний (вираз має нульове значення) і наявна else-частина, тоді виконується твердження2, натомість.
Оскільки if просто перевіряє числове значення виразу, це дозволяє використовувати певні скорочення в кодуванні. Найочевиднішим є написання
if (вираз)
замість
if (вираз != 0)
Іноді це виглядає природньо і зрозуміло, а інколи занадто зашифровано.
Оскільки else-частина у виразі if-else не обов'язкова, то може виникнути неоднозначність, коли якийсь else пропущено у гніздованій послідовності виразів if. Це вирішується шляхом пов'язування else із найближчим попереднім if, що не має відповідного else. Так, наприклад, у
if (n > 0)
if (a > b)
z = a;
else
z = b;
else відповідає if, що знаходиться всередині, що ми також підкреслили відступами. Якщо це не те, чого ви хотіли, тоді потрібно використати фігурні дужки, щоб змусити до відповідної приналежності:
if (n > 0) {
if (a > b)
z = a;
}
else
z = b;
Неоднозначність особливо небезпечна в ситуаціях як наступна:
if (n > 0)
for (i = 0; i < n; i++)
if (s[i] > 0) {
printf("...");
return i;
}
else /* ПОМИЛКА */
printf("error -- n is negative\n");
Не зважаючи на те, що відступи ніби недвозначно вказують на те, що саме ви маєте на увазі, компілятор не збагне цього і пов'яже else з if всередині. Таку помилку буває дуже важко знайти пізніше, тому у випадку гніздованих if краще вживати фігурні дужки.
До речі, зверніть увагу на крапки з комою після z = a
if (a > b)
z = a;
else
z = b;
Вони там тому, що граматично за if слідує твердження, а стверджувальні вирази на зразок «a = z;» завжди мають закінчуватися крапкою з комою.
3.3 Else if
Конструкція
if (вираз)
твердження
else if (вираз)
твердження
else if (вираз)
твердження
else if (вираз)
твердження
else
твердження
зустрічаються настільки часто, що вимагають невеликого обговорення. Ця форма if-виразів являється найбільш узагальненим способом написання виразів вибору з-поміж багатьох напрямків. Умови (вираз) розглядаються по-черзі, якщо якась з умов справджується, твердження, пов'язані з цією умовою буде виконано, і це покладе кінець усьому ланцюжку. Звичайно, що код кожного виразу може складатися як з одного рядка, так і з групи, включеної у фігурні дужки.
Останнє else має справу з випадками, що не збіглися з жодною умовою вище, це те що виконуватиметься поза вибором, якщо всі умови не справдилися. Іноді може не бути жодної дії яку потрібно би було виконати поза вибором, у таких випадках завершуючє
else
твердження
можна не включати, або його можна використати для вловлювання помилок на кшталт «неможливих» умов.
Для ілюстрації вибору у трьох напрямках, ось функція двійчастого пошуку, яка виявляє чи певне значення x з'являється у масиві v. Елементи v мають знаходитись у порядку зростання. Функція повертає позицію (число між 0 та n-1), якщо значення x знайдено у v, і -1 — якщо ні.
Двійчастий пошук спочатку порівнює (введене) значення x із середнім елементом масиву v. Якщо x менший за значення посередині, тоді пошук зосереджується на нижній частині списку, якщо навпаки — більший, то на верхній. В обох випадках, наступним кроком буде порівняння xіз середнім елементом вибраної половини. Цей процес поділу відрізків на два продовжується доти, доки значення не буде знайдено, або діапазон виявиться порожнім.
/* binsearch: знаходить x серед v[0] <= v[1] <= ... <= v[n-1] */
int binsearch(int x, int v[], int n)
{
int low, high, mid;
low = 0;
high = n - 1;
while (low <= high) {
mid = (low+high) / 2;
if (x < v[mid])
high = mid - 1;
else if (x > v[mid])
low = mid + 1;
else /* знайдено збіг */
return mid;
}
return -1; /* збігу немає */
}
Фундаментальним рішенням є, чи x менший, більший, чи рівний середньому елементу v[mid]при кожному поступі; це дуже властиво else-if.
Вправа 3-1. Наш двійчастий пошук здійснює дві перевірки всередині циклу, тоді як однієї може вистачити (за умови додаткових перевірок поза циклом). Напишіть версію із одним тестуванням всередині циклу і порівняйте різницю в швидкості виконання програми.
